Microsoft Teams, Direct Routing and Asterisk running on a Raspberry Pi 4

Thanks to another post for assisting with my knowledge of setting up Asterisk as a gateway to Microsoft Teams. (This is not supported but a good cost effective way to learn how Microsoft Direct Routing works). A Raspberry Pi 4 was the smallest footprint to run a gateway for Teams without having virtual infrastructure, appliances etc while learning and doing a proof of concept just to test some Poly handsets etc.

Link to post that got me started:

https://www.ayonik.de/blog/item/90-microsoft-teams-direct-routing-with-asterisk-pbx

Thanks to ayonik.de for getting my POC started with their notes. I now have a fully working Asterisk to Direct Routing MS Teams trunk. I only use it for POC/demos at work but if you are interested in learning more about Asterisk and Microsoft Teams it is a good place to start. I know very little about Asterisk so I have only put together a config just to be able to test. I can share my config and learnings in the hope of helping someone else out. Only abbreviated notes but happy to help if you are stuck. Also happy to take advice on how I could do it better. I'm not looking to boil the ocean.

Topology:

Poly D230 Dect Cordless IP Handset ==>Asterisk==>Direct Routing==>MS TEams==>Poly CCX 500 Teams Handset or Laptop(s)

So pretending D230 Dect is on the PSTN with e.g.+61755551000 and MS TEams Handset/Laptop has Dummy +61755559000. Testing Call forking inbound to ring another D230 and MS Tams at the same time. and vicy versa.

Recompiled Asterisk (first on Asterisk 17.0.1 but now on 17.3 due to intermittent / dodgy failing on refer on transfer with SIP). So I would start with Asterisk 17.3 and recompile with headers that match your DNS name for the Asterisk "SBC" (using term loosely) to Microsoft Teams direct routing trunk.

  • Make sure you have a valid cert. I use LetsEncrypt 90 day certs as they are free for testing.

  • Setup a trial or test O365 tenant with E5 licenses etc if you can get it.

  • Setup DNS correctly. My SIP SBC is sipconnect.o365.fullys.xyz

Powershell:

  • Setup Powershell first

  • Install Skype For Business online module

If the Skype for Business Online Windows PowerShell Module is not already installed on the current Windows computer then download and install it from here: https://www.microsoft.com/en-us/download/details.aspx?id=39366

Make sure the powershell script can run:

Set-ExecutionPolicy Unrestricted

Setup powershell session for the Teams commands:

Import-Module SkypeOnlineConnector

$sfbo = New-CsOnlineSession -UserName "afullagar@o365.fullys.xyz"

Import-PSSession $sfbo

Disbale CS-IpPhonePolicy just to make sure devices are not updated when least expected.

Set-CsIPPhonePolicy -EnableDeviceUpdate $false

Run Powershell for the Direct Routing trunk:

New-CsOnlinePSTNGateway -Fqdn sipconnect.o365.fullys.xyz -SipSignallingPort 5061 -MaxConcurrentSessions 10 -Enabled $true

  • There are other powershell commands to run like dialplan and Set-CsUser settings etc.

    • Set Teams Calling Policy

    • Check if TeamsCalling is enabled for users

$userName="eg1@o365.fullys.xyz"

Grant-CsTeamsCallingPolicy -PolicyName Tag:AllowCalling -Identity $userName

Get-CsOnlineUser | ft display*,userprin*,teamscallingpolicy


e.g.

DisplayName UserPrincipalName TeamsCallingPolicy

----------- ----------------- ------------------

eg 1 eg1@o365.fullys.xyz AllowCalling

eg 2 eg2@o365.fullys.xyz AllowCalling

eg 3 eg3@o365.fullys.xyz AllowCalling


  • Add a test PSTN Usage and voice route for all.

Set-CsOnlinePstnUsage -Identity Global -Usage @{Add="All"}


PS C:\Windows\system32> Get-CsOnlinePstnUsage



Identity : Global

Usage : {All}



New-CsOnlineVoiceRoute -Identity "All" -NumberPattern ".*" -OnlinePstnGatewayList sipconnect.o365.fullys.xyz -Priority 0 -OnlinePstnUsages "All"


