SIP API Developer's Guide

Chapter 2 Oracle Solaris SIP Internals

The following sections describe various components of the stack in detail. The interfaces described in this section are internal to the library unless otherwise specified.

SIP Stack Initialization

An application initializes the stack before performing any other tasks. The initialization parameters can be broadly subdivided into the following four types:

After initializing the stack, an application creates requests by using the interfaces that are provided by the header management layer. Applications send requests by using the message formatting layer's interface. Applications receive incoming requests and responses after initializing the stack and pass the requests and responses to the stack for processing.

Generic Stack Parameters

SIP version

SIP_STACK_VERSION defines the SIP stack version.

Stack flags

SIP_STACK_DIALOGS instructs the stack to maintain dialogs. If this flag is not set, the stack does not maintain any dialog information.

Upper Layer Registrations

Upper layer receive routine

The stack uses this function to deliver SIP messages to the application. Applications must register this routine.

Application specific timeout and un-timeout routines

The application can define timeout and un-timeout routines for the stack to use. Applications must define both routines or neither routine. If the application does not define these routines, the stack uses the built-in timeout routines.

Transactions error notifications

The application can register an error notification routine. The stack invokes the error notification routine when the transaction layer encounters a network error while the transaction layer is sending a SIP message. If the application does not register an error notification routine, the stack will not send notifications in case of a network error.

Dialog delete notification

If the stack is configured to maintain dialogs, the application can register a callback routine. The stack invokes the callback routine when a dialog is deleted. If the application does not register a callback routine, the stack will not send notifications when a dialog is deleted.

Transaction state transition notifications

An application can register a routine that is invoked when a transaction changes state. The routine is invoked with the transaction handle, the message that resulted in the transition, and the previous and current states. If an application does not register this routine, the stack will not send notifications when a transaction changes state.

Dialog state transition notifications

If the stack is configured to maintain dialogs, the application can register a callback routine. The stack invokes the callback routine when a dialog changes state. The callback routine is invoked with the dialog handle, the message that resulted in the transition, and the previous and current state. If an application does not register this routine, the stack will not send notifications when a dialog changes state.

Connection Manager Interfaces

Applications must register the interfaces in this section with the SIP library.

Send routine

The stack calls this routine to send SIP messages.

Hold/Release functions

The application must provide functions to increment and decrement reference counts on a connection object.

Connection attributes.

The application must register functions to query the following attributes of a connection object:

is_stream

The connection is a byte-stream

is_reliable

The connection is reliable

remote address

Remote endpoint information

local address

Local endpoint information

transport

The transport type

The application can register the following timer attributes that the API uses to obtain timer values for a connection object:

Timer1

This value defines the RTT estimate

Timer2

This value defines the maximum retrieval interval for non-INVITE requests and INVITE responses

Timer4

This value defines the maximum duration that a message remains in the network

TimerD

This value defines the wait time for response retransmits

If an application does not specify values for these timer attributes, the stack uses default values.

Custom Header Table

An application can register a table of custom headers with the library. The table must include parsing functions for those headers. The application can include standard headers in the table. If the application includes standard headers in the custom header table, the stack uses the parsing functions that are defined in the custom header table instead of the built-in parsing functions.

Header Management Layer

This layer provides interfaces that enable an application to create, parse, modify, and examine SIP messages. A SIP message consists of a start line, a variable number of headers, and an optional message body. The start line and headers are terminated by a single Carriage Return/Line Feed (CRLF) character. The message body is preceded by an empty line that contains only a CRLF. SIP allows the combination of multiple headers of the same name type under one header. A single header can have multiple values. A header value consists of a number of descriptive parameters and an optional name-value pair. The Backus-Naur Form (BNF) for a VIA header is defined in section 24 of RFC 3261 as the following:

via        = ("Via" / "v") HCOLON via-parm * (COMMA via-parm)
via-parm   = sent-protocol LWS sent-by *(SEMI via-params)
via-params = via-ttl / via-maddr / via-branch / via-received / via-extension

