1

I'm trying to set up a SIP infrastructure using OpenSIPS as a load balancer for multiple backend SIP servers. The goal is to route incoming INVITE requests to a backend server and have OpenSIPS manage the call's media using rtpproxy.

Here's a quick overview of my setup:

  • OpenSIPS: Handles registration with an upstream provider, load balances INVITEs, and uses rtpproxy for media.

  • Backend Servers: Custom Python SIP servers built with PJSUA2.

  • rtpproxy: Handles media streams.

My issue is with the ACK message. After an INVITE is sent to a backend and the backend replies with a 200 OK, OpenSIPS receives the final ACK from the provider but never forwards it to the backend server. This causes the call to fail.

The call flow looks like this:

Provider -> OpenSIPS -> Backend (INVITE)
Provider <- OpenSIPS <- Backend (200 OK)
Provider -> OpenSIPS (ACK)
... The ACK is lost here. The backend never receives it.

suspect the problem lies in my OpenSIPS configuration, specifically how I'm handling stateful routing and dialog management. I've tried tweaking the Record-Route and Contact headers, but nothing seems to work.

Has anyone encountered a similar issue? What OpenSIPS configuration parameters or routing logic could cause a final ACK to be dropped instead of being routed to the correct backend server?

Any guidance on how to properly set up a stateful proxy with load_balancer and rtpproxy would be a great help! 🙏

You can find my full opensips.cfg :

#
# OpenSIPS loadbalancer script
#     by OpenSIPS Solutions <[email protected]>
#
# This script was generated via "make menuconfig", from
#   the "Load Balancer" scenario.
# You can enable / disable more features / functionalities by
#   re-generating the scenario with different options.
#
# Please refer to the Core CookBook at:
#      https://opensips.org/Resources/DocsCookbooks
# for a explanation of possible statements, functions and parameters.
#


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

/* uncomment the following lines to enable debugging */
debug_mode=yes

log_level=2
xlog_level=2
stderror_enabled=no
syslog_enabled=yes
syslog_facility=LOG_LOCAL0

udp_workers=4

# CUSTOMIZE ME
listen=udp:0.0.0.0:5060 as [OPENSIPS_PUBLIC_IP]:5060

# listen=udp:172.31.0.10:5060



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

#set module path
mpath="/usr/lib/x86_64-linux-gnu/opensips/modules/"

#### UAC module
loadmodule "uac.so"
loadmodule "uac_auth.so"
loadmodule "uac_registrant.so"
modparam("uac_registrant", "db_url", "mysql://opensips:opensipsrw@mysql/opensips")
modparam("uac_registrant", "timer_interval", 30)
modparam("uac_registrant", "hash_size", 2)

#### HTTPD module
loadmodule "httpd.so"
modparam("httpd", "port", 8888)

#### 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", 0)

#### 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)

#### MYSQL module
loadmodule "db_mysql.so"

#### SQLOPS module
loadmodule "sqlops.so"
modparam("sqlops","db_url","mysql://opensips:opensipsrw@mysql/opensips") # CUSTOMIZE ME

#### 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 to enable "append_fromtag"
   in "rr" module */
modparam("acc", "detect_direction", 0)


#### DIALOG module
loadmodule "dialog.so"
modparam("dialog", "dlg_match_mode", 1)
modparam("dialog", "default_timeout", 21600)  # 6 hours timeout
modparam("dialog", "db_mode", 2)
modparam("dialog", "db_url", "mysql://opensips:opensipsrw@mysql/opensips") # CUSTOMIZE ME


#### LOAD BALANCER module
loadmodule "load_balancer.so"
modparam("load_balancer", "db_url", "mysql://opensips:opensipsrw@mysql/opensips") # CUSTOMIZE ME
modparam("load_balancer", "probing_method", "OPTIONS")

modparam("load_balancer", "probing_interval", 30)



####  MI_HTTP module
loadmodule "mi_http.so"

loadmodule "proto_udp.so"

#### Proxy modules
loadmodule "usrloc.so"
loadmodule "nathelper.so"
modparam("nathelper", "natping_interval", 10)
loadmodule "rtpproxy.so"
modparam("rtpproxy", "rtpproxy_sock", "unix:/var/run/rtpproxy/rtpproxy.sock")
modparam("rtpproxy", "rtpproxy_autobridge", 1)



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


