Skip to main content

Creating a New Endpoint: Lifecycle of a Session

0. About

FreeSWITCH endpoints are designed to create, handle, and destroy media streams. This page describes sessions and channels from the point of view of writing an extension module for FreeSWITCH.

1. Endpoints

FreeSWITCH manages the establishment and destruction of linked sessions between entities. For those of us from a telephony background, FreeSWITCH performs the functions of a PBX or central office. The following document is written with respect to voice calls, but the principles apply to the other types of connections that FreeSWITCH can manage.

The voice entities that we're most familiar with are simple telephones. The modules that handles these are called “endpoints". The most used endpoint modules are mod_sofia (SIP phones) and mod_skinny (Cisco phones).

As modules, they must have the following entry points to be loaded and unloaded:

-module_load()
-module_unload()

TODO Couldn't find direct references to module_load() and module_unload() in Creating New Modules, so check what this section is referring to.

Usually a module will also implement two interfaces, as arrays of callback functions. This is how FreeSWITCH communicates with the module.

An endpoint module deals a lot with sessions.

2. Sessions

A FreeSWITCH session is equivalent to a call-leg when the sessionis managed by mod_sofia.

A sessioncontains a channel and private data.

This diagram shows containment, when in fact pointers are used.

A session object owns the channel, so in a sense the session is the parent of the channel. The session also wraps the channel in some management overhead, including:

  • thread to run the code
  • memory pool
  • array of codecs
  • message queues
  • read/write data buffers

The session also owns a private object. The author of the module defines what this object contains and what it's used for. In almost all modules, this object is called session->tech_pvt. Some common elements of a tech_pvt are:

  • IP addresses involved in the communication channel
  • encryption keys for the communication channel
  • the external codecs involved in the media flow

3. Channels

The channel is the basic entity for one end of a FreeSWITCH call. Channel objects manage the flow of media to the outside world. Channels are where the most interesting things happen. The channel runs through states that represent the state of call setup, the media flow and the call tear down.

The most interesting members of a channel (see switch_channel.c) are:

  • name
  • uuid
  • direction
  • its session (wrapper)
  • state
  • caller_profile
  • flags
  • variables

3.1 state

The single most important channel member is the state variable, designed to handle the connection status. The state member is the one that most FreeSWITCH documentation references. This state runs through the sequence CS_NEW, CS_INIT, CS_ROUTING, CS_EXECUTE, CS_HANGUP, CS_REPORTING and CS_DESTROY. The transitions from one state to the other are handled gracefully by the internals of FreeSWITCH (switch_core_state_machine.c) unless you intercept these transitions. This state describes the highest level connection status of the channel/session.

The state variable is not directly what one would normally use in telephony. Because FreeSWITCH can handle many kinds of media, the state variable is designed to handle the connection status. You need also look at CF_RING_READY, CF_EARLY_MEDIA and CF_ANSWERED in order to determine the telephony state.

Here's a table to illustrate some details of the states:

StateNormal activitiesStandard processingYour module code should….
CS_NEWright after session creationnonechange to CS_INIT
CS_INITcreate any data structures required for the rest of the session/callchanges to CS_ROUTING
CS_ROUTINGinbound: dial tone, accepting digits, ringbackinbound: uses the caller_profile & the dialplan to pick a destinationinbound: your module will get a message SWITCH_INDICATE_ANSWERoutbound: your module should call switch_call_answer()
CS_CONSUME_MEDIAoutbound: ringing
CS_EXCHANGE_MEDIAoutbound: talking
CS_EXECUTEinbound: media flows; talkingnear_end release: your module should call switch_channel_hangup()far_end release: your module will get a callback on my_module_on_hangup()
CS_HANGUPChanges to CS_REPORTING*
CS_REPORTINGChanges to CS_DESTROY*
CS_DESTROYRelease any memory

3.2 running_state and callstate

The channel object also has two other state members (running_state and callstate) which are hardly used at all in the core of FreeSWITCH, and which you probably won't need. The running_state is just used in state transitions, usually holds the “previous state". The callstate is a more fine-grained version of the state, and it has its own getter and setter. If you chose to use the setter, it will fire an event. callstate includes CCS_DOWN, CCS_DIALING, CCS_RINGING, CCS_EARLY, CCS_ACTIVE and CCS_HANGUP.

3.3 caller_profile

Within the channel is a single caller_profile. This contains descriptions of both the caller and callee.

The internal sections of FreeSWITCH only use:

  • uuid
  • caller_id_name, caller_id_number, caller_extension
  • dialplan
  • destination_number

