Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
58e1ad9
feat: add SOCKS5 implementation plan
mcollina Aug 4, 2025
7d0e4d4
feat: implement SOCKS5 proxy support (Phase 1)
mcollina Aug 4, 2025
43ae9be
fix: update SOCKS5 implementation plan to require Pool instead of Client
mcollina Aug 4, 2025
c779f19
feat: complete SOCKS5 ProxyAgent integration (Phase 2)
mcollina Aug 4, 2025
b8c7d72
fix: refactor SOCKS5 proxy to use Pool instead of Client
mcollina Aug 6, 2025
0a2da5e
feat: add TypeScript definitions and comprehensive tests for SOCKS5 p…
mcollina Aug 6, 2025
abdbb19
docs: add comprehensive documentation for SOCKS5 proxy support
mcollina Aug 6, 2025
2634ca1
style: apply linting fixes for SOCKS5 implementation
mcollina Aug 6, 2025
edbaa59
feat: add IPv6 support to SOCKS5 client
mcollina Dec 22, 2025
6eedb92
fix: move docker-compose to fixtures and fix ProxyAgent close for SOCKS5
mcollina Dec 22, 2025
ce3478e
chore: remove PLAN.md
mcollina Dec 22, 2025
0658e46
refactor: rename Socks5ProxyWrapper to Socks5Agent and add experiment…
mcollina Dec 22, 2025
f5353b3
fix: remove redundant method declarations from Socks5Agent types
mcollina Dec 22, 2025
c7c82eb
refactor: move TestSocks5Server to fixtures
mcollina Dec 23, 2025
489c414
refactor: address PR review feedback for SOCKS5 support
mcollina Jan 25, 2026
1b8b506
fix: lazy load TLS module in SOCKS5 agent for --without-ssl builds
mcollina Jan 26, 2026
9426fae
fix: add defensive guard in ProxyAgent connect for SOCKS5 proxies
mcollina Mar 4, 2026
84470b3
refactor: address second round of PR review feedback
mcollina Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ const response = await fetch('https://api.example.com/data');
- Superior performance, especially with `undici.request`
- HTTP/1.1 pipelining support
- Custom interceptors and middleware
- Advanced features like `ProxyAgent`, `MockAgent`
- Advanced features like `ProxyAgent`, `Socks5Agent`, `MockAgent`

**Cons:**
- Additional dependency to manage
Expand All @@ -122,7 +122,7 @@ const response = await fetch('https://api.example.com/data');
#### Use Undici Module When:
- You need the latest undici features and performance improvements
- You require advanced connection pooling configuration
- You need APIs not available in the built-in fetch (`ProxyAgent`, `MockAgent`, etc.)
- You need APIs not available in the built-in fetch (`ProxyAgent`, `Socks5Agent`, `MockAgent`, etc.)
- Performance is critical (use `undici.request` for maximum speed)
- You want better error handling and debugging capabilities
- You need HTTP/1.1 pipelining or advanced interceptors
Expand Down
274 changes: 274 additions & 0 deletions docs/docs/api/Socks5ProxyAgent.md
Comment thread
mcollina marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
# Class: Socks5ProxyAgent

Extends: `undici.Dispatcher`

A SOCKS5 proxy wrapper class that implements the Dispatcher API. It enables HTTP requests to be routed through a SOCKS5 proxy server, providing connection tunneling and authentication support.

## `new Socks5ProxyAgent(proxyUrl[, options])`

Arguments:

* **proxyUrl** `string | URL` (required) - The SOCKS5 proxy server URL. Must use `socks5://` or `socks://` protocol.
* **options** `Socks5ProxyAgent.Options` (optional) - Additional configuration options.

Returns: `Socks5ProxyAgent`

### Parameter: `Socks5ProxyAgent.Options`

