Kamailio script to front standard FusionPBX cluster v2.0

DigitalDaz

Administrator
Staff member
Here is the latest version of the Kamailio script, this seems to be working well. What became apparent is that in order for things like conferencing, parking, queuing etc to work, we also need to bring in the DIDs via Kamailio, if not then we have no way of tracking which server they are on. In this version I have achieved this by adding another group to the dispatcher list, this set, set 20, is a list of your carrier IPs.

Please note, for this to work we need to make some ACL changes to Freeswitch and a change to the profile settings.

Please note, in the dispatcher list I have added transport=tcp to my FusionPBX servers, this was necessary on my system before I added code to remove X- headers from the carrier as the max packet size for UDP was being exceeded.

I have left the POSTGRES info in the Kamailio config as I will be bringing this into play in later revisions and when we step up to multiple Kamailio instances.

I've been testing with Digital Ocean and it looks like a couple of droplets can be configured just fine with a shared IP.

Code:
#!KAMAILIO
#!subst "/MY_PUBLIC_IP_ADDRESS/X.X.X.X/"
#!subst "/POSTGRESQL_IP_ADDRESS/192.168.201.25/"
#!define DBPGURL "postgres://kamailio:dj98dusjj8@POSTGRESQL_IP_ADDRESS:5432/kamailio"
####!define WITH_DEBUG

# - flags
#!define FLAG_FROM_FREESWITCH 1
#!define FLAG_FROM_CARRIER 2

# - defines
#!define DBURL "postgres://kamailio:kamailio@POSTGRESQL_IP_ADDRESS/kamailio"
#!define JANSSON_RPC "conn=presence;addr=localhost;port=8080;priority=10;weight=10"

### LOG Levels: 3=DBG, 2=INFO, 1=NOTICE, 0=WARN, -1=ERR
debug=3
log_stderror=no

memdbg=5
memlog=5

log_facility=LOG_LOCAL0

fork=yes
children=4

listen=udp:"MY_PUBLIC_IP_ADDRESS":5060
listen=tcp:"MY_PUBLIC_IP_ADDRESS":5060
advertised_address="MY_PUBLIC_IP_ADDRESS"

tcp_connection_lifetime=3605

#enable_tls=yes

####### Modules Section ########

# set paths to location of modules (to sources or installation folders)
mpath="/usr/lib/x86_64-linux-gnu/kamailio/modules"

#loadmodule "db_postgres.so"
loadmodule "mi_fifo.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 "textops.so"
loadmodule "siputils.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "ctl.so"
loadmodule "cfg_rpc.so"
loadmodule "mi_rpc.so"
loadmodule "usrloc.so"
loadmodule "regex.so"
loadmodule "registrar.so"
loadmodule "nathelper.so"
#loadmodule "tls.so"
loadmodule "path.so"
#!ifdef WITH_DEBUG
loadmodule "debugger.so"
#!endif

loadmodule "dispatcher.so"
loadmodule "htable.so"
#loadmodule "presence.so"
#loadmodule "presence_dialoginfo.so"
#loadmodule "presence_mwi.so"
#loadmodule "presence_xml.so"
#loadmodule "nsq.so"
#loadmodule "htable.so"

# ----------------- setting module-specific parameters ---------------

# ----- mi_fifo params -----
modparam("mi_fifo", "fifo_name", "/var/run/kamailio/kamailio_fifo")
modparam("ctl", "binrpc", "unix:/var/run/kamailio/kamailio_ctl")

# ----- tm params -----
# auto-discard branches from previous serial forking leg
modparam("tm", "failure_reply_mode", 3)
# default retransmission timeout: 30sec
modparam("tm", "fr_timer", 30000)
# default invite retransmission timeout after 1xx: 120sec
modparam("tm", "fr_inv_timer", 120000)


# ----- nathelper params -----
modparam("nathelper|registrar", "received_avp", "$avp(s:rcv)")

#!ifdef WITH_DEBUG
# ----- debugger params -----
modparam("debugger", "cfgtrace", 1)
#!endif

