{ povSelf, lib, config, pkgs, ... }: let inherit (lib) types; cfg = lib.getAttrFromPath povSelf config; in { options = { enable = { type = types.bool; default = false; }; asn = { type = types.int; }; routerId = { type = types.int; }; address = { type = types.str; }; range = { type = types.str; }; peerings = { default = { }; type = with types; attrsOf (submodule { options = { asn = lib.mkOption { type = types.int; }; remoteLinkLocalAddress = lib.mkOption { type = types.str; }; localLinkLocalAddress = lib.mkOption { type = types.str; default = "fe80::6b61/64"; }; endpoint = lib.mkOption { type = with types; nullOr str; default = null; }; listenPort = lib.mkOption { type = types.int; }; publicKey = lib.mkOption { type = types.str; }; hasPresharedKey = lib.mkOption { type = types.bool; default = false; }; }; }); }; }; config = lib.mkIf cfg.enable { networking.firewall.allowedUDPPorts = lib.mapAttrsToList ( name: peerConfig: peerConfig.listenPort ) cfg.peerings; systemd.services.systemd-networkd.after = [ "sops-install-secrets.service" ]; sops.secrets = ( cfg.peerings |> lib.mapAttrsToList ( name: peerConfig: [ (lib.nameValuePair "dn42/peerings/${name}/wgPrivateKey" { owner = "systemd-network"; group = "systemd-network"; }) ] ++ lib.optionals peerConfig.hasPresharedKey [ (lib.nameValuePair "dn42/peerings/${name}/wgPresharedKey" { owner = "systemd-network"; group = "systemd-network"; }) ] ) |> lib.lists.flatten |> lib.listToAttrs ); systemd.network = { netdevs = lib.mapAttrs' ( name: peerConfig: lib.nameValuePair "20-dn42${name}" { netdevConfig = { Kind = "wireguard"; Name = "dn42${name}"; }; wireguardConfig = { ListenPort = peerConfig.listenPort; PrivateKeyFile = config.sops.secrets."dn42/peerings/${name}/wgPrivateKey".path; } // (lib.optionalAttrs peerConfig.hasPresharedKey { PresharedKeyFile = config.sops.secrets."dn42/peerings/${name}/wgPresharedKey".path; }); wireguardPeers = [ { PersistentKeepalive = 30; Endpoint = lib.mkIf (!builtins.isNull peerConfig.endpoint) peerConfig.endpoint; PublicKey = peerConfig.publicKey; AllowedIPs = [ "fd00::/8" peerConfig.remoteLinkLocalAddress ]; } ]; } ) cfg.peerings; networks = lib.mapAttrs' ( name: peerConfig: lib.nameValuePair "20-dn42${name}" { matchConfig.Name = "dn42${name}"; linkConfig.RequiredForOnline = "no"; address = [ peerConfig.localLinkLocalAddress ]; routes = [ { Destination = "${peerConfig.remoteLinkLocalAddress}/128"; } ]; networkConfig = { IPv6Forwarding = true; IPv6AcceptRA = false; DHCP = false; }; } ) cfg.peerings; }; systemd.services.stayrtr = { wantedBy = [ "multi-user.target" "bird.service" ]; serviceConfig.DynamicUser = true; serviceConfig.ExecStart = '' ${lib.getExe pkgs.stayrtr} \ -bind [::1]:8282 \ -cache=https://dn42.burble.com/roa/dn42_roa_46.json \ -checktime=false ''; }; services.bird = { enable = true; package = pkgs.bird3; preCheckConfig = '' # Remove roa files for checking, because they are only available at runtime sed -i 's|include "/etc/bird/roa_dn42.conf";||' bird.conf cat -n bird.conf # here for debugging purposes ''; config = '' log syslog { debug, trace, info, remote, warning, error, auth, fatal, bug }; log stderr all; define OWNAS = ${toString cfg.asn}; define OWNNET = ${cfg.range}; define OWNNETSET = [ ${cfg.range} ]; define OWNIP = ${cfg.address}; router id ${toString cfg.routerId}; hostname "${config.networking.hostName}"; roa6 table dn42_roa; function is_self_net() -> bool { return net ~ OWNNETSET; } function is_valid_network() -> bool { return net ~ [ fd00::/8{44,64} ]; } function import_filter() { if (net.type != NET_IP6 || ! is_valid_network() || is_self_net()) then reject; if (roa_check(dn42_roa, net, bgp_path.last) != ROA_VALID) then { # Reject when unknown or invalid according to ROA print "[dn42] ROA check failed for ", net, " ASN ", bgp_path.last; reject; } accept; } function export_filter() { if ( ! is_valid_network() ) then reject; if (source !~ [RTS_STATIC, RTS_BGP]) then reject; accept; } protocol rpki { roa6 { table dn42_roa; }; remote ::1; port 8282; refresh 600; retry 300; expire 7200; } protocol device { scan time 10; } protocol static { route OWNNET unreachable; ipv6 { import all; export none; }; } protocol kernel { scan time 20; ipv6 { import none; export filter { # dont export static routes if source = RTS_STATIC then reject; # preferred outgoing source address krt_prefsrc = OWNIP; accept; }; }; } template bgp dn42_peers { local as OWNAS; path metric 1; advertise hostname on; enforce first as on; med metric on; ipv6 { import keep filtered; import limit 9000 action block; import where import_filter(); next hop self; # advertise this router as next hop export where export_filter(); }; } '' + (lib.concatStringsSep "\n" ( lib.mapAttrsToList (name: peerConfig: '' protocol bgp ${name} from dn42_peers { neighbor ${peerConfig.remoteLinkLocalAddress}%dn42${name} as ${toString peerConfig.asn}; enable extended messages; } '') cfg.peerings )); }; }; }