Skip to content

Feature Request: Enable STUN/TURN Support in MeshAgent for WebRTC #324

@Gamechiefx

Description

@Gamechiefx

Summary

The MeshCentral agent does not use STUN/TURN servers for WebRTC ICE candidate gathering, which causes WebRTC connections to fail when the browser and agent are on different networks (even when both are behind the same NAT but on different subnets).

Problem Description

When initiating a remote desktop session, MeshCentral attempts to establish a WebRTC connection between the browser and the agent. The browser correctly receives the webrtcconfig from the server and generates:

  • Host candidates (local IPs)
  • Server reflexive (srflx) candidates (public IP via STUN)
  • Relay candidates (via TURN servers)

However, the agent only generates host candidates. It never contacts STUN servers to discover its public IP, nor does it connect to TURN servers to allocate relay addresses.

Evidence from WebRTC Internals

Browser's local candidates:

candidate:xxx 1 udp 2113937151 [local-ip] 55598 typ host
candidate:xxx 1 udp 1677729535 [public-ip] 55598 typ srflx
candidate:xxx 1 udp 33563135 [turn-server-ip] 53157 typ relay

Agent's candidates (from SDP answer):

a=candidate:0 1 UDP 2128609535 [agent-local-ip-1] 56514 typ host
a=candidate:1 1 UDP 2128609534 [agent-local-ip-2] 56514 typ host
a=candidate:2 1 UDP 2128609533 [agent-local-ip-3] 56514 typ host
a=candidate:3 1 UDP 2128609532 [agent-local-ip-4] 56514 typ host

Note: All agent candidates are typ host - no srflx or relay candidates.

Result

ICE connectivity checks fail because:

  1. Browser's host/srflx candidates cannot reach agent's private IPs across networks
  2. Browser's relay candidates (via TURN) cannot reach agent's private IPs (TURN servers cannot route to private addresses)
  3. For TURN relay to work, both endpoints must connect to the TURN server
ICE connection state: "checking" → "disconnected"
Connection state: "connecting" → "failed"

Technical Analysis

Browser Side (Working Correctly)

The server sends webrtcconfig to the browser via the serverinfo message:

// In meshcentral.js or webserver.js
serverinfo.webrtcconfig = { iceServers: [...] };

The browser uses this configuration when creating the RTCPeerConnection:

// In default.handlebars
var configuration = webrtcconfiguration;
webRtcDesktop.webrtc = new RTCPeerConnection(configuration);

Agent Side (Issue Location)

In meshcore.js, the WebRTC connection is created without any configuration:

// Around line 3888 in meshcore.js
ws.webrtc = rtc.createConnection();

The rtc module is require('ILibWebRTC'), which is the native C WebRTC implementation. The createConnection() call does not accept or use any STUN/TURN configuration.

C Code Analysis

In microscript/ILibDuktape_WebRTC.c, there is a SetTurn function:

duk_ret_t ILibWebRTC_Duktape_ConnectionFactory_SetTurn(duk_context *ctx)
{
    char *host = Duktape_GetStringPropertyValue(ctx, 0, "Host", NULL);
    int port = Duktape_GetIntPropertyValue(ctx, 0, "Port", 3478);
    char *username = Duktape_GetStringPropertyValueEx(ctx, 0, "Username", NULL, &usernameLen);
    char *password = Duktape_GetStringPropertyValueEx(ctx, 0, "Password", "", &passwordLen);
    // ...
    ILibWrapper_WebRTC_ConnectionFactory_SetTurnServer(factory, server, username, ...);
}

This indicates that the underlying C WebRTC library (ILibWebRTC) does support TURN, but:

  1. The setTurn method is not exposed to JavaScript via ILibDuktape_CreateInstanceMethod
  2. There is no mechanism for the server to send STUN/TURN configuration to the agent
  3. meshcore.js never calls any STUN/TURN setup methods

Proposed Solution

1. Expose setTurn to JavaScript

In ILibDuktape_WebRTC.c, add the method binding:

ILibDuktape_CreateInstanceMethod(ctx, "setTurn", ILibWebRTC_Duktape_ConnectionFactory_SetTurn, 1);

Also add a setStun method for STUN-only configurations.

2. Server Sends WebRTC Config to Agent

When initiating a WebRTC session, the server should include the webrtcconfig in the tunnel request:

// Server side - when sending WebRTC offer to agent
tunnelMessage.webrtcconfig = domain.webrtcconfig || obj.args.webrtcconfig;

3. Agent Uses WebRTC Config

In meshcore.js, before creating the WebRTC connection:

// Parse webrtcconfig from tunnel message
if (ws.httprequest.webrtcconfig && ws.httprequest.webrtcconfig.iceServers) {
    for (var i = 0; i < ws.httprequest.webrtcconfig.iceServers.length; i++) {
        var server = ws.httprequest.webrtcconfig.iceServers[i];
        if (server.urls) {
            for (var j = 0; j < server.urls.length; j++) {
                var url = server.urls[j];
                if (url.startsWith('turn:')) {
                    rtc.setTurn({
                        Host: url.replace('turn:', '').split(':')[0],
                        Port: parseInt(url.split(':')[2]) || 3478,
                        Username: server.username,
                        Password: server.credential
                    });
                }
            }
        }
    }
}
ws.webrtc = rtc.createConnection();

Current Workaround

WebRTC only works when:

  • Browser and agent are on the same subnet (host-to-host connectivity)
  • There is direct UDP routing between browser and agent networks

When clients are on different networks (common in enterprise environments with VLANs, or remote access scenarios), WebRTC fails and falls back to WebSocket relay through the server, resulting in:

  • Higher latency (~200ms+ vs ~5-10ms for direct)
  • Additional server bandwidth usage
  • No end-to-end encryption (server decrypts/re-encrypts)

Environment

  • MeshCentral Server Version: 1.1.x+
  • Agent Version: Windows/Linux (both affected)
  • Browser: Chrome 120+ (tested)
  • WebRTC enabled in config.json: "webrtc": true
  • STUN/TURN servers: Configured and working (verified with browser-only tests)

Related Documentation

From MeshCentral2DesignArchitecture.pdf, Section 9:

"Browsers typically use STUN and TURN servers to help establish WebRTC connections..."
"When a web application makes use of WebRTC, the browser and MeshAgent will attempt to negotiate a direct connection."

The documentation describes the intended behavior but does not mention that the agent lacks STUN/TURN support.

Impact

This affects all MeshCentral deployments where:

  • Users access agents from different networks
  • Enterprise networks with VLAN segmentation
  • Remote access over the internet
  • Symmetric NAT environments

Requested Changes

  1. Expose STUN/TURN configuration methods in the agent's JavaScript runtime
  2. Add server-to-agent WebRTC config transmission
  3. Implement STUN/TURN usage in meshcore.js
  4. Update documentation to reflect STUN/TURN requirements for cross-network WebRTC

Thank you for considering this enhancement. WebRTC with STUN/TURN support would significantly improve performance for cross-network remote desktop sessions.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions