Table of Contents

Class IosVpnAdapter

Namespace
VpnHood.Net.VpnAdapters.IosTun
Assembly
VpnHood.Net.VpnAdapters.IosTun.dll

Provides the VPN adapter for iOS and tvOS, inside an app's packet tunnel extension: it applies the tunnel's network settings through the NetworkExtension.NEPacketTunnelProvider, and reads and writes packets through its NetworkExtension.NEPacketTunnelFlow.

public class IosVpnAdapter : TunVpnAdapter, IVpnAdapter, IPacketTransport, IDisposable
Inheritance
IosVpnAdapter
Implements
Inherited Members

Remarks

The start's addresses, routes, DNS servers and MTU are gathered and applied all at once when the adapter opens. iOS hands the packets the device sends in batches, through callbacks, not through reads; the adapter writes in batches of up to 32.

It has no NAT, no metric and no session name, and no app filter, which iOS leaves to device management. It cannot protect a socket (CanProtectSocket): the extension's own sockets were seen to go around its tunnel on an Apple TV, and the VPN keeps its server's address out of the routes in any case. Its steps take no notice of their cancellation tokens, but the open's wait for iOS to apply the settings.

Constructors

IosVpnAdapter(NEPacketTunnelProvider, IosVpnAdapterSettings, Action<NSError?>)

Provides the VPN adapter for iOS and tvOS, inside an app's packet tunnel extension: it applies the tunnel's network settings through the NetworkExtension.NEPacketTunnelProvider, and reads and writes packets through its NetworkExtension.NEPacketTunnelFlow.

public IosVpnAdapter(NEPacketTunnelProvider tunnelProvider, IosVpnAdapterSettings settings, Action<NSError?> completeStartTunnel)

Parameters

tunnelProvider NEPacketTunnelProvider

The extension's packet tunnel provider.

settings IosVpnAdapterSettings

The adapter's settings.

completeStartTunnel Action<NSError>

The completion handler iOS gave the provider's start of the tunnel, wrapped so that only its first call counts, as every open calls it: with null once the settings are applied, or with the error of applying them. A failure before that, as while the settings are built, does not reach it: the caller answers iOS for such a one.

Remarks

The start's addresses, routes, DNS servers and MTU are gathered and applied all at once when the adapter opens. iOS hands the packets the device sends in batches, through callbacks, not through reads; the adapter writes in batches of up to 32.

It has no NAT, no metric and no session name, and no app filter, which iOS leaves to device management. It cannot protect a socket (CanProtectSocket): the extension's own sockets were seen to go around its tunnel on an Apple TV, and the VPN keeps its server's address out of the routes in any case. Its steps take no notice of their cancellation tokens, but the open's wait for iOS to apply the settings.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

Properties

AppPackageId

Gets the ID the app filter knows this app by, which it always lets into the tunnel, or null where there is no app filter.

protected override string AppPackageId { get; }

Property Value

string

The bundle identifier of the extension.

Exceptions

Exception

The extension's bundle has no identifier.

CanProtectSocket

Gets a value indicating whether the adapter can keep a socket's traffic out of the tunnel. The default is true.

public override bool CanProtectSocket { get; }

Property Value

bool

Always false: the client then leaves its sockets out of its protection wrapper, while a direct call of ProtectSocket(Socket, IPAddress), as its server address check makes, still gets true. The VPN leaves its server's address out of the routes in any case.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

CheckAdapterInterfaceAfterNetworkChanged

Gets a value indicating whether the adapter checks, at each change of the host's network, that its interface is still there, and stops when it is not. The check is IsAdapterInterfaceEnabled(), which by default looks for the interface by name or address only.

protected override bool CheckAdapterInterfaceAfterNetworkChanged { get; }

Property Value

bool

Always false.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

IsAppFilterSupported

Gets a value indicating whether the adapter can include or exclude apps from the tunnel (IncludeApps, ExcludeApps).

public override bool IsAppFilterSupported { get; }

Property Value

bool

Always false: iOS leaves the apps of a VPN to device management.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

IsSocketProtectedByBind

Gets a value indicating whether the adapter keeps a socket out of the tunnel by binding it to the primary address, rather than by an OS call of its own.

protected override bool IsSocketProtectedByBind { get; }

Property Value

bool

Always false.

Remarks

When true, the routes leave out the probe networks the adapter aims at to find the primary addresses, and that search runs on an unprotected socket after a network change.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

RestartAfterNetworkAddressChanged

Gets a value indicating whether the adapter restarts when the host moves to other networks. When false, it takes the new primary addresses and raises PrimaryNetworkChanged.

protected override bool RestartAfterNetworkAddressChanged { get; }

Property Value

bool

Always false.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

Methods

AdapterAdd(CancellationToken)

Makes the interface: the first step of a start.

protected override Task AdapterAdd(CancellationToken cancellationToken)

Parameters

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that represents the asynchronous operation.

Remarks

It does nothing: the settings are applied when the adapter opens.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

AdapterClose()

Closes the interface for reading and writing: the first step of a stop, under the base's stop lock. It may run after a partial start.

