Hi all, I have added nathelper support to basic ser.cfg file of Ottendorf release. It is a simple setup without any complexity of using other features such as connectivity_realm etc. You can download it at http://www.iptel.org/ser/howtos/optimizing_the_use_of_rtp_proxy File is called ser_config_2_0.txt
The more complex one will come with updated document soon.
Lada
Hi Lada
many thanks for putting this together. I have now spent some time cross-merging between your config file, and my own. I have tested both and they are working fine. I have some minor feedback to your config file, see the patch below (ser_config_2_0-minor.patch)
The majority of my comments are about indentation and whitespace, most people probably dont care about that, but I prefer that the config file looks clean and tidy, with no trailing whitespace, only tab etc. I hope you can consider that for your "main" file.
A major comment is here:
- if ((status =~ "(183)|2[0-9][0-9]") && !search("^Content-Length: 0")) { + + if ((status =~ "(183)|2[0-9][0-9]") && search("^(Content-Type|c):.*application/sdp")) { force_rtp_proxy();
- basically instead of calling force_rtp_proxy() for all messages with content-length>0, we should check that the content type is SDP instead. Also my fix supports compact header form of "Content-Type"..
you can download my config file from here: (also attached)
/alfred
Ladislav Andel wrote:
Hi all, I have added nathelper support to basic ser.cfg file of Ottendorf release. It is a simple setup without any complexity of using other features such as connectivity_realm etc. You can download it at http://www.iptel.org/ser/howtos/optimizing_the_use_of_rtp_proxy File is called ser_config_2_0.txt
The more complex one will come with updated document soon.
Lada
# # ser.cfg # # Copyright (C) 2006 - 2007 Alfred E. Heggestad # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. #
# ----------- global configuration parameters ------------------------
debug=3 # 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)
listen=ser listen=tls:ser:5061 listen=localhost
port=5060 children=2 #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=1
# For STUN testing stun_refresh_interval=25000 # number in millisecond (default 0); value for attribute REFRESH INTERVAL stun_allow_stun=1 # 0 | 1 (off | on - default 1); use STUN or not if compiled stun_allow_fp=0 # 0 | 1 (off | on - default 1); use FINGERPRINT attribute
# ------------------ module loading ----------------------------------
loadmodule "/usr/local/lib/ser/modules/mysql.so" loadmodule "/usr/local/lib/ser/modules/sl.so" loadmodule "/usr/local/lib/ser/modules/tm.so" loadmodule "/usr/local/lib/ser/modules/rr.so" loadmodule "/usr/local/lib/ser/modules/maxfwd.so" loadmodule "/usr/local/lib/ser/modules/msilo.so" loadmodule "/usr/local/lib/ser/modules/usrloc.so" loadmodule "/usr/local/lib/ser/modules/registrar.so" loadmodule "/usr/local/lib/ser/modules/xlog.so" loadmodule "/usr/local/lib/ser/modules/textops.so" loadmodule "/usr/local/lib/ser/modules/ctl.so" loadmodule "/usr/local/lib/ser/modules/fifo.so" loadmodule "/usr/local/lib/ser/modules/auth.so" loadmodule "/usr/local/lib/ser/modules/auth_db.so" loadmodule "/usr/local/lib/ser/modules/gflags.so" loadmodule "/usr/local/lib/ser/modules/domain.so" loadmodule "/usr/local/lib/ser/modules/uri.so" loadmodule "/usr/local/lib/ser/modules/uri_db.so" loadmodule "/usr/local/lib/ser/modules/avp.so" loadmodule "/usr/local/lib/ser/modules/avp_db.so" loadmodule "/usr/local/lib/ser/modules/avpops.so" loadmodule "/usr/local/lib/ser/modules/acc_db.so" loadmodule "/usr/local/lib/ser/modules/xmlrpc.so" loadmodule "/usr/local/lib/ser/modules/eval.so" loadmodule "/usr/local/lib/ser/modules/nathelper.so" loadmodule "/usr/local/lib/ser/modules/acc_syslog.so" loadmodule "/usr/local/lib/ser/modules/tls.so" loadmodule "/usr/local/lib/ser/modules/sanity.so"
# ----------------- setting script FLAGS ----------------------------- flags FLAG_ACC : 1, # include message in accouting FLAG_FAILUREROUTE : 2, # we are operating from a failure route FLAG_NAT_UAC : 3, # NAT flags FLAG_NAT_UAS : 4;
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 -- modparam("usrloc", "db_mode", 1)
# -- auth params -- modparam("auth_db", "calculate_ha1", yes) modparam("auth_db", "password_column", "password")
# -- rr params -- modparam("rr", "enable_full_lr", 1) modparam("rr", "cookie_filter", "(account)") modparam("rr", "cookie_secret", "b9bEg61ExAOrMsASsdsdS19asj")
# -- gflags params -- modparam("gflags", "load_global_attrs", 1)
# -- domain params -- modparam("domain", "load_domain_attrs", 1)
# -- ctl params -- modparam("ctl", "binrpc", "unixs:/tmp/ser_ctl") modparam("ctl", "fifo", "fifo:/tmp/ser_fifo") modparam("ctl", "binrpc", "tcp:127.0.0.1:2046")
# -- acc_db params -- modparam("acc_db", "failed_transactions", 1)
# comment the next line if you dont want to have accouting to DB modparam("acc_db", "log_flag", "FLAG_ACC")
# -- registrar params -- modparam("registrar", "save_nat_flag", "FLAG_NAT_UAC") modparam("registrar", "load_nat_flag", "FLAG_NAT_UAS")
# -- nathelper params -- modparam("nathelper", "natping_interval", 30) modparam("nathelper", "natping_method", "OPTIONS") modparam("nathelper", "ping_nated_only", 1) modparam("nathelper", "rtpproxy_sock", "udp:127.0.0.1:22222")
# -- 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");
# -- acc_syslog params -- modparam("acc_syslog", "early_media", no) modparam("acc_syslog", "failed_transactions", no) modparam("acc_syslog", "report_ack", no) modparam("acc_syslog", "report_cancels", no) modparam("acc_syslog", "log_flag", "acc.log") modparam("acc_syslog", "log_missed_flag", "acc.missed");
# -- tls params -- modparam("tls", "config", "tls.cfg")
# ------------------------- 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"
xlog("L_NOTICE", "%rm From <%fu> To <%tu> (%ua)\n");
# first do some initial sanity checks route(INIT);
# 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);
# check if any UA participating in the call is behind NAT route(NAT_DETECT);
# 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
route(NAT_MANGLE); t_on_reply("NAT_MANGLE");
# for NATed clients record route following cookies except it has already been loose routed if ($record_route_nated && !$loose_route) { setavpflag("$nated_caller", "dialog_cookie"); setavpflag("$nated_callee", "dialog_cookie"); } # avoids double record routing if loose routed if (!$loose_route) record_route();
# 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" && @to.tag=="") { t_on_failure("FAILURE_ROUTE"); }
# if the downstream UA does not support MESSAGE requests # go to failure_route[1] if (method=="MESSAGE") { t_on_failure("MESSAGE"); }
# send it out now; use stateful forwarding as it works reliably # even for UDP2TCP if (!t_relay()) { sl_reply_error(); } drop; }
route[INIT] { # MUST be done *after* route(RPC) sanity_check();
# 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 >= max_len ) { sl_reply("513", "Message too big"); drop; }
# you could add some NAT detection here for example #route(NAT_DETECT); # 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" && @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 # accouting tables, so prepare your accounting software for this ;-) if ($account == "yes") { setflag(FLAG_ACC); }
# this attribute is used for check in route[FORWARD] $loose_route = true;
# 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"); } } }
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 (!$t.did && !$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 (!$t.did) { xlog("L_ERR", "%rm From <%fu> 403 Register forwarding forbidden\n"); sl_reply("403", "Register forwarding forbidden"); drop; }
# we want only authenticated users to be registered if (!www_authenticate("$fd.digest_realm", "credentials")) { if ($? == -2) { xlog("L_ERR", "%rm From <%fu> 500 Internal Server Error\n"); sl_reply("500", "Internal Server Error"); } else if ($? == -3) { xlog("L_ERR", "%rm From <%fu> 400 Bad Request\n"); 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; }
# everyhting is fine so lets store the binding if (!save_contacts("location")) { sl_reply("400", "Invalid REGISTER Request"); drop; }
# MSILO - dumping user's offline messages if (m_dump("")) { xlog("L_DBG", "MSILO: offline messages dumped - if they were\n"); } else { xlog("L_DBG", "MSILO: no offline messages dumped\n"); };
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 (! $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[NAT_DETECT] { # xlog("L_NOTICE","route[NAT_DETECT] entered NAT_DETECT route");
# Skip spiralling requets if (src_ip == 127.0.0.1) return;
# Skip requests that already passed a proxy if (is_present_hf("^Record-Route:")) { return; }
if (nat_uac_test("3")) { force_rport(); setflag("FLAG_NAT_UAC"); $nated_caller = true; }
return; }
route[NAT_MANGLE] { # Rewrite Contact if the UAC is behind NAT if ($nated_caller || $t.nated_caller) { if (method == "REGISTER") { xlog("L_NOTICE","NAT_MANGLE: Fix NAT-ted REGISTER"); fix_nated_register(); } else { xlog("L_NOTICE","NAT_MANGLE: Fix NAT-ted %rm"); fix_nated_contact(); } }
if (method == "BYE" || method == "CANCEL") { unforce_rtp_proxy(); } else if (method == "INVITE") { # If either UAC or UAS is behind NAT then go on # if (!$f.nated_caller && !$t.nated_callee) return;
# Force record routing if either party is behind NAT $record_route_nated = true;
# rewrites IP address and port in SDP body force_rtp_proxy(); }
return 1; }
onreply_route["NAT_MANGLE"] { xlog("L_NOTICE","onreply_route[NAT_MANGLE] %rs %rr");
# Rewrite Contact in 200 OK if UAS is behind NAT
if ($nated_callee || $t.nated_callee) { fix_nated_contact(); }
# Apply RTP proxy if necessary, but only for INVITE transactions # and 183 or 2xx replies if (!$f.nated_caller && !$t.nated_callee) return;
if (@cseq.method != "INVITE") return;
if ((status =~ "(183)|2[0-9][0-9]") && search("^(Content-Type|c):.*application/sdp")) { xlog("L_NOTICE","onreply_route[NAT_MANGLE] force rtp proxy"); force_rtp_proxy(); } return; }
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 (! $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) { if ($t.fr_timer) { t_set_fr("$t.fr_inv_timer", "$t.fr_timer"); } else { t_set_fr("$t.fr_inv_timer"); } }
if (isflagset("FLAG_NAT_UAS")) { xlog("L_NOTICE","route[INBOUND] callee is behind NAT - setting $nated_callee = true"); $nated_callee = true; } route(FORWARD); } else { # User is offline # we care about MESSAGEs if (method == "MESSAGE") {
if (!t_newtran()) { sl_reply_error(); break; }
# store only text messages NOT isComposing... ! if (search("^(Content-Type|c):.*application/im-iscomposing+xml.*")) { t_reply("202", "Ignored"); break; }
xlog("L_NOTICE", "MESSAGE received -> storing using MSILO\n");
# MSILO - storing as offline message if (m_store("0", "")) { xlog("L_NOTICE", "MSILO: offline message stored\n"); if (!t_reply("202", "Accepted")) { sl_reply_error(); }; } else { xlog("L_NOTICE", "MSILO: offline message NOT stored\n"); if (!t_reply("503", "Service Unavailable")) { sl_reply_error(); }; };
drop; }
xlog("L_NOTICE","route[INBOUND] 480 User Offline"); sl_reply("480", "User Offline"); 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); } }
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 } }
failure_route[MESSAGE] { xlog("L_ERR", "failure route MESSAGE\n");
# forwarding failed -- check if the request was a MESSAGE if (method == "MESSAGE") { xlog("L_NOTICE", "MSILO: the downstream UA doesn't support MESSAGEs\n"); # we have changed the R-URI with the contact address, ignore it now if (m_store("1", "")) { xlog("L_NOTICE", "MSILO: offline message stored\n"); t_reply("202", "Accepted"); } else { xlog("L_NOTICE", "MSILO: offline message NOT stored\n"); t_reply("503", "Service Unavailable"); } } }
### EOF ser.cfg ###
Hi Alfred, thanks for cleaning and tidying the config file. That's the one thing I need to learn. I like cleanness in the code too :). I will use it and replace the current ser.cfg on iptel.org.
Also, thanks for comments about the Content Type.
The update of my doc is postponed since I'm quite busy at the end of the semester at my uni.
Lada
Alfred E. Heggestad wrote:
Hi Lada
many thanks for putting this together. I have now spent some time cross-merging between your config file, and my own. I have tested both and they are working fine. I have some minor feedback to your config file, see the patch below (ser_config_2_0-minor.patch)
The majority of my comments are about indentation and whitespace, most people probably dont care about that, but I prefer that the config file looks clean and tidy, with no trailing whitespace, only tab etc. I hope you can consider that for your "main" file.
A major comment is here:
if ((status =~ "(183)|2[0-9][0-9]") &&
!search("^Content-Length: 0")) {
if ((status =~ "(183)|2[0-9][0-9]") &&
search("^(Content-Type|c):.*application/sdp")) { force_rtp_proxy();
- basically instead of calling force_rtp_proxy() for all messages with
content-length>0, we should check that the content type is SDP instead. Also my fix supports compact header form of "Content-Type"..
you can download my config file from here: (also attached)
/alfred
Ladislav Andel wrote:
Hi all, I have added nathelper support to basic ser.cfg file of Ottendorf release. It is a simple setup without any complexity of using other features such as connectivity_realm etc. You can download it at http://www.iptel.org/ser/howtos/optimizing_the_use_of_rtp_proxy File is called ser_config_2_0.txt
The more complex one will come with updated document soon.
Lada