Table of Contents

SIP Routing Done In Lua with Kamailio

Starting with version 3.1.0, Kamailio SIP Server introduced support to run embedded Lua scripts.

The development version (to become next major release, 3.2.0, sometime during 2011), exported more functions to be executed natively in Lua.

This document shows how to install Kamailio development version from Git and do all SIP routing in Lua, for a quite complex configuration, running user authentication, accounting and location services.

Install Kamailio Devel

These guidelines are for Debian/Ubuntu based distributions.

Prerequisites

To be able to follow the guidelines from this document you need root access.

The following packages are required before proceeding to the next steps.

Getting sources from GIT

First of all, you have to create a directory on the file system where the sources will be stored.

  mkdir -p /usr/local/src/kamailio-dev
  cd /usr/local/src/kamailio-dev

Download the sources from GIT using the following commands.

  git clone --depth 1 git://git.sip-router.org/sip-router kamailio
  cd kamailio

Compile and Install

We want to install MySQL and Lua extensions:

make FLAVOUR=kamailio  include_modules="db_mysql app_lua" cfg
make all
make install

Create Database

To create the MySQL database, you have to use the database setup script:

  /usr/local/sbin/kamdbctl create

In order to create the database you need to specify the wanted db type (DBENGINE=MYSQL) in the

  /usr/local/etc/kamailio/kamctlrc

file. Call this script without any parameter to get some help for the usage. You will be asked for the domain name Kamailio (OpenSER) is going to serve (e.g., mysipserver.com) and the password of the 'root' MySQL user. The script will create a database named 'openser' containing the tables required by Kamailio (OpenSER). You can change the default settings in the kamctlrc file mentioned above.

The script will add two users in MySQL:

- openser - having the password 'openserrw', user which has full access rights to 'openser' database

- openserro - having the password 'openserro', user which has read-only access rights to 'openser' database

Do change the passwords for these two users immediately after the database is created.

Create SIP Accounts

A new account can be added using 'kamctl' tool via 'kamctl add <username> <password>'.

  kamctl add test testpasswd

If you are asked for SIP_DOMAIN environment variable do one of the following option.

  1.
  export SIP_DOMAIN=mysipserver.com

  2.
  edit '/usr/local/etc/kamailio/kamctlrc' and add:
  SIP_DOMAIN=mysipserver.com

Kamailio Config

This is the configuration file for Kamailio SIP server, it is needed to load the Kamailio modules and set their parameters. Then, the routing blocks just call Lua functions from kamailio.lua file.

#!KAMAILIO
#
# Kamailio (OpenSER) SIP Server v3.1 - default configuration script
#     - web: http://www.kamailio.org
#     - git: http://sip-router.org
#
# Direct your questions about this file to: <sr-users@lists.sip-router.org>
#
# Refer to the Core CookBook at http://www.kamailio.org/dokuwiki/doku.php
# for an explanation of possible statements, functions and parameters.
#
# Several features can be enabled using '#!define WITH_FEATURE' directives:
#
# *** To run in debug mode: 
#     - define WITH_DEBUG
#
# *** To enable mysql: 
#!define WITH_MYSQL
#
# *** To enable authentication execute:
#     - enable mysql
#!define WITH_AUTH
#     - add users using 'kamctl'
#
# *** To enable IP authentication execute:
#     - enable mysql
#     - enable authentication
#     - define WITH_IPAUTH
#     - add IP addresses with group id '1' to 'address' table
#
# *** To enable persistent user location execute:
#     - enable mysql
#!define WITH_USRLOCDB
#
# *** To enable multi-domain support execute:
#     - enable mysql
#     - define WITH_MULTIDOMAIN
#
 
#!define WITH_LUA
 
####### Defined Values #########
 
# *** Value defines - IDs used later in config
#!ifdef WITH_MYSQL
# - database URL - used to connect to database server by modules such
#       as: auth_db, acc, usrloc, a.s.o.
#!define DBURL "mysql://openser:openserrw@localhost/openser"
#!endif
#!ifdef WITH_MULTIDOMAIN
# - the value for 'use_domain' parameters
#!define MULTIDOMAIN 1
#!else
#!define MULTIDOMAIN 0
#!endif
 
# - flags
#   FLT_ - per transaction (message) flags
#	FLB_ - per branch flags
#!define FLT_ACC 1
#!define FLT_ACCMISSED 2
#!define FLT_ACCFAILED 3
#!define FLT_NATS 5
 
#!define FLB_NATB 6
#!define FLB_NATSIPPING 7
 
####### Global Parameters #########
 
#!ifdef WITH_DEBUG
debug=4
log_stderror=yes
#!else
debug=2
log_stderror=no
#!endif
 
memdbg=5
memlog=5
 
