test_dns_override.js (14608B)
1 "use strict"; 2 3 const override = Cc["@mozilla.org/network/native-dns-override;1"].getService( 4 Ci.nsINativeDNSResolverOverride 5 ); 6 const defaultOriginAttributes = {}; 7 const mainThread = Services.tm.currentThread; 8 9 class Listener { 10 constructor() { 11 this.promise = new Promise(resolve => { 12 this.resolve = resolve; 13 }); 14 } 15 16 onLookupComplete(inRequest, inRecord, inStatus) { 17 this.resolve([inRequest, inRecord, inStatus]); 18 } 19 20 async firstAddress() { 21 let all = await this.addresses(); 22 if (all.length) { 23 return all[0]; 24 } 25 26 return undefined; 27 } 28 29 async addresses() { 30 let [, inRecord] = await this.promise; 31 let addresses = []; 32 if (!inRecord) { 33 return addresses; // returns [] 34 } 35 inRecord.QueryInterface(Ci.nsIDNSAddrRecord); 36 while (inRecord.hasMore()) { 37 addresses.push(inRecord.getNextAddrAsString()); 38 } 39 return addresses; 40 } 41 42 then() { 43 return this.promise.then.apply(this.promise, arguments); 44 } 45 } 46 Listener.prototype.QueryInterface = ChromeUtils.generateQI(["nsIDNSListener"]); 47 48 const DOMAIN = "example.org"; 49 const OTHER = "example.com"; 50 51 add_setup(async function setup() { 52 trr_test_setup(); 53 // For canon-name flags 54 Services.prefs.setBoolPref("network.dns.always_ai_canonname", false); 55 56 registerCleanupFunction(async () => { 57 trr_clear_prefs(); 58 }); 59 }); 60 61 add_task(async function test_bad_IPs() { 62 Assert.throws( 63 () => override.addIPOverride(DOMAIN, DOMAIN), 64 /NS_ERROR_UNEXPECTED/, 65 "Should throw if input is not an IP address" 66 ); 67 Assert.throws( 68 () => override.addIPOverride(DOMAIN, ""), 69 /NS_ERROR_UNEXPECTED/, 70 "Should throw if input is not an IP address" 71 ); 72 Assert.throws( 73 () => override.addIPOverride(DOMAIN, " "), 74 /NS_ERROR_UNEXPECTED/, 75 "Should throw if input is not an IP address" 76 ); 77 Assert.throws( 78 () => override.addIPOverride(DOMAIN, "1-2-3-4"), 79 /NS_ERROR_UNEXPECTED/, 80 "Should throw if input is not an IP address" 81 ); 82 }); 83 84 add_task(async function test_ipv4() { 85 let listener = new Listener(); 86 override.addIPOverride(DOMAIN, "1.2.3.4"); 87 Services.dns.asyncResolve( 88 DOMAIN, 89 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 90 0, 91 null, 92 listener, 93 mainThread, 94 defaultOriginAttributes 95 ); 96 Assert.equal(await listener.firstAddress(), "1.2.3.4"); 97 98 Services.dns.clearCache(false); 99 override.clearOverrides(); 100 }); 101 102 add_task(async function test_ipv6() { 103 let listener = new Listener(); 104 override.addIPOverride(DOMAIN, "fe80::6a99:9b2b:6ccc:6e1b"); 105 Services.dns.asyncResolve( 106 DOMAIN, 107 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 108 0, 109 null, 110 listener, 111 mainThread, 112 defaultOriginAttributes 113 ); 114 Assert.equal(await listener.firstAddress(), "fe80::6a99:9b2b:6ccc:6e1b"); 115 116 Services.dns.clearCache(false); 117 override.clearOverrides(); 118 }); 119 120 add_task(async function test_clearOverrides() { 121 let listener = new Listener(); 122 override.addIPOverride(DOMAIN, "1.2.3.4"); 123 Services.dns.asyncResolve( 124 DOMAIN, 125 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 126 0, 127 null, 128 listener, 129 mainThread, 130 defaultOriginAttributes 131 ); 132 Assert.equal(await listener.firstAddress(), "1.2.3.4"); 133 134 Services.dns.clearCache(false); 135 override.clearOverrides(); 136 137 listener = new Listener(); 138 Services.dns.asyncResolve( 139 DOMAIN, 140 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 141 0, 142 null, 143 listener, 144 mainThread, 145 defaultOriginAttributes 146 ); 147 Assert.notEqual(await listener.firstAddress(), "1.2.3.4"); 148 149 await new Promise(resolve => do_timeout(1000, resolve)); 150 Services.dns.clearCache(false); 151 override.clearOverrides(); 152 }); 153 154 add_task(async function test_clearHostOverride() { 155 override.addIPOverride(DOMAIN, "2.2.2.2"); 156 override.addIPOverride(OTHER, "2.2.2.2"); 157 override.clearHostOverride(DOMAIN); 158 let listener = new Listener(); 159 Services.dns.asyncResolve( 160 DOMAIN, 161 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 162 0, 163 null, 164 listener, 165 mainThread, 166 defaultOriginAttributes 167 ); 168 169 Assert.notEqual(await listener.firstAddress(), "2.2.2.2"); 170 171 listener = new Listener(); 172 Services.dns.asyncResolve( 173 OTHER, 174 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 175 0, 176 null, 177 listener, 178 mainThread, 179 defaultOriginAttributes 180 ); 181 Assert.equal(await listener.firstAddress(), "2.2.2.2"); 182 183 // Note: this test will use the actual system resolver. On windows we do a 184 // second async call to the system libraries to get the TTL values, which 185 // keeps the record alive after the onLookupComplete() 186 // We need to wait for a bit, until the second call is finished before we 187 // can clear the cache to make sure we evict everything. 188 // If the next task ever starts failing, with an IP that is not in this 189 // file, then likely the timeout is too small. 190 await new Promise(resolve => do_timeout(1000, resolve)); 191 Services.dns.clearCache(false); 192 override.clearOverrides(); 193 }); 194 195 add_task(async function test_multiple_IPs() { 196 override.addIPOverride(DOMAIN, "2.2.2.2"); 197 override.addIPOverride(DOMAIN, "1.1.1.1"); 198 override.addIPOverride(DOMAIN, "::1"); 199 override.addIPOverride(DOMAIN, "fe80::6a99:9b2b:6ccc:6e1b"); 200 let listener = new Listener(); 201 Services.dns.asyncResolve( 202 DOMAIN, 203 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 204 0, 205 null, 206 listener, 207 mainThread, 208 defaultOriginAttributes 209 ); 210 Assert.deepEqual(await listener.addresses(), [ 211 "2.2.2.2", 212 "1.1.1.1", 213 "::1", 214 "fe80::6a99:9b2b:6ccc:6e1b", 215 ]); 216 217 Services.dns.clearCache(false); 218 override.clearOverrides(); 219 }); 220 221 add_task(async function test_address_family_flags() { 222 override.addIPOverride(DOMAIN, "2.2.2.2"); 223 override.addIPOverride(DOMAIN, "1.1.1.1"); 224 override.addIPOverride(DOMAIN, "::1"); 225 override.addIPOverride(DOMAIN, "fe80::6a99:9b2b:6ccc:6e1b"); 226 let listener = new Listener(); 227 Services.dns.asyncResolve( 228 DOMAIN, 229 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 230 Ci.nsIDNSService.RESOLVE_DISABLE_IPV4, 231 null, 232 listener, 233 mainThread, 234 defaultOriginAttributes 235 ); 236 Assert.deepEqual(await listener.addresses(), [ 237 "::1", 238 "fe80::6a99:9b2b:6ccc:6e1b", 239 ]); 240 241 listener = new Listener(); 242 Services.dns.asyncResolve( 243 DOMAIN, 244 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 245 Ci.nsIDNSService.RESOLVE_DISABLE_IPV6, 246 null, 247 listener, 248 mainThread, 249 defaultOriginAttributes 250 ); 251 Assert.deepEqual(await listener.addresses(), ["2.2.2.2", "1.1.1.1"]); 252 253 Services.dns.clearCache(false); 254 override.clearOverrides(); 255 }); 256 257 add_task(async function test_cname_flag() { 258 override.addIPOverride(DOMAIN, "2.2.2.2"); 259 let listener = new Listener(); 260 Services.dns.asyncResolve( 261 DOMAIN, 262 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 263 0, 264 null, 265 listener, 266 mainThread, 267 defaultOriginAttributes 268 ); 269 let [, inRecord] = await listener; 270 inRecord.QueryInterface(Ci.nsIDNSAddrRecord); 271 Assert.throws( 272 () => inRecord.canonicalName, 273 /NS_ERROR_NOT_AVAILABLE/, 274 "No canonical name flag" 275 ); 276 Assert.equal(inRecord.getNextAddrAsString(), "2.2.2.2"); 277 278 listener = new Listener(); 279 Services.dns.asyncResolve( 280 DOMAIN, 281 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 282 Ci.nsIDNSService.RESOLVE_CANONICAL_NAME, 283 null, 284 listener, 285 mainThread, 286 defaultOriginAttributes 287 ); 288 [, inRecord] = await listener; 289 inRecord.QueryInterface(Ci.nsIDNSAddrRecord); 290 Assert.equal(inRecord.canonicalName, DOMAIN, "No canonical name specified"); 291 Assert.equal(inRecord.getNextAddrAsString(), "2.2.2.2"); 292 293 Services.dns.clearCache(false); 294 override.clearOverrides(); 295 296 override.addIPOverride(DOMAIN, "2.2.2.2"); 297 override.setCnameOverride(DOMAIN, OTHER); 298 listener = new Listener(); 299 Services.dns.asyncResolve( 300 DOMAIN, 301 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 302 Ci.nsIDNSService.RESOLVE_CANONICAL_NAME, 303 null, 304 listener, 305 mainThread, 306 defaultOriginAttributes 307 ); 308 [, inRecord] = await listener; 309 inRecord.QueryInterface(Ci.nsIDNSAddrRecord); 310 Assert.equal(inRecord.canonicalName, OTHER, "Must have correct CNAME"); 311 Assert.equal(inRecord.getNextAddrAsString(), "2.2.2.2"); 312 313 Services.dns.clearCache(false); 314 override.clearOverrides(); 315 }); 316 317 add_task(async function test_nxdomain() { 318 override.addIPOverride(DOMAIN, "N/A"); 319 let listener = new Listener(); 320 Services.dns.asyncResolve( 321 DOMAIN, 322 Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT, 323 Ci.nsIDNSService.RESOLVE_CANONICAL_NAME, 324 null, 325 listener, 326 mainThread, 327 defaultOriginAttributes 328 ); 329 330 let [, , inStatus] = await listener; 331 equal(inStatus, Cr.NS_ERROR_UNKNOWN_HOST); 332 }); 333 334 function makeChan(url) { 335 let chan = NetUtil.newChannel({ 336 uri: url, 337 loadUsingSystemPrincipal: true, 338 contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, 339 }).QueryInterface(Ci.nsIHttpChannel); 340 return chan; 341 } 342 343 function channelOpenPromise(chan, flags) { 344 return new Promise(resolve => { 345 function finish(req, buffer) { 346 resolve([req, buffer]); 347 } 348 chan.asyncOpen(new ChannelListener(finish, null, flags)); 349 }); 350 } 351 352 function hexToUint8Array(hex) { 353 return new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))); 354 } 355 356 add_task( 357 { 358 skip_if: () => 359 mozinfo.os == "win" || 360 mozinfo.os == "android" || 361 mozinfo.socketprocess_networking, 362 }, 363 async function test_https_record_override() { 364 let trrServer = new TRRServer(); 365 await trrServer.start(); 366 registerCleanupFunction(async () => { 367 await trrServer.stop(); 368 }); 369 370 await trrServer.registerDoHAnswers("service.com", "HTTPS", { 371 answers: [ 372 { 373 name: "service.com", 374 ttl: 55, 375 type: "HTTPS", 376 flush: false, 377 data: { 378 priority: 1, 379 name: ".", 380 values: [ 381 { key: "alpn", value: ["h2", "h3"] }, 382 { key: "no-default-alpn" }, 383 { key: "port", value: 8888 }, 384 { key: "ipv4hint", value: "1.2.3.4" }, 385 { key: "echconfig", value: "123..." }, 386 { key: "ipv6hint", value: "::1" }, 387 { key: "odoh", value: "456..." }, 388 ], 389 }, 390 }, 391 { 392 name: "service.com", 393 ttl: 55, 394 type: "HTTPS", 395 flush: false, 396 data: { 397 priority: 2, 398 name: "test.com", 399 values: [ 400 { key: "alpn", value: "h2" }, 401 { key: "ipv4hint", value: ["1.2.3.4", "5.6.7.8"] }, 402 { key: "echconfig", value: "abc..." }, 403 { key: "ipv6hint", value: ["::1", "fe80::794f:6d2c:3d5e:7836"] }, 404 { key: "odoh", value: "def..." }, 405 ], 406 }, 407 }, 408 ], 409 }); 410 411 let chan = makeChan( 412 `https://foo.example.com:${trrServer.port()}/dnsAnswer?host=service.com&type=HTTPS` 413 ); 414 let [, buf] = await channelOpenPromise(chan); 415 let rawBuffer = hexToUint8Array(buf); 416 417 override.addHTTPSRecordOverride("service.com", rawBuffer, rawBuffer.length); 418 419 Services.prefs.setBoolPref("network.dns.native_https_query", true); 420 registerCleanupFunction(async () => { 421 Services.prefs.clearUserPref("network.dns.native_https_query"); 422 Services.prefs.clearUserPref("network.trr.excluded-domains"); 423 }); 424 425 let listener = new Listener(); 426 Services.dns.asyncResolve( 427 "service.com", 428 Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC, 429 0, 430 null, 431 listener, 432 mainThread, 433 defaultOriginAttributes 434 ); 435 436 let [, inRecord, inStatus] = await listener; 437 equal(inStatus, Cr.NS_OK); 438 Assert.ok( 439 !inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).IsTRR(), 440 "resolved by Native" 441 ); 442 let answer = inRecord.QueryInterface(Ci.nsIDNSHTTPSSVCRecord).records; 443 equal(answer[0].priority, 1); 444 equal(answer[0].name, "service.com"); 445 Assert.deepEqual( 446 answer[0].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn, 447 ["h2", "h3"], 448 "got correct answer" 449 ); 450 Assert.ok( 451 answer[0].values[1].QueryInterface(Ci.nsISVCParamNoDefaultAlpn), 452 "got correct answer" 453 ); 454 Assert.equal( 455 answer[0].values[2].QueryInterface(Ci.nsISVCParamPort).port, 456 8888, 457 "got correct answer" 458 ); 459 Assert.equal( 460 answer[0].values[3].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[0] 461 .address, 462 "1.2.3.4", 463 "got correct answer" 464 ); 465 Assert.equal( 466 answer[0].values[4].QueryInterface(Ci.nsISVCParamEchConfig).echconfig, 467 "123...", 468 "got correct answer" 469 ); 470 Assert.equal( 471 answer[0].values[5].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[0] 472 .address, 473 "::1", 474 "got correct answer" 475 ); 476 Assert.equal( 477 answer[0].values[6].QueryInterface(Ci.nsISVCParamODoHConfig).ODoHConfig, 478 "456...", 479 "got correct answer" 480 ); 481 482 Assert.equal(answer[1].priority, 2); 483 Assert.equal(answer[1].name, "test.com"); 484 Assert.equal(answer[1].values.length, 5); 485 Assert.deepEqual( 486 answer[1].values[0].QueryInterface(Ci.nsISVCParamAlpn).alpn, 487 ["h2"], 488 "got correct answer" 489 ); 490 Assert.equal( 491 answer[1].values[1].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[0] 492 .address, 493 "1.2.3.4", 494 "got correct answer" 495 ); 496 Assert.equal( 497 answer[1].values[1].QueryInterface(Ci.nsISVCParamIPv4Hint).ipv4Hint[1] 498 .address, 499 "5.6.7.8", 500 "got correct answer" 501 ); 502 Assert.equal( 503 answer[1].values[2].QueryInterface(Ci.nsISVCParamEchConfig).echconfig, 504 "abc...", 505 "got correct answer" 506 ); 507 Assert.equal( 508 answer[1].values[3].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[0] 509 .address, 510 "::1", 511 "got correct answer" 512 ); 513 Assert.equal( 514 answer[1].values[3].QueryInterface(Ci.nsISVCParamIPv6Hint).ipv6Hint[1] 515 .address, 516 "fe80::794f:6d2c:3d5e:7836", 517 "got correct answer" 518 ); 519 Assert.equal( 520 answer[1].values[4].QueryInterface(Ci.nsISVCParamODoHConfig).ODoHConfig, 521 "def...", 522 "got correct answer" 523 ); 524 525 // Adding "service.com" into excluded-domains should fail 526 // native HTTPS query. 527 Services.prefs.setCharPref("network.trr.excluded-domains", "service.com"); 528 listener = new Listener(); 529 try { 530 Services.dns.asyncResolve( 531 "service.com", 532 Ci.nsIDNSService.RESOLVE_TYPE_HTTPSSVC, 533 0, 534 null, 535 listener, 536 mainThread, 537 defaultOriginAttributes 538 ); 539 Assert.ok(false, "asyncResolve should fail"); 540 } catch (e) { 541 Assert.equal(e.result, Cr.NS_ERROR_UNKNOWN_HOST); 542 } 543 } 544 );