Skip to content

feat(native-proxy): OS proxy configuration (Windows + macOS + Linux) - #427

Draft
kdroidFilter wants to merge 6 commits into
mainfrom
feat/native-proxy
Draft

feat(native-proxy): OS proxy configuration (Windows + macOS + Linux)#427
kdroidFilter wants to merge 6 commits into
mainfrom
feat/native-proxy

Conversation

@kdroidFilter

@kdroidFilter kdroidFilter commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

What

New native-proxy module — the proxy counterpart to native-ssl: it exposes the
OS proxy configuration to JVM desktop apps on Windows, macOS and Linux.

The implementation follows Chromium's proxy stack so the effective proxy matches
what Edge/Chrome/Safari resolve, including machine-wide and managed settings.

Platform Chromium equivalent Native surface
Windows ProxyConfigServiceWin + ProxyResolverWinHttp WinHTTP + Internet Settings registry watchers
macOS ProxyConfigServiceMac + ProxyResolverApple SCDynamicStore + CFNetworkExecuteProxyAutoConfigurationURL
Linux ProxyConfigServiceLinux GSettings (org.gnome.system.proxy) → KDE kioslaverc → env vars
Piece Chromium equivalent
SystemProxySettings (autoDetect / pacUrl / rules / bypass) net::ProxyConfig
ProxyRules — single list vs per-scheme (http=…;https=…;socks=…), socks= defaults to SOCKS4 and serves unlisted schemes ProxyConfig::ProxyRules::ParseFromString
ProxyBypassRules / BypassRule — glob hostname patterns, leading . rewritten to *., :port and scheme:// restrictions, CIDR blocks, <local>, <-loopback>, implicit localhost + link-local bypass net::ProxyBypassRules
PAC (Windows): explicit script URL wins over WPAD (DHCP + DNS_A); fAutoLogonIfChallenged=FALSE first, retried TRUE only on ERROR_WINHTTP_LOGIN_FAILURE ProxyResolverWinHttp
PAC (macOS): CFNetworkExecuteProxyAutoConfigurationURL on a private CFRunLoop mode (10 s timeout) ProxyResolverApple
Change detection (Windows): RegNotifyChangeKeyValue on the 6 HKCU/HKLM Internet Settings + Policies + Connections keys, 2 s coalescing delay ProxyConfigServiceWin
Change detection (macOS): SCDynamicStore proxy-key notifications, 2 s coalescing delay ProxyConfigServiceMac
Change detection (Linux): polls the desktop/env configuration (GSettings D-Bus signals need a process-default GMainContext the JVM does not drive)

Native bridges

  • Windowsnucleus_proxy.dll (x64 + ARM64), /NODEFAULTLIB CRT-free like native-ssl
    • nativeGetProxyConfig()WinHttpGetIEProxyConfigForCurrentUser as 4 strings
    • nativeResolveProxyForUrl(url, pacUrl) → proxy list, "" for DIRECT, null on failure
    • nativeWaitForConfigChange / nativeWakeWatcher — blocking registry wait (no native callbacks, no polling)
  • macOSlibnucleus_proxy.dylib (arm64 + x86_64)
    • nativeGetProxyConfig()SCDynamicStoreCopyProxies as the same 4-string layout
    • nativeResolveProxyForUrl(url, pacUrl) → PAC via CFNetwork
    • nativeWaitForConfigChange / nativeWakeWatcherSCDynamicStore on the current CFRunLoop
  • Linuxlibnucleus_proxy.so (x64 + aarch64), GIO resolved with dlopen (no hard libgio link)
    • GNOME-like GSettings path in native; KDE kioslaverc + http_proxy/no_proxy/… env fallback in Kotlin

The 4-string config array is shared across platforms so Kotlin feeds the same
ProxyRules / ProxyBypassRules parsers:

Index Content
0 WinInet-style proxy string (http=…;https=…;socks=…)
1 Bypass list (;-joined; macOS appends <local> when ExcludeSimpleHostnames is set)
2 PAC script URL
3 WPAD flag ("1" / "0")

API

NativeProxy.install()                        // OS proxy as the JVM-wide ProxySelector
NativeProxy.proxiesFor(URI("https://intranet.corp"))
NativeProxy.addChangeListener { settings -> /**/ }

install() keeps the previous default selector as a fallback, so the JDK
http.proxyHost properties keep working for URIs the OS has no opinion on.
Resolution order per URL: bypass list → PAC script → static rules (the
latter also being the fallback when the script cannot be evaluated). PAC
results are cached per origin and dropped on every configuration change.

