modules/vpn: fix the vpn-* systemd services
This commit is contained in:
parent
71025329e7
commit
34524ea3e4
|
@ -4,6 +4,20 @@
|
|||
# docs:
|
||||
# - wireguard (nixos): <https://nixos.wiki/wiki/WireGuard#Setting_up_WireGuard_with_systemd-networkd>
|
||||
# - wireguard (arch): <https://wiki.archlinux.org/title/WireGuard>
|
||||
#
|
||||
# to route all internet traffic through a VPN endpoint, run `systemctl start vpn-${vpnName}`
|
||||
# to show the routing table: `ip rule`
|
||||
# to show the NAT rules used for bridging: `sudo iptables -t nat --list-rules -v`
|
||||
#
|
||||
# the rough idea here is:
|
||||
# 1. each VPN has an IP address: if we originate a packet, and the source address is the VPN's address, then it gets routed over the VPN trivially.
|
||||
# 2a. create a bridge device for each VPN. traffic exiting that bridge is source-NAT'd to the VPN's address.
|
||||
# 2b. to route all traffic for a specific application (or container), give it access to only the bridge device.
|
||||
# 3a. create a separate routing table for each VPN, with table id = ID.
|
||||
# 3b. if a packet enters the VPN's table then it will be routed via the VPN.
|
||||
# 3c. to apply a VPN to all internet traffic, system-wide, a rule is added that forces each packet to enter that VPN's routing table.
|
||||
# - that's done with `systemctl start vpn-$VPN`.
|
||||
# - the VPN acts as the default route. so traffic destined to e.g. a LAN device do not traverse the VPN in this case. only internet traffic is VPN'd.
|
||||
|
||||
{ config, lib, pkgs, sane-lib, ... }:
|
||||
let
|
||||
|
@ -82,6 +96,9 @@ let
|
|||
};
|
||||
});
|
||||
mkVpnConfig = name: { id, dns, endpoint, publicKey, addrV4, privateKeyFile, bridgeDevice, ... }: let
|
||||
prioMain = id + 100;
|
||||
prioWgTable = id + 200;
|
||||
prioFwMark = id + 300;
|
||||
fwmark = id + 10000;
|
||||
bridgeAddrV4 = "10.20.${builtins.toString id}.1/24";
|
||||
in {
|
||||
|
@ -91,6 +108,7 @@ let
|
|||
message = "multiple VPNs share id ${id}";
|
||||
}
|
||||
];
|
||||
|
||||
systemd.network.netdevs."98-${name}" = {
|
||||
# see: `man 5 systemd.netdev`
|
||||
netdevConfig = {
|
||||
|
@ -99,7 +117,7 @@ let
|
|||
};
|
||||
wireguardConfig = {
|
||||
PrivateKeyFile = privateKeyFile;
|
||||
FirewallMark = id;
|
||||
FirewallMark = fwmark;
|
||||
};
|
||||
wireguardPeers = [{
|
||||
wireguardPeerConfig = {
|
||||
|
@ -112,6 +130,7 @@ let
|
|||
};
|
||||
}];
|
||||
};
|
||||
|
||||
systemd.network.networks."50-${name}" = {
|
||||
# see: `man 5 systemd.network`
|
||||
matchConfig.Name = name;
|
||||
|
@ -125,11 +144,11 @@ let
|
|||
routingPolicyRules = [
|
||||
{
|
||||
routingPolicyRuleConfig = {
|
||||
# send traffic from the the container bridge out over the VPN.
|
||||
# send traffic from the container bridge out over the VPN.
|
||||
# the bridge itself does source nat (SNAT) to rewrite the packet source address to that of the VPNs
|
||||
# -- but that happens in POSTROUTING: *after* the kernel decides which interface to give the packet to next.
|
||||
# therefore, we have to route here based on the packet's address as it is in PREROUTING, i.e. the bridge address. weird!
|
||||
Priority = id;
|
||||
Priority = prioWgTable;
|
||||
From = bridgeAddrV4;
|
||||
Table = id;
|
||||
};
|
||||
|
@ -149,6 +168,7 @@ let
|
|||
netdevConfig.Kind = "bridge";
|
||||
netdevConfig.Name = bridgeDevice;
|
||||
};
|
||||
|
||||
systemd.network.networks."51-${bridgeDevice}" = {
|
||||
matchConfig.Name = bridgeDevice;
|
||||
networkConfig.Description = "NATs inbound traffic to ${name}, intended for container isolation";
|
||||
|
@ -163,6 +183,7 @@ let
|
|||
# networkConfig.DHCPServer = true;
|
||||
linkConfig.RequiredForOnline = false;
|
||||
};
|
||||
|
||||
networking.localCommands = with pkgs; ''
|
||||
# view rules with:
|
||||
# - `sudo iptables -t nat --list-rules -v`
|
||||
|
@ -194,15 +215,13 @@ let
|
|||
systemd.services."vpn-${name}" = {
|
||||
path = with pkgs; [ iproute2 ];
|
||||
serviceConfig = let
|
||||
prioMain = id - 200;
|
||||
prioFwMark = id - 100;
|
||||
mkScript = verb: pkgs.writeShellScript "vpn-${name}-${verb}" ''
|
||||
# first, allow all non-default routes (prefix-length != 0) a chance to route the packet.
|
||||
# - this allows the wireguard tunnel itself to pass traffic via our LAN gateway.
|
||||
# - incidentally, it allows traffic to LAN devices and other machine-local or virtual networks.
|
||||
ip rule ${verb} from all lookup main suppress_prefixlength 0 priority ${builtins.toString prioMain}
|
||||
# if packet hasn't gone through the wg device yet (fwmark), then move it to the table which will cause it to.
|
||||
ip rule ${verb} not from all fwmark ${builtins.toString id} lookup ${builtins.toString id} priority ${builtins.toString prioFwMark}
|
||||
ip rule ${verb} not from all fwmark ${builtins.toString fwmark} lookup ${builtins.toString id} priority ${builtins.toString prioFwMark}
|
||||
'';
|
||||
in {
|
||||
ExecStart = ''${mkScript "add"}'';
|
||||
|
|
Loading…
Reference in New Issue