Date: Sep 3, 2010 Data compiled by: Daniel-Constantin Mierla
SIP Express Router (aka SER) is a high-performance, configurable, free Session Initiation Protocol (SIP) server licensed under the open-source GNU license, offering a large set of features. Started before the publishing of RFC3261 (SIP v2.0), SER pioneered the development of many SIP extensions and pushed further the real-time communications over IP.
Initial project web site was:
Now the web sites are:
It is the oldest and most robust open source SIP server, routing billions of VoIP minutes every month world wide, being used from Telcos and Carriers to ITSP and SOHO environments. If you haven't heard of it so far, it is very likely because your VoIP provider routes the calls fast and reliable with SER-based SIP servers, so you don't need to build your own system.
First source code commit of SER was done 9 years ago: Sep 3, 2001. According to GIT log, first three commits were:
git log --pretty=format:"%h%x09%an%x09%ad%x09%s" --reverse | head -3 512dcd9 Andrei Pelinescu-Onciul Mon Sep 3 21:27:11 2001 +0000 Initial revision 888ca09 Andrei Pelinescu-Onciul Tue Sep 4 01:41:39 2001 +0000 parser seems to work e60a972 Andrei Pelinescu-Onciul Tue Sep 4 20:55:41 2001 +0000 First working release
That is 3286 days of continuous development, with over 70 registered developers and hundreds of contributors, estimated cost of development: over 8 000 000 USD.
SER code and architecture was and still is the foundation for other projects that forked over years from it or from its forks, which still keep majority of inherited code untouched.
As you can notice, SER and Kamailio are now same application (completely the same source code). The difference is made by what modules are you using for same purpose (e.g., user authentication, location, accounting) because the variants have a different database structure (you can notice later the existence of modules with same name, but located in different folders).
Of course you can combine to some extent, for example use Kamailio-specific accounting module with SER-specific database user authentication module. The limitation comes to modules that have dependencies, for example registrar module depends on usrloc module – you have to use both from one side.
There were 11 distinct numbering versions used over the years (0.9.x was overlapping, counted once), internal VCS branching representations were:
The command line arguments of SER changed over the years, but you can notice that it has IPv6 support since 2002 (well, we still wait for ISPs…).
Output of ser -h and ser -V at very young age (recompiled today):
# ser -h version: ser 0.8.7-99 (i386/linux) Usage: ser -l address [-p port] [-l address [-p port]...] [options] Options: -f file Configuration file (default /usr/local/etc/ser/ser.cfg) -p port Listen on the specified port (default: 5060) applies to the last address in -l and to all following that do not have a corespponding -p -l address Listen on the specified address (multiple -l mean listening on more addresses). The default behaviour is to listen on the addresses returned by uname(2) -n processes Number of child processes to fork per interface (default: 8) -r Use dns to check if is necessary to add a "received=" field to a via -R Same as `-r` but use reverse dns; (to use both use `-rR`) -v Turn on "via:" host checking when forwarding replies -d Debugging mode (multiple -d increase the level) -D Do not fork into daemon mode -E Log to stderr -V Version number -h This help message -b nr Maximum receive buffer size which will not be exceeded by auto-probing procedure even if OS allows -m nr Size of shared memory allocated in Megabytes -w dir change the working directory to "dir" (default "/") -t dir chroot to "dir" -u uid change uid -g gid change gid -P file create a pid file -i fifo_path create a fifo (usefull for monitoring ser) # ser -V version: ser 0.8.7-99 (i386/linux) flags: STATS:Off, USE_IPV6, DNS_IP_HACK, SHM_MEM, SHM_MMAP, PKG_MALLOC, F_MALLOC, FAST_LOCK-ADAPTIVE_WAIT ADAPTIVE_WAIT_LOOPS=1024, MAX_RECV_BUFFER_SIZE 262144, MAX_LISTEN 16, MAX_URI_SIZE 1024, BUF_SIZE 3040 @(#) $Id: main.c,v 1.119 2002-09-25 19:20:26 andrei Rel $ main.c compiled on 17:11:29Sep 3 2010 with gcc 4.4
Output of ser -h and ser -V nowadays:
# ser -h version: ser 3.0.99-dev1 (i386/linux) b72876 Usage: ser [options] Options: -f file Configuration file (default: /usr/local/etc/ser/ser.cfg) -L path Modules search path (default: /usr/local/lib/ser/modules:/usr/local/lib/ser/modules_s:/usr/local/lib/ser/modules_k) -c Check configuration file for errors -l address Listen on the specified address/interface (multiple -l mean listening on more addresses). The address format is [proto:]addr_lst[:port], where proto=udp|tcp|tls|sctp, addr_lst= addr|(addr, addr_lst) and addr= host|ip_address|interface_name. E.g: -l locahost, -l udp:127.0.0.1:5080, -l eth0:5062, -l "sctp:(eth0)", -l "(eth0, eth1, 127.0.0.1):5065". The default behaviour is to listen on all the interfaces. -n processes Number of child processes to fork per interface (default: 8) -r Use dns to check if is necessary to add a "received=" field to a via -R Same as `-r` but use reverse dns; (to use both use `-rR`) -v Turn on "via:" host checking when forwarding replies -d Debugging mode (multiple -d increase the level) -D no 1..do not fork (almost) anyway, 2..do not daemonize creator 3..daemonize (default) -E Log to stderr -T Disable tcp -N Number of tcp child processes (default: equal to `-n') -W type poll method (depending on support in OS, it can be: poll, epoll_lt, epoll_et, sigio_rt, select, kqueue, /dev/poll) -V Version number -h This help message -b nr Maximum receive buffer size which will not be exceeded by auto-probing procedure even if OS allows -m nr Size of shared memory allocated in Megabytes -w dir Change the working directory to "dir" (default: "/") -t dir Chroot to "dir" -u uid Change uid -g gid Change gid -P file Create a pid file -G file Create a pgid file -O nr Script optimization level (debugging option) -a mode Auto aliases mode: enable with yes or on, disable with no or off -A define Add config pre-processor define (e.g., -A WITH_AUTH) # ser -V version: ser 3.0.99-dev1 (i386/linux) b72876 flags: STATS: Off, USE_IPV6, USE_TCP, USE_TLS, TLS_HOOKS, USE_RAW_SOCKS, DISABLE_NAGLE, USE_MCAST, DNS_IP_HACK, SHM_MEM, SHM_MMAP, PKG_MALLOC, DBG_QM_MALLOC, USE_FUTEX, FAST_LOCK-ADAPTIVE_WAIT, USE_DNS_CACHE, USE_DNS_FAILOVER, USE_NAPTR, USE_DST_BLACKLIST, HAVE_RESOLV_RES ADAPTIVE_WAIT_LOOPS=1024, MAX_RECV_BUFFER_SIZE 262144, MAX_LISTEN 16, MAX_URI_SIZE 1024, BUF_SIZE 65535, PKG_SIZE 4MB poll method support: poll, epoll_lt, epoll_et, sigio_rt, select. id: b72876 compiled on 17:16:21 Sep 3 2010 with gcc 4.4.3
Nine years later since first one, the number of new development commits is 14200:
git log --pretty=format:"%h%x09%an%x09%ad%x09%s" --reverse | wc -l 14200
This number represents only the commits done in development branch (GIT master branch). Over all, the number of commits is far more, since every release had its own branch. However, the number includes the commits done during 2005-2008 within Kamailio (OpenSER) project in SVN development branch (SVN trunk).
The total number of files and lines used in repository (code, comments, …):
Total files: 4701 Total lines: 941217
Current lines of code statistics:
ansic: 392801 (73.38%) xml: 124390 (23.24%) sh: 8965 (1.67%) yacc: 3393 (0.63%) perl: 3314 (0.62%) python: 1372 (0.26%) php: 1047 (0.20%) awk: 43 (0.01%)
Current development branch counts over 170 modules (extensions)
ls modules | wc -w 41 app_lua cfg_rpc debugger matrix pipelimit tm app_python counters dialplan mediaproxy privacy topoh auth ctl enum mi_rpc sanity utils auth_identity db_berkeley geoip mqueue sl xhttp avpops db_flatstore iptrtpproxy mtree sms xmlops carrierroute db_mysql lcr pdb textopsx xmlrpc cfg_db db_postgres malloc_test peering tls ls modules_k | wc -w 82 acc domainpolicy nathelper pua_usrloc sqlops acc_radius drouting nat_traversal pua_xmpp sst alias_db exec osp purple statistics auth_db group path pv textops auth_diameter h350 pdt qos tmx auth_radius htable perl ratelimit uac benchmark imc perlvdb regex uac_redirect call_control jabber permissions registrar uri_db cfgutils kex pike rls userblacklist cpl-c ldap presence rr usrloc db_oracle maxfwd presence_dialoginfo rtimer xcap_client db_text memcached presence_mwi rtpproxy xcap_server db_unixodbc mi_datagram presence_xml seas xlog dialog mi_fifo pua siptrace xmpp dispatcher misc_radius pua_bla siputils diversion mi_xmlrpc pua_dialoginfo snmpstats domain msilo pua_mi speeddial ls modules_s | wc -w 51 acc_db bdb domain maxfwd pike speeddial usrloc acc_radius blst eval msilo prefix_route textops xcap acc_syslog cpl-c exec nathelper presence_b2b timer xlog auth_db db_ops fifo options print uac auth_radius dbtext gflags oracle print_lib unixsock avp dialog jabber osp ratelimit uri avp_db dispatcher ldap pdt registrar uri_db avp_radius diversion mangler permissions rr uri_radius
Version 0.8.8 had 22, some of them no longer available:
acc cpl ext jabber msilo pike radius_acc registrar sl textops usrloc auth cpl-c exec im maxfwd mysql print radius_auth rr sms tm
Next table presents the most productive time frames in number of commits and percentage.
Description | Value | Commits | Percent |
---|---|---|---|
Top Hour | 17:00-18:00 | 1259 | 8.87 |
Top Day | Thursday | 2775 | 19.54 |
Top Month | March | 1727 | 12.16 |
Top Year | 2009 | 2267 | 15.96 |
Here are the screenshots with detailed representation over the time of commits:
Many other statistics can be found at:
For example, estimated cost to develop the project from scratch is over 8 millions USD:
One of most interesting evolutions inside the projects was the default configuration file. Started with a completely different format, based on regular expression matching, changed quickly in a programmable language, format that continues today.
If you ever wondered how was the first config file published, then here it is:
# $Id: sip_router.cfg,v 1.1.1.1 2001/09/03 21:27:11 andrei Exp $ # format: # method_re sip_uri_re dest_host # (warning: re cannot contain space) ^R.* ^sip:.*@dorian.* ekina.fokus.gmd.de ^INVITE .* ape # my laptop . . 192.168.46.55 .* .*andrei helios.fokus.gmd.de # end
Probably didn't make it in any production system with this format. The config language was completely different than what is today.
One of first public official releases was 0.8.8 (burned on CD). The config language structure was pretty much the same like today.
Here is the config:
# # $Id: ser.cfg,v 1.10 2002/09/26 10:54:48 janakj Rel $ # # simple quick-start config script # # ----------- global configuration parameters ------------------------ debug=3 # debug level (cmd line: -dddddddddd) fork=yes log_stderror=no # (cmd line: -E) check_via=no # (cmd. line: -v) dns=no # (cmd. line: -r) rev_dns=no # (cmd. line: -R) port=5060 children=4 fifo="/tmp/ser_fifo" # ------------------ module loading ---------------------------------- # Uncomment this if you want to use SQL database #loadmodule "/usr/lib/ser/modules/mysql.so" loadmodule "/usr/lib/ser/modules/sl.so" loadmodule "/usr/lib/ser/modules/print.so" loadmodule "/usr/lib/ser/modules/tm.so" loadmodule "/usr/lib/ser/modules/rr.so" loadmodule "/usr/lib/ser/modules/maxfwd.so" loadmodule "/usr/lib/ser/modules/usrloc.so" loadmodule "/usr/lib/ser/modules/registrar.so" # Uncomment this if you want digest authentication # mysql.so must be loaded ! #loadmodule "/usr/lib/ser/modules/auth.so" # ----------------- setting module-specific parameters --------------- # -- usrloc params -- modparam("usrloc", "db_mode", 0) # Uncomment this if you want to use SQL database # for persistent storage and comment the previous line #modparam("usrloc", "db_mode", 2) # -- auth params -- # Uncomment if you are using auth module # #modparam("auth", "secret", "alsdkhglaksdhfkloiwr") #modparam("auth", "calculate_ha1", yes) # # If you set "calculate_ha1" parameter to yes (which true in this config), # uncomment also the following parameter) # #modparam("auth", "password_column", "password") # ------------------------- request routing logic ------------------- # main routing logic route{ # initial sanity checks -- discard local ACKs, messages with # max_forwars==0, or excessively long requests sl_filter_ACK(); if (!mf_process_maxfwd_header("10")) { sl_send_reply("483","Too Many Hops"); break; }; if (len_gt( max_len )) { sl_send_reply("513", "Message too big"); break; }; # Do strict routing if pre-loaded route headers present rewriteFromRoute(); # if the request is for other domain use UsrLoc # (in case, it does not work, use the following command # with proper names and addresses in it) if (uri==myself) { if (method=="REGISTER") { # Uncomment this if you want to use digest authentication # if (!www_authorize("iptel.org", "subscriber")) { # www_challenge("iptel.org", "0"); # break; # }; save("location"); break; }; # native SIP destinations are handled using our USRLOC DB if (!lookup("location")) { sl_send_reply("404", "Not Found"); break; }; }; # forward to current uri now if (!t_relay()) { sl_reply_error(); }; }
Nowadays we build two applications from same source code: Kamailio and SER.
Next sections show the latest versions of default config files. If you want to see the evolution for each major version, then follow the links:
You can notice in this config the modularity with sub-routes and the usage of string names for routes (e.g., route[REGISTRAR]), first ever introduced by SER in 2007.
# # $Id$ # # First start SER sample config script with: # database, accounting, authentication, multi-domain support # PSTN GW section, named flags, named routes, global-, # domain- and user-preferences with AVPs # Several of these features are only here for demonstration purpose # what can be achieved with the SER config script language. # # If you look for a simpler version with a lot less dependencies # please refer to the ser-basic.cfg file in your SER distribution. # To get this config running you need to execute the following commands # with the new serctl (the capital word are just place holders) # - ser_ctl domain add DOMAINNAME # - ser_ctl user add USERNAME@DOMAINNAME -p PASSWORD # If you want to have PID header for your user # - ser_attr add uid=UID asserted_id="PID" # If you want to have gateway support # - ser_db add attr_types name=gw_ip rich_type=string raw_type=2 description="The gateway IP for the default ser.cfg" default_flags=33 # - ser_attr add global gw_ip=GATEWAY-IP # ----------- global configuration parameters ------------------------ debug=2 # debug level (cmd line: -dddddddddd) #memdbg=10 # memory debug log level #memlog=10 # memory statistics log level #log_facility=LOG_LOCAL0 # sets the facility used for logging (see syslog(3)) /* Uncomment these lines to enter debugging mode fork=no log_stderror=yes */ check_via=no # (cmd. line: -v) dns=no # (cmd. line: -r) rev_dns=no # (cmd. line: -R) #port=5060 #children=4 #user=ser #group=ser #disable_core=yes #disables core dumping #open_fd_limit=1024 # sets the open file descriptors limit #mhomed=yes # usefull for multihomed hosts, small performance penalty #disable_tcp=yes #tcp_accept_aliases=yes # accepts the tcp alias via option (see NEWS) enable_tls=yes # # ------------------ module loading ---------------------------------- #loadpath "modules:modules_s" loadpath "/usr/lib/ser/modules:/usr/lib/ser/modules_s" # load a SQL database for authentication, domains, user AVPs etc. loadmodule "db_mysql" loadmodule "sl" loadmodule "tm" loadmodule "rr" loadmodule "maxfwd" loadmodule "usrloc" loadmodule "registrar" loadmodule "xlog" loadmodule "textops" loadmodule "ctl" loadmodule "cfg_rpc" loadmodule "auth" loadmodule "auth_db" loadmodule "gflags" loadmodule "domain" loadmodule "uri_db" loadmodule "avp" loadmodule "avp_db" loadmodule "acc_db" loadmodule "xmlrpc" #loadmodule "tls" # ----------------- setting script FLAGS ----------------------------- flags FLAG_ACC : 1, # include message in accounting FLAG_FAILUREROUTE : 2; # we are operating from a failure route avpflags dialog_cookie; # handled by rr module # ----------------- setting module-specific parameters --------------- # specify the path to you database here modparam("acc_db|auth_db|avp_db|domain|gflags|usrloc|uri_db", "db_url", "mysql://ser:heslo@127.0.0.1/ser") # -- usrloc params -- # as we use the database anyway we will use it for usrloc as well modparam("usrloc", "db_mode", 1) # -- auth params -- modparam("auth_db", "calculate_ha1", yes) modparam("auth_db", "plain_password_column", "password") # -- rr params -- # add value to ;lr param to make some broken UAs happy modparam("rr", "enable_full_lr", 1) # # limit the length of the AVP cookie to only necessary ones modparam("rr", "cookie_filter", "(account)") # # you probably do not want that someone can simply read and change # the AVP cookie in your Routes, thus should really change this # secret value below modparam("rr", "cookie_secret", "MyRRAVPcookiesecret") # -- gflags params -- # load the global AVPs modparam("gflags", "load_global_attrs", 1) # -- domain params -- # load the domain AVPs modparam("domain", "load_domain_attrs", 1) # -- ctl params -- # by default ctl listens on unixs:/tmp/ser_ctl if no other address is # specified in modparams; this is also the default for sercmd modparam("ctl", "binrpc", "unixs:/tmp/ser_ctl") # listen on the "standard" fifo for backward compatibility modparam("ctl", "fifo", "fifo:/tmp/ser_fifo") # listen on tcp, localhost #modparam("ctl", "binrpc", "tcp:localhost:2046") # -- acc_db params -- # failed transactions (=negative responses) should be logged to modparam("acc_db", "failed_transactions", 1) # comment the next line if you dont want to have accounting to DB modparam("acc_db", "log_flag", "FLAG_ACC") # -- tm params -- # uncomment the following line if you want to avoid that each new reply # restarts the resend timer (see INBOUND route below) #modparam("tm", "restart_fr_on_each_reply", "0") # -- xmlrpc params -- # using a sub-route from the module is a lot safer then relying on the # request method to distinguish HTTP from SIP modparam("xmlrpc", "route", "RPC"); # ------------------------- request routing logic ------------------- # main routing logic route{ # if you have a PSTN gateway just un-comment the follwoing line and # specify the IP address of it to route calls to it #$gw_ip = "1.2.3.4" # first do some initial sanity checks route(INIT); # bypass the rest of the script for CANCELs if possible route(CATCH_CANCEL); # check if the request is routed via Route header or # needs a Record-Route header route(RR); # check if the request belongs to our proxy route(DOMAIN); # handle REGISTER requests route(REGISTRAR); # from here on we want to know you is calling route(AUTHENTICATION); # check if we should be outbound proxy for a local user route(OUTBOUND); # check if the request is for a local user route(INBOUND); # here you could for example try to do an ENUM lookup before # the call gets routed to the PSTN #route(ENUM); # lets see if someone wants to call a PSTN number route(PSTN); # nothing matched, reject it finally sl_reply("404", "No route matched"); } route[FORWARD] { # here you could decide wether this call needs a RTP relay or not # if this is called from the failure route we need to open a new branch if (isflagset(FLAG_FAILUREROUTE)) { append_branch(); } # if this is an initial INVITE (without a To-tag) we might try another # (forwarding or voicemail) target after receiving an error if (method=="INVITE" && strempty(@to.tag)) { t_on_failure("FAILURE_ROUTE"); } # send it out now; use stateful forwarding as it works reliably # even for UDP2TCP if (!t_relay()) { sl_reply_error(); } drop; } route[INIT] { # initial sanity checks -- messages with # max_forwards==0, or excessively long requests if (!mf_process_maxfwd_header("10")) { sl_reply("483","Too Many Hops"); drop; } if (msg:len >= 4096 ) { sl_reply("513", "Message too big"); drop; } # you could add some NAT detection here for example # or you cuold call here some of the check from the sanity module # lets account all initial INVITEs # further in-dialog requests are accounted by a RR cookie (see below) if (method=="INVITE" && strempty(@to.tag)) { setflag(FLAG_ACC); } } route[RPC] { # allow XMLRPC from localhost if ((method=="POST" || method=="GET") && src_ip==127.0.0.1) { if (msg:len >= 8192) { sl_reply("513", "Request to big"); drop; } # lets see if a module wants to answer this dispatch_rpc(); drop; } } route[RR] { # subsequent messages withing a dialog should take the # path determined by record-routing if (loose_route()) { # mark routing logic in request append_hf("P-hint: rr-enforced\r\n"); # if the Route contained the accounting AVP cookie we # set the accounting flag for the acc_db module. # this is more for demonstration purpose as this could # also be solved without RR cookies. # Note: this means all in-dialog request will show up in the # accounting tables, so prepare your accounting software for this ;-) if ($account == "yes") { setflag(FLAG_ACC); } # for broken devices which overwrite their Route's with each # (not present) RR from within dialog requests it is better # to repeat the RRing # and if we call rr after loose_route the AVP cookies are restored # automatically :) record_route(); route(FORWARD); } else if (!method=="REGISTER") { # we record-route all messages -- to make sure that # subsequent messages will go through our proxy; that's # particularly good if upstream and downstream entities # use different transport protocol # if the inital INVITE got the ACC flag store this in # an RR AVP cookie. this is more for demonstration purpose if (isflagset(FLAG_ACC)) { $account = "yes"; setavpflag($account, "dialog_cookie"); } record_route(); } } route[DOMAIN] { # check if the caller is from a local domain lookup_domain("$fd", "@from.uri.host"); # check if the callee is at a local domain lookup_domain("$td", "@ruri.host"); # we dont know the domain of the caller and also not # the domain of the callee -> somone uses our proxy as # a relay if (strempty($t.did) && strempty($f.did)) { sl_reply("403", "Relaying Forbidden"); drop; } } route[REGISTRAR] { # if the request is a REGISTER lets take care of it if (method=="REGISTER") { # check if the REGISTER if for one of our local domains if (strempty($t.did)) { sl_reply("403", "Register forwarding forbidden"); drop; } # we want only authenticated users to be registered if (!www_authenticate("$fd.digest_realm", "credentials")) { if ($? == -2) { sl_reply("500", "Internal Server Error"); } else if ($? == -3) { sl_reply("400", "Bad Request"); } else { if ($digest_challenge != "") { append_to_reply("%$digest_challenge"); } sl_reply("401", "Unauthorized"); } drop; } # check if the authenticated user is the same as the target user if (!lookup_user("$tu.uid", "@to.uri")) { sl_reply("404", "Unknown user in To"); drop; } if ($f.uid != $t.uid) { sl_reply("403", "Authentication and To-Header mismatch"); drop; } # check if the authenticated user is the same as the request originator # you may uncomment it if you care, what uri is in From header #if (!lookup_user("$fu.uid", "@from.uri")) { # sl_reply("404", "Unknown user in From"); # drop; #} #if ($fu.uid != $tu.uid) { # sl_reply("403", "Authentication and From-Header mismatch"); # drop; #} # everything is fine so lets store the binding if (!save_contacts("location")) { sl_reply("400", "Invalid REGISTER Request"); drop; } drop; } } route[AUTHENTICATION] { if (method=="CANCEL" || method=="ACK") { # you are not allowed to challenge these methods break; } # requests from non-local to local domains should be permitted # remove this if you want a walled garden if (strempty($f.did)) { break; } # as gateways are usually not able to authenticate for their # requests you will have trust them base on some other information # like the source IP address. WARNING: if at all this is only safe # in a local network!!! #if (src_ip==a.b.c.d) { # break; #} if (!proxy_authenticate("$fd.digest_realm", "credentials")) { if ($? == -2) { sl_reply("500", "Internal Server Error"); } else if ($? == -3) { sl_reply("400", "Bad Request"); } else { if ($digest_challenge != "") { append_to_reply("%$digest_challenge"); } sl_reply("407", "Proxy Authentication Required"); } drop; } # check if the UID from the authentication meets the From header $authuid = $uid; if (!lookup_user("$fu.uid", "@from.uri")) { del_attr("$uid"); } if ($fu.uid != $fr.authuid) { sl_reply("403", "Fake Identity"); drop; } # load the user AVPs (preferences) of the caller, e.g. for RPID header load_attrs("$fu", "$f.uid"); } route[OUTBOUND] { # if a local user calls to a foreign domain we play outbound proxy for him # comment this out if you want a walled garden if ($f.did != "" && $t.did == "") { append_hf("P-hint: outbound\r\n"); route(FORWARD); } } route[INBOUND] { # lets see if know the callee if (lookup_user("$tu.uid", "@ruri")) { # load the preferences of the callee to have his timeout values loaded load_attrs("$tu", "$t.uid"); # if you want to know if the callee username was an alias # check it like this #if (strempty($tu.uri_canonical)) { # if the alias URI has different AVPs/preferences # you can load them into the URI track like this #load_attrs("$tr", "@ruri"); #} # check for call forwarding of the callee # Note: the forwarding target has to be full routable URI # in this example if ($tu.fwd_always_target != "") { attr2uri("$tu.fwd_always_target"); route(FORWARD); } # native SIP destinations are handled using our USRLOC DB if (lookup_contacts("location")) { append_hf("P-hint: usrloc applied\r\n"); # we set the TM module timers according to the prefences # of the callee (avoid too long ringing of his phones) # Note1: timer values have to be in ms now! # Note2: this makes even more sense if you switch to a voicemail # from the FAILURE_ROUTE below if ($t.fr_inv_timer != 0) { if ($t.fr_timer != 0) { t_set_fr("$t.fr_inv_timer", "$t.fr_timer"); } else { t_set_fr("$t.fr_inv_timer"); } } route(FORWARD); } else { sl_reply("480", "User temporarily not available"); drop; } } } route[PSTN] { # Only if the AVP 'gw_ip' is set and the request URI contains # only a number we consider sending this to the PSTN GW. # Only users from a local domain are permitted to make calls. # Additionally you might want to check the acl AVP to verify # that the user is allowed to make such expensives calls. if ($f.did != "" && $gw_ip != "" && uri=~"sips?:\+?[0-9]{3,18}@.*") { # probably you need to convert the number in the request # URI according to the requirements of your gateway here # if an AVP 'asserted_id' is set we insert an RPID header if ($asserted_id != "") { xlset_attr("$rpidheader", "<sip:%$asserted_id@%@ruri.host>;screen=yes"); replace_attr_hf("Remote-Party-ID", "$rpidheader"); } # just replace the domain part of the RURI with the # value from the AVP and send it out attr2uri("$gw_ip", "domain"); route(FORWARD); } } route[CATCH_CANCEL] { # check whether there is a corresponding INVITE to the CANCEL, # and bypass the rest of the script if possible if (method == CANCEL) { if (!t_relay_cancel()) { # implicit drop if the INVITE was found # INVITE was found but some error occurred sl_reply("500", "Internal Server Error"); drop; } # bad luck, no corresponding INVITE was found, # we have to continue with the script } } failure_route[FAILURE_ROUTE] { # mark for the other routes that we are operating from here on from a # failure route setflag(FLAG_FAILUREROUTE); if (t_check_status("486|600")) { # if we received a busy and a busy target is set, forward it there # Note: again the forwarding target has to be a routeable URI if ($tu.fwd_busy_target != "") { attr2uri("$tu.fwd_busy_target"); route(FORWARD); } # alternatively you could forward the request to SEMS/voicemail here } else if (t_check_status("408|480")) { # if we received no answer and the noanswer target is set, # forward it there # Note: again the target has to be a routeable URI if ($tu.fwd_noanswer_target != "") { attr2uri("$tu.fwd_noanswer_target"); route(FORWARD); } # alternatively you could forward the request to SEMS/voicemail here } }
You can notice here the usage of config defines (#!define XYZ, #!ifdef XYZ, …) which makes very easy to enable/disable features as well as defining values for tokens that are replaced later in config (e.g., DBURL). The config provides advanced features such as NAT traversal with RTPProxy or presence server.
#!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 presence server execute: # - enable mysql # - define WITH_PRESENCE # # *** To enable nat traversal execute: # - define WITH_NAT # - install RTPProxy: http://www.rtpproxy.org # - start RTPProxy: # rtpproxy -l _your_public_ip_ -s udp:localhost:7722 # # *** To enable PSTN gateway routing execute: # - define WITH_PSTN # - set the value of pstn.gw_ip # - check route[PSTN] for regexp routing condition # # *** To enhance accounting execute: # - enable mysql # - define WITH_ACCDB # - add following columns to database #!ifdef ACCDB_COMMENT ALTER TABLE acc ADD COLUMN src_user VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE acc ADD COLUMN src_domain VARCHAR(128) NOT NULL DEFAULT ''; ALTER TABLE acc ADD COLUMN dst_ouser VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE acc ADD COLUMN dst_user VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE acc ADD COLUMN dst_domain VARCHAR(128) NOT NULL DEFAULT ''; ALTER TABLE missed_calls ADD COLUMN src_user VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE missed_calls ADD COLUMN src_domain VARCHAR(128) NOT NULL DEFAULT ''; ALTER TABLE missed_calls ADD COLUMN dst_ouser VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE missed_calls ADD COLUMN dst_user VARCHAR(64) NOT NULL DEFAULT ''; ALTER TABLE missed_calls ADD COLUMN dst_domain VARCHAR(128) NOT NULL DEFAULT ''; #!endif # *** 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 ####### 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 /* uncomment the next line to disable TCP (default on) */ #disable_tcp=yes /* uncomment the next line to disable the auto discovery of local aliases based on reverse DNS on IPs (default on) */ #auto_aliases=no /* add local domain aliases */ #alias="sip.mydomain.com" port=5060 /* uncomment and configure the following line if you want Kamailio to bind on a specific interface/port/proto (default bind on all available) */ #listen=udp:10.0.0.10:5060 ####### Custom Parameters ######### # These parameters can be modified runtime via RPC interface # - see the documentation of 'cfg_rpc' module. # # Format: group.id = value 'desc' description # Access: $sel(cfg_get.group.id) or @cfg_get.group.id # #!ifdef WITH_PSTN # PSTN GW Routing # # - pstn.gw_ip: valid IP or hostname as string value, example: # pstn.gw_ip = "10.0.0.101" desc "My PSTN GW Address" # # - by default is empty to avoid misrouting pstn.gw_ip = "" desc "PSTN GW Address" #!endif ####### Modules Section ######## #set module path mpath="/usr/local/lib/kamailio/modules_k/:/usr/local/lib/kamailio/modules/" /* uncomment next line for MySQL DB support */ #!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 "uri_db.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" #!ifdef WITH_IPAUTH loadmodule "permissions.so" #!endif #!endif /* uncomment next line for aliases support NOTE: a DB (like db_mysql) module must be also loaded */ #loadmodule "alias_db.so" /* uncomment next line for multi-domain support NOTE: a DB (like db_mysql) module must be also loaded NOTE: be sure and enable multi-domain support in all used modules (see "multi-module params" section ) */ #loadmodule "domain.so" #!ifdef WITH_PRESENCE loadmodule "presence.so" loadmodule "presence_xml.so" #!endif #!ifdef WITH_NAT loadmodule "nathelper.so" loadmodule "rtpproxy.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) # ----- rr 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) # ----- uri_db params ----- /* by default we disable the DB support in the module as we do not need it in this configuration */ modparam("uri_db", "use_uri_table", 0) modparam("uri_db", "db_url", "") # ----- acc params ----- /* what sepcial events should be accounted ? */ modparam("acc", "early_media", 1) modparam("acc", "report_ack", 1) modparam("acc", "report_cancels", 1) /* 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", "failed_transaction_flag", 3) modparam("acc", "log_flag", 1) modparam("acc", "log_missed_flag", 2) modparam("acc", "log_extra", "src_user=$fU;src_domain=$fd;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd") /* enhanced DB accounting */ #!ifdef WITH_ACCDB modparam("acc", "db_flag", 1) modparam("acc", "db_missed_flag", 2) modparam("acc", "db_url", DBURL) modparam("acc", "db_extra", "src_user=$fU;src_domain=$fd;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd") #!endif # ----- usrloc params ----- /* enable DB persistency for location entries */ #!ifdef WITH_USRLOCDB modparam("usrloc", "db_mode", 2) modparam("usrloc", "db_url", DBURL) #!endif # ----- auth_db params ----- /* enable the DB based authentication */ #!ifdef WITH_AUTH modparam("auth_db", "calculate_ha1", yes) modparam("auth_db", "password_column", "password") modparam("auth_db", "db_url", DBURL) modparam("auth_db", "load_credentials", "") #!ifdef WITH_IPAUTH modparam("permissions", "db_url", DBURL) modparam("permissions", "db_mode", 1) #!endif #!endif # ----- alias_db params ----- /* uncomment the following lines if you want to enable the DB based aliases */ #modparam("alias_db", "db_url", DBURL) # ----- domain params ----- /* uncomment the following lines to enable multi-domain detection support */ #modparam("domain", "db_url", DBURL) #modparam("domain", "db_mode", 1) # Use caching # ----- multi-module params ----- /* uncomment the following line if you want to enable multi-domain support in the modules (dafault off) */ #modparam("alias_db|auth_db|usrloc|uri_db", "use_domain", 1) # ----- presence params ----- /* enable presence server support */ #!ifdef WITH_PRESENCE modparam("presence|presence_xml", "db_url", DBURL) modparam("presence_xml", "force_active", 1) modparam("presence", "server_address", "sip:10.0.0.10:5060") #!endif #!ifdef WITH_NAT # ----- rtpproxy ----- modparam("rtpproxy", "rtpproxy_sock", "udp:127.0.0.1:7722") # ----- nathelper ----- modparam("nathelper", "natping_interval", 30) modparam("nathelper", "ping_nated_only", 1) modparam("nathelper", "sipping_bflag", 7) modparam("nathelper", "sipping_from", "sip:pinger@kamailio.org") modparam("registrar|nathelper", "received_avp", "$avp(i:80)") modparam("usrloc", "nat_bflag", 6) #!endif ####### Routing Logic ######## # main request routing logic route { # per request initial checks route(REQINIT); # NAT detection route(NAT); # handle requests within SIP dialogs route(WITHINDLG); ### only initial requests (no To tag) # CANCEL processing if (is_method("CANCEL")) { if (t_check_trans()) t_relay(); exit; } t_check_trans(); # authentication route(AUTH); # record routing for dialog forming requests (in case they are routed) # - remove preloaded route headers remove_hf("Route"); if (is_method("INVITE|SUBSCRIBE")) record_route(); # account only INVITEs if (is_method("INVITE")) { setflag(1); # do accounting } # dispatch requests to foreign domains route(SIPOUT); ### requests for my local domains # handle presence related requests route(PRESENCE); # handle registrations route(REGISTRAR); if ($rU==$null) { # request with no Username in RURI sl_send_reply("484","Address Incomplete"); exit; } # dispatch destinations to PSTN route(PSTN); # user location service route(LOCATION); route(RELAY); } route[RELAY] { #!ifdef WITH_NAT if (check_route_param("nat=yes")) { setbflag("6"); } if (isflagset(5) || isbflagset("6")) { route(RTPPROXY); } #!endif /* example how to enable some additional event routes */ if (is_method("INVITE")) { #t_on_branch("BRANCH_ONE"); t_on_reply("REPLY_ONE"); t_on_failure("FAIL_ONE"); } if (!t_relay()) { sl_reply_error(); } exit; } # Per SIP request initial checks route[REQINIT] { if (!mf_process_maxfwd_header("10")) { sl_send_reply("483","Too Many Hops"); exit; } if(!sanity_check("1511", "7")) { xlog("Malformed SIP message from $si:$sp\n"); exit; } } # 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()) { if (is_method("BYE")) { setflag(1); # do accounting ... setflag(3); # ... even if the transaction fails } route(RELAY); } else { if (is_method("SUBSCRIBE") && uri == myself) { # in-dialog subscribe requests route(PRESENCE); exit; } if ( is_method("ACK") ) { if ( t_check_trans() ) { # non 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.\n"); exit; } } sl_send_reply("404","Not here"); } exit; } } # Handle SIP registrations route[REGISTRAR] { if (is_method("REGISTER")) { if(isflagset(5)) { setbflag("6"); # uncomment next line to do SIP NAT pinging ## setbflag("7"); } if (!save("location")) sl_reply_error(); exit; } } # USER location service route[LOCATION] { # apply DB based aliases (uncomment to enable) ##alias_db_lookup("dbaliases"); if (!lookup("location")) { switch ($rc) { case -1: case -3: t_newtran(); t_reply("404", "Not Found"); exit; case -2: sl_send_reply("405", "Method Not Allowed"); exit; } } # when routing via usrloc, log the missed calls also if (is_method("INVITE")) { setflag(2); } } # Presence server route route[PRESENCE] { if(!is_method("PUBLISH|SUBSCRIBE")) return; #!ifdef WITH_PRESENCE if (!t_newtran()) { sl_reply_error(); exit; }; if(is_method("PUBLISH")) { handle_publish(); t_release(); } else if( is_method("SUBSCRIBE")) { handle_subscribe(); t_release(); } exit; #!endif # if presence enabled, this part will not be executed if (is_method("PUBLISH") || $rU==$null) { sl_send_reply("404", "Not here"); exit; } return; } # Authentication route route[AUTH] { #!ifdef WITH_AUTH if (is_method("REGISTER")) { # authenticate the REGISTER requests (uncomment to enable auth) if (!www_authorize("$td", "subscriber")) { www_challenge("$td", "0"); exit; } if ($au!=$tU) { sl_send_reply("403","Forbidden auth ID"); exit; } } else { #!ifdef WITH_IPAUTH if(allow_source_address()) { # source IP allowed return; } #!endif # authenticate if from local subscriber if (from_uri==myself) { if (!proxy_authorize("$fd", "subscriber")) { proxy_challenge("$fd", "0"); exit; } if (is_method("PUBLISH")) { if ($au!=$tU) { sl_send_reply("403","Forbidden auth ID"); exit; } } else { if ($au!=$fU) { sl_send_reply("403","Forbidden auth ID"); exit; } } consume_credentials(); # caller authenticated } else { # caller is not local subscriber, then check if it calls # a local destination, otherwise deny, not an open relay here if (!uri==myself) { sl_send_reply("403","Not relaying"); exit; } } } #!endif return; } # Caller NAT detection route route[NAT] { #!ifdef WITH_NAT force_rport(); if (nat_uac_test("19")) { if (method=="REGISTER") { fix_nated_register(); } else { fix_nated_contact(); } setflag(5); } #!endif return; } # RTPProxy control route[RTPPROXY] { #!ifdef WITH_NAT if (is_method("BYE")) { unforce_rtp_proxy(); } else if (is_method("INVITE")){ force_rtp_proxy(); } if (!has_totag()) add_rr_param(";nat=yes"); #!endif return; } # Routing to foreign domains route[SIPOUT] { if (!uri==myself) /* replace with following line if multi-domain support is used */ ##if (!is_uri_host_local()) { append_hf("P-hint: outbound\r\n"); route(RELAY); } } # PSTN GW routing route[PSTN] { #!ifdef WITH_PSTN # check if PSTN GW IP is defined if (strempty($sel(cfg_get.pstn.gw_ip))) { xlog("SCRIPT: PSTN rotuing enabled but pstn.gw_ip not defined\n"); return; } # route to PSTN dialed numbers starting with '+' or '00' # (international format) # - update the condition to match your dialing rules for PSTN routing if(!($rU=~"^(\+|00)[1-9][0-9]{3,20}$")) return; # only local users allowed to call if(from_uri!=myself) { sl_send_reply("403", "Not Allowed"); exit; } $ru = "sip:" + $rU + "@" + $sel(cfg_get.pstn.gw_ip); route(RELAY); exit; #!endif return; } # Sample branch router branch_route[BRANCH_ONE] { xdbg("new branch at $ru\n"); } # Sample onreply route onreply_route[REPLY_ONE] { xdbg("incoming reply\n"); #!ifdef WITH_NAT if ((isflagset(5) || isbflagset("6")) && status=~"(183)|(2[0-9][0-9])") { force_rtp_proxy(); } if (isbflagset("6")) { fix_nated_contact(); } #!endif } # Sample failure route failure_route[FAIL_ONE] { #!ifdef WITH_NAT if (is_method("INVITE") && (isbflagset("6") || isflagset(5))) { unforce_rtp_proxy(); } #!endif if (t_is_canceled()) { exit; } # uncomment the following lines if you want to block client # redirect based on 3xx replies. ##if (t_check_status("3[0-9][0-9]")) { ##t_reply("404","Not found"); ## exit; ##} # uncomment the following lines if you want to redirect the failed # calls to a different new destination ##if (t_check_status("486|408")) { ## sethostport("192.168.2.100:5060"); ## append_branch(); ## # do not set the missed call flag again ## t_relay(); ##} }