Contents

VLESS

A stateless, encryption-free proxy protocol — TLS does the crypto, VLESS just routes.

VLESS answers one question per connection — which user wants to reach which address — and then gets out of the way. There is no cipher negotiation, no timestamp dance, no per-packet MAC: the protocol assumes it lives inside TLS or REALITY, so encrypting again would only burn CPU. What’s left is a header small enough to read in one sitting.

The whole protocol fits in one header

Everything VLESS ever says is said in the first few dozen bytes — after EncodeRequestHeader returns, a TCP connection is a dumb pipe (only UDP and Vision add framing, covered below). For a TCP request to example.com:443 the client sends exactly this:

proxy/vless/encoding/encoding.go
00                                       version, always 0
9a d3 …                    (16 bytes)    user UUID, raw — no hash, no timestamp
00                                       addons length (0 unless flow is xtls-rprx-vision)
01                                       command: 01 TCP · 02 UDP · 03 Mux · 04 reverse
01 bb                                    port 443 — the port comes BEFORE the address
02 0b 65 78 61 6d 70 6c 65 2e 63 6f 6d   type 02 = domain, "example.com"

The address parser is built with protocol.PortThenAddress(), so unlike SOCKS the two bytes of port precede the address. Address types are 01 IPv4, 02 domain (length-prefixed), 03 IPv6. For Mux and reverse commands the address is omitted entirely. The server’s reply header is even smaller: it echoes the version byte plus one addons-length byte — two bytes, then raw payload forever.

proxy/vless/outbound/outbound.go

Authentication is a map lookup, not cryptography

The UUID is compared as sixteen raw bytesDecodeRequestHeader reads them off the wire and calls validator.Get(id); if the map returns a user, you’re in. There is no proof of possession, no nonce, no replay window like VMess maintains — anyone who has ever seen those bytes can authenticate forever:

proxy/vless/encoding/encoding.go proxy/vless/account.go
if request.User = validator.Get(id); request.User == nil {
	u := uuid.UUID(id)
	return nil, nil, nil, isfb, errors.New("invalid request user id: " + u.String())
}

This is the trade that makes VLESS lighter than VMess: no AEAD per chunk, no time-skewed auth IDs to compute and verify — just a lookup. It is only safe because the outer layer already provides confidentiality, integrity, and replay protection. Strip that layer and every UUID and destination crosses the wire in cleartext.

Don't

Run VLESS over bare TCP

Nothing in the code stops you — and nothing protects you. The UUID (your only credential) and every destination you visit are readable by any on-path observer, and can be replayed to your server verbatim.

Do

Pair it with TLS 1.3 or REALITY

security: "reality" or "tls" on the stream settings gives the protocol the crypto it deliberately doesn’t carry. With flow: "xtls-rprx-vision" the inbound actively rejects outer TLS below 1.3.

flow is one word that swaps the entire data plane

The addons block exists for a single value: xtls-rprx-vision. EncodeHeaderAddons writes a length-prefixed protobuf only when the flow is vless.XRV; for every other account it writes a lone 0x00. When Vision is on, the body writer is replaced by proxy.NewVisionWriter and the inbound uses unsafe.Pointer reflection to grab the private input/rawInput buffers of the Go TLS connection — the hooks that later let inner TLS records be spliced to the socket without re-encryption. That mechanism has its own page; what matters here is that VLESS the protocol only carries the flag, one protobuf field in the header.

proxy/vless/encoding/addons.go proxy/vless/inbound/inbound.go

Vision also polices what it can’t protect: the outbound rejects UDP/443 (QUIC would be TLS-in-TLS with visible characteristics) unless you opt in with xtls-rprx-vision-udp443, and a server whose account declares Vision refuses plain-flow TCP clients outright.

A wrong UUID never gets an error — it gets a web server

With fallbacks configured, failed decoding doesn’t close the connection; it replays it. The inbound buffers the first read, and if the header doesn’t validate (or is shorter than 18 bytes), it picks a fallback by SNI, ALPN, and sniffed HTTP path, dials it, and forwards the buffered bytes untouched — optionally with a PROXY-protocol prefix. An active prober speaking anything but perfect VLESS sees whatever nginx you parked on the fallback port.

proxy/vless/inbound/inbound.go
Valid headers are dispatched; everything else is replayed byte-for-byte to the fallback
{
	"protocol": "vless",
	"settings": {
		"clients": [{ "id": "9ad3f9f7-33aa-45c1-b431-3c73dba872bd", "flow": "xtls-rprx-vision" }],
		"decryption": "none",
		"fallbacks": [{ "dest": 8080 }]
	},
	"streamSettings": { "security": "reality" }
}

One app connection is one tunnel connection

VLESS has no streams of its own — every connection your browser opens becomes its own transport connection carrying its own VLESS header, its own TLS handshake underneath. Multiplexing exists, but as a layer on top: command 0x03 marks the connection as carrying Mux.Cool frames, and UDP quietly rides that path too — the outbound rewrites UDP requests to command Mux, destination v1.mux.cool:666 (XUDP): always under Vision flow, and in cone mode for every port except 53 and 443. Plain UDP bodies instead become 2-byte-length-prefixed packets via MultiLengthPacketWriter. If per-connection handshake cost hurts you, that’s the page to read next.

proxy/vless/outbound/outbound.go proxy/vless/encoding/addons.go