Opensips mid_registrar and nat

Fugoo

New Member
Nov 12, 2018
4
1
3
51
I would like to use the opensips mid_registrar module in front of fusionpbx. However, I have some issues with handling the natted phones. I'm attaching below my opensips.cfg for reference.
The issue is that when a device behind nat receives a call (inbound), the ACKs are sent from the proxy to the private ip address and not the public ip : port. Funny thing is I have two-way audio but the call is not established because the ack does not come to the calling device.

If it is the natted device that makes the call, all works.

I'm sure I'm missing something obvious (mediaproxy... but how?)

C-like:
####### Global Parameters #########

log_level=4
log_stderror=yes
log_facility=LOG_LOCAL0

#children=4

/* uncomment the following line to enable debugging */
#debug_mode=yes

/* uncomment the next line to enable the auto temporary blacklisting of
   not available destinations (default disabled) */
#disable_dns_blacklist=no

/* uncomment the next line to enable IPv6 lookup after IPv4 dns
   lookup failures (default disabled) */
#dns_try_ipv6=yes

/* comment the next line to enable the auto discovery of local aliases
   based on revers DNS on IPs */
auto_aliases=no


listen=udp:0.0.0.0:5060

####### Modules Section ########

#set module path
mpath="/usr/local/lib64/opensips/modules/"

loadmodule "mid_registrar.so"
modparam("mid_registrar", "mode", 2) /* 0 = mirror / 1 = ct / 2 = AoR */
modparam("mid_registrar", "outgoing_expires", 7200)
modparam("mid_registrar", "received_avp", "$avp(received)")
modparam("mid_registrar", "received_param", "received")
#### Removed ??? modparam("mid_registrar", "insertion_mode", 0) /* 0 = contact; 1 = path */

#### SIGNALING module
loadmodule "signaling.so"

#### StateLess module
loadmodule "sl.so"

#### Transaction Module
loadmodule "tm.so"
modparam("tm", "fr_timeout", 5)
modparam("tm", "fr_inv_timeout", 30)
modparam("tm", "restart_fr_on_each_reply", 0)
modparam("tm", "onreply_avp_mode", 1)

#### Record Route Module
loadmodule "rr.so"
/* do not append from tag to the RR (no need for this script) */
modparam("rr", "append_fromtag", 1)

#### MAX ForWarD module
loadmodule "maxfwd.so"

#### SIP MSG OPerationS module
loadmodule "sipmsgops.so"

#### FIFO Management Interface
loadmodule "mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/tmp/opensips_fifo")
modparam("mi_fifo", "fifo_mode", 0666)

#### URI module
loadmodule "uri.so"
modparam("uri", "use_uri_table", 0)

#### USeR LOCation module
loadmodule "usrloc.so"
modparam("usrloc", "nat_bflag", "NAT")
modparam("usrloc", "db_mode",   0)

#### REGISTRAR module
loadmodule "registrar.so"

/* uncomment the next line not to allow more than 10 contacts per AOR */
#modparam("registrar", "max_contacts", 10)

