Microsoft Teams Direct Routing with Issabel (Asterisk)

Why?

FYI

  • Should have Issabel server properly installed with ip public and . The server firewall should be hardened by only allowing Carrier and Microsoft cloud ip address blocks (52.112.0.0/14). Even better if the only maintenance can only be accessed by VPN. Dont open your Issabel webGUI, SSH, SIP & RTP ports to public internet whenever possible.
  • My Issbel (Asterisk) server is already connected to Ofon as the PSTN Carrier, and already has a DID assigned.
  • Requires to edit chan_sip.c to comply with Microsoft Teams Phone System that demands SIP From and Contact header host part to be FQDN. From header is easy, just add fromdomain at the Asterisk trunk config and you’re good. The Contact header is the tricky one, based on this screenshot clue (stolen from thread https://community.asterisk.org/t/fqdn-in-the-contact-header/77625):
  • By half-assed editing the chan_sip.c , I expect there will be repercussion somewhere else in the system (plus I am no good at C, should find a better way to insert the change with more elegant solution), so dont share the PBX with something else for production, and use this as POC only.
  • Dont ever think to commercialize this without Microsoft approval, nor use this for officials. You may not be able to open tickets if you encounter problems with this installation although you are a legit Office 365 tenant with Phone System license. The certified SBC list can be found here.
  • Should have access to DNS server to add TXT type record for domain authentication.
  • Should have global admin account to make changes in O365 portal.
  • In this example, I have two E1 O365 + Phone System licenses. One to be assigned to my account and one to be assigned to SIP trunk domain dummy user.
  • You cant do multi tenant with this installation. Asterisk itself is a single realm (CMIIW).
  • Still cant find how to modify Contact header in OPTIONS, hence you’ll find tlsdontverifyserver=yes in Issabel general config.
  • Will periodically update this post with something new whenever I find improvement. So forgive me if you find something different when you read back the post later.

Microsoft Teams Phone System Side Config

In this stage you will need to add an SSL’ed domain to Office 365 portal. Microsoft-recommended CA list can be found here. I myself use GeoTrust.

  • Microsoft has a very detailed documentation that can be found at https://docs.microsoft.com/en-us/microsoftteams/direct-routing-plan . I wont rewrite the details here.
  • Using global admin to add domain (mine is ast.ofon.biz), to O365 admin portal.
  • Add a dummy user for that domain and assign O365 + Phone System license for that dummy user. For example, my dummy user for the newly added domain is sip@ast.ofon.biz. If successfully added, I will find the user in Active users table like this:
  • that is how Microsoft Teams will recognize the SIP trunk to our Issabel (Asterisk) server and assign license to the trunk.
  • Login using powershell to your account using global admin user. Again, the powershell preparation wont be repeated here since the detailed documentation can be found at https://docs.microsoft.com/en-us/microsoftteams/direct-routing-configure. Next steps will require us to import Sfbonline module to powershell.
  • Start the session (you will be asked for password on a popped up windows) :
$sfb = New-CsOnlineSession -Username "admin@ofonio.onmicrosoft.com"
Import-PSSession $sfb -AllowClobber
  • Add the Issabel (Asterisk server) as PSTN Gateway:
New-CsOnlinePSTNGateway -Fqdn ast.ofon.biz -SipSignalingPort 5061 -ForwardCallHistory $true -Enabled $true -ForwardPai $true -MaxConcurrentSessions 5
  • See if the server is properly added with Get-CsOnlinePstnGateway :
 Get-CsOnlinePSTNGateway


Identity                            : ast.ofon.biz
InboundTeamsNumberTranslationRules  : {}
InboundPstnNumberTranslationRules   : {}
OutboundTeamsNumberTranslationRules : {}
OutboundPstnNumberTranslationRules  : {}
Fqdn                                : ast.ofon.biz
SipSignalingPort                    : 5061
FailoverTimeSeconds                 : 10
ForwardCallHistory                  : True
ForwardPai                          : True
SendSipOptions                      : True
MaxConcurrentSessions               : 5
Enabled                             : True
MediaBypass                         : False
GatewaySiteId                       :
GatewaySiteLbrEnabled               : False
FailoverResponseCodes               : 408,503,504
GenerateRingingWhileLocatingUser    : True
PidfLoSupported                     : False
MediaRelayRoutingLocationOverride   :
ProxySbc                            :
BypassMode                          : None
  • Create a routing and routing policy that will utilize the Issabel (Asterisk) server:
Set-CsOnlinePstnUsage -Identity Global -Usage @{Add="Default"}

New-CsOnlineVoiceRoute -Identity "CatchAll" -NumberPattern ".*" -OnlinePstnGatewayList ast.ofon.biz -Priority 1 -OnlinePstnUsages "Default"

New-CsOnlineVoiceRoutingPolicy -Identity "DefaultVoicePolicy" -OnlinePstnUsages "Default"
  • Assign DID & VoiceRoutingPolicy to my account.

Issabel (Asterisk) Side Config

We will have two SIP connection configs for Microsoft Teams Phone System and two SIP connection configs for Carrier. Each connection will have different contexts. One SIP connection for Microsoft Teams will be from-internal, so call from Microsoft Teams will be treated as call from extension. And the other SIP connection will be treated as outbound trunk with from-trunk context. Same deal with the two SIP connections toward Carrier.

Microsoft provides 3 hosts to connect a SIP trunk with:

  • sip.pstnhub.microsoft.com
  • sip2.pstnhub.microsoft.com
  • sip3.pstnhub.microsoft.com

This example will only test the first one (sip.pstnhub.microsoft.com).

  • Adding Trunk to Microsoft Teams from PBX -> PBX Configuration -> Trunks. Add SIP Trunk and fill these parameters :
Trunk Name: MSTeams-Outbound
PEER Details:
disallow=all
host=sip.pstnhub.microsoft.com
type=peer
transport=tls
port=5061
allow=all&ulaw
qualify=yes
dtmfmode=rfc2833
context=from-trunk
insecure=port,invite
nat=force_rport,comedia
fromdomain=ast.ofon.biz
encryption=yes
  • And as “extension” add the following configs to sip_custom.conf using PBX -> Tools -> Asterisk File Editor :
[MSTeams-Inbound]
disallow=all
host=sip.pstnhub.microsoft.com
type=friend
transport=tls
port=5061
allow=all
allow=ulaw
qualify=yes
dtmfmode=rfc2833
context=from-internal
insecure=port,invite
nat=force_rport,comedia
fromdomain=ast.ofon.biz
  • To refer to certifcate files, go to PBX -> PBX Configuration -> Unembedded IssabelPBX. At the following page, go to Settings -> Asterisk SIP Settings and then Add field at the Other SIP Settings with these:
realm=ast.ofon.biz
tlsenable=yes
tlsbindaddr=0.0.0.0:5061
tlsprivatekey=/etc/asterisk/keys/star_ofon_biz.key
tlscertfile=/etc/asterisk/keys/star_ofon_biz.pem
tlscafile=/etc/asterisk/keys/star_ofon_biz.crt
tlscipher=ALL
tlsclientmethod=tlsv1
tlsdontverifyserver=yes
  • Save and reload the config.
  • Add the Outbound Routes to Microsoft Teams and Carrier (Microsoft Teams outbound should be aboive the Carrier). Since my carrier mostly sends with prefix 0, I have to convert it to E.164. Please modify these according to your apporpriate settings:

At this point, you should be able to call outbound from Microsoft Teams account but not the other way (inbound). As mentioned before, Microsoft Teams Phone System require us to send From and Contact header host as FQDN. The trunk settings in Asterisk wont change Contact header host to FQDN hence we’ll receive this message:

INVITE sip:+622430000062@sip.pstnhub.microsoft.com:5061 SIP/2.0
Via: SIP/2.0/TLS xxx.xxx.xxx.xxx:5061;branch=z9hG4bK4e781f8a;rport
Max-Forwards: 70
From: "02139700001" <sip:02139700001@ast.ofon.biz>;tag=as36acd9d9
To: <sip:+622430000062@sip.pstnhub.microsoft.com:5061>
Contact: <sip:02139700001@xxx.xxx.xxx.xxx:5061;transport=tls>
Call-ID: 2239ff467456d8f03c4348826168d7a6@ast.ofon.biz
CSeq: 102 INVITE
User-Agent: Asterisk 16.7.0
Date: Fri, 08 May 2020 04:45:38 GMT
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH, MESSAGE
Supported: replaces, timer
Content-Type: application/sdp
Content-Length: 1324

...
...
...
SIP/2.0 403 Forbidden
FROM: "02139700001"<sip:02139700001@ast.ofon.biz>;tag=as36acd9d9
TO: <sip:+622430000062@sip.pstnhub.microsoft.com:5061>
CSEQ: 102 INVITE
CALL-ID: 2239ff467456d8f03c4348826168d7a6@ast.ofon.biz
VIA: SIP/2.0/TLS xxx.xxx.xxx.xxx:5061;branch=z9hG4bK4e781f8a;rport
REASON: Q.850;cause=63;text="735a16a2-5f83-4cb0-bdec-9cb64edff551;Provided Trunk FQDN 'xxx.xxx.xxx.xxx' is not allowed. Connection allows following fqdns: *.ofon.biz, ofon.biz, *.ofon.biz."
CONTENT-LENGTH: 0
ALLOW: INVITE,ACK,OPTIONS,CANCEL,BYE,NOTIFY
SERVER: Microsoft.PSTNHub.SIPProxy v.2020.5.6.2 i.ASEA.2

chan_sip.c

There is no Asterisk source in Issabel distro so we have to get it ourself. From Issabel linux console, git clone the Asterisk github and checkout for 16.7.0 (since this is my Issabel’s Asterisk version).:

cd /usr/src
git clone https://github.com/asterisk/asterisk.git
cd asterisk
git checkout 16.7.0

Edit file /usr/src/asterisk/channel/chan_sip.c. Look at build_contact() we will find these lines:

static void build_contact(struct sip_pvt *p, struct sip_request *req, int incoming)
{
        char tmp[SIPBUFSIZE];
        char *user = ast_uri_encode(p->exten, tmp, sizeof(tmp), ast_uri_sip_user);
        int use_sips;
        char *transport = ast_strdupa(sip_get_transport(p->socket.type));

        if (incoming) {
                use_sips = uas_sips_contact(req);
        } else {
                use_sips = uac_sips_contact(req);
        }

        if (p->socket.type == AST_TRANSPORT_UDP) {
                ast_string_field_build(p, our_contact, "<%s:%s%s%s>", use_sips ? "sips" : "sip",
                        user, ast_strlen_zero(user) ? "" : "@",
                        ast_sockaddr_stringify_remote(&p->ourip));
        } else {
                ast_string_field_build(p, our_contact, "<%s:%s%s%s;transport=%s>",
                        use_sips ? "sips" : "sip", user, ast_strlen_zero(user) ? "" : "@",
                        ast_sockaddr_stringify_remote(&p->ourip), ast_str_to_lower(transport));
        }
}

The last conditional lines are where the Contact header gets value from. Since we only need to modify for TCP transport, edit the line:

 else {
                ast_string_field_build(p, our_contact, "<%s:%s%s%s;transport=%s>",
                        use_sips ? "sips" : "sip", user, ast_strlen_zero(user) ? "" : "@",
                        ast_sockaddr_stringify_remote(&p->ourip), ast_str_to_lower(transport));
        }

to become :

 else {
                ast_string_field_build(p, our_contact, "<%s:%s%s%s;transport=%s>",
                        use_sips ? "sips" : "sip", user, ast_strlen_zero(user) ? "" : "@",
                        p->fromdomain, ast_str_to_lower(transport));
        }

p->fromdomain will follow whatever “fromdomain=” value in the SIP trunk config.

Update:
Got input from Igo Cunha that the call from MSteams to PSTN will be disconnected after 6 sec. I can confirm I got random disconnect call at about 6 sec from the same direction. Try replace:
 
ast_sockaddr_stringify_remote(&p->ourip) 

with your Asterisk's FQDN string (e.g "ast.ofon.biz" in my case) instead of p->fromdomain.

Thanks Igo!

I understand this is not elegant way, but that should do it. Compile the source and we will have brand new chan_sip.so. Backup the original one and replace it after:

copy /usr/lib64/asterisk/modules/chan_sip.so /usr/local/src/chan_sip.so.original
copy /usr/src/asterisk/channels/chan_sip.so /usr/lib64/asterisk/modules/chan_sip.so

then reload the module from Asterisk console:

asterisk -vr

ast*CLI> module reload chan_sip.so
Module 'chan_sip.so' reloaded successfully.
 Reloading SIP

Now test call to inbound, we should have correct SIP Contact header requirement as follows:

INVITE sip:+622430000062@sip.pstnhub.microsoft.com:5061 SIP/2.0
Via: SIP/2.0/TLS 103.135.74.15:5061;branch=z9hG4bK394b6483;rport
Max-Forwards: 70
From: "02139700001" <sip:02139700001@ast.ofon.biz>;tag=as20858906
To: <sip:+622430000062@sip.pstnhub.microsoft.com:5061>
Contact: <sip:02139700001@ast.ofon.biz;transport=tls>
Call-ID: 1a5d05f10eadb0036683f88a6177f80f@ast.ofon.biz
CSeq: 102 INVITE
User-Agent: Asterisk 16.7.0
Date: Fri, 08 May 2020 04:54:36 GMT
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH, MESSAGE
Supported: replaces, timer
Content-Type: application/sdp
Content-Length: 1324

and the call went through:

Remarks

Although it is possible to connect Direct Routing with Asterisk, still easier using Kamailio (and OpenSIPS) since they’re built solely for SIP packet engineering. Until some of them become certified with Microsoft, I guess I will be using the already ceritfied ones, like Sonus or AudioCodes, or just connect to a hosted Direct Routing Provider in Indonesia, like Ofon. Alternatively, there are Kamailo based products start providing hosted DR, like Gilawa and dSIPRouter. Check them out.

Complete Configs

Links:

10 thoughts on “Microsoft Teams Direct Routing with Issabel (Asterisk)”

    1. Hi, sorry for late reply. It’s long holiday here. Email is ghwishnu@hotmail.com . This implementation is not yet complete because I havent succeed with OPTIONS and REFER. It just works minimally. Are you sure to implement this?

  1. Hello first thanks for publishing the article and helping. I did all the step by step and I’m having a problem and maybe you can help. I can make calls to Issabel -> Teams and I have no problem. When I make calls from Teams -> Issabel the call goes down in 6 seconds. And that only happens with the compiled file chan_sip.so.

    With the original chan_sip.so file without the modifications, I can call Teams -> Issabel and the call will not go down. Did you have the same problem can you help me?

    Cli-> message error:
    retransmission timeout reached on transmission and chan_sip.c:4143 retrans_pkt: hanging up call

    If possible, can you send me your compiled chan_sip.so file so that I can test it and see if the same problem occurs? thank you very much and great day. Setup Issabel 4 -> Asterisk 16

    1. Hi,

      Is this behind NAT? If it is so, I haven’t tested it behind NAT. I just put ip public right there. Any email where I can send you my compiled chan_sip.so ?

      1. Thanks for the reply and attention. Yes .. please send igo.cunha@gmail.com. Yes, I’m after a NAT. What happens is that the original asterisk 16.4 file works normally called Teams -> Asterisk. When I make the change

        else {
        ast_string_field_build (p, our_contact, “”,
        use_sips? “sips”: “sip”, user, ast_strlen_zero (user)? “”: “@”,
        p-> fromdomain, ast_str_to_lower (transport));
        }

        I start making calls from Asterisk to Teams normally and I can no longer make calls from Teams to Asterisk (Call drops with 6 seconds.) I tried to make the change you suggested without success … I am waiting to send the file to make the test. Thank you very much for your attention again.

        1. Update

          Thanks for the solution. I was compiling the term wrongly because it was not a variable. And yes the solution works. After changing p-> fromdomain to the fqdn of my PBX the call became stable. Thank you very much for your help and beautiful work. If you have new updates please share. Once again thank you very much for your attention.

          1. Awesome! Glad to know it works finally. I assume you wouldnt need my compiled chan_sip.so, right?

    2. Hi, I can confirm that the call will be randomly disconnect on 6 sec. What I mean is from 5 calls I can get 2 or 3 calls disconnect on 6 secs.

      Then I tried change p->fromdomain to “sub.domain.tld” (this is your asterik fqdn) instead, the call can survive longer. Can you please try that and share with me what the result?

Leave a Reply

Your email address will not be published. Required fields are marked *