How the SIP003 Plugin System Works in Shadowsocks-Windows: Process Lifecycle Management

The SIP003 plugin system launches external proxy binaries as managed child processes, wires them into the Shadowsocks data path via environment variables, and guarantees automatic termination through Windows Job Objects.

The SIP003 plugin system in shadowsocks/shadowsocks-windows provides a standardized mechanism for integrating traffic obfuscation tools (such as obfs-local or v2ray-plugin) into the Shadowsocks proxy chain. The implementation resides primarily in Sip003Plugin.cs and couples process management, network port allocation, and OS-level resource cleanup to ensure reliable plugin operation.

Plugin Discovery and Instantiation

The system activates when a server configuration contains a non-empty plugin field. The static factory method Sip003Plugin.CreateIfConfigured in Sip003Plugin.cs (lines 24‑34) checks for plugin availability and returns an instance only when configured:

// Sip003Plugin.cs – lines 24‑34
public static Sip003Plugin CreateIfConfigured(Server server, bool showPluginOutput)
{
    if (string.IsNullOrWhiteSpace(server.plugin)) return null;
    return new Sip003Plugin(
        server.plugin,
        server.plugin_opts,
        server.plugin_args,
        server.server,
        server.server_port,
        showPluginOutput);
}

The Server.cs model (lines 31‑44) defines three critical configuration fields consumed by this method: plugin (executable path), plugin_opts (environment options), and plugin_args (command-line arguments).

Preparing the Child Process

When instantiated, the constructor prepares a ProcessStartInfo object (lines 57‑74) that configures the execution context:

  • Executable: FileName is set to the user-supplied plugin path.
  • Arguments: Initial value drawn from plugin_args, supporting placeholder substitution.
  • Window behavior: CreateNoWindow respects the showPluginOutput boolean flag.
  • Environment variables: Injected immediately to expose the remote Shadowsocks endpoint:
// Sip003Plugin.cs – lines 70‑73
["SS_REMOTE_HOST"] = serverAddress,
["SS_REMOTE_PORT"] = serverPort.ToString(),
["SS_PLUGIN_OPTIONS"] = pluginOpts

Local Port Allocation and Environment Binding

When StartIfNeeded() is invoked, the system allocates an ephemeral TCP port to serve as the local bridge between Shadowsocks and the plugin:

// Sip003Plugin.cs – lines 94‑95
var localPort = GetNextFreeTcpPort();      // Uses temporary TcpListener (lines 38‑44)
LocalEndPoint = new IPEndPoint(IPAddress.Loopback, localPort);

The implementation then updates the child process environment with local binding details and expands argument placeholders:

// Sip003Plugin.cs – lines 97‑100
_pluginProcess.StartInfo.Environment["SS_LOCAL_HOST"] = LocalEndPoint.Address.ToString();
_pluginProcess.StartInfo.Environment["SS_LOCAL_PORT"] = LocalEndPoint.Port.ToString();
_pluginProcess.StartInfo.Arguments = ExpandEnvironmentVariables(
    _pluginProcess.StartInfo.Arguments,
    _pluginProcess.StartInfo.EnvironmentVariables);

The ExpandEnvironmentVariables method (lines 123‑136) first replaces %PLACEHOLDER% tokens with values from the plugin-specific environment dictionary, then falls back to the host process environment via Environment.ExpandEnvironmentVariables.

Process Lifetime Management via Windows Job Objects

After launching the process with _pluginProcess.Start(), the system immediately adds the child handle to a Windows Job Object. This mechanism, implemented in Job.cs, ensures the operating system terminates the plugin executable if the Shadowsocks parent process exits unexpectedly or when the plugin wrapper is disposed.

The Job.cs constructor (lines 24‑27) configures the job with the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag (0x2000), creating an atomic process group:

// Sip003Plugin.cs – lines 115‑118
_pluginJob.AddProcess(_pluginProcess.Handle);
_started = true;