Extends: [`PoolOptions`](/docs/docs/api/Pool.md#parameter-pooloptions)

* **headers** `IncomingHttpHeaders` (optional) - Additional headers to send with proxy connections.
* **username** `string` (optional) - SOCKS5 proxy username for authentication. Can also be provided in the proxy URL.
* **password** `string` (optional) - SOCKS5 proxy password for authentication. Can also be provided in the proxy URL.
* **connect** `Function` (optional) - Custom connector function for the proxy connection.
* **proxyTls** `BuildOptions` (optional) - TLS options for the proxy connection (when using SOCKS5 over TLS).

Examples:

```js
import { Socks5ProxyAgent } from 'undici'

const socks5Proxy = new Socks5ProxyAgent('socks5://localhost:1080')
// or with authentication
const socks5ProxyWithAuth = new Socks5ProxyAgent('socks5://user:pass@localhost:1080')
// or with options
const socks5ProxyWithOptions = new Socks5ProxyAgent('socks5://localhost:1080', {
username: 'user',
password: 'pass',
connections: 10
})
```

#### Example - Basic SOCKS5 Proxy instantiation

This will instantiate the Socks5ProxyAgent. It will not do anything until registered as the dispatcher to use with requests.

```js
import { Socks5ProxyAgent } from 'undici'

const socks5Proxy = new Socks5ProxyAgent('socks5://localhost:1080')
```

#### Example - Basic SOCKS5 Proxy Request with global dispatcher

```js
import { setGlobalDispatcher, request, Socks5ProxyAgent } from 'undici'

const socks5Proxy = new Socks5ProxyAgent('socks5://localhost:1080')
setGlobalDispatcher(socks5Proxy)

const { statusCode, body } = await request('http://localhost:3000/foo')

console.log('response received', statusCode) // response received 200

for await (const data of body) {
console.log('data', data.toString('utf8')) // data foo
}
```

#### Example - Basic SOCKS5 Proxy Request with local dispatcher

```js
import { Socks5ProxyAgent, request } from 'undici'

const socks5Proxy = new Socks5ProxyAgent('socks5://localhost:1080')

const {
statusCode,
body
} = await request('http://localhost:3000/foo', { dispatcher: socks5Proxy })

console.log('response received', statusCode) // response received 200

for await (const data of body) {
console.log('data', data.toString('utf8')) // data foo
}
```

#### Example - SOCKS5 Proxy Request with authentication

```js
import { setGlobalDispatcher, request, Socks5ProxyAgent } from 'undici'

// Authentication via URL
const socks5Proxy = new Socks5ProxyAgent('socks5://username:password@localhost:1080')

// Or authentication via options
// const socks5Proxy = new Socks5ProxyAgent('socks5://localhost:1080', {
// username: 'username',
// password: 'password'
// })

setGlobalDispatcher(socks5Proxy)

const { statusCode, body } = await request('http://localhost:3000/foo')

console.log('response received', statusCode) // response received 200

for await (const data of body) {
console.log('data', data.toString('utf8')) // data foo
}
```

#### Example - SOCKS5 Proxy with HTTPS requests

SOCKS5 proxy supports both HTTP and HTTPS requests through tunneling:

```js
import { Socks5ProxyAgent, request } from 'undici'

const socks5Proxy = new Socks5ProxyAgent('socks5://localhost:1080')

const response = await request('https://api.example.com/data', {
dispatcher: socks5Proxy,
method: 'GET'
})

console.log('Response status:', response.statusCode)
console.log('Response data:', await response.body.json())
```

#### Example - SOCKS5 Proxy with Fetch

```js
import { Socks5ProxyAgent, fetch } from 'undici'

const socks5Proxy = new Socks5ProxyAgent('socks5://localhost:1080')

const response = await fetch('http://localhost:3000/api/users', {
dispatcher: socks5Proxy,
method: 'GET'
})

console.log('Response status:', response.status)
console.log('Response data:', await response.text())
```

#### Example - Connection Pooling

SOCKS5ProxyWrapper automatically manages connection pooling for better performance:

```js
import { Socks5ProxyAgent, request } from 'undici'

const socks5Proxy = new Socks5ProxyAgent('socks5://localhost:1080', {
connections: 10, // Allow up to 10 concurrent connections
pipelining: 1 // Enable HTTP/1.1 pipelining
})

// Multiple requests will reuse connections through the SOCKS5 tunnel
const responses = await Promise.all([
request('http://api.example.com/endpoint1', { dispatcher: socks5Proxy }),
request('http://api.example.com/endpoint2', { dispatcher: socks5Proxy }),
request('http://api.example.com/endpoint3', { dispatcher: socks5Proxy })
])

console.log('All requests completed through the same SOCKS5 proxy')
```

### `Socks5ProxyAgent.close()`

Closes the SOCKS5 proxy wrapper and waits for all underlying pools and connections to close before resolving.

Returns: `Promise<void>`

#### Example - clean up after tests are complete

```js
import { Socks5ProxyAgent, setGlobalDispatcher } from 'undici'

const socks5Proxy = new Socks5ProxyAgent('socks5://localhost:1080')
setGlobalDispatcher(socks5Proxy)

// ... make requests

await socks5Proxy.close()
```

### `Socks5ProxyAgent.destroy([err])`

Destroys the SOCKS5 proxy wrapper and all underlying connections immediately.

Arguments:
* **err** `Error` (optional) - The error that caused the destruction.

Returns: `Promise<void>`

#### Example - force close all connections

```js
import { Socks5ProxyAgent } from 'undici'

const socks5Proxy = new Socks5ProxyAgent('socks5://localhost:1080')

// Force close all connections
await socks5Proxy.destroy()
```

### `Socks5ProxyAgent.dispatch(options, handlers)`

Implements [`Dispatcher.dispatch(options, handlers)`](/docs/docs/api/Dispatcher.md#dispatcherdispatchoptions-handlers).

### `Socks5ProxyAgent.request(options[, callback])`

See [`Dispatcher.request(options [, callback])`](/docs/docs/api/Dispatcher.md#dispatcherrequestoptions-callback).

## Debugging

SOCKS5 proxy connections can be debugged using Node.js diagnostics:

```sh
NODE_DEBUG=undici:socks5 node script.js
```

This will output detailed information about the SOCKS5 handshake, authentication, and connection establishment.

## SOCKS5 Protocol Support

The Socks5ProxyAgent supports the following SOCKS5 features:

### Authentication Methods

- **No Authentication** (`0x00`) - For public or internal proxies
- **Username/Password** (`0x02`) - RFC 1929 authentication

### Address Types

- **IPv4** (`0x01`) - Standard IPv4 addresses
- **Domain Name** (`0x03`) - Domain names (recommended for flexibility)
- **IPv6** (`0x04`) - IPv6 addresses (full support for standard and compressed notation)

### Commands

- **CONNECT** (`0x01`) - Establish TCP connection (primary use case for HTTP)

### Error Handling

The wrapper handles various SOCKS5 error conditions:

- Connection refused by proxy
- Authentication failures
- Network unreachable
- Host unreachable
- Unsupported address types or commands

## Performance Considerations

- **Connection Pooling**: Automatically pools connections through the SOCKS5 tunnel for better performance
- **HTTP/1.1 Pipelining**: Supports pipelining when enabled
- **DNS Resolution**: Domain names are resolved by the SOCKS5 proxy, reducing local DNS queries
- **TLS Termination**: HTTPS connections are encrypted end-to-end, with the SOCKS5 proxy only handling the TCP tunnel

## Security Notes

1. **Authentication**: Credentials are sent to the SOCKS5 proxy in plaintext unless using SOCKS5 over TLS
2. **DNS Leaks**: All DNS resolution happens on the proxy server, preventing DNS leaks
3. **End-to-end Encryption**: HTTPS traffic remains encrypted between client and final destination
4. **Connection Security**: Consider using authenticated proxies and secure networks

## Compatibility

- **Protocol**: SOCKS5 (RFC 1928) with Username/Password Authentication (RFC 1929)
- **Transport**: TCP only (UDP support not implemented)
- **Node.js**: Compatible with all supported Node.js versions
- **HTTP Versions**: Works with HTTP/1.1 and HTTP/2 over the tunnel
1 change: 1 addition & 0 deletions docs/docsify/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* [RoundRobinPool](/docs/api/RoundRobinPool.md "Undici API - RoundRobinPool")
* [Agent](/docs/api/Agent.md "Undici API - Agent")
* [ProxyAgent](/docs/api/ProxyAgent.md "Undici API - ProxyAgent")
* [Socks5Agent](/docs/api/Socks5Agent.md "Undici API - SOCKS5 Agent")
* [RetryAgent](/docs/api/RetryAgent.md "Undici API - RetryAgent")
* [Connector](/docs/api/Connector.md "Custom connector")
* [Errors](/docs/api/Errors.md "Undici API - Errors")
Expand Down
Loading
Loading