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;
|
||||
}
|
||||
|
||||
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 mozilla
|
||||
|
||||
@@ -1188,6 +1188,9 @@ void ParseSimpleURISchemes(const nsACString& schemeList);
|
||||
|
||||
nsresult AddExtraHeaders(nsIHttpChannel* aHttpChannel,
|
||||
const nsACString& aExtraHeaders, bool aMerge = true);
|
||||
|
||||
bool IsLocalNetworkAccess(nsILoadInfo::IPAddressSpace aParentIPAddressSpace,
|
||||
nsILoadInfo::IPAddressSpace aTargetIPAddressSpace);
|
||||
} // namespace net
|
||||
} // namespace mozilla
|
||||
|
||||
|
||||
@@ -1881,6 +1881,48 @@ networking:
|
||||
- load_is_http
|
||||
- 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:
|
||||
type: timing_distribution
|
||||
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
|
||||
nsHttpChannel::OnStopRequest(nsIRequest* request, nsresult status) {
|
||||
MOZ_ASSERT(!mAsyncOpenTime.IsNull());
|
||||
@@ -8684,6 +8742,7 @@ nsHttpChannel::OnStopRequest(nsIRequest* request, nsresult status) {
|
||||
|
||||
RecordIPAddressSpaceTelemetry(NS_SUCCEEDED(mStatus), mURI, mLoadInfo,
|
||||
mPeerAddr);
|
||||
RecordLNATelemetry(NS_SUCCEEDED(mStatus), mURI, mLoadInfo, mPeerAddr);
|
||||
|
||||
// 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
|
||||
|
||||
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"]
|
||||
skip-if = ["os == 'android' && android_version == '24' && processor == 'x86_64'"]
|
||||
|
||||
["test_ip_address_space_lna_glean.js"]
|
||||
|
||||
["test_large_port.js"]
|
||||
|
||||
["test_loadgroup_cancel.js"]
|
||||
|
||||
Reference in New Issue
Block a user