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.

Syncing DNS Zones from Windows DNS Server to Linux Bind9 DNS Server

Windows Setup

First step is to dump the DNS zones from Windows into a file. Then generate an FTP command file which will upload the DNS zone file dump to your bind server. I’ve created a batch script to handle all of this:

@echo off
dnscmd /enumzones > dns.zones.txt
echo user ftp_bind_user> ftpcmd.dat
echo ftp_bind_password>> ftpcmd.dat
echo bin>> ftpcmd.dat
echo put dns.zones.txt>> ftpcmd.dat
echo quit>> ftpcmd.dat
ftp -n -s:ftpcmd.dat 192.168.1.101
del ftpcmd.dat
del dns.zones.txt

Some notes about the above script:

192.168.1.101 is the bind9 server’s IP address.

ftp_bind_user is the ftp user name setup on the bind9 server

ftp_bind_password is the ftp password setup on the bind9 server

I have this batch script run every hour, on the hour, using the Window task scheduler. This script will most likely need to run as an administrator in order to dump the DNS zones to a file.

Linux Setup

Now we setup the bash script. This script will first parse and format dns.zones.txt into a usable bind format, then it will scan all the current zone files and remove any that are not listed in the updated zone file. I set this script to run every hour, ten minutes after each hour. This script assumes you have bind running as user “bind” in group “bind”, please change the chown line accordingly.

#!/bin/bash

ZONE_PATH="/home/bind"
BIND_PATH="/var/lib/bind"
TMP=$(mktemp)
FC="dns.zones.txt"

for ZONE in $(awk '$2=="Primary" {print $1}' "${ZONE_PATH}/${FC}") do
   printf "zone ${ZONE} {\n\ttype slave;\n\tmasters { 192.168.1.100; };\n\tfile \"${BIND_PATH}/${ZONE}.zone\";\n};\n"
done > ${TMP}

for ZONE in "$BIND_PATH"/*.zone do
   grep -q "${ZONE}" "${TMP}" || rm -rf "${ZONE}"
done

mv ${TMP} /etc/bind/named.conf.slave-zones
chown bind:bind /etc/bind/named.conf.slave-zones

rndc reload

Some notes about the above script:

ZONE_PATH is where the dns.zones.txt file is uploaded to, this is of course determined by your FTP server setup (I setup proftpd with SQL backend, but you could easily setup vsftpd)

BIND_PATH is where we are telling bind to store the zone files.

192.168.1.100 is the IP Address of the Windows DNS Server

/etc/bind/named.conf.slave-zones is setup to be included in the “named.conf” file with the line:

include “/etc/bind/named.conf.slave-zones”;