Blog

Test SOCKS5 UDP: A Python Check You Can Run Yourself

Test SOCKS5 UDP with a short Python script: log in, send UDP ASSOCIATE, relay a DNS query and read the result. Standard library only.

8 min read

To test SOCKS5 UDP, you have to speak the protocol yourself: log in, send a UDP ASSOCIATE request, then send a UDP packet to the relay address you get back. If an answer comes back, UDP works. If the proxy refuses or stays silent, it doesn't.

Tools like curl can't do this. They test TCP only. Below is a short Python 3 script that does the whole check with the standard library, and a guide to reading what it prints.


Key takeaways

  • UDP through SOCKS5 needs a TCP control connection plus a UDP relay. Closing the TCP connection ends the relay.
  • The script sends a DNS query for example.com through the relay. An answer means UDP works.
  • Reply code 0x07 means the proxy does not support UDP ASSOCIATE.
  • "Accepted but no answer" means the relay opened but your packets are not being forwarded.
  • Run the test right after you buy, so you know early whether the proxy fits the job.

Why curl can't test UDP

curl talks to a SOCKS5 proxy with the CONNECT command, which opens a TCP tunnel. That is all it does with SOCKS5. It never sends UDP ASSOCIATE, so a curl success says nothing about UDP. A proxy can pass every curl test and still drop all UDP.

Even curl's HTTP/3 option won't help. HTTP/3 runs on UDP, and curl says it does not support HTTP/3 over a SOCKS proxy. More on that in QUIC and HTTP/3 over SOCKS5.


How SOCKS5 UDP works

The steps come from RFC 1928 (SOCKS5) and RFC 1929 (username and password login).

  1. Open a TCP connection to the proxy and log in with the username and password.
  2. Send the UDP ASSOCIATE command on that connection.
  3. The proxy replies with a code and a relay address: BND.ADDR and BND.PORT.
  4. Send each UDP packet to that relay address. Each packet starts with a small SOCKS header that names the real destination.
  5. The proxy forwards the data and sends the answer back with the same kind of header.

The rule that trips people up: the association ends when the TCP connection that asked for it closes. So the TCP connection must stay open the whole time you test.

If you want the background first, read does SOCKS5 support UDP.


The test script

It logs in, asks for a UDP relay, wraps a DNS query for example.com (type A) in the SOCKS UDP header, and sends it to 1.1.1.1 port 53 through the relay. Save it as udp_test.py.

import ipaddress, socket, struct, sys

if len(sys.argv) != 5:
    sys.exit("usage: python3 udp_test.py HOST PORT USER PASS")
host, port = sys.argv[1], int(sys.argv[2])
user, pw = sys.argv[3].encode(), sys.argv[4].encode()
target = ("1.1.1.1", 53)

def recv_exact(s, n):
    buf = b""
    while len(buf) < n:
        chunk = s.recv(n - len(buf))
        if not chunk:
            sys.exit("control connection closed")
        buf += chunk
    return buf

def dns_query(name):
    q = struct.pack(">HHHHHH", 0x1234, 0x0100, 1, 0, 0, 0)
    for label in name.split("."):
        q += bytes([len(label)]) + label.encode()
    return q + b"\x00" + struct.pack(">HH", 1, 1)  # type A, class IN

tcp = socket.create_connection((host, port), timeout=10)

# 1. Greeting: offer username/password (0x02)
tcp.sendall(b"\x05\x01\x02")
if recv_exact(tcp, 2) != b"\x05\x02":
    sys.exit("proxy did not accept username/password auth")

# 2. RFC 1929 auth
tcp.sendall(b"\x01" + bytes([len(user)]) + user + bytes([len(pw)]) + pw)
if recv_exact(tcp, 2)[1] != 0:
    sys.exit("login rejected")

# 3. UDP ASSOCIATE, client address unknown -> zeros
tcp.sendall(b"\x05\x03\x00\x01" + b"\x00" * 4 + b"\x00\x00")
ver, rep, _, atyp = recv_exact(tcp, 4)
if rep != 0:
    sys.exit(f"UDP ASSOCIATE refused, reply code 0x{rep:02x}"
             + (" (command not supported)" if rep == 7 else ""))
if atyp == 1:
    bnd = socket.inet_ntoa(recv_exact(tcp, 4))
elif atyp == 4:
    bnd = socket.inet_ntop(socket.AF_INET6, recv_exact(tcp, 16))
else:
    bnd = recv_exact(tcp, recv_exact(tcp, 1)[0]).decode()
bport = struct.unpack(">H", recv_exact(tcp, 2))[0]
print(f"relay says: {bnd}:{bport}")

# 4. Private relay address case: use the proxy host instead
ip = None
try:
    ip = ipaddress.ip_address(bnd)
except ValueError:
    pass
if ip is None or ip.is_unspecified or ip.is_private or ip.is_loopback:
    print(f"relay address not usable, sending to proxy host {host}")
    bnd = host
udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp.settimeout(5)

# 5. SOCKS UDP header + DNS query. Keep `tcp` open: closing it ends the association.
header = b"\x00\x00\x00\x01" + socket.inet_aton(target[0]) + struct.pack(">H", target[1])
udp.sendto(header + dns_query("example.com"), (bnd, bport))
try:
    data, _ = udp.recvfrom(2048)
except socket.timeout:
    sys.exit("ACCEPTED BUT DROPPED: relay opened, no answer in 5 s")
payload = data[10:]  # IPv4 reply header is 10 bytes
if len(payload) >= 12 and payload[:2] == b"\x12\x34" and struct.unpack(">H", payload[6:8])[0] > 0:
    print("UDP WORKS: got a DNS answer through the relay")
else:
    print("got a reply, but not a DNS answer:", payload[:16].hex())
tcp.close()

Run it with the four values from your dashboard, which gives them as HOST:PORT:USER:PASS:

python3 udp_test.py HOST PORT USER PASS

The script is IPv4 only. It was checked against a small fake SOCKS5 server on 127.0.0.1, not against a live proxy, so treat it as a starting point. Do the first run on a proxy you can afford to test.


The private relay address

In step 3, the proxy tells you where to send UDP. Often that address is 0.0.0.0, or a private one like 10.x.x.x or 192.168.x.x. The server is saying "I listen on all my addresses", or it only knows its own internal address. You can't reach a private address from the internet.

The usual fix is to send the UDP packet to the same host you used for the TCP connection, with the port from BND.PORT. The script does this on its own and prints a line when it does.

If the proxy works with that fix, UDP is fine. It is just a server that doesn't know its public address. Some clients don't do this swap, and then UDP seems broken even though the server is fine.


Reading the result

  • **UDP WORKS:** a DNS answer came back through the relay. The proxy forwards UDP.
  • **UDP ASSOCIATE refused, reply code 0x07:** "command not supported" in RFC 1928. This proxy does not offer UDP. See UDP ASSOCIATE command not supported.
  • **ACCEPTED BUT DROPPED:** the proxy said yes and opened a relay, but nothing came back in 5 seconds. The server may not forward UDP in practice, its upstream may drop it, or a firewall may block the relay port.
  • **login rejected:** wrong username or password. Check the values; this is not a UDP problem.
  • Other reply codes: 0x01 general failure, 0x02 not allowed by ruleset. Both come from the server's side.

One timeout isn't proof. Run it twice before you decide, since UDP packets can be lost.


A second check: STUN

DNS tells you UDP passes. It doesn't tell you which IP the UDP traffic leaves from. A STUN binding request does.

STUN (RFC 5389) is a small protocol on UDP. You send a 20-byte binding request to a public STUN server. It answers with the public IP and port it saw, in an XOR-MAPPED-ADDRESS field. Send that request through the same UDP relay, using the same SOCKS header as the DNS query, with the STUN server as the destination.

Then compare two IPs:

  • The IP STUN reports for your UDP traffic.
  • The IP an ordinary TCP check reports, such as the one in check your IP before use.

Usually they match. If they differ, UDP leaves from a different address than TCP. That matters for WebRTC, calls and games, where a site may see both. It is the same kind of mismatch described in the WebRTC leak test.


Common errors

  • Closing the TCP connection before the UDP test. The relay stops with it.
  • Sending UDP to 0.0.0.0 or a private address from the reply.
  • Testing UDP with curl and calling the result a pass.
  • Testing from a network that blocks outbound UDP. Try from another network if every proxy fails.
  • Running one test, getting a timeout and giving up. Try twice.
  • Leaving the SOCKS header out. The relay expects every packet to have it.

Which proxies to test

Not every Elusive proxy has UDP. Some support it through SOCKS5 UDP ASSOCIATE, and the catalog marks them and lets you filter for them. On Static ISP, UDP is offered in the US, Canada, Germany, the UK and the Netherlands. See the catalog.

Run this script right after you buy, not a day later. A Sticky proxy can be refunded to the balance only in its first hour, and only if it is offline or its IP or location does not match the listing. Static ISP and Rotating have no refunds. If UDP fails on a proxy marked for UDP, ask support on Telegram. They are real people.


FAQ

How do I test if a SOCKS5 proxy supports UDP?

Log in, send UDP ASSOCIATE, then send a UDP packet to the relay address and wait for an answer. The script above does it with a DNS query. Keep the TCP connection open while you test.

Can I test SOCKS5 UDP with curl?

No. curl uses only the CONNECT command, which is TCP. It never asks for a UDP relay.

What does reply code 0x07 mean?

"Command not supported". The proxy does not accept UDP ASSOCIATE. You need a proxy that offers UDP.

Why does the proxy accept UDP ASSOCIATE but I get no answer?

The relay opened, but packets are not forwarded. Check that you used the proxy host when the relay address was private, that you kept the TCP connection open, and that your own network allows UDP. If all of that is fine, the proxy does not really carry UDP.

Does a working UDP test mean HTTP/3 will work?

It means the proxy can carry QUIC packets. Your client also has to speak UDP ASSOCIATE. See HTTP/3 over SOCKS5.

Get a proxy for your profiles

SOCKS5 with a username and password, ready to paste as HOST:PORT:USER:PASS. Sticky IPs for up to 24 hours, Static ISP for 7 to 90 days, or Rotating by the GB.