The values of via-parm are descriptive parameters. The values of via-params are name-value pairs. The combination of via-parm and via-params constitute the value of the header. The stack maintains a sip_header_name_value_t structure for each SIP header. In the case of a VIA header, the structure is sip_via_value_t. A header can have multiple values separated by a comma. A header with multiple values lists a sip_header_name_value_t structure for each value. For example, a SIP header with multiple VIA headers would list multiple sip_via_value_t structures, with a corresponding via-parm parameter for each structure.

The SIP message contains a list of all the headers and the content. The stack uses the receive function that the application registered with the stack to pass the message to the application.

SIP messages implement a reference count mechanism. An application can hold a reference to a SIP message by using the sip_hold_msg() function. An application can release a reference to a SIP message by using the sip_free_msg() function. The stack destroys messages whose reference count drops to zero when the application calls the sip_free_msg() function.

The stack can forward a SIP message to a SIP entity directly or after modifying the message. The stack cannot modify a message after sending it.

typedef struct sip_header_general {
        char                    		*sip_hdr_start;
        char                    		*sip_hdr_end;
        char                    		*sip_hdr_current;
        sip_parsed_header_t     	  *sip_hdr_parsed;
}sip_hdr_general_t;

typedef struct header_function_table {
        char            	*header_name;
        char            	*header_short_name;
        int             	(*header_parse_func)(struct sip_header *,
                           	    struct sip_parsed_header **);
        boolean_t       	(*header_check_compliance)(struct sip_parsed_header *);
        boolean_t       	(*header_is_equal)(struct sip_parsed_header *,
                           	    struct sip_parsed_header *);
        void            	(*header_free)(struct sip_parsed_header *);
}sip_header_function_t;

The SIP header contains a sip_header_general_t structure that provides pointers to the start and end of the header string. The structure also has a pointer to the current position in the string that a parser can use when it is processing the header. When the stack parses a header for the first time, it caches the result in sip_hdr_parsed for future reference.

SIP allows the inclusion of custom message headers that are not defined in RFC 3261. An application can provide a table of custom headers and parsing functions to support those custom headers. The application provides an array, sip_header_function_t, for this purpose. The table entries that the application provides override the header entries that are in the default function table.

The library maintains a table that has an entry for each header that is defined in RFC 3261. This table specifies the header name in long form, as well as the compact form where present. The table also specifies the associated parsing function and any other functions listed in the sip_header_function_t array.

When the application deletes a header, the value of the sip_header_state attribute is set to SIP_HEADER_DELETED. Existing references to the header remain, but new lookups will not return the deleted header. The library frees all of the message headers at the same time that the library frees the message.

The following structure represents a parsed SIP header:

typedef struct sip_parsed_header {
        int              	sip_parsed_header_version;
        struct sip_value 	*value;
        sip_header_t    	sip_header;
}sip_parsed_header_t;

The member value is a pointer to a sip_header_name_value_t structure. The stack sets the structure as a result of parsing the SIP header. The first field of the sip_header_name_value_t structure is always a sip_value_t structure. The following code defines the sip_value_t structure:

typedef struct sip_value {
        int                        sip_value_version;
        void                       *next;
        sip_param_t                *param_list;
        sip_value_state_t          value_state;
        sip_parsed_header_t        *parsed_header;
        char                       *value_start;
        char                       *value_end;
        sip_str_t                  *sip_value_uri_str;
        sip_uri_t                  sip_value_parse_uri;
}sip_value_t;

The values in the param_list array is the list of parameters for the value that the system is parsing. The following code defines the sip_param_t structure:

typedef struct sip_param {
        sip_str_t       		param_name;
        sip_str_t       		param_value;
        struct sip_param 		*param_next;
}sip_param_t;


typedef struct sip_str {
        char 			*sip_str_ptr;
        int 			sip_str_len;
}sip_str_t;

When the library deletes a value from a header, the library sets the value of the sip_header_state attribute to SIP_HEADER_DELETED_VAL. The library also sets the value of the value_state attribute to SIP_VALUE_DELETED. Existing references to the value remain. New lookups ignore the deleted value.

Traverse the following hierarchy to get to a value from a SIP message:

sip_msg_t -> sip_header_t -> sip_parsed_header_t -> sip_<header_name>_value_t

Using the free function that corresponds to a given structure also frees all of the structures that are under it in the hierarchy.

The library performs lazy header parsing. The library only parses or checks for compliance headers whose values are used by the application. This behavior complies with section 16.3 of RFC 3261, Request Validation and Reasonable syntax check. When the parser encounters an invalid value, the parser sets the value_state attribute to SIP_VALUE_BAD.

Writing Parsers For Custom Headers

An application can define custom headers or specialized versions of standard headers. Applications that define headers must register parsing functions with the library to support those headers. Use the following structures to write parsing functions:

sip_header_general_t

This structure provides the start and end of a header within a message. The parser sets the parsed header element to point to the sip_parsed_header_t structure that the parser allocates.

sip_parsed_header_t

The parser allocates and assigns this structure. The parser passes this structure back to the caller.

sip_value_t

The application defines the value of this structure depending on the header. The first element of the structure must be sip_value_t. The parser creates a linked list of values if multiple values exist. The parser sets the value of the value field in the parsed header to the first value. The application must provide any access function that value members require.

Transaction Management Layer

The transaction management layer creates and maintains transaction states for both clients and servers. The transaction management layer complies with Section 17 of RFC 3261.

The stack maintains a hash table of transactions. The index of this hash table is an MD5 hash. For messages that comply with RFC 3261, the MD5 hash is the hash of the branch ID. For messages that comply with RFC 2543, the MD5 hash is of a combination of the branch ID, Call-ID, From, To, and Cseq fields. The stack uses the branch ID in the topmost VIA header to identify the RFC that a message complies with. Messages that comply with RFC 3261 prefix their branch ID with the string z9hG4bK.

The library initializes the transaction layer as part of the stack initialization. The library also initializes the transaction error and state transition callback functions if the application provides them.

Transaction Creation And Maintenance

The stack scans the transaction hash table for an existing transaction when the stack receives incoming requests and responses. If a transaction that matches an incoming response does not exist, the stack handles the transaction statelessly. When an incoming request requires the stack to create a transaction, the application notifies the stack of the need to create a transaction when the application sends the response. In both cases, the stack passes the message to the application. If a request matches a transaction, the stack considers the request a retransmission and the transaction layer retransmits the last response. The stack drops the incoming request and does not pass the request to the application. The CANCEL request and an ACK for a non-2xx response are exceptions to this behavior. A CANCEL request matches the INVITE transaction that it is cancelling. An ACK for a non-2xx response also matches the INVITE request whose response the ACK is acknowledging. In these two cases, the request is not a retransmission and the stack sends the request sent to the application. If a response matches a transaction, the stack processes the response and statefully passes the message to the application.

The stack only creates a transaction for an outgoing response when the application requests the transaction creation by using the sip_sendmsg() function. The stack only creates a transaction for outgoing requests if the application requests the transaction creation by using the sip_sendmsg() function. The stack caches the transaction creating message and the last message that was sent on a transaction. This cache facilitates retransmissions and the processing of retransmitted requests. The stack initializes timers A, B, D, E, F, G, H, I, J and K for new transactions. See Appendix A for more information about timers.

When a message causes a transaction to change states, the stack invokes a callback function if the application registered a callback function with the stack.

Transaction Creation and ACK Signal Generation

A SIP entity must send an ACK signal for each final response that the SIP entity receives to an INVITE request. The procedure for sending the ACK signal depends on the type of response. For final responses between 300 and 699, the transaction layer handles the ACK signal processing. For 2xx responses, the application generates the ACK signal. In all cases, the application receives the response.

Transaction Deletion

The MD5 hash that is stored in the transaction is used to look up and delete the transaction from the hash table. When a transaction enters a terminated state, the transaction management layer destroys the transaction if the reference count drops to zero. If the application registered a callback function during initialization, that callback function notifies the application when the transaction changes state. If the application needs to use the transaction in the future, it must hold a reference to the transaction by using the sip_hold_trans() function to prevent the stack from destroying the transaction. After using the transaction the application must release the reference by using the sip_release_trans() function.

The four terminated states for transactions are listed below:

Transaction Lookup

An application can look up a transaction for a SIP message by using the sip_get_trans() function. A successful lookup returns the transaction and increments the transaction's reference count. The application must release the transaction by using the sip_release_trans() function after use.

Transaction Timers

The transaction layer maintains a set of timers for timing out transactions or sending retransmissions. Appendix A lists these timers. If the application initializes timeout reporting, the transaction layer notifies the application when a transaction times out.

Transaction And Network Errors

The transaction layer uses a cached connection object to send messages. In the event of a network error, the transaction layer releases the connection object and calls the error callback function, if the application provided one. If the callback function not does return a value of 0, or if the application does not provide a callback function, the stack terminates the transaction and frees the associated resources.

Dialog Management Layer

A dialog represents a persistent peer-to-peer relationship between two user agents. A dialog facilitates the sequencing of messages between the user agents and the proper routing of requests between the user agents. The dialog represents a context in which to interpret SIP messages. A dialog is uniquely identified by a dialog id, which is a combination of the Call-ID, From, and To tags.

Dialog use is optional. You can create dialogs using INVITE and SUBSCRIBE as methods. A response to an INVITE in the 101–299 range combined with a To tag creates a dialog. A response to a SUBSCRIBE in the 200–299 range or a NOTIFY request creates a dialog. Provisional responses create dialogs that are marked as EARLY dialogs. Final responses in the 200–299 range create dialogs that are marked as CONFIRMED dialogs. An EARLY dialog becomes a confirmed dialog when the stack receives a response in the 200–299 range. If the application registers a callback function with the stack to send notification when a dialog changes state, the stack invokes that function.

An application can perform dialog management internally, or delegate dialog management to the stack. If the stack manages dialogs, the stack automatically creates and maintains dialogs. The stack also delivers any dialogs, matching any incoming messages to the application. If the stack is not managing dialogs, there is no dialog-related interaction between the application and the stack.

UAC Dialog Creation

For clients, if the stack is configured to maintain dialogs, the stack creates a dialog for an incoming response to a dialog creating request. Dialog creating requests are either INVITE or SUBSCRIBE. For SUBSCRIBE requests, a corresponding NOTIFY request also creates a dialog. The stack can create multiple dialogs for a single request if the application specifies multiple dialogs by using the sip_sendmsg() function.

For servers, if the stack is configured to maintain dialogs, the stack creates a dialog when the application sends a response to the dialog creating request. The stack also creates a dialog when it sends a NOTIFY request in response to a SUBSCRIBE request.

UAS Dialog Creation

When the stack receives a dialog creating request, it creates a partial dialog by using the request. The stack sends the partial dialog to the application along with the request. The stack completes the dialog when the application sends a response. Because the stack does not insert the partial dialog into a hash table, the stack does not return partial dialogs as results of a lookup. The stack starts a timer when it creates the partial dialog. The timer's duration is set to the duration of an INVITE transaction timeout. If the UAS does not respond during this time interval, the stack deletes the partial dialog. If the application registers a callback function with the stack for dialog deletion notifications, the stack invokes the callback function before deleting the partial dialog. The stack also deletes the partial dialog if the response is outside the 100–299 range.

Dialog Caching

For an incoming message, the stack sends any existing dialog, along with the message to the application. When the application's receive function returns, the stack decrements the reference count to the dialog. If the dialog's reference count is zero after this decrement, the stack destroys the dialog if the dialog is in a terminated state. If the application needs to use the dialog in the future, it must hold a reference to the dialog by using the sip_hold_dialog() function to prevent the stack from destroying the dialog After using the dialog the application must release the reference by using the sip_release_dialog() function.

Dialog Termination, Deletion, and Notification

When the stack processes a SIP message, the processing can result in the termination of the dialog. A dialog marked as EARLY terminates if the final response is not in the 200–299 range, or if no response arrives. The termination mechanism for confirmed dialogs are method specific. The BYE method terminates an INVITE dialog and the session that is associated with it. An application can explicitly delete a dialog by using the sip_delete_dialog() function.