PS C:\Windows\system32> Get-CsOnlineVoiceRoute


Identity : All

Priority : 0

Description :

NumberPattern : .*

OnlinePstnUsages : {All}

OnlinePstnGatewayList : {sipconnect.o365.fullys.xyz}

Name : All


Identity : LocalRoute

Priority : 1

Description :

NumberPattern : ^(\+1[0-9]{10})$

OnlinePstnUsages : {}

OnlinePstnGatewayList : {}

Name : LocalRoute


Identity : Eg-Extensions

Priority : 2

Description :

NumberPattern : ^\+1(\d{3})$

OnlinePstnUsages : {All}

OnlinePstnGatewayList : {sipconnect.o365.fullys.xyz}

Name : Poly-Extensions

  • Enable users for Ent Voice and assign a number for PSTN


$userName="eg1@o365.fullys.xyz"<br>Set-CsUser -Identity $userName -EnterpriseVoiceEnabled $true -HostedVoiceMail $true -OnPremLineURI tel:+61755559001


Test-CsEffectiveTenantDialPlan -DialedNumber 1000 -Identity afullagar@o365.fullys.xyz


RunspaceId : 3f82d10f-5ce7-4df2-9638-a7c717e94a62

TranslatedNumber : +61755551000

MatchingRule : Description=;Pattern=^(1\d{3})$;Translation=+6175555$1;Name=Test_Extensions;

IsInternalExtension=False

Edit pjsip.conf (Here is mine - may look weird to some seasoned Asterisk pros but it works) - Not all these settings seem to impact the trunk but you can play and see.

[global]

max_forwards=10

user_agent=FullysPBX

default_from_user=fullyspbx ; When Asterisk generates an outgoing SIP request, the

; From header username will be set to this value if

; there is no better option (such as CallerID or

; endpoint/from_user) to be used

default_realm=fullyspbx ; When Asterisk generates a challenge, the digest realm

; will be set to this value if there is no better option

; (such as auth/realm) to be used.

ignore_uri_user_options=yes ; Enable/Disable ignoring SIP URI user field options.

; If you have this option enabled and there are semicolons

; in the user field of a SIP URI then the field is truncated

; at the first semicolon. This effectively makes the semicolon

; a non-usable character for PJSIP endpoint names, extensions,

; and AORs. This can be useful for improving compatability with

; an ITSP that likes to use user options for whatever reason.

; Example:

; URI: "sip:1235557890;phone-context=national@x.x.x.x;user=phone"

; The user field is "1235557890;phone-context=national"

; Which becomes this: "1235557890"

;

; Note: The caller-id and redirecting number strings obtained

; from incoming SIP URI user fields are always truncated at the

; first semicolon.



;===============TRANSPORT

[simpletrans]

type=transport

protocol=udp

bind=10.88.10.5


;===============ENDPOINT TEMPLATES


[endpoint-basic](!)

type=endpoint

context=internal

disallow=all

allow=ulaw,alaw

;direct_media=no

;rtp_symmetric=yes

;rewrite_contact=yes

;sdp_owner=- ; String placed as the username portion of an SDP origin o line

; (default: "-")

sdp_session=FullysPBX ; String used for the SDP session s line (default:

; "Asterisk")

[auth-userpass](!)

type=auth

auth_type=userpass


[aor-single-reg](!)

type=aor

max_contacts=10


;===============EXTENSION 1000


[1000](endpoint-basic)

auth=auth1000

aors=1000


[auth1000](auth-userpass)

password=blahblah

username=1000


[1000](aor-single-reg)


;===============EXTENSION 1001


[1001](endpoint-basic)

auth=auth1001

aors=1001


[auth1001](auth-userpass)

password=blahblah

username=1001


[1001](aor-single-reg)


;===============EXTENSION 1002


[1002](endpoint-basic)

auth=auth1002

aors=1002


[auth1002](auth-userpass)

password=blahblah

username=1002


[1002](aor-single-reg)

;

;===============EXTENSION 1003