# main request routing logic

route{

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

    if (has_totag()) {

        # handle hop-by-hop ACK (no routing required)
        if ( is_method("ACK") && t_check_trans() ) {
            xlog("ACK HOP BY HOP \n");
            t_relay();
            exit;
        }

        # sequential request withing a dialog should
        # take the path determined by record-routing
        if ( !loose_route() ) {
            # we do record-routing for all our traffic, so we should not
            # receive any sequential requests without Route hdr.
            send_reply(404,"Not here");
            exit;
        }
        
        if (is_method("BYE")) {
            # do accounting even if the transaction fails
            rtpproxy_unforce();
            do_accounting("log","failed");
        }

        # route it out to whatever destination was set by loose_route()
        # in $du (destination URI).
        route(RELAY);
        exit;
    }

    #### INITIAL REQUESTS

    # CANCEL processing
    if (is_method("CANCEL")) {
        if (t_check_trans())
            t_relay();
        exit;
    } else if (!is_method("INVITE")) {
        send_reply(405,"Method Not Allowed");
        exit;
    }

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

    t_check_trans();

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

    if (has_body("application/sdp")) {
        xlog("L_INFO", ">>> Calling rtpproxy_offer for $ci\n");
        rtpproxy_offer();
    }

    # record routing
    xlog("Record route \n");
    record_route();

    do_accounting("log");

    
    if ( !lb_start(1,"channel")) {
    
        send_reply(500,"No Destination available");
        exit;
    }


    t_on_failure("GW_FAILOVER");

    xlog("Going to route RELAY \n");
    route(RELAY);
}


route[RELAY] {
    if (has_body("application/sdp")) {
        xlog("has body app sdp \n");
        xlog("L_INFO", ">>> Calling rtpproxy_answer for $ci\n");
        xlog("answering withrtpproxy \n");
        rtpproxy_answer();
    }
    xlog("fixing nated contact \n");
    fix_nated_contact();
    xlog("calling t_relay \n");
    if (!t_relay()) {
        xlog("Error on t_relay \n");
        sl_reply_error();
    }
    exit;
}


failure_route[GW_FAILOVER] {
    if (t_was_cancelled()) {
        exit;
    }

    # failure detection with redirect to next available trunk
    if (t_check_status("(408)|([56][0-9][0-9])")) {
        xlog("Failed trunk $rd/$du detected \n");

        
        if ( lb_next() ) {
        
            t_on_failure("GW_FAILOVER");
            t_relay();
            exit;
        }
        
        send_reply(500,"All GW are down");
    }
}


local_route {
    if (is_method("BYE") && $DLG_dir=="UPSTREAM") {
        
        acc_log_request("200 Dialog Timeout");
        
    }
}
2
  • Was this question generated by AI? Commented Aug 11 at 8:41
  • Hi @lajos? I just traduce my original message in english so yes and no. However, I change globally the majority of the text Commented Aug 11 at 12:39

1 Answer 1

0

I had this issue while building a custom proxy load-balancer with opensips. It take me a long time to solve it.

Explanation :

You are using a private backend that require you to handle nat translation and dialog retreiving.

  • Nat translation : Right now, you do it with fix_nated_contact(); that's good.
  • Dialog retreiving : Using dialog module, opensips stores dialog informations to handle Incoming SIP messages that are member of the same call. Sometimes, incoming SIP messages can have missing or wrong information about the ruri, route headers and dst_uri.

Solution :

You should ensure that SIP messages belonging to a dialog contains all required information before going further in your complex routing logic.
You can do it by adding the following code on your if (has_totag()) block:

    # Your has_totag block:
    if (has_totag()) {
        # Keep this
        if (!loose_route() && !is_method("ACK")) {
            send_reply(404,"Not here");
            exit;
        }
                
        # Add this
        if (!validate_dialog()) {
            xlog("Fixing R-URI/Route/dst_uri based on stored dialog \n");
            fix_route_dialog();
        }
      
        # Keep your code after this

I hope this will help, it's always difficult to get examples or efficient documentation about opensips :)

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.