protected override void AdapterClose()

Remarks

It forgets the gathered settings and the packet flow. It does not wait for a callback under way, which may still hand on its batch; a callback that finds the flow gone drops its batch and asks for no more. The settings applied to the tunnel stay.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

AdapterOpen(CancellationToken)

Opens the interface for reading and writing packets: the last step of a start, before the reader starts.

protected override Task AdapterOpen(CancellationToken cancellationToken)

Parameters

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that represents the asynchronous operation.

Remarks

It reads the network's DNS servers first (GetPrimaryAdapterDnsServers()), then applies the gathered addresses, routes, DNS servers and MTU to the tunnel, and calls the start's completion handler with null, before the reads start. A failure to apply them, or a canceled wait, goes to the handler too, as an Foundation.NSError, and is thrown; one before, while the settings are built, is only thrown. The tunnel's remote address is the VPN server's, the protocol configuration's server address when it is an IP address, or 192.0.2.1: iOS refuses settings whose remote address is not an IP address.

When the tunnel has an IPv6 address and an IPv6 route shorter than /64, while the adapter found no IPv6 address on the network (IsIpVersionSupported(IpVersion)), ::/0 and 2000::/3 join the routes, as iOS was seen to hold back AAAA lookups otherwise. Routes of /64 or narrower bring neither, so a split tunnel stays split, and iOS may then hold back AAAA lookups for them on a network with no IPv6.

Exceptions

NSErrorException

iOS refused the settings.

OperationCanceledException

cancellationToken was canceled: the wait ends, while iOS may still apply the settings.

AdapterRemove()

Removes the interface and what the start added with it: the last step of a stop, after AdapterClose(). When the base calls it, it runs under the base's stop lock and must not wait for the reader or a sender to take that lock. It may run after a partial start, and after a close that failed.

protected override void AdapterRemove()

Remarks

It does nothing.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

AddAddress(IpNetwork, CancellationToken)

Gives the interface its address in a virtual network. A start calls it once per configured IP version.

protected override Task AddAddress(IpNetwork ipNetwork, CancellationToken cancellationToken)

Parameters

ipNetwork IpNetwork

The interface's address with its network.

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that represents the asynchronous operation.

Remarks

It keeps the address for AdapterOpen(CancellationToken), which applies the settings all at once.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

AddNat(IpNetwork, CancellationToken)

Translates a virtual network to the host's primary address (NAT). A start calls it with UseNat, for each IP version the adapter's NAT translates and the host has a primary address of.

protected override Task AddNat(IpNetwork ipNetwork, CancellationToken cancellationToken)

Parameters

ipNetwork IpNetwork

The virtual network.

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that represents the asynchronous operation.

Exceptions

NotSupportedException

Always: iOS has no NAT for an app.

AddRoute(IpNetwork, CancellationToken)

Routes a network through the interface. A start calls it once per network, for each IP version the interface carries.

protected override Task AddRoute(IpNetwork ipNetwork, CancellationToken cancellationToken)

Parameters

ipNetwork IpNetwork

The network.

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that represents the asynchronous operation.

Remarks

It keeps the route for AdapterOpen(CancellationToken), which applies the settings all at once.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

GetPrimaryAdapterDnsServers()

Gets the DNS servers of the network under the tunnel: the last reading that found any, taken when an adapter opened, before it applied the tunnel's settings, as the device's resolver reports the tunnel's own servers once the tunnel serves the device's DNS.

public override IReadOnlyList<IPAddress> GetPrimaryAdapterDnsServers()

Returns

IReadOnlyList<IPAddress>

The servers, link-local IPv6 ones and those the opening start asked the tunnel for left out; none before a reading found any. They may be stale.

Remarks

The reading is the process's: a reconnect makes a new adapter under the tunnel still up, whose open reads the tunnel's servers. Those are left out when the new start asks for the same ones, so the last reading of the network's stands; a start that asks for others can let the earlier tunnel's servers in.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

IsNatSupported(IpVersion)

Determines whether the adapter's NAT translates an IP version. A server forwards a version the adapter does not translate by its own forwarders.

public override bool IsNatSupported(IpVersion ipVersion)

Parameters

ipVersion IpVersion

The IP version.

Returns

bool

true if the adapter translates it; otherwise, false.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

ProtectSocket(Socket)

Does nothing to the socket, and returns true.

public override bool ProtectSocket(Socket socket)

Parameters

socket Socket

The socket.

Returns

bool

Always true.

Remarks

The extension's own sockets go around its tunnel, as a DNS query and a web request were seen to on an Apple TV with every route in the tunnel. The client's socket wrapper does not call it, as CanProtectSocket is false; its server address check does.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

ProtectSocket(Socket, IPAddress)

Does nothing to the socket, and returns true, as ProtectSocket(Socket) does.

public override bool ProtectSocket(Socket socket, IPAddress ipAddress)

Parameters

socket Socket

The socket.

ipAddress IPAddress

Not used.

Returns

bool

Always true.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

ReadPacket(byte[])

