Showing posts with label opensips. Show all posts
Showing posts with label opensips. Show all posts

Thursday, September 28, 2017

OpenSIPS proxy with auth on external trunk

A new task has arrived.
Make OpenSIPS (actually, here can be Kamailio as well) handle registration to external service, but also provide capability to dial out from other trunks, that are not aware of any credentials of other trunk and know about only OpenSIPS as a trunk. So, in some sort it's b2b.

1. A -> INVITE -> OpenSIPS                     B
2. A              OpenSIPS ->    INVITE     -> B
3. A              OpenSIPS <-    401(7)     <- B
4. A              OpenSIPS -> INVITE (auth) -> B
5. A              OpenSIPS <-      200      <- B

6. A  <- 200 <-   OpenSIPS

Very simplified scheme here. But the main issue here - on INVITE with auth headers you have to increase CSeq number by 1. Actually creating new dialog with side B. But you still have to hold old dialog with side A. And you have dynamically change on requests CSeq number. 
On question, "is there any OpenSIPS-wise way of making that", got an answer 

"Right now OpenSIPs does not support increasing the cseq during UAC authentication. At the end this is a limitation of the a proxy versus a B2B :)"

So, nothing is left, only to change it by hand.

So, parts of the script would looks like

...
#------------ uac related parts
loadmodule "uac_auth.so"
modparam("uac_auth","auth_realm_avp","$avp(uac_realm)")
modparam("uac_auth","auth_username_avp","$avp(uac_username)")
modparam("uac_auth","auth_password_avp","$avp(uac_password)")

loadmodule "uac.so"

loadmodule "uac_registrant.so"
modparam("uac_registrant", "db_url", "mysql://astercc:astercc@localhost/opensips")
modparam("uac_registrant", "timer_interval", 120)
modparam("uac_registrant", "hash_size", 2)
# In DB we're storing info on external services where to register
modparam("uac_registrant", "db_url", DBURL)
...

