Summary
- SOCKS5 created a UDP relay association through an authenticated TCP conversation and destroyed it when that TCP connection ended; the stream supplied state lifetime, not transport for the UDP payloads.
- The returned relay address, the client's source IP, each datagram's destination envelope and any authentication or fragment state were separate claims. None alone proved a user, a final recipient or successful delivery.
Connectionless traffic acquired a bounded custodian
Imagine a client that has been sending name lookups or real-time media through a SOCKS relay. The UDP path is busy. Then the separate TCP socket closes: perhaps the client process exits, a firewall loses state, a mobile path changes or the proxy restarts. A new datagram still reaches the relay's UDP port a moment later. Its header is valid. Its destination is clear. Why not forward it?
RFC 1928, published in March 1996, answered by refusing to treat recent traffic as authority. The UDP association terminates when the TCP connection carrying the UDP ASSOCIATE request terminates. The relay may still possess bytes and a routing table. It no longer possesses the live association that gave those bytes a place in its policy.
That rule did not turn UDP into a stream. The application data remained in separate datagrams. Loss, duplication and reordering remained possible. TCP carried method negotiation and the request that created a bounded relay context. Its ending provided cleanup evidence for state that UDP could not close on its own.
This was an economical division of work. A connectionless service gained an explicit beginning and ending without inventing a UDP close handshake or letting idle heuristics become the only source of truth.
The association began before the first relayed datagram
SOCKS5 first asked the client and server to choose an authentication method on a TCP connection. Only after the method-dependent exchange could the client issue one of three commands: CONNECT, BIND or UDP ASSOCIATE.
The UDP request included the address and port from which the client expected to send datagrams. A server could use that statement to limit the association. A client that did not yet know its eventual UDP endpoint had to send an all-zero address and port. Zero did not mean “every source is trusted.” It meant that the request lacked a specific value and that the server had to derive or constrain the usable source by its own implementation and policy.
On success, the server returned BND.ADDR and BND.PORT. For UDP ASSOCIATE, these fields told the client where to send relay requests. A multihomed server could return an address unlike the one used for TCP. The binding was therefore an ingress instruction supplied by the relay, not the identity of the ultimate target.
Three addresses could now coexist without contradiction: the TCP address used to reach the SOCKS server, the bound UDP relay endpoint returned by that server, and the remote destination carried inside each datagram. Compressing them into one “proxy address” destroys the very evidence the protocol made observable.
Each datagram retained its own destination
The client did not establish one permanent UDP destination when it opened the association. Every datagram sent to the relay carried a small SOCKS header: two reserved octets, a FRAG field, an address type, destination address, destination port and payload.
That envelope allowed one association to relay messages toward different destinations. It also made the relay an active interpreter rather than an invisible packet pipe. The relay removed the SOCKS header, sent the payload onward, and wrapped replies in the same grammar so the client could see which remote address and port had answered.
The grammar gave the relay bounded authority. It could evaluate destination and policy per datagram. It could report the origin of a returned datagram. It could not turn acceptance at its own UDP port into proof that the remote host received the packet, that an application processed it or that a reply belonged to a human actor.
Nor did BND.ADDR become a portable capability. Once the control connection ended, sending another syntactically correct envelope to the old port did not recreate the association. A remembered address was not remembered permission.
Source binding was not user identity
RFC 1928 required the UDP relay to learn the expected client IP address from the SOCKS server and silently drop datagrams arriving from another source IP for that association. This closed an obvious path by which an unrelated host might inject traffic into an established relay context.
The test remained limited. A source IP can be shared by processes, users or machines behind translation. It can change during mobility. It says where a datagram appeared at the relay, not which person caused it, whether the same application opened the TCP connection or whether the destination permits the traffic.
Authentication belonged to the earlier method negotiation. Even there, “SOCKS5 authenticated” was not one universal fact. RFC 1929 defined a username/password method whose credentials travel in cleartext within that subnegotiation and warned against using it where sniffing is possible. RFC 1961 defined a GSS-API method that could provide authentication, message integrity and optional confidentiality through negotiated protection levels.
The base protocol made room for those properties; it did not manufacture them. An operator needed to retain which method was selected, which protection it actually supplied and whether that protection covered subsequent relay messages. A version number alone was never a security receipt.
Fragmentation was optional state inside the relay grammar
The FRAG octet is easy to misread as an IP fragmentation field. It belonged to the SOCKS UDP envelope. Zero meant the datagram was standalone. Values from one through 127 described positions in a SOCKS fragment sequence, while the high bit marked the end.
Supporting this feature was optional. An implementation without reassembly had to discard every datagram whose FRAG was nonzero. A supporting receiver kept a reassembly queue and timer. Expiry abandoned the queued fragments. A new fragment numbered below the highest value already processed also reset the queue. The timer could not be shorter than five seconds, and the specification recommended that applications avoid fragmentation where possible.
This was not reliable transport in miniature. There was no acknowledgment grammar here, no retransmission promise and no license to preserve an incomplete sequence indefinitely. The state was local, finite and disposable. Its job was reconstruction, not delivery certification.
The distinction matters in diagnosis. A missing application message might have been lost before the relay, rejected for source mismatch, dropped because fragmentation was unsupported, abandoned after timer expiry, sent toward the destination and lost later, or received without producing a reply. “The proxy dropped UDP” is not a useful finding until the decision point is known.
The TCP ending prevented orphan authority
Why make the association die with TCP instead of keeping it alive until an idle timer expires? An idle timer can reclaim resources, but silence has several causes. A quiet application, a broken return path and a dead client look identical at the relay. Recent UDP traffic proves activity, yet it does not prove that the authenticated principal or process that requested the association still controls it.
The control connection supplied a stronger, if still bounded, lifecycle fact. While it survived, the SOCKS server could associate method state, request policy and the UDP relay entry with one live conversation. When it ended, the protocol chose revocation over guesswork. A client wanting more service had to establish a fresh context.
That choice imposed costs. A transient TCP failure could interrupt otherwise healthy UDP traffic. Mobile address change could make both the control path and source-IP binding stale. Keeping a stream open consumed resources. But these costs were visible and testable. The alternative—an orphan relay that remained usable because packets continued to arrive—would have made authority depend on traffic that the relay itself could not attribute confidently.
Later gateways inherited an application-layer boundary
RFC 3089 described an Informational SOCKS-based IPv6/IPv4 gateway in 2001. It relayed between terminated IPv4 and IPv6 connections at the application layer and inherited the native SOCKS command set, including UDP ASSOCIATE.
That later use illustrates both reach and restraint. SOCKS could bridge address families without pretending to be an IP router. Domain-name resolution could be delegated to a dual-stack gateway when an old application could not store an IPv6 address. Yet the gateway still acted through application-layer relay state. It did not erase the distinction among name, address, control association and forwarded data.
The historical achievement was not a universal tunnel. It was a small common grammar that let endpoints ask a relay to perform bounded work while leaving deployment policy local.
Sources
Member Briefing
Deeper Profile Context
Sign in with the right membership level to unlock the full briefing and source notes.
Only for Strategic Circle
Strategic Circle
Open to all readers. Unlock profile briefings after joining and signing in.
Join Strategic CircleOnly for Leadership Alliance
Leadership Alliance
For qualified IP-asset owners and management; sign in to unlock alliance briefings.
Join Leadership Alliance
