test_dns_retry.js (9392B)
1 "use strict"; 2 3 const { HttpServer } = ChromeUtils.importESModule( 4 "resource://testing-common/httpd.sys.mjs" 5 ); 6 trr_test_setup(); 7 let httpServerIPv4 = new HttpServer(); 8 let httpServerIPv6 = new HttpServer(); 9 let trrServer; 10 let testpath = "/simple"; 11 let httpbody = "0123456789"; 12 let CC_IPV4 = "example_cc_ipv4.com"; 13 let CC_IPV6 = "example_cc_ipv6.com"; 14 Services.prefs.clearUserPref("network.dns.native-is-localhost"); 15 16 ChromeUtils.defineLazyGetter(this, "URL_CC_IPV4", function () { 17 return `http://${CC_IPV4}:${httpServerIPv4.identity.primaryPort}${testpath}`; 18 }); 19 ChromeUtils.defineLazyGetter(this, "URL_CC_IPV6", function () { 20 return `http://${CC_IPV6}:${httpServerIPv6.identity.primaryPort}${testpath}`; 21 }); 22 ChromeUtils.defineLazyGetter(this, "URL6a", function () { 23 return `http://example6a.com:${httpServerIPv6.identity.primaryPort}${testpath}`; 24 }); 25 ChromeUtils.defineLazyGetter(this, "URL6b", function () { 26 return `http://example6b.com:${httpServerIPv6.identity.primaryPort}${testpath}`; 27 }); 28 29 const ncs = Cc[ 30 "@mozilla.org/network/network-connectivity-service;1" 31 ].getService(Ci.nsINetworkConnectivityService); 32 const { TestUtils } = ChromeUtils.importESModule( 33 "resource://testing-common/TestUtils.sys.mjs" 34 ); 35 36 registerCleanupFunction(async () => { 37 Services.prefs.clearUserPref("network.http.speculative-parallel-limit"); 38 Services.prefs.clearUserPref("network.captive-portal-service.testMode"); 39 Services.prefs.clearUserPref("network.connectivity-service.IPv6.url"); 40 Services.prefs.clearUserPref("network.connectivity-service.IPv4.url"); 41 Services.prefs.clearUserPref("network.dns.localDomains"); 42 43 trr_clear_prefs(); 44 await httpServerIPv4.stop(); 45 await httpServerIPv6.stop(); 46 await trrServer.stop(); 47 }); 48 49 function makeChan(url) { 50 let chan = NetUtil.newChannel({ 51 uri: url, 52 loadUsingSystemPrincipal: true, 53 }).QueryInterface(Ci.nsIHttpChannel); 54 chan.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; 55 chan.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; 56 chan.setTRRMode(Ci.nsIRequest.TRR_DEFAULT_MODE); 57 return chan; 58 } 59 60 function serverHandler(metadata, response) { 61 response.setHeader("Content-Type", "text/plain", false); 62 response.bodyOutputStream.write(httpbody, httpbody.length); 63 } 64 65 add_task(async function test_setup() { 66 httpServerIPv4.registerPathHandler(testpath, serverHandler); 67 httpServerIPv4.start(-1); 68 httpServerIPv6.registerPathHandler(testpath, serverHandler); 69 httpServerIPv6.start_ipv6(-1); 70 Services.prefs.setCharPref( 71 "network.dns.localDomains", 72 `foo.example.com, ${CC_IPV4}, ${CC_IPV6}` 73 ); 74 75 trrServer = new TRRServer(); 76 await trrServer.start(); 77 78 if (mozinfo.socketprocess_networking) { 79 Services.dns; // Needed to trigger socket process. 80 await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched); 81 } 82 83 Services.prefs.setIntPref("network.trr.mode", 3); 84 Services.prefs.setCharPref( 85 "network.trr.uri", 86 `https://foo.example.com:${trrServer.port()}/dns-query` 87 ); 88 89 await registerDoHAnswers(true, true); 90 }); 91 92 async function registerDoHAnswers(ipv4, ipv6) { 93 let hosts = ["example6a.com", "example6b.com"]; 94 for (const host of hosts) { 95 let ipv4answers = []; 96 if (ipv4) { 97 ipv4answers = [ 98 { 99 name: host, 100 ttl: 55, 101 type: "A", 102 flush: false, 103 data: "127.0.0.1", 104 }, 105 ]; 106 } 107 await trrServer.registerDoHAnswers(host, "A", { 108 answers: ipv4answers, 109 }); 110 111 let ipv6answers = []; 112 if (ipv6) { 113 ipv6answers = [ 114 { 115 name: host, 116 ttl: 55, 117 type: "AAAA", 118 flush: false, 119 data: "::1", 120 }, 121 ]; 122 } 123 124 await trrServer.registerDoHAnswers(host, "AAAA", { 125 answers: ipv6answers, 126 }); 127 } 128 129 Services.dns.clearCache(true); 130 } 131 132 let StatusCounter = function () { 133 this._statusCount = {}; 134 }; 135 StatusCounter.prototype = { 136 QueryInterface: ChromeUtils.generateQI([ 137 "nsIInterfaceRequestor", 138 "nsIProgressEventSink", 139 ]), 140 141 getInterface(iid) { 142 return this.QueryInterface(iid); 143 }, 144 145 onProgress() {}, 146 onStatus(request, status) { 147 this._statusCount[status] = 1 + (this._statusCount[status] || 0); 148 }, 149 }; 150 151 let HttpListener = function (finish, succeeded) { 152 this.finish = finish; 153 this.succeeded = succeeded; 154 }; 155 156 HttpListener.prototype = { 157 onStartRequest: function testOnStartRequest() {}, 158 159 onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) { 160 read_stream(stream, cnt); 161 }, 162 163 onStopRequest: function testOnStopRequest(request, status) { 164 equal(this.succeeded, status == Cr.NS_OK); 165 this.finish(); 166 }, 167 }; 168 169 function promiseObserverNotification(aTopic, matchFunc) { 170 return new Promise(resolve => { 171 Services.obs.addObserver(function observe(subject, topic, data) { 172 let matches = typeof matchFunc != "function" || matchFunc(subject, data); 173 if (!matches) { 174 return; 175 } 176 Services.obs.removeObserver(observe, topic); 177 resolve({ subject, data }); 178 }, aTopic); 179 }); 180 } 181 182 async function make_request(uri, check_events, succeeded) { 183 let chan = makeChan(uri); 184 let statusCounter = new StatusCounter(); 185 chan.notificationCallbacks = statusCounter; 186 await new Promise(resolve => 187 chan.asyncOpen(new HttpListener(resolve, succeeded)) 188 ); 189 190 if (check_events) { 191 equal( 192 statusCounter._statusCount[0x4b000b] || 0, 193 1, 194 "Expecting only one instance of NS_NET_STATUS_RESOLVED_HOST" 195 ); 196 equal( 197 statusCounter._statusCount[0x4b0007] || 0, 198 1, 199 "Expecting only one instance of NS_NET_STATUS_CONNECTING_TO" 200 ); 201 } 202 } 203 204 async function setup_connectivity(ipv6, ipv4) { 205 Services.prefs.setBoolPref("network.captive-portal-service.testMode", true); 206 207 if (ipv6) { 208 Services.prefs.setCharPref( 209 "network.connectivity-service.IPv6.url", 210 URL_CC_IPV6 + testpath 211 ); 212 } else { 213 Services.prefs.setCharPref( 214 "network.connectivity-service.IPv6.url", 215 "http://donotexist.example.com" 216 ); 217 } 218 219 if (ipv4) { 220 Services.prefs.setCharPref( 221 "network.connectivity-service.IPv4.url", 222 URL_CC_IPV4 + testpath 223 ); 224 } else { 225 Services.prefs.setCharPref( 226 "network.connectivity-service.IPv4.url", 227 "http://donotexist.example.com" 228 ); 229 } 230 231 let topic = "network:connectivity-service:ip-checks-complete"; 232 if (mozinfo.socketprocess_networking) { 233 topic += "-from-socket-process"; 234 } 235 let observerNotification = promiseObserverNotification(topic); 236 ncs.recheckIPConnectivity(); 237 await observerNotification; 238 239 if (!ipv6) { 240 equal( 241 ncs.IPv6, 242 Ci.nsINetworkConnectivityService.NOT_AVAILABLE, 243 "Check IPv6 support" 244 ); 245 } else { 246 equal(ncs.IPv6, Ci.nsINetworkConnectivityService.OK, "Check IPv6 support"); 247 } 248 249 if (!ipv4) { 250 equal( 251 ncs.IPv4, 252 Ci.nsINetworkConnectivityService.NOT_AVAILABLE, 253 "Check IPv4 support" 254 ); 255 } else { 256 equal(ncs.IPv4, Ci.nsINetworkConnectivityService.OK, "Check IPv4 support"); 257 } 258 } 259 260 // This test that we retry to connect using IPv4 when IPv6 connecivity is not 261 // present, but a ConnectionEntry have IPv6 prefered set. 262 // Speculative connections are disabled. 263 add_task(async function test_prefer_address_version_fail_trr3_1() { 264 Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0); 265 await registerDoHAnswers(true, true); 266 267 // Make a request to setup the address version preference to a ConnectionEntry. 268 await make_request(URL6a, true, true); 269 270 // connect again using the address version preference from the ConnectionEntry. 271 await make_request(URL6a, true, true); 272 273 // Make IPv6 connectivity check fail 274 await setup_connectivity(false, true); 275 276 Services.dns.clearCache(true); 277 278 // This will succeed as we query both DNS records 279 await make_request(URL6a, true, true); 280 281 // Now make the DNS server only return IPv4 records 282 await registerDoHAnswers(true, false); 283 // This will fail, because the server is not lisenting to IPv4 address as well, 284 // We should still get NS_NET_STATUS_RESOLVED_HOST and 285 // NS_NET_STATUS_CONNECTING_TO notification. 286 await make_request(URL6a, true, false); 287 288 // Make IPv6 connectivity check succeed again 289 await setup_connectivity(true, true); 290 }); 291 292 // This test that we retry to connect using IPv4 when IPv6 connecivity is not 293 // present, but a ConnectionEntry have IPv6 prefered set. 294 // Speculative connections are enabled. 295 add_task(async function test_prefer_address_version_fail_trr3_2() { 296 Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6); 297 await registerDoHAnswers(true, true); 298 299 // Make a request to setup the address version preference to a ConnectionEntry. 300 await make_request(URL6b, false, true); 301 302 // connect again using the address version preference from the ConnectionEntry. 303 await make_request(URL6b, false, true); 304 305 // Make IPv6 connectivity check fail 306 await setup_connectivity(false, true); 307 308 Services.dns.clearCache(true); 309 310 // This will succeed as we query both DNS records 311 await make_request(URL6b, false, true); 312 313 // Now make the DNS server only return IPv4 records 314 await registerDoHAnswers(true, false); 315 // This will fail, because the server is not lisenting to IPv4 address as well, 316 // We should still get NS_NET_STATUS_RESOLVED_HOST and 317 // NS_NET_STATUS_CONNECTING_TO notification. 318 await make_request(URL6b, true, false); 319 });