Bug 1868380 - Add glean telemetry for HTTPS-First r=freddyb,simonf

- Add new glean metrics.yaml for dom/security
- Add 8 new metrics for (schemeless) HTTPS-First, see metrics.yaml for
  explanation
- Add getter for timing on document load listener
- Provide `nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest` with
  complete document load listener so that it is able to access the timing
- Adapt browser_httpsfirst.js and browser_schemeless.js tests to also check
  for correct telemetry values

Differential Revision: https://phabricator.services.mozilla.com/D196072
This commit is contained in:
Malte Juergens 2024-03-06 17:19:35 +00:00
parent e5cdcfa441
commit aafba62d3d
8 changed files with 282 additions and 12 deletions

153
dom/security/metrics.yaml Normal file
View File

@ -0,0 +1,153 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Adding a new metric? We have docs for that!
# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
---
$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
$tags:
- 'Core :: DOM: Security'
httpsfirst:
upgraded:
type: counter
description: >
Counts how often a load is marked to be upgraded to HTTPS because of
HTTPS-First (`dom.security.https_first` enabled).
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10
data_sensitivity:
- technical
notification_emails:
- mjurgens@mozilla.com
- seceng-telemetry@mozilla.com
expires: never
upgraded_schemeless:
type: counter
description: >
Counts how often a load is marked to be upgraded to HTTPS because of
schemeless HTTPS-First (`dom.security.https_first` disabled, but load
marked as schemeless).
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10
data_sensitivity:
- technical
notification_emails:
- mjurgens@mozilla.com
- seceng-telemetry@mozilla.com
expires: never
downgraded:
type: counter
description: >
How many regular HTTPS-First (`dom.security.https_first` enabled)
upgrades get downgraded again.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10
data_sensitivity:
- technical
notification_emails:
- mjurgens@mozilla.com
- seceng-telemetry@mozilla.com
expires: never
downgraded_schemeless:
type: counter
description: >
How many schemeless HTTPS-First (`dom.security.https_first` disabled, but
load marked as schemeless) upgrades get downgraded again.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10
data_sensitivity:
- technical
notification_emails:
- mjurgens@mozilla.com
- seceng-telemetry@mozilla.com
expires: never
downgraded_on_timer:
type: rate
description: >
How many HTTPS-First (`dom.security.https_first` enabled) upgrades get
downgraded again because the HTTP request fired after 3s received a answer
faster than the HTTPS request.
denominator_metric: httpsfirst.downgraded
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10
data_sensitivity:
- technical
notification_emails:
- mjurgens@mozilla.com
- seceng-telemetry@mozilla.com
expires: never
downgraded_on_timer_schemeless:
type: rate
description: >
How many of schemeless HTTPS-First (`dom.security.https_first` disabled,
but load marked as schemeless) upgrades get downgraded again because the
HTTP request fired after 3s received a answer faster than the HTTPS
request
denominator_metric: httpsfirst.downgraded_schemeless
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10
data_sensitivity:
- technical
notification_emails:
- mjurgens@mozilla.com
- seceng-telemetry@mozilla.com
expires: never
downgrade_time:
type: timing_distribution
description: >
If a HTTPS-First (`dom.security.https_first` enabled) upgrade isn't
successful, measures the timespan between the navigation start and the
downgrade. This is essentially the overhead caused by HTTPS-First if a
site does not support HTTPS.
time_unit: millisecond
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10
data_sensitivity:
- technical
notification_emails:
- mjurgens@mozilla.com
- seceng-telemetry@mozilla.com
expires: never
downgrade_time_schemeless:
type: timing_distribution
description: >
If a schemeless HTTPS-First (`dom.security.https_first` disabled, but
load marked as schemeless) upgrade isn't successful, measures the
timespan between the navigation start and the downgrade. This is
essentially the overhead caused by HTTPS-First if a site does not support
HTTPS.
time_unit: millisecond
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380#c10
data_sensitivity:
- technical
notification_emails:
- mjurgens@mozilla.com
- seceng-telemetry@mozilla.com
expires: never

View File

@ -6,6 +6,8 @@
#include "mozilla/Components.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/glean/GleanMetrics.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/net/DNS.h"
@ -438,7 +440,7 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI,
// We can upgrade the request - let's log to the console and set the status
// so we know that we upgraded the request.
if (aLoadInfo->GetWasSchemelessInput() &&
mozilla::StaticPrefs::dom_security_https_first_schemeless()) {
!IsHttpsFirstModeEnabled(isPrivateWin)) {
nsAutoCString urlCString;
aURI->GetSpec(urlCString);
NS_ConvertUTF8toUTF16 urlString(urlCString);
@ -447,6 +449,8 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI,
nsHTTPSOnlyUtils::LogLocalizedString("HTTPSFirstSchemeless", params,
nsIScriptError::warningFlag, aLoadInfo,
aURI, true);
mozilla::glean::httpsfirst::upgraded_schemeless.Add();
} else {
nsAutoCString scheme;
@ -461,7 +465,12 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI,
isSpeculative ? "HTTPSOnlyUpgradeSpeculativeConnection"
: "HTTPSOnlyUpgradeRequest",
params, nsIScriptError::warningFlag, aLoadInfo, aURI, true);
if (!isSpeculative) {
mozilla::glean::httpsfirst::upgraded.Add();
}
}
// Set flag so we know that we upgraded the request
httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST;
aLoadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
@ -470,9 +479,11 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeHttpsFirstRequest(nsIURI* aURI,
/* static */
already_AddRefed<nsIURI>
nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(nsIChannel* aChannel,
nsresult aStatus) {
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(
mozilla::net::DocumentLoadListener* aDocumentLoadListener,
nsresult aStatus) {
nsCOMPtr<nsIChannel> channel = aDocumentLoadListener->GetChannel();
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
uint32_t httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
// Only downgrade if we this request was upgraded using HTTPS-First Mode
if (!(httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UPGRADED_HTTPS_FIRST)) {
@ -488,7 +499,7 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(nsIChannel* aChannel,
// to check each NS_OK for those errors.
// Only downgrade an NS_OK status if it is an 4xx or 5xx error.
if (NS_SUCCEEDED(aStatus)) {
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
// If no httpChannel exists we have nothing to do here.
if (!httpChannel) {
return nullptr;
@ -532,7 +543,7 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(nsIChannel* aChannel,
}
nsCOMPtr<nsIURI> uri;
nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
nsresult rv = channel->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, nullptr);
nsAutoCString spec;
@ -584,6 +595,33 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(nsIChannel* aChannel,
nsIScriptError::warningFlag, loadInfo,
uri, true);
// Record telemety
nsDOMNavigationTiming* timing = aDocumentLoadListener->GetTiming();
if (timing) {
mozilla::TimeStamp navigationStart = timing->GetNavigationStartTimeStamp();
if (navigationStart) {
mozilla::TimeDuration duration =
mozilla::TimeStamp::Now() - navigationStart;
bool isPrivateWin =
loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
if (loadInfo->GetWasSchemelessInput() &&
!IsHttpsFirstModeEnabled(isPrivateWin)) {
mozilla::glean::httpsfirst::downgraded_schemeless.Add();
if (timing) {
mozilla::glean::httpsfirst::downgrade_time_schemeless
.AccumulateRawDuration(duration);
}
} else {
mozilla::glean::httpsfirst::downgraded.Add();
if (timing) {
mozilla::glean::httpsfirst::downgrade_time.AccumulateRawDuration(
duration);
}
}
}
}
return newURI.forget();
}
@ -954,6 +992,19 @@ TestHTTPAnswerRunnable::OnStartRequest(nsIRequest* aRequest) {
nsresult httpsOnlyChannelStatus;
httpsOnlyChannel->GetStatus(&httpsOnlyChannelStatus);
if (httpsOnlyChannelStatus == NS_OK) {
bool isPrivateWin =
loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
if (!nsHTTPSOnlyUtils::IsHttpsOnlyModeEnabled(isPrivateWin)) {
// Record HTTPS-First Telemetry
if (loadInfo->GetWasSchemelessInput() &&
!nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) {
mozilla::glean::httpsfirst::downgraded_on_timer_schemeless
.AddToNumerator();
} else {
mozilla::glean::httpsfirst::downgraded_on_timer.AddToNumerator();
}
}
httpsOnlyChannel->Cancel(NS_ERROR_NET_TIMEOUT_EXTERNAL);
}
}

View File

@ -95,12 +95,13 @@ class nsHTTPSOnlyUtils {
/**
* Determines if the request was previously upgraded with HTTPS-First, creates
* a downgraded URI and logs to console.
* @param aStatus Status code
* @param aChannel Failed channel
* @return URI with http-scheme or nullptr
* @param aStatus Status code
* @param aDocumentLoadListener Failed document load listener
* @return URI with http-scheme or nullptr
*/
static already_AddRefed<nsIURI> PotentiallyDowngradeHttpsFirstRequest(
nsIChannel* aChannel, nsresult aStatus);
mozilla::net::DocumentLoadListener* aDocumentLoadListener,
nsresult aStatus);
/**
* Checks if the error code is on a block-list of codes that are probably

View File

@ -31,6 +31,7 @@ add_task(async function () {
await SpecialPowers.pushPrefEnv({
set: [["dom.security.https_first", false]],
});
Services.fog.testResetFOG();
await runPrefTest(
"http://example.com",
@ -42,6 +43,23 @@ add_task(async function () {
set: [["dom.security.https_first", true]],
});
for (const key of [
"upgraded",
"upgradedSchemeless",
"downgraded",
"downgradedSchemeless",
"downgradedOnTimer",
"downgradedOnTimerSchemeless",
"downgradeTime",
"downgradeTimeSchemeless",
]) {
is(
Glean.httpsfirst[key].testGetValue(),
null,
`No telemetry should have been recorded yet for ${key}`
);
}
await runPrefTest(
"http://example.com",
"Should upgrade upgradeable website",
@ -71,4 +89,21 @@ add_task(async function () {
"Should downgrade after timeout.",
"http://"
);
info("Checking expected telemetry");
is(Glean.httpsfirst.upgraded.testGetValue(), 5);
is(Glean.httpsfirst.upgradedSchemeless.testGetValue(), null);
is(Glean.httpsfirst.downgraded.testGetValue(), 3);
is(Glean.httpsfirst.downgradedSchemeless.testGetValue(), null);
is(Glean.httpsfirst.downgradedOnTimer.testGetValue().numerator, 1);
is(Glean.httpsfirst.downgradedOnTimerSchemeless.testGetValue(), null);
const downgradeSeconds =
Glean.httpsfirst.downgradeTime.testGetValue().sum / 1_000_000_000;
ok(
downgradeSeconds > 2 && downgradeSeconds < 30,
`Summed downgrade time should be above 2 and below 30 seconds (is ${downgradeSeconds.toFixed(
2
)}s)`
);
is(null, Glean.httpsfirst.downgradeTimeSchemeless.testGetValue());
});

View File

@ -153,6 +153,7 @@ async function runTest(aInput, aDesc, aExpectedScheme) {
add_task(async function () {
requestLongerTimeout(10);
Services.fog.testResetFOG();
await SpecialPowers.pushPrefEnv({
set: [
@ -183,9 +184,36 @@ add_task(async function () {
"http"
);
for (const key of [
"upgraded",
"upgradedSchemeless",
"downgraded",
"downgradedSchemeless",
"downgradedOnTimer",
"downgradedOnTimerSchemeless",
"downgradeTime",
"downgradeTimeSchemeless",
]) {
is(
Glean.httpsfirst[key].testGetValue(),
null,
`No telemetry should have been recorded yet for ${key}`
);
}
await runTest(
"example.com",
"Should upgrade upgradeable website without explicit scheme",
"https"
);
info("Checking expected telemetry");
is(Glean.httpsfirst.upgraded.testGetValue(), null);
is(Glean.httpsfirst.upgradedSchemeless.testGetValue(), 5);
is(Glean.httpsfirst.downgraded.testGetValue(), null);
is(Glean.httpsfirst.downgradedSchemeless.testGetValue(), null);
is(Glean.httpsfirst.downgradedOnTimer.testGetValue(), null);
is(Glean.httpsfirst.downgradedOnTimerSchemeless.testGetValue(), null);
is(Glean.httpsfirst.downgradeTime.testGetValue(), null);
is(Glean.httpsfirst.downgradeTimeSchemeless.testGetValue(), null);
});

View File

@ -2379,8 +2379,8 @@ bool DocumentLoadListener::MaybeHandleLoadErrorWithURIFixup(nsresult aStatus) {
// we can downgrade the scheme to HTTP again.
bool isHTTPSFirstFixup = false;
if (!newURI) {
newURI = nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(mChannel,
aStatus);
newURI =
nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(this, aStatus);
isHTTPSFirstFixup = true;
}

View File

@ -300,6 +300,7 @@ class DocumentLoadListener : public nsIInterfaceRequestor,
uint32_t GetLoadType() const { return mLoadStateLoadType; }
bool IsDownload() const { return mIsDownload; }
bool IsLoadingJSURI() const { return mIsLoadingJSURI; }
nsDOMNavigationTiming* GetTiming() { return mTiming; }
mozilla::dom::LoadingSessionHistoryInfo* GetLoadingSessionHistoryInfo() {
return mLoadingSessionHistoryInfo.get();

View File

@ -22,6 +22,7 @@ gecko_metrics = [
"dom/media/webrtc/metrics.yaml",
"dom/metrics.yaml",
"dom/performance/metrics.yaml",
"dom/security/metrics.yaml",
"gfx/metrics.yaml",
"image/decoders/metrics.yaml",
"js/xpconnect/metrics.yaml",