diff --git a/nixos/modules/services/networking/bird.nix b/nixos/modules/services/networking/bird.nix index c14adbda3c5a..fc06cdaa6e58 100644 --- a/nixos/modules/services/networking/bird.nix +++ b/nixos/modules/services/networking/bird.nix @@ -31,7 +31,23 @@ let default = true; description = '' Whether the config should be checked at build time. - Disabling this might become necessary if the config includes files not present during build time. + When the config can't be checked during build time, for example when it includes + other files, either disable this option or use preCheckConfig to create + the included files before checking. + ''; + }; + preCheckConfig = mkOption { + type = types.lines; + default = ""; + example = '' + echo "cost 100;" > include.conf + ''; + description = '' + Commands to execute before the config file check. The file to be checked will be + available as ${variant}.conf in the current directory. + + Files created with this option will not be available at service runtime, only during + build time checking. ''; }; }; @@ -45,7 +61,9 @@ let name = "${variant}.conf"; text = cfg.config; checkPhase = optionalString cfg.checkConfig '' - ${pkg}/bin/${birdBin} -d -p -c $out + ln -s $out ${variant}.conf + ${cfg.preCheckConfig} + ${pkg}/bin/${birdBin} -d -p -c ${variant}.conf ''; }; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 2f6e1dc9898f..62bc8acef606 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -46,6 +46,7 @@ in beanstalkd = handleTest ./beanstalkd.nix {}; bees = handleTest ./bees.nix {}; bind = handleTest ./bind.nix {}; + bird = handleTest ./bird.nix {}; bitcoind = handleTest ./bitcoind.nix {}; bittorrent = handleTest ./bittorrent.nix {}; blockbook-frontend = handleTest ./blockbook-frontend.nix {}; diff --git a/nixos/tests/bird.nix b/nixos/tests/bird.nix new file mode 100644 index 000000000000..50d397be14ee --- /dev/null +++ b/nixos/tests/bird.nix @@ -0,0 +1,205 @@ +# This test does a basic functionality check for all bird variants and demonstrates a use +# of the preCheckConfig option. + +{ system ? builtins.currentSystem +, pkgs ? import ../.. { inherit system; config = { }; } +}: + +let + inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest; + inherit (pkgs.lib) optionalString; + + hostShared = hostId: { pkgs, ... }: { + virtualisation.vlans = [ 1 ]; + + environment.systemPackages = with pkgs; [ jq ]; + + networking = { + useNetworkd = true; + useDHCP = false; + firewall.enable = false; + }; + + systemd.network.networks."01-eth1" = { + name = "eth1"; + networkConfig.Address = "10.0.0.${hostId}/24"; + }; + }; + + birdTest = v4: + let variant = "bird${optionalString (!v4) "6"}"; in + makeTest { + name = variant; + + nodes.host1 = makeBirdHost variant "1"; + nodes.host2 = makeBirdHost variant "2"; + + testScript = makeTestScript variant v4 (!v4); + }; + + bird2Test = makeTest { + name = "bird2"; + + nodes.host1 = makeBird2Host "1"; + nodes.host2 = makeBird2Host "2"; + + testScript = makeTestScript "bird2" true true; + }; + + makeTestScript = variant: v4: v6: '' + start_all() + + host1.wait_for_unit("${variant}.service") + host2.wait_for_unit("${variant}.service") + + ${optionalString v4 '' + with subtest("Waiting for advertised IPv4 routes"): + host1.wait_until_succeeds("ip --json r | jq -e 'map(select(.dst == \"10.10.0.2\")) | any'") + host2.wait_until_succeeds("ip --json r | jq -e 'map(select(.dst == \"10.10.0.1\")) | any'") + ''} + ${optionalString v6 '' + with subtest("Waiting for advertised IPv6 routes"): + host1.wait_until_succeeds("ip --json -6 r | jq -e 'map(select(.dst == \"fdff::2\")) | any'") + host2.wait_until_succeeds("ip --json -6 r | jq -e 'map(select(.dst == \"fdff::1\")) | any'") + ''} + + with subtest("Check fake routes in preCheckConfig do not exists"): + ${optionalString v4 ''host1.fail("ip --json r | jq -e 'map(select(.dst == \"1.2.3.4\")) | any'")''} + ${optionalString v4 ''host2.fail("ip --json r | jq -e 'map(select(.dst == \"1.2.3.4\")) | any'")''} + + ${optionalString v6 ''host1.fail("ip --json -6 r | jq -e 'map(select(.dst == \"fd00::\")) | any'")''} + ${optionalString v6 ''host2.fail("ip --json -6 r | jq -e 'map(select(.dst == \"fd00::\")) | any'")''} + ''; + + makeBirdHost = variant: hostId: { pkgs, ... }: { + imports = [ (hostShared hostId) ]; + + services.${variant} = { + enable = true; + + config = '' + log syslog all; + + debug protocols all; + + router id 10.0.0.${hostId}; + + protocol device { + } + + protocol kernel { + import none; + export all; + } + + protocol static { + include "static.conf"; + } + + protocol ospf { + export all; + area 0 { + interface "eth1" { + hello 5; + wait 5; + }; + }; + } + ''; + + preCheckConfig = + let + route = { bird = "1.2.3.4/32"; bird6 = "fd00::/128"; }.${variant}; + in + ''echo "route ${route} blackhole;" > static.conf''; + }; + + systemd.tmpfiles.rules = + let + route = { bird = "10.10.0.${hostId}/32"; bird6 = "fdff::${hostId}/128"; }.${variant}; + in + [ "f /etc/bird/static.conf - - - - route ${route} blackhole;" ]; + }; + + makeBird2Host = hostId: { pkgs, ... }: { + imports = [ (hostShared hostId) ]; + + services.bird2 = { + enable = true; + + config = '' + log syslog all; + + debug protocols all; + + router id 10.0.0.${hostId}; + + protocol device { + } + + protocol kernel kernel4 { + ipv4 { + import none; + export all; + }; + } + + protocol static static4 { + ipv4; + include "static4.conf"; + } + + protocol ospf v2 ospf4 { + ipv4 { + export all; + }; + area 0 { + interface "eth1" { + hello 5; + wait 5; + }; + }; + } + + protocol kernel kernel6 { + ipv6 { + import none; + export all; + }; + } + + protocol static static6 { + ipv6; + include "static6.conf"; + } + + protocol ospf v3 ospf6 { + ipv6 { + export all; + }; + area 0 { + interface "eth1" { + hello 5; + wait 5; + }; + }; + } + ''; + + preCheckConfig = '' + echo "route 1.2.3.4/32 blackhole;" > static4.conf + echo "route fd00::/128 blackhole;" > static6.conf + ''; + }; + + systemd.tmpfiles.rules = [ + "f /etc/bird/static4.conf - - - - route 10.10.0.${hostId}/32 blackhole;" + "f /etc/bird/static6.conf - - - - route fdff::${hostId}/128 blackhole;" + ]; + }; +in +{ + bird = birdTest true; + bird6 = birdTest false; + bird2 = bird2Test; +} diff --git a/pkgs/servers/bird/default.nix b/pkgs/servers/bird/default.nix index bc8c8df3551b..9da7d7e948eb 100644 --- a/pkgs/servers/bird/default.nix +++ b/pkgs/servers/bird/default.nix @@ -1,4 +1,4 @@ -{ lib, stdenv, fetchurl, fetchpatch, flex, bison, readline, libssh }: +{ lib, stdenv, fetchurl, fetchpatch, flex, bison, readline, libssh, nixosTests }: with lib; @@ -34,6 +34,8 @@ let "--localstatedir=/var" ] ++ optional enableIPv6 "--enable-ipv6"; + passthru.tests = nixosTests.bird; + meta = { description = "BIRD Internet Routing Daemon"; homepage = "http://bird.network.cz";