Advanced Topics

WebRTC Bundling

LiveSwitch version 1.5.0 introduces support for WebRTC Bundling as per https://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-54. Implementation covers SDP negotiation, stream multiplexing, and demultiplexing.

What is Bundling?

Bundling minimizes resource usage during connection setup, and during the call itself, because it uses a single socket to transmit data payloads rather than using one socket per stream. Specifically, core transports do not need to open sockets, forward additional ICE candidates to peers, conduct additional ICE connectivity checks, and conduct an additional DTLS (encryption) handshake. 

What is BundlePolicy?

Bundling works out of the box and its behavior is governed by the BundlePolicy property of the Connection object. BundlePolicy values are:

  • Disabled - bundling is disabled. Each stream uses its own transport stack: socket, ICE connectivity checks, DTLS encryption;
  • MaxCompatibility - bundling can be negotiated if the other peer supports it. Otherwise, bundling is disabled. When a peer is in offering mode and its BundlePolicy is set to MaxCompatibility, it will build its internal architecture in such a way that it supports non-bundling connections, will start gathering ICE candidates and create an offer. When an answer arrives, SDP is examined to identify bundling support by peers. If bundling is supported, the extra transports will be shut down. Otherwise, the connection will be established without bundling. Setting BundlePolicy to MaxCompatibility allows flexibility for negotiating bundling with other peers and should be used when the application cannot be sure whether the peer supports bundling. It comes at the expense of building transports that may be declared redundant only to be shut down if bundling is supported. If there is absolute certainty that bundling is supported by a remote peer prior to negotiation, use MaxBundle;
  • MaxBundle - bundling is required. If the remote peer does not support bundling, then the connection will not be established. Use MaxBundle when there is an absolute certainty that bundling is supported by all peers to avoid resource waste at connection establishment;

Rollout Procedure and Policy Defaults

There is a minor tradeoff between resource utilization efficiency and peer compatibility where bundling is concerned. Nevertheless, the default settings on a Connection's BundlePolicy allow for the most efficient utilization of resources and compatibility, allowing bundling to work when needed out of the box without developer intervention.

  • Server:
    • By default, LiveSwitch Sever connections' bundling policy is set to MaxCompatibility to allow for compatibility with software that does not support bundling (e.g. web-wrapper for Microsoft Edge, SIP). Since LiveSwitch Server is always in the connection answering role, there is no downside to setting the BundlePolicy to MaxCompatibility;
  • Client:
    • LiveSwitch clients for SFU and MCU mode connections (both native and browser wrappers with the exception of Microsoft Edge) have their connections' BundlePolicy set to MaxBundle. This provides greater efficiency of resource usage at connection startup because there is a guarantee that LiveSwitch Media Servers (version 1.5 and above) support bundling by default. If this is not the case (e.g. because bundling support has been disabled), please set BundlePolicy to MaxCompatibility;
    • LiveSwitch clients for peer-to-peer connections (both native and browser wrappers with the exception of Microsoft Edge) have their connection BundlePolicy set to MaxCompatibility. This allows for wider interop with third-party software (including the web wrapper for Microsoft Edge). If there is an absolute certainty that the peer-to-peer connection peers also support bundling, you can set BundlePolicy to MaxBundle;
    • LiveSwitch's Microsoft Edge web-wrapper does not support Bundling and the BundlePolicy setting on the connection is ignored. See Browser Interop for more information.

Unimplemented Features

WebRTC bundling features are implemented as per working draft v54 of the specification linked above with the following exceptions:

  • Balanced BundlePolicy is not supported. With this BundlePolicy the connection respects remote offers with multiple bundle groups per connection (ex: audio bundle group or video bundle group). Currently, the LiveSwitch API does not support multiple streams of the same type per connection. If this changes in the future then adding support for this policy may be re-evaluated;
  • "Bundle-only" SDP attribute is not sent for offers with MaxBundle bundle policy set for interop reasons with Mozilla Firefox. Once support is available, this will be introduced.

Browser Interop

LiveSwitch's WebRTC Bundling has been tested with Mozilla Firefox, Google Chrome, and Safari for interoperability. These browsers support MaxCompatibility and MaxBundle bundle policies, but they do not support the Disabled bundle policy. At this point, LiveSwitch's Microsoft Edge web-wrapper does not support bundling and will ignore BundlePolicy setting. If connectivity with Microsoft Edge is required, please keep Connection.BundlePolicy at MaxCompatibility for LiveSwitch Server and clients (if peer-to-peer connectivity is required).

