From 103659985a87b0a84988877257653803e12af506 Mon Sep 17 00:00:00 2001 From: cyphercodes Date: Tue, 21 Jul 2026 08:53:47 +0300 Subject: [PATCH 1/4] fix: preserve DNS origin hostname on sockets Signed-off-by: cyphercodes --- lib/core/connect.js | 18 ++++++++++++-- lib/dispatcher/client.js | 3 +++ test/interceptors/dns.js | 51 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) 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..48feec147e5 100644 --- a/lib/dispatcher/client.js +++ b/lib/dispatcher/client.js @@ -672,6 +672,9 @@ function _resume (client, sync) { } if (!client[kHTTPContext]) { + if (client[kUrl].protocol === 'http:') { + client[kServerName] = request.servername + } connect(client) return } diff --git a/test/interceptors/dns.js b/test/interceptors/dns.js index f4acdf72277..3cfe77cceca 100644 --- a/test/interceptors/dns.js +++ b/test/interceptors/dns.js @@ -7,6 +7,7 @@ const { lookup } = require('node:dns') const { createServer } = require('node:http') const { createServer: createSecureServer } = require('node:https') const { once } = require('node:events') +const diagnosticsChannel = require('node:diagnostics_channel') const { tspl } = require('@matteo.collina/tspl') const pem = require('@metcoder95/https-pem') @@ -215,6 +216,56 @@ 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: 4 }) + + 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 socketChannel = diagnosticsChannel.channel('net.client.socket') + const socketDetails = new Promise(resolve => { + const onSocket = ({ socket }) => { + socket.once('connect', () => { + if (socket.remotePort === server.address().port) { + resolve({ host: socket._host, remoteAddress: socket.remoteAddress }) + } + }) + } + + socketChannel.subscribe(onSocket) + context.after(() => socketChannel.unsubscribe(onSocket)) + }) + + 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!') + + const { host, remoteAddress } = await socketDetails + t.equal(host, 'localhost') + t.equal(remoteAddress, '127.0.0.1') +}) + test('Should recover on network errors (dual stack - 4)', async t => { t = tspl(t, { plan: 7 }) From 306602e1a55a12f40c572c68eec5ba18c5ecfa02 Mon Sep 17 00:00:00 2001 From: cyphercodes Date: Tue, 21 Jul 2026 17:02:15 +0300 Subject: [PATCH 2/4] test: avoid diagnostics channel in DNS socket test Signed-off-by: cyphercodes --- test/interceptors/dns.js | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/test/interceptors/dns.js b/test/interceptors/dns.js index 3cfe77cceca..881648ce6b5 100644 --- a/test/interceptors/dns.js +++ b/test/interceptors/dns.js @@ -2,12 +2,12 @@ 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') const { once } = require('node:events') -const diagnosticsChannel = require('node:diagnostics_channel') const { tspl } = require('@matteo.collina/tspl') const pem = require('@metcoder95/https-pem') @@ -238,18 +238,17 @@ test('#5573 - Should preserve DNS origin hostname on HTTP sockets', async contex await new Promise(resolve => server.close(resolve)) }) - const socketChannel = diagnosticsChannel.channel('net.client.socket') - const socketDetails = new Promise(resolve => { - const onSocket = ({ socket }) => { - socket.once('connect', () => { - if (socket.remotePort === server.address().port) { - resolve({ host: socket._host, remoteAddress: socket.remoteAddress }) - } - }) - } + const originalConnect = net.connect + let socket + let connectOptions - socketChannel.subscribe(onSocket) - context.after(() => socketChannel.unsubscribe(onSocket)) + net.connect = function (...args) { + connectOptions = args[0] + socket = originalConnect.apply(this, args) + return socket + } + context.after(() => { + net.connect = originalConnect }) const response = await client.request({ @@ -260,10 +259,8 @@ test('#5573 - Should preserve DNS origin hostname on HTTP sockets', async contex t.equal(response.statusCode, 200) t.equal(await response.body.text(), 'hello world!') - - const { host, remoteAddress } = await socketDetails - t.equal(host, 'localhost') - t.equal(remoteAddress, '127.0.0.1') + t.equal(connectOptions.host, 'localhost') + t.equal(socket.remoteAddress, '127.0.0.1') }) test('Should recover on network errors (dual stack - 4)', async t => { From 67808e2bab9ae0c2895bc88a198b392d69d05dc8 Mon Sep 17 00:00:00 2001 From: cyphercodes Date: Wed, 22 Jul 2026 05:18:35 +0300 Subject: [PATCH 3/4] test: avoid socket remoteAddress in DNS regression --- test/interceptors/dns.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/interceptors/dns.js b/test/interceptors/dns.js index 881648ce6b5..1dfa326bc48 100644 --- a/test/interceptors/dns.js +++ b/test/interceptors/dns.js @@ -217,7 +217,7 @@ test('Should respect DNS origin hostname for SNI on TLS', async t => { }) test('#5573 - Should preserve DNS origin hostname on HTTP sockets', async context => { - const t = tspl(context, { plan: 4 }) + const t = tspl(context, { plan: 5 }) const server = createServer({ joinDuplicateHeaders: true }, (req, res) => { res.end('hello world!') @@ -239,13 +239,11 @@ test('#5573 - Should preserve DNS origin hostname on HTTP sockets', async contex }) const originalConnect = net.connect - let socket let connectOptions net.connect = function (...args) { connectOptions = args[0] - socket = originalConnect.apply(this, args) - return socket + return originalConnect.apply(this, args) } context.after(() => { net.connect = originalConnect @@ -260,7 +258,19 @@ test('#5573 - Should preserve DNS origin hostname on HTTP sockets', async contex t.equal(response.statusCode, 200) t.equal(await response.body.text(), 'hello world!') t.equal(connectOptions.host, 'localhost') - t.equal(socket.remoteAddress, '127.0.0.1') + + 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 => { From 4de0540020d5b75e310cc8664be80f1e003d1982 Mon Sep 17 00:00:00 2001 From: cyphercodes Date: Wed, 22 Jul 2026 11:33:50 +0300 Subject: [PATCH 4/4] test: cover DNS hostname for all protocols --- lib/dispatcher/client.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/dispatcher/client.js b/lib/dispatcher/client.js index 48feec147e5..3383c1ad306 100644 --- a/lib/dispatcher/client.js +++ b/lib/dispatcher/client.js @@ -672,9 +672,7 @@ function _resume (client, sync) { } if (!client[kHTTPContext]) { - if (client[kUrl].protocol === 'http:') { - client[kServerName] = request.servername - } + client[kServerName] = request.servername connect(client) return }