log_facility=LOG_LOCAL0
 
fork=yes
children=4
 
 
/* port to listen to
 * - can be specified more than once if needed to listen on many ports */
port=5060
 
####### Modules Section ########
 
# set paths to location of modules
#!ifdef LOCAL_TEST_RUN
mpath="modules_k:modules"
#!else
mpath="/usr/local/lib/kamailio/modules_k/:/usr/local/lib/kamailio/modules/"
#!endif
 
#!ifdef WITH_MYSQL
loadmodule "db_mysql.so"
#!endif
 
loadmodule "mi_fifo.so"
loadmodule "kex.so"
loadmodule "tm.so"
loadmodule "tmx.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "pv.so"
loadmodule "maxfwd.so"
loadmodule "usrloc.so"
loadmodule "registrar.so"
loadmodule "textops.so"
loadmodule "siputils.so"
loadmodule "xlog.so"
loadmodule "sanity.so"
loadmodule "ctl.so"
loadmodule "mi_rpc.so"
loadmodule "acc.so"
 
#!ifdef WITH_AUTH
loadmodule "auth.so"
loadmodule "auth_db.so"
#!endif
 
#!ifdef WITH_LUA
loadmodule "app_lua.so"
#!endif
 
# ----------------- setting module-specific parameters ---------------
 
 
# ----- mi_fifo params -----
modparam("mi_fifo", "fifo_name", "/tmp/kamailio_fifo")
 
 
# ----- 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)
 
 
# ----- rr params -----
# add value to ;lr param to cope with most of the UAs
modparam("rr", "enable_full_lr", 1)
# do not append from tag to the RR (no need for this script)
modparam("rr", "append_fromtag", 0)
 
 
# ----- registrar params -----
modparam("registrar", "method_filtering", 1)
/* uncomment the next line to disable parallel forking via location */
# modparam("registrar", "append_branches", 0)
/* uncomment the next line not to allow more than 10 contacts per AOR */
#modparam("registrar", "max_contacts", 10)
 
 
# ----- acc params -----
/* what special events should be accounted ? */
modparam("acc", "early_media", 0)
modparam("acc", "report_ack", 0)
modparam("acc", "report_cancels", 0)
/* by default ww do not adjust the direct of the sequential requests.
   if you enable this parameter, be sure the enable "append_fromtag"
   in "rr" module */
modparam("acc", "detect_direction", 0)
/* account triggers (flags) */
modparam("acc", "log_flag", FLT_ACC)
modparam("acc", "log_missed_flag", FLT_ACCMISSED)
modparam("acc", "log_extra", 
	"src_user=$fU;src_domain=$fd;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd")
modparam("acc", "failed_transaction_flag", FLT_ACCFAILED)
 
 
# ----- usrloc params -----
/* enable DB persistency for location entries */
#!ifdef WITH_USRLOCDB
modparam("usrloc", "db_url", DBURL)
modparam("usrloc", "db_mode", 2)
modparam("usrloc", "use_domain", MULTIDOMAIN)
#!endif
 
 
# ----- auth_db params -----
#!ifdef WITH_AUTH
modparam("auth_db", "db_url", DBURL)
modparam("auth_db", "calculate_ha1", yes)
modparam("auth_db", "password_column", "password")
modparam("auth_db", "load_credentials", "")
modparam("auth_db", "use_domain", MULTIDOMAIN)
#!endif
 
 
# ----- alias_db params -----
#!ifdef WITH_ALIASDB
modparam("alias_db", "db_url", DBURL)
modparam("alias_db", "use_domain", MULTIDOMAIN)
#!endif
 
 
#!ifdef WITH_LUA
# ----- app_lua params -----
modparam("app_lua", "load", "/usr/local/etc/kamailio/kamailio.lua")
modparam("app_lua", "register", "sl")
modparam("app_lua", "register", "rr")
modparam("app_lua", "register", "tm")
modparam("app_lua", "register", "maxfwd")
modparam("app_lua", "register", "registrar")
modparam("app_lua", "register", "auth")
modparam("app_lua", "register", "auth_db")
 
modparam("usrloc", "preload", "location")
#!endif
 
 
####### Routing Logic ########
 
 
# Main SIP request routing logic
# - processing of any incoming SIP request starts with this route
route {
	if(!lua_runstring("route_request([[MAIN]])"))
	{
		xdbg("SCRIPT: failed to execute lua script!\n");
	}
	exit;
}
 
branch_route[TEST] {
	if(!lua_runstring("route_branch([[TEST]])"))
	{
		xdbg("SCRIPT: failed to execute branch lua script!\n");
	}
}
 
onreply_route[TEST] {
	if(!lua_runstring("route_reply([[TEST]])"))
	{
		xdbg("SCRIPT: failed to execute reply lua script!\n");
	}
}
 
