tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 57267658e01c0de85bf7b4b1b001e544d621d151
parent ef2d17cdc52c0fe32eac4fcdba62fdff1086b6d5
Author: smayya <smayya@mozilla.com>
Date:   Fri, 28 Nov 2025 09:05: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:
Mnetwerk/metrics.yaml | 46++++++++++++++++++++++++++++++++++++++++++++++
Anetwerk/pings.yaml | 23+++++++++++++++++++++++
Mnetwerk/protocol/http/nsHttpChannel.cpp | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Mtoolkit/components/glean/metrics_index.py | 1+
4 files changed, 180 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 @@ -9753,7 +9851,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",