Security

WebRTC in general is very secure and LiveSwitch follows the WebRTC spec. Here we discuss implementation specifics of LiveSwitch's security measures, as well as covering basic network related configuration.

Network Configuration and Required Ports

  • STUN servers provide a WAN address discovery service and listen on port 3478 by default, but any port can be used. Firewall rules must be added to allow inbound UDP traffic on the configured port.
  • TURN servers extend STUN to support relaying around restrictive firewalls. Like STUN, they listen on port 3478 by default, but any port can be used. Firewall rules must be added to allow inbound UDP and/or TCP traffic on the configured port.
  • TURNS servers extend TURN by using TLS (SSL) to secure the underlying TCP connection. Since application data is already encrypted, this simply adds a layer of security on the TURN headers, but is useful to traverse firewalls that only allow TLS/SSL traffic. TURNS servers listen on port 5349 by default, but any port can be used. Port 443 is recommended since it is generally allowed by client networks. Firewall rules must be added to allow inbound TCP traffic on the configured port.
  • Clients are the originator of requests and as such, they require no special firewall rules, provided outbound traffic is allowed.
  • Media Server clustering requires TCP traffic on port 8445. If 8445 is unavailable the next port will be tried incrementally until one is available (i.e. 8446, 8447, 8448, 8449, etc).
  • Media Server client connections require inbound UDP traffic on ports 49152-65535 on public interfaces.
  • The SIP Connector requires TCP/UDP traffic on port 5060 on public interfaces.

Even the most restrictive corporate networks generally allow HTTP traffic on port 80 and TLS/SSL traffic on port 443 for HTTPS. For this reason, we recommend running a TURN server that listens on port 443 for TURNS.

Encryption

All connections are secured using DTLS. Secure X.509 certificates are automatically generated for each connection using ECDSA (default, P-256 curve) or RSA (2048-bit) signing, but custom certificates and key-pairs can be provided. Certificate fingerprints (used to verify the peer during key exchange) are exchanged via signaling in the SDP blobs, so it is imperative to ensure security at the signaling layer.

Connection Security 

  • DTLS 1.2 with support for cipher suites that support perfect forward secrecy (PFS). See below for the specific cipher suites allowed by the key exchange.
  • Certificates use ECDSA signing with keys generated for the P-256 curve.
  • Certificates can use RSA signing with variable key length (default is 2048 bits).
  • Data is encrypted using AES 128-bit encryption with 80-bit message authentication per SRTP standards.
  • Certificate fingerprints used in the initial session setup use SHA2.
  • Signaling tokens used to authentication gateway requests use HMAC-SHA2.
  • Message integrity for STUN payloads used in NAT traversal use HMAC-SHA1 as mandated by https://tools.ietf.org/html/rfc5389#section-15.4.
  • Message integrity for SRTP payloads used in media flow use HMAC-SHA1 as mandated by https://tools.ietf.org/html/rfc3711#section-4.2.1.
  • LiveSwitch uses OpenSSL 1.0.2 LTS on the server for cryptographic key exchange which does not negotiate Encrypt-then-MAC by default. As OpenSSL 1.0.2 is nearing its end-of-life, we will be updating to 1.1.1 LTS before the end of 2019. OpenSSL 1.1.1 LTS negotiates Encrypt-then-MAC by default.

Cipher Suites Used for DTLS

  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
  • TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
  • TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
  • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
  • TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
  • TLS_RSA_WITH_AES_128_CBC_SHA,
  • TLS_RSA_WITH_AES_128_GCM_SHA256,
  • TLS_RSA_WITH_AES_128_CBC_SHA256

Signaling

LiveSwitch connections are extremely secure once established, provided the signaling layer is also secure. Since the certificate fingerprints must be signaled in the SDP offer/answer blobs, a weakness in the signaling layer allows the possibility of a man-in-the-middle attack. For LiveSwitch, it is the Gateway that provides the signaling layer, and when installed on a server with a trusted SSL certificate, LiveSwitch's Gateway uses TLS encryption (HTTPS/WSS). The LiveSwitch Gateway also allows you to provide rate limiting, blacklisting, and whitelisting of client connections through a token-based registration model. Managing these registration tokens is an application-level security concern. We recommend an authenticated API endpoint on your own authentication server to generate and deliver registration tokens to the client securely at runtime as requested. For DDoS protection, it's recommended to use a third-party service like CloudFlare.