[1003](endpoint-basic)

auth=auth1003

aors=1003


[auth1003](auth-userpass)

password=blahblah

username=1003


[1003](aor-single-reg)

;


;===============EXTENSION 1005

;

[1005](endpoint-basic)

auth=auth1005

aors=1005

;

[auth1005](auth-userpass)

password=blahblah

username=1005

;

[1005](aor-single-reg)

;


;===============EXTENSION 1010

;

[1010](endpoint-basic)

auth=auth1010

aors=1010

;

[auth1010](auth-userpass)

password=blahblah

username=1010

;

[1010](aor-single-reg)

;

;

;

[transporttls]

type=transport

protocol=tls

bind=0.0.0.0:5061

cert_file=/etc/asterisk/ssl/cert2.pem

priv_key_file=/etc/asterisk/ssl/privkey2.pem

cipher=ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-RSA-AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-RSA-AES128-SHA,AES256-SHA,AES128-SHA

;method=tlsv1.1

method=sslv23

local_net=10.88.10.0/24,192.168.90.0/24,192.168.74.0/24,192.168.99.0/24,192.168.75.0/24

external_media_address=sipconnect.o365.fullys.xyz

external_signaling_address=sipconnect.o365.fullys.xyz

;qualify=no

;qualify_frequency=60

;contact=sip:sip.pstnhub.microsoft.com

;qualify_timeout=5.0 ; Qualify timeout in fractional seconds (default: "3.0")

;authenticate_qualify=no



;

[msteams_trunk_from_teams]

type=endpoint

transport=transporttls

disallow=all

;allow=silk16,silk8,ulaw,alaw

allow=ulaw,alaw

aors=aor_msteams_trunk_from_teams

media_encryption=sdes

from_domain=sipconnect.o365.fullys.xyz

;sdp_owner=- ; String placed as the username portion of an SDP origin o line

; (default: "-")

sdp_session=FullysPBX ; String used for the SDP session s line (default:

; "Asterisk")

allow_transfer=yes ;testing if it helps teams

[aor_msteams_trunk_from_teams]

type = aor

qualify_frequency=60

contact=sip:sip.pstnhub.microsoft.com

;qualify_timeout=5.0 ; Qualify timeout in fractional seconds (default: "3.0")

;authenticate_qualify=no


[msteams_trunk]

type=endpoint

transport=transporttls

context=msteams

disallow=all

;allow=silk16,silk8,ulaw,alaw

allow=ulaw,alaw

media_encryption=sdes

;qualify=yes

send_pai=no

rewrite_contact=no

;sdp_owner=- ; String placed as the username portion of an SDP origin o line

; (default: "-")

sdp_session=FullysPBX ; String used for the SDP session s line (default:

; "Asterisk")

allow_transfer=yes ;testing if it helps teams


[ident_msteams_trunk]

type=identify

endpoint=msteams_trunk

match=sip-all.pstnhub.microsoft.com


Edit /etc/asterisk/extensions.conf

cat /etc/asterisk/extensions.conf

; extensions.conf - the Asterisk dial plan

;

; Static extension configuration file, used by

; the pbx_config module. This is where you configure all your

; inbound and outbound calls in Asterisk.

;

; This configuration file is reloaded

; - With the "dialplan reload" command in the CLI

; - With the "reload" command (that reloads everything) in the CLI


;

; The "General" category is for certain variables.

;

[general]

;

; If static is set to no, or omitted, then the pbx_config will rewrite

; this file when extensions are modified. Remember that all comments

; made in the file will be lost when that happens.

;

; XXX Not yet implemented XXX

;

static=yes

;

; if static=yes and writeprotect=no, you can save dialplan by

; CLI command "dialplan save" too

;

writeprotect=no

;

; If autofallthrough is set, then if an extension runs out of

; things to do, it will terminate the call with BUSY, CONGESTION

; or HANGUP depending on Asterisk's best guess. This is the default.

;

; If autofallthrough is not set, then if an extension runs out of

; things to do, Asterisk will wait for a new extension to be dialed

; (this is the original behavior of Asterisk 1.0 and earlier).

