Skip to content

Commit

Permalink
[socks-proxy-agent] pass socket_options to SocksClient (#302)
Browse files Browse the repository at this point in the history
Co-authored-by: Nathan Rajlich <n@n8.io>
  • Loading branch information
lukekarrys and TooTallNate authored Mar 30, 2024
1 parent e62863c commit ada656d
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/silver-pumas-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"socks-proxy-agent": patch
---

Pass `socket_options` to `SocksClient`
11 changes: 10 additions & 1 deletion packages/socks-proxy-agent/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,15 @@ function parseSocksURL(url: URL): { lookup: boolean; proxy: SocksProxy } {
return { lookup, proxy };
}

type SocksSocketOptions = Omit<net.TcpNetConnectOpts, 'port' | 'host'>;

export type SocksProxyAgentOptions = Omit<
SocksProxy,
// These come from the parsed URL
'ipaddress' | 'host' | 'port' | 'type' | 'userId' | 'password'
> &
> & {
socketOptions?: SocksSocketOptions;
} &
http.AgentOptions;

export class SocksProxyAgent extends Agent {
Expand All @@ -90,6 +94,7 @@ export class SocksProxyAgent extends Agent {
readonly shouldLookup: boolean;
readonly proxy: SocksProxy;
timeout: number | null;
socketOptions: SocksSocketOptions | null;

constructor(uri: string | URL, opts?: SocksProxyAgentOptions) {
super(opts);
Expand All @@ -100,6 +105,7 @@ export class SocksProxyAgent extends Agent {
this.shouldLookup = lookup;
this.proxy = proxy;
this.timeout = opts?.timeout ?? null;
this.socketOptions = opts?.socketOptions ?? null;
}

/**
Expand Down Expand Up @@ -141,6 +147,9 @@ export class SocksProxyAgent extends Agent {
},
command: 'connect',
timeout: timeout ?? undefined,
// @ts-expect-error the type supplied by socks for socket_options is wider
// than necessary since socks will always override the host and port
socket_options: this.socketOptions ?? undefined,
};

const cleanup = (tlsSocket?: tls.TLSSocket) => {
Expand Down
47 changes: 42 additions & 5 deletions packages/socks-proxy-agent/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,26 @@ describe('SocksProxyAgent', () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let socksServer: any;
let socksServerUrl: URL;
let socksServerHost: string | null = null;

beforeAll(async () => {
beforeEach(async () => {
// setup SOCKS proxy server
// @ts-expect-error no types for `socksv5`
socksServer = socks.createServer(function (_info, accept) {
accept();
});
await listen(socksServer);
const port = socksServer.address().port;
socksServerUrl = new URL(`socks://127.0.0.1:${port}`);
await listen(socksServer, 0, socksServerHost ?? '127.0.0.1');
const { port, family, address } = socksServer.address();
socksServerUrl = new URL(
`socks://${family === 'IPv6' ? 'localhost' : address}:${port}`
);
socksServer.useAuth(socks.auth.None());
});

afterEach(() => {
socksServer.close();
});

beforeAll(async () => {
// setup target HTTP server
httpServer = http.createServer();
Expand All @@ -55,7 +62,6 @@ describe('SocksProxyAgent', () => {
});

afterAll(() => {
socksServer.close();
httpServer.close();
httpsServer.close();
});
Expand Down Expand Up @@ -90,6 +96,37 @@ describe('SocksProxyAgent', () => {
});
});

describe('ipv6 host', () => {
beforeAll(() => {
socksServerHost = '::1';
});
afterAll(() => {
socksServerHost = null;
});

it('should connect over ipv6 socket', async () => {
httpServer.once('request', (req, res) => res.end());

const res = await req(new URL('/foo', httpServerUrl), {
agent: new SocksProxyAgent(socksServerUrl, { socketOptions: { family: 6 } }),
});
assert(res);
});

it('should refuse connection over ipv4 socket', async () => {
let err: Error | undefined;
try {
await req(new URL('/foo', httpServerUrl), {
agent: new SocksProxyAgent(socksServerUrl, { socketOptions: { family: 4 } }),
});
} catch (_err) {
err = _err as Error;
}
assert(err);
assert.equal(err.message, `connect ECONNREFUSED 127.0.0.1:${socksServerUrl.port}`);
});
});

describe('"http" module', () => {
it('should work against an HTTP endpoint', async () => {
httpServer.once('request', function (req, res) {
Expand Down

0 comments on commit ada656d

Please sign in to comment.