Again, all signaling connections must be secured via TLS to make security guarantees, so your LiveSwitch Gateway must be running on a server with a trusted SSL certificate, and all traffic to the Gateway must be configured to use secure connections.

See below for the client signaling flow diagram:


TURN Traffic Concerns and Secure TURN (TURNS)

The percentage of time that WebRTC traffic is established over relay (TURN) varies greatly depending on the network environment. For example, TURN may not be required in many home networks, but in more restrictive corporate networks, where symmetric NAT is normal, requiring TURN is far more common. In general, we estimate 20%-30% of connections use TURN.

When connecting to a TURN server, clients are authenticated by username and password. Managing these credentials is an application-level security concern. We recommend an authenticated API endpoint on your own authentication server to deliver these credentials to the client securely at runtime as requested.

For added security, you can force all traffic through TURN, which guarantees that clients cannot learn information about each others' private networks (optionally including their WAN IP).

When TURN is in use, all data flows through the TURN server. Before sending data to the TURN server, clients first encrypt it using the keys exchanged via DTLS and then prepend a TURN header with destination address information. The TURN server can only read the header, as it does not have access to the keys necessary to decrypt the payload. TURNS provides an additional level of layer of security on top of this (for TCP only) by securing the TCP stream with TLS. By doing this, the entire stream is encrypted, which includes the TURN headers (and re-encrypts the payload). The TURN server is still not able to decrypt the payload, but by encrypting the entire stream, the headers are now safe from packet sniffers. This allows the traffic to pass through stateful packet inspection in routers that would otherwise block non-HTTPS traffic.

Creating an Authorization Server

Creating registration tokens in your client applications is not advisable. This section will go into further detail about why this is a bad idea and will show you how to securely generate authorization tokens.

Why not generate tokens client side?

To understand why you should not generate these tokens on the client, first consider how the LiveSwitch Gateway validates an authorization request. An authorization request is valid if the token parameters are valid values and the secret key in the token matches the secret key configured on the LiveSwitch Gateway. This means that if the client knows the secret key, that almost any request they make will be valid - there is no way for your application to stop a client from registering or joining a channel.

What this means is that the security of your application depends on keeping the secret key a secret. You can take this concept further by considering that for each token that you want to generate there are two types of data: things the client knows, and things the server knows. It's not always as black and white as that, but for our purposes here it's a useful distinction. Let's apply this concept to the parameters that are required to generate authorization tokens.

Application ID: As a unique identifier for the application, this value will come from the server. It can be hard-coded or, more commonly, the id may depend on a user's profile. Applications are associated with specific feature sets, so you do not want to allow the user to specify this as it can allow them to access features that they are not entitled to.

User ID: This value will also usually come from the server. Before a server issues an authorization token, it generally requires some form of authentication from the user. Because of this, the server knows who the user is already. You do not want to allow a client to specify this value, both because the server already knows what the value should be and because it may allow a client to impersonate another user.

Device ID: This value will generally come from the client. The device id is a unique identifier associated with a particular device. This is used in the case where a user might be connected on multiple devices, ie: through a web browser and a phone.

Roles: Roles will generally come from a combination of both the client or the server. In the case where an application role is associated with a particular application privilege (ie: moderation abilities), then the role will be assigned based by the server based on the user's profile. In the case where a role is not associated with any privileged action, (ie: a presenter in a conference), then client should request this role. Of course, the server should validate all client roles based on their user profile.

Channel Claims: Channel claims usually come from the client, as they represent a claim that a client makes, ie: that they have the right to join the specified channel. The server may also add other channels as required, if there are default channels or specific metadata channels associated with an application.

Shared Secret: This arbitrary string will always come from the server, as mentioned above.

Now that you have a better understanding of the concept of client information and server information, the next step is to set up your server.

Setting Things Up

To set up your auth server, you will need to set up a few things first. Generally, you will already have some sort of server to handle application-specific data such as user profiles. This is a good starting point for your auth server, as the application server will already have access to your user data, which you will need to authorize your client requests.

Generally, you will need to set up at least two additional endpoints on your server - one for generating registration tokens and one for generating channel tokens. This guide will assume a rest-like API, and will refer to these endpoints as "/token/register" and "/token/join"; meaning to register with the server, and to join a channel. In the following sections, some examples of server endpoints are provided using ASP .NET Core for C# and Spring 4 for Java.