;

;autofallthrough=no

;

;

;

[internal]

;;;call forking inbound numbers to D230 and MSTEams 9000

;;;exten => 1000,1,Dial(PJSIP/1000&PJSIP/msteams_trunk/sip:+61755559000@sip.pstnhub.microsoft.com:5061)

;Non-Call Forking when testing

exten => 1000,1,Dial(PJSIP/1000)

exten => 1001,1,Dial(PJSIP/1001)

exten => 1003,1,Dial(PJSIP/1003)

exten => 1005,1,Dial(PJSIP/1005)

exten => 1010,1,Dial(PJSIP/1010)

;

exten => 9000,1,Goto(internal,+61755559000,1)

exten => 9001,1,Goto(internal,+61755559001,1)

exten => 9002,1,Goto(internal,+61755559002,1)

exten => 9003,1,Goto(internal,+61755559003,1)

exten => 9005,1,Goto(internal,+61755559005,1)


exten => +61755559000,1,Dial(PJSIP/msteams_trunk/sip:${EXTEN:1}@sip.pstnhub.microsoft.com:5061)

exten => +61755559001,1,Dial(PJSIP/msteams_trunk/sip:${EXTEN:1}@sip.pstnhub.microsoft.com:5061)

exten => +61755559002,1,Dial(PJSIP/msteams_trunk/sip:${EXTEN:1}@sip.pstnhub.microsoft.com:5061)

exten => +61755559003,1,Dial(PJSIP/msteams_trunk/sip:${EXTEN:1}@sip.pstnhub.microsoft.com:5061)

exten => +61755559004,1,Dial(PJSIP/msteams_trunk/sip:${EXTEN:1}@sip.pstnhub.microsoft.com:5061)

exten => +61755559005,1,Dial(PJSIP/msteams_trunk/sip:${EXTEN:1}@sip.pstnhub.microsoft.com:5061)

;

;To allow Remote Attended Transfers

exten => external_replaces,1,NoOp()

exten => s,1,Dial(PJSIP/msteams_trunk/${SIPREFERTOHDR})

;

[msteams]

; to react on incoming SIP Options Packates with an "200 OK"

; Without this MSTeams portal shows SIP OPTIONS Errors

exten => msteams_trunk_from_teams,1,HangUp()

;

exten => +1000,1,Goto(internal,1000,1)

exten => +1001,1,Goto(internal,1001,1)

exten => +611001,1,Goto(internal,1001,1)

;from MS Teams dialing 1000 translated by teams to +617.....

exten => +61755551000,1,Goto(internal,1000,1)

exten => +61755551001,1,Goto(internal,1001,1)

exten => +61755551003,1,Goto(internal,1003,1)

exten => +1003,1,Goto(internal,1003,1)

exten => +1005,1,Goto(internal,1005,1)

exten => +1010,1,Goto(internal,1010,1)

;To allow Remote Attended Transfers - Replace REFER-TO for teams

exten => external_replaces,1,NoOp()

exten => s,1,Dial(PJSIP/msteams_trunk/${SIPREFERTOHDR})


Note: No NAT in between Asterisk and Poly D230 sip handset. Asterisk on Raspberry pi4 (on the NBN Gateway to internet). Using Nftables as much easier so reach out if stuck on that front.

Debug commands

sudo asterisk -vvvvvr

pjsip set history on

core set debug 5

core set verbose 5

pjsip set logger on

pjsip show history

pjsip show history entry 56

e.g.

rp-fw-01*CLI> pjsip show history

No. Timestamp (Dir) Address SIP Message

===== ========== ============================== ===================================

00000 1584889159 * ==> 52.114.14.70:5061 OPTIONS sip:sip.pstnhub.microsoft.com SIP/2.0

00001 1584889160 * <== 52.114.14.70:5061 SIP/2.0 200 OK 00002 1584889160 * <== 52.114.14.70:7177 OPTIONS sip:msteams_trunk_from_teams@sipconnect.o365.fullys.xyz:5061;transport=TLS SIP/2.0 00003 1584889160 * ==> 52.114.14.70:7177 SIP/2.0 200 OK

