A high-performance, event-driven SOCKS5 proxy server written in a single C file. Fully RFC 1928/1929 compliant, supporting all three SOCKS5 commands (CONNECT, BIND, UDP ASSOCIATE), multiple authentication methods, and cross-platform operation on macOS, Linux, and Windows.
Version: 0.2.0
License: PolyForm Noncommercial 1.0.0
- RFC 1928 — SOCKS Protocol Version 5
- RFC 1929 — Username/Password authentication
- RFC 1961 — GSSAPI authentication (optional, compile-time flag)
| Command | Description |
|---|---|
CONNECT |
TCP proxying to a remote host |
BIND |
Two-stage reverse connection (e.g. active-mode FTP) |
UDP ASSOCIATE |
UDP relay with full SOCKS5 header encapsulation |
- IPv4
- IPv6
- Domain name (resolved server-side)
- No Auth — open proxy mode
- Username/Password (RFC 1929) — constant-time credential comparison to prevent timing attacks
- GSSAPI (RFC 1961) — Kerberos/GSS, opt-in at compile time via
GSSAPI=1
- Single-threaded event loop — no thread-per-connection overhead
- kqueue on macOS/BSD, epoll on Linux (edge-triggered)
- Non-blocking async connect — outbound connections never stall the loop
- 64 KB shared relay buffer — zero per-connection allocation during relay
- 256 KB socket send/recv buffers — enlarged for throughput
TCP_NODELAY— disabled Nagle algorithm on all socketsSO_REUSEPORT— available where the OS supports itaccept4()on Linux — atomicO_NONBLOCKon accept- Deferred connection cleanup — avoids use-after-free within event batches
- ~200 byte active connection struct — compact memory footprint
- ~5 MB base RSS — minimal baseline memory usage
- 1,000,000 default max connections — fd limit auto-raised at startup
- Connection limiting —
--max-conndrops excess connections - IP allowlist —
--allow-ipaccepts connections only from listed IPs (binary-searched, repeatable) - Constant-time credential comparison — guards against timing side-channels on auth
- Timestamped log lines —
[YYYY-MM-DD HH:MM:SS]prefix on every message - Per-session statistics — bytes sent, bytes received, and duration logged on close
- Debug mode —
-d/--debugenables verbose protocol-level tracing - Log levels — INFO, WARN, ERROR, DEBUG
--install— installs the binary and generates a systemd unit file--uninstall— removes the installed binary and unit file--version/-V— prints the version string and exits
Requires a C11 compiler (clang or gcc). The Makefile auto-detects the available compiler.
# Build the proxy binary
make
# Build the test suite
make tests
# Build and run the full test suite
make check
# Build with GSSAPI support enabled (requires libgssapi_krb5 on Linux or GSS.framework on macOS)
make GSSAPI=1
# Clean build artifacts
make cleanThe binary is built as ./socks5.
Usage: socks5 [options] [port]
Options:
-p, --port <port> Port to listen on (default: 1080)
-b, --bind <ip> Bind address (default: 0.0.0.0)
-u, --user <user:pass> Add user (enables auth). Repeatable.
--max-conn <n> Max concurrent connections (default: 1000000)
--allow-ip <ip> Allow only specific IP. Repeatable.
-d, --debug Enable verbose debug logging
--install <mode> Install as service (systemd) or to path
--uninstall <mode> Uninstall service (systemd) or from path
-V, --version Print version and exit
-h, --help Show this help message
Open proxy on the default port (1080):
./socks5Specify a custom port:
./socks5 --port 8080
# or
./socks5 8080Require username/password authentication:
./socks5 --port 1080 --user alice:s3cr3t --user bob:hunter2Adding any --user automatically enables the USERNAME/PASSWORD auth method and rejects unauthenticated clients.
Bind to a specific interface:
./socks5 --port 1080 --bind 192.168.1.10Restrict access to specific client IPs:
./socks5 --port 1080 --allow-ip 10.0.0.1 --allow-ip 10.0.0.2When any --allow-ip is set, all other source IPs are rejected immediately at accept time.
Cap concurrent connections:
./socks5 --port 1080 --max-conn 5000Enable debug logging:
./socks5 --port 1080 --debugFull example — auth + restricted bind + capped connections:
./socks5 --port 1080 --bind 0.0.0.0 \
--user deploy:p@ssw0rd \
--allow-ip 10.10.0.0 \
--max-conn 10000curl --socks5-hostname localhost:1080 https://example.comWith authentication:
curl --socks5-hostname alice:s3cr3t@localhost:1080 https://example.comInstall the binary to /usr/local/bin and write a systemd unit file:
sudo ./socks5 --install systemd
sudo systemctl enable --now socks5The generated unit runs socks5 --port 1080 --max-conn 1000000 by default. Edit /etc/systemd/system/socks5.service to customise arguments, then reload:
sudo systemctl daemon-reload
sudo systemctl restart socks5To remove the installation:
sudo ./socks5 --uninstall systemdThe server uses a single-threaded event loop backed by the native OS multiplexer:
| Platform | Backend | Trigger mode |
|---|---|---|
| macOS / FreeBSD / OpenBSD / NetBSD | kqueue |
EV_CLEAR (edge-triggered) |
| Linux | epoll |
EPOLLET (edge-triggered) |
Up to 512 events are processed per kevent / epoll_wait call. After each batch, deferred-dead connections (up to 512) are freed to prevent use-after-free bugs from same-batch cascades.
Each connection progresses through a sequence of phases:
GREETING_READ → GREETING_WRITE
↓ (no auth) ↓ (user/pass) ↓ (GSSAPI)
REQUEST_READ AUTH_READ GSSAPI_READ
AUTH_WRITE GSSAPI_WRITE
↓ ↓
REQUEST_READ ←─────────┘
↓
┌─── CONNECT ───┬─── BIND ────┬─── UDP ASSOCIATE ───┐
CONNECTING BIND_LISTEN PHASE_UDP ...
REPLY_WRITE BIND_REPLY2
└────────────────┴────────────────→ RELAY → CLOSING
All I/O is non-blocking. The handshake accumulates bytes incrementally across multiple read events — fragmented packets are handled correctly at every phase.
Once both sides are connected, relay_data() pumps data between client and remote using a single 64 KB static buffer. If a send() would block, the unsent remainder is heap-allocated into a per-connection overflow buffer and the destination fd is armed for EPOLLOUT / EVFILT_WRITE. The overflow is drained on the next writable event before more reads are attempted.
For UDP ASSOCIATE, a UDP socket is bound on an ephemeral port and registered with the event loop. Packets from the client carry a SOCKS5 UDP header encoding the target address; the relay strips the header and forwards the payload. Packets arriving from the remote are re-wrapped with a header and delivered to the client. The TCP control channel is monitored in parallel; closing it tears down the UDP relay.
The test suite is a standalone C program (tests.c) that forks the socks5 binary and exercises it over real sockets.
make check| Test | Description |
|---|---|
| Basic No Auth | Server selects NO AUTH (0x00) when no users are configured |
| Auth Success | Server selects USERNAME/PASSWORD (0x02) and accepts valid credentials |
| Auth Fail | Server closes the connection on wrong password (errno EACCES) |
| Auth Enforced | Server returns 0xFF (no acceptable methods) when client offers only NO AUTH but auth is required |
| Auth Fragmented | Handshake and RFC 1929 auth bytes delivered one-at-a-time; full reassembly verified |
| CONNECT Fragmented | SOCKS5 CONNECT request delivered byte-by-byte; server still produces a valid reply |
| Version Flag | --version exits 0 and prints a version string containing a dot |
| Observability | Log output after a session contains Session finished: with a timestamp |
| Security Max Conn | --max-conn 1 causes the second client to be rejected |
| Security Allow IP | --allow-ip 1.2.3.4 blocks loopback; --allow-ip 127.0.0.1 permits it |
| BIND Command | Full two-reply BIND flow with relay data verification |
| UDP ASSOCIATE | Full UDP relay round-trip including header encapsulation and response unwrapping |
| GSSAPI Advertised | Server selects GSSAPI (0x01) when built with GSSAPI=1 and client offers it |
GSSAPI support is disabled by default to avoid adding a mandatory dependency. Compile it in with:
# Linux (requires libgssapi_krb5)
make GSSAPI=1
# macOS (uses the system GSS.framework — no extra package needed)
make GSSAPI=1When enabled, the server accepts GSSAPI tokens via the standard two-byte-length-prefixed framing defined in RFC 1961, calls gss_accept_sec_context() in a loop until GSS_S_COMPLETE, and logs the authenticated Kerberos principal.
| Platform | Status | Notes |
|---|---|---|
| macOS | Supported | kqueue backend; GSS.framework for GSSAPI |
| Linux | Supported | epoll + accept4() + splice() zero-copy relay |
| FreeBSD / OpenBSD / NetBSD | Supported | kqueue backend |
| Windows | Supported | Winsock2; pthreads replaced with _beginthread; no kqueue/epoll fallback |
On Windows, link against ws2_32.lib (handled automatically by the Makefile when OS=Windows_NT).
socks5.c — entire proxy implementation (~2400 lines)
tests.c — C test suite (~565 lines)
Makefile — build system
index.html — project landing page
PLAN.md — feature gap analysis and roadmap
LICENSE — PolyForm Noncommercial 1.0.0
Copyright (c) 2026 Interlaced Pixel.
Licensed under the PolyForm Noncommercial License 1.0.0. Free for non-commercial use; contact the author for commercial licensing.