#modparam("dispatcher", "db_url",DBPGURL)
modparam("dispatcher", "list_file", "/etc/kamailio/dispatcher.list")
modparam("dispatcher", "table_name", "dispatcher")
modparam("dispatcher", "flags", 2)
modparam("dispatcher", "dst_avp", "$avp(dsdst)")
modparam("dispatcher", "grp_avp", "$avp(dsgrp)")
modparam("dispatcher", "cnt_avp", "$avp(dscnt)")
modparam("dispatcher", "attrs_avp", "$avp(dsattrs)")
modparam("dispatcher", "sock_avp", "$avp(dssocket)")
modparam("dispatcher", "dstid_avp", "$avp(dsdstid)")
modparam("dispatcher", "ds_hash_size", 8)
modparam("dispatcher", "ds_ping_interval", 20)
modparam("dispatcher", "ds_ping_from", "sip:kamailio@dispatcher.local")
modparam("dispatcher", "ds_probing_mode", 1)
modparam("dispatcher", "ds_ping_reply_codes", "class=2;code=480;code=404")
modparam("dispatcher", "hash_pvar", "$td")

#modparam("presence_dialoginfo", "force_dummy_dialog", 1)
#modparam("presence_xml", "force_dummy_presence", 1)
#modparam("presence_xml", "force_active", 1)
#modparam("presence_xml", "disable_winfo", 1)
#modparam("presence_xml", "disable_bla", 1)

#modparam("htable", "db_url", DBPGURL)
#modparam("htable", "htable", "p=>size=32;autoexpire=3600;")

#modparam("presence", "subs_db_mode", 3)
#modparam("presence", "send_fast_notify", 1)
#modparam("presence", "clean_period", 30)
#modparam("presence", "publ_cache", 0)
#modparam("presence", "min_expires_action", 1)
#modparam("presence", "min_expires", 300)
#modparam("presence", "max_expires", 3600)
#modparam("presence", "sip_uri_match", 1)
#modparam("presence", "waitn_time", 1)
#modparam("presence", "notifier_processes", 1)
#modparam("presence", "force_delete", 1)
#modparam("presence", "db_url", DBPGURL)
#modparam("presence", "active_watchers_table", "active_watchers")


####### Routing Logic ########


# Main SIP request routing logic
# - processing of any incoming SIP request starts with this route
# - note: this is the same as route { ... }
request_route {
    xlog("L_INFO", "$ci|log|SIP message [$rm] from $si:$sp");

        if (uri=="sip:149.202.190.100:5777") {
                if ((method==OPTIONS) && (! uri=~"sip:.*[@]+.*")) {
                        options_reply();
                }
        }

    # per request initial checks
    route(REQINIT);

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

    route(CHECK_SOURCE_IP);

    # handle requests within SIP dialogs
    route(WITHINDLG);

    ###############################
    ### HANDLE INITIAL REQUESTS ###
    # handle retransmissions
    if(t_precheck_trans()) {
        t_check_trans();
        exit;
    }
    t_check_trans();

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

    if (is_method("NOTIFY") && $hdr(event) == "check-sync" && isflagset(FLAG_FROM_FREESWITCH)) {
        record_route();
        xlog("L_INFO", "$ci|log|Rebooting phone [$ru]\n");
        t_on_reply("REPLY_FROM_DEVICE"); # handle NAT
        route(RELAY);
    }

    if (!isflagset(FLAG_FROM_FREESWITCH) && is_method("REGISTER")) {
        add_path();
    }

    # handle INVITEs
    route(DISPATCH);

    route(RELAY);
}

# Per SIP request initial checks
route[REQINIT] {
    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 (!sanity_check()) {
        #xlog("L_WARN", "$ci|end|message is insane");
        exit;
    }
    if ($ua == "friendly-scanner" || $ua == "sundayddr" || $ua =~ "sipcli" ) {
        #xlog("L_WARN", "$ci|end|dropping message with user-agent $ua");
        exit;
    }
    if (is_method("PUBLISH")) {
        xlog("L_WARN", "$ci|end|dropping PUBLISH messages for now $ua");
        exit;
    }
}

route[CHECK_SOURCE_IP] {
    if (ds_is_from_list("1","3")) {
        setflag(FLAG_FROM_FREESWITCH);
    } else {
        route(NAT_TEST_AND_CORRECT);
    }
}

