Bug 1960582 - Add LNA telemetry. r=necko-reviewers,valentin
Differential Revision: https://phabricator.services.mozilla.com/D249887
This commit is contained in:
committed by
smayya@mozilla.com
parent
d28564a0af
commit
a0f3b68a20
@@ -4166,5 +4166,36 @@ nsresult AddExtraHeaders(nsIHttpChannel* aHttpChannel,
|
|||||||
return NS_OK;
|
return NS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsLocalNetworkAccess(nsILoadInfo::IPAddressSpace aParentIPAddressSpace,
|
||||||
|
nsILoadInfo::IPAddressSpace aTargetIPAddressSpace) {
|
||||||
|
// Determine if the request is moving to a more private address space
|
||||||
|
// i.e. Public -> Private or Local
|
||||||
|
// Private -> Local
|
||||||
|
// Refer
|
||||||
|
// https://wicg.github.io/private-network-access/#private-network-request-heading
|
||||||
|
// for private network access
|
||||||
|
// XXX (sunil) add link to LNA spec once it is published
|
||||||
|
|
||||||
|
if (aTargetIPAddressSpace == nsILoadInfo::IPAddressSpace::Public ||
|
||||||
|
aTargetIPAddressSpace == nsILoadInfo::IPAddressSpace::Unknown) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Check if this is an access to a local resource from Public or Private
|
||||||
|
// network
|
||||||
|
if ((aTargetIPAddressSpace == nsILoadInfo::IPAddressSpace::Local) &&
|
||||||
|
(aParentIPAddressSpace == nsILoadInfo::IPAddressSpace::Public ||
|
||||||
|
aParentIPAddressSpace == nsILoadInfo::IPAddressSpace::Private)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this is an access to a Private Network resource from a Public
|
||||||
|
// network
|
||||||
|
if ((aTargetIPAddressSpace == nsILoadInfo::IPAddressSpace::Private) &&
|
||||||
|
(aParentIPAddressSpace == nsILoadInfo::IPAddressSpace::Public)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} // namespace net
|
} // namespace net
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
|||||||
@@ -1188,6 +1188,9 @@ void ParseSimpleURISchemes(const nsACString& schemeList);
|
|||||||
|
|
||||||
nsresult AddExtraHeaders(nsIHttpChannel* aHttpChannel,
|
nsresult AddExtraHeaders(nsIHttpChannel* aHttpChannel,
|
||||||
const nsACString& aExtraHeaders, bool aMerge = true);
|
const nsACString& aExtraHeaders, bool aMerge = true);
|
||||||
|
|
||||||
|
bool IsLocalNetworkAccess(nsILoadInfo::IPAddressSpace aParentIPAddressSpace,
|
||||||
|
nsILoadInfo::IPAddressSpace aTargetIPAddressSpace);
|
||||||
} // namespace net
|
} // namespace net
|
||||||
} // namespace mozilla
|
} // namespace mozilla
|
||||||
|
|
||||||
|
|||||||
@@ -1881,6 +1881,48 @@ networking:
|
|||||||
- load_is_http
|
- load_is_http
|
||||||
- load_is_http_for_local_domain
|
- load_is_http_for_local_domain
|
||||||
|
|
||||||
|
local_network_access:
|
||||||
|
type: labeled_counter
|
||||||
|
description: >
|
||||||
|
Whether the request is crossing to a more private addresspace
|
||||||
|
bugs:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1960582
|
||||||
|
data_reviews:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1960582
|
||||||
|
notification_emails:
|
||||||
|
- smayya@mozilla.com
|
||||||
|
- vgosu@mozilla.com
|
||||||
|
- necko@gmail.com
|
||||||
|
expires: 150
|
||||||
|
labels:
|
||||||
|
- private_to_local_http
|
||||||
|
- private_to_local_https
|
||||||
|
- public_to_local_http
|
||||||
|
- public_to_local_https
|
||||||
|
- public_to_private_http
|
||||||
|
- public_to_private_https
|
||||||
|
- success
|
||||||
|
- failure
|
||||||
|
|
||||||
|
local_network_access_port:
|
||||||
|
type: custom_distribution
|
||||||
|
description: >
|
||||||
|
port used for local network access
|
||||||
|
range_min: 1
|
||||||
|
range_max: 65535
|
||||||
|
bucket_count: 65535
|
||||||
|
unit: port number
|
||||||
|
histogram_type: linear
|
||||||
|
bugs:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1960582
|
||||||
|
data_reviews:
|
||||||
|
- https://bugzilla.mozilla.org/show_bug.cgi?id=1960582
|
||||||
|
notification_emails:
|
||||||
|
- smayya@mozilla.com
|
||||||
|
- vgosu@mozilla.com
|
||||||
|
- necko@gmail.com
|
||||||
|
expires: 150
|
||||||
|
|
||||||
http_channel_sub_open_to_first_sent_https_rr:
|
http_channel_sub_open_to_first_sent_https_rr:
|
||||||
type: timing_distribution
|
type: timing_distribution
|
||||||
time_unit: millisecond
|
time_unit: millisecond
|
||||||
|
|||||||
@@ -8533,6 +8533,64 @@ static void RecordIPAddressSpaceTelemetry(bool aLoadSuccess, nsIURI* aURI,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void RecordLNATelemetry(bool aLoadSuccess, nsIURI* aURI,
|
||||||
|
nsILoadInfo* aLoadInfo, NetAddr& aPeerAddr) {
|
||||||
|
if (!aLoadInfo || !aURI) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefPtr<mozilla::dom::BrowsingContext> bc;
|
||||||
|
aLoadInfo->GetBrowsingContext(getter_AddRefs(bc));
|
||||||
|
|
||||||
|
nsILoadInfo::IPAddressSpace parentAddressSpace =
|
||||||
|
nsILoadInfo::IPAddressSpace::Unknown;
|
||||||
|
if (!bc) {
|
||||||
|
parentAddressSpace = aLoadInfo->GetParentIpAddressSpace();
|
||||||
|
} else {
|
||||||
|
parentAddressSpace = bc->GetCurrentIPAddressSpace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mozilla::net::IsLocalNetworkAccess(parentAddressSpace,
|
||||||
|
aLoadInfo->GetIpAddressSpace())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aLoadSuccess) {
|
||||||
|
mozilla::glean::networking::local_network_access.Get("success"_ns).Add(1);
|
||||||
|
} else {
|
||||||
|
mozilla::glean::networking::local_network_access.Get("failure"_ns).Add(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t port = 0;
|
||||||
|
if (NS_SUCCEEDED(aPeerAddr.GetPort(&port))) {
|
||||||
|
mozilla::glean::networking::local_network_access_port
|
||||||
|
.AccumulateSingleSample(port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// label format is <parentAddressSpace>_to_<targetAddressSpace>_<scheme>
|
||||||
|
// 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) {
|
||||||
|
glean_lna_label.Append("public_to_"_ns);
|
||||||
|
} else {
|
||||||
|
glean_lna_label.Append("private_to_"_ns);
|
||||||
|
}
|
||||||
|
if (aLoadInfo->GetIpAddressSpace() == nsILoadInfo::IPAddressSpace::Private) {
|
||||||
|
glean_lna_label.Append("private_"_ns);
|
||||||
|
} else {
|
||||||
|
glean_lna_label.Append("local_"_ns);
|
||||||
|
}
|
||||||
|
if (aURI->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);
|
||||||
|
}
|
||||||
|
|
||||||
NS_IMETHODIMP
|
NS_IMETHODIMP
|
||||||
nsHttpChannel::OnStopRequest(nsIRequest* request, nsresult status) {
|
nsHttpChannel::OnStopRequest(nsIRequest* request, nsresult status) {
|
||||||
MOZ_ASSERT(!mAsyncOpenTime.IsNull());
|
MOZ_ASSERT(!mAsyncOpenTime.IsNull());
|
||||||
@@ -8684,6 +8742,7 @@ nsHttpChannel::OnStopRequest(nsIRequest* request, nsresult status) {
|
|||||||
|
|
||||||
RecordIPAddressSpaceTelemetry(NS_SUCCEEDED(mStatus), mURI, mLoadInfo,
|
RecordIPAddressSpaceTelemetry(NS_SUCCEEDED(mStatus), mURI, mLoadInfo,
|
||||||
mPeerAddr);
|
mPeerAddr);
|
||||||
|
RecordLNATelemetry(NS_SUCCEEDED(mStatus), mURI, mLoadInfo, mPeerAddr);
|
||||||
|
|
||||||
// If we are using the transaction to serve content, we also save the
|
// If we are using the transaction to serve content, we also save the
|
||||||
// time since async open in the cache entry so we can compare telemetry
|
// time since async open in the cache entry so we can compare telemetry
|
||||||
|
|||||||
247
netwerk/test/unit/test_ip_address_space_lna_glean.js
Normal file
247
netwerk/test/unit/test_ip_address_space_lna_glean.js
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const override = Cc["@mozilla.org/network/native-dns-override;1"].getService(
|
||||||
|
Ci.nsINativeDNSResolverOverride
|
||||||
|
);
|
||||||
|
const mockNetwork = Cc[
|
||||||
|
"@mozilla.org/network/mock-network-controller;1"
|
||||||
|
].getService(Ci.nsIMockNetworkLayerController);
|
||||||
|
const certOverrideService = Cc[
|
||||||
|
"@mozilla.org/security/certoverride;1"
|
||||||
|
].getService(Ci.nsICertOverrideService);
|
||||||
|
|
||||||
|
const DOMAIN = "example.org";
|
||||||
|
|
||||||
|
function makeChan(url, expected) {
|
||||||
|
let chan = NetUtil.newChannel({
|
||||||
|
uri: url,
|
||||||
|
loadUsingSystemPrincipal: true,
|
||||||
|
contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
|
||||||
|
}).QueryInterface(Ci.nsIHttpChannel);
|
||||||
|
|
||||||
|
if (
|
||||||
|
expected.PublicToPrivateHttp !== undefined ||
|
||||||
|
expected.PublicToLocalHttp !== undefined ||
|
||||||
|
expected.PublicToPublicHttp !== undefined ||
|
||||||
|
expected.PublicToPrivateHttps !== undefined ||
|
||||||
|
expected.PublicToLocalHttps !== undefined ||
|
||||||
|
expected.PublicToPublicHttp !== undefined
|
||||||
|
) {
|
||||||
|
chan.loadInfo.parentIpAddressSpace = Ci.nsILoadInfo.Public;
|
||||||
|
} else if (
|
||||||
|
expected.PrivateToLocalHttp !== undefined ||
|
||||||
|
expected.PrivateToPrivateHttp !== undefined ||
|
||||||
|
expected.PrivateToLocalHttps !== undefined ||
|
||||||
|
expected.PrivateToPrivateHttps !== undefined
|
||||||
|
) {
|
||||||
|
chan.loadInfo.parentIpAddressSpace = Ci.nsILoadInfo.Private;
|
||||||
|
}
|
||||||
|
return chan;
|
||||||
|
}
|
||||||
|
|
||||||
|
function channelOpenPromise(chan, flags) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
function finish(req, buffer) {
|
||||||
|
resolve([req, buffer]);
|
||||||
|
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
chan.asyncOpen(new ChannelListener(finish, null, flags));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let server;
|
||||||
|
|
||||||
|
add_setup(async function setup() {
|
||||||
|
Services.prefs.setBoolPref("network.socket.attach_mock_network_layer", true);
|
||||||
|
|
||||||
|
Services.fog.initializeFOG();
|
||||||
|
|
||||||
|
server = new NodeHTTPServer();
|
||||||
|
await server.start();
|
||||||
|
registerCleanupFunction(async () => {
|
||||||
|
Services.prefs.clearUserPref("network.disable-localhost-when-offline");
|
||||||
|
Services.prefs.clearUserPref("network.dns.use_override_as_peer_address");
|
||||||
|
Services.prefs.clearUserPref("dom.security.https_only_mode");
|
||||||
|
Services.prefs.clearUserPref("dom.security.https_first");
|
||||||
|
Services.prefs.clearUserPref("dom.security.https_first_schemeless");
|
||||||
|
Services.prefs.clearUserPref("network.socket.attach_mock_network_layer");
|
||||||
|
await server.stop();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function verifyGleanValues(aDescription, aExpected) {
|
||||||
|
info(aDescription);
|
||||||
|
|
||||||
|
let privateToLocalHttp = aExpected.PrivateToLocalHttp || null;
|
||||||
|
let publicToPrivateHttp = aExpected.PublicToPrivateHttp || null;
|
||||||
|
let publicToLocalHttp = aExpected.PublicToLocalHttp || null;
|
||||||
|
let privateToLocalHttps = aExpected.PrivateToLocalHttps || null;
|
||||||
|
let publicToPrivateHttps = aExpected.PublicToPrivateHttps || null;
|
||||||
|
let publicToLocalHttps = aExpected.PublicToLocalHttps || null;
|
||||||
|
|
||||||
|
let glean = Glean.networking.localNetworkAccess;
|
||||||
|
Assert.equal(
|
||||||
|
glean.private_to_local_http.testGetValue(),
|
||||||
|
privateToLocalHttp,
|
||||||
|
"verify private_to_local_http"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
glean.public_to_private_http.testGetValue(),
|
||||||
|
publicToPrivateHttp,
|
||||||
|
"verify public_to_private_http"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
glean.public_to_local_http.testGetValue(),
|
||||||
|
publicToLocalHttp,
|
||||||
|
"verify public_to_local_http"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
glean.private_to_local_https.testGetValue(),
|
||||||
|
privateToLocalHttps,
|
||||||
|
"verify private_to_local_http"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
glean.public_to_private_https.testGetValue(),
|
||||||
|
publicToPrivateHttps,
|
||||||
|
"verify public_to_private_http"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
glean.public_to_local_https.testGetValue(),
|
||||||
|
publicToLocalHttps,
|
||||||
|
"verify public_to_local_http"
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.equal(
|
||||||
|
glean.public_to_local_https.testGetValue(),
|
||||||
|
publicToLocalHttps,
|
||||||
|
"verify public_to_local_http"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
privateToLocalHttp ||
|
||||||
|
publicToPrivateHttp ||
|
||||||
|
publicToLocalHttp ||
|
||||||
|
privateToLocalHttps ||
|
||||||
|
publicToPrivateHttps ||
|
||||||
|
publicToLocalHttps
|
||||||
|
) {
|
||||||
|
Assert.equal(
|
||||||
|
glean.success.testGetValue(),
|
||||||
|
1,
|
||||||
|
"verify local_network_access_success"
|
||||||
|
);
|
||||||
|
// XXX (sunil) add test for local_network_access_failure cases
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function do_test(ip, expected, srcPort, dstPort) {
|
||||||
|
Services.fog.testResetFOG();
|
||||||
|
|
||||||
|
override.addIPOverride(DOMAIN, ip);
|
||||||
|
let fromAddr = mockNetwork.createScriptableNetAddr(ip, srcPort ?? 80);
|
||||||
|
let toAddr = mockNetwork.createScriptableNetAddr(
|
||||||
|
fromAddr.family == Ci.nsINetAddr.FAMILY_INET ? "127.0.0.1" : "::1",
|
||||||
|
dstPort ?? server.port()
|
||||||
|
);
|
||||||
|
|
||||||
|
mockNetwork.addNetAddrOverride(fromAddr, toAddr);
|
||||||
|
|
||||||
|
info(`do_test ${ip}, ${fromAddr} -> ${toAddr}`);
|
||||||
|
|
||||||
|
let chan = makeChan(`http://${DOMAIN}`, expected);
|
||||||
|
let [req] = await channelOpenPromise(chan);
|
||||||
|
|
||||||
|
info(
|
||||||
|
"req.remoteAddress=" +
|
||||||
|
req.QueryInterface(Ci.nsIHttpChannelInternal).remoteAddress
|
||||||
|
);
|
||||||
|
|
||||||
|
if (expected.PublicToPrivateHttp) {
|
||||||
|
Assert.equal(chan.loadInfo.ipAddressSpace, Ci.nsILoadInfo.Private);
|
||||||
|
} else if (expected.PrivateToLocalHttp || expected.PrivateToLocalHttp) {
|
||||||
|
Assert.equal(chan.loadInfo.ipAddressSpace, Ci.nsILoadInfo.Local);
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyGleanValues(`test ip=${ip}`, expected);
|
||||||
|
|
||||||
|
Services.dns.clearCache(false);
|
||||||
|
override.clearOverrides();
|
||||||
|
mockNetwork.clearNetAddrOverrides();
|
||||||
|
Services.obs.notifyObservers(null, "net:prune-all-connections");
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function test_lna_http() {
|
||||||
|
Services.prefs.setBoolPref("dom.security.https_only_mode", false);
|
||||||
|
Services.prefs.setBoolPref("dom.security.https_first", false);
|
||||||
|
Services.prefs.setBoolPref("dom.security.https_first_schemeless", false);
|
||||||
|
|
||||||
|
await do_test("10.0.0.1", { PublicToPrivateHttp: 1 });
|
||||||
|
|
||||||
|
// NO LNA access do not increment
|
||||||
|
await do_test("10.0.0.1", { PrivateToPrivateHttp: 0 });
|
||||||
|
await do_test("2.2.2.2", { PublicToPublicHttp: 0 });
|
||||||
|
|
||||||
|
await do_test("100.64.0.1", { PublicToPrivateHttp: 1 });
|
||||||
|
await do_test("127.0.0.1", { PublicToLocalHttp: 1 });
|
||||||
|
await do_test("127.0.0.1", { PrivateToLocalHttp: 1 });
|
||||||
|
if (AppConstants.platform != "android") {
|
||||||
|
await do_test("::1", { PrivateToLocalHttp: 1 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_lna_https() {
|
||||||
|
Services.prefs.setBoolPref("dom.security.https_only_mode", true);
|
||||||
|
let httpsServer = new NodeHTTPSServer();
|
||||||
|
await httpsServer.start();
|
||||||
|
registerCleanupFunction(async () => {
|
||||||
|
await httpsServer.stop();
|
||||||
|
});
|
||||||
|
await do_test(
|
||||||
|
"10.0.0.1",
|
||||||
|
{ PublicToPrivateHttps: 1 },
|
||||||
|
443,
|
||||||
|
httpsServer.port()
|
||||||
|
);
|
||||||
|
|
||||||
|
// NO LNA access do not increment
|
||||||
|
await do_test(
|
||||||
|
"10.0.0.1",
|
||||||
|
{ PrivateToPrivateHttps: 0 },
|
||||||
|
443,
|
||||||
|
httpsServer.port()
|
||||||
|
);
|
||||||
|
await do_test(
|
||||||
|
"2.2.2.2",
|
||||||
|
{ PublicToPublicHttps: 0 },
|
||||||
|
443,
|
||||||
|
httpsServer.port(),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
await do_test(
|
||||||
|
"100.64.0.1",
|
||||||
|
{ PublicToPrivateHttps: 1 },
|
||||||
|
443,
|
||||||
|
httpsServer.port()
|
||||||
|
);
|
||||||
|
await do_test(
|
||||||
|
"127.0.0.1",
|
||||||
|
{ PublicToLocalHttps: 1 },
|
||||||
|
443,
|
||||||
|
httpsServer.port()
|
||||||
|
);
|
||||||
|
await do_test(
|
||||||
|
"127.0.0.1",
|
||||||
|
{ PrivateToLocalHttps: 1 },
|
||||||
|
443,
|
||||||
|
httpsServer.port()
|
||||||
|
);
|
||||||
|
if (AppConstants.platform != "android") {
|
||||||
|
await do_test("::1", { PrivateToLocalHttps: 1 }, 443, httpsServer.port());
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -911,6 +911,8 @@ run-sequentially = "node server exceptions dont replay well"
|
|||||||
["test_ip_space_glean.js"]
|
["test_ip_space_glean.js"]
|
||||||
skip-if = ["os == 'android' && android_version == '24' && processor == 'x86_64'"]
|
skip-if = ["os == 'android' && android_version == '24' && processor == 'x86_64'"]
|
||||||
|
|
||||||
|
["test_ip_address_space_lna_glean.js"]
|
||||||
|
|
||||||
["test_large_port.js"]
|
["test_large_port.js"]
|
||||||
|
|
||||||
["test_loadgroup_cancel.js"]
|
["test_loadgroup_cancel.js"]
|
||||||
|
|||||||
Reference in New Issue
Block a user