Skip to content

Commit

Permalink
fix: add reachability checks for TLS (#3606)
Browse files Browse the repository at this point in the history
Co-authored-by: kwasniow <[email protected]>
  • Loading branch information
k-wasniowski and k-wasniowski authored May 22, 2024
1 parent 3471dfa commit ef515cf
Show file tree
Hide file tree
Showing 11 changed files with 417 additions and 90 deletions.
2 changes: 2 additions & 0 deletions docs/samples/browser-plugin-meetings/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const breakoutTable = document.getElementById('breakout-table');
const breakoutHostOperation = document.getElementById('breakout-host-operation');
const getStatsButton = document.getElementById('get-stats');
const tcpReachabilityConfigElm = document.getElementById('enable-tcp-reachability');
const tlsReachabilityConfigElm = document.getElementById('enable-tls-reachability');

const guestName = document.querySelector('#guest-name');
const getGuestToken = document.querySelector('#get-guest-token');
Expand Down Expand Up @@ -116,6 +117,7 @@ function generateWebexConfig({credentials}) {
enableUnifiedMeetings: true,
enableAdhocMeetings: true,
enableTcpReachability: tcpReachabilityConfigElm.checked,
enableTlsReachability: tlsReachabilityConfigElm.checked,
},
enableAutomaticLLM: enableLLM.checked,
},
Expand Down
3 changes: 3 additions & 0 deletions docs/samples/browser-plugin-meetings/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ <h2 class="collapsible">
<input type="checkbox" id="enable-tcp-reachability">
<label for="enable-tcp-reachability">Enable TCP reachability (experimental)</label>
<br/>
<input type="checkbox" id="enable-tls-reachability">
<label for="enable-tls-reachability">Enable TLS reachability (experimental)</label>
<br/>
<input id="meetings-enable-llm" checked type="checkbox">
<label for="meetings-enable-llm-label">Enable LLM</label>
</div>
Expand Down
1 change: 1 addition & 0 deletions packages/@webex/plugin-meetings/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export default {
enableUnifiedMeetings: true,
enableAdhocMeetings: true,
enableTcpReachability: false,
enableTlsReachability: false,
},
degradationPreferences: {
maxMacroblocksLimit: 8192,
Expand Down
18 changes: 18 additions & 0 deletions packages/@webex/plugin-meetings/src/meetings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,24 @@ export default class Meetings extends WebexPlugin {
}
}

/**
* API to toggle TLS reachability, needs to be called before webex.meetings.register()
* @param {Boolean} newValue
* @private
* @memberof Meetings
* @returns {undefined}
*/
private _toggleTlsReachability(newValue: boolean) {
if (typeof newValue !== 'boolean') {
return;
}
// @ts-ignore
if (this.config.experimental.enableTlsReachability !== newValue) {
// @ts-ignore
this.config.experimental.enableTlsReachability = newValue;
}
}

/**
* Explicitly sets up the meetings plugin by registering
* the device, connecting to mercury, and listening for locus events.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {Defer} from '@webex/common';

import LoggerProxy from '../common/logs/logger-proxy';
import {ClusterNode} from './request';
import {convertStunUrlToTurn} from './util';
import {convertStunUrlToTurn, convertStunUrlToTurnTls} from './util';

import {ICE_GATHERING_STATE, CONNECTION_STATE} from '../constants';

Expand All @@ -29,6 +29,7 @@ export type ClusterReachabilityResult = {
export class ClusterReachability {
private numUdpUrls: number;
private numTcpUrls: number;
private numXTlsUrls: number;
private result: ClusterReachabilityResult;
private pc?: RTCPeerConnection;
private defer: Defer; // this defer is resolved once reachability checks for this cluster are completed
Expand All @@ -46,6 +47,7 @@ export class ClusterReachability {
this.isVideoMesh = clusterInfo.isVideoMesh;
this.numUdpUrls = clusterInfo.udp.length;
this.numTcpUrls = clusterInfo.tcp.length;
this.numXTlsUrls = clusterInfo.xtls.length;

this.pc = this.createPeerConnection(clusterInfo);

Expand Down Expand Up @@ -94,8 +96,16 @@ export class ClusterReachability {
};
});

const turnTlsIceServers = cluster.xtls.map((urlString: string) => {
return {
username: 'webexturnreachuser',
credential: 'webexturnreachpwd',
urls: [convertStunUrlToTurnTls(urlString)],
};
});

return {
iceServers: [...udpIceServers, ...tcpIceServers],
iceServers: [...udpIceServers, ...tcpIceServers, ...turnTlsIceServers],
iceCandidatePoolSize: 0,
iceTransportPolicy: 'all',
};
Expand Down Expand Up @@ -194,7 +204,7 @@ export class ClusterReachability {
* @returns {boolean} true if we have all results, false otherwise
*/
private haveWeGotAllResults(): boolean {
return ['udp', 'tcp'].every(
return ['udp', 'tcp', 'xtls'].every(
(protocol) =>
this.result[protocol].result === 'reachable' || this.result[protocol].result === 'untested'
);
Expand All @@ -207,7 +217,7 @@ export class ClusterReachability {
* @param {number} latency
* @returns {void}
*/
private storeLatencyResult(protocol: 'udp' | 'tcp', latency: number) {
private storeLatencyResult(protocol: 'udp' | 'tcp' | 'xtls', latency: number) {
const result = this.result[protocol];

if (result.latencyInMilliseconds === undefined) {
Expand All @@ -227,6 +237,7 @@ export class ClusterReachability {
*/
private registerIceCandidateListener() {
this.pc.onicecandidate = (e) => {
const TURN_TLS_PORT = 443;
const CANDIDATE_TYPES = {
SERVER_REFLEXIVE: 'srflx',
RELAY: 'relay',
Expand All @@ -239,7 +250,8 @@ export class ClusterReachability {
}

if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
this.storeLatencyResult('tcp', this.getElapsedTime());
const protocol = e.candidate.port === TURN_TLS_PORT ? 'xtls' : 'tcp';
this.storeLatencyResult(protocol, this.getElapsedTime());
// we don't add public IP for TCP, because in the case of relay candidates
// e.candidate.address is the TURN server address, not the client's public IP
}
Expand Down Expand Up @@ -275,6 +287,9 @@ export class ClusterReachability {
this.result.tcp = {
result: this.numTcpUrls > 0 ? 'unreachable' : 'untested',
};
this.result.xtls = {
result: this.numXTlsUrls > 0 ? 'unreachable' : 'untested',
};

try {
const offer = await this.pc.createOffer({offerToReceiveAudio: true});
Expand Down
25 changes: 24 additions & 1 deletion packages/@webex/plugin-meetings/src/reachability/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ export type ReachabilityMetrics = {
reachability_public_udp_failed: number;
reachability_public_tcp_success: number;
reachability_public_tcp_failed: number;
reachability_public_xtls_success: number;
reachability_public_xtls_failed: number;
reachability_vmn_udp_success: number;
reachability_vmn_udp_failed: number;
reachability_vmn_tcp_success: number;
reachability_vmn_tcp_failed: number;
reachability_vmn_xtls_success: number;
reachability_vmn_xtls_failed: number;
};

/**
Expand Down Expand Up @@ -141,10 +145,14 @@ export default class Reachability {
reachability_public_udp_failed: 0,
reachability_public_tcp_success: 0,
reachability_public_tcp_failed: 0,
reachability_public_xtls_success: 0,
reachability_public_xtls_failed: 0,
reachability_vmn_udp_success: 0,
reachability_vmn_udp_failed: 0,
reachability_vmn_tcp_success: 0,
reachability_vmn_tcp_failed: 0,
reachability_vmn_xtls_success: 0,
reachability_vmn_xtls_failed: 0,
};

const updateStats = (clusterType: 'public' | 'vmn', result: ClusterReachabilityResult) => {
Expand All @@ -156,6 +164,10 @@ export default class Reachability {
const outcome = result.tcp.result === 'reachable' ? 'success' : 'failed';
stats[`reachability_${clusterType}_tcp_${outcome}`] += 1;
}
if (result.xtls && result.xtls.result !== 'untested') {
const outcome = result.xtls.result === 'reachable' ? 'success' : 'failed';
stats[`reachability_${clusterType}_xtls_${outcome}`] += 1;
}
};

try {
Expand Down Expand Up @@ -338,7 +350,10 @@ export default class Reachability {
LoggerProxy.logger.log(
`Reachability:index#performReachabilityChecks --> doing UDP${
// @ts-ignore
this.webex.config.meetings.experimental.enableTcpReachability ? ' and TCP' : ''
this.webex.config.meetings.experimental.enableTcpReachability ? ',TCP' : ''
}${
// @ts-ignore
this.webex.config.meetings.experimental.enableTlsReachability ? ',TLS' : ''
} reachability checks`
);

Expand All @@ -354,6 +369,14 @@ export default class Reachability {
cluster.tcp = [];
}

const includeTlsReachability =
// @ts-ignore
this.webex.config.meetings.experimental.enableTlsReachability && !cluster.isVideoMesh;

if (!includeTlsReachability) {
cluster.xtls = [];
}

this.clusterReachability[key] = new ClusterReachability(key, cluster);

return this.clusterReachability[key].start().then((result) => {
Expand Down
21 changes: 21 additions & 0 deletions packages/@webex/plugin-meetings/src/reachability/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,24 @@ export function convertStunUrlToTurn(stunUrl: string, protocol: 'udp' | 'tcp') {

return url.toString();
}

/**
* Converts a stun url to a turns url
*
* @param {string} stunUrl url of a stun server
* @returns {string} url of a turns server
*/
export function convertStunUrlToTurnTls(stunUrl: string) {
// stunUrl looks like this: "stun:external-media1.public.wjfkm-a-15.prod.infra.webex.com:443"
// and we need it to be like this: "turns:external-media1.public.wjfkm-a-15.prod.infra.webex.com:443?transport=tcp"
const url = new URL(stunUrl);

if (url.protocol !== 'stun:') {
throw new Error(`Not a STUN URL: ${stunUrl}`);
}

url.protocol = 'turns:';
url.searchParams.append('transport', 'tcp');

return url.toString();
}
13 changes: 13 additions & 0 deletions packages/@webex/plugin-meetings/test/unit/spec/meetings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,19 @@ describe('plugin-meetings', () => {
});
});

describe('#_toggleTlsReachability', () => {
it('should have _toggleTlsReachability', () => {
assert.equal(typeof webex.meetings._toggleTlsReachability, 'function');
});

describe('success', () => {
it('should update meetings to do TLS reachability', () => {
webex.meetings._toggleTlsReachability(true);
assert.equal(webex.meetings.config.experimental.enableTlsReachability, true);
});
});
});

describe('Public API Contracts', () => {
describe('#register', () => {
it('emits an event and resolves when register succeeds', async () => {
Expand Down
Loading

0 comments on commit ef515cf

Please sign in to comment.