How v2rayN Implements System Proxy Handling Across Windows, Linux, and macOS
v2rayN routes system proxy configuration through a platform-specific abstraction layer where Windows uses native WinAPI calls, while Linux and macOS execute bundled Bash scripts that interface with gsettings, kwriteconfig, and networksetup respectively.
The open-source proxy client v2rayN (2dust/v2rayN) implements a unified cross-platform system for managing OS-level proxy settings directly from its .NET-based ServiceLib architecture. Rather than relying on external dependencies or platform-specific libraries, the application embeds OS-specific logic that enables seamless switching between global proxy, PAC (Proxy Auto-Config), and direct connection modes across all supported operating systems.
The Orchestration Layer: SysProxyHandler
The static class SysProxyHandler serves as the central dispatcher for all system proxy operations in v2rayN/ServiceLib/Handler/SysProxy/SysProxyHandler.cs. When the user toggles system proxy settings or the application starts, the UpdateSysProxy method reads the desired configuration from Config.SystemProxyItem.SysProxyType and routes execution to the appropriate platform implementation.
public static async Task<bool> UpdateSysProxy(Config config, bool forceDisable)
{
var type = config.SystemProxyItem.SysProxyType;
if (forceDisable && type != ESysProxyType.Unchanged)
type = ESysProxyType.ForcedClear;
var port = AppManager.Instance.GetLocalPort(EInboundProtocol.socks);
var exceptions = config.SystemProxyItem.SystemProxyExceptions.Replace(" ", "");
switch (type)
{
case ESysProxyType.ForcedChange when Utils.IsWindows():
GetWindowsProxyString(config, port, out var strProxy, out var strExceptions);
ProxySettingWindows.SetProxy(strProxy, strExceptions, 2);
break;
case ESysProxyType.ForcedChange when Utils.IsLinux():
await ProxySettingLinux.SetProxy(Global.Loopback, port, exceptions);
break;
case ESysProxyType.ForcedChange when Utils.IsMacOS():
await ProxySettingOSX.SetProxy(Global.Loopback, port, exceptions);
break;
case ESysProxyType.ForcedClear when Utils.IsWindows():
ProxySettingWindows.UnsetProxy();
break;
case ESysProxyType.ForcedClear when Utils.IsLinux():
await ProxySettingLinux.UnsetProxy();
break;
case ESysProxyType.ForcedClear when Utils.IsMacOS():
await ProxySettingOSX.UnsetProxy();
break;
case ESysProxyType.Pac when Utils.IsWindows():
await SetWindowsProxyPac(port);
break;
}
return true;
}
The method uses the ESysProxyType enum (ForcedChange, ForcedClear, Pac, Unchanged) combined with runtime platform detection via Utils.IsWindows(), Utils.IsLinux(), and Utils.IsMacOS() to determine which helper class receives the command.
Windows Implementation: Native WinAPI and Registry
On Windows, v2rayN interfaces directly with the operating system through the InternetSetOption WinAPI function rather than modifying the registry manually. The ProxySettingWindows class in v2rayN/ServiceLib/Handler/SysProxy/ProxySettingWindows.cs constructs InternetPerConnOption structures to configure both LAN and dial-up (RAS) connections.
public static bool SetProxy(string? strProxy, string? exceptions, int type)
{
// Set LAN connection
var result = SetConnectionProxy(null, strProxy, exceptions, type);
// Set all RAS (dial-up) connections
foreach (var connection in EnumerateRasEntries())
result |= SetConnectionProxy(connection, strProxy, exceptions, type);
return result;
}
The native invocation passes the configuration directly to Windows' internet settings:
var isSuccess = NativeMethods.InternetSetOption(
nint.Zero,
InternetOption.INTERNET_OPTION_PER_CONNECTION_OPTION,
ipcoListPtr, list.dwSize);
For PAC mode on Windows, v2rayN starts a local HTTP server via PacManager and writes the PAC URL into the AutoConfigURL registry value using type 4 in the proxy configuration structure. This enables automatic proxy script updates without requiring manual file path configuration.
Linux Implementation: GNOME/KDE Script Integration
Linux systems do not expose a unified system proxy API, so v2rayN ships with an embedded Bash script proxy_set_linux_sh located in v2rayN/ServiceLib/Sample/. The ProxySettingLinux class extracts this script to a temporary location and executes it with arguments specifying the proxy mode, loopback address (127.0.0.1), port, and exception list.
public static async Task SetProxy(string host, int port, string exceptions)
{
List<string> args = ["manual", host, port.ToString(), exceptions];
await ExecCmd(args);
}
The execution logic checks for a user-provided custom script before falling back to the embedded resource:
var fileName = (customSystemProxyScriptPath.IsNotEmpty() && File.Exists(customSystemProxyScriptPath))
? customSystemProxyScriptPath
: await FileUtils.CreateLinuxShellFile(_proxySetFileName,
EmbedUtils.GetEmbedText(Global.ProxySetLinuxShellFileName), false);
await Utils.GetCliWrapOutput(fileName, args);
The bundled script configures GNOME via gsettings and KDE via kwriteconfig5, setting the proxy mode to manual and applying the SOCKS host and port to both HTTP and HTTPS proxy settings while preserving user-defined bypass domains.
macOS Implementation: Networksetup Script Execution
macOS follows a similar shell-script approach but utilizes the networksetup command-line tool through the proxy_set_osx_sh script. The ProxySettingOSX class in v2rayN/ServiceLib/Handler/SysProxy/ProxySettingOSX.cs builds argument lists for "set" or "clear" operations and passes them to the script executor.
public static async Task SetProxy(string host, int port, string exceptions)
{
List<string> args = ["set", host, port.ToString()];
if (exceptions.IsNotEmpty())
args.AddRange(exceptions.Split(','));
await ExecCmd(args);
}
The macOS script iterates through all network services reported by networksetup -listallnetworkservices and applies the proxy configuration to each active interface:
networksetup -setwebproxy "$SERVICE" "$PROXY_IP" "$PROXY_PORT"
networksetup -setsecurewebproxy "$SERVICE" "$PROXY_IP" "$PROXY_PORT"
networksetup -setsocksfirewallproxy "$SERVICE" "$PROXY_IP" "$PROXY_PORT"
networksetup -setproxybypassdomains "$SERVICE" "${BYPASS_DOMAINS[@]}"
This ensures that HTTP, HTTPS, and SOCKS traffic are all routed through the local v2rayN listener regardless of which network adapter is active.
UI Integration and ViewModel State
User interactions flow through StatusBarViewModel in v2rayN/ServiceLib/ViewModels/StatusBarViewModel.cs, where commands like SystemProxySetCmd and SystemProxyClearCmd trigger ChangeSystemProxyAsync. This method updates the BlSystemProxySet and BlSystemProxyClear boolean flags that control the visual state of the system tray menu, then delegates to SysProxyHandler.
private async Task ChangeSystemProxyAsync(ESysProxyType type, bool blChange)
{
await SysProxyHandler.UpdateSysProxy(_config, false);
BlSystemProxyClear = type == ESysProxyType.ForcedClear;
BlSystemProxySet = type == ESysProxyType.ForcedChange;
}
On application startup, the view model automatically applies the last saved proxy configuration by calling ChangeSystemProxyAsync(_config.SystemProxyItem.SysProxyType, true), ensuring the system state matches the persisted user preferences.
Practical Implementation Examples
Manually Triggering System Proxy Changes
Developers integrating with v2rayN's core library can programmatically toggle the system proxy:
// Retrieve current configuration from AppManager
var config = AppManager.Instance.Config;
// Force enable system proxy on current platform
await SysProxyHandler.UpdateSysProxy(config, forceDisable: false);
// Force disable system proxy
config.SystemProxyItem.SysProxyType = ESysProxyType.ForcedClear;
await SysProxyHandler.UpdateSysProxy(config, forceDisable: true);
Using Custom Proxy Scripts on Linux/macOS
Advanced users can bypass the bundled scripts by specifying a custom executable path in the configuration:
// Configure custom script path
config.SystemProxyItem.CustomSystemProxyScriptPath = "/home/user/custom_proxy.sh";
// The handler will execute this script instead of the embedded resource
await SysProxyHandler.UpdateSysProxy(config, false);
The custom script must accept the same command-line arguments: manual <host> <port> [exceptions] for setting, or clear for disabling the proxy.
Summary
- Centralized Dispatch:
SysProxyHandler.UpdateSysProxyorchestrates all platform-specific proxy operations based on theESysProxyTypeenum and runtime OS detection. - Windows Native API: Uses
InternetSetOptionwithINTERNET_PER_CONN_OPTIONstructures for direct system configuration, supporting both LAN and dial-up connections. - Linux Shell Integration: Executes embedded Bash scripts that configure GNOME (
gsettings) and KDE (kwriteconfig5) desktop environments. - macOS Network Setup: Leverages
networksetupcommands applied to all network services for HTTP, HTTPS, and SOCKS proxy configuration. - Customization Support: Both Linux and macOS implementations support user-provided custom scripts via
CustomSystemProxyScriptPath. - UI State Synchronization:
StatusBarViewModelmaintains boolean flags (BlSystemProxySet,BlSystemProxyClear) that reflect the current system proxy state in the user interface.
Frequently Asked Questions
How does v2rayN handle system proxy on Windows without requiring administrative privileges?
v2rayN uses the WinAPI InternetSetOption function to modify per-connection internet settings rather than writing directly to protected registry keys. This approach modifies user-specific internet settings that reside in the user profile hive (HKEY_CURRENT_USER), which does not require elevated privileges for modifications, unlike system-wide registry changes.
Can users customize the proxy configuration scripts on Linux and macOS?
Yes, users can specify a custom system proxy script by setting the CustomSystemProxyScriptPath property in the configuration. When this path points to an existing executable file, ProxySettingLinux and ProxySettingOSX will execute this user-provided script instead of extracting and running the bundled proxy_set_linux_sh or proxy_set_osx_sh scripts.
Why does v2rayN use shell scripts instead of native APIs on Linux and macOS?
Linux desktop environments lack a unified system proxy API—GNOME uses gsettings, KDE uses kwriteconfig, and other distributions may use different mechanisms. Similarly, macOS requires different approaches for each network service. Shell scripts provide a flexible abstraction that can detect the current environment and apply the appropriate commands without requiring v2rayN to link against platform-specific libraries or frameworks.
How does v2rayN manage PAC (Proxy Auto-Config) mode differently on Windows compared to Linux and macOS?
On Windows, v2rayN implements PAC mode by starting a local HTTP server (PacManager) that serves the PAC file, then registers this local URL (http://127.0.0.1:<port>/pac) in the Windows internet settings as the AutoConfigURL. On Linux and macOS, the current implementation focuses on manual proxy configuration via shell scripts, and users requiring PAC functionality typically configure the PAC URL manually in their system settings or use the global proxy mode with bypass lists.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →