Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.

Commit d4fc5b1

Browse files
committed
Bug 1884140: Glean telemetry for webrtc DTLS. r=mjf
Differential Revision: https://phabricator.services.mozilla.com/D208147
1 parent 7d7452a commit d4fc5b1

File tree

5 files changed

+326
-63
lines changed

5 files changed

+326
-63
lines changed

dom/media/webrtc/metrics.yaml

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,3 +404,84 @@ codec.stats:
404404
notification_emails:
405405
406406
expires: 132
407+
408+
webrtcdtls:
409+
protocol_version:
410+
type: labeled_counter
411+
description: >
412+
The version of DTLS used for each webrtc connection. Can be 1.0, 1.2, or 1.3 (there is no 1.1 version of DTLS)
413+
bugs:
414+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
415+
data_reviews:
416+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
417+
data_sensitivity:
418+
- technical
419+
notification_emails:
420+
421+
expires: 135
422+
423+
cipher:
424+
type: labeled_counter
425+
description: >
426+
The CipherSuite used for each webrtc DTLS connection, as a string
427+
representation of the CipherSuite's ID in 4 hex digits (eg;
428+
TLS_DHE_RSA_WITH_AES_128_CBC_SHA would be "0x0033")
429+
bugs:
430+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
431+
data_reviews:
432+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
433+
data_sensitivity:
434+
- technical
435+
notification_emails:
436+
437+
expires: 135
438+
439+
srtp_cipher:
440+
type: labeled_counter
441+
description: >
442+
The SRTPProtectionProfile (see RFC 5764) used for each webrtc SRTP
443+
connection, as a string representation of the SRTPProtectionProfile's ID
444+
in 4 hex digits (eg; SRTP_AES128_CM_HMAC_SHA1_80 would be "0x0001")
445+
bugs:
446+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
447+
data_reviews:
448+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
449+
data_sensitivity:
450+
- technical
451+
notification_emails:
452+
453+
expires: 135
454+
455+
client_handshake_result:
456+
type: labeled_counter
457+
description: >
458+
The result of each webrtc client DTLS handshake as a string containing
459+
either the name of the error code (eg; SSL_ERROR_BAD_CERTIFICATE),
460+
SUCCESS for successful handshakes, ALPN_FAILURE when ALPN negotiation
461+
fails, or CERT_FAILURE when cert validation fails.
462+
bugs:
463+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
464+
data_reviews:
465+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
466+
data_sensitivity:
467+
- technical
468+
notification_emails:
469+
470+
expires: 135
471+
472+
server_handshake_result:
473+
type: labeled_counter
474+
description: >
475+
The result of each webrtc server DTLS handshake, as the name of the error
476+
code (eg; SSL_ERROR_BAD_CERTIFICATE), the empty string for successful
477+
handshakes, ALPN_FAILURE when ALPN negotiation fails, or CERT_FAILURE when
478+
cert validation fails.
479+
bugs:
480+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
481+
data_reviews:
482+
- https://bugzilla.mozilla.org/show_bug.cgi?id=1884140
483+
data_sensitivity:
484+
- technical
485+
notification_emails:
486+
487+
expires: 135

dom/media/webrtc/tests/mochitests/iceTestUtils.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ async function iceConnected(pc) {
7575
});
7676
}
7777

78+
async function dtlsConnected(pc) {
79+
return new Promise((resolve, reject) => {
80+
pc.addEventListener("connectionstatechange", () => {
81+
if (["connected", "completed"].includes(pc.connectionState)) {
82+
resolve();
83+
} else if (pc.connectionState == "failed") {
84+
reject(new Error(`Connection failed`));
85+
}
86+
});
87+
});
88+
}
89+
7890
// Set up trickle, but does not wait for it to complete. Can be used by itself
7991
// in cases where we do not expect any new candidates, but want to still set up
8092
// the signal handling in case new candidates _do_ show up.
@@ -87,7 +99,8 @@ async function connect(
8799
answerer,
88100
timeout,
89101
context,
90-
noTrickleWait = false
102+
noTrickleWait = false,
103+
waitForDtls = false
91104
) {
92105
const trickle1 = trickleIce(offerer, answerer);
93106
const trickle2 = trickleIce(answerer, offerer);
@@ -110,8 +123,12 @@ async function connect(
110123
}
111124
};
112125

126+
const connectionPromises = waitForDtls
127+
? [dtlsConnected(offerer), dtlsConnected(answerer)]
128+
: [iceConnected(offerer), iceConnected(answerer)];
129+
113130
await Promise.race([
114-
Promise.all([iceConnected(offerer), iceConnected(answerer)]),
131+
Promise.all(connectionPromises),
115132
throwOnTimeout(timeout, context),
116133
]);
117134
} finally {

dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<head>
55
<script type="application/javascript" src="pc.js"></script>
66
<script type="application/javascript" src="sdpUtils.js"></script>
7+
<script type="application/javascript" src="iceTestUtils.js"></script>
78
</head>
89

910
<body>
@@ -580,6 +581,187 @@
580581
ok(preferredVideoCodec == 6, "checkLoggingMultipleTransceivers glean should show preferred video codec VP8 " + preferredVideoCodec);
581582
},
582583

584+
async function checkDtlsHandshakeSuccess() {
585+
const pc1 = new RTCPeerConnection();
586+
const pc2 = new RTCPeerConnection();
587+
await gleanResetTestValues();
588+
let client_successes = await GleanTest.webrtcdtls.clientHandshakeResult.SUCCESS.testGetValue() || 0;
589+
let server_successes = await GleanTest.webrtcdtls.serverHandshakeResult.SUCCESS.testGetValue() || 0;
590+
let cipher_count = await GleanTest.webrtcdtls.cipher["0x1301"].testGetValue() || 0;
591+
let srtp_cipher_count = await GleanTest.webrtcdtls.srtpCipher["0x0007"].testGetValue() || 0;
592+
is(client_successes, 0);
593+
is(server_successes, 0);
594+
is(cipher_count, 0);
595+
is(srtp_cipher_count, 0);
596+
597+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
598+
pc1.addTrack(stream.getTracks()[0]);
599+
600+
await connect(pc1, pc2, 32000, "DTLS connected", true, true);
601+
602+
client_successes = await GleanTest.webrtcdtls.clientHandshakeResult.SUCCESS.testGetValue() || 0;
603+
server_successes = await GleanTest.webrtcdtls.serverHandshakeResult.SUCCESS.testGetValue() || 0;
604+
cipher_count = await GleanTest.webrtcdtls.cipher["0x1301"].testGetValue() || 0;
605+
srtp_cipher_count = await GleanTest.webrtcdtls.srtpCipher["0x0007"].testGetValue() || 0;
606+
is(client_successes, 1);
607+
is(server_successes, 1);
608+
is(cipher_count, 2);
609+
is(srtp_cipher_count, 2);
610+
},
611+
612+
async function checkDtlsCipherPrefs() {
613+
await withPrefs([["security.tls13.aes_128_gcm_sha256", false],
614+
["security.tls13.aes_256_gcm_sha384", false],
615+
["security.tls13.chacha20_poly1305_sha256", true]],
616+
async () => {
617+
const pc1 = new RTCPeerConnection();
618+
const pc2 = new RTCPeerConnection();
619+
await gleanResetTestValues();
620+
let cipher_count = await GleanTest.webrtcdtls.cipher["0x1303"].testGetValue() || 0;
621+
is(cipher_count, 0);
622+
623+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
624+
pc1.addTrack(stream.getTracks()[0]);
625+
626+
await connect(pc1, pc2, 32000, "DTLS connected", true, true);
627+
628+
cipher_count = await GleanTest.webrtcdtls.cipher["0x1303"].testGetValue() || 0;
629+
is(cipher_count, 2);
630+
});
631+
},
632+
633+
async function checkDtlsHandshakeFailure() {
634+
// We don't have many failures we can induce here, but messing up the
635+
// fingerprint is one way.
636+
const offerer = new RTCPeerConnection();
637+
const answerer = new RTCPeerConnection();
638+
await gleanResetTestValues();
639+
let client_failures = await GleanTest.webrtcdtls.clientHandshakeResult.SSL_ERROR_BAD_CERTIFICATE.testGetValue() || 0;
640+
let server_failures = await GleanTest.webrtcdtls.serverHandshakeResult.SSL_ERROR_BAD_CERT_ALERT.testGetValue() || 0;
641+
is(client_failures, 0);
642+
is(server_failures, 0);
643+
644+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
645+
offerer.addTrack(stream.getTracks()[0]);
646+
647+
trickleIce(offerer, answerer);
648+
trickleIce(answerer, offerer);
649+
await offerer.setLocalDescription();
650+
let badSdp = offerer.localDescription.sdp;
651+
// Tweak the last digit in the fingerprint sent to the answerer. Answerer
652+
// (which will be the DTLS client) will get an SSL_ERROR_BAD_CERTIFICATE
653+
// error, and the offerer (which will be the DTLS server) will get an
654+
// SSL_ERROR_BAD_CERT_ALERT.
655+
const lastDigit = badSdp.match(/a=fingerprint:.*([0-9A-F])$/m)[1];
656+
const newLastDigit = lastDigit == '0' ? '1' : '0';
657+
badSdp = badSdp.replace(/(a=fingerprint:.*)[0-9A-F]$/m, "$1" + newLastDigit);
658+
info(badSdp);
659+
await answerer.setRemoteDescription({sdp: badSdp, type: "offer"});
660+
await answerer.setLocalDescription();
661+
await offerer.setRemoteDescription(answerer.localDescription);
662+
663+
const throwOnTimeout = async () => {
664+
await wait(32000);
665+
throw new Error(
666+
`ICE did not complete within ${timeout} ms`);
667+
};
668+
669+
const connectionPromises = [connectionStateReached(offerer, "failed"),
670+
connectionStateReached(answerer, "failed")];
671+
672+
await Promise.race([
673+
Promise.all(connectionPromises),
674+
throwOnTimeout()
675+
]);
676+
677+
client_failures = await GleanTest.webrtcdtls.clientHandshakeResult.SSL_ERROR_BAD_CERTIFICATE.testGetValue() || 0;
678+
server_failures = await GleanTest.webrtcdtls.serverHandshakeResult.SSL_ERROR_BAD_CERT_ALERT.testGetValue() || 0;
679+
is(client_failures, 1);
680+
is(server_failures, 1);
681+
},
682+
683+
async function checkDtlsVersion1_3() {
684+
// 1.3 should be the default
685+
const pc1 = new RTCPeerConnection();
686+
const pc2 = new RTCPeerConnection();
687+
await gleanResetTestValues();
688+
let count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
689+
let count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
690+
let count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
691+
is(count1_0, 0);
692+
is(count1_2, 0);
693+
is(count1_3, 0);
694+
695+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
696+
pc1.addTrack(stream.getTracks()[0]);
697+
698+
await connect(pc1, pc2, 32000, "DTLS connected", true, true);
699+
700+
count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
701+
count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
702+
count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
703+
is(count1_0, 0);
704+
is(count1_2, 0);
705+
is(count1_3, 2);
706+
},
707+
708+
async function checkDtlsVersion1_2() {
709+
// Make 1.2 the default
710+
await withPrefs([["media.peerconnection.dtls.version.max", 771]],
711+
async () => {
712+
const pc1 = new RTCPeerConnection();
713+
const pc2 = new RTCPeerConnection();
714+
await gleanResetTestValues();
715+
let count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
716+
let count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
717+
let count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
718+
is(count1_0, 0);
719+
is(count1_2, 0);
720+
is(count1_3, 0);
721+
722+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
723+
pc1.addTrack(stream.getTracks()[0]);
724+
725+
await connect(pc1, pc2, 32000, "DTLS connected", true, true);
726+
727+
count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
728+
count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
729+
count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
730+
is(count1_0, 0);
731+
is(count1_2, 2);
732+
is(count1_3, 0);
733+
});
734+
},
735+
736+
async function checkDtlsVersion1_0() {
737+
// Make 1.0 the default
738+
await withPrefs([["media.peerconnection.dtls.version.max", 770],
739+
["media.peerconnection.dtls.version.min", 770]],
740+
async () => {
741+
const pc1 = new RTCPeerConnection();
742+
const pc2 = new RTCPeerConnection();
743+
await gleanResetTestValues();
744+
let count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
745+
let count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
746+
let count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
747+
is(count1_0, 0);
748+
is(count1_2, 0);
749+
is(count1_3, 0);
750+
751+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
752+
pc1.addTrack(stream.getTracks()[0]);
753+
754+
await connect(pc1, pc2, 32000, "DTLS connected", true, true);
755+
756+
count1_0 = await GleanTest.webrtcdtls.protocolVersion["1.0"].testGetValue() || 0;
757+
count1_2 = await GleanTest.webrtcdtls.protocolVersion["1.2"].testGetValue() || 0;
758+
count1_3 = await GleanTest.webrtcdtls.protocolVersion["1.3"].testGetValue() || 0;
759+
is(count1_0, 2);
760+
is(count1_2, 0);
761+
is(count1_3, 0);
762+
});
763+
},
764+
583765
];
584766

585767
runNetworkTest(async () => {

0 commit comments

Comments
 (0)