router.wireguard.<name> configures tunnels through networkd: road warrior (listening, dual-stack), site-to-site (no transfer network) and provider dial-out with masquerade. A tunnel has a zone like a LAN, so inter-zone rules apply; management, DNS and internet are opt-in. Peers' allowedIPs become routes (destination-based routing; default-route tunnels are rejected). Tunnels have a kill switch (default on): forwarded packets for their allowedIPs are dropped if they would leave via an uplink, before the established-accept so flows that used the tunnel are cut too. Private keys stay outside the Nix store and are passed to the unprivileged systemd-networkd with LoadCredential, so key files can be root-only. Assertions cover CIDR/key/endpoint formats, duplicate allowedIPs, overlaps with LAN subnets, port and device clashes, and rule destinations inside tunnel networks. A five-VM `wireguard` check covers road warrior, site-to-site, the provider tunnel with an IPv6 transfer net, zone rules and the kill switch. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
48 lines
1.3 KiB
Nix
48 lines
1.3 KiB
Nix
{ config, lib, ... }:
|
|
let
|
|
cfg = config.router;
|
|
lans = lib.attrValues cfg.lans;
|
|
ipv4 = (import ./lib.nix { inherit lib; }).ipv4;
|
|
|
|
# Tunnels that opted into DNS: listen on each of their addresses.
|
|
dnsAddrs = lib.concatMap (w: w.addresses) (lib.filter (w: w.dns) (lib.attrValues cfg.wireguard));
|
|
addrOf = a: builtins.head (lib.splitString "/" a);
|
|
# IPv4 gets the proper network address; IPv6 netblocks are masked by Unbound.
|
|
netOf =
|
|
a:
|
|
let
|
|
parts = lib.splitString "/" a;
|
|
in
|
|
if lib.hasInfix ":" a then
|
|
a
|
|
else
|
|
ipv4.cidr (builtins.head parts) (lib.toInt (builtins.elemAt parts 1));
|
|
in
|
|
{
|
|
config = lib.mkIf cfg.enable {
|
|
services.unbound = {
|
|
enable = true;
|
|
settings = {
|
|
server = {
|
|
interface = [ "127.0.0.1" ] ++ map (l: l.ipv4.address) lans ++ map addrOf dnsAddrs;
|
|
# LAN addresses are assigned by networkd, possibly after Unbound starts.
|
|
ip-freebind = true;
|
|
access-control = [
|
|
"127.0.0.0/8 allow"
|
|
]
|
|
++ map (l: "${l.ipv4.subnet} allow") lans
|
|
++ map (a: "${netOf a} allow") dnsAddrs;
|
|
};
|
|
}
|
|
// lib.optionalAttrs (cfg.dns.forwarders != [ ]) {
|
|
forward-zone = [
|
|
{
|
|
name = ".";
|
|
forward-addr = cfg.dns.forwarders;
|
|
}
|
|
];
|
|
};
|
|
};
|
|
};
|
|
}
|