Handling Registration Tokens

One of the main roles of an auth server is to authenticate clients and to return registration tokens so that these clients can connect to the LiveSwitch service. Registration tokens are typically generated from a "/token/register" endpoint on a REST-ful web service. In a normal registration request, the application id, user id,and shared secret are provided by the server. The only remaining required parameters are the device id and the client id (because roles and channel claims are optional for a registration).

The following code snippet shows platform-specific examples of retrieving device and client ids and then making an http request to the server for a registration token. The device id and client id are provided via query parameters in a POST request.

var applicationId = "...";
var userId = "...";
var deviceId = Environment.MachineName;

var client = new FM.LiveSwitch.Client("https://liveswitch.server:8443/sync", applicationId, userId, deviceId);

var url = "https://app.server/token/register"
    + "?deviceId=" + client.DeviceId;

var httpClient = new HttpClient();
var response = await httpClient.PostAsync(url);
var token = response.Content;
client.Register(token).Then(...);
String applicationId = "...";
String userId = "...";
String deviceId = Secure.getString(getContext().getContentResolver(), Secure.ANDROID_ID);

fm.liveswitch.Client client = new fm.liveswitch.Client("https://liveswitch.server:8443/sync", applicationId, userId, deviceId);

URL url = new URL("https://app.server/token/register"
    + "?deviceId=" + client.getDeviceId();
    
InputStream inputStream = url.openStream();
BufferedReader buffer = new BufferedReader(new InputStreamReader(input));
String token = buffer.lines.collect(Collectors.joining("\n"));
client.register(token).then(...);
var applicationId = '...';
var userId = '...';
var deviceId = navigator.userAgent;

var client = new fm.liveswitch.Client("https://liveswitch.server:8443/sync", applicationId, userId, deviceId);

var url = 'https://app.server/token/register'
    + '?deviceId=' + client.getDeviceId();

var xhr = new XMLHttpRequest();
xhr.open('POST', url, true)
xhr.onreadystatechange = function() {
    if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
        var token = xhr.response;
        client.register(token).then(...);
    }
}
xhr.send(null);

You will have to configure your application server to receive this request. The application server should then respond by generating a client registration token. You can generate this token using the values the client provides, and values available to the server. Pass all these values to the GenerateClientRegisterToken static method of the FM.LiveSwitch.Token class to return a token. This is demonstrated below.

[Route("[controller]")]
public class TokenController : Controller
{
    private String secret;
    private UserManager<AppUser> userManager;
    
    public TokenController(UserManager<AppUser> userManager)
    {
        this.userManager = userManager;
    }

    [HttpPost("register")]
    public ContentResult Register([FromQuery]string deviceId, [FromQuery]string clientId)
    {
        var user = await this.userManager.GetUserAsync(this.User);
        
        return Content(FM.LiveSwitch.Token.GenerateClientRegisterToken(
            user.ApplicationId,
            user.Id,
            deviceId,
            null,
            null,
            null
            this.secret
        ));
    }
}
@Controller
@RequestMapping("/token")
public class TokenController {
    private String secret;

    @PostMapping
    @ResponseBody
    public String register(
            @RequestParam(value="deviceId", required=true) String deviceId,
            @RequestParam(value="clientId", required=true) String clientId) {
        UserDetails auth = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        
        applicationId = auth.getApplicationName();
        userId = auth.getUsername();
        
        return fm.liveswitch.Token.generateClientRegisterToken(
            applicationId,
            userId,
            deviceId,
            null,
            null,
            null,
            this.secret
        );
    }
}

Note that the details of how these controllers' shared secret property is provided is left up to you. A common way of doing this is through your dependency injection container. Another way of accessing it is to query your global application settings. Whatever way you select, it should not be accessible to any clients.

Handling Channel Tokens

The other main role of an auth server is to return channel tokens so that users who have already registered can join additional channels without needing to re-register. Channel tokens are usually generated from a "/token/join" endpoint on a REST-ful web service.

For a join token, the client must again provide its deviceId and clientId. It must also provide the id of a channel it wishes to join. The code samples below show how to make a REST-ful request with this information embedded in the query string.

var applicationId = "...";
var userId = "...";
var channelId = "...";'
var deviceId = Environment.MachineName;

