test_http3_dns_retry.js (9387B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 var { setTimeout } = ChromeUtils.importESModule( 8 "resource://gre/modules/Timer.sys.mjs" 9 ); 10 11 let h2Port; 12 let h3Port; 13 let trrServer; 14 15 const certOverrideService = Cc[ 16 "@mozilla.org/security/certoverride;1" 17 ].getService(Ci.nsICertOverrideService); 18 19 add_setup(async function setup() { 20 h2Port = Services.env.get("MOZHTTP2_PORT"); 21 Assert.notEqual(h2Port, null); 22 Assert.notEqual(h2Port, ""); 23 24 h3Port = Services.env.get("MOZHTTP3_PORT"); 25 Assert.notEqual(h3Port, null); 26 Assert.notEqual(h3Port, ""); 27 28 trr_test_setup(); 29 30 if (mozinfo.socketprocess_networking) { 31 Cc["@mozilla.org/network/protocol;1?name=http"].getService( 32 Ci.nsIHttpProtocolHandler 33 ); 34 Services.dns; // Needed to trigger socket process. 35 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 36 await new Promise(resolve => setTimeout(resolve, 1000)); 37 } 38 39 Services.prefs.setIntPref("network.trr.mode", 2); // TRR first 40 Services.prefs.setBoolPref("network.http.http3.enable", true); 41 Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6); 42 Services.prefs.setBoolPref( 43 "network.http.http3.block_loopback_ipv6_addr", 44 true 45 ); 46 Services.prefs.setBoolPref( 47 "network.http.http3.retry_different_ip_family", 48 true 49 ); 50 Services.prefs.setBoolPref("network.dns.get-ttl", false); 51 52 certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData( 53 true 54 ); 55 56 registerCleanupFunction(async () => { 57 certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData( 58 false 59 ); 60 trr_clear_prefs(); 61 Services.prefs.clearUserPref( 62 "network.http.http3.retry_different_ip_family" 63 ); 64 Services.prefs.clearUserPref("network.http.speculative-parallel-limit"); 65 Services.prefs.clearUserPref("network.http.http3.block_loopback_ipv6_addr"); 66 Services.prefs.clearUserPref("network.dns.get-ttl"); 67 if (trrServer) { 68 await trrServer.stop(); 69 } 70 }); 71 }); 72 73 function makeChan(url) { 74 let chan = NetUtil.newChannel({ 75 uri: url, 76 loadUsingSystemPrincipal: true, 77 contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT, 78 }).QueryInterface(Ci.nsIHttpChannel); 79 chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; 80 return chan; 81 } 82 83 function channelOpenPromise(chan, flags) { 84 // eslint-disable-next-line no-async-promise-executor 85 return new Promise(async resolve => { 86 function finish(req, buffer) { 87 resolve([req, buffer]); 88 } 89 chan.asyncOpen(new ChannelListener(finish, null, flags)); 90 }); 91 } 92 93 async function registerDoHAnswers(host, ipv4Answers, ipv6Answers, httpsRecord) { 94 trrServer = new TRRServer(); 95 await trrServer.start(); 96 97 Services.prefs.setIntPref("network.trr.mode", 3); 98 Services.prefs.setCharPref( 99 "network.trr.uri", 100 `https://foo.example.com:${trrServer.port()}/dns-query` 101 ); 102 103 await trrServer.registerDoHAnswers(host, "HTTPS", { 104 answers: httpsRecord, 105 }); 106 107 await trrServer.registerDoHAnswers(host, "AAAA", { 108 answers: ipv6Answers, 109 }); 110 111 await trrServer.registerDoHAnswers(host, "A", { 112 answers: ipv4Answers, 113 }); 114 115 Services.dns.clearCache(true); 116 } 117 118 // Test if we retry IPv4 address for Http/3 properly. 119 add_task(async function test_retry_with_ipv4() { 120 let host = "test.http3_retry.com"; 121 let ipv4answers = [ 122 { 123 name: host, 124 ttl: 55, 125 type: "A", 126 flush: false, 127 data: "127.0.0.1", 128 }, 129 ]; 130 // The UDP socket will return connection refused error because we set 131 // "network.http.http3.block_loopback_ipv6_addr" to true. 132 let ipv6answers = [ 133 { 134 name: host, 135 ttl: 55, 136 type: "AAAA", 137 flush: false, 138 data: "::1", 139 }, 140 ]; 141 let httpsRecord = [ 142 { 143 name: host, 144 ttl: 55, 145 type: "HTTPS", 146 flush: false, 147 data: { 148 priority: 1, 149 name: host, 150 values: [ 151 { key: "alpn", value: "h3" }, 152 { key: "port", value: h3Port }, 153 ], 154 }, 155 }, 156 ]; 157 158 await registerDoHAnswers(host, ipv4answers, ipv6answers, httpsRecord); 159 160 let chan = makeChan(`https://${host}`); 161 let [req] = await channelOpenPromise(chan); 162 Assert.equal(req.protocolVersion, "h3"); 163 164 await trrServer.stop(); 165 }); 166 167 add_task(async function test_retry_with_ipv4_disabled() { 168 let host = "test.http3_retry_ipv4_blocked.com"; 169 let ipv4answers = [ 170 { 171 name: host, 172 ttl: 55, 173 type: "A", 174 flush: false, 175 data: "127.0.0.1", 176 }, 177 ]; 178 // The UDP socket will return connection refused error because we set 179 // "network.http.http3.block_loopback_ipv6_addr" to true. 180 let ipv6answers = [ 181 { 182 name: host, 183 ttl: 55, 184 type: "AAAA", 185 flush: false, 186 data: "::1", 187 }, 188 ]; 189 let httpsRecord = [ 190 { 191 name: host, 192 ttl: 55, 193 type: "HTTPS", 194 flush: false, 195 data: { 196 priority: 1, 197 name: host, 198 values: [ 199 { key: "alpn", value: "h3" }, 200 { key: "port", value: h3Port }, 201 ], 202 }, 203 }, 204 ]; 205 206 await registerDoHAnswers(host, ipv4answers, ipv6answers, httpsRecord); 207 208 let chan = makeChan(`https://${host}`); 209 chan.QueryInterface(Ci.nsIHttpChannelInternal); 210 chan.setIPv4Disabled(); 211 212 await channelOpenPromise(chan, CL_EXPECT_FAILURE); 213 await trrServer.stop(); 214 }); 215 216 // See bug 1837252. There is no way to observe the outcome of this test, because 217 // the crash in bug 1837252 is only triggered by speculative connection. 218 // The outcome of this test is no crash. 219 add_task(async function test_retry_with_ipv4_failed() { 220 let host = "test.http3_retry_failed.com"; 221 // Return a wrong answer intentionally. 222 let ipv4answers = [ 223 { 224 name: host, 225 ttl: 55, 226 type: "AAAA", 227 flush: false, 228 data: "127.0.0.1", 229 }, 230 ]; 231 // The UDP socket will return connection refused error because we set 232 // "network.http.http3.block_loopback_ipv6_addr" to true. 233 let ipv6answers = [ 234 { 235 name: host, 236 ttl: 55, 237 type: "AAAA", 238 flush: false, 239 data: "::1", 240 }, 241 ]; 242 let httpsRecord = [ 243 { 244 name: host, 245 ttl: 55, 246 type: "HTTPS", 247 flush: false, 248 data: { 249 priority: 1, 250 name: host, 251 values: [ 252 { key: "alpn", value: "h3" }, 253 { key: "port", value: h3Port }, 254 ], 255 }, 256 }, 257 ]; 258 259 await registerDoHAnswers(host, ipv4answers, ipv6answers, httpsRecord); 260 261 // This speculative connection is used to trigger the mechanism to retry 262 // Http/3 connection with a IPv4 address. 263 // We want to make the connection entry's IP preference known, 264 // so DnsAndConnectSocket::mRetryWithDifferentIPFamily will be set to true 265 // before the second speculative connection. 266 let uri = Services.io.newURI(`https://test.http3_retry_failed.com`); 267 Services.io.speculativeConnect( 268 uri, 269 Services.scriptSecurityManager.getSystemPrincipal(), 270 null, 271 false 272 ); 273 274 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 275 await new Promise(resolve => setTimeout(resolve, 3000)); 276 277 // When this speculative connection is created, the connection entry is 278 // already set to prefer IPv4. Since we provided an invalid A response, 279 // DnsAndConnectSocket::OnLookupComplete is called with an error. 280 // Since DnsAndConnectSocket::mRetryWithDifferentIPFamily is true, we do 281 // retry DNS lookup. During retry, we should not create UDP connection. 282 Services.io.speculativeConnect( 283 uri, 284 Services.scriptSecurityManager.getSystemPrincipal(), 285 null, 286 false 287 ); 288 289 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 290 await new Promise(resolve => setTimeout(resolve, 3000)); 291 await trrServer.stop(); 292 }); 293 294 add_task(async function test_retry_with_0rtt() { 295 let host = "test.http3_retry_0rtt.com"; 296 let ipv4answers = [ 297 { 298 name: host, 299 ttl: 55, 300 type: "A", 301 flush: false, 302 data: "127.0.0.1", 303 }, 304 ]; 305 // The UDP socket will return connection refused error because we set 306 // "network.http.http3.block_loopback_ipv6_addr" to true. 307 let ipv6answers = [ 308 { 309 name: host, 310 ttl: 55, 311 type: "AAAA", 312 flush: false, 313 data: "::1", 314 }, 315 ]; 316 let httpsRecord = [ 317 { 318 name: host, 319 ttl: 55, 320 type: "HTTPS", 321 flush: false, 322 data: { 323 priority: 1, 324 name: host, 325 values: [ 326 { key: "alpn", value: "h3" }, 327 { key: "port", value: h3Port }, 328 ], 329 }, 330 }, 331 ]; 332 333 await registerDoHAnswers(host, ipv4answers, ipv6answers, httpsRecord); 334 335 let chan = makeChan(`https://${host}`); 336 chan.QueryInterface(Ci.nsIHttpChannelInternal); 337 chan.setIPv6Disabled(); 338 339 let [req] = await channelOpenPromise(chan); 340 Assert.equal(req.protocolVersion, "h3"); 341 342 // Make sure the h3 connection created by the previous test is cleared. 343 Services.obs.notifyObservers(null, "net:cancel-all-connections"); 344 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 345 await new Promise(resolve => setTimeout(resolve, 1000)); 346 347 chan = makeChan(`https://${host}`); 348 chan.QueryInterface(Ci.nsIHttpChannelInternal); 349 350 [req] = await channelOpenPromise(chan); 351 Assert.equal(req.protocolVersion, "h3"); 352 353 await trrServer.stop(); 354 });