If the plugin executable cannot be found, a FileNotFoundException is raised during startup (lines 106‑114), preventing invalid configurations from silently failing.

Clean Shutdown and Disposal

The Dispose method (lines 147‑174) implements a deterministic cleanup sequence:

  1. Kills the child process if still running.
  2. Waits for process exit to prevent zombie processes.
  3. Disposes the Process and Job objects.
  4. Marks the plugin instance as disposed.

Unit tests in Sip003PluginTest.cs (lines 86‑120) verify that environment variables propagate correctly and that the process disappears after Dispose() is called.

Practical Implementation Examples

Creating and Starting a SIP003 Plugin

using Shadowsocks.Model;
using Shadowsocks.Controller.Service;

// Configure a server with plugin support
Server srv = new Server {
    server = "1.2.3.4",
    server_port = 8388,
    plugin = "v2ray-plugin.exe",
    plugin_opts = "obfs=http;obfs-host=example.com",
    plugin_args = "--loglevel=debug %SS_REMOTE_HOST%:%SS_REMOTE_PORT%"
};

// Instantiate (returns null if no plugin configured)
Sip003Plugin plugin = Sip003Plugin.CreateIfConfigured(srv, showPluginOutput: false);
if (plugin != null) {
    plugin.StartIfNeeded();   // Launches process and assigns to Job Object
    // Use plugin.LocalEndPoint for Shadowsocks local traffic routing
}

Proper Resource Disposal

// During application shutdown or server reconfiguration
if (plugin != null) {
    plugin.Dispose();   // Terminates child process and releases Job Object
}

Inspecting Runtime Environment Variables

// As demonstrated in the test suite
Process p = Process.GetProcessesByName("v2ray-plugin").First();
var env = ProcessEnvironment.ReadEnvironmentVariables(p);
// Access env["SS_REMOTE_HOST"], env["SS_LOCAL_PORT"], etc.

Summary

  • SIP003Plugin.cs serves as the orchestrator, parsing Server configuration and managing the external process lifecycle.
  • Environment variable injection follows the SIP003 specification (SS_REMOTE_HOST, SS_LOCAL_PORT, SS_PLUGIN_OPTIONS) to communicate network endpoints to the plugin.
  • Dynamic port allocation uses a temporary TcpListener to find an available local port, then exposes it through environment variables and argument expansion.
  • Windows Job Objects (implemented in Job.cs) guarantee that child processes terminate when the parent exits, preventing orphaned plugin instances.
  • Deterministic cleanup via IDisposable ensures resources are released during server switching or application shutdown.

Frequently Asked Questions

What is the SIP003 standard and why does shadowsocks-windows use it?

SIP003 is a standardized plugin protocol for Shadowsocks that defines how proxy clients communicate with external obfuscation binaries. Shadowsocks-Windows implements this standard to ensure compatibility with community plugins like v2ray-plugin and obfs-local without requiring custom integration code for each tool.

How does the Job Object prevent zombie plugin processes?

The Job.cs wrapper creates a Windows Job Object with the JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE flag enabled. When the plugin process handle is added to this job via AssignProcessToJobObject, the OS binds the child's lifetime to the job handle. When the parent process exits or calls Dispose(), the job closes and the OS forcibly terminates all associated processes.

Can plugin arguments reference environment variables?

Yes. The ExpandEnvironmentVariables method in Sip003Plugin.cs supports %PLACEHOLDER% syntax. Placeholders are first matched against the plugin-specific environment dictionary (containing SS_REMOTE_HOST, SS_LOCAL_PORT, etc.), then fallback to the host system environment variables using standard Windows expansion rules.

What happens if the configured plugin executable is missing?

If StartIfNeeded() cannot locate the specified executable, it raises a FileNotFoundException (lines 106‑114 in Sip003Plugin.cs). This exception propagates to the caller, allowing the application to log the error and alert the user to the invalid plugin path configuration.

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 →