var client = new FM.LiveSwitch.Client("https://liveswitch.server:8443/sync", applicationId, userId, deviceId);

var url = "https://app.server/token/join"
    + "?deviceId=" + client.DeviceId
    + "&channelId=" + channelId;

var httpClient = new HttpClient();
var response = await httpClient.PostAsync(url);
string token = response.Content;
client.Join(channelId, token).Then(...);
String applicationId = "...";
String userId = "...";
String channelid = "...";
String deviceId = Secure.getString(getContext().getContentResolver(), Secure.ANDROID_ID);

Client client = new fm.liveswitch.Client("https://liveswitch.server:8443/sync", applicationId, userId, deviceId);

URL url = new URL("https://app.server/token/join"
    + "?deviceId=" + client.getDeviceId()
    + "&channelId=" + channelId;
    
InputStream inputStream = url.openStream();
BufferedReader buffer = new BufferedReader(new InputStreamReader(input));
String token = buffer.lines.collect(Collectors.joining("\n"));
client.join(channelId, token).then(...);
var applicationId = '...';
var userId = '...';
var channelId = '...';
var deviceId = navigator.userAgent;

var client = new fm.liveswitch.Client("https://liveswitch.server:8443/sync", applicationId, userId, deviceId);

var url = 'https://app.server/token/join'
    + '?deviceId=' + client.getDeviceId()
    + "&channelId=" + channelId;

var xhr = new XMLHttpRequest();
xhr.open('POST', url, true)
xhr.onreadystatechange = function() {
    if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
        var token = xhr.response;
        client.join(channelId, token);
    }
}
xhr.send(null);

Again, you will have to configure a "/token/join" endpoint to receive this request. The endpoint should respond with a join token that allows the client to join their requested channel. You generate this token using the GenerateClientJoinToken static method of the FM.LiveSwitch.Token class. Note that you must wrap the channel id provided by the client in an instance of FM.LiveSwitch.ChannelClaim.

[Route("[controller]")]
public class TokenController : Controller
{
    private String secret;
    private UserManager<AppUser> userManager;
    
    public TokenController(UserManager<AppUser> userManager)
    {
        this.userManager = userManager;
    }

    [HttpPost("join")]
    public ContentResult Join([FromQuery]string deviceId, [FromQuery]string clientId, [FromQuery]string channelId)
    {
        var user = await this.userManager.GetUserAsync(this.User);
        
        return Content(FM.LiveSwitch.Token.GenerateClientJoinToken(
            user.ApplicationId,
            user.Id,
            deviceId,
            null,
            new FM.LiveSwitch.ChannelClaim(channelId),
            this.secret
        ));
    }
}
@Controller
@RequestMapping("/token")
public class TokenController {
    @Value("${secret}")
    private String secret;

    @PostMapping
    @ResponseBody
    public String join(
            @RequestParam(value="deviceId") String deviceId,
            @RequestParam(value="clientId") String clientId,
            @RequestParam(value="channelId") String channelId) {
        UserDetails auth = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        
        applicationId = auth.getApplicationName();
        userId = auth.getUsername();
        
        return fm.liveswitch.Token.generateClientJoinToken(
            applicationId,
            userId,
            deviceId,
            null,
            new fm.liveswitch.ChannelClaim(channelId),
            this.secret
        );
    }
}

Upgrading your Server

LiveSwitch is a core product at Frozen Mountain Software and we are constantly making improvements to it. Because of this, it's important to keep your LiveSwitch server upgraded, so that you can take advantage of the latest features. We make this process as easy as possible. To upgrade to the latest version quickly and painlessly, simply download the latest LiveSwitch SDK version and run the installer. Your pre-existing settings will be saved, so you will not have to re-configure your servers.

Which component do I upgrade first?

If your gateway and media server are on the same host, then you don't need to worry about this. The installer will update them both at the same time. If your services are on different hosts, then it is only a bit more complicated. The golden rule is to always upgrade your gateway service first. The gateway service is backwards compatible with older versions of LiveSwitch. By upgrading the gateway first, you ensure that there are no interruptions for any clients that are using older versions of LiveSwitch. If you upgrade your client applications or media servers before your gateway, then you run the risk of the gateway rejecting your connections because it has not been upgraded yet. The gateway service will never accept connections from newer versions of LiveSwitch clients, because it cannot guarantee that it supports the same feature set expected by the client.

How many downtimes can I expect?