failure_route[TEST] {
	if(!lua_runstring("route_failure([[TEST]])"))
	{
		xdbg("SCRIPT: failed to execute failure lua script!\n");
	}
}

Remarks:

Lua Script

This is the Lua file you have to save in /usr/local/etc/kamailio/kamailio.lua.

-- SIP request routing
function route_request(name)
	-- some initial debug messages
	sr.dbg("routing SIP request from Lua [" .. name .. "]\n")
	ruri = sr.pv.get("$ru")
	rdomain = sr.pv.get("$rd")
	furi = sr.pv.get("$fu")
	fdomain = sr.pv.get("$fd")
	tdomain = sr.pv.get("$td")
	method = sr.pv.get("$rm")
	srcip = sr.pv.get("$si")
	sr.dbg("--- [" .. srcip .. "] (" .. method .. ") " .. furi .. " => " .. ruri .. "\n")
 
	-- initial checks of SIP request
	if sr.maxfwd.process_maxfwd(10) < 0 then
		sr.sl.send_reply(483,"Too Many Hops")
		return
	end
 
	-- route within dialog SIP requests
	if not sr.pv.is_null("$tt") then
		if sr.rr.loose_route()>0 then
			if method=="BYE" then
				sr.setflag(1)
				sr.setflag(3)
			end
			sr.tm.t_relay()
			return
		else
			if method=="ACK" then
				if sr.tm.t_check_trans()>0 then
					sr.tm.t_relay()
					return
				else
					return
				end
			end
			sr.sl.send_reply(404, "Not here")
		end
		return
	end
 
	-- only initial requests (no To tag)
 
	-- CANCEL processing
	if method =="CANCEL" then
		if sr.tm.t_check_trans() > 0 then
			sr.tm.t_relay()
		end
		return;
	end
 
	if sr.tm.t_check_trans() == 0 then return end
 
	-- authentication
	if method == "REGISTER" then
		-- authenticate the REGISTER requests
		if sr.auth_db.www_authenticate(tdomain, "subscriber") < 0 then
			sr.auth.www_challenge(tdomain, 1);
			return;
		end
 
		if sr.pv.get("$au") ~= sr.pv.get("$tU") then
			sr.sl.send_reply(403, "Forbidden auth ID");
			return;
		end
	else
		-- authenticate if from local subscriber
		if sr.is_myself( fdomain ) == true then
			if sr.auth_db.proxy_authenticate(fdomain, "subscriber") < 0 then
				sr.auth.proxy_challenge(fdomain, 1)
				return
			end
			if sr.pv.get("$au") ~= sr.pv.get("$fU") then
				sr.sl.send_reply(403, "Forbidden auth ID")
				return
			end
			sr.auth.consume_credentials();
			-- caller authenticated
		else
			-- caller is not local subscriber, if callee is not as well,
			-- do not become an open relay
			if sr.is_myself(rdomain)==false then
				sr.sl.send_reply(403, "Not relaying")
				return
			end
		end
	end
 
	-- record routing
	sr.hdr.remove("Route");
	if method == "INVITE" or  method == "SUBSCRIBE" then
		sr.rr.record_route()
	end
	if method=="INVITE" then
		sr.setflag(1)
	end
 
	-- non local destinations
	if sr.is_myself(rdomain)==false then
		sr.tm.t_relay()
		return
	end
 
	-- SIP registrar server
	if method == "REGISTER" then
		if sr.registrar.save("location") < 0 then
			sr.sl.send_reply(500, "Server error")
		end
		return
	end
 
	if sr.pv.is_null("$rU") then
		-- request with no Username in RURI
		sr.sl.send_reply(484, "Address Incomplete")
		return
	end
	-- SIP location server
	if sr.registrar.lookup("location") < 0 then
		-- destination user offline
		sr.sl.send_reply(404, "Not found")
		return
	end
	if method=="INVITE" then
		sr.setflag(2)
	end
 
	-- relay the request
	sr.tm.t_on_branch("TEST");
	sr.tm.t_on_reply("TEST");
	sr.tm.t_on_failure("TEST");
	if sr.tm.t_relay() < 0 then
		sr.sl.send_reply(500, "Server error")
	end
end
 
-- SIP branch routing
function route_branch(name)
	sr.dbg("routing SIP branch from Lua [" .. name .. "]\n")
end
 
-- SIP reply routing
function route_reply(name)
	sr.dbg("routing SIP reply from Lua [" .. name .. "]\n")
end
 
-- SIP failure routing
function route_failure(name)
	sr.dbg("routing SIP failure from Lua [" .. name .. "]\n")
end

Remarks:

References

You can see Kamailio Lua API at:

APP_LUA module documentation is available at:

Lua Project web site: