test_trr_connection_cycle.js (12362B)
1 "use strict"; 2 3 /* import-globals-from trr_common.js */ 4 /* import-globals-from head_trr.js */ 5 6 const { TestUtils } = ChromeUtils.importESModule( 7 "resource://testing-common/TestUtils.sys.mjs" 8 ); 9 10 function setLocalModeAndURI(mode, url) { 11 Services.prefs.setCharPref("network.trr.uri", url); 12 Services.prefs.setIntPref("network.trr.mode", mode); 13 } 14 15 async function registerNS() { 16 await trrServer.registerDoHAnswers("confirm.example.com", "NS", { 17 answers: [ 18 { 19 name: "confirm.example.com", 20 ttl: 55, 21 type: "NS", 22 flush: false, 23 data: "test.com", 24 }, 25 ], 26 }); 27 } 28 29 async function unregisterNS() { 30 await trrServer.registerDoHAnswers("confirm.example.com", "NS", { 31 answers: [], 32 error: 500, // Server error 33 }); 34 } 35 36 async function registerDomain(domain) { 37 await trrServer.registerDoHAnswers(domain, "A", { 38 answers: [ 39 { 40 name: domain, 41 ttl: 55, 42 type: "A", 43 flush: false, 44 data: "9.8.7.6", 45 }, 46 ], 47 }); 48 } 49 50 let trrServer; 51 add_setup(async function setup() { 52 trr_test_setup(); 53 Services.prefs.setBoolPref("network.dns.native-is-localhost", true); 54 Services.dns.clearCache(true); 55 Services.prefs.setIntPref("network.trr.request_timeout_ms", 500); 56 Services.prefs.setIntPref( 57 "network.trr.strict_fallback_request_timeout_ms", 58 500 59 ); 60 Services.prefs.setIntPref("network.trr.request_timeout_mode_trronly_ms", 500); 61 62 trrServer = new TRRServer(); 63 registerCleanupFunction(async () => { 64 await trrServer.stop(); 65 }); 66 await trrServer.start(); 67 dump(`port = ${trrServer.port()}\n`); 68 await registerNS(); 69 }); 70 71 // This test checks that connection is cycled on every failure when network.trr.retry_on_recoverable_errors is true. 72 add_task(async function test_connection_reuse_and_cycling() { 73 Services.prefs.setCharPref( 74 "network.trr.confirmationNS", 75 "confirm.example.com" 76 ); 77 setLocalModeAndURI( 78 2, 79 `https://foo.example.com:${trrServer.port()}/dns-query` 80 ); 81 Services.prefs.setBoolPref("network.trr.strict_native_fallback", true); 82 Services.prefs.setBoolPref("network.trr.retry_on_recoverable_errors", true); 83 84 await TestUtils.waitForCondition( 85 // 2 => CONFIRM_OK 86 () => Services.dns.currentTrrConfirmationState == 2, 87 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`, 88 1, 89 5000 90 ); 91 92 // Setting conncycle=true in the URI. Server will start logging reqs. 93 // We will do a specific sequence of lookups, then fetch the log from 94 // the server and check that it matches what we'd expect. 95 setLocalModeAndURI( 96 2, 97 `https://foo.example.com:${trrServer.port()}/dns-query?conncycle=true` 98 ); 99 await TestUtils.waitForCondition( 100 // 2 => CONFIRM_OK 101 () => Services.dns.currentTrrConfirmationState == 2, 102 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`, 103 1, 104 5000 105 ); 106 // Confirmation upon uri-change will have created one req. 107 108 for (let i = 1; i <= 6; i++) { 109 await registerDomain(`bar${i}.example.org`); 110 } 111 await registerDomain("newconn.example.org"); 112 await registerDomain("newconn2.example.org"); 113 114 // Two reqs for each bar1 and bar2 - A + AAAA. 115 await new TRRDNSListener("bar1.example.org", "9.8.7.6"); 116 await new TRRDNSListener("bar2.example.org", "9.8.7.6"); 117 // Total so far: (1) + 2 + 2 = 5 118 119 // Two reqs that fail, one Confirmation req, two retried reqs that succeed. 120 await new TRRDNSListener("newconn.example.org", "9.8.7.6"); 121 await TestUtils.waitForCondition( 122 // 2 => CONFIRM_OK 123 () => Services.dns.currentTrrConfirmationState == 2, 124 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`, 125 1, 126 5000 127 ); 128 // Total so far: (5) + 2 + 1 + 2 = 10 129 130 // Two reqs for each bar3 and bar4 . 131 await new TRRDNSListener("bar3.example.org", "9.8.7.6"); 132 await new TRRDNSListener("bar4.example.org", "9.8.7.6"); 133 // Total so far: (10) + 2 + 2 = 14. 134 135 // Two reqs that fail, one Confirmation req, two retried reqs that succeed. 136 await new TRRDNSListener("newconn2.example.org", "9.8.7.6"); 137 await TestUtils.waitForCondition( 138 // 2 => CONFIRM_OK 139 () => Services.dns.currentTrrConfirmationState == 2, 140 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`, 141 1, 142 5000 143 ); 144 // Total so far: (14) + 2 + 1 + 2 = 19 145 146 // Two reqs for each bar5 and bar6 . 147 await new TRRDNSListener("bar5.example.org", "9.8.7.6"); 148 await new TRRDNSListener("bar6.example.org", "9.8.7.6"); 149 // Total so far: (19) + 2 + 2 = 23 150 151 let dohReqPortLog = await trrServer.execute(`global.gDoHPortsLog`); 152 info(JSON.stringify(dohReqPortLog)); 153 154 // Since the actual ports seen will vary at runtime, we use placeholders 155 // instead in our expected output definition. For example, if two entries 156 // both have "port1", it means they both should have the same port in the 157 // server's log. 158 // For reqs that fail and trigger a Confirmation + retry, the retried reqs 159 // might not re-use the new connection created for Confirmation due to a 160 // race, so we have an extra alternate expected port for them. This lets 161 // us test that they use *a* new port even if it's not *the* new port. 162 // Subsequent lookups are not affected, they will use the same conn as 163 // the Confirmation req. 164 let expectedLogTemplate = [ 165 ["confirm.example.com", "port1"], 166 ["bar1.example.org", "port1"], 167 ["bar1.example.org", "port1"], 168 ["bar2.example.org", "port1"], 169 ["bar2.example.org", "port1"], 170 ["newconn.example.org", "port1"], 171 ["newconn.example.org", "port1"], 172 ["confirm.example.com", "port2"], 173 ["newconn.example.org", "port2"], 174 ["newconn.example.org", "port2"], 175 ["bar3.example.org", "port2"], 176 ["bar3.example.org", "port2"], 177 ["bar4.example.org", "port2"], 178 ["bar4.example.org", "port2"], 179 ["newconn2.example.org", "port2"], 180 ["newconn2.example.org", "port2"], 181 ["confirm.example.com", "port3"], 182 ["newconn2.example.org", "port3"], 183 ["newconn2.example.org", "port3"], 184 ["bar5.example.org", "port3"], 185 ["bar5.example.org", "port3"], 186 ["bar6.example.org", "port3"], 187 ["bar6.example.org", "port3"], 188 ]; 189 190 if (expectedLogTemplate.length != dohReqPortLog.length) { 191 // This shouldn't happen, and if it does, we'll fail the assertion 192 // below. But first dump the whole server-side log to help with 193 // debugging should we see a failure. Most likely cause would be 194 // that another consumer of TRR happened to make a request while 195 // the test was running and polluted the log. 196 info(dohReqPortLog); 197 } 198 199 equal( 200 expectedLogTemplate.length, 201 dohReqPortLog.length, 202 "Correct number of req log entries" 203 ); 204 205 let seenPorts = new Set(); 206 // This is essentially a symbol table - as we iterate through the log 207 // we will assign the actual seen port numbers to the placeholders. 208 let seenPortsByExpectedPort = new Map(); 209 210 for (let i = 0; i < expectedLogTemplate.length; i++) { 211 let expectedName = expectedLogTemplate[i][0]; 212 let expectedPort = expectedLogTemplate[i][1]; 213 let seenName = dohReqPortLog[i][0]; 214 let seenPort = dohReqPortLog[i][1]; 215 info(`Checking log entry. Name: ${seenName}, Port: ${seenPort}`); 216 equal(expectedName, seenName, "Name matches for entry " + i); 217 if (!seenPortsByExpectedPort.has(expectedPort)) { 218 ok(!seenPorts.has(seenPort), "Port should not have been previously used"); 219 seenPorts.add(seenPort); 220 seenPortsByExpectedPort.set(expectedPort, seenPort); 221 } else { 222 equal( 223 seenPort, 224 seenPortsByExpectedPort.get(expectedPort), 225 "Connection was reused as expected" 226 ); 227 } 228 } 229 230 // Clear log for the next test. 231 await trrServer.execute( 232 `global.gDoHPortsLog = []; global.gDoHNewConnLog = {};` 233 ); 234 }); 235 236 // network.trr.retry_on_recoverable_errors = false 237 // This test unregisters the confirmation NS (server will return HTTP error code 500) before newconn resolutions 238 // The newconn resolutions will fail, triggering a confirmation. The second confirmation will cycle the connection. 239 // We check that the connection is cycled at least once for every newconn resolution. 240 add_task(async function test_connection_reuse_and_cycling2() { 241 Services.prefs.setBoolPref("network.trr.retry_on_recoverable_errors", false); 242 Services.dns.clearCache(true); 243 Services.prefs.setCharPref( 244 "network.trr.confirmationNS", 245 "confirm.example.com" 246 ); 247 setLocalModeAndURI( 248 2, 249 `https://foo.example.com:${trrServer.port()}/dns-query` 250 ); 251 Services.prefs.setBoolPref("network.trr.strict_native_fallback", true); 252 253 await TestUtils.waitForCondition( 254 // 2 => CONFIRM_OK 255 () => Services.dns.currentTrrConfirmationState == 2, 256 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`, 257 1, 258 5000 259 ); 260 261 // Setting conncycle=true in the URI. Server will start logging reqs. 262 // We will do a specific sequence of lookups, then fetch the log from 263 // the server and check that it matches what we'd expect. 264 setLocalModeAndURI( 265 2, 266 `https://foo.example.com:${trrServer.port()}/dns-query?conncycle=true` 267 ); 268 await TestUtils.waitForCondition( 269 // 2 => CONFIRM_OK 270 () => Services.dns.currentTrrConfirmationState == 2, 271 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`, 272 1, 273 5000 274 ); 275 // Confirmation upon uri-change will have created one req. 276 277 for (let i = 1; i <= 6; i++) { 278 await registerDomain(`bar${i}.example.org`); 279 } 280 await registerDomain("newconn.example.org"); 281 await registerDomain("newconn2.example.org"); 282 283 await new TRRDNSListener("bar1.example.org", "9.8.7.6"); 284 await new TRRDNSListener("bar2.example.org", "9.8.7.6"); 285 286 let initialPort = await trrServer.execute( 287 `global.gDoHPortsLog[global.gDoHPortsLog.length-1]` 288 ); 289 // This one will fallback because of the timeout 290 await unregisterNS(); 291 await new TRRDNSListener("newconn.example.org", "127.0.0.1"); 292 await TestUtils.waitForCondition( 293 // 3 => CONFIRM_FAILED 294 () => Services.dns.currentTrrConfirmationState == 3, 295 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`, 296 1, 297 5000 298 ); 299 await registerNS(); 300 301 await TestUtils.waitForCondition( 302 // 2 => CONFIRM_OK 303 () => Services.dns.currentTrrConfirmationState == 2, 304 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`, 305 1, 306 5000 307 ); 308 let newConfirmationPort = await trrServer.execute( 309 `global.gDoHPortsLog[global.gDoHPortsLog.length-1]` 310 ); 311 312 // Two reqs for each bar3 and bar4 . 313 await new TRRDNSListener("bar3.example.org", "9.8.7.6"); 314 await new TRRDNSListener("bar4.example.org", "9.8.7.6"); 315 316 initialPort = await trrServer.execute( 317 `global.gDoHPortsLog[global.gDoHPortsLog.length-1]` 318 ); 319 await unregisterNS(); 320 // This one will fallback because of the timeout 321 await new TRRDNSListener("newconn2.example.org", "127.0.0.1"); 322 await TestUtils.waitForCondition( 323 // 3 => CONFIRM_FAILED 324 () => Services.dns.currentTrrConfirmationState == 3, 325 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`, 326 1, 327 5000 328 ); 329 await registerNS(); 330 331 await TestUtils.waitForCondition( 332 // 2 => CONFIRM_OK 333 () => Services.dns.currentTrrConfirmationState == 2, 334 `Timed out waiting for confirmation success. Currently ${Services.dns.currentTrrConfirmationState}`, 335 1, 336 5000 337 ); 338 newConfirmationPort = await trrServer.execute( 339 `global.gDoHPortsLog[global.gDoHPortsLog.length-1]` 340 ); 341 notEqual( 342 initialPort, 343 newConfirmationPort, 344 "Failed confirmation must cycle the connection" 345 ); 346 347 // Two reqs for each bar5 and bar6 . 348 await new TRRDNSListener("bar5.example.org", "9.8.7.6"); 349 await new TRRDNSListener("bar6.example.org", "9.8.7.6"); 350 351 let dohReqPortLog = await trrServer.execute(`global.gDoHPortsLog`); 352 353 const uniquePorts = new Set(dohReqPortLog.map(([_, port]) => port)); 354 if (uniquePorts.size < 3) { 355 info(JSON.stringify(dohReqPortLog)); 356 } 357 358 greaterOrEqual( 359 uniquePorts.size, 360 3, 361 "Connection must be cycled at least twice" 362 ); 363 });