VpnHood.Net.Quic.Android 9.1.864

dotnet add package VpnHood.Net.Quic.Android --version 9.1.864
                    
NuGet\Install-Package VpnHood.Net.Quic.Android -Version 9.1.864
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="VpnHood.Net.Quic.Android" Version="9.1.864" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="VpnHood.Net.Quic.Android" Version="9.1.864" />
                    
Directory.Packages.props
<PackageReference Include="VpnHood.Net.Quic.Android" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add VpnHood.Net.Quic.Android --version 9.1.864
                    
#r "nuget: VpnHood.Net.Quic.Android, 9.1.864"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package VpnHood.Net.Quic.Android@9.1.864
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=VpnHood.Net.Quic.Android&version=9.1.864
                    
Install as a Cake Addin
#tool nuget:?package=VpnHood.Net.Quic.Android&version=9.1.864
                    
Install as a Cake Tool

VpnHood.Net.Quic.Android

Android implementation of the VpnHood QUIC abstractions (IQuicClient / IQuicConnection / Stream), backed by MsQuic through our own P/Invoke bindings over a bundled libmsquic.so.

Unlike Windows/Linux (VpnHood.Net.Quic.MsQuic), this project does not use System.Net.Quic. It is conceptually the Android sibling of VpnHood.Net.Quic.Ios (which is its own client over Apple Network.framework): each mobile platform needs a native QUIC client because the BCL's QUIC stack does not work there.


Why not System.Net.Quic on Android?