If all of your services are on one host, the downtime will be minimal. The installer will terminate each service, and restart it immediately after the upgrade. If your services are on separate hosts, the upgrade should be painless as well, provided you follow the upgrade order described above. There will be a brief interruption as each service is restarted, and the service will be available again afterwards. The next question addresses scenarios where seamless upgrading is required.

How do I gracefully terminate services in use?

To ensure minimal downtime for your users and also that your upgrade process does not interrupt their use of your service, you must have at least two gateway servers and at least two media servers. With two media servers, you can upgrade each one individually, ensuring that there are no service interruptions. Through the gateway administration panel you can tell a media server to "deactivate" itself. This is a slow shutdown - the media server will not accept any new connections during this time. Once you have determined that it is safe to do so (no connections remaining) it is up to your Dev-ops team to shut down the media server. By design media servers will not shut down automatically. Once the media server has been shut down you can upgrade it without interrupting any users' session. You can do this for each media server in turn, until all are upgraded.

For a seamless upgrade of gateway services, you must ensure that you have at least two gateway services and that they are both are configured to use a redis backend, rather than the default in-memory backend. The redis backend ensures that existing clients' connection information is propagated to all gateway servers. The extra gateway service ensures that when a gateway service goes down, your clients will seamlessly reconnect to the next available gateway service.

Are there any plans to improve the upgrade process?

Yes! We love automating things, and we realize that having to manually "drain" each media server before an upgrade can take unnecessary time out of a busy developer's day. In the future, we plan to make this the default upgrade behavior.

Upgrading Linux

The LiveSwitch Linux distribution does not have an installation script, so upgrading requires slightly more manual intervention. Follow the same basic guidelines for a Windows upgrade. However, instead of running the installer, stop each service using systemctl, copy the new files to the LiveSwitch service's directory, and then restart the service.

In the future, we will provide both rpm and deb packages for easy installation.

Regionality

Regionality is a feature that improves clustering performance by allowing each client to select their preferred region. A region is an identifier that represents a specific geographic area. This allows each client to select a geographic area close to them, which ensures fast connection times and high throughput.

What regions are available?

The region identifiers are specific to your application. If your application has many users that are concentrated in a few areas, you will likely have regions with small geographic areas, such as "chicago" or "new-york". If, on the other hand, you have a global user base, you will instead want to use regions that represent larger geographic areas, such as "north-america" or "europe".

Where do I set the list of regions?

There is no canonical set of regions. Each media server can select its own region, and the set of available regions is taken from each media server that is connected to the current cluster.

How does region assignment work?

When a client requests a specific region, the gateway service will attempt to assign it to a media server in the same region. If there are no available media servers in the requested region, or all of the media servers in the requested region are over capacity, the gateway will ignore the region and assign the client to an available media server on a round-robin basis. 

What this means is that a client's selected region is only a preference and that clients requesting a specific region can potentially be assigned to any media server in the global pool if no media servers for their region are available.

Setting up Regions

There are two steps to setting up regions. First, you must assign a region to each media server that will use the regionality feature. Second, you must add an extra parameter to your logic that generates the client registration token.

Assigning a region to a media server

To assign a region to a media server, update the Server Specific Configuration.

Updating the client registration token

Once your pool of media servers have been assigned regions, you must update the client registration logic so that each client requests a specific region. You can review the client documentation for your specific platform to see how to generate tokens and join channels, but to request a specific region you simply specify the optional regionId parameter when you create the client registration token. This parameter comes after the sharedSecret parameter, and indicates the region that the client would like to be assigned to. An example of this is below:

You should never generate the token client-side in production. We're doing so for demo purposes ONLY. Refer to the section on Creating an Auth Server for more information.

var client = new FM.LiveSwitch.Client("http://localhost:8080/sync", applicationId, userId, deviceId);

string token = FM.LiveSwitch.Token.GenerateClientRegisterToken(
    "my-application-id",
    client.UserId,
    client.DeviceId,
    client.Id,
    null,
    new[] { new FM.LiveSwitch.ChannelClaim(channelId) },
    "--replaceThisWithYourOwnSharedSecret--",
    "north-america"
);

TURN in the Media Server

Embedded TURN

LiveSwitch now integrates TURN server functionality directly into your Media Server. No longer do you have to maintain separate server infrastructure for your TURN and secure TURN (TURNS) servers. Now, you simply configure TURN bindings in your LiveSwitch Configuration Console, and the rest takes care of itself.

