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

> Discover how the SIP003 plugin system in Shadowsocks-Windows manages external proxy binaries as child processes. Learn about environment variable integration and automatic termination using Windows Job Objects.

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

---

**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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/Sip003Plugin.cs)** (lines 24‑34) checks for plugin availability and returns an instance only when configured:

```csharp
// 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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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:

```csharp
// 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:

```csharp
// 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:

```csharp
// 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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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:

```csharp
// 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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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

```csharp
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

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

```

### Inspecting Runtime Environment Variables

```csharp
// 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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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`](https://github.com/shadowsocks/shadowsocks-windows/blob/main/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.