Skip to content

[Android] Bridge SocketsHttpHandler to java.net.ProxySelector#128031

Draft
simonrozsival wants to merge 5 commits into
dotnet:mainfrom
simonrozsival:dev/simonrozsival/android-platform-proxy
Draft

[Android] Bridge SocketsHttpHandler to java.net.ProxySelector#128031
simonrozsival wants to merge 5 commits into
dotnet:mainfrom
simonrozsival:dev/simonrozsival/android-platform-proxy

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

Note

This pull request (description and code) was drafted with the assistance of GitHub Copilot CLI.

Summary

Adds an AndroidPlatformProxy : IWebProxy that resolves the system proxy chain for SocketsHttpHandler on Android by JNI-calling java.net.ProxySelector.getDefault().select(URI.create(url)).

This brings the Android system proxy — Wi-Fi proxy, MDM-deployed proxy, PAC scripts, per-network and VPN-attached ProxyInfo — to the managed HTTP stack, matching the behavior HttpURLConnection / AndroidMessageHandler exhibit today. Closes the largest behavioral regression apps would face when flipping the default HttpClientHandler backend from AndroidMessageHandler to SocketsHttpHandler on net*-android* (SystemProxyInfo.Unix.cs currently honors only env vars on Android).

Implementation lives entirely in dotnet/runtime — no dependency on dotnet/android, no [UnsafeAccessor] into Mono.Android.

Background

HttpClient.DefaultProxy on Android currently resolves to HttpEnvironmentProxy (env-var only) via the shared Unix path:

runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SystemProxyInfo.Unix.cs

In contrast, java.net.HttpURLConnection (and therefore AndroidMessageHandler) defers to java.net.ProxySelector.getDefault(), which on Android consults system properties, ConnectivityManager.getDefaultProxy(), per-network LinkProperties.getHttpProxy(), and PAC scripts (via the privileged PacProxyService). All of that is silently lost when an app switches to SocketsHttpHandler today.

What this PR changes

Native PAL (System.Security.Cryptography.Native.Android)

  • pal_proxy.{h,c}AndroidCryptoNative_GetProxyForUrl / AndroidCryptoNative_FreeProxyResult.
    • Pure C/JNI; no .java helper (we're just calling APIs, not implementing a Java interface).
    • Uses the existing pal_jni.h macros (INIT_LOCALS, RELEASE_LOCALS, ON_EXCEPTION_PRINT_AND_GOTO) for leak-safe local-ref handling on every exception path.
    • Per-iteration INIT_LOCALS(iter, …) + RELEASE_LOCALS on every continue to prevent local-ref-table overflow on large proxy lists.
  • pal_jni.{h,c} — global refs added for java.net.ProxySelector, Proxy, Proxy$Type (HTTP/SOCKS jfieldIDs), InetSocketAddress, URI, java.util.List. All populated once in InitializeJObjectsForPal.
  • pal_jni.{h,c} — promotes AllocateString (previously private to pal_sslstream.c) into a shared PAL helper. It returns a NUL-terminated UTF-16 buffer that managed code reads via Marshal.PtrToStringUni (zero-conversion memcpy: both Java and .NET strings are internally UTF-16). pal_sslstream.c is updated to use the shared helper.
  • CMakeLists.txtpal_proxy.c added to NATIVECRYPTO_SOURCES.

Managed (System.Net.Http)

  • AndroidPlatformProxy.Android.csIWebProxy implementation (same shape as MacProxy).
    • GetProxy(uri) P/Invokes the PAL, takes the first non-DIRECT entry, falls back to direct on any failure.
    • IsBypassed(_) => false (matches HttpWindowsProxy.cs:349–360 rationale: computing the real answer costs the same as GetProxy, so we let SHH call GetProxy exactly once per pool key).
    • Credentials exists (required by IWebProxy) but is never populated by us — Android's proxy APIs do not surface credentials. Users authenticate via HttpClientHandler.DefaultProxyCredentials, the same as on macOS (MacProxy has the same limitation tracked as Use system proxy configuration on macOS #24799).
  • SystemProxyInfo.Android.cs — env vars first (HttpEnvironmentProxy), then AndroidPlatformProxy, then HttpNoProxy. Gated by a new FeatureSwitchDefinition("System.Net.Http.UseAndroidSystemProxy") with default true.
  • Interop.Proxy.cs[LibraryImport] declarations.
  • System.Net.Http.csproj — new <ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'android'"> adds the three managed files and the Android Interop.Libraries.cs. The existing fall-through ItemGroup is updated to exclude Android.

Feature switch: System.Net.Http.UseAndroidSystemProxy

Defaults to true. Apps can opt out via <RuntimeHostConfigurationOption> (or by setting the property name in runtimeconfig.json):

<RuntimeHostConfigurationOption
    Include="System.Net.Http.UseAndroidSystemProxy"
    Value="false" Trim="true" />

Three legitimate reasons to disable:

  1. Back-compat with apps that previously ran on SocketsHttpHandler and got env-var-only behavior — honoring the system proxy could change which server their HTTP requests reach.
  2. Trimming — the linker can trim AndroidPlatformProxy and the P/Invokes when set to false at publish time.
  3. Test/debug isolation where pure env-var behavior is required.

SOCKS support

ProxySelector may return Proxy.Type.SOCKS (transport-level proxy protocol per RFC 1928; tunnels arbitrary TCP at the socket layer). On modern Android Proxy.Type.SOCKS maps to SOCKS5. We translate to a socks5://host:port URI; SocketsHttpHandler accepts that scheme via HttpUtilities.IsSupportedProxyScheme.

Verification

  • ./build.sh libs.native -os android -arch arm64 — 0 warnings, 0 errors.
  • dotnet build src/libraries/System.Net.Http/src/System.Net.Http.csproj /p:TargetOS=android /p:TargetArchitecture=arm64 — 0 warnings, 0 errors across all RIDs (also confirmed pal_sslstream.c still links cleanly after moving AllocateString out).
  • nm -D libSystem.Security.Cryptography.Native.Android.so confirms both AndroidCryptoNative_GetProxyForUrl and AndroidCryptoNative_FreeProxyResult are exported.

What this PR does NOT do (follow-ups)

  • No tests yet. A device-test APK modeled on the AndroidPlatformTrustTests from [Android] Respect platform trust manager in SslStream #124173 is the right follow-up. The test matrix should cover: Wi-Fi system proxy honored, Settings.Global.HTTP_PROXY honored, PAC script evaluated, no-proxy fall-through, bypass list, UseProxy=false skips, explicit handler Proxy overrides, DefaultProxyCredentials flows to Proxy-Authorization, feature switch off.
  • No lib rename. The new code lives in System.Security.Cryptography.Native.Android even though the lib's scope is now broader than crypto. Renaming it (e.g. System.Net.Native.Android) is a separate, mechanical refactor.
  • pal_eckey.c and pal_x509chain.c still have inline duplicates of the AllocateString pattern. Hoisting them to use the shared helper is a separate cleanup.
  • No MAUI/Android SDK MSBuild wiring for a per-app $(UseAndroidSystemProxy) property. The runtime config switch can be set explicitly today via <RuntimeHostConfigurationOption>; SDK-level wiring can come in dotnet/android.

Microsoft Reviewers: Open in CodeFlow

simonrozsival and others added 2 commits May 11, 2026 13:17
Adds an AndroidPlatformProxy : IWebProxy that resolves the system
proxy chain for SocketsHttpHandler on Android by JNI-calling
java.net.ProxySelector.getDefault().select(URI.create(url)).

This brings the Android system proxy (Wi-Fi proxy, MDM-deployed proxy,
PAC scripts, per-network and VPN ProxyInfo) to the managed HTTP stack,
matching the behavior HttpURLConnection / AndroidMessageHandler exhibit
today. Closes the largest compatibility regression apps would face when
flipping the default HttpClientHandler backend from AndroidMessageHandler
to SocketsHttpHandler on net*-android*.

Implementation lives entirely in dotnet/runtime — no dependency on
dotnet/android, no [UnsafeAccessor] into Mono.Android.

Native side (System.Security.Cryptography.Native.Android):
  * pal_proxy.{h,c}: AndroidCryptoNative_GetProxyForUrl /
    AndroidCryptoNative_FreeProxyResult. Pure C/JNI using the
    existing pal_jni.h INIT_LOCALS / RELEASE_LOCALS /
    ON_EXCEPTION_PRINT_AND_GOTO macros for leak-safe local-ref
    handling on all exception paths.
  * pal_jni.{h,c}: cached jclass/jmethodID/jfieldID globals for
    java.net.ProxySelector, Proxy, Proxy$Type, InetSocketAddress,
    URI, and java.util.List.
  * CMakeLists.txt: pal_proxy.c added to NATIVECRYPTO_SOURCES.

Managed side (System.Net.Http):
  * AndroidPlatformProxy.Android.cs: IWebProxy implementation that
    P/Invokes the PAL, takes the first non-DIRECT entry, and falls
    back to direct on any failure. Mirrors MacProxy in shape.
  * SystemProxyInfo.Android.cs: env vars first
    (HttpEnvironmentProxy), then AndroidPlatformProxy, then
    HttpNoProxy. Gated by the FeatureSwitchDefinition
    System.Net.Http.UseAndroidSystemProxy (default true).
  * Interop.Proxy.cs: [LibraryImport] declarations.
  * csproj: new ItemGroup for TargetPlatformIdentifier=android that
    replaces SystemProxyInfo.Unix.cs with the Android-specific files.

Credentials note: AndroidPlatformProxy.Credentials is required by the
IWebProxy contract but is never populated by this class — Android's
proxy APIs do not surface credentials. Users authenticate to system
proxies via HttpClientHandler.DefaultProxyCredentials, the same as on
macOS (MacProxy has the same limitation, tracked as dotnet#24799).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* IsBypassed: always return false (HttpWindowsProxy:349–360 pattern).
  The previous double-call-into-GetProxy was wasteful — SHH's contract
  is 'IsBypassed first; if false, call GetProxy', so returning false
  unconditionally results in exactly one JNI round-trip per origin.

* Return hostnames as UTF-16, not modified UTF-8. Promotes the existing
  pal_sslstream.c::AllocateString helper to a shared pal_jni helper
  (pal_eckey.c and pal_x509chain.c also follow this UTF-16 pattern).
  Managed side reads via Marshal.PtrToStringUni which is a
  zero-conversion copy because System.String is internally UTF-16;
  modified UTF-8 from GetStringUTFRegion would have required an
  Encoding.UTF8.GetString allocation and was not standard UTF-8 anyway.

* Document the SOCKS5 transport-level proxy choice inline.

* Document why UseAndroidSystemProxy is a feature switch (back-compat
  for apps previously on SocketsHttpHandler, trimming, testing).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @karelz, @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

@simonrozsival simonrozsival marked this pull request as draft May 11, 2026 11:52
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Android-specific system proxy support for SocketsHttpHandler by introducing a managed AndroidPlatformProxy : IWebProxy backed by a new native PAL entrypoint that queries java.net.ProxySelector via JNI, and wires it into SystemProxyInfo with a feature switch.

Changes:

  • Add native JNI/PAL support (AndroidCryptoNative_GetProxyForUrl / ...FreeProxyResult) to query ProxySelector.select(URI), returning HTTP/SOCKS proxy entries.
  • Add managed AndroidPlatformProxy and Android-specific SystemProxyInfo implementation to prefer env-var proxy first, then Android platform proxy (feature-switchable).
  • Update build glue (CMake + System.Net.Http.csproj) and add Android interop declarations for the new PAL exports.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/native/libs/System.Security.Cryptography.Native.Android/pal_sslstream.c Removes the file-local AllocateString helper in favor of a shared PAL helper.
src/native/libs/System.Security.Cryptography.Native.Android/pal_proxy.h Defines native proxy result structs/enums and PAL export signatures.
src/native/libs/System.Security.Cryptography.Native.Android/pal_proxy.c Implements ProxySelector-based proxy resolution and result marshalling.
src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.h Adds cached JNI class/method/field IDs for proxy-related Java types and exposes AllocateString.
src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.c Defines/initializes the new JNI caches and provides the shared AllocateString implementation.
src/native/libs/System.Security.Cryptography.Native.Android/CMakeLists.txt Adds pal_proxy.c to the native build sources.
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SystemProxyInfo.Android.cs Android-specific proxy construction order + System.Net.Http.UseAndroidSystemProxy feature switch.
src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/AndroidPlatformProxy.Android.cs Managed IWebProxy implementation calling into the native PAL and mapping to http/socks5 URIs.
src/libraries/System.Net.Http/src/System.Net.Http.csproj Includes Android-only proxy files + Android interop sources; excludes Android from the Unix fallback item group.
src/libraries/Common/src/Interop/Android/System.Security.Cryptography.Native.Android/Interop.Proxy.cs Adds [LibraryImport] declarations and the blittable native result struct.

Comment thread src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.h Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native.Android/pal_jni.c Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native.Android/pal_proxy.c Outdated
- Remove duplicate java.util.List JNI cache (g_List/g_ListSize/g_ListGet);
  reuse existing g_ListClass/g_ListGet and g_CollectionSize.
- pal_proxy: free result and report outProxies = NULL when no proxy entries
  survive filtering (DIRECT-only / unknown types), matching the managed
  contract that count == 0 implies a null buffer.
- AndroidPlatformProxy.GetProxy: always free the native result via try/finally
  whenever proxies != null (defense in depth against the count == 0 case).
- SystemProxyInfo.Android: cache UseAndroidSystemProxy in a static get-only
  property initializer so the runtime switch is read once and can participate
  in trimming / JIT constant propagation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Return Java Proxy.Type.DIRECT entries from the Android proxy PAL and handle them explicitly in AndroidPlatformProxy so ProxySelector ordering is preserved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 27, 2026 10:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Comment thread src/native/libs/System.Security.Cryptography.Native.Android/pal_proxy.h Outdated
Comment thread src/native/libs/System.Security.Cryptography.Native.Android/pal_proxy.c Outdated
Move Android-specific proxy tests to an Android-only test file and document/directly handle Java Proxy.Type.DIRECT semantics while avoiding JNI exception logging during proxy resolution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants