Skip to content

Commit f89935a

Browse files
committed
fix: preserve original hostname on HTTP socket when using DNS interceptor
When the DNS interceptor resolves the hostname and replaces the connection origin with the resolved IP address, the original hostname was lost for HTTP connections. For HTTPS, the hostname was still available through TLS SNI (servername), but for plain HTTP it was nowhere accessible on the socket. This fix: 1. Sets client[kServerName] for HTTP connections when request.servername differs (the DNS interceptor already sets servername in dispatch opts) 2. Overrides socket._host with the original hostname for HTTP connections when servername is provided Fixes: #5573
1 parent 197a83d commit f89935a

3 files changed

Lines changed: 68 additions & 1 deletion

File tree

lib/core/connect.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ function buildConnector ({ allowH2, preferH2, useH2c, maxCachedSessions, socketP
115115
if (useH2c === true) {
116116
socket.alpnProtocol = 'h2'
117117
}
118+
119+
if (servername) {
120+
socket._host = servername
121+
}
118122
}
119123

120124
// Set TCP keep alive options on the socket here instead of in connect() for the case of assigning the socket

lib/dispatcher/client.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,8 @@ function _resume (client, sync) {
665665
client[kHTTPContext] = null
666666
resume(client)
667667
})
668+
} else if (client[kUrl].protocol !== 'https:' && client[kServerName] !== request.servername) {
669+
client[kServerName] = request.servername
668670
}
669671

670672
if (client[kConnecting]) {

test/interceptors/dns.js

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const { once } = require('node:events')
1111
const { tspl } = require('@matteo.collina/tspl')
1212
const pem = require('@metcoder95/https-pem')
1313

14-
const { interceptors, Agent, Client, Pool, request } = require('../..')
14+
const { interceptors, Agent, Client, Pool, request, fetch } = require('../..')
1515
const { dns } = interceptors
1616

1717
// Helper to check if IPv6 is available for localhost
@@ -2252,3 +2252,64 @@ test('#4234 - Should pass Pool requests without origin through', async t => {
22522252
t.equal(await response.body.text(), 'hello world!')
22532253
t.equal(lookupCount, 0)
22542254
})
2255+
2256+
test('Should preserve original hostname on HTTP socket when using DNS interceptor', async t => {
2257+
t = tspl(t, { plan: 2 })
2258+
2259+
const server = createServer()
2260+
server.on('request', (req, res) => {
2261+
res.writeHead(200, { 'content-type': 'text/plain' })
2262+
res.end('hello world!')
2263+
})
2264+
2265+
server.listen(0)
2266+
await once(server, 'listening')
2267+
2268+
let hostFromSocket = null
2269+
const RESOLVED_ADDRESS = '127.0.0.1'
2270+
const diagnosticsChannel = require('node:diagnostics_channel')
2271+
const netClientSocketChannel = diagnosticsChannel.channel('net.client.socket')
2272+
2273+
after(async () => {
2274+
await dispatcher.close()
2275+
server.close()
2276+
await once(server, 'close')
2277+
})
2278+
2279+
// Subscribe to the net.client.socket channel BEFORE creating the socket
2280+
const connectPromise = new Promise((resolve) => {
2281+
netClientSocketChannel.subscribe(({ socket }) => {
2282+
socket.once('connect', () => {
2283+
hostFromSocket = socket._host
2284+
resolve()
2285+
})
2286+
})
2287+
})
2288+
2289+
const dispatcher = new Agent().compose(
2290+
dns({
2291+
maxTTL: 60_000,
2292+
lookup (_origin, _opts, cb) {
2293+
cb(null, [
2294+
{
2295+
address: RESOLVED_ADDRESS,
2296+
family: 4,
2297+
ttl: 60_000
2298+
}
2299+
])
2300+
}
2301+
})
2302+
)
2303+
2304+
const url = `http://localhost:${server.address().port}`
2305+
2306+
// Perform a request with the DNS interceptor
2307+
const response = await fetch(url, { dispatcher })
2308+
t.equal(response.status, 200)
2309+
2310+
// Wait for the connect event to fire
2311+
await connectPromise
2312+
2313+
// The socket._host should be the original hostname, not the resolved IP
2314+
t.equal(hostFromSocket, 'localhost')
2315+
})

0 commit comments

Comments
 (0)