route[DISPATCH] {
    if (ds_is_from_list("20","3")){
        setflag(FLAG_FROM_CARRIER);
        xlog("L_NOTICE", "FLAG_FROM_CARRIER SET!");
        # strip all headers whose names match "Subject", contain "P-" or "X-".
        remove_hf_re("Subject|P-.*|X-.*");
        append_hf("X-Auth-IP: $si\r\n");
        if (!ds_select_dst("1", "7")) {
                #if we are here that means no destination is available. We notify the user by 404 and exit the script.
                xlog("L_NOTICE", "No destination available!");
                send_reply("404", "No destination");
                exit;
        }
    }



    if (isflagset(FLAG_FROM_FREESWITCH)) {
        xlog("L_NOTICE", "FLAG_FROM_FREESWITCH IS SET!");
        t_on_reply("REPLY_FROM_DEVICE"); # handle NAT
    } else if (!ds_select_dst("1", "7")) {
        #if we are here that means no destination is available. We notify the user by 404 and exit the script.
        xlog("L_NOTICE", "No destination available!");
        send_reply("404", "No destination");
        exit;
    }
}

route[RELAY] {
        if (is_method("INVITE")) {
                if(!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE");
        }

        if (!t_relay()) {
           sl_reply_error();
        }
        exit;
}

onreply_route[REPLY_FROM_DEVICE] {
    route(NAT_TEST_AND_CORRECT);
}

# manage failure routing cases
failure_route[MANAGE_FAILURE] {
    if (t_is_canceled()) {
        exit;
    }
}

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

# Handle requests within SIP dialogs
route[WITHINDLG] {
    if (has_totag()) {
        if (is_method("INVITE|UPDATE|NOTIFY")) { # fix reply from UPDATE or NOTIFY (in-dialog)
            t_on_reply("REPLY_FROM_DEVICE"); # handle NAT
        }
        # 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
                route(PRESENCE);
                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;
    }
}

####### Presence Routes #######

route[PRESENCE] {
        if(!is_method("PUBLISH|SUBSCRIBE"))
                return;
                route(TOFUSIONPBX);
                exit;
}

# Send to FusionPBX
route[TOFUSIONPBX] {
        route(DISPATCH);
        route(RELAY);
        exit;
}
Here is the dispatcher.list:
Code:
1 sip:37.187.X.X:5060;transport=tcp
1 sip:176.31.X.X:5060;transport=tcp
20 sip:178.22.136.19:5060 4
20 sip:178.22.136.34:5060 4
20 sip:178.22.140.34:5060 4
20 sip:178.22.140.35:5060 4
20 sip:178.22.143.66:5060 4
20 sip:178.22.143.69:5060 4
In FusionPBX we need to add a parameter to the internal profile to enable sip proxy acl:

sipproxyacl.jpg

We then need to actually create the proxy acl in Advanced/Access Controls, the CIDR value needs to be the CIDR of your Kamailio instance. Do this for each Kamailio instance that you have.

proxyacl.jpg

Once you have done that go into Advanced/Access Controls and add your carrier IPs in the CIDR field of your domain acl list.

carrierips.jpg

At this point I would memcache flush, reloadacl and also restart the internal profile. If in doubt, REBOOT :)

So not forget that to make changes to profiles work across multiple servers, you need to log into each server and in Advanced/Sip Profiles, edit one variable and then save in order for it to come into play on that server.
 

Attached Files:

Nice, would love to try this out, but what about Kamailio installation instructions? How is that done? I don't really see a script, but maybe I'm missing something. I just see the config file.
 
Hi
Registration is down in Kamailio and registration shared with fusionpbx ? or fusionpbx es are not aware of registration ?
you route all calls to fusionpbx or you use rtpproxy and send just some calls to fusionpbx ?
 

DigitalDaz

Administrator
Staff member
Hi
Registration is down in Kamailio and registration shared with fusionpbx ? or fusionpbx es are not aware of registration ?
you route all calls to fusionpbx or you use rtpproxy and send just some calls to fusionpbx ?
Babak, in this scenario the Kamailio is pretty dumb, ie just forwarding everything to the fusionpbx.

No RTP Proxy involved.

I have thought of using RTP Proxy but cannot see the real benefit as essentially, the freeswitch instances are doing that job already. The problem with using RTP Proxy type setup with fusionpbx is that you would need some pretty complex logic I think if you were going to bypass media, for example call recording, conference, IVR, voicemail, you name it, all need to have the media within freeswitch.
 
Hi
Please take look at SIPWISE Architecture:
https://www.sipwise.org/doc/mr5.0.1/spce/ar01s02.html
It's Media Relay (also called rtpengine) is a Kernel-based packet relay not user-space so may be its performance boost is from this point.

