Categories
Kamailio SIP VoIP

Kamailio: Basic SIP Proxy (all requests) Setup

In this example, I will share how to setup Kamailio to proxy SIP requests to a SIP switch (such as FreeSWITCH or Asterisk).
192.168.1.101 is the IP of Kamailio
192.168.1.102 is the IP of FreeSWITCH or Asterisk
Here are snippets from the main config script, kamailio.cfg:

...
 

#!define IPADDRESS "192.168.1.101"

#!define SWITCH_IPADDRESS "192.168.1.102"


#!define FLAG_FROM_SWITCH 1
#!define FLAG_FROM_USER 2

# ------------------ module loading ----------------------------------
loadmodule "tm.so"
loadmodule "rr.so"
loadmodule "pv.so"
loadmodule "sl.so"
loadmodule "maxfwd.so"
loadmodule "nathelper.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "path.so"

# ----------------- setting module-specific parameters ---------------
modparam("nathelper|registrar", "received_avp", "$avp(s:rcv)")
# -------------------------  request routing logic -------------------
# main routing logic

route {

        # per request initial checks
        route(SANITY_CHECK);

        # CANCEL processing
        if (is_method("CANCEL")) {
                if (t_check_trans()) {
                        t_relay();
                }
                exit;
        }

        route(CHECK_SOURCE_IP);

        ##################################
        ### HANDLE SEQUENTIAL REQUESTS ###
        route(WITHINDLG);

        ###############################
        ### HANDLE INITIAL REQUESTS ###
        t_check_trans();

        if (is_method("INVITE|REFER")) {
                record_route();
        }
       
        if (is_method("REGISTER")) {
            add_path();
        }

        if (isflagset(FLAG_FROM_SWITCH)) {
                # don't send INVITE from SWITCH back to SWITCH, set reply route to handle NAT and forward them along
                t_on_reply("EXTERNAL_REPLY");
        } else {
                # set destination to your SWITCH
                $du = "sip:192.168.1.102:5060";
        }

        route(RELAY);


}


route[SANITY_CHECK]
{
        if (!sanity_check()) {
                #xlog("L_WARN", "$ci|end|message is insane");
                exit;
        }

        if (!mf_process_maxfwd_header("10")) {
                #xlog("L_WARN", "$ci|end|too much hops, not enough barley");
                send_reply("483", "Too Many Hops");
                exit;
        }

        if ($ua == "friendly-scanner" ||
                $ua == "sundayddr" ||
                $ua =~ "sipcli" ) {
                #xlog("L_WARN", "$ci|end|dropping message with user-agent $ua");
                exit;
        }

        if ($si == IPADDRESS) {
                #xlog("L_WARN", "$ci|end|dropping message");
                exit;
        }

}


route[CHECK_SOURCE_IP]
{
        if ($si == SWITCH_IPADDRESS) {
                setflag(FLAG_FROM_SWITCH);
        } else {
                setflag(FLAG_FROM_USER);
        }
}

# Handle requests within SIP dialogs
route[WITHINDLG]
{
        if (has_totag()) {
                # sequential request withing a dialog should
                # take the path determined by record-routing
                if (loose_route()) {
                        route(RELAY);
                } else {
                        if (is_method("NOTIFY")) {
                                route(RELAY);
                        }
                        if (is_method("SUBSCRIBE") && uri == myself) {
                                # in-dialog subscribe requests
                                exit;
                        }
                        if (is_method("ACK")) {
                                if (t_check_trans()) {
                                        # no loose-route, but stateful ACK;
                                        # must be an ACK after a 487
                                        # or e.g. 404 from upstream server
                                        t_relay();
                                        exit;
                                } else {
                                        # ACK without matching transaction ... ignore and discard
                                        #xlog("ACK without matching transaction ... ignore and discard");
                                        exit;
                                }
                        }
                        sl_send_reply("404","Not here");
                }
                exit;
        }
}

onreply_route[EXTERNAL_REPLY]
{
        route(NAT_TEST_AND_CORRECT);
}


route[NAT_TEST_AND_CORRECT]
{
        if (nat_uac_test("3")) {
                if (is_method("REGISTER")) {
                        fix_nated_register();
                } else {
                        fix_nated_contact();
                }
                force_rport();
        }
        if (has_body("application/sdp") && nat_uac_test("8")) {
                fix_nated_sdp("10");
        }
}

route[RELAY]
{
        if (!t_relay()) {
                sl_reply_error();
        }
        exit;
}

