diff --git a/lib/core/connect.js b/lib/core/connect.js index ad962c31944..f729dfb0526 100644 --- a/lib/core/connect.js +++ b/lib/core/connect.js @@ -105,13 +105,27 @@ function buildConnector ({ allowH2, preferH2, useH2c, maxCachedSessions, socketP port = port || 80 - socket = net.connect({ + const connectOptions = { highWaterMark: 64 * 1024, // Same as nodejs fs streams. ...options, localAddress, port, host: hostname - }) + } + + const family = net.isIP(hostname) + if (family !== 0 && servername && servername !== hostname) { + connectOptions.host = servername + connectOptions.lookup = (_hostname, lookupOptions, cb) => { + if (lookupOptions.all) { + cb(null, [{ address: hostname, family }]) + } else { + cb(null, hostname, family) + } + } + } + + socket = net.connect(connectOptions) if (useH2c === true) { socket.alpnProtocol = 'h2' } diff --git a/lib/dispatcher/client.js b/lib/dispatcher/client.js index 8a4f65171bd..3383c1ad306 100644 --- a/lib/dispatcher/client.js +++ b/lib/dispatcher/client.js @@ -672,6 +672,7 @@ function _resume (client, sync) { } if (!client[kHTTPContext]) { + client[kServerName] = request.servername connect(client) return } diff --git a/test/interceptors/dns.js b/test/interceptors/dns.js index f4acdf72277..1dfa326bc48 100644 --- a/test/interceptors/dns.js +++ b/test/interceptors/dns.js @@ -2,7 +2,8 @@ const FakeTimers = require('@sinonjs/fake-timers') const { test, after } = require('node:test') -const { isIP } = require('node:net') +const net = require('node:net') +const { isIP } = net const { lookup } = require('node:dns') const { createServer } = require('node:http') const { createServer: createSecureServer } = require('node:https') @@ -215,6 +216,63 @@ test('Should respect DNS origin hostname for SNI on TLS', async t => { t.equal(await response2.body.text(), 'hello world!') }) +test('#5573 - Should preserve DNS origin hostname on HTTP sockets', async context => { + const t = tspl(context, { plan: 5 }) + + const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { + res.end('hello world!') + }) + + server.listen(0, '127.0.0.1') + await once(server, 'listening') + + const client = new Agent().compose(dns({ + dualStack: false, + lookup (_origin, _opts, cb) { + cb(null, [{ address: '127.0.0.1', family: 4 }]) + } + })) + + context.after(async () => { + await client.close() + await new Promise(resolve => server.close(resolve)) + }) + + const originalConnect = net.connect + let connectOptions + + net.connect = function (...args) { + connectOptions = args[0] + return originalConnect.apply(this, args) + } + context.after(() => { + net.connect = originalConnect + }) + + const response = await client.request({ + method: 'GET', + path: '/', + origin: `http://localhost:${server.address().port}` + }) + + t.equal(response.statusCode, 200) + t.equal(await response.body.text(), 'hello world!') + t.equal(connectOptions.host, 'localhost') + + await new Promise((resolve, reject) => { + connectOptions.lookup(connectOptions.host, {}, (err, address, family) => { + if (err) { + reject(err) + return + } + + t.equal(address, '127.0.0.1') + t.equal(family, 4) + resolve() + }) + }) +}) + test('Should recover on network errors (dual stack - 4)', async t => { t = tspl(t, { plan: 7 })