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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.