00004 1584889173 * <== 192.168.90.128:5060 INVITE sip:9000@10.88.10.5:5060 SIP/2.0 00005 1584889173 * ==> 192.168.90.128:5060 SIP/2.0 401 Unauthorized

00006 1584889173 * <== 192.168.90.128:5060 ACK sip:9000@10.88.10.5:5060 SIP/2.0 00007 1584889173 * <== 192.168.90.128:5060 INVITE sip:9000@10.88.10.5:5060 SIP/2.0 00008 1584889173 * ==> 192.168.90.128:5060 SIP/2.0 100 Trying

00009 1584889173 * ==> 52.114.14.70:5061 INVITE sip:61755559000@sip.pstnhub.microsoft.com:5061 SIP/2.0

00010 1584889173 * <== 52.114.14.70:5061 SIP/2.0 100 Trying 00011 1584889173 * <== 52.114.14.70:5061 SIP/2.0 400 Bad Request 00012 1584889173 * ==> 52.114.14.70:5061 ACK sip:61755559000@sip.pstnhub.microsoft.com:5061 SIP/2.0

00013 1584889173 * ==> 192.168.90.128:5060 SIP/2.0 503 Service Unavailable

00014 1584889173 * <== 192.168.90.128:5060 ACK sip:9000@10.88.10.5:5060 SIP/2.0 00015 1584889216 * <== 192.168.90.128:5060 INVITE sip:9000@10.88.10.5:5060 SIP/2.0 00016 1584889216 * ==> 192.168.90.128:5060 SIP/2.0 401 Unauthorized

00017 1584889216 * <== 192.168.90.128:5060 ACK sip:9000@10.88.10.5:5060 SIP/2.0 00018 1584889216 * <== 192.168.90.128:5060 INVITE sip:9000@10.88.10.5:5060 SIP/2.0 00019 1584889216 * ==> 192.168.90.128:5060 SIP/2.0 100 Trying

00020 1584889216 * ==> 52.114.14.70:5061 INVITE sip:61755559000@sip.pstnhub.microsoft.com:5061 SIP/2.0

00021 1584889216 * <== 52.114.14.70:5061 SIP/2.0 100 Trying 00022 1584889216 * <== 52.114.14.70:5061 SIP/2.0 400 Bad Request 00023 1584889216 * ==> 52.114.14.70:5061 ACK sip:61755559000@sip.pstnhub.microsoft.com:5061 SIP/2.0

00024 1584889216 * ==> 192.168.90.128:5060 SIP/2.0 503 Service Unavailable

00025 1584889216 * <== 192.168.90.128:5060 ACK sip:9000@10.88.10.5:5060 SIP/2.0 00026 1584889219 * ==> 52.114.14.70:5061 OPTIONS sip:sip.pstnhub.microsoft.com SIP/2.0

00027 1584889219 * <== 52.114.14.70:5061 SIP/2.0 200 OK 00028 1584889219 * <== 52.114.14.70:7177 OPTIONS sip:msteams_trunk_from_teams@sipconnect.o365.fullys.xyz:5061;transport=TLS SIP/2.0 00029 1584889219 * ==> 52.114.14.70:7177 SIP/2.0 200 OK

00030 1584889279 * ==> 52.114.14.70:5061 OPTIONS sip:sip.pstnhub.microsoft.com SIP/2.0

00031 1584889279 * <== 52.114.14.70:5061 SIP/2.0 200 OK 00032 1584889279 * <== 52.114.14.70:7177 OPTIONS sip:msteams_trunk_from_teams@sipconnect.o365.fullys.xyz:5061;transport=TLS SIP/2.0 00033 1584889279 * ==> 52.114.14.70:7177 SIP/2.0 200 OK

00034 1584889334 * <== 192.168.90.128:5060 INVITE sip:9000@10.88.10.5:5060 SIP/2.0 00035 1584889334 * ==> 192.168.90.128:5060 SIP/2.0 401 Unauthorized