route {
...
# Handle sequential requests part
if (has_totag()) {
  if (loose_route()) {
    ...
  } else {
    ...
    if (is_method("ACK") && isflagset(AUTH_DONE)) {
      # Process ACK's
      if ($cs == $avp(original_cseq)) {
        route(INCREASE_CSEQ);
      }
    }
    ...
  }
}
...
# CANCEL processing
if (is_method("CANCEL")) {
  # Addition of process CSeq in a case of auth call
  if (isflagset(AUTH_DONE)) {
    # Process CANCEL's to both sides
    if ($cs == $avp(original_cseq)) {
        route(INCREASE_CSEQ);
    } else {
        route(RESTORE_CSEQ);
    }
  }
  ...
}
...
# INVITE processing
if (is_method("INVITE")) {
  ...
  # Here to find out call to external auth trunk
  if (...) {
    $avp(original_cseq) = $cs;
    setflag(IS_OUTBOUND_CALL);
    # Also point, here, if we already know that trunk with username and pass, provider may require specific Form and To fields
    #if (...) {
    #  uac_replace_to("sip:$tU@$rd");
    #  uac_replace_from("sip:$avp(uac_username)@$rd");
    #}
  }
  ...
}

onreply_route[1] {
...
  # On reply just restore original CSeq.
  route(RESTORE_CSEQ);
...
}

failure_route[1] {
  ...
  # Authentication reply on outbound call received?
  if (t_check_status("40[17]") && isflagset(IS_OUTBOUND_CALL)) {
    # Have we already tried to authenticate?
    if (isflagset(AUTH_DONE)) {
      t_reply("503","Authentication failed");
      exit;
    }
    # Set flag of auth preformed
    setflag(AUTH_DONE);

    # Getting realm from responce
    # Get Proxy-Auth header
    if ($(<reply>hdr(Proxy-Authenticate))) {
      $var(raw_auth) = $(<reply>hdr(Proxy-Authenticate));
    } 
    # Prefer WWW-Authenticate to Proxy-Authenticate
    if ($(<reply>hdr(WWW-Authenticate))) {
      $var(raw_auth) = $(<reply>hdr(WWW-Authenticate));
    }
    $var(reg_start) = "/(.*?)realm=\"//g";
    $var(reg_end) = "/\"(.*)//g";
    $var(raw_auth) = $(var(raw_auth){re.subst,$var(reg_start)});
    $avp(uac_realm) = $(var(raw_auth){re.subst,$var(reg_end)});
    # --- Here we assume, that avp(uac_username) and avp(uac_password) are set elsewhere
    if (uac_auth()) {
      route(INCREASE_CSEQ);
    } else {
      exit;
    }
    route(RELAY);
  }
  ...
}

route[RESTORE_CSEQ] {
  if (isflagset(AUTH_DONE) && is_avp_set("$avp(original_cseq)")) {
    remove_hf("CSeq:");
    append_hf("CSeq: $avp(original_cseq) $rm\r\n", "Call-ID"); 
  }
}

route[INCREASE_CSEQ] {
  if (isflagset(AUTH_DONE) && is_avp_set("$avp(original_cseq)")) {
    $var(inc_cseq) = $(avp(original_cseq){s.int}) + 1;
    remove_hf("CSeq:");
    append_hf("CSeq: $var(inc_cseq) $rm\r\n", "Call-ID");   
  }
}

Wednesday, April 2, 2014

Попытки сделать полноценный SIP SBC или OpenSIPS в руках....

Итак.... История не стоит на месте и я тоже. Выпала задачка сделать масштабируемый и (возможно в будущем) высоконагруженный SBC для всяческих сервисов.
Схема, к которой я пришел не отличается оптимизированностью, зато довольно проста в масштабировании (я надеюсь) и эксплуатации.
Схема:

WAN <->  OpenSIPS (1.8) - - Asterisk (any)
                          |                 |
WAN <-> FreeSwitch (1.2) - ---------

Роли:
OpenSIPS - регистрация пользователей, регистрация на внешних сервисах, балансировка между FreeSwitch и Asterisk
FreeSwitch - Meдиа-прокси, при необходимости - транскодинг, скрытие топологии. Количество ограничено внешними IP-адресами.
Asterisk - Собственно, сервер приложений типа голосовой почты и прочего. Количество - в разумных пределах :). Почему, собственно, Asterisk? Потому что человек, который пишет под него приложения, знает AGI.
Схема работы примерно такая:
Пользователь набирает какой-то номер -> OpenSIPS проверяет авторизации и прочее и перенаправляет звонок по WAN на один из FreeSwith'ей -> FreeSwitch просто делает "заворот" WAN - LAN и отправляет звонок обратно на OpenSIPS - > OpenSIPS, видя, что звонок пришел от одного из FreeSwith'ей, балансирует его на один из Астерисков.

Собственно, сигнализация при таком звонке ходит так:
Пользователь (NAT/WAN) - (WAN) OpenSIPS (WAN) - (WAN) FreeSwitch (LAN) - (LAN) OpenSIPS (LAN) - (LAN) Asterisk.
Медиа же ходит так:
Пользователь (NAT/WAN) - (WAN) FreeSwitch (LAN) - (LAN) Asterisk.
 Т.е. в минимальной комплектации нужно 2 внешних IP адреса.

Далее пойдет файлик opensips.cfg с некоторыми комментариями:

#
# $Id: opensips.cfg 8758 2012-02-29 11:59:26Z vladut-paiu $
#
# OpenSIPS residential configuration script
#     by OpenSIPS Solutions
#
# This script was generated via "make menuconfig", from
#   the "Residential" scenario.
# You can enable / disable more features / functionalities by
#   re-generating the scenario with different options.#
#
# Please refer to the Core CookBook at:
#      http://www.opensips.org/Resources/DocsCookbooks
# for a explanation of possible statements, functions and parameters.
#


####### Global Parameters #########

debug=3
log_stderror=no
log_facility=LOG_LOCAL7

fork=yes
children=4

auto_aliases=yes

listen=udp:172.16.3.8:5060
listen=udp:XXX:5060
listen=udp:XXX:5060

