Hello Experts, 

I'm not an expert in Kamailio, but thanks to some excellent online resources, I managed to set up a basic interconnection between Teams and Kamailio.

So far, outbound calls, as well as blind and attended call transfers, are working smoothly. However, I'm facing a challenge with call disconnection for outbound calls. Specifically, when an outbound call is made and the called party hangs up, the BYE message isn't being relayed properly towards the Teams side.

In my routing configurations, I remove the Record-Route header received from Teams and add a new Record-Route header with Kamailio's IP before forwarding it to my IP PBX (FreeSwitch). For the BYE message to be processed correctly, Microsoft Teams requires the original contact in the Request URI (RURI) and the Route header to match the initial Record-Route included in the INVITE. Currently, my BYE message appears as follows:


BYE sip:api-du-a-auea.pstnhub.microsoft.com:443;x-i=5e96d703-b1e7-449c-aebf-a87bfa628176;x-c=f61b674bc597513ab8d888dbe7c560fb/d/8/9a6c508a9cbd44ebb892b62c33ba9b67 SIP/2.0
Via: SIP/2.0/UDP x.x.x.x:5060;branch=z9hG4bKeab5.b657e3455b6d11ed6975c69882632a27.0
Via: SIP/2.0/UDP x.x.x.x;received=x.x.x.x;rport=5060;branch=z9hG4bKK3HKQBUvX42ya
Max-Forwards: 69
From: <sip:+61403225xxx@kamsbc.xyzdomain.com:5061;user=phone>;tag=j5gptBSXXrXDc
To: "XYZ Pty Ltd" <sip:+61291455xxx@sip.pstnhub.microsoft.com:5061;user=phone>;tag=3a5f0c9e32bc4eb1bffed29dc954118f
Call-ID: f61b674bc597513ab8d888dbe7c560fb
CSeq: 80555743 BYE
User-Agent: Unknown/v1.0
Allow: INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY
Supported: timer, path, replaces
Reason: MVTSLocal;cause=10;text="BYE received"
Content-Length: 0


Kamailio conducts a lookup for api-du-a-auea.pstnhub.microsoft.com but ends up sending the BYE message to an incorrect IP.

Below is a snippet of my routing block configurations:


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


/* Main SIP request routing logic
 * - processing of any incoming SIP request starts with this route
 * - note: this is the same as route { ... } */
request_route {

        # per request initial checks
        route(REQINIT);

        #check who is the sender
        route(INITCHECK);

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

        # handle retransmissions
        if (!is_method("ACK")) {
                if(t_precheck_trans()) {
                        t_check_trans();
                        exit;
                }
                t_check_trans();
        }

        # handle requests within SIP dialogs
        route(WITHINDLG);

        ### only initial requests (no To tag)

        # record routing for dialog forming requests (in case they are routed)
        # - remove preloaded route headers
        remove_hf("Route");
        if (is_method("INVITE|SUBSCRIBE")) {
                record_route();
        }

        # account only INVITEs
        if (is_method("INVITE")) {
                setflag(FLT_ACC); # do accounting
        }

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

        # update $du to set the destination address for proxying
        #$du = "sip:" + $rd + ":9";

        route(RELAY);
        exit;
}

route[INITCHECK] {
        if(from_uri =~ ".*microsoft.com")
        {
                setflag(FROM_TEAMS);
                $du = "sip:" + "MY_PBX_IP" + ":" + "5060";

                //added by SHK

                remove_hf("Record-Route"); // Remove existing Record-Route header
                append_hf("Record-Route: <sip:KAM_IP:5060;transport=udp>\r\n");

                route(HANDLE_RTP_FROM_TEAMS);
        }

        if(from_uri =~ ".*" + "MY_PBX_IP")
        {
                setflag(FROM_PBX);
                $du="sip:sip.pstnhub.microsoft.com;transport=tls";
                route(HANDLE_RTP_FROM_PBX);
        }
}

#Manage RTP & transcoding comming from Teams to PBX
route[HANDLE_RTP_FROM_TEAMS] {
        t_on_reply("PBX_REPLY_TO_TEAMS");

        if (has_body("application/sdp"))
        {
                rtpengine_manage("RTP codec-mask=all codec-transcode=PCMA replace-origin replace-session-connection ICE=remove media-address=HOST_IP");
               // record_route();
                t_relay_to_udp("MY_PBX_IP","5060");
        }
        else
        {
                rtpengine_manage();
        }
}

#Manage RTP & transcoding comming from PBX to Teams
route[HANDLE_RTP_FROM_PBX] {
        t_on_reply("TEAMS_REPLY_TO_PBX");

        if (has_body("application/sdp"))
        {
                rtpengine_manage("SRTP codec-mask=all ICE=force codec-transcode=PCMA replace-origin replace-session-connection media-address=ADVERTISE_IP");

                $rd = "sip.pstnhub.microsoft.com";
                $td = "kamsbc.xyzdomain.com";
                $fd = "kamsbc.xyzdomain.com";

               // record_route();

                #Set TLS SNI (server name & server id)
                $xavp(tls=>server_name) = "kamsbc.xyzdomain.com";
                $xavp(tls=>server_id) = "kamsbc.xyzdomain.com";

                t_relay();
        }
        else
        {
                rtpengine_manage();
        }
}