#### ACCounting module
loadmodule "acc.so"
/* what special events should be accounted ? */
modparam("acc", "early_media", 0)
modparam("acc", "report_cancels", 0)
/* by default we 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)

### UAC Module ####
loadmodule "uac.so"
modparam("uac","restore_mode","auto")
modparam("rr", "append_fromtag", 1)

#### NAT modules
loadmodule "nathelper.so"
modparam("nathelper", "natping_interval", 60)
modparam("nathelper", "ping_nated_only", 1)
modparam("nathelper", "received_avp", "$avp(received)")
modparam("nathelper", "sipping_from", "sip:nat-alive@mid_registrar")

### Mediaproxy
loadmodule "mediaproxy.so"
modparam("mediaproxy", "media_relay_avp", "$avp(media_relay)")

#### UDP protocol
loadmodule "proto_udp.so"

####### Routing Logic ########

# main request routing logic

route{
   if (!mf_process_maxfwd_header("10")) {
      sl_send_reply("483","Too Many Hops");
      exit;
   }

   if ( nat_uac_test("31")) {
      if ( is_method("REGISTER")) {
         fix_nated_register() ;
      } else {
         fix_nated_contact() ;
      }
      force_rport() ;
      setbflag(NAT);
      xlog("L_INFO", "[LOG] NATed. [rm:$rm] [fu:$fu] [ou:$ou] [ru:$ru] [si:$si]");
   }

   if (has_totag()) {
      # sequential requests within a dialog should
      # take the path determined by record-routing
      if (loose_route()) {
         if (is_method("BYE")) {
            # do accunting, even if the transaction fails
            if (isbflagset(NAT)) {
                    end_media_session();
                }

            do_accounting("log","failed");
            xlog("L_INFO", "[LOG] BYE. [rm:$rm] [fu:$fu] [ou:$ou] [ru:$ru] [si:$si]");
         } else if (is_method("INVITE")) {
            # even if in most of the cases is useless, do RR for
            # re-INVITEs alos, as some buggy clients do change route set
            # during the dialog.
            xlog("L_INFO", "[LOG] INVITE. [rm:$rm] [fu:$fu] [ou:$ou] [ru:$ru] [si:$si]");
            record_route();
         }

         # route it out to whatever destination was set by loose_route()
         # in $du (destination URI).
         route(relay);
      } else {
         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
               xlog("L_INFO", "[LOG] ACK. [rm:$rm] [fu:$fu] [ou:$ou] [ru:$ru] [si:$si]");
               t_relay();
               exit;
            } else {
               # ACK without matching transaction ->
               # ignore and discard
               xlog("L_INFO", "[LOG] ACK. not match [rm:$rm] [fu:$fu] [ou:$ou] [ru:$ru] [si:$si]");
               exit;
            }
         }
         xlog("L_INFO", "[LOG] BAD REQUEST. [rm:$rm] [fu:$fu] [ou:$ou] [ru:$ru] [si:$si]");
         sl_send_reply("404","Not here");
      }
      exit;
   }

   # CANCEL processing
   if (is_method("CANCEL"))
   {
      if (t_check_trans())
         t_relay();
      exit;
   }

   t_check_trans();

   if (is_method("REGISTER")) {
      xlog("L_INFO", "[LOG] REGISTER [rm:$rm] [fu:$fu] [ou:$ou] [ru:$ru] [si:$si]");
      mid_registrar_save("location","v"); #,"$avp(received)");
      switch ($retcode) {
         case 1:
            $ru = "sip:fusion.pbx:5060";

            t_relay();
            break;
         case 2:
            xlog("L_INFO", "[LOG] absorbing REGISTER! ($$ci=$ci)\n");
            break;
         default:
            xlog("L_INFO", "[LOG] failed to save registration! ($$ci=$ci)\n");
         }

      exit;
   }

   if ( is_method("INVITE|MESSAGE|CANCEL|BYE|NOTIFY") && ($si != "fusion.ip.add.ress" || $sp != 5060) ) {
         $ru = "sip:fusion.pbx:5060";
         xlog("L_INFO", "[LOG] relay to $avp(new_to_user) from $avp(new_from_user)\n");
   }

   # preloaded route checking
   if (loose_route()) {
      xlog("L_ERR", "[LOG] Attempt to route with preloaded Route's [$fu/$tu/$ru/$ci]");
      if (!is_method("ACK"))
         sl_send_reply("403","Preload Route denied");
      exit;
   }

   # record routing
   if (!is_method("REGISTER|MESSAGE|NOTIFY"))
      record_route();

   # account only INVITEs
   if (is_method("INVITE")) {
      do_accounting("log");
   }

   if (!is_myself("$rd")) {
      append_hf("P-hint: outbound\r\n");
      route(relay);
   }

   # requests for my domain
   if (is_method("PUBLISH|SUBSCRIBE"))
   {
      sl_send_reply("503", "Service Unavailable");
      exit;
   }

   if ($rU==NULL) {
      # request with no Username in RURI
      sl_send_reply("484","Address Incomplete");
      exit;
   }

   # initial requests from main registrar, need to look them up!
   # $si = ip.
   if (is_method("INVITE|MESSAGE") && $si == "fusion.ip.add.ress" && $sp == 5060) {
      xlog("L_INFO", "[LOG] INVITE|MESSAGE Received. looking up $ru!\n");
      if (!mid_registrar_lookup("location")) {
         xlog("L_ERR", "[LOG] Cannot find $ru!\n");
         t_reply("404", "Not Found");
         exit;
      }

      t_relay();
      exit;
   }

   # when routing via usrloc, log the missed calls also
   do_accounting("log","missed");
   route(relay);
}


route[relay] {
   # for INVITEs enable some additional helper routes
   if (is_method("INVITE")) {
      if ( isbflagset(NAT)) {
         if ( has_body("application/sdp")) {
            xlog("L_INFO", "[LOG] using media proxy");
            use_media_proxy();
         }
      }
      t_on_branch("per_branch_ops");
      t_on_reply("handle_nat");
      t_on_failure("missed_call");
   }

    if (isbflagset(NAT)) {
        add_rr_param(";nat=yes");
    }

   if (!t_relay()) {
      send_reply("500","Internal Error");
   };
   exit;
}

branch_route[per_branch_ops] {

   xlog("L_INFO", "[LOG] new branch at $ru\n");
}


onreply_route[handle_nat] {
   xlog("L_INFO", "[LOG] (handle_nat) incoming reply\n");
   if ( nat_uac_test("1")) {
      fix_nated_contact() ;
   }
   if ( isbflagset(NAT)) {
      if ( has_body("application/sdp")) {
         use_media_proxy();
      }
   }
   if ( is_method("INVITE")) {
      xlog("L_INFO", "[LOG] re-INVITE. [rm:$rm] [fu:$fu] [ou:$ou] [ru:$ru] [si:$si]\n");
   }
}


failure_route[missed_call] {
   if (t_was_cancelled()) {
      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;
   ##}

}
 
Last edited:

Fugoo

New Member
Nov 12, 2018
4
1
3
51
Thank you for that reference. I had a quick skim over it a few weeks ago, but some of the stuff there was not implemented in opensips (can't remember exactly, but some of the alias handling is different). I'm trying to use opensips because of it's mid-registrar proxy which has no counterpart in kamailio. I'd have to manually fix everything and basically implement the mid-registrar in the routing script with kamailio.
I have one last issue with opensips, which is that I need to fix the "via" private addresses in the sip transactions. Those are never touched by nathelper and my upstream provider does not recognize the dialog on a bye and the calls stay open on one side, even if all other identifiers are ok.
As soon as I use STUN in the phone, all works fine, but I'd like to not have to request anything from the end device.

Not sure how to fix via headers in the routing script, short of string search/replace. Any ideas?

ps: I'll post my updated script on monday, as reference (I don't have access to it atm).
 

atmosphere617

New Member
May 19, 2018
29
3
3
The IP address advertised in the Via header is usually configured on the socket. Based on the Opensips docs it should look something like this:
"listen = udp:<private_ip>:5060 as <public_ip>:5060"

I'm not 100 percent sure on that syntax, in Kamailio it's:
"listen = udp:<private_ip>:5060 advertise <public_ip>:5060".

You could also probably accomplish the same thing by setting the "advertised_address" parameter to the public IP of your server.

Just looked over your config, and that seems to be missing. This should fix your problem.
 
Last edited:

Adrian Fretwell

Active Member
Aug 13, 2017
657
160
43
You may want to make your NAT detection a little more aggressive, snippets from my Opensips config:
Code:
In main route:
    # -----------------------------------------------------------------
    # NAT Check/Fix Section only on customer IPs
    # -----------------------------------------------------------------

    if ( $Rp == 5090 ) {
        force_rport();
        if (nat_uac_test(119)) {   
            if (is_method("REGISTER")) {
                fix_nated_register();
                setbflag(SIPPING_ENABLE);
            } else {
                fix_nated_contact();
                setflag(SIPPING_ENABLE);
            }
        }
    }


In on reply route(s)

# ------------------------- Reply routing logic -------------------
#
# Reply Routes in Alpabetical Order
# --------------------------------------------------------------------

onreply_route {
    # -----------------------------------------------------------------
    # REPLY Message Handler - Global
    # -----------------------------------------------------------------

    xlog("L_DBG", "OnReplyGlobal: called $fu $rU from $si:$sp method $rm on $Ri");

    if ( $Rp == 5090) {
        if (nat_uac_test(97)) {    # changed from 33 on 20161211
            fix_nated_contact();
            setflag(SIPPING_ENABLE);
        }
    }


    xlog("L_INFO", "OnReplyGlobal: Incoming reply $rs $rr from $si:$sp");
}
 

Fugoo

New Member
Nov 12, 2018
4
1
3
51
My opensips is on a public IP address and the upstream provider also. The only natted device is the phone that is behind a firewall (pure nat, no sip alg or anything mangling the packets).
A more aggressive nat detection actually fixed my "Via" issue. The Via now contain a proper public address. Thank you.
However, some of the BYE transactions result in "SIP/2.0 481 Call Leg/Transaction Does Not Exist" answer from the provider.
Specifically, when the original invite from from the upstream provider but the BYE is from the UAC behind the mid-registrar proxy.
I am still at a loss as to why as all tags/ip addresses/cseq etc match perfectly (or at least I can't find the difference). I'll try to post two dialogs, one working and one not working. Perhaps I'm missing some nuance.
 

Adrian Fretwell

Active Member
Aug 13, 2017
657
160
43
BYE transactions that result in "SIP/2.0 481 Call Leg/Transaction Does Not Exist" are often caused by some sort of ALG in the nattting router. Some ALGs do a wholesale find and replace of anything that looks like an IP address in an attempt to change any internal addresses into external ones. The problem with this is that some phones, especially Yealink include their local IP address in the Call-ID header like:
Code:
Call-ID: 0_121395108@192.168.83.43

If this "IP" in the Call-ID header gets changed to something else, then the Call Leg will not exist.

I hope that makes sense.