"use strict"; const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js"); trr_test_setup(); let httpServerIPv4 = new HttpServer(); let httpServerIPv6 = new HttpServer(); let trrServer; let testpath = "/simple"; let httpbody = "0123456789"; let CC_IPV4 = "example_cc_ipv4.com"; let CC_IPV6 = "example_cc_ipv6.com"; Services.prefs.clearUserPref("network.dns.native-is-localhost"); XPCOMUtils.defineLazyGetter(this, "URL_CC_IPV4", function() { return `http://${CC_IPV4}:${httpServerIPv4.identity.primaryPort}${testpath}`; }); XPCOMUtils.defineLazyGetter(this, "URL_CC_IPV6", function() { return `http://${CC_IPV6}:${httpServerIPv6.identity.primaryPort}${testpath}`; }); XPCOMUtils.defineLazyGetter(this, "URL6a", function() { return `http://example6a.com:${httpServerIPv6.identity.primaryPort}${testpath}`; }); XPCOMUtils.defineLazyGetter(this, "URL6b", function() { return `http://example6b.com:${httpServerIPv6.identity.primaryPort}${testpath}`; }); const ncs = Cc[ "@mozilla.org/network/network-connectivity-service;1" ].getService(Ci.nsINetworkConnectivityService); const { TestUtils } = ChromeUtils.importESModule( "resource://testing-common/TestUtils.sys.mjs" ); registerCleanupFunction(async () => { Services.prefs.clearUserPref("network.http.speculative-parallel-limit"); Services.prefs.clearUserPref("network.captive-portal-service.testMode"); Services.prefs.clearUserPref("network.connectivity-service.IPv6.url"); Services.prefs.clearUserPref("network.connectivity-service.IPv4.url"); Services.prefs.clearUserPref("network.dns.localDomains"); trr_clear_prefs(); await httpServerIPv4.stop(); await httpServerIPv6.stop(); await trrServer.stop(); }); function makeChan(url) { let chan = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true, }).QueryInterface(Ci.nsIHttpChannel); chan.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; chan.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; chan.setTRRMode(Ci.nsIRequest.TRR_DEFAULT_MODE); return chan; } function serverHandler(metadata, response) { response.setHeader("Content-Type", "text/plain", false); response.bodyOutputStream.write(httpbody, httpbody.length); } add_task(async function test_setup() { httpServerIPv4.registerPathHandler(testpath, serverHandler); httpServerIPv4.start(-1); httpServerIPv6.registerPathHandler(testpath, serverHandler); httpServerIPv6.start_ipv6(-1); Services.prefs.setCharPref( "network.dns.localDomains", `foo.example.com, ${CC_IPV4}, ${CC_IPV6}` ); trrServer = new TRRServer(); await trrServer.start(); if (mozinfo.socketprocess_networking) { Services.dns; // Needed to trigger socket process. await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched); } Services.prefs.setIntPref("network.trr.mode", 3); Services.prefs.setCharPref( "network.trr.uri", `https://foo.example.com:${trrServer.port}/dns-query` ); await registerDoHAnswers(true, true); }); async function registerDoHAnswers(ipv4, ipv6) { let hosts = ["example6a.com", "example6b.com"]; for (const host of hosts) { let ipv4answers = []; if (ipv4) { ipv4answers = [ { name: host, ttl: 55, type: "A", flush: false, data: "127.0.0.1", }, ]; } await trrServer.registerDoHAnswers(host, "A", { answers: ipv4answers, }); let ipv6answers = []; if (ipv6) { ipv6answers = [ { name: host, ttl: 55, type: "AAAA", flush: false, data: "::1", }, ]; } await trrServer.registerDoHAnswers(host, "AAAA", { answers: ipv6answers, }); } Services.dns.clearCache(true); } let StatusCounter = function() { this._statusCount = {}; }; StatusCounter.prototype = { QueryInterface: ChromeUtils.generateQI([ "nsIInterfaceRequestor", "nsIProgressEventSink", ]), getInterface(iid) { return this.QueryInterface(iid); }, onProgress(request, progress, progressMax) {}, onStatus(request, status, statusArg) { this._statusCount[status] = 1 + (this._statusCount[status] || 0); }, }; let HttpListener = function(finish, succeeded) { this.finish = finish; this.succeeded = succeeded; }; HttpListener.prototype = { onStartRequest: function testOnStartRequest(request) {}, onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) { read_stream(stream, cnt); }, onStopRequest: function testOnStopRequest(request, status) { equal(this.succeeded, status == Cr.NS_OK); this.finish(); }, }; function promiseObserverNotification(topic, matchFunc) { return new Promise((resolve, reject) => { Services.obs.addObserver(function observe(subject, topic, data) { let matches = typeof matchFunc != "function" || matchFunc(subject, data); if (!matches) { return; } Services.obs.removeObserver(observe, topic); resolve({ subject, data }); }, topic); }); } async function make_request(uri, check_events, succeeded) { let chan = makeChan(uri); let statusCounter = new StatusCounter(); chan.notificationCallbacks = statusCounter; await new Promise(resolve => chan.asyncOpen(new HttpListener(resolve, succeeded)) ); if (check_events) { equal( statusCounter._statusCount[0x804b000b] || 0, 1, "Expecting only one instance of NS_NET_STATUS_RESOLVED_HOST" ); equal( statusCounter._statusCount[0x804b0007] || 0, 1, "Expecting only one instance of NS_NET_STATUS_CONNECTING_TO" ); } } async function setup_connectivity(ipv6, ipv4) { Services.prefs.setBoolPref("network.captive-portal-service.testMode", true); if (ipv6) { Services.prefs.setCharPref( "network.connectivity-service.IPv6.url", URL_CC_IPV6 + testpath ); } else { Services.prefs.setCharPref( "network.connectivity-service.IPv6.url", "http://donotexist.example.com" ); } if (ipv4) { Services.prefs.setCharPref( "network.connectivity-service.IPv4.url", URL_CC_IPV4 + testpath ); } else { Services.prefs.setCharPref( "network.connectivity-service.IPv4.url", "http://donotexist.example.com" ); } let topic = "network:connectivity-service:ip-checks-complete"; if (mozinfo.socketprocess_networking) { topic += "-from-socket-process"; } let observerNotification = promiseObserverNotification(topic); ncs.recheckIPConnectivity(); await observerNotification; if (!ipv6) { equal( ncs.IPv6, Ci.nsINetworkConnectivityService.NOT_AVAILABLE, "Check IPv6 support" ); } else { equal(ncs.IPv6, Ci.nsINetworkConnectivityService.OK, "Check IPv6 support"); } if (!ipv4) { equal( ncs.IPv4, Ci.nsINetworkConnectivityService.NOT_AVAILABLE, "Check IPv4 support" ); } else { equal(ncs.IPv4, Ci.nsINetworkConnectivityService.OK, "Check IPv4 support"); } } // This test that we retry to connect using IPv4 when IPv6 connecivity is not // present, but a ConnectionEntry have IPv6 prefered set. // Speculative connections are disabled. add_task(async function test_prefer_address_version_fail_trr3_1() { Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0); await registerDoHAnswers(true, true); // Make a request to setup the address version preference to a ConnectionEntry. await make_request(URL6a, true, true); // connect again using the address version preference from the ConnectionEntry. await make_request(URL6a, true, true); // Make IPv6 connectivity check fail await setup_connectivity(false, true); Services.dns.clearCache(true); // This will succeed as we query both DNS records await make_request(URL6a, true, true); // Now make the DNS server only return IPv4 records await registerDoHAnswers(true, false); // This will fail, because the server is not lisenting to IPv4 address as well, // We should still get NS_NET_STATUS_RESOLVED_HOST and // NS_NET_STATUS_CONNECTING_TO notification. await make_request(URL6a, true, false); // Make IPv6 connectivity check succeed again await setup_connectivity(true, true); }); // This test that we retry to connect using IPv4 when IPv6 connecivity is not // present, but a ConnectionEntry have IPv6 prefered set. // Speculative connections are enabled. add_task(async function test_prefer_address_version_fail_trr3_2() { Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6); await registerDoHAnswers(true, true); // Make a request to setup the address version preference to a ConnectionEntry. await make_request(URL6b, false, true); // connect again using the address version preference from the ConnectionEntry. await make_request(URL6b, false, true); // Make IPv6 connectivity check fail await setup_connectivity(false, true); Services.dns.clearCache(true); // This will succeed as we query both DNS records await make_request(URL6b, false, true); // Now make the DNS server only return IPv4 records await registerDoHAnswers(true, false); // This will fail, because the server is not lisenting to IPv4 address as well, // We should still get NS_NET_STATUS_RESOLVED_HOST and // NS_NET_STATUS_CONNECTING_TO notification. await make_request(URL6b, true, false); });