Kamailio is basically only a transaction stateful proxy, without any dialog support built in. Here the TM module enables stateful processing of SIP transactions ( by maintaining state machine). State is a requirement for many complex logic such as accounting, forking, DNS resolution.
Although most of the Kamailio module related description is covered here, I wanted to keep a separate space to describe and explain how Kamailio handles transactions and in particular, the Transaction Module.
Note: This article has been updated many times to match v5.1 since v3.0 from when it was written, if u see and outdated content or deprecated functions, please point them out to me in the comments. If you are new to Kamailio, this post is probably not a good starting point for you, instead read more on Kamailio. It is a powerful open-source SIP server here and has a widespread application in telephony.
Kamailio can manage stateless replying as well as stateful processing – SIP transaction management. The difference between the two is below
Stateful | Stateless |
stateful processing is per SIP transaction Each SIP transaction will be kept in memory so that any replies, failures, or retransmissions can be recognized | Forwarding each msg in the dialog without any context. |
Application understands the transactions , for example – recognize if a new INVITE message is a resend – know that 200 OK reponse belongs to the initial INVITE which it will be able to handle in an onreply_route[x] block. | it doesnt know that the call is on-going. However it can use callId to match INVITE and BYE. |
Uses : manage call state , routing , call control like forward on busy, voicemail | Uses : Load distribution , proxying |
Kamailio’s Transaction management
t_relay, t_relay_to_udp and t_relay_to_tcp are main functions to setup transaction state, absorb retransmissions from upstream, generate downstream retransmissions and correlate replies to requests in Kamailio.
Lifecycle of Transaction
Transactions lifecycle are controlled by various factors which includes reliable ( TCP) or non reliable transport, invite or non-invite transaction types etc. Transaction are terminated either by final response or when timers are fired, which control it.
ACK is considered part of INVITE trasnaction when non 2xx / negative final resposne is received , When 2xx final / positive response is recievd than ACK is not considered part of the transaction.
Memory Management in Transactions
Transaction Module copies clones of received SIP messages in shared memory. Non-TM functions operate over the received message in private memory. Therefore core operations ( like record_route) should not be called before settings the transaction state ( t_realy ) for state-fully processing a message.
An INVITE transaction will be kept in memory for maximum: max_inv_lifetime + fr_timer + wt_timer.
While A non-INVITE transaction will be kept in memory for a maximum: max_noninv_lifetime + wt_timer.
Branches
A single SIP INVITE request may be forked to multiple destinations, all of which together is called destination sets and Individual elements within the destination sets are called branches. A transaction can have more than one branch. For example, during DNA failover, each failed DNS SRV destination can introduce a new branch.
Serial, Parallel and Combined Forking
By default Kamailio performs parallel forking sending msg to all destinations and waiting for a response, however it can also do serial ie send requests one by one and wait for response /timeout before sending next.
By use of priorities ( q value 0 – 1.0) , Kamailio can also intermix the forking technique ie describing priority oder for serial and same level for parallel . The destination uri are loaded using unctions t_load_contacts() and t_next_contacts().
Parallel forking snippet
request_route { seturi("sip:a@example.com"); append_branch("sip:b@example.com"); append_branch("sip:c@example.com"); append_branch("sip:d@example.com"); t_relay(); break; }
Mixed forking snippet
modparam("tm", "contacts_avp", "tm_contacts"); modparam("tm", "contact_flows_avp", "tm_contact_flows"); request_route { seturi("sip:a@example.com"); // lowest 0 append_branch("sip:b@example.com", "0.5"); // shoudl be in parallel with C append_branch("sip:c@example.com", "0.5"); // shoudl be in parallel with B append_branch("sip:d@example.com", "1.0"); // highest priority , should be tried first t_load_contacts(); // load all branches as per q values, store them in AVP configured in modparam t_next_contacts(); // takes AVP and extracts higher q value branch t_relay(); break; }
Code to terminate when no more branches are found ( -1 returned) and return the message upstream
failure_route["serial"] { if (!t_next_contacts()) { exit; } t_on_failure("serial"); t_relay(); }
TM Module
t_relay, t_relay_to_udp and t_relay_to_tcp are main functions to setup transaction state, absorb retransmissions from upstream, generate downstream retransmissions and correlate replies to requests.
Memory
TM copies clones of received SIP messages in shared memory. non-TM functions operate over the received message in private memory. Therefore core operations ( like record_route) should ne called before settings the trasnaction state ( t_realy ) for statefully processing a message.
An INVITE transaction will be kept in memory for maximum: max_inv_lifetime + fr_timer + wt_timer.
While A non-INVITE transaction will be kept in memory for a maximum: max_noninv_lifetime + wt_timer.
Parameters
Various parameters are used to fine tune how trsnactions are handled and timedout in kamailio. Note all timers are set in miliseconds notation.
- fr_timer (integer) – timer hit when no final reply for a request or ACK for a negative INVITE reply arrives. Default 30000 ms (30 seconds).
- fr_inv_timer (integer) – timer hit when no final reply for an INVITE arrives after a provisional message was received on branch. Default 120000 ms (120 seconds).
- restart_fr_on_each_reply (integer) – restart fr_inv_timer fir INVITE transaction for each provisional reply. Otherwise it will be sreatred only for fisrt and then increasing provisonal replies. Turn it off in cases when dealing with bad UAs that continuously retransmit 180s, not allowing the transaction to timeout.
- max_inv_lifetime (integer) – Maximum time an INVITE transaction is allowed to be active in a tansaction. It starts from the time trnsaction was created and after this timer is hit , transaction is moved to either wait state or in the final response retransmission state. Default 180000 ms (180 seconds )
- max_noninv_lifetime (integer) – Maximum time a non-INVITE transaction is allowed to be active. default 32000 ms (32 seconds )
- wt_timer (integer) – Time for which a transaction stays in memory to absorb delayed messages after it completed.
- delete_timer (integer) – Time after which a to-be-deleted transaction currently ref-ed by a process will be tried to be deleted again. This is now obsolte and now transaction is deleted the moment it’s not referenced anymore.
Retry transmission timers
- retr_timer1 (integer) – Initial retransmission period
- retr_timer2 (integer) – Maximum retransmission period started increasingly from starts with retr_timer1 and stays constant after this
- noisy_ctimer (integer) – if set, INVITE transactions that time-out (FR INV timer) will be always replied. Otherwise they will be quitely dropped without any 408 branch timeout resposne
- auto_inv_100 (integer) – automatically send and 100 reply to INVITEs.
- auto_inv_100_reason (string) – Set reason text of the automatically sent 100 to an INVITE.
- unix_tx_timeout (integer) – nix socket transmission timeout,
- aggregate_challenges (integer) – if more than one branch received a 401 or 407 as final response, then all the WWW-Authenticate and Proxy-Authenticate headers from all the 401 and 407 replies will be aggregated in a new final response.
Blacklist
- blst_503 (integer) – reparse_invite=1.
- blst_503_def_timeout (integer) – blacklist interval if no “Retry-After” header is present
- blst_503_min_timeout / blst_503_max_timeout (integer) – minimum and maximun blacklist interval respectively
- blst_methods_add (unsigned integer) – Bitmap of method types that trigger blacklisting on transaction timeouts and by default INVITE triggers blacklisting only
- blst_methods_lookup (unsigned integer) – Bitmap of method types that are looked-up in the blacklist before being forwarded statefully. For default only applied to BYE.
Reparse
- reparse_invite (integer) – set if CANCEL and negative ACK requests are to be constructed from the INVITE message ( same record-set etc as INVITE ) which was sent out instead of building them from the received request.
- reparse_on_dns_failover (integer) – SIP message after a DNS failover is constructed from the outgoing message buffer of the failed branch instead of from the received request.
- ac_extra_hdrs (string) – Header fields prefixed by this parameter value are included in the CANCEL and negative ACK messages if they were present in the outgoing INVITE. Can be only used with reparse_invite=1.
- on_sl_reply (string) – Sets reply route block, to which control is passed when a reply is received that has no associated transaction.
modparam("tm", "on_sl_reply", "stateless_replies") ... onreply_route["stateless_replies"] { // return 0 if do not allow stateless replies to be forwarded return 1; // will pass to core for stateless forwading }
- xavp_contact (string) – name of XAVP storing the attributes per contact.
- contacts_avp (string) – name of an XAVP that stores names of destination sets. Used by t_load_contacts() and t_next_contacts() for forking branches
- contact_flows_avp (string) – name of an XAVP that were skipped
- fr_timer_avp (string) – override teh value of fr_timer on per transactio basis , outdated
- cancel_b_method (integer) – method to CANCEL an unreplied transaction branch. Params :
- 0 will immediately stop the request (INVITE) retransmission on the branch so that unrpelied branches will be terminated
- 1 will keep retransmitting the request on unreplied branches.
- 2 end and retransmit CANCEL even on unreplied branches, stopping the request retransmissions.
- unmatched_cancel (string) – sets how to forward CANCELs that do not match any transaction. Params :
- 0 statefully
- 1 statelessly
- 2 dropping them
- ruri_matching (integer) – try to match the request URI when doing SIP 1.0 transaction matching as older SIP didnt have via cookies as in RFC 3261
- via1_matching (integer) – match the topmost “Via” header when doing SIP 1.0 transaction matching
- callid_matching (integer) – match the callid when doing transaction matching.
- pass_provisional_replies (integer)
- default_code (integer) – Default response code sent by t_reply() ( 500 )
- default_reason (string) – Default SIP reason phrase sent by t_reply() ( “Server Internal Error” )
- disable_6xx_block (integer)- treat all the 6xx replies like normal replies. However according to RFC receiving a 6xx will cancel all the running parallel branches, will stop DNS failover and forking.
- local_ack_mode (integer) – where locally generated ACKs for 2xx replies to local transactions are sent. Params :
- 0 – the ACK destination is choosen according next hop in contact and the route set and then DNS resolution is used on it
- 1 – the ACK is sent to the same address as the corresponding INVITE branch
- 2 – the ACK is sent to the source of the 2xx reply.
- failure_reply_mode (integer) – how branches are managed and replies are selected for failure_route handling. Params :
- 0 – all branches are kept
- 1 – all branches are discarded
- 2 – only the branches of previous leg of serial forking are discarded
- 3 – all previous branches are discarded
- if you dont want to drop all branches then use t_drop_replies() to sleectively drop
- faked_reply_prio (integer) – how branch selection is done.
- local_cancel_reason (boolean) – add reason headers for CANCELs generated due to receiving a final reply.
- e2e_cancel_reason (boolean) – add reason headers for CANCELs generated due to receiving a CANCEL
- remap_503_500 (boolean) – conversion of 503 response code to 500. RFC requirnment.
- failure_exec_mode (boolean) – Add local failed branches in timer to be considered for failure routing blocks.
- dns_reuse_rcv_socket (boolean) – reuse of the receive socket for additional branches added by DNS failover.
- event_callback (str) – function in the kemi configuration file (embedded scripting language such as Lua, Python, …) to be executed instead of event_route[tm:local-request] block. The function recives a string param with name of the event.
modparam("tm", "event_callback", "ksr_tm_event") ... function ksr_tm_event(evname) KSR.info("===== TM module triggered event: " .. evname .. "\n"); return 1; end
- relay_100 (str) – whether or not a SIP 100 response is proxied. not valid behavior when operating in stateful mode and only useful when in stateless mode
- rich_redirect (int) – to add branch info in 3xx class reply. Params :
0 – no extra info is added (default)
1 – include branch flags as contact header parameter
2 – include path as contact uri Route header
Functions
These functions are operational blocks and route handlers for trsnactions handling in kamailio
- t_relay([host, port]) – Relay a message statefully.
Exmaple to show if t_relay fails, atleast send a reply to UAC statelessly to not keep it waiting
if (!t_relay()) { sl_reply_error(); break; };
- t_relay_to_udp([ip, port]) / t_relay_to_tcp([ip, port]) – same as above, relay a message statefully but using specific protocol
if (some_conditon) t_relay_to_udp("1.2.3.4", "5060"); # sent to 1.2.3.4:5060 over udp else t_relay_to_tcp(); # relay to msg. uri, but over tcp
- t_relay_to_tls([ip, port])
- t_relay_to_sctp([ip, port])
- t_on_failure(failure_route) – on route block for failure management on a branch when a negative reply is recived to transaction. here uri is reset to value which it had on relaying.
- t_on_branch_failure(branch_failure_route) – controls when negative response come for a transacion. here uri is reset to value which it had on relaying.
- t_on_reply(onreply_route) – gets control when a reply from transaction is received
- t_on_branch(branch_route) – control is passed after forking (when a new branch is created)
- t_newtran() – Creates a new transaction
- t_reply(code, reason_phrase) – Sends a stateful reply after a transaction has been established.
- t_send_reply(code, reason)
- t_lookup_request() – Checks if a transaction exists
- t_retransmit_reply()
- t_release() – Remove transaction from memory
- t_forward_nonack([ip, port]) – forward a non-ACK request statefully
- t_forward_nonack_udp(ip, port) / t_forward_nonack_tcp(ip, port)
- t_forward_nonack_tls(ip, port)
- t_forward_nonack_sctp(ip, port)
- t_set_fr(fr_inv_timeout [, fr_timeout]) – Sets the fr_inv_timeout
- t_reset_fr()
- t_set_max_lifetime(inv_lifetime, noninv_lifetime) – Sets the maximum lifetime for the current INVITE or non-INVITE transaction, or for transactions created during the same script invocation
- t_reset_max_lifetime()
- t_set_retr(retr_t1_interval, retr_t2_interval) – Sets the retr_t1_interval and retr_t2_interval for the current transaction
- t_reset_retr()
- t_set_auto_inv_100(0|1) – switch automatically sending 100 replies to INVITEs on/off on a per transaction basis
- t_branch_timeout() – Returns true if the failure route is executed for a branch that did timeout.
- t_branch_replied()
- t_any_timeout()
- t_any_replied()
- t_grep_status(“code”)
- t_is_canceled()
- t_is_expired()
- t_relay_cancel()
- t_lookup_cancel([1])
- t_drop_replies([mode])
- t_save_lumps()
- t_load_contacts()
- t_next_contacts()
- t_next_contact_flow()
- t_check_status(re)
- t_check_trans() – check if a message belongs or is related to a transaction.
- t_set_disable_6xx(0|1)
- t_set_disable_failover(0|1)
- t_set_disable_internal_reply(0|1)
- t_replicate([params]) – Replicate the SIP request to a specific address.
- t_relay_to(proxy, flags) – KSR.tm.t_relay()
- t_set_no_e2e_cancel_reason(0|1)
- t_is_set(target) – KEMI – KSR.tm.t_is_set() Return true if the attribute specified by ‘target’ is set for transaction. Target can be branch_route , failure_route and onreply_route.
if not(KSR.tm.t_is_set("branch_route")>0) then core.set_branch_route("ksr_branch_manage"); end if not(KSR.tm.t_is_set("onreply_route")>0) then core.set_reply_route("ksr_onreply_manage"); end if not(KSR.tm.t_is_set("failure_route")>0) and (req_method == "INVITE") then core.set_failure_route("ksr_failure_manage"); end
- t_use_uac_headers()
- t_is_retr_async_reply()
- t_uac_send(method, ruri, nexthop, socket, headers, body)
- t_get_status_code() – Return the status code for transaction or -1 in case of error or no status code was set.
Snippet to demo stateful handling of trsansactions
Yhis program is designed to accept all Register with 200 OK and create a new transaction. Does a check for username altanai. After the check cutom message hello is replied and any other username is printed a different rejection reply.
# ------------------ module loading ---------------------------------- loadmodule "tm.so" route{ # for testing purposes, simply okay all REGISTERs if (method=="REGISTER") { log("REGISTER"); sl_send_reply("200", "ok"); break; }; # create transaction state with t_newtran(); abort if error occurred if (t_newtran()){ log("New Transaction created"); } else { sl_reply_error(); break; }; log(1, "New Transaction Arrived\n"); # add a check for matching username to print a cutom message with t_reply() if (uri=~"altanai@") { if (!t_reply("409", "Well , hello altanai !")) { sl_reply_error(); }; } else { if (!t_reply("699", "Do not proceed with this one")) { sl_reply_error(); }; }; }
Raw RPC cmds
1. kamctl rpc tm.list
{ "jsonrpc":"2.0", "result":[ { "cell":"0x7f0698d06488", "tindex":50969, "tlabel":163886326, "method":"INVITE", "from":"From: ;tag=dddab54e\r\n", "to":"To: \r\n", "callid":"Call-ID: NjkyYjJlNzJkNzQ1OTYyZjE2MDM2NjFlYWZkNjY4OWE\r\n", "cseq":"CSeq: 1", "uas_request":"yes", "tflags":65, "outgoings":2, "ref_count":1, "lifetime":29578635 } ], "id":3922 }
2. kamctl rpc tm.stats
before call
{ "jsonrpc":"2.0", "result":{ "current":0, "waiting":0, "total":3, "total_local":0, "rpl_received":6, "rpl_generated":6, "rpl_sent":6, "6xx":0, "5xx":3, "4xx":0, "3xx":0, "2xx":0, "created":3, "freed":3, "delayed_free":0 }, "id":4119 }
during call
{ "jsonrpc":"2.0", "result":{ "current":1, "waiting":0, "total":4, "total_local":0, "rpl_received":7, "rpl_generated":7, "rpl_sent":7, "6xx":0, "5xx":3, "4xx":0, "3xx":0, "2xx":0, "created":4, "freed":3, "delayed_free":0 }, "id":4217 }
during call wait
{ "jsonrpc":"2.0", "result":{ "current":1, "waiting":1, "total":4, "total_local":0, "rpl_received":8, "rpl_generated":8, "rpl_sent":8, "6xx":0, "5xx":4, "4xx":0, "3xx":0, "2xx":0, "created":4, "freed":3, "delayed_free":0 }, "id":4275 }
after call is completed
{ "jsonrpc":"2.0", "result":{ "current":0, "waiting":0, "total":4, "total_local":0, "rpl_received":8, "rpl_generated":8, "rpl_sent":8, "6xx":0, "5xx":4, "4xx":0, "3xx":0, "2xx":0, "created":4, "freed":4, "delayed_free":0 }, "id":4333 }