We tried two simpler approaches first; both are dead ends. The history is worth keeping so nobody repeats them:

  1. Reuse MsQuicClient (System.Net.Quic) + bundle libmsquic.so. QuicConnection.IsSupported does become true once libmsquic.so is present, and the QUIC handshake even starts — but the connection then hard-crashes (SIGSEGV) inside System.Net.Quic's certificate validation:

    #00 libcrypto.so  __aarch64_ldadd4_relax
    #01 libcrypto.so  X509_up_ref
    #02 libSystem.Security.Cryptography.Native.OpenSsl.so  CryptoNative_X509UpRef
    

    Root cause: System.Net.Quic validates the peer cert with the OpenSSL crypto backend (X509_up_ref, CryptoNative_*), but on Android X509Certificate2/X509Chain use the Android crypto backend (libSystem.Security.Cryptography.Native.Android). The cert handle it hands to OpenSSL is an Android handle, not an X509* — so it dereferences garbage. This is a fundamental backend split, not fixable by shipping OpenSSL libs (the cert object simply isn't an OpenSSL object). This is exactly why Microsoft lists System.Net.Quic as unsupported on Android.

  2. Bundle the OpenSSL crypto shim + libcrypto/libssl for Android. Removes the DllNotFoundException but not the crash above — same backend split. Abandoned.

The working approach (this project): drive libmsquic directly via P/Invoke and do certificate validation ourselves (msquic performs the TLS handshake with its own statically-linked OpenSSL; we never touch OpenSSL from managed code).


Architecture

Type Role
Interop/MsQuicApi Opens the native QUIC_API_TABLE + one registration once per process (lazy, on the bundled libmsquic.so). IsSupported probes this.
AndroidQuicClient : IQuicClient Opens a configuration (ALPN h3, client credential) + connection, starts it, awaits handshake, returns a connection.
AndroidQuicConnection : IQuicConnection Owns the connection handle; routes the unmanaged connection callback; opens outbound / accepts inbound streams; performs certificate validation.
AndroidQuicStream : Stream An async Stream over one msquic stream: RECEIVE → a Pipe that ReadAsync drains; WriteAsync → StreamSend awaited until SEND_COMPLETE (backpressure).

This project is pure managed code. The native libmsquic.so and the MsQuic C# P/Invoke bindings (Microsoft.Quic namespace) both come from the self-contained package VpnHood.Net.Quic.MsQuic.AndroidNative, which this project references as a NuGet package pinned to one version. That package exposes the Microsoft.Quic binding types as public API, so all native-linking complexity is hidden behind one reference. Unmanaged callbacks here are [UnmanagedCallersOnly(CallConvs=[CallConvCdecl])] statics that resolve the managed instance from a GCHandle passed as the callback context.

Certificate validation (the important part)

The client credential uses:

CLIENT | INDICATE_CERTIFICATE_RECEIVED | DEFER_CERTIFICATE_VALIDATION | USE_PORTABLE_CERTIFICATES
  • INDICATE_CERTIFICATE_RECEIVED — raises PEER_CERTIFICATE_RECEIVED so we can inspect the cert.
  • DEFER_CERTIFICATE_VALIDATION — required. Without it msquic runs its own validation and rejects the (untrusted-on-Android) cert with TLS alert UNKNOWN_CA before our callback runs. With it, our callback's return value decides.
  • USE_PORTABLE_CERTIFICATES — the peer cert + chain arrive as DER (QUIC_BUFFERs), not native handles.

In PEER_CERTIFICATE_RECEIVED we then replicate exactly what SslStream/System.Net.Quic would do, using the Android crypto backend (no OpenSSL):

  1. Build X509Certificate2 from the leaf DER.
  2. Add the intermediates from the event's Chain buffer to X509Chain.ChainPolicy.ExtraStore (otherwise the chain never builds → false RemoteCertificateChainErrors).
  3. Compute SslPolicyErrors: chain.Build(cert) → RemoteCertificateChainErrors; cert.MatchesHostname(TargetHost) → RemoteCertificateNameMismatch.
  4. Call VpnHood's RemoteCertificateValidationCallback (accepts when errors == None, or by pinned hash otherwise). Return QUIC_STATUS_SUCCESS / QUIC_STATUS_BAD_CERTIFICATE.

SNI / connect target

MsQuic.ConnectionStart's ServerName is used as both the connect target and the TLS SNI. We must send the TargetHost as SNI (so the server returns the cert for that name) while connecting to the exact resolved IP. So we set QUIC_PARAM_CONN_REMOTE_ADDRESS to RemoteEndPoint and pass TargetHost as ServerName. (Sending the IP as SNI made the server return its default cert, whose name did not match TargetHost → validation failed.)


Native dependency & build

This project bundles no native libraries itself. Everything native comes from the referenced VpnHood.Net.Quic.MsQuic.AndroidNative package, which ships the prebuilt libmsquic.so per ABI (committed in that package's native/ folder) plus the Microsoft.Quic bindings. msquic statically links its own OpenSSL, so no libcrypto/libssl/.NET-OpenSSL-shim is needed.

  • That package lives in the sibling repo VpnHood.Net.Quic.MsQuic.AndroidNative (a msquic+OpenSSL fork) at android/AndroidNative/. See its android/DEV-GUIDE.md for how the .so is produced (build-android.ps1); the package commits the binary so consumers never build it.
  • The <AndroidNativeLibrary> items in that package flow transitively into any consuming APK (lib/<abi>/libmsquic.so), through this project and on to the app.
  • arm64-v8a, armeabi-v7a and x86_64 are produced (armeabi-v7a since package 8.0.7 — many Android TVs run a 32-bit userland), covering every ABI the apps publish. Should the lib ever be missing for an ABI, IsQuicSupported is false there and the client transparently falls back to TCP.

Wiring: AndroidSocketFactory (which AndroidVpnService hands the client) exposes IsQuicSupported / CreateQuicClient() → AndroidQuicClient. QUIC engages only when the client's ChannelProtocol is Quic and the server advertises a QuicPort (otherwise TCP).


Testing (without the VPN)

A standalone test harness lives in VpnHood.Net.Quic.MsQuic.AndroidNative/android/:

  • quic-test-android/ — minimal APK that runs AndroidQuicClient against a VpnHood.NetTester QUIC echo server.
  • quic-test/ — desktop runner of the same logic (sanity check on Windows).

Run the echo server: nettester /ep <ip>:4040 /server. The client POSTs a ServerConfig (QuicPort, Domain, …) to http://<ip>:4040/config; the per-stream protocol is upSize(Int64) + downSize(Int64) + up bytes, then read down bytes.

To adb install a Debug APK directly, build it self-contained (-p:EmbedAssembliesIntoApk=true) — otherwise Fast Deployment leaves the assemblies off-device and the app aborts at startup (No assemblies found … Fast Deployment).

Product Compatible and additional computed target framework versions.
.NET net11.0-android37.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on VpnHood.Net.Quic.Android:

Package Downloads
VpnHood.Core.Client.Devices.Android

VpnHood client device provider for Android

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
9.1.864 84 11/3/2026
9.0.860 104 10/31/2026
9.0.859.16-prerelease 96 10/30/2026
9.0.857 100 10/20/2026
9.0.856 90 10/19/2026
9.0.855 100 10/13/2026
8.2.853.11-prerelease 104 10/5/2026
8.2.853 136 9/23/2026
8.2.852 119 9/23/2026