Why?
- Inspired by a post “Using Kamailio as SBC for Microsoft Teams” written by Henning Westerholt.
- While preparing Ribbon Communication certification, I found out what it takes to connect to Microsoft Teams Phone System.
- I play with Issabel (Asterisk) quite a lot.
- Boredom during WFH is killing me.
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:
- https://www.kamailio.org/w/2019/10/using-kamailio-as-sbc-for-microsoft-teams/
- https://www.issabel.org/
- https://ofon.co.id/enterprise/clouddirectrouting
- https://gilawa.com/en/products
- https://dsiprouter.org/msteams
- https://community.asterisk.org/t/fqdn-in-the-contact-header/77625
- https://docs.microsoft.com/en-us/microsoftteams/direct-routing-plan
- https://docs.microsoft.com/en-us/microsoftteams/direct-routing-configure
- https://support.sonus.net/display/IOT/Ribbon+Configuration+With+Microsoft+Teams+for+Carrier+Trunk+and+CAC+-+ERE+and+PSX?desktop=true¯oName=style
can you help us with the same implementation?
How to contact you? I need help for our issabel and teams direct routing. Willing to pay.
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?
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
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 ?
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.
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.
Awesome! Glad to know it works finally. I assume you wouldnt need my compiled chan_sip.so, right?
Hi godril, could you help me with the chan_sip.so file, I don’t know what I’m doing wrong but when I proceed to compile it throws me an error
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?
Pingback: Complete Issabel Configs for MSTeams Direct Routing – otakudang.org (v2.0)
Hi, I did all the information above. Can you help me why my trunk is unreachable? What could be? Thanks Jorge
Hi,
how is your setup topology? Are behind NAT or is it connected directly to public ip address?
Hi godril, could you help me with the chan_sip.so file, I don’t know what I’m doing wrong but when I proceed to compile it throws me an error
Sorry for really late reply, been in a project so just got chance to check blog.
How can I help? what error did you get?
Pak godril, layanan ofon yang dipakai, layanan apa ya namanya(saya lihat websitenya ada banyak macam layanannya)? selama ini saya langganan sip trunk Telkom (hanya bisa on premises) saya mao yg bisa diakses secara cloud, oiya terimakasih atas tutorialnya yang sangat berguna, it works like magic
Halo,
Salah satunya SIP trunk juga, dan biasanya bisa bekerja sama dengan ISP yang dikehendaki pengguna. Layanan lainnya ada hosted PBX yang juga banyak dipakai untuk dapat diakses dari mana pun. Selain itu da juga Hosted MSTeams Direct Routing.
Terima kasih juga sudah membaca blog saya. Senang bisa membantu.
I’m having issues where the trunk to MS is unrechable. TLS seems to be ok according to cli. I can confirm i have firewall ports open for MS addresses.
On the management console for 0365 says there’s no response from mi FQDN, no SIP response. Issabel VM is on a Public IP, no nat is being used, just some firewall rules. What am i missing??
Question,
how did you generate yor tls certificates. I’m having problems with that.
Hi,
In my case, I already have a wildcard certificate from GeoTrust. To obtain the certificate you can generate the CSR from wherever machine you want. Doesnt matter if you generate the CSR from your laptop and plant the obtained certificates from MS recommended CA to your Issabel.
The list of MS recommended CAs can be found here:
https://docs.microsoft.com/en-us/microsoftteams/direct-routing-plan#public-trusted-certificate-for-the-sbc
Make sure you also download Baltimore root CA for mutual TLS (since MS is using Baltimore root certificate) that can be found here:
https://docs.microsoft.com/en-us/microsoft-365/compliance/encryption-office-365-certificate-chains?view=o365-worldwide
Hi,
I’ve got outbound calls working fine but inbound (issable -> teams ) just like not response to the invite or timeout, firewall is Ok, I working with let’sencrypt
Does the contact header in INVITE contain your hostname already? Usually when Teams doesnt respon to invite because the contact header doesnt contain the hostname that is registered as in OnlinePSTNGateway.
Hi ,
you did a perfect job with your Issabel!
I’ve been doing the same now with native Asterisk 17.7 with chan_sip too, all is working!!
Only a little detail is not working, can’t hear anything!
But only when I do calls to or from Teams!
The rest is working, all other clients doing directly via siptrunk of the provider and all is ok!
Do you have an idea?
Best regards.
Josef
Hi ,
you did a perfect job with your Issabel!
I’ve been doing the same now with native Asterisk 17.7 with chan_sip too, all is working!!
Only a little detail is not working, can’t hear anything, no voice! 🙂
But only when I do calls to or from Teams!
The rest is working, all other clients doing directly via siptrunk of the provider and all is ok!
Do you have an idea?
Best regards.
Josef
Hi Josef,
Is it possible for you to share the SIP dialogs when falling to/from Teams via Asterisk? Especially the INVITE and 200OK SIP headers? Maybe we can find the answer there.
Regards,
Gabriel.
This is really awesome, but would be great if we had the same option for PJSIP. PJSIP has the option to define a static FQDN, but I can’t find a way to do this dynamically as mentioned here, by referencing an endpoint parameter (such as fromdomain, from_domain in pjsip.conf).