As an added bonus, this improves the overall efficiency of relay connections, as the latency between the TURN server and the Media Server disappears, and thanks to Regionality, you can easily ensure that your clients are connecting to Media Servers that are nearby.

Configuring TURN/TURNS Bindings

TURN and TURNS Bindings are configured in the Advanced Configuration section of the LiveSwitch Configuration Console.  You will also need to edit the Server Specific Configuration on each Media Server.

The Public Hostname is necessary for TURNS, because all traffic to the server must include this hostname, which is then compared to the hostname that is part of your certificate. Only when they match are TURNS connections possible.

This presents an interesting Operations problem: as you spin up Media Servers to accommodate the increasing load, you must also have a mechanism that creates the hostname for the embedded TURNS server this new Media Server will use. Your Ops process will have to create the necessary DNS record, and also supply the new hostname as part of the TURNS configuration of the new Media Server. Conversely, as you tear down Media Servers you can clean up the DNS records for the TURNS domain names that are no longer required.

How does it all work?

No doubt you are well aware that it is typically clients that supply ICE servers, and provide these to a connection, which in turn gathers ICE candidates, etc. So, how is this all working? Obviously, the Media Server can make use of its own embedded TURN functionality, but how are clients gaining access when they have not yet opened a connection to the Media Server?

The simple answer is the Gateway. When your Media Server registers with the Gateway, it lets the Gateway know about its TURN configuration. Clients in turn get their ICE server credentials from the Gateway before opening a connection provided (a) you have not already supplied ICE servers to the pending connection, and (b) the connection has the DisableAutomaticIceServers property set to false.

Embedded TURN and P2P Connections

Ok, you’re thinking, this all makes a lot of sense for SFU/MCU connections, but what about my P2P connections? they do not connect with the Media Server. How are P2P connections getting access to the ICE servers they require?

It’s true that P2P connections do not even touch the Media Server, but they do register with the Gateway, and just like SFU/MCU connections, will query the Gateway for ICE servers before opening a connection. A very nice side effect here is that even P2P connections now benefit from your Regionality configuration. If you have configured Regionality properly, then clients will in turn use the region appropriate Media Server for relay (where the relay is necessary), which can in turn mitigate latency.

High Availability Architecture


Configuration

Server Specific Configuration

You set basic server settings using the LiveSwitch Configuration Console. There are a few server-specific settings that you have to set in a configuration JSON file.  Each service has its own configuration file in its install location:

  • Gateway: FM.LiveSwitch.Gateway.Service.config.json
  • Media Server: FM.LiveSwitch.MediaServer.Service.config.json
  • SIP Connector: FM.LiveSwitch.Connector.Sip.Service.config.json

Common Configuration

You can set the following for all services:

  • Redis Connection String - The connection string to the Redis server that contains the configuration.  The format of the connection string is defined here.  This string is mandatory and all services must connect to the same Redis server or they will not work correctly.

    "ConnectionStrings": {
      "Default": "127.0.0.1:6379"
    }
  • Deployment Id - Specifies the Deployment Configuration to use for this service.  If this value is not set, LiveSwitch uses the Default Deployment.

    "deploymentId": "Deployment1"
  • Region: Specifies the region of the server for Regionality.

    "Region": "Europe"

Media Server Configuration

The Media Server configuration includes extra settings required for the Embedded TURN Server.

  • External IP Address: The local IP Address(es) of the Media Server.

    "External": {
      "IPAddresses": []
    }
  • Public IP Address: The IP Address of the Media Server, as seen by remote clients.  If this value is not set then the External STUN Servers are used to retrieve it.

    "External": {
      "PublicIPAddress": ""
    }
  • Public Hostname: The hostname of the Media Server.  This value is mandatory if you have configured TURNS Bindings.

    "External": {
      "PublicHostname": ""
    }

How do I reset the Configuration console password?

You can reset the password to your LiveSwitch Console using the LiveSwitch command-line tool that is installed with your Gateways.

Windows

& 'C:\Program Files\Frozen Mountain Software\LiveSwitch\Config Tool\FM.LiveSwitch.Cli.exe' reset password --user=<current_username> --password=<new_password>

Linux

dotnet /opt/liveswitch/config-tool/FM.LiveSwitch.Cli.dll reset password --user=<current_username> --password=<new_password>