network/nix/nixos-module/container/bird.nix

382 lines
12 KiB
Nix

# Routing daemon configuration
{ hostName, config, options, lib, ... }:
let
hostConf = config.site.hosts.${hostName};
isUpstream =
builtins.match "upstream.*" hostName != null ||
builtins.match "anon.*" hostName != null;
# 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 {
${lib.optionalString (!isUpstream) ''
export all;
''}
${lib.optionalString isUpstream ''
# Do not set another default route on upstreams
export where net != 0.0.0.0/0;
# Learn the upstream default route
import where net = 0.0.0.0/0;
''}
};
}
protocol kernel K6 {
learn;
ipv6 {
${lib.optionalString (!isUpstream) ''
export all;
''}
${lib.optionalString isUpstream ''
# Do not set another default route on upstreams
export where net != ::/0;
# Learn the upstream default route
import where net = ::/0;
''}
};
}
protocol device {
scan time 10;
}
${lib.optionalString (builtins.match "anon.*" hostName != null) ''
# BIRD routing table for Wireguard transport
ipv4 table vpn4_table;
# Kernel routing table for Wireguard transport
protocol kernel VPN4 {
# "vpn4_table" configured on anon routers
kernel table 100;
ipv4 {
export all;
table vpn4_table;
};
}
''}
${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 where net != 0.0.0.0/0;
};
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
)}
};
}
${lib.optionalString isUpstream ''
# OSPFv2 to advertise my default route
protocol ospf v2 ZW4_${hostName} {
ipv4 {
export where net = 0.0.0.0/0;
};
area ${config.site.net.core.hosts4.${hostName}} {
# 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 interfaces with a secret.
lib.optionalString (config.site.net.${net}.ospf.secret != null) ''
interface "${net}" instance 1 {
authentication cryptographic;
password "${config.site.net.${net}.ospf.secret}";
};
''
) hostConf.interfaces
)
)}
};
}
''}
${(
builtins.foldl' ({ text, instance }: upstream: {
text = ''
${text}
# OSPFv2 to receive a default route
protocol ospf v2 ZW4_${upstream} {
ipv4 {
export filter {
preference = preference + 100 - ${toString instance};
accept;
};
${lib.optionalString (builtins.match "anon.*" hostName != null) ''
table vpn4_table;
''}
};
area ${config.site.net.core.hosts4.${upstream}} {
# 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 interfaces with a secret.
lib.optionalString (config.site.net.${net}.ospf.secret != null) ''
interface "${net}" instance ${toString instance} {
authentication cryptographic;
password "${config.site.net.${net}.ospf.secret}";
};
''
) hostConf.interfaces
)
)}
};
}
'';
instance = instance + 1;
}) { text = ""; instance = 2; } config.site.hosts.${hostName}.ospf.allowedUpstreams
).text}
# OSPFv3 for site-local IPv6
protocol ospf v3 ZW6 {
ipv6 {
export where net != ::/0;
};
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
)}
};
}
${lib.optionalString isUpstream ''
# OSPFv3 to advertise my default route
protocol ospf v3 ZW6_${hostName} {
ipv6 {
export where net = ::/0;
};
area ${config.site.net.core.hosts4.${hostName}} {
# 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 interfaces with a secret.
lib.optionalString (config.site.net.${net}.ospf.secret != null) ''
interface "${net}" instance 1 {
authentication cryptographic;
password "${config.site.net.${net}.ospf.secret}";
};
''
) hostConf.interfaces
)
)}
};
}
''}
${lib.optionalString (builtins.match "anon.*" hostName != null) (
builtins.foldl' ({ text, instance }: upstream: {
text = ''
${text}
# OSPFv3 to receive a default route
protocol ospf v3 ZW6_${upstream} {
ipv6 {
export filter {
preference = preference + 100 - ${toString instance};
accept;
};
};
area ${config.site.net.core.hosts4.${upstream}} {
# 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 interfaces with a secret.
lib.optionalString (config.site.net.${net}.ospf.secret != null) ''
interface "${net}" instance ${toString instance} {
authentication cryptographic;
password "${config.site.net.${net}.ospf.secret}";
};
''
) hostConf.interfaces
)
)}
};
}
'';
instance = instance + 1;
}) { text = ""; instance = 2; } config.site.hosts.${hostName}.ospf.allowedUpstreams
).text}
# 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))
)}
''}
'';
};
}