diff --git a/lib/dispatcher/socks5-proxy-agent.js b/lib/dispatcher/socks5-proxy-agent.js index c0b681929ac..db60a8a025f 100644 --- a/lib/dispatcher/socks5-proxy-agent.js +++ b/lib/dispatcher/socks5-proxy-agent.js @@ -1,6 +1,5 @@ 'use strict' -const net = require('node:net') const { URL } = require('node:url') let tls // include tls conditionally since it is not always available @@ -17,6 +16,7 @@ const debug = debuglog('undici:socks5-proxy') const kProxyUrl = Symbol('proxy url') const kProxyHeaders = Symbol('proxy headers') const kProxyAuth = Symbol('proxy auth') +const kProxyProtocol = Symbol('proxy protocol') const kPools = Symbol('pools') const kConnector = Symbol('connector') @@ -52,6 +52,7 @@ class Socks5ProxyAgent extends DispatcherBase { this[kProxyUrl] = url this[kProxyHeaders] = options.headers || {} + this[kProxyProtocol] = options.proxyTls ? 'https:' : 'http:' // Extract auth from URL or options this[kProxyAuth] = { @@ -81,25 +82,20 @@ class Socks5ProxyAgent extends DispatcherBase { // Connect to the SOCKS5 proxy const socketReady = Promise.withResolvers() - const onSocketConnect = () => { - socket.removeListener('error', onSocketError) - socketReady.resolve(socket) - } - - const onSocketError = (err) => { - socket.removeListener('connect', onSocketConnect) - socketReady.reject(err) - } - - const socket = net.connect({ + this[kConnector]({ + hostname: proxyHost, host: proxyHost, - port: proxyPort + port: proxyPort, + protocol: this[kProxyProtocol] + }, (err, socket) => { + if (err) { + socketReady.reject(err) + } else { + socketReady.resolve(socket) + } }) - socket.once('connect', onSocketConnect) - socket.once('error', onSocketError) - - await socketReady.promise + const socket = await socketReady.promise // Create SOCKS5 client const socks5Client = new Socks5Client(socket, this[kProxyAuth]) diff --git a/test/socks5-proxy-agent.js b/test/socks5-proxy-agent.js index 51587f6d4cb..9ffd9bb953e 100644 --- a/test/socks5-proxy-agent.js +++ b/test/socks5-proxy-agent.js @@ -1,5 +1,6 @@ 'use strict' +const net = require('node:net') const { tspl } = require('@matteo.collina/tspl') const { test } = require('node:test') const { request } = require('..') @@ -34,6 +35,55 @@ test('Socks5ProxyAgent - constructor validation', async (t) => { await p.completed }) +test('Socks5ProxyAgent - uses custom connector for proxy connection', async (t) => { + const p = tspl(t, { plan: 3 }) + + const server = createServer((req, res) => { + res.writeHead(200, { 'content-type': 'application/json' }) + res.end(JSON.stringify({ ok: true })) + }) + + await new Promise((resolve) => { + server.listen(0, resolve) + }) + const serverPort = server.address().port + + const socksServer = new TestSocks5Server() + const socksAddress = await socksServer.listen() + let connectorCalled = false + + const proxyWrapper = new Socks5ProxyAgent(`socks5://localhost:${socksAddress.port}`, { + connect (opts, callback) { + connectorCalled = true + const socket = net.connect({ + host: opts.hostname, + port: opts.port + }) + socket.once('connect', () => callback(null, socket)) + socket.once('error', callback) + return socket + } + }) + + const response = await request(`http://localhost:${serverPort}/test`, { + dispatcher: proxyWrapper + }) + + p.equal(response.statusCode, 200, 'should get 200 status code') + p.deepEqual(await response.body.json(), { ok: true }, 'should get correct response body') + p.equal(connectorCalled, true, 'should use custom connector for proxy connection') + + t.after(async () => { + if (proxyWrapper) { + await proxyWrapper.close() + } + await socksServer.close() + server.close() + }) + + await p.completed +}) + test('Socks5ProxyAgent - basic HTTP connection', async (t) => { const p = tspl(t, { plan: 2 })