The channel flags hold extra state information above and beyond the basic 13 states that are held in the state variable. The interesting flags are:

FlagDescription
CF_ANSWEREDChannel is answered
CF_OUTBOUNDChannel is an outbound channel
CF_EARLY_MEDIAChannel is ready for audio before answer
CF_ORIGINATORChannel is an originator
CF_TRANSFERChannel is being transfered
CF_HOLDChannel is on hold
CF_GEN_RINGBACKChannel is generating it's own ringback
CF_RING_READYChannel is ready to send ringback
CF_BREAKChannel should stop what it's doing

3.4 Variables

A channel also has a hash of key-value pairs that can hold all kinds of interesting data related to the channel. These are used extensively by mod_sofia to bind things like sip_user_agent and sip_profile_name to a channel. These key-value pairs can be queried by either the dialplan or LUA processing. They can also be a handy way to accumulate data in preparation for issuing a FreeSWITCH event. See channel variables

4. How to Write a FreeSWITCH Endpoint

First follow the New Module Checklist. Earlier directions suggest using freeswitch/src/mod/applications/mod_skel to start off, but for endpoints you're better off starting with freeswitch/src/mod/endpoints/mod_reference.

module_load() is the first entry point called. Usually at this point you will inform the caller about your module, its callback interface arrays, and register any API extensions. You may also want to create a memory pool. You should read your config data, which by convention is XML, and buried somewhere in freeswitch/conf.

5. Handling calls from within your module

See Life Cycle of a Call.

5.1 Common tasks to both incoming and outgoing calls

Below is our current understanding. We are not core developers, so all is guessed --Sathieu 11:14, 18 February 2010 (UTC) – PatB 2015-09-25

For both outgoing and incoming calls, you must allocate the session. The core of FreeSWITCH will automatically allocate and bind a channel to your session. FreeSWITCH does not create the caller_profile; y_our code must allocate a caller_profile,_ and bind it to the channel. Y_ou probably also want to allocate a private data object, and bind it to the session._

Notice that in a normal call, the inbound/originating channel ends up in the CS_EXECUTE state, and the outbound/called channel ends up in the CS_EXCHANGE_MEDIA state. (Once we start playing with 'hold' and 'transfer', this statement gets muddied.)

If your module wants to shut down the call, just use switch_channel_hangup(). You will get a callback to your on_hangup() entry point and the call will shut down.

If the other end wants to shut down the call, you will get a SIG_KILL sent to your on_channel_kill() entry point. Shortly thereafter you will get a call to your on_hangup() entry point and the call will shut down.

Notes

[1] you don't need to create the channel.it is created inside the session

[2] at minimum, you should set the .name member of the channel

[3] or if you wish to abort, call switch_channel_hangup(channel)

[4] strangely, you only call s_c_s_thread_launch() on incoming calls; it's called automatically for you on outgoing calls (deep in switch_ivr.c)

5.2 Outgoing calls

Outgoing calls are created either by a call coming through the dialplan routing, or when you use the originate command in the console:

  • your_io_routines.outgoing_channel() is called
    • you get a pointer to the other call half (session/ channel)
  • create a new session (with switch_core_session_request()) and private data[1]
  • parse outbound_profile->destination_number to get called number
  • create a caller_profileand bind it to the session
  • qualify the session, attached channel and attached private data [2]
  • the channel is currently in CS_NEW; change channel state to CS_INIT
    • make the phone ring (and call switch_channel_ring_ready())
  • your_state_handler.on_init() is called
    • no need to do anything
    • the default handler will change channel state to CS_ROUTING
  • your_state_handler.on_routing() is called
    • no need to do anything
    • the default handler will change channel state to CS_CONSUME_MEDIA
  • when you decide the call is answered
    • call switch_channel_mark_answered(channel) [3] which will set the channel state to CS_EXCHANGE_MEDIA
  • your_state_handler.on_exchange_media() is called
    • and the call is up

5.3 Incoming calls

Incoming calls are initiated by your code. To start up a call

  • create a new session _(_with switch_core_session_request()) and private data [1]
    • create a caller_profile (with switch_caller_profile_new()) and fill in the ->destination_number
    • qualify the channel and private data [2]
    • create a caller_profile and bind it to the channel
    • start up the thread that manages the session (with switch_core_session_thread_launch()) [4]
    • change channel state from CS_NEW to CS_INIT
  • your_state_handler.on_init() will be called
    • no need to do anything
    • the default handler will change channel state to CS_ROUTING
  • your_state_handler.on_routing() is called
    • no need to do anything
    • if your caller_profile->destination_number has been set, the default handler will search the dialplan and start an application; if this is a telephone call, usually the 'bridge' application will start an outbound session toward the called party and change change this channel state to CS_EXECUTE
  • when the distant party answers
    • your_state_handler.on_receive_message() is called
      • filter the messages you get for SWITCH_MESSAGE_INDICATE_ANSWER
        • take any action that you need to start up the call

