Merge pull request #78476 from Ma27/networkd-vrf-options
nixos/networkd: add vrfConfig option for netdevs, add simple test
This commit is contained in:
commit
355b31c98e
@ -55,6 +55,11 @@ let
|
|||||||
(assertMacAddress "MACAddress")
|
(assertMacAddress "MACAddress")
|
||||||
];
|
];
|
||||||
|
|
||||||
|
checkVRF = checkUnitConfig "VRF" [
|
||||||
|
(assertOnlyFields [ "Table" ])
|
||||||
|
(assertMinimum "Table" 0)
|
||||||
|
];
|
||||||
|
|
||||||
# NOTE The PrivateKey directive is missing on purpose here, please
|
# NOTE The PrivateKey directive is missing on purpose here, please
|
||||||
# do not add it to this list. The nix store is world-readable let's
|
# do not add it to this list. The nix store is world-readable let's
|
||||||
# refrain ourselves from providing a footgun.
|
# refrain ourselves from providing a footgun.
|
||||||
@ -349,6 +354,21 @@ let
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
vrfConfig = mkOption {
|
||||||
|
default = {};
|
||||||
|
example = { Table = 2342; };
|
||||||
|
type = types.addCheck (types.attrsOf unitOption) checkVRF;
|
||||||
|
description = ''
|
||||||
|
Each attribute in this set specifies an option in the
|
||||||
|
<literal>[VRF]</literal> section of the unit. See
|
||||||
|
<citerefentry><refentrytitle>systemd.netdev</refentrytitle>
|
||||||
|
<manvolnum>5</manvolnum></citerefentry> for details.
|
||||||
|
A detailed explanation about how VRFs work can be found in the
|
||||||
|
<link xlink:href="https://www.kernel.org/doc/Documentation/networking/vrf.txt">kernel
|
||||||
|
docs</link>.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
wireguardConfig = mkOption {
|
wireguardConfig = mkOption {
|
||||||
default = {};
|
default = {};
|
||||||
example = {
|
example = {
|
||||||
@ -844,6 +864,11 @@ let
|
|||||||
[Xfrm]
|
[Xfrm]
|
||||||
${attrsToSection def.xfrmConfig}
|
${attrsToSection def.xfrmConfig}
|
||||||
|
|
||||||
|
''}
|
||||||
|
${optionalString (def.vrfConfig != { }) ''
|
||||||
|
[VRF]
|
||||||
|
${attrsToSection def.vrfConfig}
|
||||||
|
|
||||||
''}
|
''}
|
||||||
${optionalString (def.wireguardConfig != { }) ''
|
${optionalString (def.wireguardConfig != { }) ''
|
||||||
[WireGuard]
|
[WireGuard]
|
||||||
|
@ -275,6 +275,7 @@ in
|
|||||||
systemd-analyze = handleTest ./systemd-analyze.nix {};
|
systemd-analyze = handleTest ./systemd-analyze.nix {};
|
||||||
systemd-confinement = handleTest ./systemd-confinement.nix {};
|
systemd-confinement = handleTest ./systemd-confinement.nix {};
|
||||||
systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
|
systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
|
||||||
|
systemd-networkd-vrf = handleTest ./systemd-networkd-vrf.nix {};
|
||||||
systemd-networkd-wireguard = handleTest ./systemd-networkd-wireguard.nix {};
|
systemd-networkd-wireguard = handleTest ./systemd-networkd-wireguard.nix {};
|
||||||
systemd-nspawn = handleTest ./systemd-nspawn.nix {};
|
systemd-nspawn = handleTest ./systemd-nspawn.nix {};
|
||||||
pdns-recursor = handleTest ./pdns-recursor.nix {};
|
pdns-recursor = handleTest ./pdns-recursor.nix {};
|
||||||
|
221
nixos/tests/systemd-networkd-vrf.nix
Normal file
221
nixos/tests/systemd-networkd-vrf.nix
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
import ./make-test-python.nix ({ pkgs, lib, ... }: let
|
||||||
|
inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
|
||||||
|
in {
|
||||||
|
name = "systemd-networkd-vrf";
|
||||||
|
meta.maintainers = with lib.maintainers; [ ma27 ];
|
||||||
|
|
||||||
|
nodes = {
|
||||||
|
client = { pkgs, ... }: {
|
||||||
|
virtualisation.vlans = [ 1 2 ];
|
||||||
|
|
||||||
|
networking = {
|
||||||
|
useDHCP = false;
|
||||||
|
useNetworkd = true;
|
||||||
|
firewall.checkReversePath = "loose";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.network = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
netdevs."10-vrf1" = {
|
||||||
|
netdevConfig = {
|
||||||
|
Kind = "vrf";
|
||||||
|
Name = "vrf1";
|
||||||
|
MTUBytes = "1300";
|
||||||
|
};
|
||||||
|
vrfConfig.Table = 23;
|
||||||
|
};
|
||||||
|
netdevs."10-vrf2" = {
|
||||||
|
netdevConfig = {
|
||||||
|
Kind = "vrf";
|
||||||
|
Name = "vrf2";
|
||||||
|
MTUBytes = "1300";
|
||||||
|
};
|
||||||
|
vrfConfig.Table = 42;
|
||||||
|
};
|
||||||
|
|
||||||
|
networks."10-vrf1" = {
|
||||||
|
matchConfig.Name = "vrf1";
|
||||||
|
networkConfig.IPForward = "yes";
|
||||||
|
routes = [
|
||||||
|
{ routeConfig = { Destination = "192.168.1.2"; Metric = "100"; }; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
networks."10-vrf2" = {
|
||||||
|
matchConfig.Name = "vrf2";
|
||||||
|
networkConfig.IPForward = "yes";
|
||||||
|
routes = [
|
||||||
|
{ routeConfig = { Destination = "192.168.2.3"; Metric = "100"; }; }
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
networks."10-eth1" = {
|
||||||
|
matchConfig.Name = "eth1";
|
||||||
|
linkConfig.RequiredForOnline = "no";
|
||||||
|
networkConfig = {
|
||||||
|
VRF = "vrf1";
|
||||||
|
Address = "192.168.1.1";
|
||||||
|
IPForward = "yes";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
networks."10-eth2" = {
|
||||||
|
matchConfig.Name = "eth2";
|
||||||
|
linkConfig.RequiredForOnline = "no";
|
||||||
|
networkConfig = {
|
||||||
|
VRF = "vrf2";
|
||||||
|
Address = "192.168.2.1";
|
||||||
|
IPForward = "yes";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
node1 = { pkgs, ... }: {
|
||||||
|
virtualisation.vlans = [ 1 ];
|
||||||
|
networking = {
|
||||||
|
useDHCP = false;
|
||||||
|
useNetworkd = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.openssh.enable = true;
|
||||||
|
users.users.root.openssh.authorizedKeys.keys = [ snakeOilPublicKey ];
|
||||||
|
|
||||||
|
systemd.network = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
networks."10-eth1" = {
|
||||||
|
matchConfig.Name = "eth1";
|
||||||
|
linkConfig.RequiredForOnline = "no";
|
||||||
|
networkConfig = {
|
||||||
|
Address = "192.168.1.2";
|
||||||
|
IPForward = "yes";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
node2 = { pkgs, ... }: {
|
||||||
|
virtualisation.vlans = [ 2 ];
|
||||||
|
networking = {
|
||||||
|
useDHCP = false;
|
||||||
|
useNetworkd = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.network = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
networks."10-eth2" = {
|
||||||
|
matchConfig.Name = "eth2";
|
||||||
|
linkConfig.RequiredForOnline = "no";
|
||||||
|
networkConfig = {
|
||||||
|
Address = "192.168.2.3";
|
||||||
|
IPForward = "yes";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
node3 = { pkgs, ... }: {
|
||||||
|
virtualisation.vlans = [ 2 ];
|
||||||
|
networking = {
|
||||||
|
useDHCP = false;
|
||||||
|
useNetworkd = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.network = {
|
||||||
|
enable = true;
|
||||||
|
|
||||||
|
networks."10-eth2" = {
|
||||||
|
matchConfig.Name = "eth2";
|
||||||
|
linkConfig.RequiredForOnline = "no";
|
||||||
|
networkConfig = {
|
||||||
|
Address = "192.168.2.4";
|
||||||
|
IPForward = "yes";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
def compare_tables(expected, actual):
|
||||||
|
assert (
|
||||||
|
expected == actual
|
||||||
|
), """
|
||||||
|
Routing tables don't match!
|
||||||
|
Expected:
|
||||||
|
{}
|
||||||
|
Actual:
|
||||||
|
{}
|
||||||
|
""".format(
|
||||||
|
expected, actual
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
start_all()
|
||||||
|
|
||||||
|
client.wait_for_unit("network.target")
|
||||||
|
node1.wait_for_unit("network.target")
|
||||||
|
node2.wait_for_unit("network.target")
|
||||||
|
node3.wait_for_unit("network.target")
|
||||||
|
|
||||||
|
client_ipv4_table = """
|
||||||
|
192.168.1.2 dev vrf1 proto static metric 100
|
||||||
|
192.168.2.3 dev vrf2 proto static metric 100
|
||||||
|
""".strip()
|
||||||
|
vrf1_table = """
|
||||||
|
broadcast 192.168.1.0 dev eth1 proto kernel scope link src 192.168.1.1
|
||||||
|
192.168.1.0/24 dev eth1 proto kernel scope link src 192.168.1.1
|
||||||
|
local 192.168.1.1 dev eth1 proto kernel scope host src 192.168.1.1
|
||||||
|
broadcast 192.168.1.255 dev eth1 proto kernel scope link src 192.168.1.1
|
||||||
|
""".strip()
|
||||||
|
vrf2_table = """
|
||||||
|
broadcast 192.168.2.0 dev eth2 proto kernel scope link src 192.168.2.1
|
||||||
|
192.168.2.0/24 dev eth2 proto kernel scope link src 192.168.2.1
|
||||||
|
local 192.168.2.1 dev eth2 proto kernel scope host src 192.168.2.1
|
||||||
|
broadcast 192.168.2.255 dev eth2 proto kernel scope link src 192.168.2.1
|
||||||
|
""".strip()
|
||||||
|
|
||||||
|
# Check that networkd properly configures the main routing table
|
||||||
|
# and the routing tables for the VRF.
|
||||||
|
with subtest("check vrf routing tables"):
|
||||||
|
compare_tables(
|
||||||
|
client_ipv4_table, client.succeed("ip -4 route list | head -n2").strip()
|
||||||
|
)
|
||||||
|
compare_tables(
|
||||||
|
vrf1_table, client.succeed("ip -4 route list table 23 | head -n4").strip()
|
||||||
|
)
|
||||||
|
compare_tables(
|
||||||
|
vrf2_table, client.succeed("ip -4 route list table 42 | head -n4").strip()
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure that other nodes are reachable via ICMP through the VRF.
|
||||||
|
with subtest("icmp through vrf works"):
|
||||||
|
client.succeed("ping -c5 192.168.1.2")
|
||||||
|
client.succeed("ping -c5 192.168.2.3")
|
||||||
|
|
||||||
|
# Test whether SSH through a VRF IP is possible.
|
||||||
|
# (Note: this seems to be an issue on Linux 5.x, so I decided to add this to
|
||||||
|
# ensure that we catch this when updating the default kernel).
|
||||||
|
with subtest("tcp traffic through vrf works"):
|
||||||
|
node1.wait_for_open_port(22)
|
||||||
|
client.succeed(
|
||||||
|
"cat ${snakeOilPrivateKey} > privkey.snakeoil"
|
||||||
|
)
|
||||||
|
client.succeed("chmod 600 privkey.snakeoil")
|
||||||
|
client.succeed(
|
||||||
|
"ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i privkey.snakeoil root@192.168.1.2 true"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Only configured routes through the VRF from the main routing table should
|
||||||
|
# work. Additional IPs are only reachable when binding to the vrf interface.
|
||||||
|
with subtest("only routes from main routing table work by default"):
|
||||||
|
client.fail("ping -c5 192.168.2.4")
|
||||||
|
client.succeed("ping -I vrf2 -c5 192.168.2.4")
|
||||||
|
|
||||||
|
client.shutdown()
|
||||||
|
node1.shutdown()
|
||||||
|
node2.shutdown()
|
||||||
|
node3.shutdown()
|
||||||
|
'';
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user