This example will proxy all SIP requests to the switch. Typically, this is not recommended as Kamailio is able to handle the SIP REGISTERs but I’ll save that for another tutorial.

31 replies on “Kamailio: Basic SIP Proxy (all requests) Setup”

Hello,

Is there a way to implement failover to a different upstream register if 192.168.1.102 fails?

Hello,
Yes, you can use a module such as dispatcher to check health of the B2BUA and route accordingly.

You generally have to enable multihome mode with
mhomed=1
It also requires a bit more tweaking. You may need to use force_send_socket() in places as well. I’d be happy to assist more if you can provide a bit more detail on your setup. Feel free to email me direct, eschmidbauer@gmail.com

I just came across this post from a few years ago, although I see it is still getting a bit of traction.

Thanks for posting this! This is the only configuration I’ve been able to find online of someone with a similar setup of what I am looking to achieve. Unfortunately, I don’t have any existing Kamailio knowledge, so this is completely foreign to me.

However, I installed Kamilio and the required modules, and used your script, replacing the IP addresses with my own setup.

A little background: I’m attempting to use Kamailio as an outbound SIP proxy for my IP phones to a 3CX server in the cloud. The IP phones are obviously behind NAT, so I was hoping to use Kamailio as a very basic SIP proxy with NAT traversal.

Using your script as is and saving it as kamailio.cfg does indeed proxy everything. However, I’m a little confused because it does not seem to be doing any NAT traversal. Under route[NAT_TEST_AND_CORRECT] you are indeed performing fix_nated_register(); and fix_nated_contact(); but I’m not sure if the script is running those.

As an experiment, I did a very sloppy hack: I modified route[CHECK_SOURCE_IP] as follows to include route(NAT_TEST_AND_CORRECT);

route[CHECK_SOURCE_IP]
{
if ($si == SWITCH_IPADDRESS) {
setflag(FLAG_FROM_SWITCH);
} else {
setflag(FLAG_FROM_USER);
route(NAT_TEST_AND_CORRECT);
}
}

This does in fact result in REGISTER and the resulting 407 Authentication Required response being properly translated with the correct ports. However, the issue I ran into with that bootleg fix is that that INVITE and some other stuff from the 3CX server sent to the proxy are not being properly translated (they are attempting to be sent to the private IP and port).

Hi Brian,
Yes, this was meant as a broad example and definitely requires some tweaking based on your needs. You are on the right track for fixing NAT from users, but to be even more precise you can add something like this:
route[CHECK_SOURCE_IP] {
if ($si == SWITCH_IPADDRESS) {
setflag(FLAG_FROM_SWITCH);
} else if (allow_routing(“user”)) { # make sure request matches .userdomain.com
setflag(FLAG_FROM_USER);
route(NAT_TEST_AND_CORRECT);
} else { # reject everything else
setflag(FLAG_UNAUTHORIZED);
}
}

and use permissions module with something like (assuming you are using SIP domains) – `/etc/kamailio/user.allow`:
# only allow .userdomain.com
“^sip:[^@]+@[^.]+\.userdomain\.com(:[0-9]+)?$” : ALL

this will seriously cut down on “noise” from script kiddie bots too.
Feel free to email me with any more questions: eschmidbauer@gmail.com

Thanks, that’s a pretty good idea. So I’m glad that I was on the right path, but I seem to be stuck and fairly confused about one issue with the NAT behavior.

“REGISTER” and “STATUS” messages are translated properly. However, if an INVITE message comes in from my PBX server (3CX), Kamailio is not NATing it.

Here is a link to a screenshot illustrating what I mean: https://i.imgur.com/ENctyPq.png
For reference, 40.76.x.x is the IP of the 3CX server (static public, no NAT). 10.111.222.4 is the eth0 interface of Kamailio (has a static public IP with 1:1 NAT which is not an issue). 108.24.x.x is the public IP of my home network (with NAT). 10.0.0.125 is the local IP of my Polycom IP phone on my home network (NAT).

The “contact” address (4245@10.0.0.125:5060) in the 200 OK message (which is working properly) is the same as the “contact” in the INVITE, so I would expect that Kamailio should be able to NAT INVITE messages and any other messages properly based on the existing session of the same contact.

Did you ever get this figured out Brian?
I’m seeing something pretty similar and I’m having a heck of a time figuring it out.

Hi Emmanuel,
Did you ever do the write up on having Kamailio handle the SIP registers but proxying everything else?
I’d like to set up Kamailio to NAT/Reverse proxy to our Asterisk server, but to have the authentication/registration handled at the Kamailio server.

