Class TunVpnAdapter
- Namespace
- VpnHood.Net.VpnAdapters.Abstractions
- Assembly
- VpnHood.Net.VpnAdapters.Abstractions.dll
Provides the start, stop, restart, packet I/O and network-change handling of a VPN adapter over a TUN-like interface; a derived class supplies the platform's steps.
public abstract class TunVpnAdapter : PacketTransport, IVpnAdapter, IPacketTransport, IDisposable
- Inheritance
-
TunVpnAdapter
- Implements
- Derived
- Inherited Members
Remarks
A start runs the derived class's steps in order: AdapterAdd(CancellationToken); SetSessionName(string, CancellationToken) when a name is given; AddAddress(IpNetwork, CancellationToken) per configured IP version; SetMetric(int, bool, bool, CancellationToken) and SetMtu(int, bool, bool, CancellationToken) when given; SetDnsServers(IReadOnlyList<IPAddress>, CancellationToken); AddRoute(IpNetwork, CancellationToken) per network and IP version; AddNat(IpNetwork, CancellationToken); the app filter; then AdapterOpen(CancellationToken). A stop runs AdapterClose() and AdapterRemove(). A stop or disposal during a start cancels the token the steps get; the base checks it before the routes and before opening, and each step should observe it, as a start may be torn down part-way. An owner's start runs its steps under no lock of the base, a restart under its restart lock.
Packets are read on a task of the adapter's own and handed to the transport's receivers; sent packets are written one by one. Ten exceptions in a row from reading, or ten from writing, stop the adapter: it then restarts itself with AutoRestart, or raises Failed.
When the host's network changes, the adapter restarts if RestartAfterNetworkAddressChanged says so, or takes the new primary addresses and raises PrimaryNetworkChanged; a new temporary IPv6 address on the same network only refreshes the primary addresses.
Constructors
TunVpnAdapter(VpnAdapterSettings)
Initializes a new instance of the TunVpnAdapter class, which starts watching the host's network changes until it is disposed.
protected TunVpnAdapter(VpnAdapterSettings adapterSettings)
Parameters
adapterSettingsVpnAdapterSettingsThe adapter's settings.
Exceptions
- ArgumentOutOfRangeException
The settings' blocking timeout is negative or too long, or their queue capacity is not above zero.
Properties
AdapterIpNetworkV4
Gets the interface's IPv4 address with its network in the current run, or null.
public IpNetwork? AdapterIpNetworkV4 { get; }
Property Value
AdapterIpNetworkV6
Gets the interface's IPv6 address with its network in the current run, or null.
public IpNetwork? AdapterIpNetworkV6 { get; }
Property Value
AdapterName
Gets the name of the adapter's interface.
public string AdapterName { get; }
Property Value
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 abstract string? AppPackageId { get; }
Property Value
AutoRestartDelay
Gets the wait before AutoRestart tries a failed restart again. The default is 10 seconds.
protected virtual TimeSpan AutoRestartDelay { get; }
Property Value
CanProtectSocket
Gets a value indicating whether the adapter can keep a socket's traffic out of the tunnel. The default is true.
public virtual bool CanProtectSocket { get; }
Property Value
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 abstract bool CheckAdapterInterfaceAfterNetworkChanged { get; }
Property Value
GatewayIpV4
Gets the gateway of the interface's IPv4 network: its first address plus one, or null for a /31 or /32 network or when not started.
public IPAddress? GatewayIpV4 { get; }
Property Value
GatewayIpV6
Gets the gateway of the interface's IPv6 network: its first address plus one, or null for a /128 network, when not started, or in a run whose IPv6 address the interface refused.
public IPAddress? GatewayIpV6 { get; }
Property Value
IsAppFilterSupported
Gets a value indicating whether the adapter can include or exclude apps from the tunnel (IncludeApps, ExcludeApps).
public abstract bool IsAppFilterSupported { get; }
Property Value
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 abstract bool IsSocketProtectedByBind { get; }
Property Value
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.
IsStarted
Gets a value indicating whether the adapter is started.
public bool IsStarted { get; }
Property Value
PrimaryAdapterIpV4
Gets the host's primary IPv4 address, outside the tunnel, or null. A stop clears it; a start finds it again.
public IPAddress? PrimaryAdapterIpV4 { get; }
Property Value
PrimaryAdapterIpV6
Gets the host's primary IPv6 address, outside the tunnel, or null. A stop clears it; a start finds it again.
public IPAddress? PrimaryAdapterIpV6 { get; }
Property Value
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 abstract bool RestartAfterNetworkAddressChanged { get; }
Property Value
ServerIp
Gets the VPN server's address the current run's options name, or null.
protected IPAddress? ServerIp { get; }
Property Value
UseNat
Gets a value indicating whether the current run translates its virtual networks (NAT).
protected bool UseNat { get; }
Property Value
Methods
AdapterAdd(CancellationToken)
Makes the interface: the first step of a start.
protected abstract Task AdapterAdd(CancellationToken cancellationToken)
Parameters
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
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 abstract void AdapterClose()
AdapterOpen(CancellationToken)
Opens the interface for reading and writing packets: the last step of a start, before the reader starts.
protected abstract Task AdapterOpen(CancellationToken cancellationToken)
Parameters
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
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 abstract void AdapterRemove()
AddAddress(IpNetwork, CancellationToken)
Gives the interface its address in a virtual network. A start calls it once per configured IP version.
protected abstract Task AddAddress(IpNetwork ipNetwork, CancellationToken cancellationToken)
Parameters
ipNetworkIpNetworkThe interface's address with its network.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
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 abstract Task AddNat(IpNetwork ipNetwork, CancellationToken cancellationToken)
Parameters
ipNetworkIpNetworkThe virtual network.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
AddRoute(IpNetwork, CancellationToken)
Routes a network through the interface. A start calls it once per network, for each IP version the interface carries.
protected abstract Task AddRoute(IpNetwork ipNetwork, CancellationToken cancellationToken)
Parameters
ipNetworkIpNetworkThe network.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
BindSocketToIp(Socket, IPAddress)
Binds a socket to an address, which is how ProtectSocket(Socket) keeps it out of the tunnel. The default binds it to the address with any port.
protected virtual void BindSocketToIp(Socket socket, IPAddress address)
Parameters
socketSocketThe socket, not bound yet.
addressIPAddressThe host's primary address of the socket's IP version.
DisposeManaged()
Stops watching the host's network changes, raises Disposed, then disposes the transport.
protected override void DisposeManaged()
GetGatewayIp(IpVersion)
Gets the gateway of the interface's network of an IP version.
public IPAddress? GetGatewayIp(IpVersion ipVersion)
Parameters
ipVersionIpVersionThe IP version.
Returns
- IPAddress
GatewayIpV4 or GatewayIpV6, which may be null.
GetIpNetwork(IpVersion)
Gets the interface's address with its network of an IP version.
public IpNetwork? GetIpNetwork(IpVersion ipVersion)
Parameters
ipVersionIpVersionThe IP version.
Returns
- IpNetwork
AdapterIpNetworkV4 or AdapterIpNetworkV6, which may be null.
GetPrimaryAdapterAddress(IpVersion)
Gets the host's primary address of an IP version: the one it reaches the internet from, outside the tunnel.
public IPAddress? GetPrimaryAdapterAddress(IpVersion ipVersion)
Parameters
ipVersionIpVersionThe IP version.
Returns
GetPrimaryAdapterDnsServers()
Gets the DNS servers of the network the tunnel runs over: the host's DNS servers as .NET lists them, less those the tunnel itself gives.
public virtual IReadOnlyList<IPAddress> GetPrimaryAdapterDnsServers()
Returns
- IReadOnlyList<IPAddress>
The DNS servers; none where .NET lists none, which a platform's adapter may answer itself.
IsAdapterInterfaceEnabled()
Determines whether the adapter's interface is still on the host's IP stack, found by its name or its address. Its operational status is not consulted: a fresh interface may report down for a while.
protected virtual bool IsAdapterInterfaceEnabled()
Returns
IsIpVersionSupported(IpVersion)
Determines whether the host's network, outside the tunnel, supports an IP version.
public virtual bool IsIpVersionSupported(IpVersion ipVersion)
Parameters
ipVersionIpVersionThe IP version.
Returns
Remarks
It tells whether the primary interface has an address of that version, for routing and split-tunneling decisions and to know whether the server can be reached with it. It says nothing of what the tunnel itself carries.
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 abstract bool IsNatSupported(IpVersion ipVersion)
Parameters
ipVersionIpVersionThe IP version.
Returns
OnPrimaryNetworkChanged()
Raises PrimaryNetworkChanged.
protected void OnPrimaryNetworkChanged()
PreDispose()
Stops the adapter, after any restart under way ends, before the transport is disposed.
protected override sealed void PreDispose()
ProtectSocket(Socket)
Keeps a socket's traffic out of the tunnel by binding it to the host's primary address of its IP version. Call it before the socket is bound or connected.
public virtual bool ProtectSocket(Socket socket)
Parameters
socketSocketThe socket, not bound yet.
Returns
- bool
true if the socket is bound; false if the host has no primary address of the socket's IP version.
Exceptions
- ObjectDisposedException
The adapter is disposed.
- InvalidOperationException
The socket is already bound.
- NotSupportedException
The socket is neither IPv4 nor IPv6.
ProtectSocket(Socket, IPAddress)
Keeps a socket's traffic to a given address out of the tunnel by binding it to the host's primary address of its IP version. Call it before the socket is bound or connected.
public virtual bool ProtectSocket(Socket socket, IPAddress remoteAddress)
Parameters
socketSocketThe socket, not bound yet.
remoteAddressIPAddressThe remote address the socket will reach.
Returns
- bool
true if the socket is bound; false if the host has no primary address of the socket's IP version, or exactly one of the two addresses is a loopback address, which no bind can route.
Exceptions
- ObjectDisposedException
The adapter is disposed.
- InvalidOperationException
The socket is already bound.
- NotSupportedException
The socket is neither IPv4 nor IPv6.
ReadPacket(byte[])
Reads one packet from the interface into a buffer, without waiting for one.
protected abstract int ReadPacket(byte[] buffer)
Parameters
bufferbyte[]The buffer, at least the MTU long.
Returns
- int
The number of bytes read, from the buffer's start; 0 if no packet is ready.
Remarks
Only the bytes read are parsed, and a packet whose header claims more is refused: the buffer is rented, so past them it holds an earlier read's bytes.
ReadPacket(int)
Reads one packet into an array rented from Shared, with ReadPacket(byte[]).
protected virtual IpPacket? ReadPacket(int mtu)
Parameters
mtuintThe size of the array to rent: the run's MTU.
Returns
Exceptions
- ArgumentException
The bytes read are no valid IP packet.
RefreshPrimaryAdapterIps()
Looks at the host's network again for its primary addresses, as a change of the host's addresses makes the adapter do, and raises PrimaryAdapterIpChanged when they differ from the last ones.
public void RefreshPrimaryAdapterIps()
Remarks
For a caller that needs GetPrimaryAdapterAddress(IpVersion) and IsIpVersionSupported(IpVersion) to tell the network as it is now: the OS may move the host's traffic to another network without an address change. An adapter that is stopped, starting or restarting is left alone: its start finds the addresses.
Restart(CancellationToken)
Stops the adapter and starts it again with its last start's options, after two seconds.
public Task Restart(CancellationToken cancellationToken)
Parameters
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
Remarks
A restart already under way, an owner's start under way, the owner's stop or a disposal makes it return without restarting; the stop or disposal is checked again after the two seconds. A failed start leaves the adapter stopped, and the adapter answers that itself: it retries with AutoRestart, else raises Failed.
Exceptions
- InvalidOperationException
The adapter has never been started.
- OperationCanceledException
cancellationTokenwas canceled, or the owner stopped the adapter meanwhile.
SendPacket(IpPacket)
Writes one packet to the interface, retrying with doubling waits while it cannot take it (see MaxPacketSendDelay), after which the packet is dropped with a warning.
protected void SendPacket(IpPacket ipPacket)
Parameters
ipPacketIpPacketThe packet.
Remarks
A packet the interface refuses during a stop or a start is dropped. A dropped packet is counted as dropped, not sent, so call it only from SendPacketsAsync(IReadOnlyList<IpPacket>). Ten exceptions in a row from writing stop the adapter, which then restarts itself or raises Failed; a send that completes without an exception, a drop included, resets the count.
Exceptions
- InvalidOperationException
The adapter is not started, or is stopping or disposed.
SendPacketsAsync(IReadOnlyList<IpPacket>)
Writes a batch of packets to the interface, one after another, with SendPacket(IpPacket).
protected override ValueTask SendPacketsAsync(IReadOnlyList<IpPacket> ipPackets)
Parameters
ipPacketsIReadOnlyList<IpPacket>The packets.
Returns
- ValueTask
A completed task: the writes are synchronous.
Exceptions
- InvalidOperationException
The adapter is not started, or is stopping or disposed.
SetAllowedApps(IEnumerable<string>, CancellationToken)
Lets only these apps' traffic into the tunnel. A start calls it with IncludeApps, where IsAppFilterSupported is true.
protected abstract Task SetAllowedApps(IEnumerable<string> packageIds, CancellationToken cancellationToken)
Parameters
packageIdsIEnumerable<string>The apps' IDs, this app's own included.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
SetDisallowedApps(IEnumerable<string>, CancellationToken)
Keeps these apps' traffic out of the tunnel. A start calls it with ExcludeApps, where IsAppFilterSupported is true.
protected abstract Task SetDisallowedApps(IEnumerable<string> packageIds, CancellationToken cancellationToken)
Parameters
packageIdsIEnumerable<string>The apps' IDs, never this app's own.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
SetDnsServers(IReadOnlyList<IPAddress>, CancellationToken)
Sets the DNS servers the interface gives the host. A start calls it every time.
protected abstract Task SetDnsServers(IReadOnlyList<IPAddress> dnsServers, CancellationToken cancellationToken)
Parameters
dnsServersIReadOnlyList<IPAddress>The servers, only of the IP versions the interface carries; may be empty.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
SetMetric(int, bool, bool, CancellationToken)
Sets the interface's route metric. A start calls it when Metric is set.
protected abstract Task SetMetric(int metric, bool ipV4, bool ipV6, CancellationToken cancellationToken)
Parameters
metricintThe metric.
ipV4booltrue if the interface carries IPv4 in this run.
ipV6booltrue if the interface carries IPv6 in this run.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
SetMtu(int, bool, bool, CancellationToken)
Sets the interface's MTU. A start calls it when Mtu is set.
protected abstract Task SetMtu(int mtu, bool ipV4, bool ipV6, CancellationToken cancellationToken)
Parameters
mtuintThe MTU in bytes.
ipV4booltrue if the interface carries IPv4 in this run.
ipV6booltrue if the interface carries IPv6 in this run.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
SetSessionName(string, CancellationToken)
Names the session where the OS shows it. A start calls it when SessionName is not empty.
protected abstract Task SetSessionName(string sessionName, CancellationToken cancellationToken)
Parameters
sessionNamestringThe name.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation.
Start(VpnAdapterOptions, CancellationToken)
Starts the adapter: makes its interface with its addresses, DNS servers, routes, NAT and app filter, opens it and starts reading packets.
public Task Start(VpnAdapterOptions options, CancellationToken cancellationToken)
Parameters
optionsVpnAdapterOptionsThe options this run uses, which each restart uses again.
cancellationTokenCancellationTokenThe token to monitor for cancellation requests.
Returns
- Task
A task that represents the asynchronous operation. It completes when the adapter is up.
Remarks
Await one start before the next. From here the adapter keeps itself started until Stop(), disposal, or a failure it does not recover from (without AutoRestart). IsStarted is true from the start's beginning, before the interface exists. A failed start tries to remove what it added and leaves the adapter stopped, with none of the run's addresses and gateways; it raises no Failed, as the caller has the exception. An IPv6 address the interface refuses is logged, and the run goes on without IPv6. Once the adapter is up and before the start completes, it raises Started on its own thread.
Exceptions
- ObjectDisposedException
The adapter is disposed.
- OperationCanceledException
cancellationTokenwas canceled, or a stop or disposal came during the start.- NotSupportedException
UseNat is set and the adapter's NAT does not translate IPv4.
- InvalidOperationException
Both IncludeApps and ExcludeApps are non-empty on an adapter with an app filter, or the filter has no AppPackageId.
- Exception
A route could not be added; the message names it.
StartReadingPackets()
Reads packets from the interface and hands each to the transport's receivers, until a stop retires this run. It runs on the reader's task; ten read errors in a row stop the adapter.
protected virtual void StartReadingPackets()
Stop()
Stops the adapter and removes its interface, with its routes and DNS servers. A restart under way ends first, and a pending retry is dropped, even when the adapter is already stopped.
public void Stop()
Remarks
The adapter counts as stopped even when closing or removing the interface fails: the error reaches the caller, and the next start removes a leftover. A close that fails does not skip the removal.
Exceptions
- ObjectDisposedException
The adapter is disposed.
WaitForReader(TimeSpan)
Waits for this run's reader to end, for an adapter about to free what the reader uses. A stop made on the reader's own thread, by its read errors or by a subscriber, does not wait for itself.
protected bool WaitForReader(TimeSpan timeout)
Parameters
timeoutTimeSpanThe longest wait.
Returns
WaitForSender(TimeSpan)
Waits for the batch being sent to end, for an adapter that has cleared the handle its WritePacket uses: that batch may still hold the handle, a later one finds it cleared. The sender writes its flag with no fence, so a process-wide barrier before the wait makes a flag set before the clearing seen here, and one after it makes all the batch did before clearing its flag seen too (ARM64 may show the clearing first). A stop made by this adapter's sender, on its own errors, does not wait for itself.
protected bool WaitForSender(TimeSpan timeout)
Parameters
timeoutTimeSpanThe longest wait.
Returns
WaitForTunRead()
Blocks until a packet may be ready, after a read found none. It runs on the reader's task.
protected abstract void WaitForTunRead()
WaitForTunWrite()
Blocks until the interface may take a packet again, between the retries of a write it refused.
protected abstract void WaitForTunWrite()
WritePacket(IpPacket)
Writes one packet to the interface, without waiting for room.
protected abstract bool WritePacket(IpPacket ipPacket)
Parameters
ipPacketIpPacketThe 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).
Events
Disposed
Occurs when the adapter is disposed.
public event EventHandler? Disposed
Event Type
Failed
Occurs when the adapter stopped on its own and stays down: by repeated I/O errors, by losing its interface, or by a restart that could not start it again. Its owner must act, a client by ending its session.
public event EventHandler<Exception>? Failed
Event Type
Remarks
It is raised on a thread-pool task, after the failure. A stop, a disposal or a new start by the owner that begins before that task runs cancels it; one that begins while a handler runs does not interrupt the handler. It is not raised with AutoRestart, where the adapter retries instead. The owner's stop or disposal does not raise it, nor does a failed owner's start, whose caller gets the exception.
PrimaryAdapterIpChanged
Occurs when the adapter finds new primary addresses without a restart, as when the OS rotates a temporary IPv6 address within its network.
public event EventHandler? PrimaryAdapterIpChanged
Event Type
Remarks
It is raised on the thread that saw the network change, only when the addresses found differ from the last ones; not for the start's own discovery, nor when a stop clears them.
PrimaryNetworkChanged
Occurs when the host moved to other networks and the adapter took their primary addresses without a restart: what was connected from the old networks may be dead, though an OS may keep it open, as Linux does.
public event EventHandler? PrimaryNetworkChanged
Event Type
Remarks
It is raised on the thread that saw the network change. The primary addresses may stay the same.
Started
Occurs when the adapter is up, with the options it runs with: after the owner's start and after each restart, which may make the interface anew. What points at the interface from outside, such as a kill switch's block, must follow it.
public event EventHandler<VpnAdapterOptions>? Started
Event Type
Remarks
It is raised on the starting thread, once the adapter is up and before the start or the restart completes. A handler may stop or dispose the adapter, and start it again once it has stopped it; it must not wait for another start or restart of it. A run that is over by a handler's turn is not announced to that handler, and one run's handlers return before the next run's are called, except those of a run a handler itself starts. A stop or a disposal on another thread does not wait for the handlers: one may still run, or be called, as that stop returns.