RECOMMENDED HARDWARE:
  • Dual-core x86_64 compatible
  • 3GHz, 4GB RAM, 128GB HDD
PERFORMANCE:
  • 50.000 Subscriber Lines
  • 180.000 Busy Hour Call Attempts
  • 50 Call Attempts per Second
  • 2.000 Concurrent Calls
  • Unlimited number of Class4 Peerings
  • Unlimited number of SIP Peerings
 
Last edited:
You say you brought in DID via kamailio too, does this mean that queues and the others are fixed by this config? What if your carrier delivers the same DID from multiple of their own SBC's?

My thoughts, 2 FuionPBX servers, with Customer A having 10 users 5 on each of these servers. Does your script fix queueing and conferencing in this regard?

Thanks
 

DigitalDaz

Administrator
Staff member
From testing, yes it fixes these issues, in fact the only reason we are bringing the dids in is to allow kamailio to put them on the correct server.

Where the carrier sends them from is irrelevant as long as their IPs are in the list
 

DigitalDaz

Administrator
Staff member
Ah, sorry, that's not really relevant for you, use the code up top if you like. That's for my own personal implementation where I made redundant kamailio boxes. That address and port was used for the other box to send keepalives to that one. So basically it was just sending a 200OK to options packets from the other box.
 
First issue was regex module not being found but I couldnt see where this was used so commented it out. Now I'm getting the below. I'm using Debian and installed from apt with apt-get install kamailio

INFO: path [../outbound/api.h:54]: ob_load_api(): unable to import bind_ob - maybe module is not loaded
INFO: path [path_mod.c:147]: mod_init(): outbound module not available
 
Hum well its not starting..

Mar 11 18:39:55 ip-172-31-4-213 kamailio[8202]: udp: 195.12.34.1.24:5060
Mar 11 18:39:55 ip-172-31-4-213 /usr/sbin/kamailio[8204]: INFO: rr [../outbound/api.h:54]: ob_load_api(): unable to import bind_ob - maybe module is not loaded
Mar 11 18:39:55 ip-172-31-4-213 /usr/sbin/kamailio[8204]: INFO: rr [rr_mod.c:174]: mod_init(): outbound module not available
Mar 11 18:39:55 ip-172-31-4-213 /usr/sbin/kamailio[8204]: INFO: usrloc [hslot.c:51]: ul_init_locks(): locks array size 1024
Mar 11 18:39:55 ip-172-31-4-213 /usr/sbin/kamailio[8204]: INFO: path [../outbound/api.h:54]: ob_load_api(): unable to import bind_ob - maybe module is not loaded
Mar 11 18:39:55 ip-172-31-4-213 /usr/sbin/kamailio[8204]: INFO: path [path_mod.c:147]: mod_init(): outbound module not available
Mar 11 18:39:55 ip-172-31-4-213 /usr/sbin/kamailio[8204]: INFO: <core> [udp_server.c:150]: probe_max_receive_buffer(): SO_RCVBUF is initially 212992
Mar 11 18:39:55 ip-172-31-4-213 systemd[1]: kamailio.service: control process exited, code=exited status=255
Mar 11 18:39:55 ip-172-31-4-213 systemd[1]: Failed to start Kamailio (OpenSER) - the Open Source SIP Server.
Mar 11 18:39:55 ip-172-31-4-213 systemd[1]: Unit kamailio.service entered failed state.
 
Ok, my listen ip was wrong sorted now. So my fusionpbx users do I need another kamailio for them to register to, or do they need to register to this on? I just tried registering a user to this but on checking config there is no setting I see to check database.

I was thinking maybe ideally to have kamailio that users register to and one that my provider registers to, or does that defeat what we are trying to do here..lol
 

DigitalDaz

Administrator
Staff member
It is completely different, if Kamailio is to act as the registrar it is a completely different config. Thi config is designed to bolt on to the front of a cluster taught using the methods in the FusionPBX advanced class.
 
@DigitalDaz

Is presence on fusion/freeswitch working for you with this setup?

E.g are you seeing kamailio sending the publish/subscribe requests through to fusion/fs (and ultimately populating thr sip_presence table in the freeswitch db).

Presence is all good if I register clients directly to a fusion/fs server, but not when proxied.