I would also like to know this. We’re kind of in a position that requires us to have a SIP registrar and proxy available for private address to public address to an upstream provider.

setup like this is not difficult- just is is_method(“REGISTER”) and handle locally as registrar
anything else, send to asterisk

in some places you can use FQDN, for example you should be able to use it to set $du like:

$du = "sip:my_domain.com:5060";

some lines require an IP Addr like this line:

if ($si == SWITCH_IPADDRESS) {

This is because $si is the “source ip”

Hello, thank you for that post. I am new to kamailio, trying to do a full (reverse?) proxy to an asterisk on internal LAN, and this config looks like what I need to do it.
With your config as is, I can register my softphone on the server, and see the OPTIONS that asterisk sends back.
Looks good until I try to INVITE: the first (non-auth) round of packets are OK as expected, but when the softphone sends the request with the Proxy-auth header, no response from kamailio.
Is this expected from that “simple” config?

Found the problem! Looks like that when the packet is bigger than 1300 bytes or so, it gets ignored (is this a bug? if it is, one of the two softphones I tested has it too). I removed most codecs to keep the SIP packet small and now the dialog happens.

sounds like an MTU issue
in which case, switching to TCP or enabling jumbo frames would probably solve it

a very old version as this post is 5+ years old. but i dont see why it should not work on latest version of kamailio

when i add your kamailio config, I found below ERROR and can’t find what module or additional thing should i did:
-failed to find command nat_uac_test
-failed to find command fix_nated_register
-failed to find command fix_nated_contact

I can’t found how load the module inside this link, but I tried load and install the module from link https://kamailio.org/docs/tutorials/5.5.x/kamailio-install-guide-git

what i did inside kamailio source code is:
make include_modules=”nathelper usrloc db_mysql tls” cfg
make all
make install

then here the result logs from kamailio.log :
Mar 18 14:11:02 newkam kamailio: INFO: [core/sctp_core.c:53]: sctp_core_destroy(): SCTP API not initialized
Mar 18 14:11:02 newkam kamailio: ERROR: [core/cfg.y:3545]: yyparse(): cfg. parser: failed to find command nat_uac_test (params 1)
Mar 18 14:11:02 newkam kamailio: CRITICAL: [core/cfg.y:3686]: yyerror_at(): parse error in config file /usr/local/etc/kamailio/kamailio.cfg, line 610, column 29: unknown command, missing loadmodule?#012
Mar 18 14:11:02 newkam kamailio: ERROR: [core/cfg.y:3545]: yyparse(): cfg. parser: failed to find command fix_nated_register (params 0)
Mar 18 14:11:02 newkam kamailio: CRITICAL: [core/cfg.y:3686]: yyerror_at(): parse error in config file /usr/local/etc/kamailio/kamailio.cfg, line 612, column 44: unknown command, missing loadmodule?#012
Mar 18 14:11:02 newkam kamailio: ERROR: [core/cfg.y:3545]: yyparse(): cfg. parser: failed to find command fix_nated_contact (params 0)
Mar 18 14:11:02 newkam kamailio: CRITICAL: [core/cfg.y:3686]: yyerror_at(): parse error in config file /usr/local/etc/kamailio/kamailio.cfg, line 614, column 43: unknown command, missing loadmodule?#012
Mar 18 14:11:02 newkam kamailio: ERROR: [core/cfg.y:3545]: yyparse(): cfg. parser: failed to find command nat_uac_test (params 1)
Mar 18 14:11:02 newkam kamailio: CRITICAL: [core/cfg.y:3686]: yyerror_at(): parse error in config file /usr/local/etc/kamailio/kamailio.cfg, line 618, column 60: unknown command, missing loadmodule?#012
Mar 18 14:11:02 newkam kamailio: ERROR: [core/cfg.y:3545]: yyparse(): cfg. parser: failed to find command fix_nated_sdp (params 1)
Mar 18 14:11:02 newkam kamailio: CRITICAL: [core/cfg.y:3686]: yyerror_at(): parse error in config file /usr/local/etc/kamailio/kamailio.cfg, line 619, column 35: unknown command, missing loadmodule?#012

actually, the modules has been loaded, found at :

/usr/local/lib64/kamailio/modules# ls -tlra nathelper.so usrloc.so
-rwxr-xr-x 1 root root 392032 Mar 18 14:08 nathelper.so
-rwxr-xr-x 1 root root 717456 Mar 18 14:08 usrloc.so

Leave a Reply

Your email address will not be published. Required fields are marked *