SOCKS & HTTP
The everyday local inbounds your browser and system proxy talk to.
SOCKS5 and HTTP CONNECT are the doors between your applications and Xray — the ports your
browser, curl, or the OS system proxy point at. They carry traffic in plaintext on localhost
and offer zero resistance to censorship; their job is purely to accept connections so the
router can send them out through a real tunnel (VLESS, Trojan, …).
One local port answers both SOCKS and HTTP
The socks inbound embeds a complete HTTP proxy server and picks the protocol per
connection. NewServer constructs an http.Server alongside the SOCKS one, and Process reads exactly one byte before deciding where the connection goes:
if firstbyte[0] != 5 && firstbyte[0] != 4 { // Check if it is Socks5/4/4a
errors.LogDebug(ctx, "Not Socks request, try to parse as HTTP request")
return s.httpServer.ProcessWithFirstbyte(ctx, network, conn, dispatcher, firstbyte...)
} A SOCKS5 hello starts with 0x05, SOCKS4/4a with 0x04 — and no HTTP method starts with
either byte, so GET/CONNECT requests fall through to the HTTP handler with the consumed
byte stitched back in front (ProcessWithFirstbyte).
{
"listen": "127.0.0.1",
"port": 1080,
"protocol": "socks",
"settings": { "auth": "password", "accounts": [{ "user": "u", "pass": "p" }], "udp": true }
} The handshake accepts SOCKS4 and 4a too (handshake4), but only CONNECT — and SOCKS4 is
rejected outright when password auth is on, because the protocol has no password field.
UDP associate splits control and data
A SOCKS5 UDP session is two connections: a TCP one that does nothing and a UDP one that does
everything. After the client sends UDP ASSOCIATE, the server replies with the IP and port
to send datagrams to, then parks the TCP connection — handleUDP literally copies its bytes
into buf.DiscardBytes and waits for the client to hang up, because closing the TCP side ends
the association.
Every datagram is self-addressed: a 10-byte-ish header (RSV RSV FRAG ATYP ADDR PORT) precedes
the payload, parsed by DecodeUDPPacket — fragmented packets (FRAG != 0) are dropped, not
reassembled. Because each packet names its own destination, one association can talk to many
remote hosts. handleUDPPayload attaches that per-packet destination to the payload itself
(payload.UDP = &destination); the dispatch destination — what the session is keyed on —
depends on cone behavior: with full-cone on, it is pinned to the first packet’s target and
never changes (if !s.cone || dest == nil), and the outbound honors each payload’s own
address; with cone off, the dispatch is re-targeted with every packet.
UDP authentication is only an IP allowlist
Username/password auth cannot protect UDP, so Xray falls back to filtering by source IP. UDP datagrams arrive on a separate socket with no session state, so there is nowhere to put credentials. The source is refreshingly honest about it:
proxy/socks/udpfilter.go// In the sock implementation of * ray, UDP authentication is flawed and can be bypassed.
// ...
// We create a filter, add remote IP to the pool when it try to establish a UDP connection with auth.
// And drop UDP packets from unauthorized IP. When auth is enabled, UDPFilter.Add records the client IP at UDP ASSOCIATE time and Check drops datagrams from any other IP. There is no expiry — the comment notes a timeout was
deliberately skipped.
As outbounds, they chain you through an upstream proxy
The same two protocols work in reverse to hop through another proxy — a corporate gateway,
an SSH -D tunnel, Tor. The SOCKS outbound performs the full client handshake
(ClientHandshake), including UDP ASSOCIATE: it dials the relay address the server returns
and wraps each packet with UDPWriter, so UDP survives the hop. The HTTP outbound only ever
sends CONNECT and refuses UDP with an explicit error.
{
"protocol": "socks",
"settings": { "servers": [{ "address": "10.0.0.2", "port": 1080 }] }
} Chain through an HTTP outbound when apps need UDP
http.Client.Process returns "UDP is not supported by HTTP outbound" — DNS, QUIC and games
routed there simply fail.
Use a SOCKS outbound (or route UDP elsewhere)
The SOCKS client negotiates UDP associate and relays datagrams; keep an HTTP upstream only for TCP routes.
The HTTP outbound has two tricks of its own. It always reads your app’s first payload before
even dialing the upstream (link.Reader.ReadMultiBuffer() runs before setUpHTTPTunnel), so
the opening bytes go out the moment the tunnel exists — which also means a protocol where the server speaks first will stall here waiting for data. And if the upstream proxy speaks TLS
and negotiates h2, Xray caches the HTTP/2 connection per proxy address (cachedH2Conns) and
multiplexes future CONNECTs as streams over it, shipping that first payload inside the
CONNECT request body itself — no extra round trip.