How the PortForwarder Service Enables HTTP Proxy Support in Shadowsocks-Windows

The PortForwarder service acts as a lightweight TCP bridge that forwards HTTP proxy requests from the local Shadowsocks listener to the bundled Privoxy instance, enabling standard HTTP/HTTPS proxy support without adding HTTP protocol logic to the Shadowsocks core.

The PortForwarder is a specialized service within the shadowsocks/shadowsocks-windows client that transforms the SOCKS5 proxy into a fully functional HTTP proxy. While Shadowsocks natively operates as a SOCKS5 proxy, many Windows applications require a standard HTTP proxy interface—this is where the PortForwarder service bridges the gap by transparently forwarding traffic to an embedded Privoxy instance.

Architecture and Service Integration

The PortForwarder operates as one of several services managed by the main Listener class. When the Shadowsocks client starts, the ShadowsocksController constructs a list of services that handle different types of incoming traffic.

In shadowsocks-csharp/Controller/ShadowsocksController.cs, the controller initializes PrivoxyRunner to launch the bundled Privoxy executable, then injects the PortForwarder with Privoxy's listening port:

// shadowsocks-csharp/Controller/ShadowsocksController.cs
List<Listener.IService> services = new List<Listener.IService>
{
    tcpRelay,
    udpRelay,
    _pacServer,
    new PortForwarder(privoxyRunner.RunningPort)   // HTTP proxy bridge
};

The Listener class (defined in shadowsocks-csharp/Controller/Service/Listener.cs) implements a service dispatcher pattern. It accepts inbound TCP connections on the configured local port (_config.localPort) and iterates through the service list, calling each service's Handle method until one returns true:

// shadowsocks-csharp/Controller/Service/Listener.cs
public interface IService
{
    bool Handle(byte[] firstPacket, int length, Socket socket, object state);
    void Stop();
}

When PortForwarder.Handle detects an HTTP proxy connection attempt, it assumes responsibility for that socket, preventing further processing by the SOCKS5 relay.

How PortForwarder Processes HTTP Proxy Connections

The PortForwarder implementation in shadowsocks-csharp/Controller/Service/PortForwarder.cs manages the complete lifecycle of HTTP proxy connections through five distinct phases:

Service Entry Point and Validation

The Handle method (lines 18–26) performs initial validation to ensure the socket uses TCP protocol, then immediately spawns a Handler instance to manage the connection asynchronously. This non-blocking approach allows the Listener to continue accepting new connections while the PortForwarder manages the data pipeline.

Establishing the Remote Endpoint

The Handler class connects to the Privoxy instance via WrappedSocket. In the Start method (lines 55–60), it establishes a TCP connection to 127.0.0.1 (or [::1] for IPv6) on the port provided during PortForwarder construction—the privoxyRunner.RunningPort:

// From PortForwarder.cs Start method
// Connects to localhost:PrivoxyPort
remote = new WrappedSocket();
remote.BeginConnect(destEndPoint, ConnectCallback, null);

Handshake and Initial Data Forwarding

Once the remote socket connects, the handler immediately forwards the firstPacket (typically an HTTP CONNECT request) to Privoxy without modification. The HandshakeReceive callback (lines 94–98) uses BeginSend to transmit this initial data:

// Forward the first packet (HTTP CONNECT) to Privoxy
remote.BeginSend(_firstPacket, 0, _firstPacket.Length, 
                 SocketFlags.None, PipeRemoteSendCallback, null);

This transparent forwarding ensures that Privoxy receives the complete HTTP request headers necessary to parse the target destination and establish appropriate tunneling.

Bidirectional Data Piping

After the handshake completes, two asynchronous read loops manage data flow:

  • Remote → Local: PipeRemoteReceiveCallback (lines 126–174) reads data from Privoxy and writes it back to the client socket
  • Local → Remote: PipeConnectionReceiveCallback (lines 152–170) reads from the client and forwards to Privoxy

Each callback implements EOF detection. When a read operation returns zero bytes, that side of the connection has initiated shutdown. The handler tracks shutdown state through boolean flags (_remoteShutdown, _localShutdown) to coordinate graceful closure.

Connection Cleanup

The CheckClose method (lines 178–226) monitors both shutdown flags. When both directions have closed, it invokes the Close method to dispose the WrappedSocket instances and free system resources, preventing file descriptor leaks during high-throughput HTTP proxy usage.

Why This Design Uses Privoxy

The PortForwarder service delegates HTTP protocol handling to Privoxy rather than implementing HTTP proxy logic directly for three critical reasons:

  • Separation of concerns: Shadowsocks core focuses exclusively on encrypted tunneling (SOCKS5/UDP), while Privoxy—a mature, standalone project—handles HTTP parsing, header rewriting, and CONNECT method semantics
  • Protocol completeness: Privoxy supports both HTTP and HTTPS transparent proxying, including PAC (Proxy Auto-Configuration) file serving, which would require significant additional code to implement natively
  • Performance efficiency: The PortForwarder acts as a zero-copy byte pipe, imposing negligible latency overhead while leveraging Privoxy's optimized HTTP processing

