commit ca45b023f26bc964f4987825ff0703c5827d29d0
parent eaec21428869c7fbab2ce135ca2c83605e058a08
Author: smayya <smayya@mozilla.com>
Date: Fri, 28 Nov 2025 00:08:27 +0000
Bug 1989916 - add telemetry to collect site-specific LNA data. r=necko-reviewers,toolkit-telemetry-reviewers,data-stewards,jesup
Differential Revision: https://phabricator.services.mozilla.com/D268859
Diffstat:
4 files changed, 182 insertions(+), 12 deletions(-)
diff --git a/netwerk/metrics.yaml b/netwerk/metrics.yaml
@@ -1776,6 +1776,52 @@ networking:
- necko@mozilla.com
expires: 160
+ local_network_access_connection:
+ type: event
+ description: >
+ Recorded when a site attempts to access local network resources.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1989916
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1989916#c5
+ data_sensitivity:
+ - stored_content
+ notification_emails:
+ - smayya@mozilla.com
+ - vgosu@mozilla.com
+ - necko@mozilla.com
+ send_in_pings:
+ - local-network-access
+ expires: 150
+ extra_keys:
+ top_level_site:
+ description: The base domain (eTLD+1) of the top-level document/main window.
+ type: string
+ initiator:
+ description: The base domain (eTLD+1) of the site that triggered the LNA request (triggering principal).
+ type: string
+ target_host:
+ description: The base domain (eTLD+1) of the target host being accessed.
+ type: string
+ target_ip:
+ description: The IP address the target URL resolves to.
+ type: string
+ target_port:
+ description: The port being accessed.
+ type: quantity
+ protocol:
+ description: The protocol used for the connection (websocket/webtransport/http/https/fetch/xhr).
+ type: string
+ is_secure_context:
+ description: Whether the request is from a secure context.
+ type: boolean
+ prompt_action:
+ description: The user action on the permission prompt (allow/deny/dismissed/not_shown).
+ type: string
+ load_success:
+ description: Whether the load/connection was successful.
+ type: boolean
+
http_channel_sub_open_to_first_sent_https_rr:
type: timing_distribution
time_unit: millisecond
diff --git a/netwerk/pings.yaml b/netwerk/pings.yaml
@@ -0,0 +1,23 @@
+# 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/.
+
+---
+$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
+
+local-network-access:
+ description: |
+ A ping representing Local Network Access (LNA) connection attempts and
+ permission prompt interactions. This ping does not include client_id for
+ privacy. Only collected in early Beta and Nightly builds.
+ include_client_id: false
+ metadata:
+ include_info_sections: false
+ uploader_capabilities:
+ - ohttp
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1989916
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1989916#c5
+ notification_emails:
+ - smayya@mozilla.com
diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -9123,6 +9123,14 @@ void nsHttpChannel::MaybeUpdateDocumentIPAddressSpaceFromCache() {
nsresult nsHttpChannel::OnPermissionPromptResult(bool aGranted,
const nsACString& aType) {
mWaitingForLNAPermission = false;
+
+ // Set prompt action for telemetry
+ if (aGranted) {
+ mLNAPromptAction.AssignLiteral("allow");
+ } else {
+ mLNAPromptAction.AssignLiteral("deny");
+ }
+
if (aGranted) {
LOG(
("nsHttpChannel::OnPermissionPromptResult [this=%p] "
@@ -9548,25 +9556,32 @@ static void RecordIPAddressSpaceTelemetry(bool aLoadSuccess, nsIURI* aURI,
}
}
-static void RecordLNATelemetry(bool aLoadSuccess, nsIURI* aURI,
- nsILoadInfo* aLoadInfo, NetAddr& aPeerAddr) {
- if (!aLoadInfo || !aURI) {
+static void RecordLNATelemetry(nsHttpChannel* aChannel, bool aLoadSuccess) {
+ // Extract data from channel
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ NetAddr peerAddr = aChannel->GetPeerAddr();
+ const nsACString& promptAction = aChannel->GetLNAPromptAction();
+
+ if (!loadInfo || !uri) {
return;
}
RefPtr<mozilla::dom::BrowsingContext> bc;
- aLoadInfo->GetBrowsingContext(getter_AddRefs(bc));
+ loadInfo->GetBrowsingContext(getter_AddRefs(bc));
nsILoadInfo::IPAddressSpace parentAddressSpace =
nsILoadInfo::IPAddressSpace::Unknown;
if (!bc) {
- parentAddressSpace = aLoadInfo->GetParentIpAddressSpace();
+ parentAddressSpace = loadInfo->GetParentIpAddressSpace();
} else {
parentAddressSpace = bc->GetCurrentIPAddressSpace();
}
+ // Early return if NOT LNA - don't record telemetry or log
if (!mozilla::net::IsLocalOrPrivateNetworkAccess(
- parentAddressSpace, aLoadInfo->GetIpAddressSpace())) {
+ parentAddressSpace, loadInfo->GetIpAddressSpace())) {
return;
}
@@ -9577,7 +9592,7 @@ static void RecordLNATelemetry(bool aLoadSuccess, nsIURI* aURI,
}
uint16_t port = 0;
- if (NS_SUCCEEDED(aPeerAddr.GetPort(&port))) {
+ if (NS_SUCCEEDED(peerAddr.GetPort(&port))) {
mozilla::glean::networking::local_network_access_port
.AccumulateSingleSample(port);
}
@@ -9586,24 +9601,107 @@ static void RecordLNATelemetry(bool aLoadSuccess, nsIURI* aURI,
// At this point we are sure that the request is a LNA,
// Hence we can safely assume few conditions to construct the label
nsAutoCString glean_lna_label;
- if (aLoadInfo->GetParentIpAddressSpace() ==
- nsILoadInfo::IPAddressSpace::Public) {
+ if (parentAddressSpace == nsILoadInfo::IPAddressSpace::Public) {
glean_lna_label.Append("public_to_"_ns);
} else {
glean_lna_label.Append("private_to_"_ns);
}
- if (aLoadInfo->GetIpAddressSpace() == nsILoadInfo::IPAddressSpace::Private) {
+ if (loadInfo->GetIpAddressSpace() == nsILoadInfo::IPAddressSpace::Private) {
glean_lna_label.Append("private_"_ns);
} else {
glean_lna_label.Append("local_"_ns);
}
- if (aURI->SchemeIs("https")) {
+ if (uri->SchemeIs("https")) {
glean_lna_label.Append("https"_ns);
} else {
glean_lna_label.Append("http"_ns);
}
mozilla::glean::networking::local_network_access.Get(glean_lna_label).Add(1);
+
+ // Get top-level site (main window principal) - TLD+1
+ nsAutoCString topLevelSite;
+ if (bc && bc->Top()) {
+ if (bc->Top()->Canonical()) {
+ RefPtr<mozilla::dom::WindowGlobalParent> topWindowGlobal =
+ bc->Top()->Canonical()->GetCurrentWindowGlobal();
+ if (topWindowGlobal) {
+ nsCOMPtr<nsIPrincipal> topPrincipal =
+ topWindowGlobal->DocumentPrincipal();
+ if (topPrincipal) {
+ (void)topPrincipal->GetBaseDomain(topLevelSite);
+ }
+ }
+ }
+ }
+
+ // Get initiator (triggering principal) - TLD+1
+ nsAutoCString initiator;
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ loadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal));
+ if (triggeringPrincipal) {
+ (void)triggeringPrincipal->GetBaseDomain(initiator);
+ }
+
+ bool isSecureContext =
+ triggeringPrincipal
+ ? triggeringPrincipal->GetIsOriginPotentiallyTrustworthy()
+ : false;
+
+ // Get target host - TLD+1
+ nsCOMPtr<nsIEffectiveTLDService> eTLDService =
+ mozilla::components::EffectiveTLD::Service();
+ nsAutoCString targetHost;
+ if (eTLDService) {
+ (void)eTLDService->GetBaseDomain(uri, 0, targetHost);
+ }
+
+ // Get target IP address
+ nsCString targetIp = peerAddr.ToString();
+
+ // Determine protocol from LoadInfo
+ nsAutoCString protocol;
+ ExtContentPolicyType contentType = loadInfo->GetExternalContentPolicyType();
+ switch (contentType) {
+ case ExtContentPolicyType::TYPE_WEBSOCKET:
+ protocol.AssignLiteral("websocket");
+ break;
+ case ExtContentPolicyType::TYPE_WEB_TRANSPORT:
+ protocol.AssignLiteral("webtransport");
+ break;
+ case ExtContentPolicyType::TYPE_FETCH:
+ protocol.AssignLiteral("fetch");
+ break;
+ case ExtContentPolicyType::TYPE_XMLHTTPREQUEST:
+ protocol.AssignLiteral("xhr");
+ break;
+ default:
+ if (uri->SchemeIs("https")) {
+ protocol.AssignLiteral("https");
+ } else {
+ protocol.AssignLiteral("http");
+ }
+ break;
+ }
+
+ // Record the event
+ glean::networking::LocalNetworkAccessConnectionExtra extra = {
+ .initiator = initiator.IsEmpty() ? Nothing() : Some(initiator),
+ .isSecureContext = Some(isSecureContext),
+ .loadSuccess = Some(aLoadSuccess),
+ .promptAction =
+ promptAction.IsEmpty() ? Nothing() : Some(nsCString(promptAction)),
+ .protocol = Some(protocol),
+ .targetHost = targetHost.IsEmpty() ? Nothing() : Some(targetHost),
+ .targetIp = Some(targetIp),
+ .targetPort = Some(port),
+ .topLevelSite = topLevelSite.IsEmpty() ? Nothing() : Some(topLevelSite),
+
+ };
+ glean::networking::local_network_access_connection.Record(Some(extra));
+
+ ReportLNAAccessToConsole(aChannel, "LocalNetworkAccessDetected",
+ promptAction);
}
NS_IMETHODIMP
@@ -9687,6 +9785,8 @@ nsHttpChannel::OnStopRequest(nsIRequest* request, nsresult status) {
bool isFromNet = request == mTransactionPump;
+ RecordIPAddressSpaceTelemetry(NS_SUCCEEDED(mStatus), mURI, mLoadInfo,
+ mPeerAddr);
if (mTransaction) {
// determine if we should call DoAuthRetry
bool authRetry = (mAuthRetryPending && NS_SUCCEEDED(status) &&
@@ -9753,7 +9853,7 @@ nsHttpChannel::OnStopRequest(nsIRequest* request, nsresult status) {
RecordIPAddressSpaceTelemetry(NS_SUCCEEDED(mStatus), mURI, mLoadInfo,
mPeerAddr);
- RecordLNATelemetry(NS_SUCCEEDED(mStatus), mURI, mLoadInfo, mPeerAddr);
+ RecordLNATelemetry(this, NS_SUCCEEDED(mStatus));
uint32_t flags;
if (mStatus == NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED &&
diff --git a/toolkit/components/glean/metrics_index.py b/toolkit/components/glean/metrics_index.py
@@ -207,6 +207,7 @@ metrics_yamls = sorted(
# Order is lexicographical, enforced by t/c/glean/tests/pytest/test_yaml_indices.py
gecko_pings = [
"dom/pings.yaml",
+ "netwerk/pings.yaml",
"toolkit/components/antitracking/bouncetrackingprotection/pings.yaml",
"toolkit/components/backgroundhangmonitor/pings.yaml",
"toolkit/components/captchadetection/pings.yaml",