365 UC
Published in

365 UC

How to configure Microsoft Teams Direct Routing with Local Media Optimization using Ribbon SBCs

I’ve recently been working on reconfiguring my existing Ribbon SBCs to support Microsoft Teams. There are a few decent blogs around showing how to configure Direct Routing (and this is easy enough), but not many about the newest feature, Local Media Optimization (LMO). If I’m honest, this has taken me quite a while to get working so I thought it was worth sharing my finished config. There are a few subtle differences and a couple of gotchas I have experienced that this blog might help you with. Benefit from my hours, days and weeks of tinkering.

In my opinion, the documentation from Ribbon is poo. It’s fine if you have a greenfield site and can blat a load of config onto your SBC using the Easy Setup wizard, but if your SBC is in production and you’re not happy doing that, you will probably want to configure this manually. This, for me, is where the documentation falls down.

Local Media Optimization is a massive improvement over the first iteration of media bypass for Direct Routing. In the original version, whilst the media could bypass the Teams service it would have to hit the external side of the SBC. This worked but meant firewall config was potentially more complex and meant the media traffic would often needlessly hairpin the firewall.

Local Media Optimization offers two key benefits:

  1. It means you can control how the media flows between Teams clients and your SBCs. It can help keep media within your corporate network, whether this is for security, efficiency or quality reasons.
External Client using LMO
Internal Client using LMO

First off, I want to point out that in my environment, we only allow Microsoft to connect to our SBCs from the Internet. So, any client on the Internet will have to send media via the Teams service before it hits the SBC. This isn’t a massive deal in my opinion since it simplifies config and chances are that your datacentre ISP peers with Microsoft anyway.

So, for an Internet-based (external) client the media would go as follows:

Client > Teams service > Proxy SBC > Downstream SBC > PSTN Carrier

For an internal client the media would go as follows:

Client > Downstream SBC > PSTN Carrier

Note: the signaling traffic always takes the proxy SBC, downstream SBC path.

