In the earlier teardown
I pulled apart a class of SOCKS5 proxy that demands a password on TCP but relays UDP for anybody — no ASSOCIATE, no source check, no memory of who you are. Once I stopped staring at the reflection mechanics, the load-bearing detail was this: a relay that forwards a lone, unsolicited SOCKS5-wrapped datagram is stateless on the UDP path. And stateless forwarders compose. Which raises an obvious, slightly gremlin-brained follow-up — what happens if you nest the headers and point these things at each other?
So I spent a session finding out. Chaining relays end to end, looping a single relay through itself, hunting for where the loop leaks, and finally paying real money for a “proper” commercial proxy to check whether the by-the-book RFC build is any safer. Usual disclaimer up front: I publish signals, not weapons. Anything volumetric below was rebuilt and measured inside the same isolated Docker network from the first post — a /28 with no uplink, nothing leaves the bridge, not one third-party box touched. Every figure is measured and then deliberately deflated, because the punchline hasn’t moved: these relays are worse at the frightening thing than the napkin math promises, and better as detection targets.
Nesting is free
The SOCKS5 UDP request header (RFC 1928 §7) is tiny and self-describing: 2 reserved + 1 fragment + 1 address-type + 4 (IPv4) + 2 port = 10 bytes, then the payload. A relay reads exactly one header, forwards the rest to whatever address it names, and forgets the whole transaction. So to route through N relays you don’t need a protocol — you just stack N headers, innermost-last, and let the relays unwrap themselves:
sent to P1 : [hdr→P2][hdr→P3]…[hdr→P_N][hdr→target][DATA]
P1 → P2 : [hdr→P3]…[hdr→P_N][hdr→target][DATA]
…
P_N → target: [DATA]
Each hop strips its ten bytes and forwards a smaller packet. There’s nothing clever going on; the relays do all the labour, peeling their own layer like an onion. Standing several of these stateless builds up in the lab and chaining them, the forward path was almost boringly reliable — runs of 15–20 hops delivered 100% of 500 datagrams. I’d already been burned once by a hairpin artifact in the first teardown, so this time I checked the exit IP actually matched the intended last hop before believing anything. It did. The chain was honoring the path, not quietly collapsing to a gateway.
The amplification that isn’t
The intuition most people have here is wrong: “I send a 1-byte payload wrapped in 100 bytes of headers — that’s ×100 amplification!” It is exactly backwards. The headers get peeled, not multiplied. You transmit the fattest packet (101 B); the target receives the runt (1 B). At the destination it’s de-amplification — you spend ~100× more than you deliver.
The only thing that actually grows is total aggregate work across the chain, and only modestly. Sum the shrinking packets and you get ≈ 5N² bytes of relay traffic for your ≈ 10N-byte input — an amplification of N/2 — but that traffic lands on the relays, spread out, not on any victim:
| chain depth N | relay-side traffic | amplification (N/2) |
|---|---|---|
| 50 | ~12.5 KB | ×25 |
| 100 | ~50 KB | ×50 |
| 147 (MTU cap) | ~108 KB | ×73 ← ceiling |
And it’s capped. 147 ten-byte headers exactly fill one 1472-byte UDP payload, so a single un-fragmented packet tops out around ×73 no matter how you distribute the hops. What chaining buys you is source obfuscation — your traffic laundered behind a line of residential-looking IPs — not bytes on a target. That distinction matters, because the literature keeps filing UDP relays under “amplifier.” As a reflector aimed at a victim they’re bad. As a way to hide where a packet came from, they’re excellent. Two very different things.
One relay is a whole chain: the self-loop
This is where it got fun. Needing N genuinely independent relays that all chain is fragile — pools rot, hosts drop. But a stateless forwarder will cheerfully forward to its own address, so you can build arbitrary depth out of a single relay by pointing every header back at it:
[hdr→P][hdr→P][hdr→P]…[hdr→target][DATA] sent to P
P receives, peels, forwards to P, receives, peels, forwards to P… all the way down until the innermost header finally releases the payload to the target. In the isolated lab I watched a single high-port relay build self-loop 140 times at 100% delivery, no anti-loop guard anywhere in sight. Depth had decoupled completely from relay count. A single box, one packet, and it ran 140 hops deep.
Now — this is a genuine amplifier, and it’s the first honest one I’ve found in this whole saga. But read the fine print on who it hits. The self-loop is the same N/2 ≈ ×70 at N=140, and every multiplied byte is the relay flooding itself. One small packet makes the box perform 140 sequential forwards; in my run that was 500 × 140 = 70,000 self-forwards spat out from a 0.03 s burst. The victim of this amplifier is the relay and its provider’s bandwidth, full stop — not some external target. It’s a self-DoS / resource-exhaustion primitive, quadratic in depth, MTU-capped. Which is another way of saying: a relay that self-loops with no hop limit is a clean, catalogable vulnerability. Basically it announces itself.
Why “lossless” localhost still drops
Then I pointed the loop headers at 127.0.0.1 instead of the relay’s public IP — and delivery cratered to ~28%. My first reaction was the obviously-wrong one: how does localhost drop packets? It doesn’t. The loopback wire is lossless; that’s the entire point of it. What’s actually happening is the relay drowning its own UDP receive buffer. A 140-deep self-loop at 500 packets rams ~70,000 datagrams into the relay’s single listener faster than its SOCKS handler can drain them; the socket buffer overflows and the kernel throws the rest on the floor. Textbook receiver overflow — except the relay is the one inflicting it, on itself. The quadratic self-traffic bites its own buffer, and deeper loops drop harder. The 28% isn’t wire loss at all. It’s the relay’s own self-DoS overflowing its buffer. I spent a good while confused by that number before it clicked that the drop was the finding.
The loopback leak is an SSRF primitive
That 127.0.0.1 experiment buried a much bigger result under all the buffer noise: the relay forwarded UDP to its own loopback at all. Which is worth pausing on: Some of these relays will happily relay a datagram to 127.0.0.1, into RFC 1918 space, or at a cloud metadata address — reaching services that are only ever supposed to be reachable from inside the host. A stateless UDP relay with no destination egress filter is a UDP SSRF: point the innermost header at an internal resolver, an SNMP daemon, a 169.254.169.254 metadata endpoint, and the relay obligingly delivers on your behalf.
The contrast between builds is itself the signal. The :1080 pool sitting on Alibaba and Huawei Cloud tended to block loopback and internal destinations — basic SSRF hygiene — while still chaining freely out to other public relays. The high-port relays self-looped into internal space without a second thought. Two populations, two postures, and “will it forward to 169.254.169.254?” is a single-packet question that sorts one from the other.
The paid proxy fails the source check
A correct SOCKS5 proxy treats the TCP control connection as the root of trust: you authenticate, you issue UDP ASSOCIATE, and the relay you get back is pinned to your source — RFC 1928 §6 is explicit that the relay must drop datagrams that didn’t come from the associating client. Because TCP can’t be spoofed (the handshake needs return packets), a properly source-pinned relay is simply not reflectable. That pinning is supposed to be the entire defense.
So I bought one. A paid commercial residential proxy — real USER:PASS, real TCP ASSOCIATE, three distinct IPs for front, relay, and exit, the works — specifically to test whether it actually enforces §6. The probe is minimal and I’m the paying customer, so nobody else is involved: open the association over TCP, then inject a single datagram into the relay endpoint from a different source (an open relay’s exit IP), and watch a STUN oracle for a reply.
associate: OK -> relay endpoint <proxy>:5114
inject via open relay (foreign source IP) -> proxy relay port
reply: 2 SOCKS5 headers peeled, STUN reflexive = proxy egress
RESULT: the proxy accepted UDP from a non-client source — §6 NOT enforced
It reflected. The paid, authenticated, association-based proxy honored a datagram that never came from the client who paid for the association. The TCP gate authorizes setup — and then, the moment the relay port is open, this build stops caring who fills it. That collapses the one structural reason an ASSOCIATE proxy was ever supposed to be safe. And it means reflectability isn’t a property of “open vs. paid” at all. It’s a property of whether this specific build re-validates source on every datagram. Most, it turns out, don’t.
What all of this is actually for
None of it is a flood. The honest summary:
- Chaining launders origin; it de-amplifies at the target.
- Self-looping amplifies
N/2, capped ~×73, aimed squarely at the relay itself. - Loopback forwarding is an SSRF reach into the host’s internal services.
- Missing §6 source-pinning turns even a paid proxy into a reflector.
Every one of those is weak as an attack and sharp as a fingerprint. That’s the whole value for me: each behavior is a single-packet, deterministic probe that scores a node without abusing anyone.
| Probe | Question | Signal |
|---|---|---|
| Self-reference forward | Forwards to its own IP? loop limit? | self-DoS-able |
| Loopback / RFC1918 dst | Forwards to 127.0.0.1/internal? | UDP SSRF |
| Foreign-source inject | Accepts UDP from a non-client source? | reflectable (§6 fail) |
| MTU sweep | Where does egress drop? | DF/PMTUD fingerprint |
| Self-loop depth | How deep before buffer overflow? | capacity / build fingerprint |
A relay’s vulnerabilities are its identity. The box that self-loops to 140, forwards to its own loopback, and skips the source check isn’t just exploitable, it’s easy to tell apart from a well-built proxy next to it. And that’s what lets you separate a residential-proxy exit from an actual residential user.
For operators
If you run a SOCKS5 UDP relay and want off this list, the fixes are unglamorous and they all point the same way: re-validate the datagram source against the association on every packet (RFC 1928 §6 — not just once at setup); drop loopback, RFC 1918, link-local and metadata destinations at the relay; refuse to forward to your own ingress address, and cap forward depth; and don’t bind the UDP relay somewhere an unauthenticated datagram can reach it without a live, source-matched association. The reflection, the SSRF, and the self-loop all close the instant the relay stops trusting a bare datagram.
That’s the through-line across both posts. I sell signals, not verdicts — and a proxy that flunks these probes is about the loudest signal there is.