00036 1584889334 * <== 192.168.90.128:5060 ACK sip:9000@10.88.10.5:5060 SIP/2.0 00037 1584889334 * <== 192.168.90.128:5060 INVITE sip:9000@10.88.10.5:5060 SIP/2.0 00038 1584889334 * ==> 192.168.90.128:5060 SIP/2.0 100 Trying

00039 1584889334 * ==> 52.114.14.70:5061 INVITE sip:61755559000@sip.pstnhub.microsoft.com:5061 SIP/2.0

00040 1584889335 * <== 52.114.14.70:5061 SIP/2.0 100 Trying 00041 1584889335 * <== 52.114.14.70:5061 SIP/2.0 180 Ringing 00042 1584889335 * ==> 192.168.90.128:5060 SIP/2.0 180 Ringing

00043 1584889336 * <== 52.114.14.70:5061 SIP/2.0 180 Ringing 00044 1584889337 * <== 52.114.14.70:5061 SIP/2.0 180 Ringing 00045 1584889337 * <== 52.114.14.70:5061 SIP/2.0 183 Session Progress 00046 1584889337 * ==> 192.168.90.128:5060 SIP/2.0 183 Session Progress

00047 1584889337 * ==> 192.168.90.128:5060 SIP/2.0 183 Session Progress

00048 1584889339 * ==> 52.114.14.70:5061 OPTIONS sip:sip.pstnhub.microsoft.com SIP/2.0

00049 1584889339 * <== 52.114.14.70:5061 SIP/2.0 200 OK 00050 1584889339 * <== 52.114.14.70:7177 OPTIONS sip:msteams_trunk_from_teams@sipconnect.o365.fullys.xyz:5061;transport=TLS SIP/2.0 00051 1584889339 * ==> 52.114.14.70:7177 SIP/2.0 200 OK

00052 1584889344 * <== 52.114.14.70:5061 SIP/2.0 200 OK 00053 1584889344 * ==> 192.168.90.128:5060 SIP/2.0 200 OK

00054 1584889344 * <== 192.168.90.128:5060 ACK sip:10.88.10.5:5060 SIP/2.0 00055 1584889344 * ==> 52.114.14.70:5061 ACK sip:api-du-a-asse.pstnhub.microsoft.com:443;x-i=71112d70-d44a-41d0-82bb-f147f2e7c908;x-c=���d�����q� SIP/2.0

00056 1584889348 * <== 192.168.90.128:5060 BYE sip:10.88.10.5:5060 SIP/2.0 00057 1584889348 * ==> 192.168.90.128:5060 SIP/2.0 200 OK

00058 1584889348 * ==> 52.114.14.70:5061 BYE sip:api-du-a-asse.pstnhub.microsoft.com:443;x-i=71112d70-d44a-41d0-82bb-f147f2e7c908;x-c=���d�����q� SIP/2.0

rp-fw-01*CLI> pjsip show history entry 56

<--- History Entry 56 Received from 192.168.90.128:5060 at 1584889348 --->

BYE sip:10.88.10.5:5060 SIP/2.0

Call-ID: 3959642ea2bf8d91@192.168.90.128

Content-Length: 0

CSeq: 8003 BYE

From: "Andrew Fullagar D230" ;tag=SP15aff86857ffdbea3

Max-Forwards: 70

To: ;tag=cb72d178-4b1f-4d0b-8571-15b0782ff2e7

Via: SIP/2.0/UDP 192.168.90.128:5060;rport=5060;received=192.168.90.128;branch=z9hG4bK-cd7b40a5

Authorization: Digest username="1000", realm="fullyspbx", nonce="1584889334/1a8744b5c6d06caebacf32dcd6964663", uri="sip:10.88.10.5:5060", response="308222908856c2b7be89f5270e3304ef", algorithm=MD5, cnonce="f3ff998052f955b6", opaque="436369ba26a8f8c5", qop=auth, nc=00000002

User-Agent: OBIHAI/VVXD230-7.0.2.7359

X-RTP-Stat: PS=548,OS=94256,PR=241,OR=41452,PL=0,JI=4,DU=4,EN=G711U,DE=G711U

Content-Length: 0