VpnHood.Net.Quic.Ios 9.1.874

dotnet add package VpnHood.Net.Quic.Ios --version 9.1.874
                    
NuGet\Install-Package VpnHood.Net.Quic.Ios -Version 9.1.874
                    
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.Ios" Version="9.1.874" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="VpnHood.Net.Quic.Ios" Version="9.1.874" />
                    
Directory.Packages.props
<PackageReference Include="VpnHood.Net.Quic.Ios" />
                    
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.Ios --version 9.1.874
                    
#r "nuget: VpnHood.Net.Quic.Ios, 9.1.874"
                    
#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.Ios@9.1.874
                    
#: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.Ios&version=9.1.874
                    
Install as a Cake Addin
#tool nuget:?package=VpnHood.Net.Quic.Ios&version=9.1.874
                    
Install as a Cake Tool

VpnHood.Net.Quic.Ios

iOS QUIC client backed by Apple's Network.framework (NWConnectionGroup / NWMultiplexGroup, iOS 15+). It implements the same VpnHood.Net.Quic.Abstractions surface (IQuicClient / IQuicConnection / stream) as the desktop MsQuic client, so the tunneling layer consumes QUIC identically on every platform. There is no MsQuic on iOS — libmsquic is not shippable inside a Network Extension, so this project bridges to the OS's own QUIC stack instead.

This document is the authoritative record of the iOS QUIC implementation. It lives with the code (not in the iOS app repo) because everything here is QUIC/Network.framework-specific and platform code in ../VpnHood.Net.Quic.Ios is referenced by ProjectReference, never copied.

Why this exists (memory, not features)

The driving constraint is the iOS Network Extension ~52 MB jetsam limit. QUIC receive buffering on iOS lands in native memory (Network.framework), which phys_footprint counts and jetsam enforces — so the whole design is about bounding native receive buffering while keeping throughput. See the shared record in the app repo's docs/ios/ios-extension-memory-and-throughput.md for the broader memory story.

Files

File Role
IosQuicClient.cs IQuicClient. ConnectAsync stands up the QUIC tunnel: builds NWParameters.CreateQuic (ALPN h3, flow-control windows, pinned-cert TLS bridge), starts an NWConnectionGroup over an NWMultiplexGroup, waits for Ready.
IosQuicConnection.cs IQuicConnection. Opens outbound streams (nw_connection_group_extract_connection with a NULL endpoint) and accepts inbound (peer-initiated) streams from a channel; brings each up on a queue until Ready.
IosQuicStream.cs Stream adapter over a single QUIC stream (NWConnection). One armed native receive per ReadAsync, no read-ahead; per-operation callback state prevents late native completions from completing a later operation.
IosQuicStreamReadOperation.cs, IosQuicStreamWriteOperation.cs That per-operation state: one read's or one write's value-task source, which its native completion reserves before it touches the caller's buffer.
ReusableValueTaskSource.cs, ReusableValueTaskSource_1.cs IValueTaskSource[<T>] bridge used by read/write operation state to complete Network.framework callbacks as ValueTasks.
IosQuicTls.cs Configures SecProtocolOptions: ALPN + the pinned-certificate verify callback bridge.
NWConnectionExtensions.cs, NWConnectionGroupExtensions.cs Best-effort teardown of a stream and of the group: each call is attempted and logged, never masking the failure that led to it.
IosQuicDiagnostics.cs, IosQuicEventIds.cs The stream counters and [VHQUIC] log lines (see Diagnostics), and their log event id.
IosQuicClientFactory.cs The platform's IQuicClientFactory: the clients with the flow-control credits below.

Connection & stream model

  • One QUIC tunnel = NWMultiplexGroup + NWConnectionGroup. Streams are individual NWConnections multiplexed over that group.
  • Outbound stream: nw_connection_group_extract_connection(group, NULL, NULL) — the native equivalent of Swift's NWConnection(from: group). The managed ExtractConnection binding NREs on a null endpoint, and passing the tunnel endpoint / fresh QUIC options makes the extracted stream try to stand up its own transport and fail (ENETDOWN). So we P/Invoke the native function directly with NULL endpoint AND NULL options so the stream inherits the group's tunnel + QUIC options. (See the [DllImport] in IosQuicConnection.)
  • Inbound streams: the group's new-connection handler (wired before Start() so none are missed) writes them to an unbounded channel; AcceptInboundStreamAsync drains it.
  • With TCP forwarding there is one QUIC stream per forwarded flow — this is why stream lifecycle and the per-stream window dominate memory (see below).

Flow control (the load-bearing numbers)

IosQuicClientFactory.CreateClient picks them, times its memory scale (at least 1), and IosQuicClient.ConnectAsync sets them on the QUIC options block; an IosQuicClient made directly keeps Network.framework's defaults:

