Table of Contents

VpnHood.Net.Proxies

Standard proxy clients and servers for .NET — SOCKS5, SOCKS4/4a, HTTP, and HTTPS, in one small cross-platform library with no native dependencies, built on async/await and Span<byte>.

This is an independent, general-purpose library: it implements the standard proxy protocols (RFC 1928, RFC 1929, HTTP CONNECT) and has no dependency on any VPN product. Use it in any .NET application to add proxy support, embed a lightweight proxy server, bridge traffic between networks, or test proxy-aware software. It is maintained by VpnHood and battle-tested there in production, but it works entirely on its own.

Feature matrix

Protocol Client Server
SOCKS5 (RFC 1928) TCP CONNECT, UDP ASSOCIATE, username/password auth (RFC 1929), remote DNS (domain addresses), IPv4 + IPv6 TCP CONNECT, UDP ASSOCIATE with source validation, username/password auth, IPv4 + IPv6
SOCKS4 / 4a TCP CONNECT, user-id, host names via SOCKS4a —
HTTP proxy CONNECT tunnels, Basic auth, optional TLS to the proxy itself CONNECT tunnels + plain GET/POST … forwarding, Basic auth
HTTPS proxy (TLS between client and proxy) HttpProxyClient with UseTls = true HttpsProxyServer with your X509Certificate2

Common properties:

  • Standalone and reusable — plain .NET, no VPN or VpnHood runtime required, and no package dependencies
  • Fully asynchronous, cancellation-aware I/O; no blocking calls
  • Handshake and connect timeouts with sensible defaults (options classes)
  • Built for memory-constrained hosts (mobile network extensions, small containers): every per-connection resource is bounded — an optional MaxConnections cap (unlimited by default), capped header sizes, bounded UDP destination tables, and a TCP half-close linger timeout so idle peers cannot retain tunnels indefinitely
  • Correct TCP half-close: a peer that shuts down its send side still receives the full response through the tunnel
  • Low-allocation hot paths (ArrayPool, pooled tunnel buffers, spans)
  • Optional Microsoft.Extensions.Logging integration — pass an ILogger, or nothing
  • Privacy-friendly: SOCKS5 client sends host names to the proxy for remote DNS resolution (no local lookup or DNS leak)
  • SOCKS5 UDP relay only accepts datagrams from the associated client and from destinations that client has contacted — third parties cannot inject packets into an association

Installation

dotnet add package VpnHood.Net.Proxies

Or add the project to your solution and reference it directly.

Quick start

Each example below runs as a test in CI (ReadmeExampleTests.cs). Their namespaces are VpnHood.Net.Proxies.Socks5ProxyServers, .Socks5ProxyClients, .HttpProxyServers, .HttpProxyClients and .Socks4ProxyClients.

Run a SOCKS5 proxy server

using var server = new Socks5ProxyServer(new Socks5ProxyServerOptions {
    ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), // 0: the system picks a free port
    Username = null, // set a user name to require RFC 1929 authentication
    Password = null
});

server.Start(); // or: await server.RunAsync(cancellationToken);
var proxyEndPoint = server.ListenerEndPoint;

Connect through a SOCKS5 proxy (TCP)

var client = new Socks5ProxyClient(new Socks5ProxyClientOptions {
    ProxyEndPoint = proxyEndPoint
});

using var tcp = new TcpClient();

// A host name goes to the proxy as a SOCKS5 domain name, so the proxy resolves it: no local DNS query.
await client.ConnectAsync(tcp, destinationHost, destinationPort, CancellationToken.None);

var stream = tcp.GetStream(); // the tunnel: speak HTTP, TLS or anything else

A single Socks5ProxyClient instance is reusable across many connections; the handshake is negotiated per connection.

Send UDP through a SOCKS5 proxy (UDP ASSOCIATE)

var client = new Socks5ProxyClient(new Socks5ProxyClientOptions {
    ProxyEndPoint = proxyEndPoint
});

// the local UDP socket that exchanges datagrams with the proxy
using var udp = new UdpClient(new IPEndPoint(IPAddress.Loopback, 0));
var localUdpEndPoint = (IPEndPoint)udp.Client.LocalEndPoint!;

