Skip to content

Commit ed8f609

Browse files
committed
feat: metrics on reachability subnet
1 parent f027038 commit ed8f609

File tree

4 files changed

+133
-3
lines changed

4 files changed

+133
-3
lines changed

packages/@webex/plugin-meetings/src/meeting/index.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,7 @@ export default class Meeting extends StatelessWebexPlugin {
722722
private rtcMetrics?: RtcMetrics;
723723
private uploadLogsTimer?: ReturnType<typeof setTimeout>;
724724
private logUploadIntervalIndex: number;
725+
private mediaServerIp: string;
725726

726727
/**
727728
* @param {Object} attrs
@@ -1598,6 +1599,15 @@ export default class Meeting extends StatelessWebexPlugin {
15981599
* @memberof Meeting
15991600
*/
16001601
this.#isoLocalClientMeetingJoinTime = undefined;
1602+
1603+
/**
1604+
* IP Address of remote media server
1605+
* @instance
1606+
* @type {number}
1607+
* @private
1608+
* @memberof Meeting
1609+
*/
1610+
this.mediaServerIp = null;
16011611
}
16021612

16031613
/**
@@ -6329,6 +6339,11 @@ export default class Meeting extends StatelessWebexPlugin {
63296339
? MeetingsUtil.getMediaServer(roapMessage.sdp)
63306340
: undefined;
63316341

6342+
const mediaServerIp =
6343+
roapMessage.messageType === 'ANSWER'
6344+
? MeetingsUtil.getMediaServerIp(roapMessage.sdp)
6345+
: undefined;
6346+
63326347
if (this.isMultistream && mediaServer && mediaServer !== 'homer') {
63336348
throw new MultistreamNotSupportedError(
63346349
`Client asked for multistream backend (Homer), but got ${mediaServer} instead`
@@ -6339,6 +6354,34 @@ export default class Meeting extends StatelessWebexPlugin {
63396354
if (mediaServer) {
63406355
this.mediaProperties.webrtcMediaConnection.mediaServer = mediaServer;
63416356
}
6357+
6358+
if (this.isMultistream && mediaServerIp) {
6359+
this.mediaServerIp = mediaServerIp;
6360+
}
6361+
6362+
// console.log('mediaServerIp: ', mediaServerIp);
6363+
6364+
// if (this.isMultistream && roapMessage.messageType === 'ANSWER') {
6365+
// console.log('Processing answer!!!');
6366+
6367+
// const candidate_regex = /^a=candidate:.*$/gm;
6368+
6369+
// const candidateLines = roapMessage.sdp.match(candidate_regex) || [];
6370+
6371+
// console.log('Candidate lines:', candidateLines);
6372+
6373+
// try {
6374+
// const ip_regex = /a=candidate:\d+ \d+ \w+ \d+ ([\d.]+) \d+ typ \w+/;
6375+
6376+
// const udp_ice_candidate = candidateLines.find((line) => line.includes('UDP'));
6377+
6378+
// // a=candidate:0 1 UDP 2130706431 144.196.73.28 5004 typ host
6379+
// const candidate_ip = udp_ice_candidate.match(ip_regex)?.[1];
6380+
// console.log('Candidate IP:', candidate_ip);
6381+
// } catch (error) {
6382+
// console.error('Error extracting candidate IP:', error);
6383+
// }
6384+
// }
63426385
};
63436386

63446387
/**
@@ -7741,13 +7784,20 @@ export default class Meeting extends StatelessWebexPlugin {
77417784
const reachabilityStats = await this.webex.meetings.reachability.getReachabilityMetrics();
77427785
const iceCandidateErrors = Object.fromEntries(this.iceCandidateErrors);
77437786

7787+
const isSubnetReachable = this.getWebexObject().meetings.reachability.isSubnetReachable(
7788+
this.mediaConnections[0]?.mediaAgentCluster,
7789+
this.mediaServerIp
7790+
);
7791+
77447792
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
77457793
correlation_id: this.correlationId,
77467794
locus_id: this.locusUrl.split('/').pop(),
77477795
connectionType,
77487796
selectedCandidatePairChanges,
77497797
numTransports,
77507798
isMultistream: this.isMultistream,
7799+
// @ts-ignore
7800+
isSubnetReachable,
77517801
retriedWithTurnServer: this.addMediaData.retriedWithTurnServer,
77527802
isJoinWithMediaRetry: this.joinWithMediaRetryInfo.isRetry,
77537803
...reachabilityStats,

packages/@webex/plugin-meetings/src/meetings/util.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,28 @@ MeetingsUtil.getMediaServer = (sdp) => {
9999
return mediaServer;
100100
};
101101

102+
MeetingsUtil.getMediaServerIp = (sdp) => {
103+
let mediaServerIp;
104+
105+
// Attempt to collect the media server from the roap message.
106+
try {
107+
mediaServerIp = sdp
108+
.split('\r\n')
109+
.find(
110+
(line) =>
111+
// Make sure we non-FQDN
112+
line.startsWith('a=candidate') && line.includes('UDP') && !line.includes('webex.com')
113+
)
114+
.match(/a=candidate:\d+ \d+ \w+ \d+ ([\d.]+) \d+ typ \w+/)?.[1]
115+
.toLowerCase()
116+
.trim();
117+
} catch {
118+
mediaServerIp = undefined;
119+
}
120+
121+
return mediaServerIp;
122+
};
123+
102124
MeetingsUtil.checkForCorrelationId = (deviceUrl, locus) => {
103125
let devices = [];
104126

packages/@webex/plugin-meetings/src/reachability/clusterReachability.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export class ClusterReachability extends EventsScope {
4949
private srflxIceCandidates: RTCIceCandidate[] = [];
5050
public readonly isVideoMesh: boolean;
5151
public readonly name;
52+
public reachedSubnets: string[] = [];
5253

5354
/**
5455
* Constructor for ClusterReachability
@@ -264,9 +265,15 @@ export class ClusterReachability extends EventsScope {
264265
* @param {string} protocol
265266
* @param {number} latency
266267
* @param {string|null} [publicIp]
268+
* @param {string|null} [serverIp]
267269
* @returns {void}
268270
*/
269-
private saveResult(protocol: 'udp' | 'tcp' | 'xtls', latency: number, publicIp?: string | null) {
271+
private saveResult(
272+
protocol: 'udp' | 'tcp' | 'xtls',
273+
latency: number,
274+
publicIp?: string | null,
275+
serverIp?: string | null
276+
) {
270277
const result = this.result[protocol];
271278

272279
if (result.latencyInMilliseconds === undefined) {
@@ -294,6 +301,10 @@ export class ClusterReachability extends EventsScope {
294301
} else {
295302
this.addPublicIP(protocol, publicIp);
296303
}
304+
305+
if (serverIp) {
306+
this.reachedSubnets.push(serverIp);
307+
}
297308
}
298309

299310
/**
@@ -350,15 +361,40 @@ export class ClusterReachability extends EventsScope {
350361
const latencyInMilliseconds = this.getElapsedTime();
351362

352363
if (e.candidate) {
364+
// const url = '';
365+
// if ('url' in e.candidate) {
366+
// const candidate = e.candidate as any;
367+
368+
// // url = candidate.url;
369+
370+
// console.log(`Candidate: `, candidate);
371+
// // eslint-disable-next-line dot-notation
372+
// console.log(`Url: `, candidate['url']);
373+
374+
// // eslint-disable-next-line dot-notation
375+
// console.log(`Url[dot]: `, candidate.url);
376+
// }
377+
353378
if (e.candidate.type === CANDIDATE_TYPES.SERVER_REFLEXIVE) {
354-
this.saveResult('udp', latencyInMilliseconds, e.candidate.address);
379+
let serverIp = null;
380+
if ('url' in e.candidate) {
381+
const regex = /stun:([\d.]+):\d+/;
382+
383+
const match = (e.candidate as any).url.match(regex);
384+
if (match) {
385+
// eslint-disable-next-line prefer-destructuring
386+
serverIp = match[1];
387+
}
388+
}
389+
390+
this.saveResult('udp', latencyInMilliseconds, e.candidate.address, serverIp);
355391

356392
this.determineNatType(e.candidate);
357393
}
358394

359395
if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
360396
const protocol = e.candidate.port === TURN_TLS_PORT ? 'xtls' : 'tcp';
361-
this.saveResult(protocol, latencyInMilliseconds);
397+
this.saveResult(protocol, latencyInMilliseconds, null, e.candidate.address);
362398
// we don't add public IP for TCP, because in the case of relay candidates
363399
// e.candidate.address is the TURN server address, not the client's public IP
364400
}

packages/@webex/plugin-meetings/src/reachability/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,28 @@ export default class Reachability extends EventsScope {
138138
}
139139
}
140140

141+
/**
142+
* Checks if the given subnet is reachable
143+
* @param {string} selectedCluster - cluster to check
144+
* @param {string} subnet - subnet to check
145+
* @returns {boolean} true if reachable, false otherwise
146+
* @public
147+
* @memberof Reachability
148+
*/
149+
public isSubnetReachable(selectedCluster: string, subnet: string): boolean {
150+
const cluster = Object.values(this.clusterReachability).find((c) =>
151+
c.name.startsWith(selectedCluster)
152+
);
153+
154+
if (!cluster) {
155+
return false;
156+
}
157+
158+
const subnetFirstOctet = subnet.split('.')[0];
159+
160+
return !!cluster.reachedSubnets.find((ss) => ss.startsWith(subnetFirstOctet));
161+
}
162+
141163
/**
142164
* Gets a list of media clusters from the backend and performs reachability checks on all the clusters
143165
* @param {string} trigger - explains the reason for starting reachability

0 commit comments

Comments
 (0)