Table of Contents

Class LinuxVpnKillSwitch

Namespace
VpnHood.Core.Client.Devices.Linux
Assembly
VpnHood.Core.Client.Devices.Linux.dll

Represents the kill switch's block on Linux: an nftables table of ours for both IP versions (inet), replaced whole by one nft -f, a transaction with no gap.

public sealed class LinuxVpnKillSwitch : IVpnKillSwitch
Inheritance
LinuxVpnKillSwitch
Implements
Inherited Members

Remarks

nft, not iptables, for one table over both IP versions, one transaction, and sets for the ranges. It sits beside ufw's, firewalld's and Docker's tables, and a drop in any table wins, so ours only adds drops. The output chain lets out the tunnel's interface by name (first, so a tunnel packet costs one compare, and a name matches a tun made later), loopback, the service by its cgroup, DHCP and NDP, and an out-of-tunnel plan's resolvers; then it blocks DNS, a LAN router's too; then it lets out the device-level split and blocks the rest. The kernel runs the table for every outbound packet while the switch is on, a cost accepted for the switch: eBPF hooks at connect would be more work and miss unconnected UDP.

A blocked TCP connect is reset, so it fails at once: of a dropped SYN the kernel only sends another, and the app hangs till its own timeout (seen on Ubuntu 26.04); a dropped UDP send fails at once (EPERM). The forward chain does the same for forwarded traffic (Docker, libvirt), which would otherwise go around it; its DNS rules too, or a container's lookups reach the LAN's router (seen with Docker on Ubuntu 26.04).

The kernel keeps the table through a crash, not a reboot, so a block also goes to a file that a boot unit of its own loads before the network comes up, where systemd runs; without it, the block does not outlast a reboot. That copy leaves the service's rule out: nft resolves a cgroup when it loads the rule, and the service's is not there yet; the service is let out again at its next block. The names are the SDK's, so every app built on VpnHood shares the one block; the table's comment names the app whose block it is. It needs root: the service.

Constructors

LinuxVpnKillSwitch(string)

Represents the kill switch's block on Linux: an nftables table of ours for both IP versions (inet), replaced whole by one nft -f, a transaction with no gap.

public LinuxVpnKillSwitch(string owner)

Parameters

owner string

The app's storage folder, which names the app whose block it is and an update keeps.

Remarks

nft, not iptables, for one table over both IP versions, one transaction, and sets for the ranges. It sits beside ufw's, firewalld's and Docker's tables, and a drop in any table wins, so ours only adds drops. The output chain lets out the tunnel's interface by name (first, so a tunnel packet costs one compare, and a name matches a tun made later), loopback, the service by its cgroup, DHCP and NDP, and an out-of-tunnel plan's resolvers; then it blocks DNS, a LAN router's too; then it lets out the device-level split and blocks the rest. The kernel runs the table for every outbound packet while the switch is on, a cost accepted for the switch: eBPF hooks at connect would be more work and miss unconnected UDP.

A blocked TCP connect is reset, so it fails at once: of a dropped SYN the kernel only sends another, and the app hangs till its own timeout (seen on Ubuntu 26.04); a dropped UDP send fails at once (EPERM). The forward chain does the same for forwarded traffic (Docker, libvirt), which would otherwise go around it; its DNS rules too, or a container's lookups reach the LAN's router (seen with Docker on Ubuntu 26.04).

The kernel keeps the table through a crash, not a reboot, so a block also goes to a file that a boot unit of its own loads before the network comes up, where systemd runs; without it, the block does not outlast a reboot. That copy leaves the service's rule out: nft resolves a cgroup when it loads the rule, and the service's is not there yet; the service is let out again at its next block. The names are the SDK's, so every app built on VpnHood shares the one block; the table's comment names the app whose block it is. It needs root: the service.

Methods

Block(VpnKillSwitchRules, CancellationToken)

Puts this app's block up, or re-points it, in one change with no gap, in place of any app's.

public Task Block(VpnKillSwitchRules rules, CancellationToken cancellationToken)

Parameters

rules VpnKillSwitchRules

What the block lets pass.

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that completes once the block is up.

Remarks

It fails where it cannot, and then nothing may connect: a connect never goes on unprotected.

Exceptions

NotSupportedException

Linux has no nftables, or the service is in no cgroup v2 group of its own.

ExternalException

The OS refused the block: Linux's nft, or Windows' filtering engine, as without an administrator.

OperationCanceledException

cancellationToken was canceled.

NotSupportedException

nftables (nft) is not installed, or the service is in no cgroup v2 group of its own.

ExternalException

nft refused the table, or systemctl the boot unit.

IsBlocking(CancellationToken)

Determines whether this app's block is up.

public Task<bool> IsBlocking(CancellationToken cancellationToken)

Parameters

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task<bool>

A task whose result is true if this app's block is up; otherwise, false.

Remarks

Without root, it cannot read the table and answers false. Where the table is gone but its boot copy is this app's, as after a reboot before the boot unit has loaded it, it answers true though no filtering runs.

Exceptions

ExternalException

The OS's rules could not be read: Linux's nft, or Windows' filtering engine.

OperationCanceledException

cancellationToken was canceled.

Unblock(bool, CancellationToken)

Removes this app's block, or, with anyApp, any app's: the tunnel's holder's call.

public Task Unblock(bool anyApp, CancellationToken cancellationToken)

Parameters

anyApp bool

true to remove any app's block; false to remove this app's only.

cancellationToken CancellationToken

The token to monitor for cancellation requests.

Returns

Task

A task that completes once the block is gone.

Remarks

It removes the table and its boot copy. Without root, it does nothing: a process of a person's could not have put a block up.

Exceptions

ExternalException

The OS refused the change: Linux's nft, or Windows' filtering engine.

OperationCanceledException

cancellationToken was canceled.