When the stack terminates a dialog, it notifies the application if the application has registered a callback function with the stack for this purpose.

Message Formatting Layer

The message formatting layer represents the message in the form that the next component or layer requires.

If the incoming message arrives over TCP, the message formatting layer breaks the byte stream at message boundaries. The message formatting layer parses the message in order to represent the message as a SIP message.

When the application is sending a message, the message formatting layer copies all of the message's headers and contents into a contiguous buffer before delivering the message to the application's send function.

Receiving Messages

The application sends the incoming message to the stack along with the connection object. If the transport is TCP, the message formatting layer breaks the byte stream at message boundaries. The SIP message's Content-Length header determines the message boundaries. A Content-Length header must be present in every message delivered over TCP in order to comply with RFC 3261. If the stack receives a message over TCP that does not contain a Content-Length header, the stack's behavior is unspecified.

The message formatting layer holds excess data that is not part of the current message for use with the next packet. When the system closes or reuses a connection, the connection manager must notify the stack. After receiving the notification, the stack frees the data and resources that the stack allocated to that connection. The stack delivers the message to the application by calling the receive function. The application registers the receive function with the stack during stack initialization.

Sending Messages

An application sends a SIP message by using the sip_sendmsg() function with the connection object and any message specific flags. The message formatting layer adds a Content-Length header and a line that contains only a Carriage Return/Line Feed (CRLF) character to the message and delivers the packet to the next layer. The message formatting layer copies all of the SIP message's headers and contents into a contiguous buffer before delivering the message to the application's send function. The connection manager provides the send function.

Connection Manager

The connection manager provides I/O functionality. The connection manager is not part of the library but interacts with the stack using well defined interfaces. This section describes the usage model of the connection manager, its interface with the stack, and the requirements that are imposed by the library. The connection manager must register the following mandatory interfaces with the stack as part of stack initialization:

int          sip_conn_send(const sip_conn_object_t, char *, int);
void         sip_hold_conn_object(sip_conn_object_t);
void         sip_rel_conn_object(sip_conn_object_t);
boolean_t    sip_conn_is_reliable(sip_conn_object_t);
boolean_t    sip_conn_is_stream(sip_conn_object_t);
int          sip_conn_remote_address(sip_conn_object_t, 
                    struc sockaddr *, socklen_t *);
int          sip_conn_local_address(sip_conn_object_t, 
                    struct sockaddr *, socklen_t *);
int          sip_conn_transport(sip_conn_object_t);

If the application uses timer values that are specific to a connection object, the application must register the following interfaces to provide those values for Timer 1, Timer 2, Timer 4, and Timer D:

int		sip_conn_timer1(sip_conn_object_t);
int		sip_conn_timer2(sip_conn_object_t);
int		sip_conn_timer4(sip_conn_object_t);
int		sip_conn_timerd(sip_conn_object_t);

A connection object represents a connection. A connection is identified by the local endpoint, the remote endpoint, and the transport.

The library requires that the first element of the connection object is a void pointer. The stack reserves this void pointer for its own use. The application must initialize each connection object by calling the sip_init_conn_object() function before using the object. The connection object is opaque to the stack.

Connection Object

When the stack initializes a connection object, the stack sets the first element of a connection object to point to a structure. The structure tracks transactions that cache the connection object. The stack also uses the structure to break a TCP stream at the message boundaries.

Because the library does not interact with listening endpoints, it does not impose any restriction on creating or maintaining listening endpoints.

The connection manager maintains a unique connection object for each remote address, local address, and transport tuple. This behavior is particularly important for UDP, where the underlying endpoint does not uniquely identify a local/remote endpoint pair.

Caching a Connection Object

The stack caches connection objects for use by the transaction layer. The stack increments the reference count on the connection object to prevent the connection object from being freed by the system when the connection object is in use by the stack. When the transport protocol is TCP, the stack adds a reference count on the connection object when the stack allocates resources to break the TCP stream at message boundaries. The connection manager registers the sip_hold_conn_object() and sip_rel_conn_object() functions for the purposes of holding and releasing connection objects.

