# Routing daemon configuration { hostName, config, options, lib, ... }: let hostConf = config.site.hosts.${hostName}; isUpstream = builtins.any (net: hostConf.interfaces.${net}.upstream != null ) (builtins.attrNames hostConf.interfaces); # Configuring a gateway? If so, this is the associated net. gatewayNet = let m = builtins.match "(.+)-gw" hostName; in if m == [ "cls" ] then "cluster" else if m == null then null else builtins.head m; enumerate = n: list: if list == [] then [] else [ { n = n; x = builtins.head list; } ] ++ (enumerate (n + 1) (builtins.tail list)); in { services.bird2 = { enable = true; config = '' router id ${config.site.net.core.hosts4.${hostName}}; protocol kernel K4 { learn; ipv4 { export all; ${lib.optionalString isUpstream '' # Learn the default route import filter { if net ~ [ 0.0.0.0/0 ] then { accept; } reject; }; ''} }; } protocol kernel K6 { learn; ipv6 { export all; ${lib.optionalString isUpstream '' # Learn the default route import filter { if net ~ [ ::/0 ] then { accept; } reject; }; ''} }; } protocol device { scan time 10; } ${lib.optionalString (builtins.match "anon.*" hostName != null) '' ipv4 table vpn4_table; protocol pipe { table master4; peer table vpn4_table; export filter { if net ~ [ 0.0.0.0/0 ] then { # Copy default route to vpn4 table accept; } reject; }; } # Routing table for Wireguard transport protocol kernel VPN4 { # "vpn4_table" configured on anon routers kernel table 100; ipv4 { table vpn4_table; export all; }; } ''} ${lib.optionalString (gatewayNet != null) '' # Router advertisements protocol radv { rdnss ${config.site.net.serv.hosts6.dn42.dnscache}; interface "${gatewayNet}" { min ra interval 10; max ra interval 60; ${builtins.concatStringsSep "\n" ( map (subnet6: '' prefix ${subnet6} { preferred lifetime 600; valid lifetime 1800; }; '') (builtins.attrValues config.site.net.${gatewayNet}.subnets6) )} dnssl "${config.site.net.${gatewayNet}.domainName}"; }; } ''} # OSPFv2 for site-local IPv4 protocol ospf v2 ZW4 { ipv4 { export all; import filter { if net ~ [ 0.0.0.0/0 ] then { ${(builtins.foldl' (result: gateway: { text = '' ${result.text} if ospf_router_id = ${config.site.net.core.hosts4.${gateway}} then { preference = preference + ${toString result.n}; accept; } ''; n = result.n - 10; }) { text = ""; n = 900; } config.site.hosts.${hostName}.ospf.allowedUpstreams ).text} reject; } accept; }; }; area 0 { # Enabled on these networks networks { ${builtins.concatStringsSep " " ( map (n: " ${n};") config.site.ospf.networks4 )} }; ${builtins.concatStringsSep "\n" ( builtins.attrValues ( builtins.mapAttrs (net: _: # Enable OSPF only on networks with a secret. Others # are treated as a stubnet whose routes to # advertise. if config.site.net.${net}.ospf.secret != null then '' interface "${net}" { authentication cryptographic; password "${config.site.net.${net}.ospf.secret}"; }; '' else if config.site.net.${net}.subnet4 != null then '' # Advertise route of network ${net} stubnet ${config.site.net.${net}.subnet4} {}; '' else "" ) hostConf.interfaces ) )} ${builtins.concatStringsSep "\n" ( map (stubnet4: '' # Advertise additional route stubnet ${stubnet4} {}; '') hostConf.ospf.stubNets4 )} }; } # OSPFv3 for site-local IPv6 protocol ospf v3 ZW6 { ipv6 { export all; import filter { if net ~ [ ::/0 ] then { ${(builtins.foldl' (result: gateway: { text = '' ${result.text} if ospf_router_id = ${config.site.net.core.hosts4.${gateway}} then { preference = preference + ${toString result.n}; accept; } ''; n = result.n - 10; }) { text = ""; n = 900; } config.site.hosts.${hostName}.ospf.allowedUpstreams ).text} reject; } accept; }; }; area 0 { # Enabled on these networks networks { ${builtins.concatStringsSep " " ( map (n: " ${n};") config.site.ospf.networks6 )} }; ${builtins.concatStringsSep "\n" ( builtins.attrValues ( builtins.mapAttrs (net: _: # Enable OSPF only on networks with a secret. Others # are treated as a stubnet whose routes to # advertise. if config.site.net.${net}.ospf.secret != null then '' interface "${net}" { # TODO: enable when all bird 1.x have shut down #authentication cryptographic; #password "${config.site.net.${net}.ospf.secret}"; }; '' else builtins.concatStringsSep "\n" ( map (subnet6: '' # Advertise route of network ${net} stubnet ${subnet6} {}; '') (builtins.attrValues config.site.net.${net}.subnets6) ) ) hostConf.interfaces ) )} ${builtins.concatStringsSep "\n" ( map (stubnet6: '' # Advertise additional route stubnet ${stubnet6} {}; '') hostConf.ospf.stubNets6 )} }; } # Zentralwerk DN42 protocol static { ipv4; route 172.20.72.0/21 unreachable; } protocol static { ipv6; route fd23:42:c3d2:580::/57 unreachable; } # Static Vodafone protocol static { ipv6; route 2a02:8106:208:5200::/56 unreachable; route 2a02:8106:211:e900::/56 unreachable; } ${lib.optionalString (hostConf.bgp != null) '' template bgp bgppeer { local as ${toString hostConf.bgp.asn}; ipv4 { import all; export where source=RTS_STATIC; }; ipv6 { import all; export where source=RTS_STATIC; }; } ${builtins.concatStringsSep "\n" ( map ({ n, x }: let peer = x; peerConf = hostConf.bgp.peers.${peer}; in '' protocol bgp bgp_${toString n} from bgppeer { neighbor ${peer} as ${toString peerConf.asn}; } '' ) (enumerate 1 (builtins.attrNames hostConf.bgp.peers)) )} ''} ''; }; }