IPv6 Support Implementation in Shadowsocks‑Windows: Architecture and Differences from IPv4

Shadowsocks‑Windows implements IPv6 support through a global isIPv6Enabled configuration flag that cascades through socket creation, endpoint binding, and SOCKS5 protocol handling to ensure all network components use the correct address family.

The IPv6 support implementation in shadowsocks/shadowsocks-windows is gated by an experimental feature flag that forces the entire application stack to adopt IPv6 addressing when enabled. Unlike IPv4, which uses 32-bit addresses and AddressFamily.InterNetwork, the IPv6 pathway requires 16-byte address handling, bracketed URI notation, and specific SOCKS5 address type identifiers. This architecture ensures that when isIPv6Enabled is true, every socket, listener, and relay operates exclusively on the IPv6 loopback and any-address interfaces.

Configuration and Feature Flag

The foundation of IPv6 support resides in Model/Configuration.cs, where the Boolean property isIPv6Enabled determines the address family for the entire application. At startup, the code validates this setting against Socket.OSSupportsIPv6 (lines 46-215); if the operating system lacks IPv6 capability, the flag is forced to false regardless of user preference.

When IPv6 is active, the LocalHost property (line 126) returns "[::1]" instead of "127.0.0.1", ensuring that loopback connections use the correct IPv6 loopback interface:

// Model/Configuration.cs
public string LocalHost => isIPv6Enabled ? "[::1]" : "127.0.0.1";

This single configuration value propagates to every network component, acting as the source of truth for address family selection throughout the codebase.

Socket Creation and Listener Binding

In Controller/Service/Listener.cs, the application creates TCP and UDP sockets based on the configuration flag. Lines 70-77 instantiate sockets using AddressFamily.InterNetworkV6 when IPv6 is enabled, falling back to AddressFamily.InterNetwork for IPv4-only operation:

// Controller/Service/Listener.cs
_socket = new Socket(
    config.isIPv6Enabled ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork,
    SocketType.Stream,
    ProtocolType.Tcp);

Endpoint binding follows the same conditional logic. Lines 35-39 select between IPAddress.IPv6Any and IPAddress.Any (or their loopback equivalents) to ensure the listener attaches to the correct network interface:

// Controller/Service/Listener.cs
var localAddress = config.isIPv6Enabled ? IPAddress.IPv6Any : IPAddress.Any;

SOCKS5 Protocol Handling

The TCP relay layer in Controller/Service/TCPRelay.cs implements IPv6-specific parsing for SOCKS5 protocol packets. Encryption/EncryptorBase.cs defines the address type constant ATYP_IPv6 = 0x04 (line 72), which the relay uses to identify 16-byte IPv6 payloads in request and response handling (lines 481, 540, and 579).

When parsing incoming SOCKS5 requests, the relay extracts IPv6 addresses using a dedicated case block that handles the 16-byte address length:

// Controller/Service/TCPRelay.cs
case ATYP_IPv6: // IPv6 address, 16 bytes
    Buffer.BlockCopy(data, offset, response, 4, 16);
    offset += 16;
    break;

This differs from IPv4, which uses ATYP_IPv4 = 0x01 and copies only 4 bytes.

Server URI Formatting

IPv6 addresses require bracket notation to comply with RFC 3986. In Model/Server.cs (line 129), the code detects IPv6 hostnames and wraps them in square brackets when constructing connection strings:

// Model/Server.cs
case UriHostNameType.IPv6:
    // Add square brackets when IPv6 (RFC3986)
    return $"[{host}]";

This produces valid URIs such as http://[2001:db8::1]:8388, whereas IPv4 addresses are appended directly without delimiters.

UDP Relay and Privoxy Integration

The UDP relay implementation in Controller/Service/UDPRelay.cs mirrors the TCP listener’s behavior. At line 71, it returns IPAddress.IPv6Any when the global IPv6 flag is enabled, ensuring UDP sockets bind to the correct any-address interface.

The bundled Privoxy proxy also respects this setting. Controller/Service/PrivoxyRunner.cs (lines 55-58 and 150-156) passes the isIPv6Enabled flag to GetFreePort(), which binds the helper HTTP proxy to an IPv6 loopback address when necessary. This guarantees that the local proxy chain remains consistent with the chosen address family.

Key Differences from IPv4

The IPv6 support implementation differs from IPv4 in five critical ways:

  • Address Family Selection – All sockets use InterNetworkV6 instead of InterNetwork, affecting every connection in the stack.
  • Address Length – SOCKS5 packets carry 16-byte IPv6 addresses (type 0x04) versus 4-byte IPv4 addresses (type 0x01).
  • Loopback Interfaces – The code uses [::1] and IPv6Loopback instead of 127.0.0.1 and Loopback.
  • URI Syntax – IPv6 server addresses are wrapped in [...] brackets to conform to RFC 3986.
  • OS Validation – The application automatically disables IPv6 if Socket.OSSupportsIPv6 returns false, preventing runtime errors on IPv4-only systems.

Summary

  • The isIPv6Enabled flag in Configuration.cs controls the entire application's address family, defaulting to false for backward compatibility.
  • Socket creation in Listener.cs switches between AddressFamily.InterNetworkV6 and InterNetwork based on the configuration flag.
  • SOCKS5 protocol handling requires a separate code path for the ATYP_IPv6 address type to process 16-byte addresses.
  • IPv6 addresses in URIs must be bracketed, implemented in Server.cs for RFC 3986 compliance.
  • Both TCP and UDP relays, along with the Privoxy helper, bind to IPv6Any or IPv6Loopback when IPv6 mode is active.

Frequently Asked Questions

How do I enable IPv6 support in Shadowsocks‑Windows?

Set "isIPv6Enabled": true in your configuration file or enable the option in the GUI settings. The application will verify that your operating system supports IPv6 via Socket.OSSupportsIPv6 before activating the feature. If the OS check fails, the flag is automatically forced to false to prevent connection errors.

Why does Shadowsocks‑Windows use different address types for IPv4 and IPv6 in SOCKS5 packets?

The SOCKS5 protocol defines specific address type identifiers: 0x01 for IPv4 (4 bytes) and 0x04 for IPv6 (16 bytes). The TCPRelay.cs implementation checks these type bytes to determine how many bytes to copy from the packet buffer—4 bytes for IPv4 or 16 bytes for IPv6—ensuring correct address parsing regardless of the protocol version.

What happens to the local listener when IPv6 is enabled?

When isIPv6Enabled is true, Listener.cs creates sockets with AddressFamily.InterNetworkV6 and binds them to IPAddress.IPv6Any or IPAddress.IPv6Loopback instead of their IPv4 equivalents. This restricts all local proxy traffic to the IPv6 loopback interface [::1], isolating the listener from IPv4-only network stacks.

Does the UDP relay support IPv6 addressing?

Yes. UDPRelay.cs handles IPv6 by returning IPAddress.IPv6Any when the global IPv6 flag is enabled (line 71). This ensures that UDP socket creation and binding follow the same address family selection logic as the TCP components, maintaining protocol consistency across the application.

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 →