commit 2ad5e7198c6c164ab2d4efcd4057bece53c5f755
parent 387fd9eaeeb53c7e8d8e2ddc8a05ba84868a4f45
Author: Katja (zaphyra) <git@ctu.cx>
Date: Mon, 9 Jun 2025 13:28:25 +0200
parent 387fd9eaeeb53c7e8d8e2ddc8a05ba84868a4f45
Author: Katja (zaphyra) <git@ctu.cx>
Date: Mon, 9 Jun 2025 13:28:25 +0200
config/nixos/modules/networking: add `dn42`
2 files changed, 293 insertions(+), 0 deletions(-)
A
|
202
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
91
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/config/nixos/modules/networking/dn42.nix b/config/nixos/modules/networking/dn42.nix @@ -0,0 +1,202 @@ +{ + 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; }; + ownLinkLocalAddress = lib.mkOption { + type = types.str; + default = "fe80::6b61/64"; + }; + linkLocalAddress = lib.mkOption { type = types.str; }; + endpoint = lib.mkOption { + type = with types; nullOr str; + default = null; + }; + publicKey = lib.mkOption { type = types.str; }; + listenPort = lib.mkOption { type = types.int; }; + }; + }); + }; + }; + + config = lib.mkIf cfg.enable { + networking.firewall.allowedUDPPorts = lib.mapAttrsToList (name: peerConfig: peerConfig.listenPort) cfg.peerings; + + sops.secrets = lib.mapAttrs' (name: peerConfig: lib.nameValuePair "dn42/peerings/${name}/wgPrivateKey" { + owner = "systemd-network"; + group = "systemd-network"; + }) cfg.peerings; + + systemd.network = { + netdevs = lib.mapAttrs' (name: peerConfig: lib.nameValuePair "dn42${name}" { + netdevConfig = { + Kind = "wireguard"; + Name = "dn42${name}"; + }; + wireguardConfig = { + ListenPort = peerConfig.listenPort; + PrivateKeyFile = config.sops.secrets."dn42/peerings/${name}/wgPrivateKey".path; + }; + wireguardPeers = [ + { + PersistentKeepalive = 30; + Endpoint = lib.mkIf (!builtins.isNull peerConfig.endpoint) peerConfig.endpoint; + PublicKey = peerConfig.publicKey; + AllowedIPs = [ "fd00::/8" peerConfig.linkLocalAddress ]; + } + ]; + }) cfg.peerings; + + networks = lib.mapAttrs' (name: peerConfig: lib.nameValuePair "dn42${name}" { + matchConfig.Name = "dn42${name}"; + linkConfig.RequiredForOnline = "no"; + + address = [ peerConfig.ownLinkLocalAddress ]; + routes = [ { Destination = "fe80::6b61/128"; } ]; + + networkConfig = { + IPv6Forwarding = true; + IPv6AcceptRA = false; + DHCP = false; + }; + }) cfg.peerings; + }; + + systemd.services.updateDN42ROA = { + after = [ "network.target" ]; + before = [ "bird.service" ]; + startAt = [ "*-*-* */1:00:00" ]; + script = '' + mkdir -p /etc/bird/ + ${pkgs.curl}/bin/curl -sfSLR {-o,-z}/etc/bird/roa_dn42.conf https://dn42.burble.com/roa/dn42_roa_bird2_6.conf + ${pkgs.bird3}/bin/birdc c + ${pkgs.bird3}/bin/birdc reload filters all + ''; + }; + + 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 OWNNETv6 = ${cfg.range}; + define OWNNETSETv6 = [ ${cfg.range} ]; + define OWNIPv6 = ${cfg.address}; + + router id ${toString cfg.routerId}; + hostname "${config.networking.hostName}"; + + protocol device { + scan time 10; + } + + function is_self_net_v6() -> bool { + return net ~ OWNNETSETv6; + } + + function is_valid_network_v6() -> bool { + return net ~ [ + fd00::/8{44,64} # ULA address space as per RFC 4193 + ]; + } + + roa6 table dn42_roa_v6; + + protocol static { + roa6 { table dn42_roa_v6; }; + include "/etc/bird/roa_dn42.conf"; + }; + + # dn42 default route + protocol static { + route OWNNETv6 unreachable; + + ipv6 { + import all; + export none; + }; + } + + protocol kernel { + scan time 20; + + ipv6 { + import none; + export filter { + if source = RTS_STATIC then reject; # dont export static routes + krt_prefsrc = OWNIPv6; # preferred outgoing source address + accept; + }; + }; + }; + + template bgp dnpeers { + local as OWNAS; + path metric 1; + + ipv6 { + import filter { + if is_valid_network_v6() && !is_self_net_v6() then { + if (roa_check(dn42_roa_v6, 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; + } else accept; + } else reject; + }; + + export filter { if is_valid_network_v6() && source ~ [RTS_STATIC, RTS_BGP] then accept; else reject; }; + import limit 9000 action block; + }; + } + '' + (lib.concatStringsSep "\n" (lib.mapAttrsToList (name: peerConfig: '' + protocol bgp ${name} from dnpeers { + neighbor ${peerConfig.linkLocalAddress}%dn42${name} as ${toString peerConfig.asn}; + enable extended messages; + } + '') cfg.peerings)); + }; + }; + +}
diff --git a/hosts/novus/dn42.nix b/hosts/novus/dn42.nix @@ -0,0 +1,91 @@ +{ hostConfig, ... }: + +{ + + dns.zones."zaphyra.eu".subdomains."router-a.dn42".AAAA = [ hostConfig.networking.ip6Address ]; + + modules.networking.dn42 = { + enable = true; + routerId = 42171801; + asn = 4242421718; + address = "fd6b:6174:6a61::1"; + range = "fd6b:6174:6a61::/48"; + peerings = { + # void = { + # asn = 4242420575; + # linkLocalAddress = "fe80::497a"; + # endpoint = "gw.srv.eukaryote.eu:49508"; + # publicKey = ""; + # listenPort = 51821; + # }; + # tech9 = { + # asn = 4242421588; + # ownLinkLocalAddress = "fe80::100/64"; + # linkLocalAddress = "fe80::1588"; + # endpoint = "de-nue01.dn42.tech9.io:57730"; + # publicKey = "4JoJgCZWITUFAq7ol8/DZzJOuYzebveqMWPk9qQEHRU="; + # listenPort = 51822; + # }; + kioubit = { + asn = 4242423914; + linkLocalAddress = "fe80::ade0"; + endpoint = "de2.g-load.eu:21718"; + publicKey = "B1xSG/XTJRLd+GrWDsB06BqnIq8Xud93YVh/LYYYtUY="; + listenPort = 51823; + }; + pleiades = { + asn = 4242420069; + linkLocalAddress = "fe80::706c:6569:6164:6573"; + endpoint = "central.net.nojus.org:21718"; + publicKey = "1YAga5Bhreysf/XmhOnDGh3FmbN3Mp2jZjMSAQb/TEM="; + listenPort = 51824; + }; + echonet = { + asn = 4242420714; + linkLocalAddress = "fe80::718"; + publicKey = "NxYj58YhWf0JXC+pQAHfh3saUkQSII0lBTDvYGe5kw4="; + listenPort = 51825; + }; + tbspace = { + asn = 76190; + linkLocalAddress = "fe80::1299:e"; + endpoint = "dn42.tbspace.de:49158"; + publicKey = "NW8IeEmAXmwYMuMlvrb9Zpkcko6bzotDlYtGePtgzQE="; + listenPort = 51826; + }; + antibldg = { + asn = 4242421403; + linkLocalAddress = "fe80::1234:9320"; + endpoint = "zaphyra.dn42.antibuild.ing:15569"; + publicKey = "vambITMGGpA7kxCRGFlY1X36bevxXYELT/ORNgZ72ms="; + listenPort = 51827; + }; + dahlabandon = { + asn = 4242420814; + linkLocalAddress = "fe80::1718"; + endpoint = "cargobridge25.iron-bear.de:1718"; + publicKey = "+tg4bDDwfyQZSw0x8x9Ye2tDWPZ/VAf+KTAE1QLaKEI="; + listenPort = 51828; + }; + }; + }; + + systemd.network = { + netdevs.dn42 = { + netdevConfig = { + Kind = "dummy"; + Name = "dn42"; + }; + }; + + networks.dn42 = { + matchConfig.Name = "dn42"; + linkConfig.RequiredForOnline = "no"; + address = [ "fd6b:6174:6a61::1/64" ]; + networkConfig = { + IPv6Forwarding = true; + }; + }; + }; + +}