Skip to content

Protocols

One set of credentials works across every protocol. There are no separate accounts and no per-protocol pricing, and targeting behaves identically on all of them.

Protocol Port Notes
HTTP 8000
HTTPS 8000 via CONNECT
SOCKS5 1080 including CONNECT to any port
SOCKS5 UDP 1080 via UDP ASSOCIATE
HTTP/3 (QUIC) 1080 over SOCKS5 UDP ASSOCIATE
STUN 1080 over SOCKS5 UDP ASSOCIATE
Terminal window
curl -x USERNAME:[email protected]:8000 https://api.ipify.org

Both SOCKS5 modes work. They differ in who resolves the hostname.

Terminal window
curl --socks5-hostname USERNAME:[email protected]:1080 \
https://api.ipify.org

We resolve the hostname at the exit. Two things follow, and both usually matter:

  • Your local resolver never sees what you are visiting.
  • You get the DNS answer a user in that location would get. Many large sites return different addresses per region, so resolving locally can send you to the wrong edge and undo your country targeting.

In a proxy URL these are the socks5h:// and socks5:// schemes:

Terminal window
curl -x socks5h://USERNAME:[email protected]:1080 https://api.ipify.org

Most libraries follow the same naming. In Python requests, socks5h:// sends the hostname and socks5:// does not, which is the same distinction under a different spelling.

SOCKS5 also gives you CONNECT to arbitrary ports, not just 80 and 443. Useful for anything that is not a web server.

SOCKS5 UDP, HTTP/3 and STUN all run over SOCKS5 UDP ASSOCIATE (RFC 1928 command 0x03). curl cannot drive any of them, so these need real code.

They run on the same host and port as SOCKS5, with the same credentials.

Keep the control connection open. RFC 1928 scopes the association to the TCP control socket. Close it and the relay is torn down. This is the single most common SOCKS5 UDP mistake.

The relay address is not the proxy address. The associate reply names a worker that will carry your datagrams, and you send UDP straight to it. Your egress firewall has to allow outbound UDP to arbitrary high ports.

import socket, struct
def socks5_udp_associate(proxy_host, proxy_port, user, password, timeout=30):
"""Returns (ctrl_socket, (relay_ip, relay_port)).
KEEP ctrl OPEN for the life of the association. Closing it tears the
relay down.
"""
def rd(s, n):
b = b""
while len(b) < n:
p = s.recv(n - len(b))
if not p: raise IOError("proxy closed the control connection")
b += p
return b
ctrl = socket.create_connection((proxy_host, proxy_port), timeout=timeout)
ctrl.settimeout(timeout)
ctrl.sendall(b"\x05\x01\x02") # offer user/password
if rd(ctrl, 2)[1] != 0x02: raise IOError("no user/password auth offered")
u, p = user.encode(), password.encode()
ctrl.sendall(bytes([0x01, len(u)]) + u + bytes([len(p)]) + p)
if rd(ctrl, 2)[1] != 0x00: raise IOError("credentials rejected")
ctrl.sendall(b"\x05\x03\x00\x01\x00\x00\x00\x00\x00\x00") # UDP ASSOCIATE
r = rd(ctrl, 4)
if r[1] != 0x00: raise IOError(f"UDP ASSOCIATE refused, rep=0x{r[1]:02x}")
atyp = r[3]
if atyp == 0x01: relay_ip = socket.inet_ntoa(rd(ctrl, 4))
elif atyp == 0x04: relay_ip = socket.inet_ntop(socket.AF_INET6, rd(ctrl, 16))
else: relay_ip = rd(ctrl, rd(ctrl, 1)[0]).decode()
relay_port = struct.unpack("!H", rd(ctrl, 2))[0]
if relay_ip in ("0.0.0.0", "::"): relay_ip = proxy_host
return ctrl, (relay_ip, relay_port)
def udp_wrap(host, port, payload):
"""SOCKS5 UDP request header + payload. The header names the FINAL target."""
try:
addr = b"\x01" + socket.inet_aton(host)
except OSError:
h = host.encode(); addr = b"\x03" + bytes([len(h)]) + h
return b"\x00\x00\x00" + addr + struct.pack("!H", port) + payload
def udp_unwrap(datagram):
"""Strip the SOCKS5 UDP header from a reply."""
atyp = datagram[3]
off = 4 + (4 if atyp == 1 else 16 if atyp == 4 else 1 + datagram[4]) + 2
return datagram[off:]
import socket
ctrl, relay = socks5_udp_associate(
"us-east.gw.rayobyte.com", 1080, "USERNAME", "PASSWORD-country-US"
)
try:
target = socket.gethostbyname("example.com")
u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
u.settimeout(5)
u.sendto(udp_wrap(target, 20443, b"probe"), relay)
print(udp_unwrap(u.recvfrom(65535)[0]).decode().strip())
finally:
ctrl.close()

A standard RFC 5389 binding request, wrapped the same way. Point it at any STUN server on UDP 19302 and read XOR-MAPPED-ADDRESS (0x0020) out of the reply. Remember STUN attributes are padded to four bytes when you walk them.

Install aioquic, then drive a QuicConnection yourself, wrapping every outbound QUIC packet with udp_wrap and unwrapping every inbound one.

See Errors & retries for the SOCKS5 reply codes and what to do about each.

Was this page helpful?