Hi all!
got a weird behavior that I cannot understand the reason for... In our LAB environment, we have 2 Asterisk instances (version 13.38.3 and chan_sip) and 1 Kamailio 5.7 in between. All servers are in the same network, so, there is no NAT involved. No RTPEngine either. Network is 10.20.0.0/24 and Asterisk #1 has IP .1 Asterisk #2 has IP .3 and Kamailio has IP .5
The Asterisk servers are used only for testing, nothing serious. However, Kamailio is setup to use RTJson requesting routes to a Routing Server on the same network. And it works fine.
Both Asterisk servers have the same dialplan, which only Answers the call and plays MOH on both ends so that RTP audio streams both ways.
When making a call on Asterisk Server #1 via command line to go directly to Asterisk Server #2 without using Kamailio (CLI> channel originate SIP/123@10.20.0.3 application MusicOnHold() ) the Asterisk #2 receives the call, answers and plays MOH too and I can see RTP streams coming from both ends correctly.
However, if I use Kamailio to proxy the call generated from Asterisk #1 to Asterisk #2, using similar command line instruction (CLI> channel originate SIP/123@10.20.0.5 application MusicOnHold() ), the call is indeed received on Kamailio who then sends it to Asterisk #2, who answers the call and plays MOH, *but* despite the audio stream being sent to Asterisk #1 it is never received, however audio from Asterisk #1 is received by Asterisk #2, which configures a typical One Way Audio issue due to NAT. This is where it gets strange, because there is no NAT, SDP on INVITE and SIP 200 messages seem OK, as far as I understand it. Also, Asterisk servers have SIP configuration with directmedia enabled and NAT disabled to make sure that media is direct. But I have also tried with directmedia disabled and NAT enabled and get identical results.
I am most probably missing some tiny detail, but I have no clue.... and I bet it is simple and stupid....
Could another pair of eyes help me with this? What is wrong? Do I really need RTPEngine even when the network has no NAT? I am sure it would work that way, but it doesn't make sense...
Here are some screenshots:
Call Scenario #1 - direct call from Asterisk #1 to Asterisk #2 without Kamailio in between:
Invite from Asterisk #1 to Asterisk #2 with direct media between both ends: https://drive.google.com/file/d/1eLjT3nr_Rc-UBaf4QhIgZ95bjETOVvxo/view?usp=d...
Replies from Asterisk #2 to Asterisk #1 with direct media between both ends: https://drive.google.com/file/d/11lLcB-V8rWGSrVqWiit-q9WX2FfqB6BZ/view?usp=d...
Call Scenario #2 - call from Asterisk #1 using Kamailio to relay call to Asterisk #2, with one way audio Invite from Asterisk #1 to Asterisk #2 via Kamailio with SDP details: https://drive.google.com/file/d/1Cp9xrGcwNmQ9Ks36N_oD1Dj7lxfu-tbH/view?usp=d...
Invite from Kamailio relayed to Asterisk #2 with SDP details from Asterisk #1 identical to above: https://drive.google.com/file/d/1mi3FCNjM3luXfENEp-0088XLgcyfXRK6/view?usp=d...
Reply from Asterisk #2 to Kamailio with SDP details: https://drive.google.com/file/d/1TpMGe2tvpX_5SIpSbm2b3Zro9EuYwcO-/view?usp=d...
Reply from Kamailio to Asterisk #2 with SDP details from Asterisk #2 identical to above: https://drive.google.com/file/d/12jq5APfFwVVPc0vJ3RkcXyN1hBE51fnQ/view?usp=d...
As we can see, SDP details seem OK, but if I check call flow on Asterisk #1, I can only find 1 RTP channel with audio coming from Asterisk #2 https://drive.google.com/file/d/1iEfvkylZVbthHM5kxkurWh-GAYCYLytl/view?usp=d...
and the same on Asterisk #2 : https://drive.google.com/file/d/12rvf9Lrwp-MNZvGCwlXEEBsBRmfcinph/view?usp=d...
My Kamailio.cfg is as follows:
#!KAMAILIO # # config file for SIPProxy # - load balancing of VoIP calls # - no TPC listening # # Kamailio (OpenSER) SIP Server v3.2 # - web: http://www.kamailio.org # - git: http://sip-router.org # # # Refer to the Core CookBook at http://www.kamailio.org/dokuwiki/doku.php # for an explanation of possible statements, functions and parameters. # # Several features can be enabled using '#!define WITH_FEATURE' directives: # # *** To run in debug mode: # - define WITH_DEBUG # #!define WITH_DEBUG ###!define WITH_NAT #!define WITH_PSTN /* enables Accounting Log functions */ #!define FLT_ACC 1 /* enable Accounting of missed or failed calls */ #!define FLT_ACCMISSED 2 #!define FLT_ACCFAILED 3
/* defines DB connection string */ #!ifndef DBURL #!define DBURL "mysql://kamailio:kamailio@10.20.0.1:3306/kamailio" #!endif
# - the value for 'use_domain' parameters #!define MULTIDOMAIN 1
####### Global Parameters ######### #!ifdef WITH_DEBUG debug=4 log_stderror=no #!else debug=2 log_stderror=no #!endif
#!define FLT_DISPATCH_SETID 1 #!define FLT_FS 10 #!define FLT_NATS 5 #!define FLB_NATB 6 #!define FLB_NATSIPPING 7
#!define FLT_SRC_ALLOWED 8 #!define FLT_DST_INTERNAL_IP 9 #!define FLT_SRC_INTERNAL_IP 10
#!substdef "!INTERNAL_IP_NET!10.20.0.0/24!g" #!substdef "!INTERNAL_IP_ADDR!10.20.0.2!g" #!substdef "!EXTERNAL_IP_ADDR!10.20.0.2!g"
#!ifndef HTTP_ASYNC_CLIENT_WORKERS #!define HTTP_ASYNC_CLIENT_WORKERS 8 #!endif
/* add API http timeout */ #!define HTTP_API_TIMEOUT 5000 #!define HTTP_API_ROUTING_ENDPOINT "http://10.246.212.40:7778/get_route"
/* DMQ SIP message sharing */ #!define DMQ_PORT 5062 #!define DMQ_LISTEN "sip:10.20.0.2:5062" #!define DMQ_SERVER_ADDRESS "sip:10.20.0.2:5062" #!define DMQ_NOTIFICATION_ADDRESS "sip:10.20.0.4:5062"
memdbg=5 memlog=5
log_facility=LOG_LOCAL0 log_prefix="{$mt $hdr(CSeq) $ci} "
fork=yes children=8
/* comment the next line to enable TCP - all trunks are UDP only */ disable_tcp=yes
/* uncomment the next line to disable the auto discovery of local aliases based on revers DNS on IPs (default on) */ auto_aliases=no
port=5060
/* uncomment and configure the following line if you want Kamailio to bind on a specific interface/port/proto (default bind on all available) */ listen=udp:10.20.0.5:5060 advertise 10.20.0.5:5060 listen=tcp:10.20.0.5:5060 advertise 10.20.0.5:5060 listen=udp:10.20.0.2:5062
advertised_address="10.20.0.5";
sip_warning=no;
use_dns_failover = on; ####### Modules Section ########
#set module path mpath="/usr/local/lib64/kamailio/modules/"
loadmodule "db_mysql.so" loadmodule "jsonrpcs.so" loadmodule "kex.so" loadmodule "tm.so" loadmodule "tmx.so" loadmodule "sl.so" loadmodule "rr.so" loadmodule "pv.so" loadmodule "maxfwd.so" loadmodule "textops.so" loadmodule "siputils.so" loadmodule "xlog.so" loadmodule "sanity.so" loadmodule "ctl.so" loadmodule "acc.so" loadmodule "usrloc.so"
loadmodule "nathelper.so" #loadmodule "rtimer.so" #loadmodule "sqlops.so" # --- CPS Limiter
# --- end of CPS Limiter loadmodule "ipops.so" loadmodule "textopsx.so" loadmodule "sdpops.so" loadmodule "http_async_client.so" loadmodule "rtjson.so" loadmodule "jansson.so"
loadmodule "dmq.so" loadmodule "dmq_usrloc.so" loadmodule "htable.so" loadmodule "dialog.so"
#!ifdef WITH_DEBUG loadmodule "debugger.so" #!endif
#!ifdef WITH_DEBUG # ----- debugger params ----- modparam("debugger", "log_level_name", "exec") #!endif
# ----------------- setting module-specific parameters --------------- # ----- jsonrpcs params ----- modparam("jsonrpcs", "fifo_name", "/var/run/kamailio/kamailio_rpc.fifo") modparam("jsonrpcs", "pretty_format", 1)
# ----- rr params ----- modparam("jsonrpcs", "fifo_name", "/var/run/kamailio/kamailio_rpc.fifo") modparam("jsonrpcs", "pretty_format", 1)
# ----- rr params ----- # add value to ;lr param to cope with most of the UAs modparam("rr", "enable_full_lr", 1) # do not append from tag to the RR (no need for this script) modparam("rr", "append_fromtag", 0)
# ----- acc params ----- modparam("acc", "failed_transaction_flag", 3) modparam("acc", "log_extra","src_user=$fU;src_domain=$fd;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd;src_ip=$si")
# ----- acc params ----- /* what special events should be accounted ? */ modparam("acc", "early_media", 0) modparam("acc", "report_ack", 0) modparam("acc", "report_cancels", 0) /* by default we do not adjust the direct of the sequential requests. if you enable this parameter, be sure the enable "append_fromtag" in "rr" module */ modparam("acc", "detect_direction", 0) /* account triggers (flags) */ modparam("acc", "log_flag", FLT_ACC) modparam("acc", "log_missed_flag", FLT_ACCMISSED) modparam("acc", "log_extra","src_user=$fU;src_domain=$fd;src_ip=$si;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd") modparam("acc", "failed_transaction_flag", FLT_ACCFAILED) /* enhanced DB accounting */ modparam("acc", "db_flag", FLT_ACC) modparam("acc", "db_missed_flag", FLT_ACCMISSED) modparam("acc", "db_url", DBURL) modparam("acc", "db_extra","src_user=$fU;src_domain=$fd;src_ip=$si;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd") //;calltype=$avp(calltype)")
# ----- tm params ----- # ----- the TM module enables stateful processing of SIP requests modparam("tm", "fr_timer", 5000) modparam("tm", "fr_inv_timer", 60000) modparam("tm", "remap_503_500", 0) # ----- usrloc params ----- /* enable DB persistency for location entries */ modparam("usrloc", "db_url", DBURL) modparam("usrloc", "db_mode", 2) modparam("usrloc", "use_domain", MULTIDOMAIN) # params needed for NAT traversal in other modules modparam("usrloc", "nat_bflag", FLB_NATB)
# ----- nathelper params ----- modparam("nathelper", "received_avp", "$avp(s:rcv)") modparam("nathelper", "natping_interval", 30) modparam("nathelper", "ping_nated_only", 1) modparam("nathelper", "sipping_bflag", FLB_NATSIPPING) modparam("nathelper", "sipping_from", "sip:ping@kamailio.org")
#modparam("rtimer", "timer", "name=cdr;interval=300;mode=1;") #modparam("rtimer", "exec", "timer=cdr;route=CDRS") #modparam("sqlops", "sqlcon", "ca=>mysql://kamailio:kamailiorw@10.19.139.113:3306/kamailio")
#modparam("dmq", "server_socket", DMQ_SERVER_SOCKET ) modparam("dmq", "server_address", DMQ_SERVER_ADDRESS ) modparam("dmq", "notification_address", DMQ_NOTIFICATION_ADDRESS ) modparam("dmq", "multi_notify", 1) modparam("dmq", "num_workers", 4) modparam("dmq", "ping_interval", 60) modparam("dmq_usrloc", "enable", 1)
# -- CPS Limiter modparam("htable", "htable", "rhs=>size=32;initval=0;autoexpire=10;") modparam("htable", "htable", "rhm=>size=32;initval=0;autoexpire=120;") modparam("htable", "enable_dmq", 1) modparam("htable", "dmq_init_sync", 1)
modparam("dialog", "profiles_with_value", "concurrent_calls") modparam("dialog", "enable_dmq", 1)
# ----- http_async_client params ----- modparam("http_async_client", "workers", HTTP_ASYNC_CLIENT_WORKERS) modparam("http_async_client", "connection_timeout", 2000)
####### Routing Logic ########
# main request routing logic
route { if (is_method("KDMQ") && $Rp == 5062) { dmq_handle_message(); }
xlog("L_INFO"," ********** Route START ***********");
# log the basic info regarding this call xlog("L_INFO","start|\n"); xlog("L_INFO","===================================================\n"); xlog("L_INFO","New SIP message $rm with call-ID $ci \n"); xlog("L_INFO","---------------------------------------------------\n"); xlog("L_INFO"," received $pr request $rm $ou\n"); xlog("L_INFO"," source $si:$sp\n"); xlog("L_INFO"," from $fu\n"); xlog("L_INFO"," to $tu\n"); xlog("L_INFO","---------------------------------------------------\n"); xlog("L_INFO","---------------------------------------------------\n");
# OPTIONS requests without a username in the Request-URI but one # of our domains or IPs are addressed to the proxy itself and # can be answered statelessly. if (is_method("OPTIONS")) { sl_send_reply("200","OK"); exit; }
if ($fU=="ping") { sl_send_reply("200","OK"); exit; }
# extract original source ip / port from X-forwarded-For header route(HANDLE_X_FORWARDED_FOR);
# per request initial checks route(REQINIT);
# NAT detection route(NATDETECT);
# handle requests within SIP dialogs ### only initial requests (no To tag) # CANCEL processing if (is_method("CANCEL")) { if (t_check_trans()){ route(RELAY); } exit; }
# handle retransmissions if (!is_method("ACK")) { if(t_precheck_trans()) { t_check_trans(); xlog("L_INFO", "ROUTE - Exiting after Retransmission check - method $rm"); exit; } t_check_trans(); }
route(WITHINDLG);
# record routing for dialog forming requests (in case they are routed) # - remove preloaded route headers xlog("L_INFO", "ROUTE - Removing Headers"); remove_hf("Route");
if (is_method("INVITE|SUBSCRIBE")){ t_on_failure("MANAGE_FAILURE"); xlog("L_INFO", "ROUTE - Recording Route"); record_route();
if (is_method("INVITE") && is_request()) { if (has_body("application/sdp")) { xlog("L_INFO", "ROUTE - goiing to t_on_reply[ON_REPLY]\n"); t_on_reply("ON_REPLY"); } } }
if ($rU==$null) { # request with no Username in RURI sl_send_reply("484","ROUTE - Address Incomplete"); exit; } route(TOCARRIER);
xlog("L_INFO", " ********** Route END *************");
}
# extract original source ip / port from X-forwarded-For header route[HANDLE_X_FORWARDED_FOR] { if (is_present_hf("X-Forwarded-For")) { $var(source_ip) = $(hdr(X-Forwarded-For){s.select,0,:}); $var(source_port) = $(hdr(X-Forwarded-For){s.select,1,:}); } else { $var(source_ip) = $si; $var(source_port) = $sp; } $var(to_number) = $rU; }
route[RELAY_API] { xlog("L_INFO","RELAY_API - from_ip $var(source_ip):$var(source_port) from_number $fU to_number $ru"); $http_req(all) = $null; $http_req(suspend) = 1; $http_req(timeout) = HTTP_API_TIMEOUT; $http_req(method) = "POST"; $http_req(hdr) = "Content-Type: application/json"; jansson_set("string","from_ip",$var(source_ip), "$var(http_routing_query)"); jansson_set("string","from_port",$var(source_port), "$var(http_routing_query)"); jansson_set("string","from_number",$fU, "$var(http_routing_query)"); jansson_set("string","to_number",$var(to_number) , "$var(http_routing_query)");
xlog("L_INFO","RELAY_API - API ASYNC ROUTING REQUEST: $var(http_routing_query)\n"); $http_req(body) = $var(http_routing_query); t_newtran(); http_async_query(HTTP_API_ROUTING_ENDPOINT, "RELAY_API_RESPONSE"); }
# Relay request using the API (response) route[RELAY_API_RESPONSE] {
if ($http_ok==1 && $http_rs==200) { xlog("L_INFO","RELAY_API_RESPONSE - RESPONSE: $http_rb\n");
if (jansson_get("rtjson", $http_rb, "$var(rtjson)")) { xlog("L_INFO","RELAY_API_RESPONSE - $var(rtjson)"); rtjson_init_routes("$var(rtjson)"); rtjson_push_routes(); # relay the message t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE");
route(RELAY); return; } }
send_reply(500, "API Not Available - http response = $http_rs $http_ok"); exit; }
onreply_route[ON_REPLY] {
xlog("L_INFO", "ON_REPLY - In onreply_route[ON_REPLY] $rs"); # on reply if (t_check_status("183|180|200")) { xlog("L_INFO", "ON_REPLY - Fixing Contacts"); # subst_hf("Contact","/@.*:/@EXTERNAL_IP_ADDR:/","a"); //subst_hf("Record-Route","/INTERNAL_IP_ADDR/EXTERNAL_IP_ADDR/","f"); }
if (has_body("application/sdp")){ if (sdp_remove_line_by_prefix("a=maxptime")){ xlog("L_INFO", "ON_REPLY - remove maxptime "); msg_apply_changes(); } else{ xlog("L_INFO", "ON_REPLY - did not removed maxptime "); } }
if (t_check_status("408")) { xlog("L_INFO", "ROUTE - Handling 408 Timeout\n"); }
}
route[TOCARRIER]{ #using rtjson, unsomment following line route(RELAY_API);
}
# Per SIP request initial checks route[REQINIT] { xlog("L_INFO", "REQINIT - Starting"); if (!mf_process_maxfwd_header("10")) { xlog("L_INFO", "REQINIT - 483 - Too Many Hops"); sl_send_reply("483","Too Many Hops"); exit; }
if(!sanity_check("1511", "7")) { xlog("L_INFO","REQINIT - Sanity Check -> Malformed SIP message from $si:$sp\n"); exit; } }
# Caller NAT detection route[NATDETECT] { xlog("L_INFO", "NATDETECT - Entering"); #!ifdef WITH_NAT force_rport(); if (nat_uac_test("19")) { if (is_method("REGISTER")) { xlog("L_INFO", "NATDETECT - Fix Nated Register"); fix_nated_register(); } else { if(is_first_hop()){ xlog("L_INFO", "NATDETECT - Set Contact Alias"); set_contact_alias(); } } xlog("L_INFO", "NATDETECT - Set FLT_NATS" + FLT_NATS); setflag(FLT_NATS); } #!endif xlog("L_INFO", "NATDETECT - NAT Detect set FLT_NTS = " + FLT_NATS); return; }
# Handle requests within SIP dialogs route[WITHINDLG] { xlog("L_INFO", "WITHINDLG - Entering"); if (!has_totag()) return;
if (is_present_hf("Route") && $hdrc(Route)==1) {
if (search_hf("Route", ".*EXTERNAL_IP_ADDR.*", "f")) { xlog("L_INFO", "WITHINDLG - Removing the route to self"); remove_hf("Route"); } }
# sequential request within a dialog should # take the path determined by record-routing if (loose_route()) { route(DLGURI); if (is_method("BYE|CANCEL")) { setflag(FLT_ACC); # do accounting ... setflag(FLT_ACCFAILED); # ... even if the transaction fails } else if ( is_method("ACK") ) { # ACK is forwarded statelessy xlog("L_INFO", "WITHINDLG - Going to NATMANAGE"); route(NATMANAGE); } else if ( is_method("NOTIFY") ) { #Add Record-Route for in-dialog NOTIFY as per RFC 6665. record_route(); } if(is_method("BYE")) xlog("L_INFO", "WITHINDLG - BYE message from $rU");
route(RELAY); exit; }
if ( is_method("ACK|BYE|INVITE|UPDATE") ) { if ( t_check_trans() ) { # no loose-route, but stateful ACK; # must be an ACK after a 487 # or e.g. 404 from upstream server route(RELAY); exit; } else { # ACK without matching transaction. Try to route anyway - being optimistic # since it has at least a To Tag route(RELAY); exit; } } sl_send_reply("404","Not here"); xlog("L_INFO", "WITHINDLG - Finishing WITHINDLG"); exit; }
# URI update for dialog requests route[DLGURI] { xlog("L_INFO", "WITHINDLG - Entering DLGURI"); #!ifdef WITH_NAT if(!isdsturiset()) { xlog("L_INFO", "WITHINDLG - Handle ruri ALIAS"); handle_ruri_alias(); } #!endif return; }
# Routing to foreign domains ---> NOT USED route[SIPOUT] { xlog("L_INFO", "WITHINDLG - Entering SIPOUT"); if (uri==myself){ xlog("L_INFO", "WITHINDLG - URI is MySelf!"); return; }
append_hf("P-hint: outbound\r\n"); xlog("L_INFO", "WITHINDLG - Finishing SIPOUT"); route(RELAY); exit; }
# Wrapper for relaying requests route[RELAY] { xlog("L_INFO", " ******** RELAY *******"); xlog("L_INFO", "RELAY - $si $su $ru"); # enable additional event routes for forwarded requests # - serial forking, RTP relaying handling, a.s.o. if (is_method("INVITE|BYE|CANCEL|SUBSCRIBE|UPDATE")) { if(!t_is_set("branch_route")) { xlog("L_INFO", "RELAY - branch_route NOT SET!"); t_on_branch("MANAGE_BRANCH"); } }
xlog("L_INFO", "RELAY - checking method"); if (is_method("INVITE|SUBSCRIBE|UPDATE")) { xlog("L_INFO", "RELAY - is INVITE|SUBSCRIBE|UPDATE"); if(!t_is_set("onreply_route")) { xlog("L_INFO", "RELAY - onreply_route NOT SET!"); t_on_reply("ON_REPLY"); # MANAGE_REPLY"); } }
if (is_method("INVITE")) { xlog("L_INFO", "RELAY - is INVITE"); t_on_failure("FAILED_RELAY"); if(!t_is_set("failure_route")) { xlog("L_INFO", "RELAY - failure_route NOT SET!"); t_on_failure("MANAGE_FAILURE"); } }
if (!t_relay()) { xlog("L_INFO", "RELAY - t_relay returns FALSE"); route("MANAGE_FAILURE"); #sl_reply_error(); }
xlog("L_INFO", "RELAY - exiting"); exit; }
failure_route[FAILED_RELAY] { xlog("L_INFO", "FAILED_RELAY - Entering"); if (t_check_status("[4-5][0-9][0-9]")){ xlog("L_INFO", "FAILED_RELAY - Could not reach destination endpoint!"); if (rtjson_next_route()) { xlog("L_INFO", "MANAGE_FAILURE - Getting next route"); t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE"); route(RELAY); } } }
route[NATMANAGE] { xlog("L_INFO", "NATMANAGE - Entering"); #!ifdef WITH_NAT if (is_request()) { if(has_totag()) { xlog("L_INFO", "NATMANAGE - nat=yes --- Setting FLB_NATB"); setbflag(FLB_NATB); } }
if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB) )) { xlog("L_INFO", "NATMANAGE - NO FLT_NATS/B Set!!! Getting out of NATMANAGE"); return; }
if (is_request()) { xlog("L_INFO", "NATMANAGE - is_request - $rm from $si"); if (!has_totag()) { if(t_is_branch_route()) { xlog("L_INFO", "NATMANAGE - adding nat=yes"); add_rr_param(";nat=yes"); } } } if (is_reply()) { xlog("L_INFO", "NATMANAGE - is_reply - $rm from $si"); if(isbflagset(FLB_NATB)) { if(is_first_hop()) { xlog("L_INFO", "NATMANAGE - Set Contact Alias"); set_contact_alias(); } } }
#!endif return; }
# Manage failure routing cases route[MANAGE_FAILURE] {
xlog("L_INFO", "MANAGE_FAILURE - Entering ");
route(NATMANAGE);
xlog("L_INFO", "MANAGE_FAILURE - t_is_canceled"); if (t_is_canceled()) exit;
#!ifdef WITH_BLOCK3XX # block call redirect based on 3xx replies. if (t_check_status("3[0-9][0-9]")) { xlog("L_INFO", "MANAGE_FAILURE - SIP 3XX returned!!"); t_reply("404","Not found"); exit; } #!endif
#!ifdef WITH_BLOCK401407 # block call redirect based on 401, 407 replies. if (t_check_status("401|407")) { xlog("L_INFO", "MANAGE_FAILURE - SIP 401|407 returned!!"); t_reply("404","Not found"); exit; } #!endif
if (t_check_status("503")){ xlog("L_INFO", "MANAGE_FAILURE - SIP 503 returned : no destination available"); t_reply("503", "Destination not available"); exit; }
if (rtjson_next_route()) { xlog("L_INFO", "MANAGE_FAILURE - Getting next route!!"); t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE"); route(RELAY); exit; } }
# Manage outgoing branches branch_route[MANAGE_BRANCH] { xlog("L_INFO","MANAGE_BRANCH - New branch [$T_branch_idx] to $ru\n"); xlog("L_INFO", "MANAGE_BRANCH - branch_route MANAGE_BRANCH 1 "); rtjson_update_branch(); route(NATMANAGE); }
Any help would be greatly appreciated.
Thanks in advance.
Sérgio Charrua
Hi all!
some additional details for this issue.
Currently, Kamailio is using RTJSON to get routes from the routing engine and forward calls to the correct route. Please note that the 2 testing endpoints and Kamailio are all in the same network, no NAT involved, and firewalls are disabled!
Following route function does the magic:
route[TOCARRIER]{ #Route to send calls to a carrier at 192.168.200.130 route(RELAY_API); #Route relay }
route[RELAY_API]{ # makes the HTTP Assync request ..... # once response is received from HTTP REST API, go to RELAY_API_RESPONSE ..... }
# Relay request using the API (response) route[RELAY_API_RESPONSE] {
if ($http_ok==1 && $http_rs==200) {
xlog("L_INFO","RELAY_API_RESPONSE - RESPONSE: $http_rb\n");
if (jansson_get("rtjson", $http_rb, "$var(rtjson)")) {
xlog("L_INFO","RELAY_API_RESPONSE - $var(rtjson)");
rtjson_init_routes("$var(rtjson)");
rtjson_push_routes();
# relay the message
t_on_branch("MANAGE_BRANCH");
t_on_failure("MANAGE_FAILURE");
route(RELAY);
return;
}
}
}
This is working correctly. However, as mentioned in previous email, when the call is forwarded to the endpoint using RTJSON module (and for testing purposes, we are using Asterisk 13.38.x as an endpoint), it results in a one-way audio issue: A Leg sends Audio Streams correctly directly to B Leg (direct media) but B Leg seems to not sending any audio, even though both endpoints are playing some Music On Hold stuff. Even TCPDUMP shows no RTP traffic from B to A, but can find traffic from A to B!
What I found out is that if I modify RELAY_API route to be as follows:
route[TOCARRIER]{
rewritehost("10.20.0.3"); #Rewrite host to be the endpoint's IP
route(RELAY);
}
The audio streams are fully working, both ways! TCPDUMP shows audio traffic both ways, no issues! The SIP Traces show the same structure, both for SIP and SDP (of course, CallID, BranchID and RURI are different), so I think the issue is *not* within endpoints, but somewhere in Kamailio (module or configuration).
Any suggestions?
*Sérgio Charrua*
On Tue, Mar 5, 2024 at 1:05 PM Sergio Charrua sergio.charrua@voip.pt wrote:
Hi all!
got a weird behavior that I cannot understand the reason for... In our LAB environment, we have 2 Asterisk instances (version 13.38.3 and chan_sip) and 1 Kamailio 5.7 in between. All servers are in the same network, so, there is no NAT involved. No RTPEngine either. Network is 10.20.0.0/24 and Asterisk #1 has IP .1 Asterisk #2 has IP .3 and Kamailio has IP .5
The Asterisk servers are used only for testing, nothing serious. However, Kamailio is setup to use RTJson requesting routes to a Routing Server on the same network. And it works fine.
Both Asterisk servers have the same dialplan, which only Answers the call and plays MOH on both ends so that RTP audio streams both ways.
When making a call on Asterisk Server #1 via command line to go directly to Asterisk Server #2 without using Kamailio (CLI> channel originate SIP/123@10.20.0.3 application MusicOnHold() ) the Asterisk #2 receives the call, answers and plays MOH too and I can see RTP streams coming from both ends correctly.
However, if I use Kamailio to proxy the call generated from Asterisk #1 to Asterisk #2, using similar command line instruction (CLI> channel originate SIP/123@10.20.0.5 application MusicOnHold() ), the call is indeed received on Kamailio who then sends it to Asterisk #2, who answers the call and plays MOH, *but* despite the audio stream being sent to Asterisk #1 it is never received, however audio from Asterisk #1 is received by Asterisk #2, which configures a typical One Way Audio issue due to NAT. This is where it gets strange, because there is no NAT, SDP on INVITE and SIP 200 messages seem OK, as far as I understand it. Also, Asterisk servers have SIP configuration with directmedia enabled and NAT disabled to make sure that media is direct. But I have also tried with directmedia disabled and NAT enabled and get identical results.
I am most probably missing some tiny detail, but I have no clue.... and I bet it is simple and stupid....
Could another pair of eyes help me with this? What is wrong? Do I really need RTPEngine even when the network has no NAT? I am sure it would work that way, but it doesn't make sense...
Here are some screenshots:
Call Scenario #1 - direct call from Asterisk #1 to Asterisk #2 without Kamailio in between:
Invite from Asterisk #1 to Asterisk #2 with direct media between both ends:
https://drive.google.com/file/d/1eLjT3nr_Rc-UBaf4QhIgZ95bjETOVvxo/view?usp=d...
Replies from Asterisk #2 to Asterisk #1 with direct media between both ends:
https://drive.google.com/file/d/11lLcB-V8rWGSrVqWiit-q9WX2FfqB6BZ/view?usp=d...
Call Scenario #2 - call from Asterisk #1 using Kamailio to relay call to Asterisk #2, with one way audio Invite from Asterisk #1 to Asterisk #2 via Kamailio with SDP details:
https://drive.google.com/file/d/1Cp9xrGcwNmQ9Ks36N_oD1Dj7lxfu-tbH/view?usp=d...
Invite from Kamailio relayed to Asterisk #2 with SDP details from Asterisk #1 identical to above:
https://drive.google.com/file/d/1mi3FCNjM3luXfENEp-0088XLgcyfXRK6/view?usp=d...
Reply from Asterisk #2 to Kamailio with SDP details:
https://drive.google.com/file/d/1TpMGe2tvpX_5SIpSbm2b3Zro9EuYwcO-/view?usp=d...
Reply from Kamailio to Asterisk #2 with SDP details from Asterisk #2 identical to above:
https://drive.google.com/file/d/12jq5APfFwVVPc0vJ3RkcXyN1hBE51fnQ/view?usp=d...
As we can see, SDP details seem OK, but if I check call flow on Asterisk #1, I can only find 1 RTP channel with audio coming from Asterisk #2
https://drive.google.com/file/d/1iEfvkylZVbthHM5kxkurWh-GAYCYLytl/view?usp=d...
and the same on Asterisk #2 :
https://drive.google.com/file/d/12rvf9Lrwp-MNZvGCwlXEEBsBRmfcinph/view?usp=d...
My Kamailio.cfg is as follows:
#!KAMAILIO # # config file for SIPProxy # - load balancing of VoIP calls # - no TPC listening # # Kamailio (OpenSER) SIP Server v3.2 # - web: http://www.kamailio.org # - git: http://sip-router.org # # # Refer to the Core CookBook at http://www.kamailio.org/dokuwiki/doku.php # for an explanation of possible statements, functions and parameters. # # Several features can be enabled using '#!define WITH_FEATURE' directives: # # *** To run in debug mode: # - define WITH_DEBUG # #!define WITH_DEBUG ###!define WITH_NAT #!define WITH_PSTN /* enables Accounting Log functions */ #!define FLT_ACC 1 /* enable Accounting of missed or failed calls */ #!define FLT_ACCMISSED 2 #!define FLT_ACCFAILED 3
/* defines DB connection string */ #!ifndef DBURL #!define DBURL "mysql://kamailio:kamailio@10.20.0.1:3306/kamailio" #!endif
# - the value for 'use_domain' parameters #!define MULTIDOMAIN 1
####### Global Parameters ######### #!ifdef WITH_DEBUG debug=4 log_stderror=no #!else debug=2 log_stderror=no #!endif
#!define FLT_DISPATCH_SETID 1 #!define FLT_FS 10 #!define FLT_NATS 5 #!define FLB_NATB 6 #!define FLB_NATSIPPING 7
#!define FLT_SRC_ALLOWED 8 #!define FLT_DST_INTERNAL_IP 9 #!define FLT_SRC_INTERNAL_IP 10
#!substdef "!INTERNAL_IP_NET!10.20.0.0/24!g" #!substdef "!INTERNAL_IP_ADDR!10.20.0.2!g" #!substdef "!EXTERNAL_IP_ADDR!10.20.0.2!g"
#!ifndef HTTP_ASYNC_CLIENT_WORKERS #!define HTTP_ASYNC_CLIENT_WORKERS 8 #!endif
/* add API http timeout */ #!define HTTP_API_TIMEOUT 5000 #!define HTTP_API_ROUTING_ENDPOINT "http://10.246.212.40:7778/get_route"
/* DMQ SIP message sharing */ #!define DMQ_PORT 5062 #!define DMQ_LISTEN "sip:10.20.0.2:5062" #!define DMQ_SERVER_ADDRESS "sip:10.20.0.2:5062" #!define DMQ_NOTIFICATION_ADDRESS "sip:10.20.0.4:5062"
memdbg=5 memlog=5
log_facility=LOG_LOCAL0 log_prefix="{$mt $hdr(CSeq) $ci} "
fork=yes children=8
/* comment the next line to enable TCP - all trunks are UDP only */ disable_tcp=yes
/* uncomment the next line to disable the auto discovery of local aliases based on revers DNS on IPs (default on) */ auto_aliases=no
port=5060
/* uncomment and configure the following line if you want Kamailio to bind on a specific interface/port/proto (default bind on all available) */ listen=udp:10.20.0.5:5060 advertise 10.20.0.5:5060 listen=tcp:10.20.0.5:5060 advertise 10.20.0.5:5060 listen=udp:10.20.0.2:5062
advertised_address="10.20.0.5";
sip_warning=no;
use_dns_failover = on; ####### Modules Section ########
#set module path mpath="/usr/local/lib64/kamailio/modules/"
loadmodule "db_mysql.so" loadmodule "jsonrpcs.so" loadmodule "kex.so" loadmodule "tm.so" loadmodule "tmx.so" loadmodule "sl.so" loadmodule "rr.so" loadmodule "pv.so" loadmodule "maxfwd.so" loadmodule "textops.so" loadmodule "siputils.so" loadmodule "xlog.so" loadmodule "sanity.so" loadmodule "ctl.so" loadmodule "acc.so" loadmodule "usrloc.so"
loadmodule "nathelper.so" #loadmodule "rtimer.so" #loadmodule "sqlops.so" # --- CPS Limiter
# --- end of CPS Limiter loadmodule "ipops.so" loadmodule "textopsx.so" loadmodule "sdpops.so" loadmodule "http_async_client.so" loadmodule "rtjson.so" loadmodule "jansson.so"
loadmodule "dmq.so" loadmodule "dmq_usrloc.so" loadmodule "htable.so" loadmodule "dialog.so"
#!ifdef WITH_DEBUG loadmodule "debugger.so" #!endif
#!ifdef WITH_DEBUG # ----- debugger params ----- modparam("debugger", "log_level_name", "exec") #!endif
# ----------------- setting module-specific parameters --------------- # ----- jsonrpcs params ----- modparam("jsonrpcs", "fifo_name", "/var/run/kamailio/kamailio_rpc.fifo") modparam("jsonrpcs", "pretty_format", 1)
# ----- rr params ----- modparam("jsonrpcs", "fifo_name", "/var/run/kamailio/kamailio_rpc.fifo") modparam("jsonrpcs", "pretty_format", 1)
# ----- rr params ----- # add value to ;lr param to cope with most of the UAs modparam("rr", "enable_full_lr", 1) # do not append from tag to the RR (no need for this script) modparam("rr", "append_fromtag", 0)
# ----- acc params ----- modparam("acc", "failed_transaction_flag", 3) modparam("acc",
"log_extra","src_user=$fU;src_domain=$fd;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd;src_ip=$si")
# ----- acc params ----- /* what special events should be accounted ? */ modparam("acc", "early_media", 0) modparam("acc", "report_ack", 0) modparam("acc", "report_cancels", 0) /* by default we do not adjust the direct of the sequential requests. if you enable this parameter, be sure the enable "append_fromtag" in "rr" module */ modparam("acc", "detect_direction", 0) /* account triggers (flags) */ modparam("acc", "log_flag", FLT_ACC) modparam("acc", "log_missed_flag", FLT_ACCMISSED) modparam("acc",
"log_extra","src_user=$fU;src_domain=$fd;src_ip=$si;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd") modparam("acc", "failed_transaction_flag", FLT_ACCFAILED) /* enhanced DB accounting */ modparam("acc", "db_flag", FLT_ACC) modparam("acc", "db_missed_flag", FLT_ACCMISSED) modparam("acc", "db_url", DBURL) modparam("acc",
"db_extra","src_user=$fU;src_domain=$fd;src_ip=$si;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd") //;calltype=$avp(calltype)")
# ----- tm params ----- # ----- the TM module enables stateful processing of SIP requests modparam("tm", "fr_timer", 5000) modparam("tm", "fr_inv_timer", 60000) modparam("tm", "remap_503_500", 0) # ----- usrloc params ----- /* enable DB persistency for location entries */ modparam("usrloc", "db_url", DBURL) modparam("usrloc", "db_mode", 2) modparam("usrloc", "use_domain", MULTIDOMAIN) # params needed for NAT traversal in other modules modparam("usrloc", "nat_bflag", FLB_NATB)
# ----- nathelper params ----- modparam("nathelper", "received_avp", "$avp(s:rcv)") modparam("nathelper", "natping_interval", 30) modparam("nathelper", "ping_nated_only", 1) modparam("nathelper", "sipping_bflag", FLB_NATSIPPING) modparam("nathelper", "sipping_from", "sip:ping@kamailio.org")
#modparam("rtimer", "timer", "name=cdr;interval=300;mode=1;") #modparam("rtimer", "exec", "timer=cdr;route=CDRS") #modparam("sqlops", "sqlcon", "ca=>mysql://kamailio:kamailiorw@10.19.139.113:3306/kamailio")
#modparam("dmq", "server_socket", DMQ_SERVER_SOCKET ) modparam("dmq", "server_address", DMQ_SERVER_ADDRESS ) modparam("dmq", "notification_address", DMQ_NOTIFICATION_ADDRESS ) modparam("dmq", "multi_notify", 1) modparam("dmq", "num_workers", 4) modparam("dmq", "ping_interval", 60) modparam("dmq_usrloc", "enable", 1)
# -- CPS Limiter modparam("htable", "htable", "rhs=>size=32;initval=0;autoexpire=10;") modparam("htable", "htable", "rhm=>size=32;initval=0;autoexpire=120;") modparam("htable", "enable_dmq", 1) modparam("htable", "dmq_init_sync", 1)
modparam("dialog", "profiles_with_value", "concurrent_calls") modparam("dialog", "enable_dmq", 1)
# ----- http_async_client params ----- modparam("http_async_client", "workers", HTTP_ASYNC_CLIENT_WORKERS) modparam("http_async_client", "connection_timeout", 2000)
####### Routing Logic ########
# main request routing logic
route { if (is_method("KDMQ") && $Rp == 5062) { dmq_handle_message(); }
xlog("L_INFO"," ********** Route START ***********");
# log the basic info regarding this call xlog("L_INFO","start|\n"); xlog("L_INFO","===================================================\n"); xlog("L_INFO","New SIP message $rm with call-ID $ci \n"); xlog("L_INFO","---------------------------------------------------\n"); xlog("L_INFO"," received $pr request $rm $ou\n"); xlog("L_INFO"," source $si:$sp\n"); xlog("L_INFO"," from $fu\n"); xlog("L_INFO"," to $tu\n"); xlog("L_INFO","---------------------------------------------------\n"); xlog("L_INFO","---------------------------------------------------\n");
# OPTIONS requests without a username in the Request-URI but one # of our domains or IPs are addressed to the proxy itself and # can be answered statelessly. if (is_method("OPTIONS")) { sl_send_reply("200","OK"); exit; }
if ($fU=="ping") { sl_send_reply("200","OK"); exit; }
# extract original source ip / port from X-forwarded-For header route(HANDLE_X_FORWARDED_FOR);
# per request initial checks route(REQINIT);
# NAT detection route(NATDETECT);
# handle requests within SIP dialogs ### only initial requests (no To tag) # CANCEL processing if (is_method("CANCEL")) { if (t_check_trans()){ route(RELAY); } exit; }
# handle retransmissions if (!is_method("ACK")) { if(t_precheck_trans()) { t_check_trans(); xlog("L_INFO", "ROUTE - Exiting after Retransmission check - method $rm"); exit; } t_check_trans(); }
route(WITHINDLG);
# record routing for dialog forming requests (in case they are routed) # - remove preloaded route headers xlog("L_INFO", "ROUTE - Removing Headers"); remove_hf("Route");
if (is_method("INVITE|SUBSCRIBE")){ t_on_failure("MANAGE_FAILURE"); xlog("L_INFO", "ROUTE - Recording Route"); record_route();
if (is_method("INVITE") && is_request()) { if (has_body("application/sdp")) { xlog("L_INFO", "ROUTE - goiing to t_on_reply[ON_REPLY]\n"); t_on_reply("ON_REPLY"); } } }
if ($rU==$null) { # request with no Username in RURI sl_send_reply("484","ROUTE - Address Incomplete"); exit; } route(TOCARRIER);
xlog("L_INFO", " ********** Route END *************");
}
# extract original source ip / port from X-forwarded-For header route[HANDLE_X_FORWARDED_FOR] { if (is_present_hf("X-Forwarded-For")) { $var(source_ip) = $(hdr(X-Forwarded-For){s.select,0,:}); $var(source_port) = $(hdr(X-Forwarded-For){s.select,1,:}); } else { $var(source_ip) = $si; $var(source_port) = $sp; } $var(to_number) = $rU; }
route[RELAY_API] { xlog("L_INFO","RELAY_API - from_ip $var(source_ip):$var(source_port) from_number $fU to_number $ru"); $http_req(all) = $null; $http_req(suspend) = 1; $http_req(timeout) = HTTP_API_TIMEOUT; $http_req(method) = "POST"; $http_req(hdr) = "Content-Type: application/json"; jansson_set("string","from_ip",$var(source_ip), "$var(http_routing_query)"); jansson_set("string","from_port",$var(source_port), "$var(http_routing_query)"); jansson_set("string","from_number",$fU, "$var(http_routing_query)"); jansson_set("string","to_number",$var(to_number) , "$var(http_routing_query)");
xlog("L_INFO","RELAY_API - API ASYNC ROUTING REQUEST: $var(http_routing_query)\n"); $http_req(body) = $var(http_routing_query); t_newtran(); http_async_query(HTTP_API_ROUTING_ENDPOINT, "RELAY_API_RESPONSE"); }
# Relay request using the API (response) route[RELAY_API_RESPONSE] {
if ($http_ok==1 && $http_rs==200) { xlog("L_INFO","RELAY_API_RESPONSE - RESPONSE: $http_rb\n");
if (jansson_get("rtjson", $http_rb, "$var(rtjson)")) { xlog("L_INFO","RELAY_API_RESPONSE - $var(rtjson)"); rtjson_init_routes("$var(rtjson)"); rtjson_push_routes(); # relay the message t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE");
route(RELAY); return; } }
send_reply(500, "API Not Available - http response = $http_rs $http_ok"); exit; }
onreply_route[ON_REPLY] {
xlog("L_INFO", "ON_REPLY - In onreply_route[ON_REPLY] $rs"); # on reply if (t_check_status("183|180|200")) { xlog("L_INFO", "ON_REPLY - Fixing Contacts"); # subst_hf("Contact","/@.*:/@EXTERNAL_IP_ADDR:/","a"); //subst_hf("Record-Route","/INTERNAL_IP_ADDR/EXTERNAL_IP_ADDR/","f"); }
if (has_body("application/sdp")){ if (sdp_remove_line_by_prefix("a=maxptime")){ xlog("L_INFO", "ON_REPLY - remove maxptime "); msg_apply_changes(); } else{ xlog("L_INFO", "ON_REPLY - did not removed maxptime "); } }
if (t_check_status("408")) { xlog("L_INFO", "ROUTE - Handling 408 Timeout\n"); }
}
route[TOCARRIER]{ #using rtjson, unsomment following line route(RELAY_API);
}
# Per SIP request initial checks route[REQINIT] { xlog("L_INFO", "REQINIT - Starting"); if (!mf_process_maxfwd_header("10")) { xlog("L_INFO", "REQINIT - 483 - Too Many Hops"); sl_send_reply("483","Too Many Hops"); exit; }
if(!sanity_check("1511", "7")) { xlog("L_INFO","REQINIT - Sanity Check -> Malformed SIP message from $si:$sp\n"); exit; } }
# Caller NAT detection route[NATDETECT] { xlog("L_INFO", "NATDETECT - Entering"); #!ifdef WITH_NAT force_rport(); if (nat_uac_test("19")) { if (is_method("REGISTER")) { xlog("L_INFO", "NATDETECT - Fix Nated Register"); fix_nated_register(); } else { if(is_first_hop()){ xlog("L_INFO", "NATDETECT - Set Contact Alias"); set_contact_alias(); } } xlog("L_INFO", "NATDETECT - Set FLT_NATS" + FLT_NATS); setflag(FLT_NATS); } #!endif xlog("L_INFO", "NATDETECT - NAT Detect set FLT_NTS = " + FLT_NATS); return; }
# Handle requests within SIP dialogs route[WITHINDLG] { xlog("L_INFO", "WITHINDLG - Entering"); if (!has_totag()) return;
if (is_present_hf("Route") && $hdrc(Route)==1) {
if (search_hf("Route", ".*EXTERNAL_IP_ADDR.*", "f")) { xlog("L_INFO", "WITHINDLG - Removing the route to self"); remove_hf("Route"); } }
# sequential request within a dialog should # take the path determined by record-routing if (loose_route()) { route(DLGURI); if (is_method("BYE|CANCEL")) { setflag(FLT_ACC); # do accounting ... setflag(FLT_ACCFAILED); # ... even if the transaction fails } else if ( is_method("ACK") ) { # ACK is forwarded statelessy xlog("L_INFO", "WITHINDLG - Going to NATMANAGE"); route(NATMANAGE); } else if ( is_method("NOTIFY") ) { #Add Record-Route for in-dialog NOTIFY as per RFC 6665. record_route(); } if(is_method("BYE")) xlog("L_INFO", "WITHINDLG - BYE message from $rU");
route(RELAY); exit; }
if ( is_method("ACK|BYE|INVITE|UPDATE") ) { if ( t_check_trans() ) { # no loose-route, but stateful ACK; # must be an ACK after a 487 # or e.g. 404 from upstream server route(RELAY); exit; } else { # ACK without matching transaction. Try to route anyway - being optimistic # since it has at least a To Tag route(RELAY); exit; } } sl_send_reply("404","Not here"); xlog("L_INFO", "WITHINDLG - Finishing WITHINDLG"); exit; }
# URI update for dialog requests route[DLGURI] { xlog("L_INFO", "WITHINDLG - Entering DLGURI"); #!ifdef WITH_NAT if(!isdsturiset()) { xlog("L_INFO", "WITHINDLG - Handle ruri ALIAS"); handle_ruri_alias(); } #!endif return; }
# Routing to foreign domains ---> NOT USED route[SIPOUT] { xlog("L_INFO", "WITHINDLG - Entering SIPOUT"); if (uri==myself){ xlog("L_INFO", "WITHINDLG - URI is MySelf!"); return; }
append_hf("P-hint: outbound\r\n"); xlog("L_INFO", "WITHINDLG - Finishing SIPOUT"); route(RELAY); exit; }
# Wrapper for relaying requests route[RELAY] { xlog("L_INFO", " ******** RELAY *******"); xlog("L_INFO", "RELAY - $si $su $ru"); # enable additional event routes for forwarded requests # - serial forking, RTP relaying handling, a.s.o. if (is_method("INVITE|BYE|CANCEL|SUBSCRIBE|UPDATE")) { if(!t_is_set("branch_route")) { xlog("L_INFO", "RELAY - branch_route NOT SET!"); t_on_branch("MANAGE_BRANCH"); } }
xlog("L_INFO", "RELAY - checking method"); if (is_method("INVITE|SUBSCRIBE|UPDATE")) { xlog("L_INFO", "RELAY - is INVITE|SUBSCRIBE|UPDATE"); if(!t_is_set("onreply_route")) { xlog("L_INFO", "RELAY - onreply_route NOT SET!"); t_on_reply("ON_REPLY"); # MANAGE_REPLY"); } }
if (is_method("INVITE")) { xlog("L_INFO", "RELAY - is INVITE"); t_on_failure("FAILED_RELAY"); if(!t_is_set("failure_route")) { xlog("L_INFO", "RELAY - failure_route NOT SET!"); t_on_failure("MANAGE_FAILURE"); } }
if (!t_relay()) { xlog("L_INFO", "RELAY - t_relay returns FALSE"); route("MANAGE_FAILURE"); #sl_reply_error(); }
xlog("L_INFO", "RELAY - exiting"); exit; }
failure_route[FAILED_RELAY] { xlog("L_INFO", "FAILED_RELAY - Entering"); if (t_check_status("[4-5][0-9][0-9]")){ xlog("L_INFO", "FAILED_RELAY - Could not reach destination endpoint!"); if (rtjson_next_route()) { xlog("L_INFO", "MANAGE_FAILURE - Getting next route"); t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE"); route(RELAY); } } }
route[NATMANAGE] { xlog("L_INFO", "NATMANAGE - Entering"); #!ifdef WITH_NAT if (is_request()) { if(has_totag()) { xlog("L_INFO", "NATMANAGE - nat=yes --- Setting FLB_NATB"); setbflag(FLB_NATB); } }
if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB) )) { xlog("L_INFO", "NATMANAGE - NO FLT_NATS/B Set!!! Getting
out of NATMANAGE"); return; }
if (is_request()) { xlog("L_INFO", "NATMANAGE - is_request - $rm from $si"); if (!has_totag()) { if(t_is_branch_route()) { xlog("L_INFO", "NATMANAGE - adding nat=yes"); add_rr_param(";nat=yes"); } } } if (is_reply()) { xlog("L_INFO", "NATMANAGE - is_reply - $rm from $si"); if(isbflagset(FLB_NATB)) { if(is_first_hop()) { xlog("L_INFO", "NATMANAGE - Set Contact Alias"); set_contact_alias(); } } }
#!endif return; }
# Manage failure routing cases route[MANAGE_FAILURE] {
xlog("L_INFO", "MANAGE_FAILURE - Entering "); route(NATMANAGE); xlog("L_INFO", "MANAGE_FAILURE - t_is_canceled"); if (t_is_canceled()) exit;
#!ifdef WITH_BLOCK3XX # block call redirect based on 3xx replies. if (t_check_status("3[0-9][0-9]")) { xlog("L_INFO", "MANAGE_FAILURE - SIP 3XX returned!!"); t_reply("404","Not found"); exit; } #!endif
#!ifdef WITH_BLOCK401407 # block call redirect based on 401, 407 replies. if (t_check_status("401|407")) { xlog("L_INFO", "MANAGE_FAILURE - SIP 401|407 returned!!"); t_reply("404","Not found"); exit; } #!endif
if (t_check_status("503")){ xlog("L_INFO", "MANAGE_FAILURE - SIP 503 returned : no destination available"); t_reply("503", "Destination not available"); exit; }
if (rtjson_next_route()) { xlog("L_INFO", "MANAGE_FAILURE - Getting next route!!"); t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE"); route(RELAY); exit; } }
# Manage outgoing branches branch_route[MANAGE_BRANCH] { xlog("L_INFO","MANAGE_BRANCH - New branch [$T_branch_idx] to $ru\n"); xlog("L_INFO", "MANAGE_BRANCH - branch_route MANAGE_BRANCH 1 "); rtjson_update_branch(); route(NATMANAGE); }
Any help would be greatly appreciated.
Thanks in advance.
Sérgio Charrua
I have found the issue! For future reference, here is the explanation.
The JSON object returned from the Routing Logique Engine is the standard object as per module's description, but with the "extra" property filled with an extra header value. The resulting SIP Message is :
INVITE sip:129292929@10.20.0.3:5060 SIP/2.0 Record-Route: sip:10.20.0.5;lr=on Via: SIP/2.0/UDP 10.20.0.5:5060 ;branch=z9hG4bK9eda.8923b369b80093ea0deadbf4aacfbe87.1 Via: SIP/2.0/UDP 10.20.0.1:5060;branch=z9hG4bK7b6c14db Max-Forwards: 69 From: "Anonymous" sip:anonymous@anonymous.invalid;tag=as0f86b6e9 To: sip:129292929@10.20.0.5 Contact: sip:anonymous@10.20.0.1:5060 Call-ID: 53d119be3283ab831a41827011395c9f@10.20.0.1:5060 CSeq: 102 INVITE User-Agent: Asterisk PBX 13.38.3 Date: Thu, 07 Mar 2024 17:16:03 GMT Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH, MESSAGE Supported: replaces, timer Content-Type: application/sdp Content-Length: 270 *ExtraHdr*: testing v=0 o=root 1450091166 1450091166 IN IP4 10.20.0.1 s=Asterisk PBX 13.38.3 c=IN IP4 10.20.0.1 t=0 0 m=audio 10570 RTP/AVP 8 0 101 a=rtpmap:8 PCMA/8000 a=rtpmap:0 PCMU/8000 a=rtpmap:101 telephone-event/8000 a=fmtp:101 0-16 a=ptime:20 a=maxptime:150 a=sendrecv
As you may observe, the extra tag is "*ExtraHdr*: testing" which is here purely for testing and not used at all!
However, as you may have guessed, the Header part (SIP) and the Body part (SDP) have no blank line to separate both parts. This is what was causing the issue! Asterisk (and probably any endpoint) would not parse the message correctly and fail to initiate RTP audio!
Looking at the documentation, RTJSON Module (kamailio.org) https://kamailio.org/docs/modules/5.5.x/modules/rtjson.html , at the end of the page, the JSON used as an example *does *include \r\n at the end of the extra header value. But nowhere in the page it is mentioned that the extra value should end with a \r\n symbol ! In fact, the extra header could contain multiple values separated by \r\n, as for example (found in documentation):
"extra": "X-Hdr-A: abc\r\nX-Hdr-B: bcd\r\n"
clearly mentioned in the example of JSON object, in the documentation, which would result in SIP header containing the following lines:
[...] Content-Type: application/sdp Content-Length: 270 X-Hdr-A: abc X-Hdr-B: bcd
v=0 o=root 1450091166 1450091166 IN IP4 10.20.0.1 [...]
It probably should be common knowledge, I will accept that, but the order of how and when the extra header is added to the SIP message is not mentioned, nor it is said that Kamailio will automatically add a \r\n at the end of each extra header value.
I would like to humbly suggest to mention in the document that any extra header should always end with \r\n symbol because it will be added at the end of the SIP Header, which requires a blank line to separate from the SIP Body.
Hope this helps someone out there!
Cheers!
*Sérgio Charrua*
On Thu, Mar 7, 2024 at 3:44 PM Sergio Charrua sergio.charrua@voip.pt wrote:
Hi all!
some additional details for this issue.
Currently, Kamailio is using RTJSON to get routes from the routing engine and forward calls to the correct route. Please note that the 2 testing endpoints and Kamailio are all in the same network, no NAT involved, and firewalls are disabled!
Following route function does the magic:
route[TOCARRIER]{ #Route to send calls to a carrier at 192.168.200.130 route(RELAY_API); #Route relay }
route[RELAY_API]{ # makes the HTTP Assync request ..... # once response is received from HTTP REST API, go to RELAY_API_RESPONSE ..... }
# Relay request using the API (response) route[RELAY_API_RESPONSE] {
if ($http_ok==1 && $http_rs==200) {
xlog("L_INFO","RELAY_API_RESPONSE - RESPONSE: $http_rb\n");
if (jansson_get("rtjson", $http_rb, "$var(rtjson)")) {
xlog("L_INFO","RELAY_API_RESPONSE - $var(rtjson)");
rtjson_init_routes("$var(rtjson)");
rtjson_push_routes();
# relay the message
t_on_branch("MANAGE_BRANCH");
t_on_failure("MANAGE_FAILURE");
route(RELAY);
return;
}
}
}
This is working correctly. However, as mentioned in previous email, when the call is forwarded to the endpoint using RTJSON module (and for testing purposes, we are using Asterisk 13.38.x as an endpoint), it results in a one-way audio issue: A Leg sends Audio Streams correctly directly to B Leg (direct media) but B Leg seems to not sending any audio, even though both endpoints are playing some Music On Hold stuff. Even TCPDUMP shows no RTP traffic from B to A, but can find traffic from A to B!
What I found out is that if I modify RELAY_API route to be as follows:
route[TOCARRIER]{
rewritehost("10.20.0.3"); #Rewrite host to be the endpoint's IP
route(RELAY);
}
The audio streams are fully working, both ways! TCPDUMP shows audio traffic both ways, no issues! The SIP Traces show the same structure, both for SIP and SDP (of course, CallID, BranchID and RURI are different), so I think the issue is *not* within endpoints, but somewhere in Kamailio (module or configuration).
Any suggestions?
*Sérgio Charrua*
On Tue, Mar 5, 2024 at 1:05 PM Sergio Charrua sergio.charrua@voip.pt wrote:
Hi all!
got a weird behavior that I cannot understand the reason for... In our LAB environment, we have 2 Asterisk instances (version 13.38.3 and chan_sip) and 1 Kamailio 5.7 in between. All servers are in the same network, so, there is no NAT involved. No RTPEngine either. Network is 10.20.0.0/24 and Asterisk #1 has IP .1 Asterisk #2 has IP .3 and Kamailio has IP .5
The Asterisk servers are used only for testing, nothing serious. However, Kamailio is setup to use RTJson requesting routes to a Routing Server on the same network. And it works fine.
Both Asterisk servers have the same dialplan, which only Answers the call and plays MOH on both ends so that RTP audio streams both ways.
When making a call on Asterisk Server #1 via command line to go directly to Asterisk Server #2 without using Kamailio (CLI> channel originate SIP/123@10.20.0.3 application MusicOnHold() ) the Asterisk #2 receives the call, answers and plays MOH too and I can see RTP streams coming from both ends correctly.
However, if I use Kamailio to proxy the call generated from Asterisk #1 to Asterisk #2, using similar command line instruction (CLI> channel originate SIP/123@10.20.0.5 application MusicOnHold() ), the call is indeed received on Kamailio who then sends it to Asterisk #2, who answers the call and plays MOH, *but* despite the audio stream being sent to Asterisk #1 it is never received, however audio from Asterisk #1 is received by Asterisk #2, which configures a typical One Way Audio issue due to NAT. This is where it gets strange, because there is no NAT, SDP on INVITE and SIP 200 messages seem OK, as far as I understand it. Also, Asterisk servers have SIP configuration with directmedia enabled and NAT disabled to make sure that media is direct. But I have also tried with directmedia disabled and NAT enabled and get identical results.
I am most probably missing some tiny detail, but I have no clue.... and I bet it is simple and stupid....
Could another pair of eyes help me with this? What is wrong? Do I really need RTPEngine even when the network has no NAT? I am sure it would work that way, but it doesn't make sense...
Here are some screenshots:
Call Scenario #1 - direct call from Asterisk #1 to Asterisk #2 without Kamailio in between:
Invite from Asterisk #1 to Asterisk #2 with direct media between both ends:
https://drive.google.com/file/d/1eLjT3nr_Rc-UBaf4QhIgZ95bjETOVvxo/view?usp=d...
Replies from Asterisk #2 to Asterisk #1 with direct media between both ends:
https://drive.google.com/file/d/11lLcB-V8rWGSrVqWiit-q9WX2FfqB6BZ/view?usp=d...
Call Scenario #2 - call from Asterisk #1 using Kamailio to relay call to Asterisk #2, with one way audio Invite from Asterisk #1 to Asterisk #2 via Kamailio with SDP details:
https://drive.google.com/file/d/1Cp9xrGcwNmQ9Ks36N_oD1Dj7lxfu-tbH/view?usp=d...
Invite from Kamailio relayed to Asterisk #2 with SDP details from Asterisk #1 identical to above:
https://drive.google.com/file/d/1mi3FCNjM3luXfENEp-0088XLgcyfXRK6/view?usp=d...
Reply from Asterisk #2 to Kamailio with SDP details:
https://drive.google.com/file/d/1TpMGe2tvpX_5SIpSbm2b3Zro9EuYwcO-/view?usp=d...
Reply from Kamailio to Asterisk #2 with SDP details from Asterisk #2 identical to above:
https://drive.google.com/file/d/12jq5APfFwVVPc0vJ3RkcXyN1hBE51fnQ/view?usp=d...
As we can see, SDP details seem OK, but if I check call flow on Asterisk #1, I can only find 1 RTP channel with audio coming from Asterisk #2
https://drive.google.com/file/d/1iEfvkylZVbthHM5kxkurWh-GAYCYLytl/view?usp=d...
and the same on Asterisk #2 :
https://drive.google.com/file/d/12rvf9Lrwp-MNZvGCwlXEEBsBRmfcinph/view?usp=d...
My Kamailio.cfg is as follows:
#!KAMAILIO # # config file for SIPProxy # - load balancing of VoIP calls # - no TPC listening # # Kamailio (OpenSER) SIP Server v3.2 # - web: http://www.kamailio.org # - git: http://sip-router.org # # # Refer to the Core CookBook at http://www.kamailio.org/dokuwiki/doku.php # for an explanation of possible statements, functions and parameters. # # Several features can be enabled using '#!define WITH_FEATURE' directives: # # *** To run in debug mode: # - define WITH_DEBUG # #!define WITH_DEBUG ###!define WITH_NAT #!define WITH_PSTN /* enables Accounting Log functions */ #!define FLT_ACC 1 /* enable Accounting of missed or failed calls */ #!define FLT_ACCMISSED 2 #!define FLT_ACCFAILED 3
/* defines DB connection string */ #!ifndef DBURL #!define DBURL "mysql://kamailio:kamailio@10.20.0.1:3306/kamailio" #!endif
# - the value for 'use_domain' parameters #!define MULTIDOMAIN 1
####### Global Parameters ######### #!ifdef WITH_DEBUG debug=4 log_stderror=no #!else debug=2 log_stderror=no #!endif
#!define FLT_DISPATCH_SETID 1 #!define FLT_FS 10 #!define FLT_NATS 5 #!define FLB_NATB 6 #!define FLB_NATSIPPING 7
#!define FLT_SRC_ALLOWED 8 #!define FLT_DST_INTERNAL_IP 9 #!define FLT_SRC_INTERNAL_IP 10
#!substdef "!INTERNAL_IP_NET!10.20.0.0/24!g" #!substdef "!INTERNAL_IP_ADDR!10.20.0.2!g" #!substdef "!EXTERNAL_IP_ADDR!10.20.0.2!g"
#!ifndef HTTP_ASYNC_CLIENT_WORKERS #!define HTTP_ASYNC_CLIENT_WORKERS 8 #!endif
/* add API http timeout */ #!define HTTP_API_TIMEOUT 5000 #!define HTTP_API_ROUTING_ENDPOINT "http://10.246.212.40:7778/get_route"
/* DMQ SIP message sharing */ #!define DMQ_PORT 5062 #!define DMQ_LISTEN "sip:10.20.0.2:5062" #!define DMQ_SERVER_ADDRESS "sip:10.20.0.2:5062" #!define DMQ_NOTIFICATION_ADDRESS "sip:10.20.0.4:5062"
memdbg=5 memlog=5
log_facility=LOG_LOCAL0 log_prefix="{$mt $hdr(CSeq) $ci} "
fork=yes children=8
/* comment the next line to enable TCP - all trunks are UDP only */ disable_tcp=yes
/* uncomment the next line to disable the auto discovery of local aliases based on revers DNS on IPs (default on) */ auto_aliases=no
port=5060
/* uncomment and configure the following line if you want Kamailio to bind on a specific interface/port/proto (default bind on all available) */ listen=udp:10.20.0.5:5060 advertise 10.20.0.5:5060 listen=tcp:10.20.0.5:5060 advertise 10.20.0.5:5060 listen=udp:10.20.0.2:5062
advertised_address="10.20.0.5";
sip_warning=no;
use_dns_failover = on; ####### Modules Section ########
#set module path mpath="/usr/local/lib64/kamailio/modules/"
loadmodule "db_mysql.so" loadmodule "jsonrpcs.so" loadmodule "kex.so" loadmodule "tm.so" loadmodule "tmx.so" loadmodule "sl.so" loadmodule "rr.so" loadmodule "pv.so" loadmodule "maxfwd.so" loadmodule "textops.so" loadmodule "siputils.so" loadmodule "xlog.so" loadmodule "sanity.so" loadmodule "ctl.so" loadmodule "acc.so" loadmodule "usrloc.so"
loadmodule "nathelper.so" #loadmodule "rtimer.so" #loadmodule "sqlops.so" # --- CPS Limiter
# --- end of CPS Limiter loadmodule "ipops.so" loadmodule "textopsx.so" loadmodule "sdpops.so" loadmodule "http_async_client.so" loadmodule "rtjson.so" loadmodule "jansson.so"
loadmodule "dmq.so" loadmodule "dmq_usrloc.so" loadmodule "htable.so" loadmodule "dialog.so"
#!ifdef WITH_DEBUG loadmodule "debugger.so" #!endif
#!ifdef WITH_DEBUG # ----- debugger params ----- modparam("debugger", "log_level_name", "exec") #!endif
# ----------------- setting module-specific parameters --------------- # ----- jsonrpcs params ----- modparam("jsonrpcs", "fifo_name", "/var/run/kamailio/kamailio_rpc.fifo") modparam("jsonrpcs", "pretty_format", 1)
# ----- rr params ----- modparam("jsonrpcs", "fifo_name", "/var/run/kamailio/kamailio_rpc.fifo") modparam("jsonrpcs", "pretty_format", 1)
# ----- rr params ----- # add value to ;lr param to cope with most of the UAs modparam("rr", "enable_full_lr", 1) # do not append from tag to the RR (no need for this script) modparam("rr", "append_fromtag", 0)
# ----- acc params ----- modparam("acc", "failed_transaction_flag", 3) modparam("acc",
"log_extra","src_user=$fU;src_domain=$fd;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd;src_ip=$si")
# ----- acc params ----- /* what special events should be accounted ? */ modparam("acc", "early_media", 0) modparam("acc", "report_ack", 0) modparam("acc", "report_cancels", 0) /* by default we do not adjust the direct of the sequential requests. if you enable this parameter, be sure the enable "append_fromtag" in "rr" module */ modparam("acc", "detect_direction", 0) /* account triggers (flags) */ modparam("acc", "log_flag", FLT_ACC) modparam("acc", "log_missed_flag", FLT_ACCMISSED) modparam("acc",
"log_extra","src_user=$fU;src_domain=$fd;src_ip=$si;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd") modparam("acc", "failed_transaction_flag", FLT_ACCFAILED) /* enhanced DB accounting */ modparam("acc", "db_flag", FLT_ACC) modparam("acc", "db_missed_flag", FLT_ACCMISSED) modparam("acc", "db_url", DBURL) modparam("acc",
"db_extra","src_user=$fU;src_domain=$fd;src_ip=$si;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd") //;calltype=$avp(calltype)")
# ----- tm params ----- # ----- the TM module enables stateful processing of SIP requests modparam("tm", "fr_timer", 5000) modparam("tm", "fr_inv_timer", 60000) modparam("tm", "remap_503_500", 0) # ----- usrloc params ----- /* enable DB persistency for location entries */ modparam("usrloc", "db_url", DBURL) modparam("usrloc", "db_mode", 2) modparam("usrloc", "use_domain", MULTIDOMAIN) # params needed for NAT traversal in other modules modparam("usrloc", "nat_bflag", FLB_NATB)
# ----- nathelper params ----- modparam("nathelper", "received_avp", "$avp(s:rcv)") modparam("nathelper", "natping_interval", 30) modparam("nathelper", "ping_nated_only", 1) modparam("nathelper", "sipping_bflag", FLB_NATSIPPING) modparam("nathelper", "sipping_from", "sip:ping@kamailio.org")
#modparam("rtimer", "timer", "name=cdr;interval=300;mode=1;") #modparam("rtimer", "exec", "timer=cdr;route=CDRS") #modparam("sqlops", "sqlcon", "ca=>mysql://kamailio:kamailiorw@10.19.139.113:3306/kamailio")
#modparam("dmq", "server_socket", DMQ_SERVER_SOCKET ) modparam("dmq", "server_address", DMQ_SERVER_ADDRESS ) modparam("dmq", "notification_address", DMQ_NOTIFICATION_ADDRESS ) modparam("dmq", "multi_notify", 1) modparam("dmq", "num_workers", 4) modparam("dmq", "ping_interval", 60) modparam("dmq_usrloc", "enable", 1)
# -- CPS Limiter modparam("htable", "htable", "rhs=>size=32;initval=0;autoexpire=10;") modparam("htable", "htable", "rhm=>size=32;initval=0;autoexpire=120;") modparam("htable", "enable_dmq", 1) modparam("htable", "dmq_init_sync", 1)
modparam("dialog", "profiles_with_value", "concurrent_calls") modparam("dialog", "enable_dmq", 1)
# ----- http_async_client params ----- modparam("http_async_client", "workers", HTTP_ASYNC_CLIENT_WORKERS) modparam("http_async_client", "connection_timeout", 2000)
####### Routing Logic ########
# main request routing logic
route { if (is_method("KDMQ") && $Rp == 5062) { dmq_handle_message(); }
xlog("L_INFO"," ********** Route START ***********");
# log the basic info regarding this call xlog("L_INFO","start|\n"); xlog("L_INFO","===================================================\n"); xlog("L_INFO","New SIP message $rm with call-ID $ci \n"); xlog("L_INFO","---------------------------------------------------\n"); xlog("L_INFO"," received $pr request $rm $ou\n"); xlog("L_INFO"," source $si:$sp\n"); xlog("L_INFO"," from $fu\n"); xlog("L_INFO"," to $tu\n"); xlog("L_INFO","---------------------------------------------------\n"); xlog("L_INFO","---------------------------------------------------\n");
# OPTIONS requests without a username in the Request-URI but one # of our domains or IPs are addressed to the proxy itself and # can be answered statelessly. if (is_method("OPTIONS")) { sl_send_reply("200","OK"); exit; }
if ($fU=="ping") { sl_send_reply("200","OK"); exit; }
# extract original source ip / port from X-forwarded-For header route(HANDLE_X_FORWARDED_FOR);
# per request initial checks route(REQINIT);
# NAT detection route(NATDETECT);
# handle requests within SIP dialogs ### only initial requests (no To tag) # CANCEL processing if (is_method("CANCEL")) { if (t_check_trans()){ route(RELAY); } exit; }
# handle retransmissions if (!is_method("ACK")) { if(t_precheck_trans()) { t_check_trans(); xlog("L_INFO", "ROUTE - Exiting after Retransmission check - method $rm"); exit; } t_check_trans(); }
route(WITHINDLG);
# record routing for dialog forming requests (in case they are routed) # - remove preloaded route headers xlog("L_INFO", "ROUTE - Removing Headers"); remove_hf("Route");
if (is_method("INVITE|SUBSCRIBE")){ t_on_failure("MANAGE_FAILURE"); xlog("L_INFO", "ROUTE - Recording Route"); record_route();
if (is_method("INVITE") && is_request()) { if (has_body("application/sdp")) { xlog("L_INFO", "ROUTE - goiing to t_on_reply[ON_REPLY]\n"); t_on_reply("ON_REPLY"); } } }
if ($rU==$null) { # request with no Username in RURI sl_send_reply("484","ROUTE - Address Incomplete"); exit; } route(TOCARRIER);
xlog("L_INFO", " ********** Route END *************");
}
# extract original source ip / port from X-forwarded-For header route[HANDLE_X_FORWARDED_FOR] { if (is_present_hf("X-Forwarded-For")) { $var(source_ip) = $(hdr(X-Forwarded-For){s.select,0,:}); $var(source_port) = $(hdr(X-Forwarded-For){s.select,1,:}); } else { $var(source_ip) = $si; $var(source_port) = $sp; } $var(to_number) = $rU; }
route[RELAY_API] { xlog("L_INFO","RELAY_API - from_ip $var(source_ip):$var(source_port) from_number $fU to_number $ru"); $http_req(all) = $null; $http_req(suspend) = 1; $http_req(timeout) = HTTP_API_TIMEOUT; $http_req(method) = "POST"; $http_req(hdr) = "Content-Type: application/json"; jansson_set("string","from_ip",$var(source_ip), "$var(http_routing_query)"); jansson_set("string","from_port",$var(source_port), "$var(http_routing_query)"); jansson_set("string","from_number",$fU, "$var(http_routing_query)"); jansson_set("string","to_number",$var(to_number) , "$var(http_routing_query)");
xlog("L_INFO","RELAY_API - API ASYNC ROUTING REQUEST: $var(http_routing_query)\n"); $http_req(body) = $var(http_routing_query); t_newtran(); http_async_query(HTTP_API_ROUTING_ENDPOINT, "RELAY_API_RESPONSE"); }
# Relay request using the API (response) route[RELAY_API_RESPONSE] {
if ($http_ok==1 && $http_rs==200) { xlog("L_INFO","RELAY_API_RESPONSE - RESPONSE: $http_rb\n");
if (jansson_get("rtjson", $http_rb, "$var(rtjson)")) { xlog("L_INFO","RELAY_API_RESPONSE - $var(rtjson)"); rtjson_init_routes("$var(rtjson)"); rtjson_push_routes(); # relay the message t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE");
route(RELAY); return; } }
send_reply(500, "API Not Available - http response = $http_rs $http_ok"); exit; }
onreply_route[ON_REPLY] {
xlog("L_INFO", "ON_REPLY - In onreply_route[ON_REPLY] $rs"); # on reply if (t_check_status("183|180|200")) { xlog("L_INFO", "ON_REPLY - Fixing Contacts"); # subst_hf("Contact","/@.*:/@EXTERNAL_IP_ADDR:/","a"); //subst_hf("Record-Route","/INTERNAL_IP_ADDR/EXTERNAL_IP_ADDR/","f"); }
if (has_body("application/sdp")){ if (sdp_remove_line_by_prefix("a=maxptime")){ xlog("L_INFO", "ON_REPLY - remove maxptime "); msg_apply_changes(); } else{ xlog("L_INFO", "ON_REPLY - did not removed maxptime "); } }
if (t_check_status("408")) { xlog("L_INFO", "ROUTE - Handling 408 Timeout\n"); }
}
route[TOCARRIER]{ #using rtjson, unsomment following line route(RELAY_API);
}
# Per SIP request initial checks route[REQINIT] { xlog("L_INFO", "REQINIT - Starting"); if (!mf_process_maxfwd_header("10")) { xlog("L_INFO", "REQINIT - 483 - Too Many Hops"); sl_send_reply("483","Too Many Hops"); exit; }
if(!sanity_check("1511", "7")) { xlog("L_INFO","REQINIT - Sanity Check -> Malformed SIP message from $si:$sp\n"); exit; } }
# Caller NAT detection route[NATDETECT] { xlog("L_INFO", "NATDETECT - Entering"); #!ifdef WITH_NAT force_rport(); if (nat_uac_test("19")) { if (is_method("REGISTER")) { xlog("L_INFO", "NATDETECT - Fix Nated Register"); fix_nated_register(); } else { if(is_first_hop()){ xlog("L_INFO", "NATDETECT - Set Contact Alias"); set_contact_alias(); } } xlog("L_INFO", "NATDETECT - Set FLT_NATS" + FLT_NATS); setflag(FLT_NATS); } #!endif xlog("L_INFO", "NATDETECT - NAT Detect set FLT_NTS = " + FLT_NATS); return; }
# Handle requests within SIP dialogs route[WITHINDLG] { xlog("L_INFO", "WITHINDLG - Entering"); if (!has_totag()) return;
if (is_present_hf("Route") && $hdrc(Route)==1) {
if (search_hf("Route", ".*EXTERNAL_IP_ADDR.*", "f")) { xlog("L_INFO", "WITHINDLG - Removing the route to self"); remove_hf("Route"); } }
# sequential request within a dialog should # take the path determined by record-routing if (loose_route()) { route(DLGURI); if (is_method("BYE|CANCEL")) { setflag(FLT_ACC); # do accounting ... setflag(FLT_ACCFAILED); # ... even if the transaction fails } else if ( is_method("ACK") ) { # ACK is forwarded statelessy xlog("L_INFO", "WITHINDLG - Going to NATMANAGE"); route(NATMANAGE); } else if ( is_method("NOTIFY") ) { #Add Record-Route for in-dialog NOTIFY as per RFC 6665. record_route(); } if(is_method("BYE")) xlog("L_INFO", "WITHINDLG - BYE message from $rU");
route(RELAY); exit; }
if ( is_method("ACK|BYE|INVITE|UPDATE") ) { if ( t_check_trans() ) { # no loose-route, but stateful ACK; # must be an ACK after a 487 # or e.g. 404 from upstream server route(RELAY); exit; } else { # ACK without matching transaction. Try to route anyway - being optimistic # since it has at least a To Tag route(RELAY); exit; } } sl_send_reply("404","Not here"); xlog("L_INFO", "WITHINDLG - Finishing WITHINDLG"); exit; }
# URI update for dialog requests route[DLGURI] { xlog("L_INFO", "WITHINDLG - Entering DLGURI"); #!ifdef WITH_NAT if(!isdsturiset()) { xlog("L_INFO", "WITHINDLG - Handle ruri ALIAS"); handle_ruri_alias(); } #!endif return; }
# Routing to foreign domains ---> NOT USED route[SIPOUT] { xlog("L_INFO", "WITHINDLG - Entering SIPOUT"); if (uri==myself){ xlog("L_INFO", "WITHINDLG - URI is MySelf!"); return; }
append_hf("P-hint: outbound\r\n"); xlog("L_INFO", "WITHINDLG - Finishing SIPOUT"); route(RELAY); exit; }
# Wrapper for relaying requests route[RELAY] { xlog("L_INFO", " ******** RELAY *******"); xlog("L_INFO", "RELAY - $si $su $ru"); # enable additional event routes for forwarded requests # - serial forking, RTP relaying handling, a.s.o. if (is_method("INVITE|BYE|CANCEL|SUBSCRIBE|UPDATE")) { if(!t_is_set("branch_route")) { xlog("L_INFO", "RELAY - branch_route NOT SET!"); t_on_branch("MANAGE_BRANCH"); } }
xlog("L_INFO", "RELAY - checking method"); if (is_method("INVITE|SUBSCRIBE|UPDATE")) { xlog("L_INFO", "RELAY - is INVITE|SUBSCRIBE|UPDATE"); if(!t_is_set("onreply_route")) { xlog("L_INFO", "RELAY - onreply_route NOT SET!"); t_on_reply("ON_REPLY"); # MANAGE_REPLY"); } }
if (is_method("INVITE")) { xlog("L_INFO", "RELAY - is INVITE"); t_on_failure("FAILED_RELAY"); if(!t_is_set("failure_route")) { xlog("L_INFO", "RELAY - failure_route NOT SET!"); t_on_failure("MANAGE_FAILURE"); } }
if (!t_relay()) { xlog("L_INFO", "RELAY - t_relay returns FALSE"); route("MANAGE_FAILURE"); #sl_reply_error(); }
xlog("L_INFO", "RELAY - exiting"); exit; }
failure_route[FAILED_RELAY] { xlog("L_INFO", "FAILED_RELAY - Entering"); if (t_check_status("[4-5][0-9][0-9]")){ xlog("L_INFO", "FAILED_RELAY - Could not reach destination endpoint!"); if (rtjson_next_route()) { xlog("L_INFO", "MANAGE_FAILURE - Getting next route"); t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE"); route(RELAY); } } }
route[NATMANAGE] { xlog("L_INFO", "NATMANAGE - Entering"); #!ifdef WITH_NAT if (is_request()) { if(has_totag()) { xlog("L_INFO", "NATMANAGE - nat=yes --- Setting FLB_NATB"); setbflag(FLB_NATB); } }
if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB) )) { xlog("L_INFO", "NATMANAGE - NO FLT_NATS/B Set!!! Getting
out of NATMANAGE"); return; }
if (is_request()) { xlog("L_INFO", "NATMANAGE - is_request - $rm from $si"); if (!has_totag()) { if(t_is_branch_route()) { xlog("L_INFO", "NATMANAGE - adding nat=yes"); add_rr_param(";nat=yes"); } } } if (is_reply()) { xlog("L_INFO", "NATMANAGE - is_reply - $rm from $si"); if(isbflagset(FLB_NATB)) { if(is_first_hop()) { xlog("L_INFO", "NATMANAGE - Set Contact Alias"); set_contact_alias(); } } }
#!endif return; }
# Manage failure routing cases route[MANAGE_FAILURE] {
xlog("L_INFO", "MANAGE_FAILURE - Entering "); route(NATMANAGE); xlog("L_INFO", "MANAGE_FAILURE - t_is_canceled"); if (t_is_canceled()) exit;
#!ifdef WITH_BLOCK3XX # block call redirect based on 3xx replies. if (t_check_status("3[0-9][0-9]")) { xlog("L_INFO", "MANAGE_FAILURE - SIP 3XX returned!!"); t_reply("404","Not found"); exit; } #!endif
#!ifdef WITH_BLOCK401407 # block call redirect based on 401, 407 replies. if (t_check_status("401|407")) { xlog("L_INFO", "MANAGE_FAILURE - SIP 401|407 returned!!"); t_reply("404","Not found"); exit; } #!endif
if (t_check_status("503")){ xlog("L_INFO", "MANAGE_FAILURE - SIP 503 returned : no destination available"); t_reply("503", "Destination not available"); exit; }
if (rtjson_next_route()) { xlog("L_INFO", "MANAGE_FAILURE - Getting next route!!"); t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE"); route(RELAY); exit; } }
# Manage outgoing branches branch_route[MANAGE_BRANCH] { xlog("L_INFO","MANAGE_BRANCH - New branch [$T_branch_idx] to $ru\n"); xlog("L_INFO", "MANAGE_BRANCH - branch_route MANAGE_BRANCH 1 "); rtjson_update_branch(); route(NATMANAGE); }
Any help would be greatly appreciated.
Thanks in advance.
Sérgio Charrua
Hey Sergio,
thanks for the detailed explanation. If you like you could create a PR for this topic against the module docs XML file.
Cheers,
Henning
From: Sergio Charrua via sr-users sr-users@lists.kamailio.org Sent: Donnerstag, 7. März 2024 18:10 To: Kamailio (SER) - Users Mailing List sr-users@lists.kamailio.org Cc: Sergio Charrua sergio.charrua@voip.pt Subject: [SR-Users] Re: direct media between UACs
I have found the issue! For future reference, here is the explanation.
The JSON object returned from the Routing Logique Engine is the standard object as per module's description, but with the "extra" property filled with an extra header value. The resulting SIP Message is :
INVITE sip:129292929@10.20.0.3:5060http://sip:129292929@10.20.0.3:5060 SIP/2.0 Record-Route: sip:10.20.0.5;lr=on Via: SIP/2.0/UDP 10.20.0.5:5060;branch=z9hG4bK9eda.8923b369b80093ea0deadbf4aacfbe87.1 Via: SIP/2.0/UDP 10.20.0.1:5060;branch=z9hG4bK7b6c14db Max-Forwards: 69 From: "Anonymous" sip:anonymous@anonymous.invalid;tag=as0f86b6e9 To: <sip:129292929@10.20.0.5mailto:sip%3A129292929@10.20.0.5> Contact: <sip:anonymous@10.20.0.1:5060http://sip:anonymous@10.20.0.1:5060> Call-ID: 53d119be3283ab831a41827011395c9f@10.20.0.1:5060http://53d119be3283ab831a41827011395c9f@10.20.0.1:5060 CSeq: 102 INVITE User-Agent: Asterisk PBX 13.38.3 Date: Thu, 07 Mar 2024 17:16:03 GMT Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH, MESSAGE Supported: replaces, timer Content-Type: application/sdp Content-Length: 270 ExtraHdr: testing v=0 o=root 1450091166 1450091166 IN IP4 10.20.0.1 s=Asterisk PBX 13.38.3 c=IN IP4 10.20.0.1 t=0 0 m=audio 10570 RTP/AVP 8 0 101 a=rtpmap:8 PCMA/8000 a=rtpmap:0 PCMU/8000 a=rtpmap:101 telephone-event/8000 a=fmtp:101 0-16 a=ptime:20 a=maxptime:150 a=sendrecv
As you may observe, the extra tag is "ExtraHdr: testing" which is here purely for testing and not used at all!
However, as you may have guessed, the Header part (SIP) and the Body part (SDP) have no blank line to separate both parts. This is what was causing the issue! Asterisk (and probably any endpoint) would not parse the message correctly and fail to initiate RTP audio!
Looking at the documentation, RTJSON Module (kamailio.org)https://kamailio.org/docs/modules/5.5.x/modules/rtjson.html , at the end of the page, the JSON used as an example does include \r\n at the end of the extra header value. But nowhere in the page it is mentioned that the extra value should end with a \r\n symbol ! In fact, the extra header could contain multiple values separated by \r\n, as for example (found in documentation):
"extra": "X-Hdr-A: abc\r\nX-Hdr-B: bcd\r\n" clearly mentioned in the example of JSON object, in the documentation, which would result in SIP header containing the following lines:
[...] Content-Type: application/sdp Content-Length: 270 X-Hdr-A: abc X-Hdr-B: bcd
v=0 o=root 1450091166 1450091166 IN IP4 10.20.0.1 [...]
It probably should be common knowledge, I will accept that, but the order of how and when the extra header is added to the SIP message is not mentioned, nor it is said that Kamailio will automatically add a \r\n at the end of each extra header value.
I would like to humbly suggest to mention in the document that any extra header should always end with \r\n symbol because it will be added at the end of the SIP Header, which requires a blank line to separate from the SIP Body.
Hope this helps someone out there!
Cheers!
Sérgio Charrua
On Thu, Mar 7, 2024 at 3:44 PM Sergio Charrua <sergio.charrua@voip.ptmailto:sergio.charrua@voip.pt> wrote: Hi all!
some additional details for this issue.
Currently, Kamailio is using RTJSON to get routes from the routing engine and forward calls to the correct route. Please note that the 2 testing endpoints and Kamailio are all in the same network, no NAT involved, and firewalls are disabled!
Following route function does the magic:
route[TOCARRIER]{ #Route to send calls to a carrier at 192.168.200.130 route(RELAY_API); #Route relay }
route[RELAY_API]{ # makes the HTTP Assync request ..... # once response is received from HTTP REST API, go to RELAY_API_RESPONSE ..... }
# Relay request using the API (response) route[RELAY_API_RESPONSE] { if ($http_ok==1 && $http_rs==200) { xlog("L_INFO","RELAY_API_RESPONSE - RESPONSE: $http_rb\n"); if (jansson_get("rtjson", $http_rb, "$var(rtjson)")) { xlog("L_INFO","RELAY_API_RESPONSE - $var(rtjson)"); rtjson_init_routes("$var(rtjson)"); rtjson_push_routes(); # relay the message t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE"); route(RELAY); return; } } }
This is working correctly. However, as mentioned in previous email, when the call is forwarded to the endpoint using RTJSON module (and for testing purposes, we are using Asterisk 13.38.x as an endpoint), it results in a one-way audio issue: A Leg sends Audio Streams correctly directly to B Leg (direct media) but B Leg seems to not sending any audio, even though both endpoints are playing some Music On Hold stuff. Even TCPDUMP shows no RTP traffic from B to A, but can find traffic from A to B!
What I found out is that if I modify RELAY_API route to be as follows:
route[TOCARRIER]{ rewritehost("10.20.0.3"); #Rewrite host to be the endpoint's IP route(RELAY); }
The audio streams are fully working, both ways! TCPDUMP shows audio traffic both ways, no issues! The SIP Traces show the same structure, both for SIP and SDP (of course, CallID, BranchID and RURI are different), so I think the issue is *not* within endpoints, but somewhere in Kamailio (module or configuration).
Any suggestions?
Sérgio Charrua
On Tue, Mar 5, 2024 at 1:05 PM Sergio Charrua <sergio.charrua@voip.ptmailto:sergio.charrua@voip.pt> wrote: Hi all!
got a weird behavior that I cannot understand the reason for... In our LAB environment, we have 2 Asterisk instances (version 13.38.3 and chan_sip) and 1 Kamailio 5.7 in between. All servers are in the same network, so, there is no NAT involved. No RTPEngine either. Network is 10.20.0.0/24http://10.20.0.0/24 and Asterisk #1 has IP .1 Asterisk #2 has IP .3 and Kamailio has IP .5
The Asterisk servers are used only for testing, nothing serious. However, Kamailio is setup to use RTJson requesting routes to a Routing Server on the same network. And it works fine.
Both Asterisk servers have the same dialplan, which only Answers the call and plays MOH on both ends so that RTP audio streams both ways.
When making a call on Asterisk Server #1 via command line to go directly to Asterisk Server #2 without using Kamailio (CLI> channel originate SIP/123@10.20.0.3mailto:123@10.20.0.3 application MusicOnHold() ) the Asterisk #2 receives the call, answers and plays MOH too and I can see RTP streams coming from both ends correctly.
However, if I use Kamailio to proxy the call generated from Asterisk #1 to Asterisk #2, using similar command line instruction (CLI> channel originate SIP/123@10.20.0.5mailto:123@10.20.0.5 application MusicOnHold() ), the call is indeed received on Kamailio who then sends it to Asterisk #2, who answers the call and plays MOH, *but* despite the audio stream being sent to Asterisk #1 it is never received, however audio from Asterisk #1 is received by Asterisk #2, which configures a typical One Way Audio issue due to NAT. This is where it gets strange, because there is no NAT, SDP on INVITE and SIP 200 messages seem OK, as far as I understand it. Also, Asterisk servers have SIP configuration with directmedia enabled and NAT disabled to make sure that media is direct. But I have also tried with directmedia disabled and NAT enabled and get identical results.
I am most probably missing some tiny detail, but I have no clue.... and I bet it is simple and stupid....
Could another pair of eyes help me with this? What is wrong? Do I really need RTPEngine even when the network has no NAT? I am sure it would work that way, but it doesn't make sense...
Here are some screenshots:
Call Scenario #1 - direct call from Asterisk #1 to Asterisk #2 without Kamailio in between:
Invite from Asterisk #1 to Asterisk #2 with direct media between both ends: https://drive.google.com/file/d/1eLjT3nr_Rc-UBaf4QhIgZ95bjETOVvxo/view?usp=d...
Replies from Asterisk #2 to Asterisk #1 with direct media between both ends: https://drive.google.com/file/d/11lLcB-V8rWGSrVqWiit-q9WX2FfqB6BZ/view?usp=d...
Call Scenario #2 - call from Asterisk #1 using Kamailio to relay call to Asterisk #2, with one way audio Invite from Asterisk #1 to Asterisk #2 via Kamailio with SDP details: https://drive.google.com/file/d/1Cp9xrGcwNmQ9Ks36N_oD1Dj7lxfu-tbH/view?usp=d...
Invite from Kamailio relayed to Asterisk #2 with SDP details from Asterisk #1 identical to above: https://drive.google.com/file/d/1mi3FCNjM3luXfENEp-0088XLgcyfXRK6/view?usp=d...
Reply from Asterisk #2 to Kamailio with SDP details: https://drive.google.com/file/d/1TpMGe2tvpX_5SIpSbm2b3Zro9EuYwcO-/view?usp=d...
Reply from Kamailio to Asterisk #2 with SDP details from Asterisk #2 identical to above: https://drive.google.com/file/d/12jq5APfFwVVPc0vJ3RkcXyN1hBE51fnQ/view?usp=d...
As we can see, SDP details seem OK, but if I check call flow on Asterisk #1, I can only find 1 RTP channel with audio coming from Asterisk #2 https://drive.google.com/file/d/1iEfvkylZVbthHM5kxkurWh-GAYCYLytl/view?usp=d...
and the same on Asterisk #2 : https://drive.google.com/file/d/12rvf9Lrwp-MNZvGCwlXEEBsBRmfcinph/view?usp=d...
My Kamailio.cfg is as follows:
#!KAMAILIO # # config file for SIPProxy # - load balancing of VoIP calls # - no TPC listening # # Kamailio (OpenSER) SIP Server v3.2 # - web: http://www.kamailio.org # - git: http://sip-router.org # # # Refer to the Core CookBook at http://www.kamailio.org/dokuwiki/doku.php # for an explanation of possible statements, functions and parameters. # # Several features can be enabled using '#!define WITH_FEATURE' directives: # # *** To run in debug mode: # - define WITH_DEBUG # #!define WITH_DEBUG ###!define WITH_NAT #!define WITH_PSTN /* enables Accounting Log functions */ #!define FLT_ACC 1 /* enable Accounting of missed or failed calls */ #!define FLT_ACCMISSED 2 #!define FLT_ACCFAILED 3
/* defines DB connection string */ #!ifndef DBURL #!define DBURL "mysql://kamailio:kamailio@10.20.0.1:3306/kamailiohttp://kamailio:kamailio@10.20.0.1:3306/kamailio" #!endif
# - the value for 'use_domain' parameters #!define MULTIDOMAIN 1
####### Global Parameters ######### #!ifdef WITH_DEBUG debug=4 log_stderror=no #!else debug=2 log_stderror=no #!endif
#!define FLT_DISPATCH_SETID 1 #!define FLT_FS 10 #!define FLT_NATS 5 #!define FLB_NATB 6 #!define FLB_NATSIPPING 7
#!define FLT_SRC_ALLOWED 8 #!define FLT_DST_INTERNAL_IP 9 #!define FLT_SRC_INTERNAL_IP 10
#!substdef "!INTERNAL_IP_NET!10.20.0.0/24!ghttp://10.20.0.0/24!g" #!substdef "!INTERNAL_IP_ADDR!10.20.0.2!g" #!substdef "!EXTERNAL_IP_ADDR!10.20.0.2!g"
#!ifndef HTTP_ASYNC_CLIENT_WORKERS #!define HTTP_ASYNC_CLIENT_WORKERS 8 #!endif
/* add API http timeout */ #!define HTTP_API_TIMEOUT 5000 #!define HTTP_API_ROUTING_ENDPOINT "http://10.246.212.40:7778/get_route"
/* DMQ SIP message sharing */ #!define DMQ_PORT 5062 #!define DMQ_LISTEN "sip:10.20.0.2:5062http://10.20.0.2:5062" #!define DMQ_SERVER_ADDRESS "sip:10.20.0.2:5062http://10.20.0.2:5062" #!define DMQ_NOTIFICATION_ADDRESS "sip:10.20.0.4:5062http://10.20.0.4:5062"
memdbg=5 memlog=5
log_facility=LOG_LOCAL0 log_prefix="{$mt $hdr(CSeq) $ci} "
fork=yes children=8
/* comment the next line to enable TCP - all trunks are UDP only */ disable_tcp=yes
/* uncomment the next line to disable the auto discovery of local aliases based on revers DNS on IPs (default on) */ auto_aliases=no
port=5060
/* uncomment and configure the following line if you want Kamailio to bind on a specific interface/port/proto (default bind on all available) */ listen=udp:10.20.0.5:5060http://10.20.0.5:5060 advertise 10.20.0.5:5060http://10.20.0.5:5060 listen=tcp:10.20.0.5:5060http://10.20.0.5:5060 advertise 10.20.0.5:5060http://10.20.0.5:5060 listen=udp:10.20.0.2:5062http://10.20.0.2:5062
advertised_address="10.20.0.5";
sip_warning=no;
use_dns_failover = on; ####### Modules Section ########
#set module path mpath="/usr/local/lib64/kamailio/modules/"
loadmodule "db_mysql.so" loadmodule "jsonrpcs.so" loadmodule "kex.so" loadmodule "tm.so" loadmodule "tmx.so" loadmodule "sl.so" loadmodule "rr.so" loadmodule "pv.so" loadmodule "maxfwd.so" loadmodule "textops.so" loadmodule "siputils.so" loadmodule "xlog.so" loadmodule "sanity.so" loadmodule "ctl.so" loadmodule "acc.so" loadmodule "usrloc.so"
loadmodule "nathelper.so" #loadmodule "rtimer.so" #loadmodule "sqlops.so" # --- CPS Limiter
# --- end of CPS Limiter loadmodule "ipops.so" loadmodule "textopsx.so" loadmodule "sdpops.so" loadmodule "http_async_client.so" loadmodule "rtjson.so" loadmodule "jansson.so"
loadmodule "dmq.so" loadmodule "dmq_usrloc.so" loadmodule "htable.so" loadmodule "dialog.so"
#!ifdef WITH_DEBUG loadmodule "debugger.so" #!endif
#!ifdef WITH_DEBUG # ----- debugger params ----- modparam("debugger", "log_level_name", "exec") #!endif
# ----------------- setting module-specific parameters --------------- # ----- jsonrpcs params ----- modparam("jsonrpcs", "fifo_name", "/var/run/kamailio/kamailio_rpc.fifo") modparam("jsonrpcs", "pretty_format", 1)
# ----- rr params ----- modparam("jsonrpcs", "fifo_name", "/var/run/kamailio/kamailio_rpc.fifo") modparam("jsonrpcs", "pretty_format", 1)
# ----- rr params ----- # add value to ;lr param to cope with most of the UAs modparam("rr", "enable_full_lr", 1) # do not append from tag to the RR (no need for this script) modparam("rr", "append_fromtag", 0)
# ----- acc params ----- modparam("acc", "failed_transaction_flag", 3) modparam("acc", "log_extra","src_user=$fU;src_domain=$fd;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd;src_ip=$si")
# ----- acc params ----- /* what special events should be accounted ? */ modparam("acc", "early_media", 0) modparam("acc", "report_ack", 0) modparam("acc", "report_cancels", 0) /* by default we do not adjust the direct of the sequential requests. if you enable this parameter, be sure the enable "append_fromtag" in "rr" module */ modparam("acc", "detect_direction", 0) /* account triggers (flags) */ modparam("acc", "log_flag", FLT_ACC) modparam("acc", "log_missed_flag", FLT_ACCMISSED) modparam("acc", "log_extra","src_user=$fU;src_domain=$fd;src_ip=$si;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd") modparam("acc", "failed_transaction_flag", FLT_ACCFAILED) /* enhanced DB accounting */ modparam("acc", "db_flag", FLT_ACC) modparam("acc", "db_missed_flag", FLT_ACCMISSED) modparam("acc", "db_url", DBURL) modparam("acc", "db_extra","src_user=$fU;src_domain=$fd;src_ip=$si;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd") //;calltype=$avp(calltype)")
# ----- tm params ----- # ----- the TM module enables stateful processing of SIP requests modparam("tm", "fr_timer", 5000) modparam("tm", "fr_inv_timer", 60000) modparam("tm", "remap_503_500", 0) # ----- usrloc params ----- /* enable DB persistency for location entries */ modparam("usrloc", "db_url", DBURL) modparam("usrloc", "db_mode", 2) modparam("usrloc", "use_domain", MULTIDOMAIN) # params needed for NAT traversal in other modules modparam("usrloc", "nat_bflag", FLB_NATB)
# ----- nathelper params ----- modparam("nathelper", "received_avp", "$avp(s:rcv)") modparam("nathelper", "natping_interval", 30) modparam("nathelper", "ping_nated_only", 1) modparam("nathelper", "sipping_bflag", FLB_NATSIPPING) modparam("nathelper", "sipping_from", "sip:ping@kamailio.orgmailto:sip%3Aping@kamailio.org")
#modparam("rtimer", "timer", "name=cdr;interval=300;mode=1;") #modparam("rtimer", "exec", "timer=cdr;route=CDRS") #modparam("sqlops", "sqlcon", "ca=>mysql://kamailio:kamailiorw@10.19.139.113:3306/kamailiohttp://kamailio:kamailiorw@10.19.139.113:3306/kamailio")
#modparam("dmq", "server_socket", DMQ_SERVER_SOCKET ) modparam("dmq", "server_address", DMQ_SERVER_ADDRESS ) modparam("dmq", "notification_address", DMQ_NOTIFICATION_ADDRESS ) modparam("dmq", "multi_notify", 1) modparam("dmq", "num_workers", 4) modparam("dmq", "ping_interval", 60) modparam("dmq_usrloc", "enable", 1)
# -- CPS Limiter modparam("htable", "htable", "rhs=>size=32;initval=0;autoexpire=10;") modparam("htable", "htable", "rhm=>size=32;initval=0;autoexpire=120;") modparam("htable", "enable_dmq", 1) modparam("htable", "dmq_init_sync", 1)
modparam("dialog", "profiles_with_value", "concurrent_calls") modparam("dialog", "enable_dmq", 1)
# ----- http_async_client params ----- modparam("http_async_client", "workers", HTTP_ASYNC_CLIENT_WORKERS) modparam("http_async_client", "connection_timeout", 2000)
####### Routing Logic ########
# main request routing logic
route { if (is_method("KDMQ") && $Rp == 5062) { dmq_handle_message(); }
xlog("L_INFO"," ********** Route START ***********");
# log the basic info regarding this call xlog("L_INFO","start|\n"); xlog("L_INFO","===================================================\n"); xlog("L_INFO","New SIP message $rm with call-ID $ci \n"); xlog("L_INFO","---------------------------------------------------\n"); xlog("L_INFO"," received $pr request $rm $ou\n"); xlog("L_INFO"," source $si:$sp\n"); xlog("L_INFO"," from $fu\n"); xlog("L_INFO"," to $tu\n"); xlog("L_INFO","---------------------------------------------------\n"); xlog("L_INFO","---------------------------------------------------\n");
# OPTIONS requests without a username in the Request-URI but one # of our domains or IPs are addressed to the proxy itself and # can be answered statelessly. if (is_method("OPTIONS")) { sl_send_reply("200","OK"); exit; }
if ($fU=="ping") { sl_send_reply("200","OK"); exit; }
# extract original source ip / port from X-forwarded-For header route(HANDLE_X_FORWARDED_FOR);
# per request initial checks route(REQINIT);
# NAT detection route(NATDETECT);
# handle requests within SIP dialogs ### only initial requests (no To tag) # CANCEL processing if (is_method("CANCEL")) { if (t_check_trans()){ route(RELAY); } exit; }
# handle retransmissions if (!is_method("ACK")) { if(t_precheck_trans()) { t_check_trans(); xlog("L_INFO", "ROUTE - Exiting after Retransmission check - method $rm"); exit; } t_check_trans(); }
route(WITHINDLG);
# record routing for dialog forming requests (in case they are routed) # - remove preloaded route headers xlog("L_INFO", "ROUTE - Removing Headers"); remove_hf("Route");
if (is_method("INVITE|SUBSCRIBE")){ t_on_failure("MANAGE_FAILURE"); xlog("L_INFO", "ROUTE - Recording Route"); record_route();
if (is_method("INVITE") && is_request()) { if (has_body("application/sdp")) { xlog("L_INFO", "ROUTE - goiing to t_on_reply[ON_REPLY]\n"); t_on_reply("ON_REPLY"); } } }
if ($rU==$null) { # request with no Username in RURI sl_send_reply("484","ROUTE - Address Incomplete"); exit; } route(TOCARRIER);
xlog("L_INFO", " ********** Route END *************");
}
# extract original source ip / port from X-forwarded-For header route[HANDLE_X_FORWARDED_FOR] { if (is_present_hf("X-Forwarded-For")) { $var(source_ip) = $(hdr(X-Forwarded-For){s.select,0,:}); $var(source_port) = $(hdr(X-Forwarded-For){s.select,1,:}); } else { $var(source_ip) = $si; $var(source_port) = $sp; } $var(to_number) = $rU; }
route[RELAY_API] { xlog("L_INFO","RELAY_API - from_ip $var(source_ip):$var(source_port) from_number $fU to_number $ru"); $http_req(all) = $null; $http_req(suspend) = 1; $http_req(timeout) = HTTP_API_TIMEOUT; $http_req(method) = "POST"; $http_req(hdr) = "Content-Type: application/json"; jansson_set("string","from_ip",$var(source_ip), "$var(http_routing_query)"); jansson_set("string","from_port",$var(source_port), "$var(http_routing_query)"); jansson_set("string","from_number",$fU, "$var(http_routing_query)"); jansson_set("string","to_number",$var(to_number) , "$var(http_routing_query)");
xlog("L_INFO","RELAY_API - API ASYNC ROUTING REQUEST: $var(http_routing_query)\n"); $http_req(body) = $var(http_routing_query); t_newtran(); http_async_query(HTTP_API_ROUTING_ENDPOINT, "RELAY_API_RESPONSE"); }
# Relay request using the API (response) route[RELAY_API_RESPONSE] {
if ($http_ok==1 && $http_rs==200) { xlog("L_INFO","RELAY_API_RESPONSE - RESPONSE: $http_rb\n");
if (jansson_get("rtjson", $http_rb, "$var(rtjson)")) { xlog("L_INFO","RELAY_API_RESPONSE - $var(rtjson)"); rtjson_init_routes("$var(rtjson)"); rtjson_push_routes(); # relay the message t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE");
route(RELAY); return; } }
send_reply(500, "API Not Available - http response = $http_rs $http_ok"); exit; }
onreply_route[ON_REPLY] {
xlog("L_INFO", "ON_REPLY - In onreply_route[ON_REPLY] $rs"); # on reply if (t_check_status("183|180|200")) { xlog("L_INFO", "ON_REPLY - Fixing Contacts"); # subst_hf("Contact","/@.*:/@EXTERNAL_IP_ADDR:/mailto:/@.*:/@EXTERNAL_IP_ADDR:/","a"); //subst_hf("Record-Route","/INTERNAL_IP_ADDR/EXTERNAL_IP_ADDR/","f"); }
if (has_body("application/sdp")){ if (sdp_remove_line_by_prefix("a=maxptime")){ xlog("L_INFO", "ON_REPLY - remove maxptime "); msg_apply_changes(); } else{ xlog("L_INFO", "ON_REPLY - did not removed maxptime "); } }
if (t_check_status("408")) { xlog("L_INFO", "ROUTE - Handling 408 Timeout\n"); }
}
route[TOCARRIER]{ #using rtjson, unsomment following line route(RELAY_API);
}
# Per SIP request initial checks route[REQINIT] { xlog("L_INFO", "REQINIT - Starting"); if (!mf_process_maxfwd_header("10")) { xlog("L_INFO", "REQINIT - 483 - Too Many Hops"); sl_send_reply("483","Too Many Hops"); exit; }
if(!sanity_check("1511", "7")) { xlog("L_INFO","REQINIT - Sanity Check -> Malformed SIP message from $si:$sp\n"); exit; } }
# Caller NAT detection route[NATDETECT] { xlog("L_INFO", "NATDETECT - Entering"); #!ifdef WITH_NAT force_rport(); if (nat_uac_test("19")) { if (is_method("REGISTER")) { xlog("L_INFO", "NATDETECT - Fix Nated Register"); fix_nated_register(); } else { if(is_first_hop()){ xlog("L_INFO", "NATDETECT - Set Contact Alias"); set_contact_alias(); } } xlog("L_INFO", "NATDETECT - Set FLT_NATS" + FLT_NATS); setflag(FLT_NATS); } #!endif xlog("L_INFO", "NATDETECT - NAT Detect set FLT_NTS = " + FLT_NATS); return; }
# Handle requests within SIP dialogs route[WITHINDLG] { xlog("L_INFO", "WITHINDLG - Entering"); if (!has_totag()) return;
if (is_present_hf("Route") && $hdrc(Route)==1) {
if (search_hf("Route", ".*EXTERNAL_IP_ADDR.*", "f")) { xlog("L_INFO", "WITHINDLG - Removing the route to self"); remove_hf("Route"); } }
# sequential request within a dialog should # take the path determined by record-routing if (loose_route()) { route(DLGURI); if (is_method("BYE|CANCEL")) { setflag(FLT_ACC); # do accounting ... setflag(FLT_ACCFAILED); # ... even if the transaction fails } else if ( is_method("ACK") ) { # ACK is forwarded statelessy xlog("L_INFO", "WITHINDLG - Going to NATMANAGE"); route(NATMANAGE); } else if ( is_method("NOTIFY") ) { #Add Record-Route for in-dialog NOTIFY as per RFC 6665. record_route(); } if(is_method("BYE")) xlog("L_INFO", "WITHINDLG - BYE message from $rU");
route(RELAY); exit; }
if ( is_method("ACK|BYE|INVITE|UPDATE") ) { if ( t_check_trans() ) { # no loose-route, but stateful ACK; # must be an ACK after a 487 # or e.g. 404 from upstream server route(RELAY); exit; } else { # ACK without matching transaction. Try to route anyway - being optimistic # since it has at least a To Tag route(RELAY); exit; } } sl_send_reply("404","Not here"); xlog("L_INFO", "WITHINDLG - Finishing WITHINDLG"); exit; }
# URI update for dialog requests route[DLGURI] { xlog("L_INFO", "WITHINDLG - Entering DLGURI"); #!ifdef WITH_NAT if(!isdsturiset()) { xlog("L_INFO", "WITHINDLG - Handle ruri ALIAS"); handle_ruri_alias(); } #!endif return; }
# Routing to foreign domains ---> NOT USED route[SIPOUT] { xlog("L_INFO", "WITHINDLG - Entering SIPOUT"); if (uri==myself){ xlog("L_INFO", "WITHINDLG - URI is MySelf!"); return; }
append_hf("P-hint: outbound\r\n"); xlog("L_INFO", "WITHINDLG - Finishing SIPOUT"); route(RELAY); exit; }
# Wrapper for relaying requests route[RELAY] { xlog("L_INFO", " ******** RELAY *******"); xlog("L_INFO", "RELAY - $si $su $ru"); # enable additional event routes for forwarded requests # - serial forking, RTP relaying handling, a.s.o. if (is_method("INVITE|BYE|CANCEL|SUBSCRIBE|UPDATE")) { if(!t_is_set("branch_route")) { xlog("L_INFO", "RELAY - branch_route NOT SET!"); t_on_branch("MANAGE_BRANCH"); } }
xlog("L_INFO", "RELAY - checking method"); if (is_method("INVITE|SUBSCRIBE|UPDATE")) { xlog("L_INFO", "RELAY - is INVITE|SUBSCRIBE|UPDATE"); if(!t_is_set("onreply_route")) { xlog("L_INFO", "RELAY - onreply_route NOT SET!"); t_on_reply("ON_REPLY"); # MANAGE_REPLY"); } }
if (is_method("INVITE")) { xlog("L_INFO", "RELAY - is INVITE"); t_on_failure("FAILED_RELAY"); if(!t_is_set("failure_route")) { xlog("L_INFO", "RELAY - failure_route NOT SET!"); t_on_failure("MANAGE_FAILURE"); } }
if (!t_relay()) { xlog("L_INFO", "RELAY - t_relay returns FALSE"); route("MANAGE_FAILURE"); #sl_reply_error(); }
xlog("L_INFO", "RELAY - exiting"); exit; }
failure_route[FAILED_RELAY] { xlog("L_INFO", "FAILED_RELAY - Entering"); if (t_check_status("[4-5][0-9][0-9]")){ xlog("L_INFO", "FAILED_RELAY - Could not reach destination endpoint!"); if (rtjson_next_route()) { xlog("L_INFO", "MANAGE_FAILURE - Getting next route"); t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE"); route(RELAY); } } }
route[NATMANAGE] { xlog("L_INFO", "NATMANAGE - Entering"); #!ifdef WITH_NAT if (is_request()) { if(has_totag()) { xlog("L_INFO", "NATMANAGE - nat=yes --- Setting FLB_NATB"); setbflag(FLB_NATB); } }
if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB) )) { xlog("L_INFO", "NATMANAGE - NO FLT_NATS/B Set!!! Getting out of NATMANAGE"); return; }
if (is_request()) { xlog("L_INFO", "NATMANAGE - is_request - $rm from $si"); if (!has_totag()) { if(t_is_branch_route()) { xlog("L_INFO", "NATMANAGE - adding nat=yes"); add_rr_param(";nat=yes"); } } } if (is_reply()) { xlog("L_INFO", "NATMANAGE - is_reply - $rm from $si"); if(isbflagset(FLB_NATB)) { if(is_first_hop()) { xlog("L_INFO", "NATMANAGE - Set Contact Alias"); set_contact_alias(); } } }
#!endif return; }
# Manage failure routing cases route[MANAGE_FAILURE] {
xlog("L_INFO", "MANAGE_FAILURE - Entering ");
route(NATMANAGE);
xlog("L_INFO", "MANAGE_FAILURE - t_is_canceled"); if (t_is_canceled()) exit;
#!ifdef WITH_BLOCK3XX # block call redirect based on 3xx replies. if (t_check_status("3[0-9][0-9]")) { xlog("L_INFO", "MANAGE_FAILURE - SIP 3XX returned!!"); t_reply("404","Not found"); exit; } #!endif
#!ifdef WITH_BLOCK401407 # block call redirect based on 401, 407 replies. if (t_check_status("401|407")) { xlog("L_INFO", "MANAGE_FAILURE - SIP 401|407 returned!!"); t_reply("404","Not found"); exit; } #!endif
if (t_check_status("503")){ xlog("L_INFO", "MANAGE_FAILURE - SIP 503 returned : no destination available"); t_reply("503", "Destination not available"); exit; }
if (rtjson_next_route()) { xlog("L_INFO", "MANAGE_FAILURE - Getting next route!!"); t_on_branch("MANAGE_BRANCH"); t_on_failure("MANAGE_FAILURE"); route(RELAY); exit; } }
# Manage outgoing branches branch_route[MANAGE_BRANCH] { xlog("L_INFO","MANAGE_BRANCH - New branch [$T_branch_idx] to $ru\n"); xlog("L_INFO", "MANAGE_BRANCH - branch_route MANAGE_BRANCH 1 "); rtjson_update_branch(); route(NATMANAGE); }
Any help would be greatly appreciated.
Thanks in advance.
Sérgio Charrua