From the directory that you installed FreeSWITCH, you can simply type “make mod_my_module" to just remake your loadable module. In the FreeSWITCH console, you can enter load mod_my_module and unload mod_my_module as often as you want, fixing bugs between builds.

6. The Babel tower of the API

When trying to learn FreeSWITCH programming, you're going to be overwhelmed by the number of API methods that are available. Here's a list on the one's you're likely to need in session and channel management.

switch_core_session_request()allocate and initialize a new session
switch_core_session_get_channel()given a session, get the associated channel
switch_core_session_get_private()given a session, get the private data
switch_channel_get/set_state()used to coerce a channel state; usually the only one you will do is CS_NEW→CS_INIT
switch_core_session_alloc()allocayr space, usually for tech_pvt
switch_caller_profile_clone()on outgoing, clone the caller's caller_profile
switch_channel_get /set_caller_profile()bind a caller_profile to a channel
switch_channel_test_flag() switch_channel_set_flag_value()set/test CF_ANSWERED, CF_EARLY_MEDIA, etc
switch_channel_get/set_name()give your channel a name, like “superphone/1001"
switch_channel_ring_ready()ignored if the channel is already answeredif inbound, sends the message SWITCH_MESSAGE_INDICATE_RINGINGcalls switch_channel_mark_ring_ready()
switch_channel_mark_ring_ready()set CF_RING_READYsends a PROGRESS eventsets callstate=CCS_RINGINGsends message SWITCH_MESSAGE_RING_EVENT
switch_channel_pre_answer()ignored if the channel is already answeredif inbound, sends the messageSWITCH_MESSAGE_INDICATE_PROGRESScalls switch_channel_mark_pre_answer()
switch_channel_mark_pre_answer()set CF_EARLY_MEDIAsends a PROGRESS_MEDIA eventsets callstate=CCS_EARLYsends message SWITCH_MESSAGE_PROGRESS_EVENT
switch_channel_answer()ignores outbound calls or calls already answeredsends message SWITCH_MESSAGE_INDICATE_ANSWERcall switch_channel_mark_answered()
switch_channel_mark_answered()ignores calls already answeredsets CF_ANSWEREDsends CHANNEL_ANSWER eventsets callstate=CCS_ACTIVEsends message SWITCH_MESSAGE_ANSWER_EVENT
switch_channel_hangup()change channel state to CS_HANGUPsends the HANGUP eventsends SIG_KILL(1) to itself
switch_core_session_thread_launch()starts up the state machine; inbound only
switch_channel_direction()check inbound or outbound

There are several similar calls with the word "_perform_" in the name; these are just versions that include the filename/line number for debugging. Don't call those directly.

There are three pairs in the middle, with and without the "_mark_" in the name. You call

switch_channel_ring_ready()
switch_channel_pre_answer()
switch_channel_answer()

when you want to issue the SWITCH_MESSAGE_INDICATE_* messages into the message queue. If you're responding to an INDICATE message, you don't need to re-issue that message, so you would call these below:

switch_channel_mark_ring_ready()
switch_channel_mark_pre_answer()
switch_channel_mark_answered()

For example, if you just got a SWITCH_MESSAGE_INDICATE_ANSWER, you would only call the switch_channel_mark_answered() version. If you are working on something else, and you decide the call should be answered, call the non-_mark_ version switch_channel_anwer().

In practice, the standard state code does a pretty good job of managing channel flags, channel variable, and channel states. The only routines that you will probably need to call are

switch_channel_set_state() // once, during session creation

switch_channel_mark_answered() // when your device answers

switch_channel_hangup() // to shut down


Source code for those UML diagrams above:

using PlantUML syntax

Fig 1

session *-- channel
channel *-- caller_profile
session *-- tech_pvt

Fig 2

session *-- channel
channel *-- caller_profile
session *-- tech_pvt

session : thread
session : memory pool
session : codec[]
session: messages[]

channel: name
channel: uuid
channel: direction
channel: flags[]
channel: variables[]

caller_profile: destination_number
caller_profile: caller_id

tech_pvt: ip addresses
tech_pvt: other data that you may need

Attachments:

session.png (image/png)
full_session.png (image/png)