Not used: iOS hands the packets to StartReadingPackets()'s callbacks.

protected override int ReadPacket(byte[] buffer)

Parameters

buffer byte[]

Not used.

Returns

int

Never returns.

Exceptions

NotSupportedException

Always.

SendPacketsAsync(IReadOnlyList<IpPacket>)

Writes a batch of packets to the packet flow, in chunks of up to 32, and is done when it returns.

protected override ValueTask SendPacketsAsync(IReadOnlyList<IpPacket> ipPackets)

Parameters

ipPackets IReadOnlyList<IpPacket>

The batch. The list is reused after the call.

Returns

ValueTask

A completed task.

Remarks

A chunk iOS refuses is dropped and counted as dropped, which is logged once until writes succeed again; unlike the base's sender, no count of failures stops the adapter. An exception while a chunk is written ends the batch, which the base then counts as dropped whole.

Exceptions

InvalidOperationException

The adapter is not open.

SetAllowedApps(IEnumerable<string>, CancellationToken)

Lets only these apps' traffic into the tunnel. A start calls it with IncludeApps, where IsAppFilterSupported is true.

protected override Task SetAllowedApps(IEnumerable<string> packageIds, CancellationToken cancellationToken)

Parameters

packageIds IEnumerable<string>

The apps' IDs, this app's own included.

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that represents the asynchronous operation.

Remarks

It does nothing: iOS leaves the apps of a VPN to device management.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

SetDisallowedApps(IEnumerable<string>, CancellationToken)

Keeps these apps' traffic out of the tunnel. A start calls it with ExcludeApps, where IsAppFilterSupported is true.

protected override Task SetDisallowedApps(IEnumerable<string> packageIds, CancellationToken cancellationToken)

Parameters

packageIds IEnumerable<string>

The apps' IDs, never this app's own.

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that represents the asynchronous operation.

Remarks

It does nothing: iOS leaves the apps of a VPN to device management.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

SetDnsServers(IReadOnlyList<IPAddress>, CancellationToken)

Sets the DNS servers the interface gives the host. A start calls it every time.

protected override Task SetDnsServers(IReadOnlyList<IPAddress> dnsServers, CancellationToken cancellationToken)

Parameters

dnsServers IReadOnlyList<IPAddress>

The servers, only of the IP versions the interface carries; may be empty.

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that represents the asynchronous operation.

Remarks

It keeps the servers for AdapterOpen(CancellationToken), which applies the settings all at once.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

SetMetric(int, bool, bool, CancellationToken)

Sets the interface's route metric. A start calls it when Metric is set.

protected override Task SetMetric(int metric, bool ipV4, bool ipV6, CancellationToken cancellationToken)

Parameters

metric int

The metric.

ipV4 bool

true if the interface carries IPv4 in this run.

ipV6 bool

true if the interface carries IPv6 in this run.

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that represents the asynchronous operation.

Remarks

It does nothing.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

SetMtu(int, bool, bool, CancellationToken)

Sets the interface's MTU. A start calls it when Mtu is set.

protected override Task SetMtu(int mtu, bool ipV4, bool ipV6, CancellationToken cancellationToken)

Parameters

mtu int

The MTU in bytes.

ipV4 bool

true if the interface carries IPv4 in this run.

ipV6 bool

true if the interface carries IPv6 in this run.

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that represents the asynchronous operation.

Remarks

It keeps the MTU for AdapterOpen(CancellationToken), which applies it to both IP versions.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

SetSessionName(string, CancellationToken)

Names the session where the OS shows it. A start calls it when SessionName is not empty.

protected override Task SetSessionName(string sessionName, CancellationToken cancellationToken)

Parameters

sessionName string

The name.

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that represents the asynchronous operation.

Remarks

It does nothing.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

StartReadingPackets()

Asks iOS for the packets the device sends: each batch arrives in a callback, which hands its packets to the transport's receivers and asks for the next batch.

protected override void StartReadingPackets()

Remarks

A packet that does not parse is dropped. With no packet flow, as during a stop, it does nothing, and logs a warning when the adapter is started.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

WaitForTunRead()

Blocks until a packet may be ready, after a read found none. It runs on the reader's task.

protected override void WaitForTunRead()

Remarks

It sleeps 10 milliseconds.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

WaitForTunWrite()

Blocks until the interface may take a packet again, between the retries of a write it refused.

protected override void WaitForTunWrite()

Remarks

It sleeps 10 milliseconds.

Exceptions

ArgumentOutOfRangeException

The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.

WritePacket(IpPacket)

Writes one packet to the interface, without waiting for room.

protected override bool WritePacket(IpPacket ipPacket)

Parameters

ipPacket IpPacket

The packet.

Returns

bool

true if the interface took it; false if it cannot take it now, which the adapter retries with doubling waits (see MaxPacketSendDelay).

Remarks

It writes one packet through the packet flow; the adapter's sender writes with SendPacketsAsync(IReadOnlyList<IpPacket>) instead.

Exceptions

InvalidOperationException

The adapter is not open.