Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -100,5 +100,6 @@ export default {
logUploadIntervalMultiplicationFactor: 0, // if set to 0 or undefined, logs won't be uploaded periodically, if you want periodic logs, recommended value is 1
stopIceGatheringAfterFirstRelayCandidate: false,
enableAudioTwccForMultistream: false,
enablePerUdpUrlReachability: false, // true: separate peer connection per each UDP URL; false: single peer connection for all URLs
},
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {ClusterNode} from './request';
import EventsScope from '../common/events/events-scope';
import LoggerProxy from '../common/logs/logger-proxy';

import {Enum} from '../constants';
import {
Expand Down Expand Up @@ -37,36 +38,117 @@ export type Events = Enum<typeof Events>;

/**
* A class that handles reachability checks for a single cluster.
* Creates and orchestrates a ReachabilityPeerConnection instance.
* Creates and orchestrates ReachabilityPeerConnection instance(s).
* Listens to events and emits them to consumers.
*
* When enablePerUdpUrlReachability is true:
* - Creates one ReachabilityPeerConnection for each UDP URL
* - Creates one ReachabilityPeerConnection for all TCP and TLS URLs together
* Otherwise:
* - Creates a single ReachabilityPeerConnection for all URLs
*/
export class ClusterReachability extends EventsScope {
private reachabilityPeerConnection: ReachabilityPeerConnection;
private reachabilityPeerConnection: ReachabilityPeerConnection | null = null;
private reachabilityPeerConnectionsForUdp: ReachabilityPeerConnection[] = [];

public readonly isVideoMesh: boolean;
public readonly name;
public readonly reachedSubnets: Set<string> = new Set();

private enablePerUdpUrlReachability: boolean;
private udpResultEmitted = false;

/**
* Constructor for ClusterReachability
* @param {string} name cluster name
* @param {ClusterNode} clusterInfo information about the media cluster
* @param {boolean} enablePerUdpUrlReachability whether to create separate peer connections per UDP URL
*/
constructor(name: string, clusterInfo: ClusterNode) {
constructor(name: string, clusterInfo: ClusterNode, enablePerUdpUrlReachability = false) {
super();
this.name = name;
this.isVideoMesh = clusterInfo.isVideoMesh;
this.enablePerUdpUrlReachability = enablePerUdpUrlReachability;

this.reachabilityPeerConnection = new ReachabilityPeerConnection(name, clusterInfo);
if (this.enablePerUdpUrlReachability) {
this.initializePerUdpUrlReachabilityCheck(clusterInfo);
} else {
this.initializeSingleReachabilityPeerConnection(clusterInfo);
}
}

this.setupReachabilityPeerConnectionEventListeners();
/**
* Initializes a single ReachabilityPeerConnection for all protocols
* @param {ClusterNode} clusterInfo information about the media cluster
* @returns {void}
*/
private initializeSingleReachabilityPeerConnection(clusterInfo: ClusterNode) {
this.reachabilityPeerConnection = new ReachabilityPeerConnection(this.name, clusterInfo);
this.setupReachabilityPeerConnectionEventListeners(this.reachabilityPeerConnection);
}

/**
* Sets up event listeners for the ReachabilityPeerConnection instance
* Initializes per-URL UDP reachability checks:
* - One ReachabilityPeerConnection per UDP URL
* - One ReachabilityPeerConnection for all TCP and TLS URLs together
* @param {ClusterNode} clusterInfo information about the media cluster
* @returns {void}
*/
private setupReachabilityPeerConnectionEventListeners() {
this.reachabilityPeerConnection.on(ReachabilityPeerConnectionEvents.resultReady, (data) => {
private initializePerUdpUrlReachabilityCheck(clusterInfo: ClusterNode) {
LoggerProxy.logger.log(
`ClusterReachability#initializePerUdpUrlReachabilityCheck --> cluster: ${this.name}, performing per-URL UDP reachability for ${clusterInfo.udp.length} URLs`
);

// Create one ReachabilityPeerConnection for each UDP URL
clusterInfo.udp.forEach((udpUrl) => {
const singleUdpClusterInfo: ClusterNode = {
isVideoMesh: clusterInfo.isVideoMesh,
udp: [udpUrl],
tcp: [],
xtls: [],
};
const rpc = new ReachabilityPeerConnection(this.name, singleUdpClusterInfo);
this.setupReachabilityPeerConnectionEventListeners(rpc, true);
this.reachabilityPeerConnectionsForUdp.push(rpc);
});

// Create one ReachabilityPeerConnection for all TCP and TLS URLs together
if (clusterInfo.tcp.length > 0 || clusterInfo.xtls.length > 0) {
const tcpTlsClusterInfo: ClusterNode = {
isVideoMesh: clusterInfo.isVideoMesh,
udp: [],
tcp: clusterInfo.tcp,
xtls: clusterInfo.xtls,
};
this.reachabilityPeerConnection = new ReachabilityPeerConnection(
this.name,
tcpTlsClusterInfo
);
this.setupReachabilityPeerConnectionEventListeners(this.reachabilityPeerConnection);
}
}

/**
* Sets up event listeners for a ReachabilityPeerConnection instance
* @param {ReachabilityPeerConnection} rpc the ReachabilityPeerConnection instance
* @param {boolean} isUdpPerUrl whether this is a per-URL UDP instance
* @returns {void}
*/
private setupReachabilityPeerConnectionEventListeners(
rpc: ReachabilityPeerConnection,
isUdpPerUrl = false
) {
rpc.on(ReachabilityPeerConnectionEvents.resultReady, (data) => {
// For per-URL UDP checks, only emit the first successful UDP result
if (isUdpPerUrl && data.protocol === 'udp') {
if (this.udpResultEmitted) {
return;
}
if (data.result === 'reachable') {
this.udpResultEmitted = true;
}
}

this.emit(
{
file: 'clusterReachability',
Expand All @@ -77,21 +159,18 @@ export class ClusterReachability extends EventsScope {
);
});

this.reachabilityPeerConnection.on(
ReachabilityPeerConnectionEvents.clientMediaIpsUpdated,
(data) => {
this.emit(
{
file: 'clusterReachability',
function: 'setupReachabilityPeerConnectionEventListeners',
},
Events.clientMediaIpsUpdated,
data
);
}
);
rpc.on(ReachabilityPeerConnectionEvents.clientMediaIpsUpdated, (data) => {
this.emit(
{
file: 'clusterReachability',
function: 'setupReachabilityPeerConnectionEventListeners',
},
Events.clientMediaIpsUpdated,
data
);
});

this.reachabilityPeerConnection.on(ReachabilityPeerConnectionEvents.natTypeUpdated, (data) => {
rpc.on(ReachabilityPeerConnectionEvents.natTypeUpdated, (data) => {
this.emit(
{
file: 'clusterReachability',
Expand All @@ -102,26 +181,72 @@ export class ClusterReachability extends EventsScope {
);
});

this.reachabilityPeerConnection.on(ReachabilityPeerConnectionEvents.reachedSubnets, (data) => {
data.subnets.forEach((subnet) => {
rpc.on(ReachabilityPeerConnectionEvents.reachedSubnets, (data) => {
data.subnets.forEach((subnet: string) => {
this.reachedSubnets.add(subnet);
});
});
}

/**
* Gets the aggregated reachability result for this cluster.
* @returns {ClusterReachabilityResult} reachability result for this cluster
*/
getResult(): ClusterReachabilityResult {
return this.reachabilityPeerConnection.getResult();
if (!this.enablePerUdpUrlReachability) {
return (
this.reachabilityPeerConnection?.getResult() ?? {
udp: {result: 'untested'},
tcp: {result: 'untested'},
xtls: {result: 'untested'},
}
);
}

const result: ClusterReachabilityResult = {
udp: {result: 'untested'},
tcp: {result: 'untested'},
xtls: {result: 'untested'},
};

// Get the first reachable UDP result from per-URL instances
for (const rpc of this.reachabilityPeerConnectionsForUdp) {
const rpcResult = rpc.getResult();
if (rpcResult.udp.result === 'reachable') {
result.udp = rpcResult.udp;
break;
}
if (rpcResult.udp.result === 'unreachable' && result.udp.result === 'untested') {
result.udp = rpcResult.udp;
}
}

// Get TCP and TLS results from the main peer connection
if (this.reachabilityPeerConnection) {
const mainResult = this.reachabilityPeerConnection.getResult();
result.tcp = mainResult.tcp;
result.xtls = mainResult.xtls;
}

return result;
}

/**
* Starts the process of doing UDP, TCP, and XTLS reachability checks on the media cluster.
* @returns {Promise<ClusterReachabilityResult>}
*/
async start(): Promise<ClusterReachabilityResult> {
await this.reachabilityPeerConnection.start();
const startPromises: Promise<ClusterReachabilityResult>[] = [];

this.reachabilityPeerConnectionsForUdp.forEach((rpc) => {
startPromises.push(rpc.start());
});

if (this.reachabilityPeerConnection) {
startPromises.push(this.reachabilityPeerConnection.start());
}

await Promise.all(startPromises);

return this.getResult();
}
Expand All @@ -131,6 +256,7 @@ export class ClusterReachability extends EventsScope {
* @returns {void}
*/
public abort() {
this.reachabilityPeerConnection.abort();
this.reachabilityPeerConnectionsForUdp.forEach((rpc) => rpc.abort());
this.reachabilityPeerConnection?.abort();
}
}
7 changes: 6 additions & 1 deletion packages/@webex/plugin-meetings/src/reachability/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -961,7 +961,12 @@ export default class Reachability extends EventsScope {
Object.keys(clusterList).forEach((key) => {
const cluster = clusterList[key];

this.clusterReachability[key] = new ClusterReachability(key, cluster);
this.clusterReachability[key] = new ClusterReachability(
key,
cluster,
// @ts-ignore
this.webex.config.meetings.enablePerUdpUrlReachability
);
this.clusterReachability[key].on(Events.resultReady, async (data: ResultEventData) => {
const {protocol, result, clientMediaIPs, latencyInMilliseconds} = data;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,9 @@ export class ReachabilityPeerConnection extends EventsScope {
if (result.latencyInMilliseconds === undefined) {
LoggerProxy.logger.log(
// @ts-ignore
`Reachability:ReachabilityPeerConnection#saveResult --> Successfully reached ${this.clusterName} over ${protocol}: ${latency}ms`
`Reachability:ReachabilityPeerConnection#saveResult --> Successfully reached ${
this.clusterName
} over ${protocol}: ${latency}ms, serverIp=${serverIp || 'unknown'}`
);
result.latencyInMilliseconds = latency;
result.result = 'reachable';
Expand Down
Loading
Loading