#port=5060
disable_tcp=yes

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

#set module path
mpath="/usr/lib/opensips/modules/"


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

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

#### MySQL module
loadmodule "db_mysql.so"


loadmodule "regex.so"
loadmodule "textops.so"
loadmodule "auth.so"
loadmodule "auth_db.so"
loadmodule "domain.so"
loadmodule "alias_db.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"
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_BFLAG")
modparam("usrloc", "db_mode",   2)
modparam("usrloc", "matching_mode", 1)
modparam("usrloc", "cseq_delay", 5)
modparam("usrloc", "accept_replicated_contacts", 0)
modparam("usrloc", "skip_replicated_db_ops", 1)

#### User Agent Client module
loadmodule "uac_auth.so"
loadmodule "uac.so"
loadmodule "uac_registrant.so"
modparam("uac","restore_mode","auto")
modparam("uac_registrant", "timer_interval", 120)
modparam("uac_registrant", "hash_size", 2)


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

modparam("registrar", "received_avp", "$avp(42)")


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

# ----- auth_db params -----
modparam("auth_db", "calculate_ha1", yes)
modparam("auth_db", "password_column", "password")
modparam("auth_db", "load_credentials", "")

# ----- domain params -----

#modparam("domain", "db_url",DBURL)
modparam("domain", "db_mode", 1)   # Use caching

#modparam("alias_db", "db_url", DBURL)

# ----- multi-module params -----
/* uncomment the following line if you want to enable multi-domain support
   in the modules (dafault off) */

# ----- Dialog params ----
loadmodule "dialog.so"
modparam("dialog", "enable_stats", 0)


# ------ NAT Traversal Module ----

loadmodule "nat_traversal.so"
modparam("nat_traversal", "keepalive_interval", 60)
modparam("nat_traversal", "keepalive_method", "OPTIONS")
modparam("nat_traversal", "keepalive_from", "sip:pinger@XXX")
modparam("nat_traversal", "keepalive_extra_headers", "X-KeepAliveHeader: Is there any out there?\r\n")
modparam("nat_traversal", "keepalive_state_file", "/tmp/opensips_keepalive_state")

# ----- Load-Balancer params ---- Switched to stateless dispatcher
#loadmodule "load_balancer.so"
#modparam("load_balancer", "db_url", DBURL)
#modparam("load_balancer", "probing_interval", 60)
#modparam("load_balancer", "probing_reply_codes", "404, 200, 481")
#modparam("load_balancer", "probing_method", "INFO")


# ----- DISPATCHER Module ------
loadmodule "dispatcher.so"
modparam("dispatcher", "flags", 3)
modparam("dispatcher", "attrs_avp", "$avp(attrs_avp)")
modparam("dispatcher", "ds_ping_method", "INFO")
modparam("dispatcher", "ds_ping_from", "sip:dispatcher@XXX")
modparam("dispatcher", "ds_ping_interval", 30)
modparam("dispatcher", "ds_probing_threshhold", 10)
modparam("dispatcher", "options_reply_codes", "404, 200, 481")


# ------AVPops Module params ------
loadmodule "avpops.so"


# ------Stun Module -------

loadmodule "stun.so"
modparam("stun", "primary_ip", "XXX")
modparam("stun", "alternate_ip", "172.16.3.8")

# ------Presence User Agent Module -----

loadmodule "xcap.so"
loadmodule "xcap_client.so"

loadmodule "pua.so"
loadmodule "pua_mi.so"
loadmodule "pua_dialoginfo.so"
loadmodule "presence.so"
loadmodule "presence_dialoginfo.so"
loadmodule "presence_xml.so"
loadmodule "presence_mwi.so"
loadmodule "presence_xcapdiff.so"
loadmodule "rls.so"
loadmodule "mi_xmlrpc.so"

modparam("xcap", "integrated_xcap_server", 1)
modparam("xcap_client", "periodical_query", 0)

modparam("mi_xmlrpc", "port", 8888)

modparam("presence", "server_address", "sip:precense@XXX:5060")
modparam("presence", "fallback2db", 1)
modparam("presence", "clean_period",  30)

modparam("presence_xml", "force_active", 0)
modparam("presence_xml", "pidf_manipulation", 1)

modparam("rls", "server_address", "sip:rls@XXX:5060")
modparam("rls", "to_presence_code", 5)

modparam("pua_dialoginfo", "presence_server", "sip:precense@XXX:5060")


# ---- DBURL for all modules ------

modparam("avpops|alias_db|domain|auth_db|uac_registrant|usrloc|dialog|pua|presence|xcap|dispatcher", "db_url", "mysql://opensips:XXX@db1.local/opensips")

####### Routing Logic ########
# main request routing logic

route{

# ### Here will be list of proxies. Yes, it's duplicating with MySQL base, but I've not figured what to be best.
# may be here to make db queries.... Ok, let's make some tests later;

# Group 1
        $var(external_media_proxy) = "XXX";
# Group 2
        $var(internal_media_proxy) = "172.16.3.2";
# Group 3
        $var(media_server) = "172.16.3.16";
# Group 4
    $var(webrtc_proxy) = "172.16.3.7";

# ### Var ends


Небольшая защита от сканеров




        if (search("friendly-scanner|sipvicious|sipcli*")) {
        xlog("L_INFO", "IS_FRIENDLYSCANNER_MESSAGE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        exit;
    }

        if (search_body("friendly-scanner|sipvicious|sipcli")) {
        xlog("L_INFO", "IS_FRIENDLYSCANNER_BODY: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        exit;
    }


   

    if (!mf_process_maxfwd_header("10")) {
                xlog("L_INFO", "TOO_MANY_HOPS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                exit;
        }


Этот кусок работает как-то странно и часто отвечает 503. Особенно в случае doubango webrtc2sip, хотя он уже не используется. На период тестов он закомменчен.


#        if (msg:len >= 4096 ) {
#                xlog("L_INFO", "MESSAGE_TOO_BIG: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
#                sl_send_reply("513", "Message too big");
#                exit;
#        }

        force_rport();

        if (client_nat_test("3")) {
                xlog("L_INFO", "CLIENT_NAT_TEST: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        fix_contact();
                if (is_method("REGISTER|SUBSCRIBE")) {
                        xlog("L_INFO", "CLIENT_NAT_TEST_REGISTERSUBSCRIBEINVITE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        nat_keepalive();
                }
        }   


    #Precense and BLF part....

    if (uri==myself) {
        if( is_method("PUBLISH|SUBSCRIBE|NOTIFY")) {
            route(PRESENCE);
        }
    }

        #Client behind NAT
   
        route(TO_TAG);

        route(CANCEL);

    route(IS_FROM_MEDIA_INTERNAL);

    route(IS_FROM_MEDIA_EXTERNAL);

    route(IS_FROM_MEDIASERVER);
   
    route(IS_FROM_WEB);

    route(IS_FROM_PROVIDER);

  
        # authenticate if from local subscriber. Works for local users, but not for REGISTER
        if (!(method =="REGISTER") && is_from_local()) {
                xlog("L_INFO", "NOTREGISTRATIONANDLOCAL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (!proxy_authorize("", "subscriber")) {
                        proxy_challenge("", "0");
                        xlog("L_INFO", "NOTREGISTRATIONANDLOCAL_NOTPROXYAUTHORIZE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        exit;
                } else {
                        xlog("L_INFO", "NOTREGISTRATIONANDLOCAL_PROXYAUTHORIZE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                }
                if (!db_check_from()) {
                        send_reply("403","Forbidden auth ID");
                        xlog("L_INFO", "NOTREGISTRATIONANDLOCAL_NOTDBCHECKFROM: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        exit;
                } else {
                        xlog("L_INFO", "NOTREGISTRATIONANDLOCAL_DBCHECKFROM: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                }
                xlog("L_INFO", "NOTREGISTRATIONANDLOCAL_CONSUMECREDENTIALS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                consume_credentials();
                # caller authenticated
        }

        # preloaded route checking
        route(LOOSE_ROUTE);

        # record routing
        if (!is_method("REGISTER|MESSAGE")) {
                xlog("L_INFO", "NOTREGISTERORMESSAGE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                record_route();
        }

        # account only INVITEs
        #if (is_method("INVITE")) {
        #        xlog("L_INFO", "INVITEACCOUNTING: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        #        setflag(1); # do accounting
        #}

        # if not a targetting a local SIP domain, just send it out
        # based on DNS (calls to foreign SIP domains)
        if (!is_uri_host_local()) {
                xlog("L_INFO", "NOTLOCALURI: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                append_hf("P-hint: outbound\r\n");
                t_on_failure("1");
                resetflag(8);
                route(1);
        }


        # requests for my domain
        # Register users on OpenSIPS

        if (is_method("REGISTER")) {
                xlog("L_INFO", "REGISTER: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                # authenticate the REGISTER requests
                if (!www_authorize("", "subscriber")) {
                        xlog("L_INFO", "REGISTER_NOTWWWAUTHORIZE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        www_challenge("", "0");
                        exit;
                } else {
                        xlog("L_INFO", "REGISTER_WWWAUTHORIZE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                }
                if (!db_check_to()) {
                        xlog("L_INFO", "REGISTER_NOTDBCHECKTO: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        send_reply("403","Forbidden auth ID");
                        exit;
                } else {
                        xlog("L_INFO", "REGISTER_DBCHECKTO: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                }

                if (!save("location")) {
                        xlog("L_INFO", "REGISTER_NOTSAVELOCATION: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        sl_reply_error();
                } else {
                        xlog("L_INFO", "REGISTER_SAVELOCATION: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                }
                exit;
        }

        # Some error processing
        route(ERROR);
        #Change aliases
        if(alias_db_lookup("dbaliases")) {
                xlog("L_INFO", "ALIASDBLOOKUP: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        }


        # Nave we found our user?
        if (!lookup("location")) {
                xlog("L_INFO", "NOTLOOKUPLOCATION: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                # Let's try here to loadbalance it to mediaproxy :)
                if (is_method("INVITE")) {
                        xlog("L_INFO", "LOOKUPLOCATION_INVITE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        ds_select_dst("1", "4");
                } else {
                        xlog("L_INFO", "LOOKUPLOCATION_NOTINVITE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        if (is_method("OPTIONS")) {
                                #Answer OPTIONS anyway
                                xlog("L_INFO", "LOOKUPLOCATION_NOTINVITE_OPTIONS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                                sl_send_reply("200", "ALIVE OK");
                        }
                        exit;
                }
        } else {
                xlog("L_INFO", "LOOKUPLOCATION: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        }

        # when routing via usrloc, log the missed calls also
        #setflag(2);

        # arm a failure route in order to catch failed calls
        # targeting local subscribers; if we fail to deliver
        # the call to the user, we send the call to voicemail
        t_on_failure("1");
        resetflag(8);

        route(1);
}

route[1] {
        # Just relay it forward....
        if (!t_relay()) {
                xlog("L_INFO", "NOTTRELAY: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                sl_reply_error();
        }
    if (is_method("OPTIONS")) {
        xlog("L_INFO", "OPTIONS_ANSWER: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        sl_send_reply("200", "ALIVE OK");
    }
        exit;
}


route[TO_TAG] {
        if (has_totag()) {
                xlog("L_INFO", "HAS_TO_TAG: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                # sequential request withing a dialog should
                # take the path determined by record-routing
                if (loose_route()) {
                        route(1);
                } else {
                        if ( is_method("ACK") ) {
                                xlog("L_INFO", "HAS_TO_TAG_NOTLR_ACK: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                                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", "HAS_TO_TAG_NOTLR_ACK_TRANS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                                        t_relay();
                                        exit;
                                } else {
                                        # ACK without matching transaction ->
                                        # ignore and discard
                                        xlog("L_INFO", "HAS_TO_TAG_NOTLR_ACK_NOTTRANS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                                        exit;
                                }
                        }
                        xlog("L_INFO", "HAS_TO_TAG_NOTLR: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        send_reply("404","Not here");
                }
                exit;
        }
}


route[CANCEL] {
        # CANCEL processing
        if (is_method("CANCEL")) {
                xlog("L_INFO", "CANCEL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (t_check_trans())
                        t_relay();
                exit;
        }

        if (t_check_trans()) {
                xlog("L_INFO", "TRANSACTION: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        }

}

route[LOOSE_ROUTE] {
        if (loose_route()) {
                xlog("L_INFO", "LOOSEROUTE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (!is_method("ACK"))
                        xlog("L_INFO", "LOOSEROUTE_ACK: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        send_reply("403","Preload Route denied");
                exit;
        }
}

route[ERROR] {
        if ($rU==NULL) {
                # request with no Username in RURI
                xlog("L_INFO", "RURIISNULL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (is_method("OPTIONS")) {
                        xlog("L_INFO", "RURIISNULL_OPTIONS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        send_reply("200","ALIVE OK");
                }
                else {
                        send_reply("484","Address Incomplete");
                }
                exit;
        }
}


failure_route[1] {
        # authentication reply received?
        if ( t_check_status("40[17]") ) {
                # have we already tried to authenticate?
                if (isflagset(8)) {
                        t_reply("503","Authentication failed");
                        exit;
                }

Кусок, который отвечает за регистрацию на внешних сервисах. Т.е. ту последовательность REGISTER - 401 - REGISTER (digest) - 200
                if (uac_auth()) {
                        # mark that auth was performed
                        setflag(8);
                        # trigger again the failure route
                        t_on_failure("1");
                        # repeat the request with auth response this time
                        append_branch();
                        t_relay();
                }
        }

        if (t_was_cancelled()) {
                xlog("L_INFO", "FAILUREROUTE_TWASCANCELLED: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                exit;
        }

        # if the failure code is "408 - timeout" or "486 - busy",
        if (t_check_status("486|408")) {
                xlog("L_INFO", "FAILUREROUTE_TCHECKSTATUS: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                exit;
        }
}


route[ROUTINE] {

        route(CANCEL);
        route(LOOSE_ROUTE);
        route(ERROR);
}


route[IS_FROM_MEDIA_INTERNAL] {
        if ($(var(internal_media_proxy){param.exist,$si})) {
                xlog("L_INFO", "IS_FROM_MEDIA_INTERNAL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (is_method("REGISTER|MESSAGE")) {
                        sl_send_reply("500", "Method not allowed");
                }
                route(ROUTINE);

                if (is_method("INVITE")) {
                        ds_select_dst("3", "4");
                }
                route(1);
                exit;
        }
    xlog("L_INFO", "IS_NOTFROM_MEDIA_INTERNAL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
}

route[IS_FROM_MEDIA_EXTERNAL] {
        if ($(var(external_media_proxy){param.exist,$si})) {
                xlog("L_INFO", "IS_FROM_MEDIA_EXTERNAL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci] + DD = $dd / $ds");
                if (is_method("REGISTER|MESSAGE")) {
                        sl_send_reply("500", "Method not allowed");
            exit;
                }
                route(ROUTINE);

        if (is_method("INVITE")) {
            if (!registered("location") && is_uri_host_local()) {
                xlog("L_INFO", "IS_FROM_MEDIA_EXTERNAL_INVITE_USER_NOTREG: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                sl_send_reply("404", "User not found");
                exit;
            } else {
                xlog("L_INFO", "IS_FROM_MEDIA_EXTERNAL_INVITE_USERREG: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
            }
        }
   

                route(1);

                exit;
        }
    xlog("L_INFO", "IS_NOTFROM_MEDIA_EXTERNAL: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
}

route[IS_FROM_MEDIASERVER] {
        if ($(var(media_server){param.exist,$si})) {
                xlog("L_INFO", "IS_FROM_MEDIASERVER: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (is_method("REGISTER|MESSAGE")) {
                        sl_send_reply("500", "Method not allowed");
            exit;
                }

                route(ROUTINE);

Тут разделение. В качестве фронта для webrtc выступает FreeSwitch, и на нем регаются клиенты с префиксом web_

                if (is_method("INVITE")) {
            if (pcre_match("$oU","^web_")) {
                            ds_select_dst("4", "4");
            } else {
                            ds_select_dst("2", "4");
            }
                }
                route(1);

                exit;
        }
    xlog("L_INFO", "IS_NOTFROM_MEDIASERVER: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
}


Для провайдеров заведена отдельная табличка.

route[IS_FROM_PROVIDER] {
    avp_db_query("SELECT IF( EXISTS( SELECT name FROM providers WHERE ip_address = INET_ATON('$si')), 1, 0)","$avp(is_provider)");
    if ($avp(is_provider)) {
        xlog("L_INFO", "IS_FROM_PROVIDER: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
        # Here will be some ideas about providers not handling DID. I really don't know what to do so. Test will tell us
        if (is_method("REGISTER|MESSAGE")) {
            xlog("L_INFO", "IS_FROM_PROVIDER_REGISTERORMESSAGE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
            sl_send_reply("500", "Method not allowed");
        }
        route(ROUTINE);
        if (is_method("INVITE")) {
            xlog("L_INFO", "IS_FROM_PROVIDER_INVITE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
            ds_select_dst("1", "4");
        }
        route(1);
        exit;
    }
    xlog("L_INFO", "IS_NOTFROM_PROVIDER: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
}


route[IS_FROM_WEB] {
        if ($(var(webrtc_proxy){param.exist,$si})) {
                xlog("L_INFO", "IS_FROM_WEB: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                if (is_method("REGISTER|MESSAGE")) {
            xlog("L_INFO", "IS_FROM_WEB_REGISTERORMESSAGE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        sl_send_reply("500", "Method not allowed");
            exit;
                }

                route(ROUTINE);

                if (is_method("INVITE")) {
            xlog("L_INFO", "IS_FROM_WEB_INVITE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                        ds_select_dst("3", "4");
                }
                route(1);

                exit;
        }
    xlog("L_INFO", "IS_NOTFROM_WEB: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");

}

route[PRESENCE] {
        if (!t_newtran()) {
            xlog("L_INFO", "PRESENCE_NOTNEWTRAN: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
            sl_reply_error();
            exit;
        };

        if(is_method("PUBLISH")) {
            xlog("L_INFO", "PRESENCE_PUBLISH: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
            handle_publish();
        } else {
            if(is_method("SUBSCRIBE")) {
                xlog("L_INFO", "PRESENCE_SUBSCRIBE: [F=$fu R=$ru D=$du M=$rm IP=($si:$sp $Ri:$Rp) ID=$ci]");
                rls_handle_subscribe();
                handle_subscribe();
            }
        }
        exit;
}



Да, тут много лишнего и некрасивого, но работа идет. В перспективе - интеграция с OpenXCAP для пристутствия и поддержка SIP_MESSAGE.
Но топология скрывается и звук ходит, что меня не может не радовать.

https://code.google.com/p/opensips-freeswitch-sbc/source/browse/ - Тут мои конфиги для opensips и freeswitch

-- upd.
Залита новая версия конфига. Добавлена авторизация провайдера по IP, добавлена некая защита от сканеров. Пока нету теста при регистрациях на внешних серверах. Также добавлена поддержка BLF (не проверено)
-- upd 2
Залит новый конфиг. Миграция на 1.11,  load_balancer сменен на dispatcher (потому что stateless), nathelper -> nat_traversal (потому что rtpproxy не используется), выпилен модуль accounting. Ну и мелкие изменения по ходу дела.
-- upd 3
На Гуглокод залит только конфиг OpenSIPS 1.11, теперь он может работать как  proxy-auth, т.е. полностью на себя берет вопрос аутентификации при звонке на внешние сервисы. Для этого пришлось сделать еще одну табличку в mysql с параметрами username, password, realm.