Platform caveats

  • Linux does not evaluate PAC/WPAD scripts (no WinHTTP/CFNetwork equivalent
    without embedding a JS engine) — only static rules and bypass lists.
  • macOS pure WPAD without an explicit PAC URL is reported via autoDetect
    but not evaluated: when DHCP WPAD succeeds, Apple embeds the discovered PAC
    URL into the system settings, so an empty pacUrl almost always means there
    is nothing to run.

Verification

  • :native-proxy:check green (detekt + ktlint + unit tests for the parsers,
    bypass matcher, env/KDE helpers, and per-platform load/read smoke tests)
  • Windows — both DLLs build with MSVC (x64 + ARM64); live box: config read,
    localhost → DIRECT, registry write detected by the watcher, nativeWakeWatcher
    releases the parked thread immediately
  • macOS — both dylibs build with clang; live check against scutil --proxy:
    HTTP/HTTPS/SOCKS hosts match, loopback stays DIRECT, install() wires the
    JVM selector
  • Linux.so builds on x64/aarch64; GSettings/KDE/env unit tests +
    optional E2E via NUCLEUS_PROXY_E2E=true

Follow-ups (not in this PR)

  • Wiring into native-http / native-http-okhttp / native-http-ktor
  • Demo app integration
  • Linux PAC evaluation (would need a JS engine or an external helper)

New `native-proxy` module exposing the OS proxy configuration, modelled on
`native-ssl` and ported from Chromium's `net::ProxyConfigServiceWin` and
`net::ProxyResolverWinHttp`:

- `WinHttpGetIEProxyConfigForCurrentUser` for the effective configuration
  (WPAD flag, PAC URL, proxy string, bypass list)
- `WinHttpGetProxyForUrl` for PAC/WPAD resolution, without auto-logon first
  and retried with it only on ERROR_WINHTTP_LOGIN_FAILURE
- `RegNotifyChangeKeyValue` on the Internet Settings / Policies / Connections
  keys for change notification, with Chromium's 2 s coalescing delay
- Chromium-compatible parsing of per-scheme proxy strings and bypass rules
  (glob patterns, CIDR blocks, `<local>`, `<-loopback>`, implicit loopback)

`NativeProxy.install()` publishes a `ProxySelector` backed by the OS
configuration, keeping the previous default as fallback. macOS and Linux use a
no-op provider: `isSupported` is false and everything degrades to direct.
Port Chromium's ProxyConfigServiceLinux resolution order:

- GNOME-like: org.gnome.system.proxy via dlopen'd libgio
- KDE: kioslaverc under ~/.config and XDG_CONFIG_DIRS
- Fallback: http_proxy / https_proxy / all_proxy / no_proxy / SOCKS_SERVER

PAC/WPAD is reported but not evaluated (no WinHTTP equivalent). Change
detection polls the configuration; GSettings D-Bus signals require a
process-default GMainContext the JVM does not drive.
@kdroidFilter kdroidFilter changed the title feat(native-proxy): OS proxy configuration module (Windows) feat(native-proxy): OS proxy configuration (Windows + Linux) Aug 6, 2026
InterlockedCompareExchangePointer is not a freestanding intrinsic on ARM64
MSVC and pulls an unresolved _InterlockedCompareExchangePointer under
/NODEFAULTLIB. Use InitOnceExecuteOnce (kernel32) for the WinHTTP session
and wake-event singletons, and fail the ARM64 link step hard when it errors.
Port Chromium's ProxyConfigServiceMac / ProxyResolverApple:

- SCDynamicStoreCopyProxies for the effective configuration (per-scheme
  HTTP/HTTPS/FTP/SOCKS, ExceptionsList, ExcludeSimpleHostnames, PAC URL,
  ProxyAutoDiscoveryEnable)
- CFNetworkExecuteProxyAutoConfigurationURL for PAC evaluation, pumping a
  private CFRunLoop mode so the call stays synchronous from the JVM
- SCDynamicStore notification keys for change detection (no polling)

Verified on a Mac with a live HTTP/HTTPS/SOCKS proxy: config matches
`scutil --proxy`, loopback stays DIRECT, install() wires the JVM selector.
@kdroidFilter kdroidFilter changed the title feat(native-proxy): OS proxy configuration (Windows + Linux) feat(native-proxy): OS proxy configuration (Windows + macOS + Linux) Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant