From b93a83447a99fecf41a09c1b6857ae855a2254c9 Mon Sep 17 00:00:00 2001 From: Carlos Fuentes Date: Wed, 20 Nov 2024 10:18:51 +0100 Subject: [PATCH] fix(#3817): send servername for SNI on TLS (#3821) * fix(#3817): send servername for SNI on TLS * fix: set host header to servername * refactor: attach regardless --- lib/interceptor/dns.js | 10 ++++- test/interceptors/dns.js | 89 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/lib/interceptor/dns.js b/lib/interceptor/dns.js index bd2f4e8e95f..917732646e6 100644 --- a/lib/interceptor/dns.js +++ b/lib/interceptor/dns.js @@ -352,9 +352,15 @@ module.exports = interceptorOpts => { return handler.onError(err) } - const dispatchOpts = { + let dispatchOpts = null + dispatchOpts = { ...origDispatchOpts, - origin: newOrigin + servername: origin.hostname, // For SNI on TLS + origin: newOrigin, + headers: { + host: origin.hostname, + ...origDispatchOpts.headers + } } dispatch( diff --git a/test/interceptors/dns.js b/test/interceptors/dns.js index 18bacc4bad7..6b4b30b13cc 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,93 @@ 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: 12 }) + + const hostsnames = [] + const server = createSecureServer(pem) + const requestOptions = { + method: 'GET', + path: '/', + headers: { + 'content-type': 'application/json' + } + } + + server.on('request', (req, res) => { + t.equal(req.headers.host, 'localhost') + 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 })