From 4ce4abad1dd7bf8ecf001f94de06599ffbb0e945 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Sun, 10 Nov 2024 23:54:23 +0000 Subject: [PATCH 1/3] fix(#3817): send servername for SNI on TLS --- lib/interceptor/dns.js | 15 +++++-- test/interceptors/dns.js | 88 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/lib/interceptor/dns.js b/lib/interceptor/dns.js index bd2f4e8e95f..cad00d7820b 100644 --- a/lib/interceptor/dns.js +++ b/lib/interceptor/dns.js @@ -352,9 +352,18 @@ module.exports = interceptorOpts => { return handler.onError(err) } - const dispatchOpts = { - ...origDispatchOpts, - origin: newOrigin + let dispatchOpts = null + if (origin.protocol === 'https:') { + dispatchOpts = { + ...origDispatchOpts, + servername: origin.hostname, // For SNI on TLS + origin: newOrigin + } + } else { + dispatchOpts = { + ...origDispatchOpts, + origin: newOrigin + } } dispatch( diff --git a/test/interceptors/dns.js b/test/interceptors/dns.js index 18bacc4bad7..40ecdeb896d 100644 --- a/test/interceptors/dns.js +++ b/test/interceptors/dns.js @@ -4,10 +4,12 @@ const { test, after } = require('node:test') const { isIP } = require('node:net') const { lookup } = require('node:dns') const { createServer } = require('node:http') +const { createServer: createSecureServer } = require('node:https') const { once } = require('node:events') const { setTimeout: sleep } = require('node:timers/promises') const { tspl } = require('@matteo.collina/tspl') +const pem = require('https-pem') const { interceptors, Agent } = require('../..') const { dns } = interceptors @@ -108,6 +110,92 @@ test('Should automatically resolve IPs (dual stack)', async t => { t.equal(await response2.body.text(), 'hello world!') }) +test('Should respect DNS origin hostname for SNI on TLS', async t => { + t = tspl(t, { plan: 10 }) + + const hostsnames = [] + const server = createSecureServer(pem) + const requestOptions = { + method: 'GET', + path: '/', + headers: { + 'content-type': 'application/json' + } + } + + server.on('request', (req, res) => { + res.writeHead(200, { 'content-type': 'text/plain' }) + res.end('hello world!') + }) + + server.listen(0) + + await once(server, 'listening') + + const client = new Agent({ + connect: { + rejectUnauthorized: false + } + }).compose([ + dispatch => { + return (opts, handler) => { + const url = new URL(opts.origin) + + t.equal(hostsnames.includes(url.hostname), false) + t.equal(opts.servername, 'localhost') + + if (url.hostname[0] === '[') { + // [::1] -> ::1 + t.equal(isIP(url.hostname.slice(1, 4)), 6) + } else { + t.equal(isIP(url.hostname), 4) + } + + hostsnames.push(url.hostname) + + return dispatch(opts, handler) + } + }, + dns({ + lookup: (_origin, _opts, cb) => { + cb(null, [ + { + address: '::1', + family: 6 + }, + { + address: '127.0.0.1', + family: 4 + } + ]) + } + }) + ]) + + after(async () => { + await client.close() + server.close() + + await once(server, 'close') + }) + + const response = await client.request({ + ...requestOptions, + origin: `https://localhost:${server.address().port}` + }) + + t.equal(response.statusCode, 200) + t.equal(await response.body.text(), 'hello world!') + + const response2 = await client.request({ + ...requestOptions, + origin: `https://localhost:${server.address().port}` + }) + + t.equal(response2.statusCode, 200) + t.equal(await response2.body.text(), 'hello world!') +}) + test('Should recover on network errors (dual stack - 4)', async t => { t = tspl(t, { plan: 8 }) From 0bf4b0d578e182f1969549cb27785fa8ae996129 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Mon, 11 Nov 2024 23:04:29 +0000 Subject: [PATCH 2/3] fix: set host header to servername --- lib/interceptor/dns.js | 6 +++++- test/interceptors/dns.js | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/interceptor/dns.js b/lib/interceptor/dns.js index cad00d7820b..c3e0d37f48e 100644 --- a/lib/interceptor/dns.js +++ b/lib/interceptor/dns.js @@ -357,7 +357,11 @@ module.exports = interceptorOpts => { dispatchOpts = { ...origDispatchOpts, servername: origin.hostname, // For SNI on TLS - origin: newOrigin + origin: newOrigin, + headers: { + host: origin.hostname, + ...origDispatchOpts.headers + } } } else { dispatchOpts = { diff --git a/test/interceptors/dns.js b/test/interceptors/dns.js index 40ecdeb896d..6b4b30b13cc 100644 --- a/test/interceptors/dns.js +++ b/test/interceptors/dns.js @@ -111,7 +111,7 @@ test('Should automatically resolve IPs (dual stack)', async t => { }) test('Should respect DNS origin hostname for SNI on TLS', async t => { - t = tspl(t, { plan: 10 }) + t = tspl(t, { plan: 12 }) const hostsnames = [] const server = createSecureServer(pem) @@ -124,6 +124,7 @@ test('Should respect DNS origin hostname for SNI on TLS', async t => { } server.on('request', (req, res) => { + t.equal(req.headers.host, 'localhost') res.writeHead(200, { 'content-type': 'text/plain' }) res.end('hello world!') }) From 646222496275b0c4e99589a973cee9712c61de0b Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Wed, 20 Nov 2024 10:09:46 +0100 Subject: [PATCH 3/3] refactor: attach regardless --- lib/interceptor/dns.js | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/interceptor/dns.js b/lib/interceptor/dns.js index c3e0d37f48e..917732646e6 100644 --- a/lib/interceptor/dns.js +++ b/lib/interceptor/dns.js @@ -353,20 +353,13 @@ module.exports = interceptorOpts => { } let dispatchOpts = null - if (origin.protocol === 'https:') { - dispatchOpts = { - ...origDispatchOpts, - servername: origin.hostname, // For SNI on TLS - origin: newOrigin, - headers: { - host: origin.hostname, - ...origDispatchOpts.headers - } - } - } else { - dispatchOpts = { - ...origDispatchOpts, - origin: newOrigin + dispatchOpts = { + ...origDispatchOpts, + servername: origin.hostname, // For SNI on TLS + origin: newOrigin, + headers: { + host: origin.hostname, + ...origDispatchOpts.headers } }