Parameter Value Why
InitialMaxData 16 KB Connection-wide initial flow-control credit across all streams. Tuned down in steps after measured sub-second native transient spikes near the jetsam limit; prioritizes extension stability over peak iOS download speed.
InitialMaxStreamDataBidirectionalLocal 4 KB Per-stream credit for streams we open (TCP forwarding's download streams).
InitialMaxStreamDataBidirectionalRemote 4 KB Per-stream credit for peer-opened streams.
IosQuicStream.MaxReceiveLength 16 KB Caps a single armed native receive so even a huge caller buffer pulls native data in bounded chunks.

Without an explicit window, Network.framework advertises a large default and a download burst floods native receive buffers faster than forwarding drains them — measured +16 MB in ~1 s at ~25 streams → jetsam kill. These are the initial credits the client advertises. Network.framework grants more as it buffers data, not as we read it, so they bound no native memory, and the read path has no memory brake of its own.

⚠️ Interaction with the shared LocalTcpStack (read this before tuning windows)

QUIC's tight windows make this transport less forgiving than kernel TCP, which means it exposes send-side stalls in the shared TCP stack that big OS socket buffers otherwise hide. The QUIC+TcpForwarding download collapse (40→3 Mbps) was exactly this: a LocalTcpConnection Zero Window Probe that failed to make forward progress stalled the forwarding pump; kernel TCP masked it, QUIC did not. That bug and its forward-progress invariant live in ../VpnHood.Net.TcpStack/LocalTcpConnection.cs (SendZeroWindowProbe) with a regression test in tests/VpnHood.Net.TcpStack.Test. If you shrink these windows, re-verify the TcpStack send path under sustained download.

Threading

Network.framework delivers a connection's callbacks (receive, FIN/close, state, and Cancel teardown) serially on its assigned queue. The QUIC tunnel and extracted streams currently share the same VpnHood.Quic.Ios queue. Stream callbacks are therefore serialized through one queue, with no per-stream queue pool.

Native interop gotchas

  • QUIC options re-wrap: the NWParameters.CreateQuic callback argument is typed as the base NWProtocolOptions even though the native object is a QUIC options block. A direct C# cast to NWProtocolQuicOptions throws InvalidCastException, and thrown on the native trampoline thread it SIGABRTs the process. Re-wrap the same native handle via Runtime.GetINativeObject<NWProtocolQuicOptions>(handle, owns:false) before setting options.
  • NULL-endpoint stream extraction: see "Outbound stream" above — direct [DllImport] of nw_connection_group_extract_connection, returns a +1-retained nw_connection_t (owns:true).
  • NSAutoreleasePool wraps native receive/send arming and callbacks to release autoreleased temporaries promptly (matters under burst).

Read / write semantics

  • Read: one native ReceiveData(minimumIncompleteLength: 1, maximumLength: min(buf, 16 KB)) per ReadAsync; the completion reserves the value-task source before touching the caller buffer (TryReserve() on the read operation's Source) — if cancellation/Dispose already completed the read, the buffer may have been returned to a shared pool and re-rented, so copying into it would corrupt unrelated memory. Empty completion ⇒ EOF (returns 0); a non-empty final chunk is returned, the EOF is kept, and the next read returns 0 without a native receive.
  • Write: isComplete:false always (never signals FIN mid-stream). The bytes are copied into a DispatchData, native-owned storage that Network.framework keeps for the asynchronous send.
  • Dispose: Cancel() + Dispose() the NWConnection, dispose pending cancellation registrations (native callbacks may never fire after Cancel()), then unblock any pending read/write. Second native completions are no-ops via the value-task source's state guard.

Diagnostics

IosQuicDiagnostics holds the stream counters and the [VHQUIC] +CONN/-CONN live=… id=… log lines, which confirm streams (= native NWConnections) are released promptly at flow-end rather than lingering. Its counters move only while it is enabled (a log level below Information), so production pays nothing; TakeMaxStreamCancelMs resets its figure either way. LiveStreamCount is public static so the iOS memory probe in another assembly can read it.

Targeting

net*-ios (Apple platforms only); referenced by the iOS device/extension via ProjectReference. Requires iOS 15+ (IosQuicClient.IsSupported). </content> </invoke>

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

NuGet packages (2)

Showing the top 2 NuGet packages that depend on VpnHood.Net.Quic.Ios:

Package Downloads
VpnHood.Core.Client.Devices.Ios

VpnHood client device provider for iOS and tvOS: the app's side, and what its Network Extension shares

VpnHood.Core.Client.VpnServices.Host.Ios

VpnHood's VPN service on iOS and tvOS: the Network Extension's half, which runs the engine

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
9.1.874 0 11/15/2026
9.1.864 96 11/3/2026
9.0.860 107 10/31/2026
9.0.859.16-prerelease 100 10/30/2026
9.0.857 110 10/20/2026
9.0.856 100 10/19/2026
9.0.855 104 10/13/2026
8.2.853.11-prerelease 108 10/5/2026
8.2.853 124 9/23/2026
8.2.852 136 9/23/2026