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

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 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 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:

// 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), which exposes the CustomSystemProxyPacPath property. This path persists in ConfigItems (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 (lines 33-49) follows this priority:

  1. Check if CustomSystemProxyPacPath points to an existing file.
  2. Fall back to 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:

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

// 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, 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:

// 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.

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.

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 →