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
<PackageReference Include="VpnHood.Net.Quic.Android" Version="9.1.864" />
<PackageVersion Include="VpnHood.Net.Quic.Android" Version="9.1.864" />
<PackageReference Include="VpnHood.Net.Quic.Android" />
paket add VpnHood.Net.Quic.Android --version 9.1.864
#r "nuget: VpnHood.Net.Quic.Android, 9.1.864"
#:package VpnHood.Net.Quic.Android@9.1.864
#addin nuget:?package=VpnHood.Net.Quic.Android&version=9.1.864
#tool nuget:?package=VpnHood.Net.Quic.Android&version=9.1.864
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:
Reuse
MsQuicClient(System.Net.Quic) + bundlelibmsquic.so.QuicConnection.IsSupporteddoes becometrueoncelibmsquic.sois present, and the QUIC handshake even starts — but the connection then hard-crashes (SIGSEGV) insideSystem.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_X509UpRefRoot cause:
System.Net.Quicvalidates the peer cert with the OpenSSL crypto backend (X509_up_ref,CryptoNative_*), but on AndroidX509Certificate2/X509Chainuse the Android crypto backend (libSystem.Security.Cryptography.Native.Android). The cert handle it hands to OpenSSL is an Android handle, not anX509*— 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 listsSystem.Net.Quicas unsupported on Android.Bundle the OpenSSL crypto shim +
libcrypto/libsslfor Android. Removes theDllNotFoundExceptionbut 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— raisesPEER_CERTIFICATE_RECEIVEDso 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 alertUNKNOWN_CAbefore 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):
- Build
X509Certificate2from the leaf DER. - Add the intermediates from the event's
Chainbuffer toX509Chain.ChainPolicy.ExtraStore(otherwise the chain never builds → falseRemoteCertificateChainErrors). - Compute
SslPolicyErrors:chain.Build(cert)→RemoteCertificateChainErrors;cert.MatchesHostname(TargetHost)→RemoteCertificateNameMismatch. - Call VpnHood's
RemoteCertificateValidationCallback(accepts whenerrors == None, or by pinned hash otherwise). ReturnQUIC_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) atandroid/AndroidNative/. See itsandroid/DEV-GUIDE.mdfor how the.sois 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,
IsQuicSupportedisfalsethere 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 runsAndroidQuicClientagainst 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 | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net11.0-android37.0 is compatible. |
-
net11.0-android37.0
- VpnHood.Net.Quic.MsQuic.AndroidNative (>= 9.0.15)
- VpnHood.Net.Toolkit (>= 9.1.864)
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 |