Practical Implementation Examples

Using the HTTP Proxy in C# Applications

When Shadowsocks-Windows runs with system proxy enabled, applications can route traffic through the local HTTP proxy endpoint. The PortForwarder automatically bridges this to Privoxy:

using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

class HttpProxyExample
{
    static async Task Main()
    {
        // Shadowsocks-Windows exposes HTTP proxy on the local port (default 1080)
        // PortForwarder internally forwards this to Privoxy
        var proxy = new WebProxy("http://127.0.0.1:1080");
        var handler = new HttpClientHandler { Proxy = proxy, UseProxy = true };
        var client = new HttpClient(handler);

        // Request flows: Application → PortForwarder → Privoxy → Shadowsocks tunnel
        var response = await client.GetStringAsync("https://httpbin.org/ip");
        Console.WriteLine($"Response via Shadowsocks HTTP proxy: {response}");
    }
}

Manual PortForwarder Instantiation for Testing

Developers can create PortForwarder instances programmatically to test HTTP proxy handling without running the full Shadowsocks client:

using System.Net;
using System.Net.Sockets;
using Shadowsocks.Controller;

class PortForwarderTest
{
    static void TestForwarder()
    {
        // Privoxy default port when running standalone
        int privoxyPort = 8118;
        var forwarder = new PortForwarder(privoxyPort);
        
        // Simulate incoming HTTP proxy request
        using (var client = new Socket(AddressFamily.InterNetwork, 
                                       SocketType.Stream, ProtocolType.Tcp))
        {
            client.Connect(IPAddress.Loopback, 1080);
            
            byte[] connectRequest = System.Text.Encoding.ASCII.GetBytes(
                "CONNECT www.example.com:443 HTTP/1.1\r\nHost: www.example.com:443\r\n\r\n"
            );
            
            // PortForwarder.Handle takes ownership of the socket
            bool handled = forwarder.Handle(connectRequest, 
                                           connectRequest.Length, 
                                           client, 
                                           null);
            Console.WriteLine($"Connection handled by PortForwarder: {handled}");
        }
    }
}

This low-level access allows unit testing of the connection bridging logic without requiring a full proxy chain.

Summary

  • PortForwarder is implemented in shadowsocks-csharp/Controller/Service/PortForwarder.cs as a TCP-only bridge service
  • It integrates with the Listener service chain via the IService interface, specifically handling connections that target the HTTP proxy port
  • The service forwards raw TCP streams to Privoxy (running on privoxyRunner.RunningPort), which handles HTTP CONNECT methods and header parsing
  • Bidirectional asynchronous piping (PipeRemoteReceiveCallback and PipeConnectionReceiveCallback) maintains high-performance data flow between client and Privoxy
  • This architecture allows Shadowsocks-Windows to expose a standard http://127.0.0.1:<localPort> endpoint to applications while keeping HTTP protocol complexity out of the core encryption layer

Frequently Asked Questions

What is Privoxy and why does Shadowsocks-Windows use it?

Privoxy is a non-caching web proxy with advanced filtering capabilities. Shadowsocks-Windows bundles Privoxy to handle HTTP proxy protocol specifics—including CONNECT tunneling for HTTPS—without bloating the Shadowsocks core with HTTP parsing logic. According to the source code in shadowsocks-csharp/Data/privoxy.exe.gz, the client extracts and launches this dedicated proxy process, then uses PortForwarder to route traffic to it.

How does PortForwarder differ from the standard TCP relay?

While both handle TCP connections, the TCP relay (tcpRelay in the service list) implements the SOCKS5 protocol state machine—handling authentication requests, CONNECT commands, and relaying to remote Shadowsocks servers. The PortForwarder simply bridges TCP streams to the local Privoxy port without interpreting any protocol; it operates transparently on raw bytes rather than SOCKS5 frames.

Can I use the HTTP proxy feature without Privoxy running?

No. If Privoxy fails to start or the privoxyRunner.RunningPort is unavailable, the PortForwarder cannot establish its remote endpoint. The Handler attempts to connect to the configured Privoxy port during the Start phase; if this connection fails, the HTTP proxy functionality becomes unavailable, though the SOCKS5 proxy (handled by tcpRelay) continues operating normally.

Which port does the HTTP proxy listen on?

The HTTP proxy listens on the same port as the Shadowsocks local SOCKS5 proxy—defined by Configuration.localPort (default 1080). The PortForwarder distinguishes HTTP proxy traffic from SOCKS5 traffic by its position in the service chain and socket handling characteristics, allowing both protocols to coexist on a single port.

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 →