test_dooh.js (11581B)
1 /* Any copyright is dedicated to the Public Domain. 2 * https://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /* import-globals-from trr_common.js */ 7 8 const { setTimeout } = ChromeUtils.importESModule( 9 "resource://gre/modules/Timer.sys.mjs" 10 ); 11 12 let httpServer; 13 let ohttpServer; 14 let ohttpEncodedConfig = "not a valid config"; 15 16 // Decapsulate the request, send it to the actual TRR, receive the response, 17 // encapsulate it, and send it back through `response`. 18 async function forwardToTRR(request, response) { 19 let inputStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( 20 Ci.nsIScriptableInputStream 21 ); 22 inputStream.init(request.bodyInputStream); 23 let requestBody = inputStream.readBytes(inputStream.available()); 24 let ohttpResponse = ohttpServer.decapsulate(stringToBytes(requestBody)); 25 let bhttp = Cc["@mozilla.org/network/binary-http;1"].getService( 26 Ci.nsIBinaryHttp 27 ); 28 let decodedRequest = bhttp.decodeRequest(ohttpResponse.request); 29 let headers = {}; 30 for ( 31 let i = 0; 32 i < decodedRequest.headerNames.length && decodedRequest.headerValues.length; 33 i++ 34 ) { 35 headers[decodedRequest.headerNames[i]] = decodedRequest.headerValues[i]; 36 } 37 let uri = `${decodedRequest.scheme}://${decodedRequest.authority}${decodedRequest.path}`; 38 let body = new Uint8Array(decodedRequest.content.length); 39 for (let i = 0; i < decodedRequest.content.length; i++) { 40 body[i] = decodedRequest.content[i]; 41 } 42 try { 43 // Timeout after 10 seconds. 44 let fetchInProgress = true; 45 let controller = new AbortController(); 46 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 47 setTimeout(() => { 48 if (fetchInProgress) { 49 controller.abort(); 50 } 51 }, 10000); 52 let trrResponse = await fetch(uri, { 53 method: decodedRequest.method, 54 headers, 55 body: decodedRequest.method == "POST" ? body : undefined, 56 credentials: "omit", 57 signal: controller.signal, 58 }); 59 fetchInProgress = false; 60 let data = new Uint8Array(await trrResponse.arrayBuffer()); 61 let trrResponseContent = []; 62 for (let i = 0; i < data.length; i++) { 63 trrResponseContent.push(data[i]); 64 } 65 let trrResponseHeaderNames = []; 66 let trrResponseHeaderValues = []; 67 for (let header of trrResponse.headers) { 68 trrResponseHeaderNames.push(header[0]); 69 trrResponseHeaderValues.push(header[1]); 70 } 71 let binaryResponse = new BinaryHttpResponse( 72 trrResponse.status, 73 trrResponseHeaderNames, 74 trrResponseHeaderValues, 75 trrResponseContent 76 ); 77 let responseBytes = bhttp.encodeResponse(binaryResponse); 78 let encResponse = ohttpResponse.encapsulate(responseBytes); 79 response.setStatusLine(request.httpVersion, 200, "OK"); 80 response.setHeader("Content-Type", "message/ohttp-res", false); 81 response.write(bytesToString(encResponse)); 82 } catch (e) { 83 // Some tests involve the responder either timing out or closing the 84 // connection unexpectedly. 85 } 86 } 87 88 let trrServer; 89 add_setup(async function setup() { 90 trr_test_setup(); 91 runningOHTTPTests = true; 92 93 trrServer = new TRRServer(); 94 await trrServer.start(); 95 h2Port = trrServer.port(); 96 97 if (mozinfo.socketprocess_networking) { 98 Services.dns; // Needed to trigger socket process. 99 await TestUtils.waitForCondition(() => Services.io.socketProcessLaunched); 100 } 101 102 Services.prefs.setIntPref("network.trr.mode", Ci.nsIDNSService.MODE_TRRONLY); 103 104 let ohttp = Cc["@mozilla.org/network/oblivious-http;1"].getService( 105 Ci.nsIObliviousHttp 106 ); 107 ohttpServer = ohttp.server(); 108 109 httpServer = new HttpServer(); 110 httpServer.registerPathHandler("/relay", function (request, response) { 111 response.processAsync(); 112 forwardToTRR(request, response).then(() => { 113 response.finish(); 114 }); 115 }); 116 httpServer.registerPathHandler("/config", function (request, response) { 117 response.setStatusLine(request.httpVersion, 200, "OK"); 118 response.setHeader("Content-Type", "application/ohttp-keys", false); 119 response.write(ohttpEncodedConfig); 120 }); 121 httpServer.start(-1); 122 123 Services.prefs.setBoolPref("network.trr.use_ohttp", true); 124 // On windows the TTL fetch will race with clearing the cache 125 // to refresh the cache entry. 126 Services.prefs.setBoolPref("network.dns.get-ttl", false); 127 128 registerCleanupFunction(async () => { 129 trr_clear_prefs(); 130 Services.prefs.clearUserPref("network.trr.use_ohttp"); 131 Services.prefs.clearUserPref("network.trr.ohttp.config_uri"); 132 Services.prefs.clearUserPref("network.trr.ohttp.relay_uri"); 133 Services.prefs.clearUserPref("network.trr.ohttp.uri"); 134 Services.prefs.clearUserPref("network.dns.get-ttl"); 135 await new Promise(resolve => { 136 httpServer.stop(resolve); 137 }); 138 if (trrServer) { 139 await trrServer.stop(); 140 } 141 }); 142 }); 143 144 // Test that if DNS-over-OHTTP isn't configured, the implementation falls back 145 // to platform resolution. 146 add_task(async function test_ohttp_not_configured() { 147 Services.dns.clearCache(true); 148 setModeAndURI(2, "doh?responseIP=2.2.2.2"); 149 await new TRRDNSListener("example.com", "127.0.0.1"); 150 }); 151 152 add_task(async function set_ohttp_invalid_prefs() { 153 let configPromise = TestUtils.topicObserved("ohttp-service-config-loaded"); 154 Services.prefs.setCharPref( 155 "network.trr.ohttp.relay_uri", 156 "http://nonexistent.test" 157 ); 158 Services.prefs.setCharPref( 159 "network.trr.ohttp.config_uri", 160 "http://nonexistent.test" 161 ); 162 163 Cc["@mozilla.org/network/oblivious-http-service;1"].getService( 164 Ci.nsIObliviousHttpService 165 ); 166 await configPromise; 167 }); 168 169 // Test that if DNS-over-OHTTP has an invalid configuration, the implementation 170 // falls back to platform resolution. 171 add_task(async function test_ohttp_invalid_prefs_fallback() { 172 Services.dns.clearCache(true); 173 setModeAndURI(2, "doh?responseIP=2.2.2.2"); 174 await new TRRDNSListener("example.com", "127.0.0.1"); 175 }); 176 177 add_task(async function set_ohttp_prefs_500_error() { 178 let configPromise = TestUtils.topicObserved("ohttp-service-config-loaded"); 179 Services.prefs.setCharPref( 180 "network.trr.ohttp.relay_uri", 181 `http://localhost:${httpServer.identity.primaryPort}/relay` 182 ); 183 Services.prefs.setCharPref( 184 "network.trr.ohttp.config_uri", 185 `http://localhost:${httpServer.identity.primaryPort}/500error` 186 ); 187 await configPromise; 188 }); 189 190 // Test that if DNS-over-OHTTP has an invalid configuration, the implementation 191 // falls back to platform resolution. 192 add_task(async function test_ohttp_500_error_fallback() { 193 Services.dns.clearCache(true); 194 setModeAndURI(2, "doh?responseIP=2.2.2.2"); 195 await new TRRDNSListener("example.com", "127.0.0.1"); 196 }); 197 198 add_task(async function retryConfigOnConnectivityChange() { 199 Services.prefs.setCharPref("network.trr.confirmationNS", "skip"); 200 // First we make sure the config is properly loaded 201 setModeAndURI(2, "doh?responseIP=2.2.2.2"); 202 let ohttpService = Cc[ 203 "@mozilla.org/network/oblivious-http-service;1" 204 ].getService(Ci.nsIObliviousHttpService); 205 ohttpService.clearTRRConfig(); 206 ohttpEncodedConfig = bytesToString(ohttpServer.encodedConfig); 207 let configPromise = TestUtils.topicObserved("ohttp-service-config-loaded"); 208 Services.prefs.setCharPref( 209 "network.trr.ohttp.relay_uri", 210 `http://localhost:${httpServer.identity.primaryPort}/relay` 211 ); 212 Services.prefs.setCharPref( 213 "network.trr.ohttp.config_uri", 214 `http://localhost:${httpServer.identity.primaryPort}/config` 215 ); 216 let [, status] = await configPromise; 217 equal(status, "success"); 218 info("retryConfigOnConnectivityChange setup complete"); 219 220 ohttpService.clearTRRConfig(); 221 222 let port = httpServer.identity.primaryPort; 223 // Stop the server so getting the config fails. 224 await httpServer.stop(); 225 226 configPromise = TestUtils.topicObserved("ohttp-service-config-loaded"); 227 Services.obs.notifyObservers( 228 null, 229 "network:captive-portal-connectivity-changed" 230 ); 231 [, status] = await configPromise; 232 equal(status, "failed"); 233 234 // Should fallback to native DNS since the config is empty 235 Services.dns.clearCache(true); 236 await new TRRDNSListener("example.com", "127.0.0.1"); 237 238 // Start the server back again. 239 httpServer.start(port); 240 Assert.equal( 241 port, 242 httpServer.identity.primaryPort, 243 "server should get the same port" 244 ); 245 246 // Still the config hasn't been reloaded. 247 await new TRRDNSListener("example2.com", "127.0.0.1"); 248 249 // Signal a connectivity change so we reload the config 250 configPromise = TestUtils.topicObserved("ohttp-service-config-loaded"); 251 Services.obs.notifyObservers( 252 null, 253 "network:captive-portal-connectivity-changed" 254 ); 255 [, status] = await configPromise; 256 equal(status, "success"); 257 258 await new TRRDNSListener("example3.com", "2.2.2.2"); 259 260 // Now check that we also reload a missing config if a TRR confirmation fails. 261 ohttpService.clearTRRConfig(); 262 configPromise = TestUtils.topicObserved("ohttp-service-config-loaded"); 263 Services.obs.notifyObservers( 264 null, 265 "network:trr-confirmation", 266 "CONFIRM_FAILED" 267 ); 268 [, status] = await configPromise; 269 equal(status, "success"); 270 await new TRRDNSListener("example4.com", "2.2.2.2"); 271 272 // set the config to an invalid value and check that as long as the URL 273 // doesn't change, we dont reload it again on connectivity notifications. 274 ohttpEncodedConfig = "not a valid config"; 275 configPromise = TestUtils.topicObserved("ohttp-service-config-loaded"); 276 Services.obs.notifyObservers( 277 null, 278 "network:captive-portal-connectivity-changed" 279 ); 280 281 await new TRRDNSListener("example5.com", "2.2.2.2"); 282 283 // The change should not cause any config reload because we already have a config. 284 [, status] = await configPromise; 285 equal(status, "no-changes"); 286 287 await new TRRDNSListener("example6.com", "2.2.2.2"); 288 // Clear the config_uri pref so it gets set to the proper value in the next test. 289 configPromise = TestUtils.topicObserved("ohttp-service-config-loaded"); 290 Services.prefs.setCharPref("network.trr.ohttp.config_uri", ``); 291 await configPromise; 292 }); 293 294 add_task(async function set_ohttp_prefs_valid() { 295 let ohttpService = Cc[ 296 "@mozilla.org/network/oblivious-http-service;1" 297 ].getService(Ci.nsIObliviousHttpService); 298 ohttpService.clearTRRConfig(); 299 let configPromise = TestUtils.topicObserved("ohttp-service-config-loaded"); 300 ohttpEncodedConfig = bytesToString(ohttpServer.encodedConfig); 301 Services.prefs.setCharPref( 302 "network.trr.ohttp.config_uri", 303 `http://localhost:${httpServer.identity.primaryPort}/config` 304 ); 305 await configPromise; 306 }); 307 308 add_task(test_A_record); 309 310 add_task(test_AAAA_records); 311 312 add_task(test_RFC1918); 313 314 add_task(test_GET_ECS); 315 316 add_task(test_timeout_mode3); 317 318 add_task(test_strict_native_fallback); 319 320 add_task(test_no_answers_fallback); 321 322 add_task(test_404_fallback); 323 324 add_task(test_mode_1_and_4); 325 326 add_task(test_CNAME); 327 328 add_task(test_name_mismatch); 329 330 add_task(test_mode_2); 331 332 add_task(test_excluded_domains); 333 334 add_task(test_captiveportal_canonicalURL); 335 336 add_task(test_parentalcontrols); 337 338 // TRR-first check that DNS result is used if domain is part of the builtin-excluded-domains pref 339 add_task(test_builtin_excluded_domains); 340 341 add_task(test_excluded_domains_mode3); 342 343 add_task(test25e); 344 345 add_task(test_parentalcontrols_mode3); 346 347 add_task(test_builtin_excluded_domains_mode3); 348 349 add_task(count_cookies); 350 351 // This test doesn't work with having a JS httpd server as a relay. 352 // add_task(test_connection_closed); 353 354 add_task(test_fetch_time); 355 356 add_task(test_fqdn); 357 358 add_task(test_ipv6_trr_fallback); 359 360 add_task(test_ipv4_trr_fallback); 361 362 add_task(test_no_retry_without_doh);