Freeing a Connection Object

The connection manager can close a connection at any time. The connection manager cannot free the connection object while references to the connection object exist. To free a connection object that has existing references, the connection manager calls the sip_conn_destroyed() function. The library provides this function. When the connection manager invokes the sip_conn_destroyed() function, the stack locates and terminates the transactions that are caching the connection object. The stack frees any resources that had been allocated for TCP handling after terminating the transactions.

Sending Messages

When an application sends a message by using the sip_sendmsg() function, the stack calls the sip_conn_send() function. The connection manager registers the sip_conn_send() function with the stack. The stack calls the sip_conn_send() function after the stack processes the outbound message. In case of a network error, the application can define the response of the connection manager, including returning an error. The connection manager's behavior must be consistent for all subsequent calls that use the same connection object. The connection manager can create a new connection to update an existing connection. The connection manager must flush any stale TCP data that is left in the connection object as a result of breaking the TCP stream at message boundaries. The connection manager uses the library function sip_clear_stale_data() to flush any stale TCP data. The Connection Manager must not change the transport type of a connection object.

Receiving Messages

The Connection Manager delivers new packets to the stack by calling the sip_process_new_packet(). After processing the message, the stack calls the receive callback function to pass the message to the application. The application must register the receive callback function with the stack at initialization. The stack frees the message and releases the connection object when the application's receive function returns. To queue a message, an application must manage the reference counts on the connection object as described in the Caching Connection Objects section. The application uses the sip_hold_msg() and sip_free_msg() functions to manage a SIP message's reference counts.

Transaction Layer and I/O Errors

The transaction layer uses a cached connection object to send messages such as retransmitting requests or responses. In the event of a network error, the transaction layer releases the connection object. If the application registered a callback function for transaction error notification, the transaction layer invokes that function. If the application did not register a callback function for transaction error notification, or if the callback function returns a nonzero value, the stack terminates the transaction. If the callback function returns a value of zero, the stack does not release the cached object.

Timer Management Layer

The timer management facility mimics the timeout and un-timeout functionality of the UNIX kernel. A thread maintains a list of timer objects sorted by time. The thread invokes the callback function at the interval that the timer specifies.

The stack initializes the timer layer during stack initialization. Initializing the timer layer starts the timer thread.

The sip_timeout() function takes the following arguments:

The return value of the timeout function is a timeout identifier. The tack can cancel a timeout by passing the timeout ID of the timeout to cancel to the sip_untimeout() function

Generating Call-ID, From and To tags, Branch-ID and Cseq

The library provides the sip_guid() function to generate unique identifiers for the Call-ID, From, and To tags. The stack generates the identifier by combining the upper 32 bits that are returned by the gethrtime() function with a 32–bit random number from the /dev/urandom pseudo-device. The stack randomly replaces numbers in the result with alphabetic characters. The applicatioon is responsible for freeing the string that is returned by the sip_guid() function.

The library provides the sip_branchid() function to generate Branch-ID fields. If the application invokes the sip_branchid() function without a SIP message, or if the SIP message does not have a VIA header, the stack forms a Branch-ID field by using the sip_guid() function. If the SIP message already has a VIA header, the stack generates the Branch-ID by using the MD5 hash of the following fields:

All branch identifiers begin with the string z9hG4bK. This behavior complies with RFC 3261. The application is responsible for freeing the string that is returned by the sip_branchid() function.

An application can use the sip_get_cseq() or sip_get_rseq() functions to obtain the initial sequence number. The sip_get_cseq() and sip_get_rseq() functions use the 31 most significant bits of the value that is returned by the time(2) function call.

Multithreading Support

The library is completely multithreaded with respect to handling headers and header values. Multiple application threads can work on the same header of a message. When the stack or the application deletes a header or one of the values in a header, the stack marks the header as deleted. Headers marked as deleted are not available via lookups.

Calling the sip_free_msg() function to free a SIP message deletes the message if the reference count for the message falls to zero. You can increment the reference count on a message by using the sip_hold_msg() function.