# Wrapper for relaying requests
route[RELAY] {

        # enable additional event routes for forwarded requests
        # - serial forking, RTP relaying handling, a.s.o.

        if (is_method("INVITE|BYE|SUBSCRIBE|UPDATE")) {
                if(!t_is_set("branch_route")) t_on_branch("MANAGE_BRANCH");
        }
        if (is_method("INVITE|SUBSCRIBE|UPDATE")) {
                if(!t_is_set("onreply_route")) t_on_reply("MANAGE_REPLY");
        }
        if (is_method("INVITE")) {
                if(!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE");
        }

        if (!t_relay()) {
                sl_reply_error();
        }
        exit;
}

# Per SIP request initial checks
route[REQINIT] {
        if($ua =~ "friendly-scanner|sipcli|VaxSIPUserAgent") {
                # silent drop for scanners - uncomment next line if want to reply
                # sl_send_reply("200", "OK");
                exit;
        }

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

        if(is_method("OPTIONS")) {
                sl_send_reply("200","Keepalive");
                exit;
        }

        if(!sanity_check("1511", "7")) {
                xlog("Malformed SIP message from $si:$sp\n");
                exit;
        }
}

# Handle requests within SIP dialogs
route[WITHINDLG] {
        if (!has_totag()) return;

        #Teams reINVITEs
        if(isflagset(FROM_TEAMS)) {
                loose_route();
                t_relay();
                exit;
        }

        if(isflagset(FROM_PBX)) {
                #Set TLS SNI (server name & server id)
                $xavp(tls=>server_name) = "kamsbc.xyzdomain.com";
                $xavp(tls=>server_id) = "kamsbc.xyzdomain.com";

                loose_route();
                t_relay();
                exit;
        }


        # sequential request withing a dialog should
        # take the path determined by record-routing
        if (loose_route()) {
                if (is_method("BYE")) {
                        setflag(FLT_ACC); # do accounting ...
                        setflag(FLT_ACCFAILED); # ... even if the transaction fails

                        #set coresponding cert on transactions
                        if($fd == "kamsbc.xyzdomain.com") {
                                $xavp(tls=>server_name) = "kamsbc.xyzdomain.com";
                                $xavp(tls=>server_id) = "kamsbc.xyzdomain.com";
                        }

                } else if ( is_method("NOTIFY") ) {
                        # Add Record-Route for in-dialog NOTIFY as per RFC 6665.
                        record_route();
                }
                route(RELAY);
                exit;
        }

        if ( is_method("ACK") ) {
                if ( t_check_trans() ) {
                        # no loose-route, but stateful ACK;
                        # must be an ACK after a 487
                        # or e.g. 404 from upstream server
                        route(RELAY);
                        exit;
                } else {
                        # ACK without matching transaction ... ignore and discard
                        exit;
                }
        }
        sl_send_reply("404","Not here");
        exit;
}

# Manage outgoing branches
branch_route[MANAGE_BRANCH] {
        xdbg("new branch [$T_branch_idx] to $ru\n");
}

# Manage incoming replies
onreply_route[MANAGE_REPLY] {
        xdbg("incoming reply\n");
}

#PBX On Reply
onreply_route[PBX_REPLY_TO_TEAMS]
{

        if (has_body("application/sdp"))
        {
                rtpengine_manage("SRTP codec-mask=all codec-transcode=PCMA replace-origin replace-session-connection media-address=ADVERTISE_IP");
        }
        else
        {
                rtpengine_manage();
        }

//added by SHK

       if (status == "200" && method == "INVITE") {
        remove_hf("Contact"); # Remove existing Contact header
        append_hf("Contact: <sip:kamsbc.xyzdomain.com:5061;transport=tls>\r\n");
    }


       if (status=="200")
       {
        remove_hf("ALLOW:");
        append_hf("ALLOW: INVITE,ACK,OPTIONS,CANCEL,BYE,NOTIFY \r\n","CONTENT-TYPE");

        }

}


#From Teams On Reply
onreply_route[TEAMS_REPLY_TO_PBX]
{
        if (has_body("application/sdp"))
        {
                rtpengine_manage("RTP codec-mask=all codec-transcode=PCMA replace-origin replace-session-connection media-address=HOST_IP");
        }
        else
        {
                rtpengine_manage();
        }


}

# Manage failure routing cases
failure_route[MANAGE_FAILURE] {
        if (t_is_canceled()) exit;
}

event_route[tm:local-request] {
        if(is_method("OPTIONS") && $ru =~ "pstnhub.microsoft.com") {
                append_hf("Contact: <sip:kamsbc.xyzdomain.com:SBC_PORT;transport=tls>\r\n");
        }
        xlog("L_INFO", "Sent out tm request: $mb\n");
}


####### Routing Logic End ########


Given these details, I suspect the issue might be related to how I'm handling the Record-Route headers or potentially a misconfiguration in directing the BYE message. I would greatly appreciate any insights, suggestions, or guidance on how to correctly relay the BYE message back to Teams, or further refine my existing configuration. 

Thank you in advance for your help and support!



Regards, 
Shah Hussain