// the association lasts while this TCP control connection stays open
using var controlTcp = new TcpClient();
var relayEndPoint = await client.CreateUdpAssociateAsync(controlTcp, localUdpEndPoint, CancellationToken.None);

// wrap the payload in the SOCKS5 UDP header (10 bytes for IPv4, 22 for IPv6) and send it to the relay
var buffer = new byte[22 + payload.Length];
var length = Socks5ProxyClient.WriteUdpRequest(buffer, destination, payload);
await udp.SendAsync(buffer.AsMemory(0, length), relayEndPoint);

// receive the answer and unwrap it
var result = await udp.ReceiveAsync();
var from = Socks5ProxyClient.ParseUdpResponse(result.Buffer, out var answer);

Run an HTTP proxy server

using var server = new HttpProxyServer(new HttpProxyServerOptions {
    ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), // 0: the system picks a free port
    Username = "user", // set a user name to require Basic authentication
    Password = "pass"
});

server.Start();
var proxyEndPoint = server.ListenerEndPoint;

Handles CONNECT tunnels (HTTPS traffic, IPv6 authorities like [::1]:443 included) and forwards plain absolute-URI requests (GET http://… HTTP/1.1) with the client's headers preserved, repeated ones included. A request for an https:// URI gets 501 Not Implemented: HTTPS goes through CONNECT.

Run an HTTPS proxy server (TLS between client and proxy)

using var server = new HttpsProxyServer(new HttpProxyServerOptions {
    ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0), // 0: the system picks a free port
    ServerCertificate = certificate // an X509Certificate2 with its private key
});

server.Start();

Connect through an HTTP(S) proxy

var client = new HttpProxyClient(new HttpProxyClientOptions {
    ProxyEndPoint = proxyEndPoint,
    Username = "user", // Basic authentication, sent with every request
    Password = "pass",
    UseTls = false // true for an HTTPS proxy, with ProxyHost as the name on its certificate
});

using var tcp = new TcpClient();
await client.ConnectAsync(tcp, destinationHost, destinationPort, CancellationToken.None);

// Take the tunnel from the client: with UseTls, the connection's own stream would bypass TLS.
var stream = client.GetStream(tcp);

The client accepts any 2xx CONNECT response and maps proxy errors (407, 429, 5xx, …) to a typed ProxyClientException with a meaningful SocketError code. Tunnels where the server speaks first (SMTP, SSH, …) work correctly — no bytes are lost after the handshake.

Connect through a SOCKS4/4a proxy

var client = new Socks4ProxyClient(new Socks4ProxyClientOptions {
    ProxyEndPoint = proxyEndPoint,
    UserName = null // the SOCKS4 user ID, when the proxy wants one
});

using var tcp = new TcpClient();
await client.ConnectAsync(tcp, destinationHost, destinationPort, CancellationToken.None); // a host name uses SOCKS4a

var stream = tcp.GetStream(); // the tunnel

Command-line apps

The repo ships two small CLI utilities, handy for manual testing:

  • VpnHood.Net.Proxies.ServerApp (VhProxyServer) — run an HTTP, HTTPS, or SOCKS5 proxy server, with optional auth and self-signed certificate generation.
  • VpnHood.Net.Proxies.ClientApp (VhProxyClient) — connect through any supported proxy and exchange test data.
dotnet run --project samples/VpnHood.Net.Proxies.ServerApp -- socks5 --port 1080 --username user --password pass
dotnet run --project samples/VpnHood.Net.Proxies.ClientApp -- socks5 --proxy-port 1080 --username user --password pass

See USAGE.md for all commands and options.

Error handling

Proxy-level failures throw ProxyClientException, which derives from SocketException and carries a mapped SocketError (e.g. ConnectionRefused, HostUnreachable, AccessDenied), so existing socket error handling keeps working. Authentication failures throw UnauthorizedAccessException; malformed peers throw ProtocolViolationException.

Testing

Integration tests live in tests/VpnHood.Net.Proxies.Tests and run the real clients against the real servers over loopback — including UDP relays, TLS proxies, IPv6 CONNECT, pipelined requests, and injection attempts against the UDP relay:

dotnet test

License

LGPL-2.1 © OmegaHood LLC — an independent library maintained by VpnHood.