NkSIP hides most SIP complexity from the developer, make it easy to build complex, robust and scalable SIP applications, even with basic SIP knowledge. But it is necessary to know some basic SIP concepts to use NkSIP. We won't try to describe SIP here (it would be difficult even to scratch the surface of the first of the RFCs), but we will try to explain the way NkSIP uses some basic SIP concepts in order to better understand how to use it.
A Service represents a SIP entity started by NkSIP. When starting a service, you configure some basic aspects and the new SIP element is started in the network. From this moment, it can send requests and receive them from other SIP elements. NkSIP allows you to start any number of services simultaneously, as long as they don't listen on the same ip and port or url resource.
You can develop any kind of SIP application with NkSIP. This includes endpoints, stateful and stateless proxies, registrars and redirect servers, B2BUAs or any combination of the above, all at the same time, in the same service or in different ones.
Each service starts listening on one or more sets of transport, ip address and port. For example, you could start a app1 service, which could be a proxy server listening on 192.168.0.1:5060 using protocols UDP and TCP, and on 192.168.0.1:5061 using TLS, and another one called app2 behaving as a B2BUA could be started listening on any other ip and port of the host. For websocket (WS and WSS transports), services can share the same ip and port, and the request is routed to the right service depending on the url.
When starting a service, you can optionally supply a callback Erlang module for it. There is a number of callback functions this module can implement. Each of them has an default behaviour, so all of them are optional. If you don't plan to receive SIP requests (only sending them) you don't need to provide any callback module.
You start a service calling nksip:start/2
. Any erlang term can be used as a name, but NkSIP will generate an atom as internal name for the service. In most API calls you can use any of them.
Under the hood, the service is a standard gen_server erlang process, and you can use it with standard functions like gen_server:call/3
, etc. The internal name is also the registered name for this process.
SIP is all about sending specific SIP messages (requests), and receiving one or more messages in response for them (responses). Any SIP element must behave, at the same time, as a client (uac in SIP terminology) sending requests and receiving responses, and as a server (uas), receiving requests and sending responses.
There are a number of SIP request types (INVITE, OPTIONS, etc.), and each corresponding response will have a HTTP-type code (100-699). Responses of type 1xx are provisional, and the rest are final. 2xx responses denote a successful processing, 3xx denote a redirect proposal, 4xx indicate an error processing the request, 5xx a server error and 6xx a global error.
In NkSIP you can start sending requests using the functions in nksip_uac
module, such as options/3
, invite/3
etc., and the response will be received as part of the function's return value. For example, if you send and OPTIONS request, the called party will probably reply with a 200 OK response containing, among other things, the codecs it supports.
Your application will also start receiving requests sent from other SIP endpoints or proxies, and NkSIP will then call the corresponding function in your callback module. Depending on the value your function returns, a specific SIP response will be generated and sent. For example, if someone sends you an INVITE, NkSIP will call sip_invite(Request, Call)
in your callback module (if this function is not defined, the default implementation in nksip_callbacks
module would be used). You could answer {reply, busy}
to send a standard 486 Busy response, and NkSIP will generate all required SIP headers and send back the response.
SIP offers two possibilities for sending requests and receiving responses: inside a transaction or without transaction.
A SIP transaction is a piece of state created by a SIP endpoint acting as a client when the request is sent, and it is destroyed soon after a final response is received, or it is created by a SIP endpoint acting as a server when a request is received, and it is destroyed after a final response is sent. Among other things, transactions take care of message retransmissions automatically.
Most SIP requests are usually sent and received inside transactions, but in some cases it makes sense to send or receive requests without a corresponding transaction. As no additional state is stored, the application scales better. NkSIP request generating functions such as nksip_uac:invite/3
or nksip_uac:options/3
always use transactions. But you have the option to process incoming requests statelessly (using responses process_stateless
, reply_stateless
or proxy_stateless
in your sip_route/5 callback function), mainly for failed authentication responses (or to test the maximum speed NkSIP is able to process messages). They are also used if you decide to behave as a stateless proxy for any incoming request.
If transactions are no used, retransmissions and forked responses will not be detected, and they will look like brand new requests to the application. NkSIP won't send retransmissions either.
Except for INVITE, SIP transactions should be very short-lived. They should complete quickly (bellow half a second) or retransmissions will start to be sent. Final responses for INVITE transactions can last for seconds or even minutes (as the user must usually reply to the invite manually), but even in this case provisional responses should be sent quickly.
A SIP dialog represents a long-term relationship between two endpoints, usually lasting for the duration of a call or subscription.
A dialog can host several usages simultaneously: zero or one INVITE usage, and any number of SUBSCRIBE usages, simultaneously. The first usage creates the dialog, and it is destroyed after the last usage is removed.
A successful response to a INVITE request creates a INVITE usage, that is maintained until a BYE request is received. Any starting call will usually create a new dialog (it can actually create several dialogs, but NkSIP will automatically send BYE to all but the first one). When the call is hung up, a BYE is usually sent and NkSIP destroys the usage.
New requests can be sent inside the newly created dialog. If no new request is sent or received during a specific period of time, NkSIP would also destroy the usage. You should refresh the usage periodically (for example calling nksip_uac:refresh/3
or sending a new in-dialog request).
A successfull SUBSCRIBE followed by a NOTIFY request creates a SUBSCRIBE usage, and it is maintained until a NOTIFY with status terminated is received or the subscription expires. You should refresh the usage sending new SUBSCRIBE requests.
When a dialog is created, destroyed or updated the corresponding function in your callback module is called. You can use these calls to know about the dialog current state, for example for billing purposes. Stateless proxies don't generate or process dialogs.
INVITE SIP requests usually carry a body describing a session proposal, using SDP protocol. The remote party can reply with its own SDP, and a new session is then established (audio, video or any other class), associated to the corresponding dialog. During the dialog lifetime, any of the parties can send a new INVITE (it would be what is commonly known as a reINVITE) or UPDATE with a new SDP to modify the current session (for example, to put the call on hold).
When, inside a dialog, NkSIP detects that a session has been established, modified or terminated, it calls the corresponding function in the callback module. You can use this callback to discover the codecs being used, if a call has been put on hold, or the RTP and RTCP ips and ports been used.
NkSIP is a pure SIP framework and, as such, has no RTP processing capability by itself. This means it cannot decode any codec, put the audio on the speaker, save the call audio on disk or host an audio conference. This functions are usually done by a SIP media server.
If you are developing a SIP proxy, you won't usually want to do any media processing. If you are developing an endpoint or an B2BUA, you can pretend to have media processing capabilities using a media server as a backend, for example inviting the media server before answering the call and sending the media server's SDP as if it were generated by ours.
You can use the functions in SDP API to access, create or modify SDP bodies.
You can send a new subscription requirement to a server sending a subscribe request. You should select an event package supported at the server. It the remote party accepts the request, it will start sending NOTIFYs requests any time it wants to, and NkSIP will call your callback sip_notify/2 for each one. The body of the NOTIFY will have the meaning defined in this specific event package. You should send a new SUBSCRIBE before the subscriptions expires.
If you are defining a server, you indicate in the service's config the event packages you support, and NkSIP will call your callback sip_subscribe/2 when a new valid SUBSCRIBE arrives. If you accept it, you should call inmeditaly nksip_uac:notify/2 to send a NOTIFY, and after that, any time you want to. You can also terminate the subscription at any moment.
NkSIP will auto expire the subscriptions after the time proposed in the Expires header (or config parameter nksip_expires
if not present), and adding the offet time defined in config parameter nksip_expires_offset
).
There many places in NkSIP where you must SIP Uris. For example, to send a request to a remote host, you use a SIP uri:
nksip_uac:options(my_app, "<sip:sip2sip.info>", [])
In this example, you could also have used sip:sip2sip.info
, but, according to RFC3261, you should use the enclosing <
and >
if your are using uri parameters or headers:
<sip:host;transport=tcp?user-agent=my_user_agent>
If you don't enclose them, the transport uri parameter and user-agent header will not be part of the request uri, but external parameters and headers (some common external parameters are used in SIP, like <sip:user@host>;tag=abc
.
You can use the SIP uri to include headers to be included in the request or proxy request, but they must be escaped, using for example http_uri:encode/1
. You can use the uri parameter method
to specify the method and body
for the body (also escaped).
When specifing Route uris, you should nearly ever use the lr
uri parameter.
There are several situations in any SIP enabled application where we must send to the other party a SIP URI showing the transport protocol, ip address and port where we are currently listening to receive new requests. A Contact header is used for this. For example, when sending a REGISTER we must indicate to the registrar where it must send any request directed to us. When starting a dialog with INVITE we must inform the other party where to receive in-dialog requests.
NkSIP will try to automatically generate a correct Contact header for you, using the same transport, ip and port used to send this specific request. For example, if we have a service listening on 192.168.0.1:5070 for UDP and TCP, and 192.168.0.1:5071 for TLS, sending a REGISTER to the URI <sip:registrar.com;transport=tcp>
, will generate a Contact header like <sip:192.168.0.1:5070;transport=tcp>
.
If the listening address of the selected transport is all (meaning "listen on all interfaces"). NkSIP will try to find, among all the ip addresses of the host, which is the best one to use. It uses a sorted list of network cards (specifically eth0
, eth1
, en0
and en1
) fetching the ip address from the first active network card. If none is available it will get any ip from the host.
In some circumstances, it makes sense to override NkSIP automatic calculation of contacts and provide a specific one, for example if we want to offer a host name instead of an ip (typically a host resolving to several different ips in different hosts), or to force the use of a specific IP of the host or a specific transport.
There are two different ways to include behaviours in NkSIP: Services and Plugins.
Services are the easier way. They are fully described in the documentation, and should be used for nearly all user SIP applications. In the future, it will possible to write Services in other languages than Erlang.
Plugins are designed as a way to add functionality to NkSIP, useful for many services. They must be written in Erlang, work very closely to the core and can make NkSIP fail when processing a call if they have a bug. When starting any application, you tell NkSIP all the plugins you want to use for your it. Each one can have a different set of active plugins.
Plugins are typically used for event packages, implementing specific RFCs, adding new APIs to manage any external thing (like database access) or any other common, low-level functionality.