My proxy SBCs have two interfaces. The public interface has a public IP address assigned. I don’t have any NAT. I’m lucky enough to be able to get public IPs assigned directly to my SBCs, albeit behind corporate firewalls. The default gateway is configured to route traffic to the Internet. The second interface is on an internal subnet. I have static routes configured to point RFC 1918 traffic (,, through the internal interface.

Microsoft Teams Config

Voice Config

There is loads written about voice config for Microsoft Teams and nothing of importance that I can really add to it to be honest. Do yourself a favour, head over to ucdialplans.com, drop Ken Lasko a few quid/dollars and use his scripts. Even if you just pick out all the translations, usages and routes, it’s well worth it.

PSTN Gateway Config

Key takeaway from my testing: if you’re gonna use LMO, both the proxy SBC and the downstream SBC have to have media bypass enabled in the Teams config. If the downstream SBC does not have media bypass enabled, the media will always route via Teams. Seems obvious now, but that caused me a couple of days of confusion.

I have used a bypass mode of Always. You can use OnlyForLocalUsers if you want to restrict the media bypass to people in the same site as the gateway. You might want to do this if your internal network isn’t completely routable or if inter-site links are poor. Microsoft recommend the Always setting.

Get-CsOnlinePSTNGateway outputs are shown below for both the proxy and downstream SBCs.

Proxy SBC CsOnlinePSTNGateway Config
Downstream SBC CsOnlinePSTNGateway Config

Network Topology Config for LMO

In order for a client to be considered internal, there needs to be a match for the external/trusted IP that Microsoft sees the call come from and the subnet that the client is on.

You can get some help with managing your network topology here: https://docs.microsoft.com/en-us/microsoftteams/manage-your-network-topology

I have also written a short troubleshooting piece so you can confirm where your client thinks it is here: https://medium.com/365uc/troubleshooting-network-topology-in-microsoft-teams-direct-routing-66266d9f39ba

I would recommend you approach this part at the end of your configuration/testing. Get the calling without the local media optimization first, then put your network topology config in and test the LMO part after that. I think it’s easier that way round.

Same with Location Based Routing. Do that at the very end once you’ve confirmed everything else is working.


There are quite a few prerequisites, a number of which I will mention below. It’s worth reading through this official Microsoft Direct Routing documentation first.

Ribbon SBC Firmware

Local Media Optimization support was added to Ribbon SBC firmware in these versions.

  • SBC SWe Lite: Release 8.1.5 Build 239

Domain Name & FQDN

This should be obvious, but you will need to use a public domain name that you own. You will need an FQDN that you can get a cert and update DNS for. You can’t use your whatever.onmicrosoft.com tenant domain.

SBC Public Certificate

You will need a public certificate for both the proxy and downstream SBC from a trusted CA. You can use SANs so get a single cert you can use on both the proxy and downstream SBCs. More info about the trusted CAs can be found here: https://docs.microsoft.com/en-us/microsoftteams/direct-routing-plan#public-trusted-certificate-for-the-sbc

Firewall Rules

As I said previously, we only allow Microsoft to connect to our SBC from the Internet. Medium doesn’t allow me to embed tables which is really, really annoying below this is what I have allowed. It’s wise to keep an eye on Microsoft documentation as they often add new IPs or ranges for signaling and media.

It’s worth noting that your SBC ports may differ. You can get these from Media > Media System Configuration

Media System Configuration

Note: You will not see the ICE ports until you have enabled ICE on at least one signaling group.

Firewall Rules

Ribbon SBC Config

Okay, this is going to be quite long as there is a fair bit of configuration and I will be doing lots of screenshots. The reason I’m doing this is that there are A LOT of settings on a Ribbon SBC. If I’m honest I don’t know what half of them mean (I’m not a telephony engineer) so I’m gonna do what I would have appreciated and provide a lot of screenshots of my working config. Maybe it doesn’t work for you as your environment doesn't exactly match mine, but hopefully there will be enough to get you most of the way. I’m gonna blur out names and IP addresses in most places, but I will use names like ‘proxy SBC’ and ‘downstream SBC’ and ‘internal IP’ and ‘public IP’ etc. to make it as easy as possible. Anyway, let’s get down to it.

Common Config on Proxy SBC & Downstream SBC

Trusted Root Cert

Import the Baltimore CyberTrust root certificate into the Trusted Root Certificate store. This can be found under Security > SBC Certificates > Trusted CA Certificates. You can download the cert from here: https://cacert.omniroot.com/bc2025.crt

I know you don’t technically need this on the downstream SBC, but what’s the damage, eh?

TLS Profile

Under Security > TLS Profiles create a new TLS Profile called Microsoft Teams

  • TLS Protocol: TLS 1.2 Only
TLS Profile

SIP Profile

Under SIP > SIP Profiles create a new SIP Profile called Microsoft Teams

At this point I would strongly recommend that if you can, make the host name and domain name in System > Node-Level Settings equal the public FQDN of your SBC. This will save you having to specify a static FQDN in the SIP Profile. In fact, I couldn’t get LMO working without making the host name and domain name on the proxy SBC equal the public FQDN. I’m not sure if this is due to a bug in Ribbon SBC firmware or if it’s just an anomaly in my environment, but merely specifying a static FQDN didn’t work with LMO. Bottom line, the host name and domain name of the proxy SBC must equal the public FQDN, but the downstream SBC can be statically specified.

If your host name and domain name in System > Node-Level Settings equals the public FQDN of your SBC you can use the following:

  • FQDN in From Header: SBC Edge FQDN

If this is the downstream SBC and your hostname and domain name differs from the public FQDN, you can use the following:

  • FQDN in From Header: Static

Media Crypto (SDES-SRTP) Profile

Under Media > SDES-SRTP Profiles, create a new SDES-SRTP Profile called Microsoft Teams

  • Operation Option: Required

Media List

Under Media > Media List, create a new Media List called Microsoft Teams

  • Media Profiles List: Default G711A and Default G711u

Proxy SBC

SIP Server Tables

Under SIP > SIP Server Tables, create two new SIP Server Tables:

  1. Microsoft Teams — The Microsoft PSTN Infrastructure

Microsoft Teams

  • sip.pstnhub.microsoft.com (priority 1)

Microsoft Teams Downstream

  • Host FQDN/IP: downstream.domain.com

Transformation Tables

Now, this is where my amateurishness may show through. There are almost definitely going to be better and more intelligent ways of routing calls to/from Teams and the Downstream SBC but I have done this with transformation tables.

I have three transformation tables under Call Routing > Transformation:

  1. To Microsoft Teams — so I can push numbers to Teams

If I’m honest, at this stage they will probably be empty until you have realised what test numbers you’re going to use. Once you have, you just need to add the transformations to match those incoming/outgoing numbers. We’ll use these transformation tables in the call routing tables a bit later.

The reason we need two transformation tables for numbers going to Teams is because we need two entries in the call routing tables with differing audio stream modes. More on that a bit later.

Signaling Groups

The proxy SBC will have two signaling groups:

  1. Microsoft Teams — A signaling group that connects to Microsoft Teams

Microsoft Teams Signaling Group

  • Call Routing Table: Outbound

Microsoft Teams Downstream Signaling Group

  • Call Routing Table: Inbound

Call Routing Tables

Again, call routing is probably where my weaknesses show through. Traditionally I have used Inbound and Outbound call routing tables when routing calls. Probably not the best nomenclature nowadays when routing between a cloud service and a PSTN provider, but I’m gonna stick with it for the moment. In my case:

  • Inbound = Calls from PSTN provider (via downstream SBC) to PBX (Teams)

I will create three new call routing entries under Call Routing > Call Routing Table:

  1. Inbound > To Microsoft Teams

The reason I have two routing entries for calls going to Microsoft Teams, is that if I’m using LMO with the proxy SBC (without a downstream SBC) I need the audio stream mode to be ‘DSP’. If I’m using a downstream SBC I need the audio stream mode from the proxy SBC to be ‘Direct preferred over DSP’.

Inbound > To Microsoft Teams

  • Number/Name Transformation Table: To Microsoft Teams (created previously)

Inbound > To Microsoft Teams (From Downstream)

  • Number/Name Transformation Table: To Microsoft Teams (From Downstream) (created previously)

Outbound > To Downstream SBC

  • Number/Name Transformation Table: To Downstream SBC (created previously)

Downstream SBC

SIP Server Table

Under SIP > SIP Server Tables, create a new SIP Server Table called Microsoft Teams Proxy

  • HOST FQDN/IP: proxy.domain.com

Transformation Table

Under Call Routing > Transformation, create a new transformation table called To Microsoft Teams

Again, this table will probably be empty until you know what test numbers you’re using and you’ll then add transformations to capture the calling and called numbers to route it to Teams.

Signaling Group

Under Signaling Groups, create a new SIP signaling group called Microsoft Teams Proxy

  • Call Routing Table = Outbound

Call Routing Table

I will create a new call routing entry in the Inbound routing table under Call Routing > Call Routing Table called To Microsoft Teams

  • Number/Name Transformation Table: To Microsoft Teams (created previously)

Deployment Plan

As I’ve mentioned previously, keep the network topology config within Microsoft Teams to a minimum until incoming and outgoing calling is working from both an internal client and an Internet-based clients. Once everything is working you can start to put some trusted IPs and network sites/subnets in to enable the local media optimization part of this.

In the section below I will show you a few things to look out for when testing to make sure it’s all working properly.

How does it work?

This next part is by no means a deep-dive into the inner workings of Direct Routing, but in the process of configuring and troubleshooting DR LMO in my environment I have learned a few things that I think are worth understanding.

There are three headers added to the INVITE which make all this work.

  • X-MS-UserLocation: [external/internal]
    This shows the location of the client as determined by the network topology. Trusted IP and site/subnet must match for this to be flagged as internal. Remember, if media bypass is not enabled for the downstream SBC, this will never say internal.

External/Internet-based client or internal client with no matching network topology config

In this example, the client is determined as being external. Note there is no X-MS-UserSite header since it does not match any network topology config. The media path will hit the proxy SBC first and then proxy to the downstream SBC.

For an internal client with no matching network sites/subnets, this could be a sub-optimal configuration since the media would be going out the network and then coming straight back in to hit the SBC.

External client proxying to downstream SBC

Internal client (with configured network topology)

In this example, the trusted IP and site/subnet has matched the network topology and the user location is detected as internal. Note how the media path only lists the downstream SBC.

Internal client with media going directly to the downstream SBC

So, what does a successful LMO call look like on the SBCs?

Proxy SBC

If you look at the proxy SBC when the call is connected you should see a DA which stands for Direct Audio.

Connected: Direct Audio

Downstream SBC

On the downstream you should see a B to indicate that the call is connected in Media Bypass. The client is connecting directly to the downstream SBC for its media flow.


That’s it!

I really hope this helps someone out. If it does, please let me know. Likewise, if I’ve done anything stupidly wrong, or if there is an easier/better way to do something, please let me know!




Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Matt Ellis

Unified Communications guy, Pompey fan, burger eater, coffee drinker...