How the Shadowsocks Windows UDP Relay Handles DNS Queries and Packet Forwarding

Shadowsocks-Windows forwards all UDP traffic—including DNS queries—through its UDPRelay service by encrypting payloads via IEncryptor and tunneling them to a remote Shadowsocks server, which resolves the destination and returns responses through the same encrypted path.

The shadowsocks-windows repository implements a transparent UDP relay that processes DNS queries as standard UDP datagrams. In shadowsocks-csharp/Controller/Service/UDPRelay.cs, the UDPRelay class coordinates with Listener and UDPHandler to encrypt, forward, and decrypt packets without distinguishing between DNS and other UDP traffic.

UDP Relay Architecture Components

The UDP relay system consists of three primary components working in sequence:

  1. Listener (shadowsocks-csharp/Controller/Service/Listener.cs): Accepts raw UDP packets from local SOCKS5 clients and dispatches them to registered services.

  2. UDPRelay (shadowsocks-csharp/Controller/Service/UDPRelay.cs): Creates and manages UDPHandler instances for each client session, coordinating encryption and remote transmission.

  3. IEncryptor (shadowsocks-csharp/Encryption/IEncryptor.cs): Provides EncryptUDP and DecryptUDP methods used by handlers to secure payloads using algorithms like AES-128-CBC or ChaCha20-Poly1305.

The UDP Packet Forwarding Flow

Step 1: Local Listener Receipt

When a SOCKS5 client sends the first UDP packet following a UDP-ASSOCIATE request, the Listener creates a UDPState object and iterates through registered services until UDPRelay.Handle returns true. This dispatch logic appears in Listener.cs at lines 14-30 and 88-90.

The Handle method in UDPRelay.cs (lines 28-48) checks for an existing UDPHandler for the client's IP/port or creates a new one, then passes the raw packet for processing.

Step 2: Handler Encryption and Remote Transmission

Inside UDPHandler.Send (lines 96-106 of UDPRelay.cs), the relay strips the first three bytes of the SOCKS5 UDP header and encrypts the remaining payload:

// Inside UDPHandler.Send – encrypt + forward
byte[] dataIn  = new byte[length - 3];                // strip SOCKS5 header
Array.Copy(data, 3, dataIn, 0, length - 3);
byte[] dataOut = new byte[65536];
int outlen;
encryptor.EncryptUDP(dataIn, length - 3, dataOut, out outlen);
_remote?.SendTo(dataOut, outlen, SocketFlags.None, _remoteEndPoint);

The encrypted data is sent to the remote Shadowsocks server endpoint defined in Server.cs, which decrypts the packet and forwards it to the actual destination—whether a DNS resolver (port 53) or any other UDP service.

Step 3: DNS Resolution at the Remote Server

DNS queries receive no special treatment in the client-side code. The remote Shadowsocks server receives the encrypted DNS query, performs the lookup (or forwards it upstream), and transmits the response back through the same UDP socket.

Return Path and Response Decryption

When the reply arrives from the remote server, UDPHandler.RecvFromCallback (lines 119-135 of UDPRelay.cs) processes the incoming data:

// Inside UDPHandler.RecvFromCallback – decrypt + return
int bytesRead = _remote.EndReceiveFrom(ar, ref remoteEndPoint);
byte[] dataOut = new byte[bytesRead];
encryptor.DecryptUDP(_buffer, bytesRead, dataOut, out outlen);

byte[] sendBuf = new byte[outlen + 3];               // re‑add SOCKS5 header
Array.Copy(dataOut, 0, sendBuf, 3, outlen);
_local?.SendTo(sendBuf, outlen + 3, 0, _localEndPoint);

The method decrypts the payload using IEncryptor.DecryptUDP, restores the three-byte SOCKS5 UDP header, and transmits the result to the original local client via _local.SendTo.

Why DNS Queries Work Transparently

Because the UDP relay operates at the transport layer without inspecting packet contents, DNS queries (typically sent to port 53) flow through the same encryption pipeline as any other UDP traffic. The SOCKS5 client—whether a browser, dig command, or system resolver—sends a standard UDP-ASSOCIATE request, and the relay encrypts and tunnels the query without modification.

Summary

  • All UDP traffic is equal: The UDPRelay treats DNS queries identically to other UDP packets, providing uniform encryption and forwarding.
  • Three-layer architecture: Listener accepts packets, UDPRelay manages handlers, and UDPHandler performs encryption/decryption via IEncryptor.
  • Header manipulation: The first three bytes of the SOCKS5 UDP header are stripped before encryption and restored after decryption.
  • Remote resolution: The Shadowsocks server handles actual DNS resolution; the Windows client merely tunnels the request.
  • Stateful handlers: Each client IP/port combination gets a dedicated UDPHandler instance to maintain session state.

Frequently Asked Questions

Does Shadowsocks-Windows treat DNS queries differently from other UDP traffic?

No. According to the source code in UDPRelay.cs, DNS queries are processed as standard UDP packets. The relay strips the SOCKS5 header, encrypts the payload, and forwards it to the remote server without inspecting the destination port or payload content.

What encryption methods does the UDP relay support?

The relay supports all methods defined in IEncryptor.cs and implemented in EncryptorFactory.cs, including AES-128-CBC, AES-256-GCM, and ChaCha20-Poly1305. The specific method is determined by the server configuration and applied via EncryptUDP and DecryptUDP calls in UDPHandler.

Where is the SOCKS5 UDP header stripped and re-added?

In shadowsocks-csharp/Controller/Service/UDPRelay.cs, the UDPHandler.Send method (lines 96-106) strips the first three bytes before encryption, while UDPHandler.RecvFromCallback (lines 119-135) re-adds them after decryption and before returning the packet to the local client.

How does the relay handle multiple concurrent UDP connections?

The UDPRelay.Handle method maintains a dictionary of UDPHandler instances keyed by client IP and port. When a packet arrives from a new endpoint, it creates a fresh handler; existing endpoints reuse their handler, allowing the relay to manage multiple concurrent UDP associations simultaneously.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →