2008年11月7日 星期五

How to Configure OpenSER: SIP Registar, SIP Proxy and Far-End NAT Traversal for Media

http://www.jeremy-mcnamara.com/2007/03/28/how-to-configure-openser-sip-registar-sip-proxy-and-far-end-nat-traversal-for-media/

OpenSER is a flexible, mature and stable SIP communications server. However unlike Asterisk, OpenSER requires extensive knowledge of the SIP Protocol and presumes nothing about your configuration or method of deployment.
I am basing this configuration on OpenSER v1.1.1 which is currently considered the most 'stable' version. Although v1.2 has been released, I am going to let others find and kill the lingering bugs before diving in myself.
This configuration will utilize the necessary modules and configuration directives of OpenSER to create a SIP Registrar, conditionally detect and route based on the dialed URI (digits). Plus we will overcome most NAT situations that many home and small business level deployments will encounter, using the MediaProxy option. (The other option being the older RTPProxy method)
The IP Address for this configuration will be 1.2.3.4, of course you will need to replace this with your own IP address. You also must setup an instance of MySQL and allow OpenSER to communicate with it (MySQL can very easily run on the same machine) I will use 'openser' as the username and 'valid_pass' as the password.
Once you have MySQL running you need to find the 'mysqldb.sh' script located in the scripts directory inside of the OpenSER v1.1.1 tarball.
This will create the 'openser' database, tables and required initial data.
proxy:# sh ./mysqldb.sh create
Next copy the file 'openserctl.mysql' from the scripts directory into /usr/local/lib/openser/openserctl/ directory
proxy:# cp openserctl.mysql /usr/local/lib/openser/openserctl/
The OpenSER make install process should do this for you, but as of v1.1.1 this did not happen for me.
Finally we can dive into the configuration of OpenSER. The default OpenSER configuration file is /usr/local/etc/openser/openser.cfg
Lets dive right in.
openser.cfg:
debug=3
fork=no
log_stderror=yes
Those three options set the stage for how OpenSER will act and function. By not forking and logging to STDERR one can run openser directly to gather information about an invalid configuration directive or perhaps why a specific module isn't loading as expected. However to start OpenSER using openserctl you will need to set fork=yes and log_stderror=no.
listen=1.2.3.4
alias=1.2.3.4
port=5060
The next 3 inform OpenSER which interface and tcp/dup ports to listen on. Pretty standard stuff, so far.
children=4
The children directive informs OpenSER of how many 'child' threads to keep around to process incoming messages. 4 seems to be a reasonable default for most systems. If you really start to scale up your OpenSER instance you will want to bump this value up, but remember each additional thread takes more memory so there is somewhat of a trade off - performance versus memory usage.
dns=no
rev_dns=no
Disabling the looking up up DNS is the most scalable option. Only enable the looking up of DNS if you really need it. In fact, I haven't found a really good reason for OpenSER to need to know about DNS names.
fifo="/tmp/openser_fifo"
fifo_db_url="mysql://openser:valid_pass@localhost/openser"
Those define the FIFOs that OpenSER will use to communicate on. Remember to provide valid database information.
loadmodule "/usr/local/lib/openser/modules/mysql.so"
loadmodule "/usr/local/lib/openser/modules/sl.so"
loadmodule "/usr/local/lib/openser/modules/tm.so"
loadmodule "/usr/local/lib/openser/modules/rr.so"
loadmodule "/usr/local/lib/openser/modules/maxfwd.so"
loadmodule "/usr/local/lib/openser/modules/usrloc.so"
loadmodule "/usr/local/lib/openser/modules/registrar.so"
loadmodule "/usr/local/lib/openser/modules/auth.so"
loadmodule "/usr/local/lib/openser/modules/auth_db.so"
loadmodule "/usr/local/lib/openser/modules/uri.so"
loadmodule "/usr/local/lib/openser/modules/uri_db.so"
loadmodule "/usr/local/lib/openser/modules/mediaproxy.so"
loadmodule "/usr/local/lib/openser/modules/nathelper.so"
loadmodule "/usr/local/lib/openser/modules/textops.so"
loadmodule "/usr/local/lib/openser/modules/domain.so"
loadmodule "/usr/local/lib/openser/modules/xlog.so"
loadmodule "/usr/local/lib/openser/modules/uac.so"
loadmodule "/usr/local/lib/openser/modules/speeddial.so"
loadmodule "/usr/local/lib/openser/modules/avpops.so"
The loading of the necessary modules is the meat and potatos for this configuration of OpenSER. I won't go into the specifics of each module here as the OpenSER team has done an excellent job of documenting the modules.
modparam("usrloc|auth_db|domain|speeddial|acc", "db_url", "mysql://openser:valid_pass@localhost/openser")
modparam("auth_db", "calculate_ha1", 1)
modparam("auth_db", "use_domain", 0)
modparam("domain", "db_mode", 1)
modparam("nathelper", "rtpproxy_disable", 1)
modparam("nathelper", "natping_interval", 60)
modparam("mediaproxy","natping_interval", 30)
modparam("mediaproxy","mediaproxy_socket", "/var/run/mediaproxy.sock")
modparam("usrloc", "db_mode", 2)
modparam("usrloc", "use_domain", 0)
modparam("registrar", "default_expires", 60)
modparam("registrar", "min_expires", 30)
modparam("registrar", "nat_flag", 6)
modparam("registrar", "use_domain", 0)
modparam("rr", "enable_full_lr", 1)
modparam("auth", "rpid_suffix", ";party=calling;id-type=subscriber;screen=yes")
modparam("auth", "rpid_avp", "s:rpid")
Same goes for the modparams - The OpenSER team has documented these quite nicely. I will point out that we disable the rtpproxy, enable mediaproxy, enable write-through database mode for the user location, enable full loose routing support (to appease broken SIP User Agents), and setup a few 'flags' for logging, accounting and NAT.
Flags are a fun concept - Generally speaking a flag in OpenSER is like sticking a PostIT? Note on to specific SIP messages. Then later on in the 'route' processing we can check to see if specific notes have been left, making for much cleaner and simpler configuration.
Now we get into the "main" section of the configuration. In the OpenSER configuration everything is ran through a route block - meaning every SIP message gets processed thru a block of code named route (or you can think about it as a 'main' function, if you are a programmer)
route {
# Sanity Check
# ------------
if (msg:len > max_len) {
sl_send_reply("513", "Message Overflow");
return;;
};

if (!mf_process_maxfwd_header("10")) {
sl_send_reply("483", "Too Many Hops");
return;
};
The first two directives in our route block ensure we don't process a message that is too large (buffer overflow) or a message that we believe to be looping or otherwise confused.
# Record Route and NAT Preset
# --------------------
if (method == "INVITE" && client_nat_test("3")) {
# Must add valid IP address below
record_route_preset("1.2.3.4:5060;nat=yes");
} else if (method != "REGISTER") {
record_route();
};
The 'if' directive here checks to see if the current SIP message (in OpenSER terms this is called a 'method') is an INVITE and calls a function to determine if the entity sending the current SIP message is behind NAT or not. The '3′ instructs the function to test using the first two test methods, which are detailed on the NATHELPER module. If both of those conditions are true, we call a function that appends a small bit of information to the SIP message, which we will utilize later on in the route and its related blocks.
Then If the method is not a REGISTER, we setup the necessary SIP headers to ensure that further SIP messages come back through our SIP Proxy, by calling the record_route() function.
# Call Tear down
if (method=="BYE" || method=="CANCEL") {
end_media_session();
};
Then a call get hung up, we need to detect this to ensure our RTP MediaProxy gets informed to stop relaying audio. We also set our first flag. In this case we inform the accounting module that we would like to account for Bye and Cancel methods - meaning we want 'stop' Call Detail Records. Since we defined '1′ as our accounting flag, we simply pass a '1′ to the setflag() function - Pretty simple. The accounting module takes care of everything else for us.
# Loose Route
# -----------
if (loose_route()) {
if (has_totag() && (method == "INVITE" || method == "ACK")) {
if (client_nat_test("3") || search("^Route:.*;nat=yes")) {
setflag(6);
use_media_proxy();
};
};
route(1);
return;
};
This is a rather complex and powerful group of directives. Without getting into the gory details of the SIP Protocol, this block determines if we are using the 'loose routing' concept. If so, we need to check for NAT (possibly setting flag 6) and informing the MediaProxy we will need its services. Most often than not we will be using the loose routing conventions versus strict routing, as defined in RFC3261.
We also call route(1) - In our configuration this is what we call the 'default' route or the terminating route. We will document the implementation and usage of route(1) here in a few minutes.
# Call Type Processing
# --------------------
if (uri != myself) {
route(1);
return;
};
If the 'call' is no longer for this particular proxy - send the message on its way (via the 'default' route). This is sort of another sanity check for our simple configuration. However in more complex situations, examining the URI and properly routing the messages will become very important. Unfortunately those concepts are beyond the scope of this document.
if (uri == myself) {
if (method == "BYE") {
route(4);
return;
} else if (method == "CANCEL") {
route(4);
return;
} else if (method == "INVITE") {
route(3);
return;
} else if (method == "REGISTER") {
route(2);
return;
} else if (method == "NOTIFY") {
sl_send_reply("200", "Understood");
return;
} else if (method == "OPTIONS") {
sl_send_reply("200", "Got it");
return;
}
};
route(1);
}
Finally we reach the end of our route block. We test each SIP method type and send them to an appropriate 'route' blocks, with the exceptions being NOTIFY and OPTIONS methods. These two methods typcially get used (by Asterisk and many SIP Phones) to either assist in keeping firewall/NAT paths open and/or determining if the proxy is still allve or not. Asterisk can also calculate (qualify=1000) how long it takes for the message to be responded to, giving a sort of internet path health status. By processing the NOTIFY and OPTIONS methods here, we can avoid 404 and/or Too Many Hops message, which in themselves are not necessarily a bad thing, but could lead to confusion.
# Default Message Handling
# -----------------------
route[1] {
t_on_reply("1");
if (!t_relay()) {
if (method=="INVITE" || method=="ACK") {
end_media_session();
};
sl_reply_error();
};
}
Now we are down to the specific route blocks. As we discussed above, route[1] is the default message route. Here we ensure the SIP message gets properly responded to or sent on to its intended location, if the message is intended for another SIP Proxy.
We also check to see if we are processing an INVITE or ACK (to an invite) so we can inform the MediaProxy to end its session.
# REGISTER Message Handling
# -------------------------
route[2] {
if (!search("^Contact:\ +\*") && client_nat_test("7")) {
setflag(6);
fix_nated_register();
force_rport();
};
sl_send_reply("100", "Trying");
if (!www_authorize("","subscriber")) {
www_challenge("","0");
return;
};
if (!check_to()) {
sl_send_reply("401", "Unauthorized");
return;
};
consume_credentials();
if (!save("location")) {
sl_reply_error();
};
}
route[2] is our REGISTER method processing block. This is where we examine the credentials and determine if we are going to allow the SIP UA to register or not.
Notice the call to consume_credentials(), this is a very useful function that removes the unnecessary SIP headers that can contain sensitive authentication details.
# INVITE Message Handling
# ----------------------------------
route[3] {
# Test for nat, perhaps fix headers
if (client_nat_test("3")) {
setflag(7);
force_rport();
fix_nated_contact();
};

# 3 and 4 digits URIs are sent to our Asterisk PBX
if ((uri =~ "^sip:[0-9]{3}@.*") || (uri =~ "^sip:[0-9]{4}@.*")) {
rewritehost("pbx.valid.host.com:5060");
use_media_proxy();
route(1);
return;
};

# Any URI that begins with 1 plus 10 digits authenticate and pass on
# to our SIP Provider
if (uri =~ "^sip:1[0-9]{10}@.*") {
# Authenticate these calls
if (!proxy_authorize("","subscriber")) {
proxy_challenge("","0?);
return;
} else if (!check_from()) {
sl_send_reply("403?, "Use From ID");
return;
};
consume_credentials();
rewritehostport("proxy-1.nufone.net:5060?);
route(1);
return;
};

if (!lookup("location")) {
sl_send_reply("404?, "User Not Found, Sorry");
return;
};

# If NAT is previously detected, proxy
if (isflagset(6) || isflagset(7)) {
use_media_proxy();
};

route(1);
}
route[3] is our INVITE (or call setup) processing occurs. I have set this configuration up to match on 3 and 4 digit 'extensions' to send them to our Asterisk PBX. Then match on 1+10 digits to send off to our VoIP Provider, making sure to set the accounting flag (1) so we can get proper 'start' Call Detail Records.
# CANCEL and BYE Message Handling
# ----------------------------------
route[4] {
if (client_nat_test("3")) {
setflag(7);
force_rport();
fix_nated_contact();
};
end_media_session();
route(1);

}
route[4] is pretty straight forward - If its a CANCEL or BYE Message, ensure the message gets sent back to the proper location (ip:port). Then inform the MediaProxy to stop processing audio, since the call is ending.
onreply_route[1] {
if ((isflagset(6) || isflagset(7)) && (status=~"(180)|(183)|2[0-9][0-9]")) {
if (!search("^Content-Length:\ +0")) {
use_media_proxy();
};
};
if (client_nat_test("1")) {
fix_nated_contact();
};
}
The onreply_route[1] block gets called when we are expected to reply to a message (like a progress message or an OK). We ensure the MediaProxy gets used if NAT has been previously set or detected and the Contact SIP header is properly 'fixed' for NAT situations.
There you have it. This by far is NOT a comprehensive guide to configuring OpenSER, but I hope it gives you a deeper understanding of the power that we have available to us. Be sure to checkout the OpenSER Configuration Wizard, which provides an more complex configuration situations.
OpenSER is a magical and wonderful Open Source project that has and will continue to power many VoIP Providers and forward thinking enterprises for years to come.
I need your help. I am part of a team that is putting together OpenSER and Asterisk training material. If you have any questions or would like something specific covered, please take 2 minutes and fill out my OpenSER and Asterisk Training Questionnaire.



Related Posts:
VoIP Blog
OpenSER vs SER
Run Your Own SIP Server, Today!
SER/OpenSER Configuration Wizard
Multiple Via Headers in SIP messages
This entry was posted on Wednesday, March 28th, 2007 at 9:05 pm and is filed under How To, OpenSER, VoIP. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
6 Responses to "How to Configure OpenSER: SIP Registar, SIP Proxy and Far-End NAT Traversal for Media"
1. Configuration file for OpenSER v1.2.x to do VoIP NAT traversal using RTPProxy. - Voxilla VoIP Forum on June 28th, 2007 at 2:07 pm
[…] located. Send in the last few lines of that output if you are still confused. Also, check out my OpenSER How To. It may help you understand things a little bit […]
2. Run Your Own SIP Server, Today! by Jeremy McNamara on July 21st, 2007 at 11:27 pm
[…] Unlike Asterisk, OpenSER only deals with the SIP Signaling. This focus allows for a very high levels of scale and flexibility of configuration. Be sure to check out my OpenSER HowTo and check out the OpenSER Wizard, which generates very powerful configurations for multiple situations. […]
3. OpenSER vs SER by Jeremy McNamara on September 7th, 2007 at 10:59 pm
[…] there are two different variations of the same basic platform. I have previously published an OpenSER tutorial however there is another SIP server called […]
4. Blackeye1010 on November 27th, 2007 at 4:24 pm
Hello,
Your handling of sip methods, send BYE and CANCEL to a route block of theyr own. I'm using a config derived from the onsip site. At this link http://siprouter.onsip.org/doc/gettingstarted/ch08.html#mp_handle_cancel
is stated that:
"We now explicitly handle CANCEL messages. CANCEL messages can be safely processed with a simple call to t_relay() because SER will automatically match the CANCEL message to the original INVITE message. So here we just route the message to the default message handler."
…so i'm sending my BYES and CANCELS to the generalist route[1].
Would you say it's better or worse ? Or did you do that because Openser behaves diferent in this case ?
Regards
5. George on May 13th, 2008 at 6:58 pm
You keep talking about setting the accounting flag to '1′ but I don't see it anywhere
6. katiamong on June 1st, 2008 at 5:36 am
hi jeremy, i had a problem in forwarding INVITE message to asterisk.
i've tried with basic authentification with mysql anda forwarding it to asterisk if there a call to ext 701 (just example). i've always got message 'loop detected" and i try many solution it still happen.
Later i try using your configuration using media proxy, i've got problem with authetification (when sosftphone is registering the number, it says forbidden).
is there a solution how to make it works ?
i'm sorry to interupt you but you say in your blog that you need a lot of FAQ, and have no idea where i could post my question in your blog.

沒有留言: