# socks5 vs socks5h: what six clients send to the proxy

This download backs the post at https://ipvolt.com/blog/socks5-vs-socks5h. It
shows what a client really sends to a SOCKS5 proxy for the `socks5://`,
`socks5h://` and `socks://` schemes: an IP address it resolved itself, or the
hostname for the proxy to resolve.

Everything listens on `127.0.0.1`. No proxy account is needed and no request
reaches a target: the server refuses every CONNECT. The only traffic that
leaves the machine is the DNS lookup for the unresolvable test name.

## Check one client by hand

`socks5_log.py` is the whole server: 42 lines, standard library only. It
accepts the SOCKS5 greeting, prints the address type and address of each
request, then answers "general SOCKS server failure" (RFC 1928 reply `0x01`).

Add a test name to `/etc/hosts`. Do not use `localhost`; browsers bypass
proxies for it.

```text
127.0.0.2 dnsprobe.example
```

```sh
python3 socks5_log.py 1080
curl -x socks5://127.0.0.1:1080 http://dnsprobe.example/
curl -x socks5h://127.0.0.1:1080 http://dnsprobe.example/
```

The server prints one line per request (`quickcheck.log` is the recorded run,
`quickcheck.sh` the script that produced it):

```text
LISTENING 127.0.0.1:1080
CMD=1 IPv4 127.0.0.2 port=80
CMD=1 DOMAIN dnsprobe.example port=80
```

`IPv4` or `IPv6` means the client resolved the name locally. `DOMAIN` means
the proxy received the hostname. No line means the client rejected the proxy
URL or bypassed the proxy. `NOT-SOCKS5 first byte 0x04` is a SOCKS4 client.

## Run the whole matrix

Requirements: Python 3, Node.js, curl, and Playwright's Chromium
(`npx playwright install chromium`). The recorded run used Python 3.14.4 and
Node.js 24.20.0.

```sh
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
npm install
sudo ./run_lab.sh
```

`run_lab.sh` binds a private copy of `/etc/hosts` (with the lines from
`hosts.lab` appended) inside a Linux mount namespace, so the real hosts file is
not edited; that is why it needs root. On a machine you own you can instead add
the `hosts.lab` lines to `/etc/hosts` and run `.venv/bin/python run_lab.py`.

Environment variables: `LAB_PORT` (default 11080) and `LAB_CURLS`, a
colon-separated list of curl binaries to test (default `curl`).

## Cases

Each client runs these, one process per case:

| Case | Scheme | Target |
| --- | --- | --- |
| http | `socks5`, `socks5h`, `socks` | `http://dnsprobe.example/` (hosts file: `127.0.0.2`) |
| https | `socks5`, `socks5h` | `https://dnsprobe.example/` |
| unresolvable | `socks5`, `socks5h` | `http://dnsprobe-missing.example/` (resolves nowhere) |
| ipv6-only | `socks5` | `http://dnsprobe6.example/` (hosts file: `2001:db8::2`) |

Extra cases: curl with `--socks5` and `--socks5-hostname`, and aiohttp-socks
with `rdns=False` and `rdns=True`.

## Files

- `socks5_log.py`: the logging SOCKS5 server.
- `client_requests.py`, `client_httpx.py`, `client_aiohttp.py`: one GET each,
  printing library versions and the exception as JSON.
- `client_playwright.cjs`: one Chromium navigation. `client_node.mjs`: one GET
  with Node's `http`/`https` module and `socks-proxy-agent`.
- `run_lab.py`, `run_lab.sh`, `hosts.lab`: the runner.
- `requirements.txt`, `package.json`: the pinned versions of the recorded run.
- `results.json`: all 62 recorded cases. Per case: the command, exit code,
  stdout, stderr, the server's log lines and `proxySaw`, the address type and
  address the server received for the test name.

## Recorded run

4 October 2026, Ubuntu 26.04 (x86_64), Python 3.14.4, Node.js 24.20.0.

- curl 8.22.0 (static build) and curl 8.18.0 (Ubuntu package)
- Requests 2.34.2 with PySocks 1.7.1 and urllib3 2.8.0
- HTTPX 0.28.1 with httpcore 1.0.9 and socksio 1.0.0
- aiohttp 3.14.3 with aiohttp-socks 0.12.0 and python-socks 3.1.1
- Playwright 1.63.0 with Chromium 153.0.8010.12
- socks-proxy-agent 10.1.0 with socks 2.8.10

| Client | `socks5://` | `socks5h://` | `socks://` |
| --- | --- | --- | --- |
| curl 8.22.0 and 8.18.0 | IPv4 127.0.0.2 | DOMAIN dnsprobe.example | SOCKS4 greeting (`0x04`), exit 97 |
| Requests | IPv4 127.0.0.2 | DOMAIN dnsprobe.example | `ValueError`, no connection |
| HTTPX | DOMAIN dnsprobe.example | DOMAIN dnsprobe.example | `ValueError`, no connection |
| aiohttp-socks | DOMAIN dnsprobe.example | `ValueError`, no connection | `ValueError`, no connection |
| Playwright Chromium | DOMAIN dnsprobe.example | `net::ERR_NO_SUPPORTED_PROXIES`, no connection | DOMAIN dnsprobe.example |
| socks-proxy-agent | IPv4 127.0.0.2 | DOMAIN dnsprobe.example | DOMAIN dnsprobe.example |

## Limits

One loopback server that does no authentication and refuses every request. The
lab says nothing about any proxy service, SOCKS5 authentication, UDP ASSOCIATE,
SOCKS4 proxies, macOS, Windows, Firefox or WebKit. Library defaults change
between releases; rerun the lab against the versions you use.
