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)
http://www.creytiv.com/sip/
/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", "(a)from.uri.host")ost");
# check if the callee is at a local domain
lookup_domain("$td", "(a)ruri.host")ost");
# 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", "(a)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", "(a)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", "(a)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 ###