# PAC Manager Architecture in v2rayN: How Proxy Auto-Configuration Works

> Explore the PAC manager architecture in v2rayN. Learn how this HTTP server generates PAC scripts to route Windows traffic through local proxies based on URL patterns.

- Repository: [2dust/v2rayN](https://github.com/2dust/v2rayN)
- Tags: architecture
- Published: 2026-02-27

---

**The PAC manager in v2rayN is a singleton-based HTTP server that generates and hosts a PAC script on a local TCP listener, enabling Windows to route traffic through the local HTTP proxy based on URL patterns.**

The **PAC (Proxy Auto-Configuration) manager** is a critical component of the v2rayN proxy client that automates browser and system proxy decisions. According to the v2rayN source code, this lightweight architecture dynamically generates JavaScript-based PAC scripts and serves them via an embedded HTTP server, eliminating the need for external hosting. Understanding this architecture helps developers customize proxy rules and debug connectivity issues in Windows environments.

## Core Components of the PAC Manager

The architecture follows a clear separation of concerns between script generation, network serving, and system integration. Three primary components handle these responsibilities.

### PacManager Singleton

The **`PacManager`** class in [`ServiceLib/Manager/PacManager.cs`](https://github.com/2dust/v2rayN/blob/main/ServiceLib/Manager/PacManager.cs) acts as the central coordinator. As a singleton (`PacManager.Instance`), it manages the entire lifecycle of the PAC service:

*   **Script generation**: Reads template files and injects the correct proxy address.
*   **HTTP serving**: Hosts a `TcpListener` on the configured PAC port.
*   **State management**: Handles start, stop, and restart operations when ports change.

This singleton ensures only one HTTP listener runs at a time, preventing port conflicts.

### SysProxyHandler Bridge

The **`SysProxyHandler`** in [`ServiceLib/Handler/SysProxy/SysProxyHandler.cs`](https://github.com/2dust/v2rayN/blob/main/ServiceLib/Handler/SysProxy/SysProxyHandler.cs) detects when users select PAC mode and invokes the manager. When the proxy type changes to `ESysProxyType.Pac`, it calls `SetWindowsProxyPac`, which retrieves the PAC port and starts the manager:

```csharp
// SysProxyHandler.cs – lines 91-96
var portPac = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);
await PacManager.Instance.StartAsync(port, portPac);
var strProxy = $"{Global.HttpProtocol}{Global.Loopback}:{portPac}/pac?t={DateTime.Now.Ticks}";
ProxySettingWindows.SetProxy(strProxy, "", 4);

```

This bridges the UI selection with the underlying TCP server implementation.

### Configuration Layer

User preferences flow through **`OptionSettingViewModel`** ([`ServiceLib/ViewModels/OptionSettingViewModel.cs`](https://github.com/2dust/v2rayN/blob/main/ServiceLib/ViewModels/OptionSettingViewModel.cs)), which exposes the `CustomSystemProxyPacPath` property. This path persists in **`ConfigItems`** ([`ServiceLib/Models/ConfigItems.cs`](https://github.com/2dust/v2rayN/blob/main/ServiceLib/Models/ConfigItems.cs)) under `SystemProxyItem.CustomSystemProxyPacPath`, allowing the manager to load custom rules on startup.

## How the PAC Manager Generates and Serves Scripts

The serving pipeline involves three distinct phases: text preparation, HTTP response construction, and TCP delivery.

### Script Generation with InitText

When `StartAsync` is invoked, the manager calls `InitText()` to build the PAC content. The logic in [`PacManager.cs`](https://github.com/2dust/v2rayN/blob/main/PacManager.cs) (lines 33-49) follows this priority:

1.  Check if `CustomSystemProxyPacPath` points to an existing file.
2.  Fall back to [`pac.txt`](https://github.com/2dust/v2rayN/blob/main/pac.txt) in the application config directory.
3.  If neither exists, extract the embedded default from `ServiceLib.Sample.pac` (`Global.PacFileName`).

The method then replaces the placeholder `__PROXY__` with the actual proxy string:

```csharp
// PacManager.cs – simplified from lines 48-49
var proxyString = $"PROXY 127.0.0.1:{httpPort};DIRECT;";
pacText = pacText.Replace("__PROXY__", proxyString);

```

Finally, it wraps the script in a proper HTTP response buffer (`_writeContent`) including headers, avoiding disk I/O during client requests.

### The TCP Listener Architecture

The **`RunListener`** method (lines 60-85) creates a `TcpListener` bound to `127.0.0.1` on the specified PAC port. It runs asynchronously, accepting connections in a loop:

*   Each client connection receives the pre-built `_writeContent` byte array.
*   The server sends the HTTP response immediately without parsing requests.
*   The listener stops gracefully when `Stop()` sets the cancellation token.

This minimalist approach avoids dependencies on external HTTP libraries while ensuring the PAC URL responds instantly when Windows queries it.

### Windows System Proxy Integration

Once the listener is active, `SysProxyHandler` configures Windows to use the local PAC URL:

```

http://127.0.0.1:<pacPort>/pac?t=<timestamp>

```

The timestamp query parameter prevents Windows from caching stale versions of the script. The OS then requests this URL whenever an application needs to determine proxy routing, receiving the JavaScript function that directs traffic to `127.0.0.1:<httpPort>` or bypasses the proxy based on domain rules.

## Custom PAC File Support

The architecture supports advanced users who require custom routing logic beyond the default template. When `PacManager.InitText()` executes, it evaluates:

```csharp
// PacManager.cs – line 33-36
var customSystemProxyPacPath = AppManager.Instance.Config.SystemProxyItem?.CustomSystemProxyPacPath;
var fileName = (customSystemProxyPacPath.IsNotEmpty() && File.Exists(customSystemProxyPacPath))
    ? customSystemProxyPacPath
    : Path.Combine(Utils.GetConfigPath(), "pac.txt");

```

Users can specify a custom file path through the UI ([`OptionSettingWindow.xaml.cs`](https://github.com/2dust/v2rayN/blob/main/OptionSettingWindow.xaml.cs), lines 218-226), which updates `Config.SystemProxyItem.CustomSystemProxyPacPath`. The manager reloads this file every time `StartAsync` is called, ensuring changes take effect immediately when restarting PAC mode.

## Programmatic Usage Example

Developers extending v2rayN functionality can enable PAC mode programmatically using the same interfaces as the UI:

```csharp
// Equivalent to selecting "PAC mode" in the UI
int httpPort = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
int pacPort  = AppManager.Instance.GetLocalPort(EInboundProtocol.pac);

// Initialize the PAC server
await PacManager.Instance.StartAsync(httpPort, pacPort);

// Register with Windows system proxy
string pacUrl = $"{Global.HttpProtocol}{Global.Loopback}:{pacPort}/pac?t={DateTime.Now.Ticks}";
ProxySettingWindows.SetProxy(pacUrl, "", 4);

```

This pattern, extracted directly from `SysProxyHandler.SetWindowsProxyPac`, demonstrates the correct sequence for activating the PAC manager outside the standard view model flow.

## Summary

*   **Singleton Design**: `PacManager` maintains a single `TcpListener` instance to prevent port conflicts and ensure thread-safe operations.
*   **Dynamic Generation**: The `InitText` method replaces the `__PROXY__` placeholder with the current HTTP port and caches the HTTP response bytes for zero-copy serving.
*   **Modular Integration**: `SysProxyHandler` decouples UI proxy mode selection from the networking implementation, while `OptionSettingViewModel` manages custom file persistence.
*   **Windows-Specific**: The architecture targets Windows through `ProxySettingWindows.SetProxy`, utilizing the system's built-in PAC URL support.
*   **Lightweight Serving**: The raw `TcpListener` implementation minimizes dependencies while delivering the PAC script to the OS on demand.

## Frequently Asked Questions

### How does v2rayN update the PAC script when proxy ports change?

The `PacManager` detects port changes through the `_needRestart` flag inside `StartAsync`. When invoked with different ports than the current listener uses, it calls `Stop()` to terminate the existing `TcpListener`, regenerates the script text with `InitText()` to update the `__PROXY__` placeholder, and launches a new listener on the updated port via `RunListener()`.

### Can I use a custom PAC file instead of the default template?

Yes. Place your custom JavaScript PAC file anywhere on disk, then set the path in **Settings → Option Settings → System Proxy → Custom PAC file path**. The `OptionSettingViewModel` persists this to `Config.SystemProxyItem.CustomSystemProxyPacPath`, and `PacManager.InitText()` prioritizes this file over the embedded default (`Global.PacFileName`) or the default [`pac.txt`](https://github.com/2dust/v2rayN/blob/main/pac.txt).

### Why does the PAC URL include a timestamp parameter?

The timestamp (`?t={DateTime.Now.Ticks}`) appended in `SysProxyHandler.SetWindowsProxyPac` acts as a cache-busting mechanism. Windows caches PAC scripts aggressively; the unique query string forces the operating system to fetch a fresh copy from the local `TcpListener` whenever PAC mode is re-enabled or ports change.

### Is the PAC manager available on Linux or macOS?

Currently, the automatic PAC injection through `ProxySettingWindows.SetProxy` is Windows-specific. While the `PacManager` itself (the `TcpListener` and script generation) is cross-platform capable, the system proxy integration logic in `SysProxyHandler` only implements Windows registry updates for PAC URLs. Linux and macOS users typically configure system proxies manually or use alternative routing methods.