# How the Shadowsocks Listener Routes Incoming TCP and UDP Connections to TCPRelay, UDPRelay, and PACServer

> Learn how the Shadowsocks Listener routes incoming TCP and UDP connections to TCPRelay, UDPRelay, and PACServer by inspecting initial packets and delegating to the first matching service.

- Repository: [shadowsocks/shadowsocks-windows](https://github.com/shadowsocks/shadowsocks-windows)
- Tags: internals
- Published: 2026-03-05

---

**The Shadowsocks Listener routes incoming TCP and UDP connections by iterating over registered `IService` implementations and delegating to the first service whose `Handle` method returns `true` based on protocol-specific inspection of the initial packet.**

The routing mechanism in the shadowsocks-windows repository is centralized in the `Listener` class ([`shadowsocks-csharp/Controller/Service/Listener.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/Service/Listener.cs)). When the application initializes, `ShadowsocksController` constructs a prioritized list of services including `TCPRelay`, `UDPRelay`, and `PACServer`, then injects this collection into the Listener to handle all inbound network traffic on the configured local port.

## The IService Contract and Service Registration

The Listener operates against a simple contract defined by the nested `Listener.IService` interface. Any class implementing this interface participates in the routing chain by providing a `Handle` method that inspects incoming data and optionally claims the connection.

In [`shadowsocks-csharp/Controller/ShadowsocksController.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/ShadowsocksController.cs) (lines 24-32), the controller assembles the service list before starting the Listener:

```csharp
List<Listener.IService> services = new List<Listener.IService>
{
    tcpRelay,
    udpRelay,
    _pacServer,
    new PortForwarder(privoxyRunner.RunningPort)
};
_listener = new Listener(services);
_listener.Start(_config);

```

This ordered collection determines the priority of protocol detection. The Listener stores this list in a private field and references it for every new connection or datagram.

## TCP Connection Routing: From Acceptance to Handler Dispatch

For TCP traffic, the Listener creates a `Socket` instance (`_tcpSocket`) and initiates an asynchronous accept loop in the `Start` method (lines 84-90). The routing process spans two callback methods to ensure the Listener can inspect the initial payload before selecting an appropriate handler.

### Accepting New TCP Connections

When a client connects, the `AcceptCallback` method accepts the socket and immediately begins reading the first bytes to determine the protocol. In [`Listener.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Listener.cs) (lines 65-67), the implementation starts an asynchronous receive operation:

```csharp
conn.BeginReceive(buf, 0, buf.Length, 0, ReceiveCallback, state);

```

This captures the initial handshake bytes—such as the SOCKS5 version identifier or HTTP method—without blocking the Listener's main accept loop.

### The ReceiveCallback Routing Loop

Once data arrives, `ReceiveCallback` iterates through the registered services and invokes `Handle` on each until one returns `true`. According to the source code (lines 4-10), the implementation follows this short-circuit pattern:

```csharp
foreach (IService service in _services)
{
    if (service.Handle(buf, bytesRead, conn, null))
        return;               // connection handled
}

```

If no service claims the connection, the socket is closed and resources are released.

### TCPRelay: SOCKS5 Handshake Detection

The `TCPRelay` service ([`shadowsocks-csharp/Controller/Service/TCPRelay.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/Service/TCPRelay.cs), lines 44-50) inspects the connection to identify SOCKS5 traffic. It validates two conditions: the socket protocol must be `Tcp`, and the first byte must equal `0x05` (the SOCKS5 version identifier).

When these conditions are met, `TCPRelay` creates a `TCPHandler` instance to manage the encrypted proxy session and returns `true`, preventing subsequent services from processing the connection.

### PACServer: HTTP PAC Request Handling

The `PACServer` ([`shadowsocks-csharp/Controller/Service/PACServer.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/Service/PACServer.cs), lines 57-64) handles TCP connections requesting the Proxy Auto-Config file. It parses the incoming buffer for an HTTP GET request targeting the "/pac" resource and validates any configured secret token.

Because `TCPRelay` appears earlier in the service list and filters specifically for the `0x05` byte, `PACServer` only receives connections that fail the SOCKS5 check. This ordering ensures that standard SOCKS5 clients connect to the relay, while browser PAC requests route to the configuration server.

## UDP Datagram Routing via RecvFromCallback

UDP traffic follows a connectionless path. The Listener initializes a UDP socket (`_udpSocket`) and enters a `BeginReceiveFrom` loop. When packets arrive, `RecvFromCallback` (lines 21-27 in [`Listener.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Listener.cs)) executes the service iteration:

```csharp
foreach (IService service in _services)
{
    if (service.Handle(state.buffer, bytesRead, socket, state))
        break;               // packet handled
}

```

### UDPRelay: Datagram Processing

The `UDPRelay` service ([`shadowsocks-csharp/Controller/Service/UDPRelay.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/Service/UDPRelay.cs), lines 28-38) claims all UDP traffic by checking the protocol type and ensuring the packet length is at least 4 bytes. Unlike TCP services, `UDPRelay` returns `true` for all valid UDP packets, effectively monopolizing the UDP routing path by creating or reusing a per-client `UDPHandler` for encryption and forwarding.

The `PACServer` never processes UDP traffic because its `Handle` method immediately returns `false` when `socket.ProtocolType` is not `Tcp`.

## Service Prioritization and Connection Fallbacks

The order of services in the `ShadowsocksController` initialization list directly impacts routing behavior. Because the Listener uses a short-circuit loop (breaking on the first `true` return), placing `TCPRelay` before `PACServer` is critical: a SOCKS5 handshake starting with byte `0x05` must be captured by the relay before the PAC server attempts to parse it as HTTP.

If an incoming TCP connection presents data that matches neither SOCKS5 nor the PAC endpoint pattern, all services return `false`, and the Listener closes the socket without responding. This fail-closed behavior prevents unauthorized protocol usage on the listening port.

## Summary

- The **Listener** class in [`shadowsocks-csharp/Controller/Service/Listener.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/shadowsocks-csharp/Controller/Service/Listener.cs) manages all inbound TCP and UDP sockets asynchronously using `BeginAccept` and `BeginReceiveFrom`.
- **Service registration** occurs in [`ShadowsocksController.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/ShadowsocksController.cs) (lines 24-32), which injects an ordered list of `IService` implementations into the Listener constructor.
- **TCP routing** uses a two-phase approach: `AcceptCallback` accepts the socket, then `ReceiveCallback` (lines 4-10) inspects the initial buffer and iterates through services until one returns `true`.
- **TCPRelay** claims connections starting with byte `0x05` (SOCKS5), while **PACServer** handles HTTP GET requests for "/pac" that fall through from the TCP relay check.
- **UDP routing** occurs in `RecvFromCallback` (lines 21-27), where **UDPRelay** processes all valid datagrams by verifying `ProtocolType.Udp` and minimum packet length.
- Connections unmatched by any service are silently closed, ensuring only recognized protocols consume server resources.

## Frequently Asked Questions

### What is the IService interface in Shadowsocks Windows?

The `IService` interface defines a contract used by the Listener to delegate incoming connections. It requires implementing classes to provide a `Handle` method that accepts a buffer, byte count, socket, and state object, returning a boolean indicating whether the service has claimed and processed the connection. This design allows the Listener to remain protocol-agnostic while supporting SOCKS5, HTTP, and UDP handlers simultaneously.

### How does Shadowsocks distinguish between SOCKS5 and HTTP traffic on the same port?

The Listener distinguishes protocols by inspecting the first bytes of the TCP stream in `ReceiveCallback`. `TCPRelay` checks for the SOCKS5 version byte (`0x05`), while `PACServer` parses for HTTP GET requests. Because `TCPRelay` appears first in the service list, SOCKS5 connections return `true` immediately, allowing HTTP traffic to fall through to the PAC server for handling.

### Can the service order in the Listener be customized?

Yes. The service order is determined by the `List<IService>` construction in [`ShadowsocksController.cs`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/ShadowsocksController.cs). Developers can reorder the services or insert additional `IService` implementations to change routing priority, though `TCPRelay` must precede `PACServer` to prevent SOCKS5 handshakes from being misinterpreted as HTTP requests.

### What happens if no service handles an incoming connection?

If all registered services return `false` during the `ReceiveCallback` or `RecvFromCallback` iteration, the Listener closes the client socket without sending a response. This behavior prevents hanging connections and ensures that only recognized protocols—SOCKS5, HTTP PAC requests, or valid UDP datagrams—are processed by the application.