Re: TLS/SRTP bridging
Jeremy Weibert via sr-users <[email protected]>
| Newsgroups | gmane.comp.voip.ser |
|---|---|
| Message-ID | <[email protected]> |
I'm attaching my config (changed the substitutions to fake ones).
On 7/11/2025 10:23 PM, Jeremy Weibert via sr-users wrote:
> I'm new to Kamailio, and looking for some guidance.
>
> I have a very large global PBX network (NEC). I have mobile users, that for many years we have had connecting through VPN. This has worked fine for us until recently, various circumstances have made this unreliable. In searching for a better way to handle this, I came across Kamailio. I want the mobile users using TLS/SRTP, the NEC has various limitations on that, but more importantly, I'm not putting my PBXes on the internet. I've been at this for a few days now, learning and trying to see how I can do this with Kamailio, when I eventually came across this: https://www.fredposner.com/2309/kamailio-simple-tls-gateway/#:~:text=One%20feature%20that%20truly%20shines,aka%20to%20the%20PBX.
>
> Seems this is exactly what I need. The only difference really is that I have an additional firewall between the internet and the Kamailio server, with the external IP being NATed.
>
> In the famous last words of all users "it didn't work". Specifically, registrations seem to work just fine (the NEC acts as the registrar). However, when making a call:
>
> - The original INVITE from the mobile user (port 5061, TLS) reaches Kamailio and is then proxied via UDP to the PBX.
> - The PBX behaves appears to behave as a B2BUA, creating a completely new INVITE with a different Call-ID and sending it back to Kamailio.
> - Kamailio, seeing this as a completely new INVITE coming in on UDP (not TLS), rejects it (403 Accepting TLS Only).
> - Likewise, if a call originates from the PBX side, same thing.
>
> I figure, if the INVITE is coming from the PBX, I can just skip the TLS check, as it's coming from a trusted IP. However, the INVITE looks like:
> INFO: {1 udp 1 INVITE 7776d675@PBXIP} <script>: {R-MAIN] Incoming request from PRIVATEIP using udp
>
> In other words, the INVITE is showing as coming from Kamailio.
>
> I can't use the alias tag that Kamailio sets initially as the PBX strips it with the new INVITE. I figure to just explicitly send it to registered client over TLS by just rewriting the destination URI, and then it goes into an infinite loop.
>
> I got a bit lost at this point and if someone could help point me in the right direction, I would be eternally grateful.
> __________________________________________________________
> Kamailio - Users Mailing List - Non Commercial Discussions -- [email protected]
> To unsubscribe send an email to [email protected]
> Important: keep the mailing list in the recipients, do not reply only to the sender!
__________________________________________________________
Kamailio - Users Mailing List - Non Commercial Discussions -- [email protected]
To unsubscribe send an email to [email protected]
Important: keep the mailing list in the recipients, do not reply only to the sender!
kamailio.cfg
(text/plain, 15.5 KB)
#!KAMAILIO # # Kamailio (OpenSER) SIP Server v5.3 # Kamailio as SIP Edge Router # April 2020 # Need help? # * Free/Official: # - Documentation: https://www.kamailio.org/w/documentation/ # - Wiki: https://www.kamailio.org/wiki/ # - Modules: https://www.kamailio.org/docs/modules/stable/ # - IRC: irc.freenode.net #kamailio # - Mailing Lists: https://www.kamailio.org/w/mailing-lists/ # * Commercial: # - LOD: https://lod.com [email protected] +1 (224) 334-3733 # - Business Directory: https://www.kamailio.org/w/business-directory/ ####### Define Substitutions ####### # replace with values specific to your needs #!substdef "/PRIVATEIP/10.20.20.137/" #!substdef "/PUBLICIP/12.13.14.24/" #!substdef "/PUBDOMAIN/call.now.today/" #!substdef "/PBXIP/10.10.10.22/" #!substdef "/APIKEY/thisismyvettedapikey/" ####### Define Code Blocks ######### #!define WITH_DEBUG #!define WITH_ANTIFLOOD #!define WITH_APIBAN # define WITH_SIPDEBUG ####### Global Parameters ######### ### LOG Levels: 3=DBG, 2=INFO, 1=NOTICE, 0=WARN, -1=ERR #!ifdef WITH_DEBUG debug=4 log_stderror=yes #!else debug=2 log_stderror=no #!endif memdbg=5 memlog=5 pv_buffer_size=65536 pv_cache_limit=65536 log_facility=LOG_LOCAL0 log_prefix="{$mt $proto $hdr(CSeq) $ci} " children=4 enable_tls=yes tls_max_connections=2048 tcp_connection_lifetime=3605 dns=no dns_cache_init=off dns_srv_lb=no dns_try_naptr=no maxbuffer=65536 max_while_loops=250 rev_dns=no use_dns_cache=no auto_aliases=no mhomed=0 listen=udp:PRIVATEIP:5060 listen=tls:PRIVATEIP:5061 advertise PUBLICIP:5061 alias=PRIVATEIP:5060 alias=PRIVATEIP:5061 alias=PUBLICIP:5061 alias=PUBDOMAIN:5061 port=5060 #!ifdef WITH_SIPDEBUG onsend_route_reply=yes #!endif server_header="Server: kamailio 5.8" ####### Modules Section ######## loadmodule "tls.so" loadmodule "jsonrpcs.so" loadmodule "kex.so" loadmodule "corex.so" loadmodule "tm.so" loadmodule "tmx.so" loadmodule "sl.so" loadmodule "rr.so" loadmodule "pv.so" loadmodule "maxfwd.so" loadmodule "outbound.so" loadmodule "textops.so" loadmodule "siputils.so" loadmodule "xlog.so" loadmodule "sanity.so" loadmodule "ctl.so" loadmodule "cfg_rpc.so" loadmodule "rtimer.so" loadmodule "json.so" loadmodule "http_client.so" loadmodule "jansson.so" loadmodule "htable.so" loadmodule "textopsx.so" loadmodule "rtpengine.so" loadmodule "nathelper.so" loadmodule "path.so" #!ifdef WITH_ANTIFLOOD loadmodule "pike.so" #!endif #!ifdef WITH_DEBUG loadmodule "debugger.so" #!endif # ----------------- setting module-specific parameters --------------- # ----- jsonrpcs params ----- modparam("jsonrpcs", "pretty_format", 1) # ----- ctl params ----- modparam("ctl", "binrpc", "unix:/var/run/kamailio/kamailio_ctl") modparam("ctl", "binrpc", "tcp:localhost:2046") # ----- tm params ----- modparam("tm", "failure_reply_mode", 3) modparam("tm", "noisy_ctimer", 1) modparam("tm", "cancel_b_method", 1) # ----- rr params ----- modparam("rr", "enable_full_lr", 1) modparam("rr", "append_fromtag", 1) modparam("rr", "ignore_sips", 1) modparam("rr", "enable_double_rr", 2) # ----- path ----- modparam("path", "enable_r2", 1) # ----- rtimer params ----- #!ifdef WITH_APIBAN modparam("rtimer", "timer", "name=apiban;interval=300;mode=1;") modparam("rtimer", "exec", "timer=apiban;route=APIBAN") modparam("htable", "htable", "apiban=>size=11;") modparam("htable", "htable", "apibanctl=>size=1;initval=0;") #!endif # ----- tls params ----- modparam("tls", "tls_method", "TLSv1.2+") modparam("tls", "verify_certificate", 0) modparam("tls", "require_certificate", 0) modparam("tls", "private_key", "/etc/kamailio/certs/kamailio.key") modparam("tls", "certificate", "/etc/kamailio/certs/kamailio.crt") # ----- rtpproxy params ----- modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:2223") modparam("rtpengine", "rtpengine_disable_tout", 15) modparam("rtpengine", "rtpengine_tout_ms", 2000) modparam("rtpengine", "rtpengine_retr", 2) #!ifdef WITH_ANTIFLOOD # ----- pike params ----- modparam("pike", "sampling_time_unit", 2) modparam("pike", "reqs_density_per_unit", 16) modparam("pike", "remove_latency", 4) # ----- htable params ----- /* ip ban htable with autoexpire after 5 minutes */ modparam("htable", "htable", "ipban=>size=8;autoexpire=14400") modparam("htable", "htable", "a=>size=256;autoexpire=14400") #!endif #!ifdef WITH_DEBUG # ----- debugger params ----- modparam("debugger", "cfgtrace", 1) modparam("debugger", "log_level_name", "exec") #!endif # do not follow redirects modparam("http_client", "httpredirect", 0) modparam("http_client", "connection_timeout", 4) modparam("http_client", "verify_peer", 0) modparam("http_client", "verify_host", 0) modparam("http_client", "keep_connections", 1) ####### Routing Logic ######## request_route { #!ifdef WITH_SIPDEBUG xlog("L_INFO","[R-MAIN] SIP DEBUG: $mb\n"); #!endif # per request initial checks route(REQINIT); # CANCEL processing if (is_method("CANCEL")) { rtpengine_manage(); handle_ruri_alias(); if (!t_relay_cancel()) { xlog("L_INFO", "[MAIN] No matching transaction or other error on CANCEL\n"); sl_send_reply("500", "Internal Server Error M1"); } exit; } # handle retransmissions if (!is_method("ACK")) { if (t_precheck_trans()) { t_check_trans(); exit; } t_check_trans(); } # handle requests within SIP dialogs route(WITHINDLG); if ($si=="PBXIP") { if (!loose_route()) { switch($rc) { case -2: sl_send_reply("403", "Forbidden"); exit; } } remove_hf("Route"); force_rport(); $fs = "tls:PRIVATEIP:5061"; } else { xlog("L_INFO", "[R-MAIN] Incoming request from $si using $proto\n"); if ($si == "PRIVATEIP") { xlog("L_INFO", "[R-MAIN] Accepting internal relayed INVITE from self ($si)\n"); } else { if($si == "PBXIP") { xlog("L_INFO", "[R-MAIN] Accepting INVITE from trusted PBX ($si)\n"); } else { if(proto != TLS) { xlog("L_INFO", "[R-MAIN] rejecting non tls $ru from $si\n"); sl_send_reply("403", "Accepting TLS Only"); exit; } } } if (is_method("REGISTER")) { remove_hf("Route"); add_path(); $du = "sip:PBXIP:5060"; } else { if ($si == "PRIVATEIP") { xlog("L_INFO", "[R-MAIN] Routing internal INVITE explicitly to TLS client\n"); $du = "sip:" + $rU + "@PRIVATEIP:5061;transport=tls"; $fs = "tls:PRIVATEIP:5061"; } else { if ($rU == $null) { sl_send_reply("484", "Address Incomplete"); exit; } $ru = "sip:" + $rU + "@PBXIP:5060"; } } if ($(ct{tobody.user})!=$null) { $var(ctuser) = $(ct{tobody.user}); $var(cthost) = $si; $var(ctport) = $sp; remove_hf("Contact"); insert_hf("Contact: <sip:$var(ctuser)@PRIVATEIP:5060;alias=$var(cthost)~$var(ctport)~3>\r\n", "Call-ID"); } t_on_failure("MANAGE_FAILURE"); $fs = "udp:PRIVATEIP:5060"; } route(RELAY); exit; } # Wrapper for relaying requests route[RELAY] { handle_ruri_alias(); if (is_method("INVITE|BYE|SUBSCRIBE|UPDATE")) { if (!t_is_set("branch_route")) t_on_branch("MANAGE_BRANCH"); } if (is_method("INVITE|SUBSCRIBE|UPDATE|REGISTER")) { if (!t_is_set("onreply_route")) t_on_reply("MANAGE_REPLY"); } if (is_method("INVITE")) { if (!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE"); } xlog("L_INFO", "[RELAY] Relaying $ru\n"); if (!t_relay()) { sl_reply_error(); } exit; } # Per SIP request initial checks route[REQINIT] { #!ifdef WITH_ANTIFLOOD # flood detection from same IP and traffic ban for a while # be sure you exclude checking trusted peers, such as pstn gateways # - local host excluded (e.g., loop to self) if (src_ip!=myself && src_ip!=PBXIP) { if ($sht(ipban=>$si)!=$null) { # ip is already blocked xdbg("request from blocked IP - $rm from $fu (IP:$si:$sp)\n"); exit; } if ($sht(apiban=>$si)!=$null) { # ip is blocked from apiban.org xdbg("request from apiban.org blocked IP - $rm from $fu (IP:$si:$sp)\n"); exit; } if (!pike_check_req()) { xlog("L_ALERT","[R-REQINIT]: pike blocking $rm from $fu (IP:$si:$sp)\n"); $sht(ipban=>$si) = 1; exit; } } #!endif if ($ua =~ "friendly-scanner|sipcli|VaxSIPUserAgent") { xlog("L_INFO","[R-REQINIT]: script kiddies from IP:$si:$sp - dropping and blocking\n"); route(BLOCKIP); # sl_send_reply("200", "OK"); exit; } if ($au =~ "(\=)|(\-\-)|(')|(\#)|(\%27)|(\%24)" and $au != $null) { xlog("L_INFO","[R-REQINIT]: sql injection from IP:$si:$sp - dropping\n"); route(BLOCKIP); exit; } if (!mf_process_maxfwd_header("10")) { xlog("L_INFO","[R-REQINIT]: too many hops\n"); sl_send_reply("483","R1 Too Many Hops"); exit; } if (is_method("OPTIONS")){ if (uri==myself && ($rU==$null || $rU=="kamailio")) { sl_send_reply("200","Keep on Keeping on"); exit; } } if (!sanity_check("17895", "7")) { xlog("L_INFO","[R-REQINIT]: Malformed SIP message from $si:$sp\n"); exit; } } # Handle requests within SIP dialogs route[WITHINDLG] { if (!has_totag()) return; if ($si=="PBXIP") { # && $(ru{param.value,alias})!=$null) { route(RTPMANAGE); route(RELAY); exit; } if (loose_route()) { if ( is_method("NOTIFY") ) { record_route(); } route(RTPMANAGE); route(RELAY); exit; } if ( is_method("ACK|BYE") ) { route(RTPMANAGE); route(RELAY); } sl_send_reply("404","Not here WD1"); exit; } # RTPProxy control and signaling updates for NAT traversal route[RTPMANAGE] { if (has_totag()) { if (is_method("INVITE|UPDATE|ACK") || is_method("BYE")) { if (proto==TLS) { rtpengine_manage("RTP/AVP replace-origin replace-session-connection SIP-source-address ICE=remove"); } else { rtpengine_manage("RTP/SAVP replace-origin replace-session-connection SIP-source-address ICE=remove"); } } t_on_reply("MANAGE_REPLY"); } else { if (is_method("INVITE")) { if (has_body("application/sdp")) { if (proto==TLS) { rtpengine_manage("RTP/AVP replace-origin replace-session-connection SIP-source-address ICE=remove"); } else { rtpengine_manage("RTP/SAVP replace-origin replace-session-connection SIP-source-address ICE=remove"); } } } } return; } route[BLOCKIP] { if (src_ip!=myself) { xlog("L_INFO","[R-BLOCKIP:$ci]: blocking $rm from $fu (IP:$si:$sp)\n"); $sht(ipban=>$si) = 1; } return; } # Manage outgoing branches branch_route[MANAGE_BRANCH] { xdbg("new branch [$T_branch_idx] to $ru\n"); route(RTPMANAGE); } # Manage incoming replies onreply_route[MANAGE_REPLY] { xdbg("incoming reply\n"); if ($si!="PBXIP") { if ($(ct{tobody.user})!=$null) { xlog("L_INFO","[R-M-REPLY] adding contact alias\n"); $var(ctuser) = $(ct{tobody.user}); $var(cthost) = $si; $var(ctport) = $sp; remove_hf("Contact"); insert_hf("Contact: <sip:$var(ctuser)@PRIVATEIP:5060;alias=$var(cthost)~$var(ctport)~3>\r\n", "Call-ID"); } } if (!is_method("REGISTER")) { if (status=~"[12][0-9][0-9]") { route(RTPMANAGE); } } } # Manage failure routing cases failure_route[MANAGE_FAILURE] { xlog("L_INFO","[R-M-FAILURE] handling failure $rs\n"); route(RTPMANAGE); if (t_is_canceled()) exit; } route[APIBAN] { #!ifdef WITH_APIBAN // check if we already have an APIBAN id... if so, get the updates and // if not, get the full list of banned ips. $var(KEY) = "APIKEY"; if ($sht(apibanctl=>ID) == 0) { $var(apiget) = "https://apiban.org/api/" + $var(KEY) + "/banned"; } else { $var(apiget) = "https://apiban.org/api/" + $var(KEY) + "/banned/" + $sht(apibanctl=>ID); } xlog("L_INFO","API SEND: $var(apiget)\n"); http_client_query("$var(apiget)", "$var(banned)"); # if we dont get a 200 OK from the webserver, kick it back if ($rc!=200) { xlog("L_INFO","API ERR: No 200 Received. $var(banned)\n"); exit; } else { xlog("L_INFO","API: $var(banned)\n"); } $var(count) = 0; jansson_array_size("ipaddress", $var(banned), "$var(size)"); while($var(count) < $var(size)) { jansson_get("ipaddress[$var(count)]", $var(banned), "$var(v)"); $sht(apiban=>$var(v)) = 1; xlog("L_INFO","API: ipaddress[$var(count)] == $var(v)\n"); $var(count) = $var(count) + 1; } jansson_get("ID", $var(banned), "$var(w)"); xlog("L_INFO","ID: $var(w)\n"); $sht(apibanctl=>ID) = $var(w); #!endif xdbg("apiban complete\n"); } event_route[htable:mod-init] { # pre load apiban route(APIBAN); } #!ifdef WITH_SIPDEBUG onsend_route { xlog("L_INFO","[ONSEND_ROUTE] SIP DEBUG: \n$snd(buf)\n"); } #!endif