Skip to main content
Back to Blog
Proxies & Anonymity17 min readJul 25, 2025

How to Test UDP over SOCKS

Omar Rifai
Omar Rifai

Jul 25, 2025

Proxies & Anonymity
How to Test UDP over SOCKS

Testing UDP can be tricky and confusing: few apps support it, SOCKS5 requires a separate UDP ASSOCIATE handshake to relay datagrams, and browsers neither expose raw UDP sockets nor forward UDP‐based transports like QUIC (HTTP/3) through proxies.

For this reason, we’ve put together a few different ways to test whether your SOCKS5 server is proxying UDP.

When UDP Is Used in Practice

These are some of the real-world environments in which UDP is used:

  • DNS Queries: These work over UDP and uses port 53, can be tested using dig @8.8.8.8 google.com
  • Network Time Protocol: NTP uses UDP and works on port 123, can be tested using ntpd -q time.google.com
  • Voice-over-IP Apps: Slack, Teams, Skype, Zoom, Discord, and more all use UDP to facilitate VoIP
  • Online games and live streaming apps
  • This isn’t an exhaustive list, but these are some key examples we’ll leverage to run our tests, alongside a custom script.

    Setting up the Tunnel

    In order to test whether our SOCKS proxy successfully proxies our UDP traffic, we first need to install tun2socks to create a virtual interface that will forward all our traffic (UDP and TCP) over a SOCKS5 proxy.

    You can download the binary from Github.

    Setting up the Tunnel on Linux

    First, let’s create TUN interface tun0 and assign an IP address for it:

    ip tuntap add mode tun dev tun0
    ip addr add 198.18.0.1/15 dev tun0
    ip link set dev tun0 up

    Next, configure the default route table with different metrics. Let’s say the primary interface is eth0 and gateway is 192.168.0.1.

    Important Note: Make sure you’re connected to the server via LAN and make sure the values are correct, otherwise you will lose access to the server.

    ip route del default
    ip route add default via 198.18.0.1 dev tun0 metric 1
    ip r add default via 192.168.0.1 dev eth0  metric 10

    Now all traffic will go through the new TUN interface then to the proxy.

    Once you’re done testing:

  • Remove the new tun0 routes
  • Add the default eth0 route again (tun2socks might have deleted it)
  • Setting up the Tunnel on MacOS

    For macOS, we need to start tun2socks first so that it will create a TUN interface for us.

    tun2socks -device utun123 -proxy socks5://username:password@IP:PORT -interface en0

    Your Internet interface name might be different than en0. Use ifconfig to find the correct name.

    Next, let’s use ifconfig to bring the TUN interface up and assign addresses for it.

    sudo ifconfig utun123 198.18.0.1 198.18.0.1 up

    Add these specific routes so that tun2socks can handle primary connections.

    sudo route add -net 1.0.0.0/8 198.18.0.1
    sudo route add -net 2.0.0.0/7 198.18.0.1
    sudo route add -net 4.0.0.0/6 198.18.0.1
    sudo route add -net 8.0.0.0/5 198.18.0.1
    sudo route add -net 16.0.0.0/4 198.18.0.1
    sudo route add -net 32.0.0.0/3 198.18.0.1
    sudo route add -net 64.0.0.0/2 198.18.0.1
    sudo route add -net 128.0.0.0/1 198.18.0.1
    sudo route add -net 198.18.0.0/15 198.18.0.1

    Now all traffic will go through the new TUN interface then to the proxy.

    Once you’re done testing, just exit the command and it will automatically delete the new routes.

    Testing UDP Traffic over SOCKS5

    Test UDP with DNS Query

    Use the DNS lookup utility to send a query to the DNS server at 8.8.8.8 (which is Google’s public resolver) instead of your system’s default one to ping Google’s homepage.

    Command: dig @8.8.8.8 google.com. It’s the same command on Linux and MacOS

    Success: Returned the IP behind google.com using a proxy that supports UDP.

    Failure: Timeout because the proxy does not support UDP.

    Test UDP with NTP

    Given that NTP traffic is entirely over UDP, sending ntpd -q time.google.com through your SOCKS5 proxy is another way to test whether it supports UDP.

    Command: sudo ntpd -q time.google.com (Linux only). This might require installing ntp.

    Success: It was able to send/receive and resolve Google’s time server.

    Failure: It was not able to resolve over the proxy.

    Test UDP with Discord VoIP

    VoIP uses UDP to carry its real-time audio and video. By joining a Discord call or voice channel and successfully being able to hear others and have others hear you, it confirms that UDP is being carried over your SOCKS proxy.

  • Download the Discord desktop client
  • Connect to any voice channel
  • Success: Displays “Voice connected” and others in the channel are able to hear you.

    Failure: You can’t hear anyone in the voice channel and your connection shows “No Route”.

    Test UDP over SOCKS with Custom UDP Client Script

    Testing UDP over SOCKS for dozens — if not hundreds — of proxies can be very time consuming. Instead of doing it all manually, we’ve created a custom script that tests UDP traffic via Google DNS query for you. We’ve included a link so you can download and test your own IPs for yourself.

    For the script to work you need to:

  • Have python3 installed
  • Create a proxy.txt file that lists your proxies in the following format: username:password@host:port or host:port if it has no authentication
  • Run the script via python3 test_udp.py
  • import base64
    import socket
    import struct
    import sys
    import time
    from typing import Optional, Tuple
    
    
    class SOCKS5UDPTester:
        def __init__(
            self,
            proxy_host: str,
            proxy_port: int,
            username: str = None,
            password: str = None,
        ):
            self.proxy_host = proxy_host
            self.proxy_port = proxy_port
            self.username = username
            self.password = password
            self.sock = None
    
        def connect_and_authenticate(self) -> bool:
            """Establish connection and authenticate with SOCKS5 proxy"""
            try:
                self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.sock.settimeout(10)
                self.sock.connect((self.proxy_host, self.proxy_port))
    
                # SOCKS5 greeting
                if self.username and self.password:
                    # Request username/password authentication
                    greeting = b"\x05\x02\x00\x02"  # SOCKS5, 2 methods, no auth + username/password
                else:
                    # Request no authentication
                    greeting = b"\x05\x01\x00"  # SOCKS5, 1 method, no auth
    
                self.sock.send(greeting)
                response = self.sock.recv(2)
    
                if len(response) != 2 or response[0] != 0x05:
                    print("❌ Invalid SOCKS5 greeting response")
                    return False
    
                auth_method = response[1]
    
                if auth_method == 0x00:  # No authentication
                    print("✅ Connected with no authentication")
                    return True
                elif auth_method == 0x02:  # Username/password
                    return self._authenticate_userpass()
                else:
                    print(f"❌ Unsupported authentication method: {auth_method}")
                    return False
    
            except Exception as e:
                print(f"❌ Connection failed: {e}")
                return False
    
        def _authenticate_userpass(self) -> bool:
            """Perform username/password authentication"""
            if not self.username or not self.password:
                print("❌ Username/password required but not provided")
                return False
    
            # Username/password authentication request
            username_bytes = self.username.encode("utf-8")
            password_bytes = self.password.encode("utf-8")
    
            auth_request = struct.pack("!BB", 0x01, len(username_bytes))
            auth_request += username_bytes
            auth_request += struct.pack("!B", len(password_bytes))
            auth_request += password_bytes
    
            self.sock.send(auth_request)
            auth_response = self.sock.recv(2)
    
            if len(auth_response) != 2 or auth_response[0] != 0x01:
                print("❌ Invalid authentication response")
                return False
    
            if auth_response[1] == 0x00:
                print("✅ Authentication successful")
                return True
            else:
                print("❌ Authentication failed")
                return False
    
        def test_udp_associate(self) -> Tuple[bool, Optional[Tuple[str, int]]]:
            """Test UDP ASSOCIATE command"""
            try:
                # UDP ASSOCIATE request
                # VER(0x05) + CMD(0x03) + RSV(0x00) + ATYP(0x01) + DST.ADDR(0.0.0.0) + DST.PORT(0)
                udp_request = b"\x05\x03\x00\x01\x00\x00\x00\x00\x00\x00"
    
                self.sock.send(udp_request)
                response = self.sock.recv(10)
    
                if len(response) < 10:
                    print("❌ Incomplete UDP ASSOCIATE response")
                    return False, None
    
                # Parse response
                ver, rep, rsv, atyp = struct.unpack("!BBBB", response[:4])
    
                if ver != 0x05:
                    print(f"❌ Invalid SOCKS version in response: {ver}")
                    return False, None
    
                if rep == 0x00:
                    print("✅ UDP ASSOCIATE command accepted")
    
                    # Parse relay address and port
                    if atyp == 0x01:  # IPv4
                        relay_ip = socket.inet_ntoa(response[4:8])
                        relay_port = struct.unpack("!H", response[8:10])[0]
                        print(f"✅ UDP relay endpoint: {relay_ip}:{relay_port}")
                        return True, (relay_ip, relay_port)
                    else:
                        print(f"❌ Unsupported address type in response: {atyp}")
                        return False, None
                else:
                    error_messages = {
                        0x01: "General SOCKS server failure",
                        0x02: "Connection not allowed by ruleset",
                        0x03: "Network unreachable",
                        0x04: "Host unreachable",
                        0x05: "Connection refused",
                        0x06: "TTL expired",
                        0x07: "Command not supported",
                        0x08: "Address type not supported",
                    }
                    error_msg = error_messages.get(rep, f"Unknown error code: {rep}")
                    print(f"❌ UDP ASSOCIATE failed: {error_msg}")
                    return False, None
    
            except Exception as e:
                print(f"❌ UDP ASSOCIATE test failed: {e}")
                return False, None
    
        def test_udp_relay(self, relay_endpoint: Tuple[str, int]) -> bool:
            """Test actual UDP relay functionality"""
            try:
                relay_ip, relay_port = relay_endpoint
    
                # Create UDP socket for testing
                udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                udp_sock.settimeout(5)
    
                # Create SOCKS5 UDP packet for DNS query to 8.8.8.8:53
                # RSV(0x0000) + FRAG(0x00) + ATYP(0x01) + DST.ADDR + DST.PORT + DATA
                dns_query = b"\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x06google\x03com\x00\x00\x01\x00\x01"
    
                socks_udp_packet = struct.pack(
                    "!HBB", 0x0000, 0x00, 0x01
                )  # RSV + FRAG + ATYP
                socks_udp_packet += socket.inet_aton("8.8.8.8")  # DST.ADDR
                socks_udp_packet += struct.pack("!H", 53)  # DST.PORT
                socks_udp_packet += dns_query  # DATA
    
                print(
                    f"🔄 Testing UDP relay by sending DNS query through {relay_ip}:{relay_port}"
                )
    
                udp_sock.sendto(socks_udp_packet, (relay_ip, relay_port))
    
                # Try to receive response
                try:
                    response, addr = udp_sock.recvfrom(4096)
                    print(f"✅ Received UDP response from relay ({len(response)} bytes)")
                    print(f"✅ UDP relay is working correctly!")
                    return True
                except socket.timeout:
                    print("❌ No response from UDP relay (timeout)")
                    return False
                finally:
                    udp_sock.close()
    
            except Exception as e:
                print(f"❌ UDP relay test failed: {e}")
                return False
    
        def test_proxy(self) -> dict:
            """Run complete UDP support test"""
            auth_str = f"{self.username}:{self.password}@" if self.username else ""
            result = {
                "proxy": f"{auth_str}{self.proxy_host}:{self.proxy_port}",
                "connection": False,
                "udp_associate": False,
                "udp_relay": False,
                "relay_endpoint": None,
            }
    
            print(f"\n🔍 Testing SOCKS5 proxy: {self.proxy_host}:{self.proxy_port}")
            print("=" * 50)
    
            # Test connection and authentication
            if not self.connect_and_authenticate():
                return result
            result["connection"] = True
    
            # Test UDP ASSOCIATE command
            udp_supported, relay_endpoint = self.test_udp_associate()
            result["udp_associate"] = udp_supported
            result["relay_endpoint"] = relay_endpoint
    
            if not udp_supported:
                return result
    
            # Test actual UDP relay functionality
            if relay_endpoint:
                relay_works = self.test_udp_relay(relay_endpoint)
                result["udp_relay"] = relay_works
    
            return result
    
        def close(self):
            """Close connection"""
            if self.sock:
                self.sock.close()
    
    
    def parse_proxy_file(filename: str) -> list:
        """Parse proxy file with format username:password@ip:port"""
        proxies = []
    
        try:
            with open(filename, "r", encoding="utf-8") as f:
                for line_num, line in enumerate(f, 1):
                    line = line.strip()
                    if not line or line.startswith("#"):
                        continue
    
                    try:
                        # Parse username:password@ip:port format
                        if "@" in line:
                            auth_part, addr_part = line.rsplit("@", 1)
                            if ":" in auth_part:
                                username, password = auth_part.split(":", 1)
                            else:
                                print(
                                    f"⚠️  Line {line_num}: Invalid auth format, skipping: {line}"
                                )
                                continue
                        else:
                            # No authentication
                            username, password = None, None
                            addr_part = line
    
                        # Parse ip:port
                        if ":" in addr_part:
                            host, port_str = addr_part.rsplit(":", 1)
                            port = int(port_str)
                        else:
                            print(
                                f"⚠️  Line {line_num}: Invalid address format, skipping: {line}"
                            )
                            continue
    
                        proxies.append(
                            {
                                "host": host,
                                "port": port,
                                "username": username,
                                "password": password,
                                "original_line": line,
                            }
                        )
    
                    except ValueError as e:
                        print(f"⚠️  Line {line_num}: Error parsing line '{line}': {e}")
                        continue
                    except Exception as e:
                        print(
                            f"⚠️  Line {line_num}: Unexpected error parsing line '{line}': {e}"
                        )
                        continue
    
            print(f"📖 Loaded {len(proxies)} proxies from {filename}")
            return proxies
    
        except FileNotFoundError:
            print(f"❌ File not found: {filename}")
            print("📝 Please create proxy.txt with format: username:password@ip:port")
            print("📝 Example content:")
            print("   test:test@127.0.0.1:1080")
            print("   XxoWb:CEx3v@127.0.0.1:1026")
            print("   # Lines starting with # are ignored")
            print("   127.0.0.1:1080  # No auth proxy")
            return []
        except Exception as e:
            print(f"❌ Error reading proxy file: {e}")
            return []
    
    
    def test_multiple_proxies(proxy_file: str = "proxy.txt"):
        """Test multiple SOCKS5 proxies for UDP support"""
        proxies = parse_proxy_file(proxy_file)
    
        if not proxies:
            return []
    
        results = []
    
        for proxy_config in proxies:
            tester = SOCKS5UDPTester(
                proxy_config["host"],
                proxy_config["port"],
                proxy_config.get("username"),
                proxy_config.get("password"),
            )
    
            try:
                result = tester.test_proxy()
                results.append(result)
            finally:
                tester.close()
    
        # Print summary
        print("\n" + "=" * 80)
        print("📊 SUMMARY")
        print("=" * 80)
    
        for result in results:
            proxy_str = result["proxy"]
            if result["connection"]:
                if result["udp_associate"] and result["udp_relay"]:
                    status = "✅ FULL UDP SUPPORT"
                elif result["udp_associate"]:
                    status = "⚠️  UDP ASSOCIATE ONLY (relay not working)"
                else:
                    status = "❌ NO UDP SUPPORT"
            else:
                status = "❌ CONNECTION FAILED"
    
            print(f"{proxy_str:<35} | {status}")
    
        return results
    
    
    if __name__ == "__main__":
        print("SOCKS5 UDP Support Tester")
        print("=" * 80)
    
        # Check if custom proxy file specified
        proxy_file = "proxy.txt"
        if len(sys.argv) > 1:
            proxy_file = sys.argv[1]
    
        print(f"📂 Reading proxies from: {proxy_file}")
    
        try:
            results = test_multiple_proxies(proxy_file)
    
            if results:
                # Count results
                total = len(results)
                working_udp = sum(
                    1 for r in results if r["udp_associate"] and r["udp_relay"]
                )
                partial_udp = sum(
                    1 for r in results if r["udp_associate"] and not r["udp_relay"]
                )
                no_udp = sum(
                    1 for r in results if r["connection"] and not r["udp_associate"]
                )
                failed = sum(1 for r in results if not r["connection"])
    
                print(f"\n📈 STATISTICS")
                print("=" * 80)
                print(f"Total proxies tested: {total}")
                print(f"✅ Full UDP support: {working_udp}")
                print(f"⚠️  Partial UDP support: {partial_udp}")
                print(f"❌ No UDP support: {no_udp}")
                print(f"💥 Connection failed: {failed}")
    
            else:
                print("\n❌ No proxies to test. Please check your proxy file.")
    
        except KeyboardInterrupt:
            print("\n❌ Test interrupted by user")
            sys.exit(1)
        except Exception as e:
            print(f"\n❌ Unexpected error: {e}")
            sys.exit(1)

    Here we tested two different proxies and found that one supports UDP and the other does not.

    Conclusion

    We hope that over the course of this article you’ve learned that testing UDP doesn’t have to be difficult or confusing. By using some strategically chosen methods we can, in a straightforward way, set up a SOCKS5 server and proxy UDP traffic through it.

    Key Takeaways:

  • Use tun2socks to create a virtual interface that will forward your traffic over a SOCKS5 proxy.
  • Run a DNS lookup using dig @8.8.8.8 google.com
  • On Linux: Test UDP via sudo ntpd -q time.google.com
  • Test UDP over SOCKS by connecting to a Discord voice channel.
  • Use our custom script to test your proxies at scale.
  • Between running a DNS lookup, syncing your clock via NTP, joining a Discord voice channel, and using a custom script, we’ve given you a range of options to test UDP over SOCKS.

    Proxies & Anonymity