Table of Contents
What this ser.cfg does:
Adds the ability to connect to the PSTN for inbound and outbound calling
Up until now we have focused on SIP only calls, which means you could only send and receive calls between IP phones on your SIP proxy. But most people will need to call regular land lines as well as receive calls from them.
This is accomplished by means of a PSTN Gateway. SER itself does not provide PSTN access. You must install external equipment for this. A very popular and cost effective solution is a Cisco AS5300. The Cisco AS5300 is a multi-function chassis which allows you to plug in many different feature cards. In order for the Cisco AS5300 to operate as a PSTN gateway you must have a T1/E1 interface board and a Voice Card. The T1/E1 card is used to connect the AS5300 to the actual PSTN by means of a PRI from your local telephone company. The voice card contains DSP (digital signal processing) chips that convert audio streams between the SIP world and the PSTN world.
NOTE: Some users have asked how do I get a real telephone number for my SIP phone and the answer to that question is to get a PSTN gateway. When your local telephone company installs your PRI they will be able to allocate DIDs (direct inward dial, or telephone numbers) to you. You can then assign the DIDs as usernames in the MySQL subscriber table and when the DID is called from the PSTN it will cause your PSTN gateway to generate an INVITE message and send it to SER which will ring the SIP phone just like a normal SIP-to-SIP call.
When dealing with PSTN calls, you can actually think of the SIP client as one end of the call and the PSTN gateway as the other end. Many people get confused on this minor detail because they fail to understand that the actual PSTN gateway is indeed a SIP client itself.
In order to call a PSTN phone from your SIP phone SER receives the INVITE message as usual. If the R-URI (request URI) is determined to be a PSTN number then SER changes the host IP of the R-URI to be that of the PSTN gateway and then it relays the message to the PSTN gateway. After this, the call behaves just like a normal SIP-to-SIP call.
When receiving a call from the PSTN, the PSTN gateway will send a new INVITE message to SER and it will be processed just like any normal SIP-to-SIP call.
So now that you have a brief understanding of how SIP-to-PSTN and PSTN-to-SIP calling works, lets take a look at the PSTN-enabled ser.cfg configuration file.
NOTE: This ser.cfg file does not take in to consideration user ACLs. This means that all registered user accounts will have PSTN access. We will introduce ACL control in a future example.
debug=3 fork=yes log_stderror=no listen=192.0.2.13 # INSERT YOUR IP ADDRESS HERE port=5060 children=4 dns=no rev_dns=no fifo="/tmp/ser_fifo" fifo_db_url="mysql://ser:heslo@localhost/ser" 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/usrloc.so" loadmodule "/usr/local/lib/ser/modules/registrar.so" loadmodule "/usr/local/lib/ser/modules/auth.so" loadmodule "/usr/local/lib/ser/modules/auth_db.so" loadmodule "/usr/local/lib/ser/modules/uri.so" loadmodule "/usr/local/lib/ser/modules/uri_db.so" loadmodule "/usr/local/lib/ser/modules/domain.so" loadmodule "/usr/local/lib/ser/modules/mediaproxy.so" loadmodule "/usr/local/lib/ser/modules/nathelper.so" loadmodule "/usr/local/lib/ser/modules/textops.so" loadmodule "/usr/local/lib/ser/modules/avpops.so"loadmodule "/usr/local/lib/ser/modules/permissions.so"
modparam("auth_db|permissions|uri_db|usrloc", "db_url", "mysql://ser:heslo@localhost/ser")
modparam("auth_db", "calculate_ha1", 1) modparam("auth_db", "password_column", "password") modparam("nathelper", "rtpproxy_disable", 1) modparam("nathelper", "natping_interval", 0) modparam("mediaproxy","natping_interval", 30) modparam("mediaproxy","mediaproxy_socket", "/var/run/mediaproxy.sock") modparam("mediaproxy","sip_asymmetrics","/usr/local/etc/ser/sip-clients") modparam("mediaproxy","rtp_asymmetrics","/usr/local/etc/ser/rtp-clients") modparam("usrloc", "db_mode", 2) modparam("registrar", "nat_flag", 6) modparam("rr", "enable_full_lr", 1) modparam("tm", "fr_inv_timer", 27)
modparam("tm", "fr_inv_timer_avp", "inv_timeout") modparam("permissions", "db_mode", 1)
modparam("permissions", "trusted_table", "trusted")
route { # ----------------------------------------------------------------- # Sanity Check Section # ----------------------------------------------------------------- if (!mf_process_maxfwd_header("10")) { sl_send_reply("483", "Too Many Hops"); break; }; if (msg:len > max_len) { sl_send_reply("513", "Message Overflow"); break; }; # ----------------------------------------------------------------- # Record Route Section # ----------------------------------------------------------------- if (method=="INVITE" && client_nat_test("3")) { # INSERT YOUR IP ADDRESS HERE record_route_preset("192.0.2.13:5060;nat=yes"); } else if (method!="REGISTER") { record_route(); }; # ----------------------------------------------------------------- # Call Tear Down Section # ----------------------------------------------------------------- if (method=="BYE" || method=="CANCEL") { end_media_session(); }; # ----------------------------------------------------------------- # Loose Route Section # ----------------------------------------------------------------- if (loose_route()) { if ((method=="INVITE" || method=="REFER") && !has_totag()) { sl_send_reply("403", "Forbidden"); break; }; if (method=="INVITE") { if (!allow_trusted()) {
if (!proxy_authorize("","subscriber")) { proxy_challenge("","0"); break; } else if (!check_from()) { sl_send_reply("403", "Use From=ID"); break; }; consume_credentials(); }; if (client_nat_test("3")||search("^Route:.*;nat=yes")){ setflag(6); use_media_proxy(); }; }; route(1); break; }; # ----------------------------------------------------------------- # Call Type Processing Section # -----------------------------------------------------------------
if (!is_uri_host_local()) {
if (is_from_local() || allow_trusted()) {
route(4);(11) route(1); } else { sl_send_reply("403", "Forbidden");(12) }; break;(13) }; if (method=="ACK") { route(1); break; } if (method=="CANCEL") { route(1); break; } else if (method=="INVITE") { route(3); break; } else if (method=="REGISTER") { route(2); break; }; lookup("aliases"); if (!is_uri_host_local()) { (14) route(4); route(1); break; }; if (!lookup("location")) { sl_send_reply("404", "User Not Found"); break; }; route(1); } route[1] { # ----------------------------------------------------------------- # Default Message Handler # ----------------------------------------------------------------- t_on_reply("1"); if (!t_relay()) { if (method=="INVITE" || method=="ACK") { end_media_session(); }; sl_reply_error(); }; } route[2] { # ----------------------------------------------------------------- # REGISTER Message Handler # ----------------------------------------------------------------- sl_send_reply("100", "Trying"); if (!search("^Contact:[ ]*\*") && client_nat_test("7")) { setflag(6); fix_nated_register(); force_rport(); }; if (!www_authorize("","subscriber")) { www_challenge("","0"); break; }; if (!check_to()) { sl_send_reply("401", "Unauthorized"); break; }; consume_credentials(); if (!save("location")) { sl_reply_error(); }; } route[3] { # ----------------------------------------------------------------- # INVITE Message Handler # ----------------------------------------------------------------- if (client_nat_test("3")) { setflag(7); force_rport(); fix_nated_contact(); }; if (!allow_trusted()) { (15) if (!proxy_authorize("","subscriber")) { proxy_challenge("","0"); break; } else if (!check_from()) { sl_send_reply("403", "Use From=ID"); break; }; consume_credentials(); }; if (uri=~"^sip:1[0-9]{10}@") { (16) strip(1); }; lookup("aliases"); if (!is_uri_host_local()) { (17) route(4); route(1); break; }; if (uri=~"^sip:011[0-9]*@") { # International PSTN (18) route(4); route(5); break; }; if (!lookup("location")) { if (uri=~"^sip:[0-9]{10}@") { # Domestic PSTN (19) route(4); route(5); break; }; sl_send_reply("404", "User Not Found"); break; }; route(4); route(1); } route[4] { # ----------------------------------------------------------------- # NAT Traversal Section # ----------------------------------------------------------------- if (isflagset(6) || isflagset(7)) { if (!isflagset(8)) { setflag(8); use_media_proxy(); }; }; } (20)route[5] { # ----------------------------------------------------------------- # PSTN Handler # ----------------------------------------------------------------- rewritehost("192.0.2.245");(21) # INSERT YOUR PSTN GATEWAY IP ADDRESS avp_write("i:45", "inv_timeout");(22) route(1); } 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 avpops module is a very power library for manipulating SIP message headers, storing temporary or persistent message data, and comparing data items. Avpops is also used to dynamically alter certain parts of SER itself. We use avpops in this example to dynamically change the amount of time SER will wait for an INVITE message to be forwarded to its destination. The reason we need to do this is that a SIP-to-SIP call generally connects much faster than a SIP-to-PSTN call. Therefore we will allow SER to wait a while longer when calling a PSTN telephone. |
| The permissions module gives SER access to the trusted MySQL table. The trusted table is acts as a repository of privileged devices. We must add our PSTN gateway device to the trusted table to prevent SER from challenging the PSTN gateway for authentication credentials. |
| The permissions module needs to access MySQL so we add it to the db_url parameter list. |
| The default INVITE timer will be allowed 27 seconds to connect. If we are going to call a PSTN destination then we will set the inv_timeout AVP to a higher value in order to allow additional time for the call to connect. This is show in the PSTN route handler. |
| The permissions module needs to know that it should connect to the MySQL database to find its data. |
| The trusted_table parameter informs the permissions module as to which MySQL table it will read from. In this case the table called trusted will be used. |
| Now that we can receive INVITE messages from registered SIP clients as well as our PSTN gateway, we need to challenge INVITE messages in a more specialized fashion. The allow_trusted() function is provided by the permissions module. It will access the MySQL table called "trusted" to get a list of privileged IP addresses. When the PSTN gateway sends an INVITE to SER, the allow_trusted() should return TRUE to prevent SER from replying with a 401 Unauthorized message. As a general rule, the PSTN gateway should always be allowed to send messages to SER without being challenged for credentials, however, all INVITE messages being sent to the PSTN gateway should be challenged to prevent toll fraud for which the PSTN gateway owner is responsible for. NOTE: It is very important to understand the significance of using proxy_authorize() and allow_trusted() in the loose_route() code block. A re-INVITE should only be processed by SER if it originated from a SIP client that can be authenticated or trusted by SER. Authentication is verified with the call to proxy_authorize(), which will check the MySQL subscriber table whereas trust relationships are tested with the call to allow_trusted(), which will check the MySQL trusted table. A PSTN gateway which cannot authenticate against SER must be added to the MySQL trusted table, otherwise re-INVITE and REFER messages from the PSTN gateway will not be routed by SER. |
| Our previous configuration files had a potential open relay problem where by SIP messages targeted at a domain that is not locally served could be relayed without being challenged. This was exposed in the following code if (uri!=myself) { route(4); route(1); break; }; Now that our SIP proxy has PSTN access we need to close this potential open relay to prevent toll fraud. |
| In previous ser.cfg examples we used the test condition (uri != myself) to determine if the SIP message was directed at our SIP router or not. From this point forward will will replace this test condition with a call to is_uri_host_local(). which will determine if the domain portion of the Request URI is a domain that our SIP proxy is responsible for. It does so by querying the MySQL table called domain. In order for this function to return TRUE for messages that our SIP proxy is responsible for, we must add our domain to the MySQL domain table. If your SIP proxy handles multiple domains, then you must add all domains to this table. NOTE: The is_uri_host_local() function is exposed in the domain.so module. |
| If the SIP message is not targeted at our domain, we must now check to see if the message sender belongs to our domain. We should only forward messages to external domains if the message sender belongs to our domain. The is_from_local() function will query the MySQL domain table for the host name that appears in the <From:> header of the SIP message. NOTE: The is_from_local() function is exposed in the domain.so module. Another thing that we must allow is messages from our PSTN gateway. Since our PSTN gateway is a trusted device, it must be allowed to forward messages to external domains. This is accomplished with the call to allow_trusted(). |
| (11) | If the message is permitted to leave our SIP proxy then we must take care of NAT traversal prior to relaying the message. |
| (12) | If the message is not permitted to leave our SIP proxy then we reply with a 403 message to inform the message sender that we are not an open relay. |
| (13) | Now that we have handled the message we simply stop processing. |
| (14) | Determine if the domain portion of the Request URI is a domain that our SIP proxy is responsible for. |
| (15) | INVITE message must be challenged for credentials here just as they were in the loose_route() section of the main route code block. NOTE: It is important to understand that INVITE messages can potentially pass through the SIP router in either route[3] or in the loose_route() section of the main route. Generally, original INVITE messages will be passed to route[3], whereas re-INVITE messages will get picked up by the loose_route() function. Therefore it is very important to secure both sections with the appropriate calls as demonstrated here. NOTE: Failure to properly secure your SIP route could result in toll charges that you are responsible for, assuming your SIP router has access to the PSTN. |
| (16) | Many PSTN gateways do need or permit long distance prefixes when dialing. In North America we dial 1 + a 10-digit number to place a call outside of our local calling area. So we remove the leading 1 from the R-URI before sending the message to the PSTN gateway. |
| (17) | Determine if the domain portion of the Request URI is a domain that our SIP proxy is responsible for. |
| (18) | This ser.cfg assumes that international numbers are prefixed with 011 and that domestic numbers are 10 numeric digits. Here we use regular expression matching to check the R-URI. If it starts with 011 and is followed by numeric digits only, then we have a match for an international PSTN destination. If a match is found then we route the message to the PSTN handler, route[4]. |
| (19) | If the R-URI match 10 numeric digits then we know we have a domestic number and we route the INVITE message to the PSTN hander, route[4]. NOTE: We know we have a PSTN number at this point because the call to lookup(location) on the previous line returned FALSE. |
| (20) | Here we introduce route block 5 for handling PSTN destinations. |
| (21) | Rewritehost() is used to alter the R-URI so that the message will be relayed to our PSTN gateway. You must pass the IP address of your PSTN gateway to this function. SER will then send the INVITE message to your PSTN gateway properly when you call t_relay(). |
| (22) | Avp_write() alters the amount of time allowed for a PSTN call to connect. If you recall we specified inv_timeout as the loadparam value for fr_inv_timeout. This line specifies that we want to allow 45 seconds before timing out. |