mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-12-25 23:23:07 +00:00
c4228b6c8f
The networkd backend logic for setting DHCP= on an interface is bugged and inconsistent with the scripted logic. Consider this simple NixOS configuration: { networking.useNetworkd = true; networking.interfaces.eth0.wakeOnLan.enable = true; } The default value of networking.useDHCP is true, so we expect our eth0 interface to have DHCP enabled. With the scripted backend, this works. But the networkd backend generates the following 40-eth0.network file: [Match] Name=eth0 [Network] DHCP=no IPv6PrivacyExtensions=kernel This is happening because the wakeOnLan configuration creates a key in networking.interfaces, and the networkd backend erroneously checks that instead of for explicitly configured IP addresses as in the scripted backend. The documentation is also inconsistent across various options. This change aligns the networkd backend and option documentation to the actual behavior of the scripted backend, and updates a test to account for this behavior for both backends.
1066 lines
38 KiB
Nix
1066 lines
38 KiB
Nix
{ system ? builtins.currentSystem
|
|
, config ? {}
|
|
, pkgs ? import ../.. { inherit system config; }
|
|
# bool: whether to use networkd in the tests
|
|
, networkd }:
|
|
|
|
with import ../lib/testing-python.nix { inherit system pkgs; };
|
|
with pkgs.lib;
|
|
|
|
let
|
|
qemu-common = import ../lib/qemu-common.nix { inherit (pkgs) lib pkgs; };
|
|
|
|
router = { config, pkgs, lib, ... }:
|
|
with pkgs.lib;
|
|
let
|
|
vlanIfs = range 1 (length config.virtualisation.vlans);
|
|
in {
|
|
environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules
|
|
virtualisation.vlans = [ 1 2 3 ];
|
|
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
|
|
networking = {
|
|
useDHCP = false;
|
|
useNetworkd = networkd;
|
|
firewall.checkReversePath = true;
|
|
firewall.allowedUDPPorts = [ 547 ];
|
|
interfaces = mkOverride 0 (listToAttrs (forEach vlanIfs (n:
|
|
nameValuePair "eth${toString n}" {
|
|
ipv4.addresses = [ { address = "192.168.${toString n}.1"; prefixLength = 24; } ];
|
|
ipv6.addresses = [ { address = "fd00:1234:5678:${toString n}::1"; prefixLength = 64; } ];
|
|
})));
|
|
};
|
|
services.kea = {
|
|
dhcp4 = {
|
|
enable = true;
|
|
settings = {
|
|
interfaces-config = {
|
|
interfaces = map (n: "eth${toString n}") vlanIfs;
|
|
dhcp-socket-type = "raw";
|
|
service-sockets-require-all = true;
|
|
service-sockets-max-retries = 5;
|
|
service-sockets-retry-wait-time = 2500;
|
|
};
|
|
subnet4 = map (n: {
|
|
id = n;
|
|
subnet = "192.168.${toString n}.0/24";
|
|
pools = [{ pool = "192.168.${toString n}.3 - 192.168.${toString n}.254"; }];
|
|
option-data = [{ name = "routers"; data = "192.168.${toString n}.1"; }];
|
|
|
|
reservations = [{
|
|
hw-address = qemu-common.qemuNicMac n 1;
|
|
hostname = "client${toString n}";
|
|
ip-address = "192.168.${toString n}.2";
|
|
}];
|
|
}) vlanIfs;
|
|
};
|
|
};
|
|
dhcp6 = {
|
|
enable = true;
|
|
settings = {
|
|
interfaces-config = {
|
|
interfaces = map (n: "eth${toString n}") vlanIfs;
|
|
service-sockets-require-all = true;
|
|
service-sockets-max-retries = 5;
|
|
service-sockets-retry-wait-time = 2500;
|
|
};
|
|
|
|
subnet6 = map (n: {
|
|
id = n;
|
|
subnet = "fd00:1234:5678:${toString n}::/64";
|
|
interface = "eth${toString n}";
|
|
pools = [{ pool = "fd00:1234:5678:${toString n}::2-fd00:1234:5678:${toString n}::2"; }];
|
|
}) vlanIfs;
|
|
};
|
|
};
|
|
};
|
|
services.radvd = {
|
|
enable = true;
|
|
config = flip concatMapStrings vlanIfs (n: ''
|
|
interface eth${toString n} {
|
|
AdvSendAdvert on;
|
|
AdvManagedFlag on;
|
|
AdvOtherConfigFlag on;
|
|
|
|
prefix fd00:1234:5678:${toString n}::/64 {
|
|
AdvAutonomous off;
|
|
};
|
|
};
|
|
'');
|
|
};
|
|
};
|
|
|
|
testCases = {
|
|
loopback = {
|
|
name = "Loopback";
|
|
nodes.client = { pkgs, ... }: with pkgs.lib; {
|
|
networking.useDHCP = false;
|
|
networking.useNetworkd = networkd;
|
|
};
|
|
testScript = ''
|
|
start_all()
|
|
client.wait_for_unit("network.target")
|
|
loopback_addresses = client.succeed("ip addr show lo")
|
|
assert "inet 127.0.0.1/8" in loopback_addresses
|
|
assert "inet6 ::1/128" in loopback_addresses
|
|
'';
|
|
};
|
|
static = {
|
|
name = "Static";
|
|
nodes.router = router;
|
|
nodes.client = { pkgs, ... }: with pkgs.lib; {
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
virtualisation.interfaces.enp2s0.vlan = 2;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
defaultGateway = "192.168.1.1";
|
|
defaultGateway6 = "fd00:1234:5678:1::1";
|
|
interfaces.enp1s0.ipv4.addresses = [
|
|
{ address = "192.168.1.2"; prefixLength = 24; }
|
|
{ address = "192.168.1.3"; prefixLength = 32; }
|
|
{ address = "192.168.1.10"; prefixLength = 32; }
|
|
];
|
|
interfaces.enp2s0.ipv4.addresses = [
|
|
{ address = "192.168.2.2"; prefixLength = 24; }
|
|
];
|
|
};
|
|
};
|
|
testScript = { ... }:
|
|
''
|
|
start_all()
|
|
|
|
client.wait_for_unit("network.target")
|
|
router.wait_for_unit("network-online.target")
|
|
|
|
with subtest("Make sure DHCP server is not started"):
|
|
client.fail("systemctl status kea-dhcp4-server.service")
|
|
client.fail("systemctl status kea-dhcp6-server.service")
|
|
|
|
with subtest("Test vlan 1"):
|
|
client.wait_until_succeeds("ping -c 1 192.168.1.1")
|
|
client.wait_until_succeeds("ping -c 1 192.168.1.2")
|
|
client.wait_until_succeeds("ping -c 1 192.168.1.3")
|
|
client.wait_until_succeeds("ping -c 1 192.168.1.10")
|
|
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.1")
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.2")
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.3")
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.10")
|
|
|
|
with subtest("Test vlan 2"):
|
|
client.wait_until_succeeds("ping -c 1 192.168.2.1")
|
|
client.wait_until_succeeds("ping -c 1 192.168.2.2")
|
|
|
|
router.wait_until_succeeds("ping -c 1 192.168.2.1")
|
|
router.wait_until_succeeds("ping -c 1 192.168.2.2")
|
|
|
|
with subtest("Test default gateway"):
|
|
router.wait_until_succeeds("ping -c 1 192.168.3.1")
|
|
client.wait_until_succeeds("ping -c 1 192.168.3.1")
|
|
router.wait_until_succeeds("ping -c 1 fd00:1234:5678:3::1")
|
|
client.wait_until_succeeds("ping -c 1 fd00:1234:5678:3::1")
|
|
'';
|
|
};
|
|
routeType = {
|
|
name = "RouteType";
|
|
nodes.client = { pkgs, ... }: with pkgs.lib; {
|
|
networking = {
|
|
useDHCP = false;
|
|
useNetworkd = networkd;
|
|
interfaces.eth1.ipv4.routes = [{
|
|
address = "192.168.1.127";
|
|
prefixLength = 32;
|
|
type = "local";
|
|
}];
|
|
};
|
|
};
|
|
testScript = ''
|
|
start_all()
|
|
client.wait_for_unit("network.target")
|
|
client.succeed("ip -4 route list table local | grep 'local 192.168.1.127'")
|
|
'';
|
|
};
|
|
dhcpDefault = {
|
|
name = "useDHCP-by-default";
|
|
nodes.router = router;
|
|
nodes.client = { lib, ... }: {
|
|
# Disable test driver default config
|
|
networking.interfaces = lib.mkForce {
|
|
# Make sure DHCP defaults correctly even when some unrelated config
|
|
# is set on the interface (nothing, in this case).
|
|
enp1s0 = {};
|
|
};
|
|
networking.useNetworkd = networkd;
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
};
|
|
testScript = ''
|
|
start_all()
|
|
client.wait_for_unit("multi-user.target")
|
|
client.wait_until_succeeds("ip addr show dev enp1s0 | grep '192.168.1'")
|
|
client.shell_interact()
|
|
client.succeed("ping -c 1 192.168.1.1")
|
|
router.succeed("ping -c 1 192.168.1.1")
|
|
router.succeed("ping -c 1 192.168.1.2")
|
|
client.succeed("ping -c 1 192.168.1.2")
|
|
'';
|
|
};
|
|
dhcpSimple = {
|
|
name = "SimpleDHCP";
|
|
nodes.router = router;
|
|
nodes.client = { pkgs, ... }: with pkgs.lib; {
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
virtualisation.interfaces.enp2s0.vlan = 2;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
interfaces.enp1s0.useDHCP = true;
|
|
interfaces.enp2s0.useDHCP = true;
|
|
};
|
|
};
|
|
testScript = { ... }:
|
|
''
|
|
start_all()
|
|
|
|
client.wait_for_unit("network.target")
|
|
router.wait_for_unit("network-online.target")
|
|
|
|
with subtest("Wait until we have an ip address on each interface"):
|
|
client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
|
|
client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'")
|
|
client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q '192.168.2'")
|
|
client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q 'fd00:1234:5678:2:'")
|
|
|
|
with subtest("Test vlan 1"):
|
|
client.wait_until_succeeds("ping -c 1 192.168.1.1")
|
|
client.wait_until_succeeds("ping -c 1 192.168.1.2")
|
|
client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
|
|
client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2")
|
|
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.1")
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.2")
|
|
router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
|
|
router.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::2")
|
|
|
|
with subtest("Test vlan 2"):
|
|
client.wait_until_succeeds("ping -c 1 192.168.2.1")
|
|
client.wait_until_succeeds("ping -c 1 192.168.2.2")
|
|
client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1")
|
|
client.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2")
|
|
|
|
router.wait_until_succeeds("ping -c 1 192.168.2.1")
|
|
router.wait_until_succeeds("ping -c 1 192.168.2.2")
|
|
router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::1")
|
|
router.wait_until_succeeds("ping -c 1 fd00:1234:5678:2::2")
|
|
'';
|
|
};
|
|
dhcpOneIf = {
|
|
name = "OneInterfaceDHCP";
|
|
nodes.router = router;
|
|
nodes.client = { pkgs, ... }: with pkgs.lib; {
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
virtualisation.interfaces.enp2s0.vlan = 2;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
interfaces.enp1s0 = {
|
|
mtu = 1343;
|
|
useDHCP = true;
|
|
};
|
|
};
|
|
};
|
|
testScript = { ... }:
|
|
''
|
|
start_all()
|
|
|
|
with subtest("Wait for networking to come up"):
|
|
client.wait_for_unit("network.target")
|
|
router.wait_for_unit("network.target")
|
|
|
|
with subtest("Wait until we have an ip address on each interface"):
|
|
client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
|
|
|
|
with subtest("ensure MTU is set"):
|
|
assert "mtu 1343" in client.succeed("ip link show dev enp1s0")
|
|
|
|
with subtest("Test vlan 1"):
|
|
client.wait_until_succeeds("ping -c 1 192.168.1.1")
|
|
client.wait_until_succeeds("ping -c 1 192.168.1.2")
|
|
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.1")
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.2")
|
|
|
|
with subtest("Test vlan 2"):
|
|
client.wait_until_succeeds("ping -c 1 192.168.2.1")
|
|
client.fail("ping -c 1 192.168.2.2")
|
|
|
|
router.wait_until_succeeds("ping -c 1 192.168.2.1")
|
|
router.fail("ping -c 1 192.168.2.2")
|
|
'';
|
|
};
|
|
bond = let
|
|
node = address: { pkgs, ... }: with pkgs.lib; {
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
virtualisation.interfaces.enp2s0.vlan = 2;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
bonds.bond0 = {
|
|
interfaces = [ "enp1s0" "enp2s0" ];
|
|
driverOptions.mode = "802.3ad";
|
|
};
|
|
interfaces.bond0.ipv4.addresses = mkOverride 0
|
|
[ { inherit address; prefixLength = 30; } ];
|
|
};
|
|
};
|
|
in {
|
|
name = "Bond";
|
|
nodes.client1 = node "192.168.1.1";
|
|
nodes.client2 = node "192.168.1.2";
|
|
testScript = { ... }:
|
|
''
|
|
start_all()
|
|
|
|
with subtest("Wait for networking to come up"):
|
|
client1.wait_for_unit("network.target")
|
|
client2.wait_for_unit("network.target")
|
|
|
|
with subtest("Test bonding"):
|
|
client1.wait_until_succeeds("ping -c 2 192.168.1.1")
|
|
client1.wait_until_succeeds("ping -c 2 192.168.1.2")
|
|
|
|
client2.wait_until_succeeds("ping -c 2 192.168.1.1")
|
|
client2.wait_until_succeeds("ping -c 2 192.168.1.2")
|
|
|
|
with subtest("Verify bonding mode"):
|
|
for client in client1, client2:
|
|
client.succeed('grep -q "Bonding Mode: IEEE 802.3ad Dynamic link aggregation" /proc/net/bonding/bond0')
|
|
'';
|
|
};
|
|
bridge = let
|
|
node = { address, vlan }: { pkgs, ... }: with pkgs.lib; {
|
|
virtualisation.interfaces.enp1s0.vlan = vlan;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
interfaces.enp1s0.ipv4.addresses = [ { inherit address; prefixLength = 24; } ];
|
|
};
|
|
};
|
|
in {
|
|
name = "Bridge";
|
|
nodes.client1 = node { address = "192.168.1.2"; vlan = 1; };
|
|
nodes.client2 = node { address = "192.168.1.3"; vlan = 2; };
|
|
nodes.router = { pkgs, ... }: with pkgs.lib; {
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
virtualisation.interfaces.enp2s0.vlan = 2;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
bridges.bridge.interfaces = [ "enp1s0" "enp2s0" ];
|
|
interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
|
|
interfaces.eth2.ipv4.addresses = mkOverride 0 [ ];
|
|
interfaces.bridge.ipv4.addresses = mkOverride 0
|
|
[ { address = "192.168.1.1"; prefixLength = 24; } ];
|
|
};
|
|
};
|
|
testScript = { ... }:
|
|
''
|
|
start_all()
|
|
|
|
with subtest("Wait for networking to come up"):
|
|
for machine in client1, client2, router:
|
|
machine.wait_for_unit("network.target")
|
|
|
|
with subtest("Test bridging"):
|
|
client1.wait_until_succeeds("ping -c 1 192.168.1.1")
|
|
client1.wait_until_succeeds("ping -c 1 192.168.1.2")
|
|
client1.wait_until_succeeds("ping -c 1 192.168.1.3")
|
|
|
|
client2.wait_until_succeeds("ping -c 1 192.168.1.1")
|
|
client2.wait_until_succeeds("ping -c 1 192.168.1.2")
|
|
client2.wait_until_succeeds("ping -c 1 192.168.1.3")
|
|
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.1")
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.2")
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.3")
|
|
'';
|
|
};
|
|
macvlan = {
|
|
name = "MACVLAN";
|
|
nodes.router = router;
|
|
nodes.client = { pkgs, ... }: with pkgs.lib; {
|
|
environment.systemPackages = [ pkgs.iptables ]; # to debug firewall rules
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
firewall.logReversePathDrops = true; # to debug firewall rules
|
|
# reverse path filtering rules for the macvlan interface seem
|
|
# to be incorrect, causing the test to fail. Disable temporarily.
|
|
firewall.checkReversePath = false;
|
|
macvlans.macvlan.interface = "enp1s0";
|
|
interfaces.enp1s0.useDHCP = true;
|
|
interfaces.macvlan.useDHCP = true;
|
|
};
|
|
};
|
|
testScript = { ... }:
|
|
''
|
|
start_all()
|
|
|
|
with subtest("Wait for networking to come up"):
|
|
client.wait_for_unit("network.target")
|
|
router.wait_for_unit("network.target")
|
|
|
|
with subtest("Wait until we have an ip address on each interface"):
|
|
client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q '192.168.1'")
|
|
client.wait_until_succeeds("ip addr show dev macvlan | grep -q '192.168.1'")
|
|
|
|
with subtest("Print lots of diagnostic information"):
|
|
router.log("**********************************************")
|
|
router.succeed("ip addr >&2")
|
|
router.succeed("ip route >&2")
|
|
router.execute("iptables-save >&2")
|
|
client.log("==============================================")
|
|
client.succeed("ip addr >&2")
|
|
client.succeed("ip route >&2")
|
|
client.execute("iptables-save >&2")
|
|
client.log("##############################################")
|
|
|
|
with subtest("Test macvlan creates routable ips"):
|
|
client.wait_until_succeeds("ping -c 1 192.168.1.1")
|
|
client.wait_until_succeeds("ping -c 1 192.168.1.2")
|
|
client.wait_until_succeeds("ping -c 1 192.168.1.3")
|
|
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.1")
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.2")
|
|
router.wait_until_succeeds("ping -c 1 192.168.1.3")
|
|
'';
|
|
};
|
|
fou = {
|
|
name = "foo-over-udp";
|
|
nodes.machine = { ... }: {
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
interfaces.enp1s0.ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
|
|
fooOverUDP = {
|
|
fou1 = { port = 9001; };
|
|
fou2 = { port = 9002; protocol = 41; };
|
|
fou3 = mkIf (!networkd)
|
|
{ port = 9003; local.address = "192.168.1.1"; };
|
|
fou4 = mkIf (!networkd)
|
|
{ port = 9004; local = { address = "192.168.1.1"; dev = "enp1s0"; }; };
|
|
};
|
|
};
|
|
systemd.services = {
|
|
fou3-fou-encap.after = optional (!networkd) "network-addresses-enp1s0.service";
|
|
};
|
|
};
|
|
testScript = { ... }:
|
|
''
|
|
import json
|
|
|
|
machine.wait_for_unit("network.target")
|
|
fous = json.loads(machine.succeed("ip -json fou show"))
|
|
assert {"port": 9001, "gue": None, "family": "inet"} in fous, "fou1 exists"
|
|
assert {"port": 9002, "ipproto": 41, "family": "inet"} in fous, "fou2 exists"
|
|
'' + optionalString (!networkd) ''
|
|
assert {
|
|
"port": 9003,
|
|
"gue": None,
|
|
"family": "inet",
|
|
"local": "192.168.1.1",
|
|
} in fous, "fou3 exists"
|
|
assert {
|
|
"port": 9004,
|
|
"gue": None,
|
|
"family": "inet",
|
|
"local": "192.168.1.1",
|
|
"dev": "enp1s0",
|
|
} in fous, "fou4 exists"
|
|
'';
|
|
};
|
|
sit = let
|
|
node = { address4, remote, address6 }: { pkgs, ... }: with pkgs.lib; {
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
sits.sit = {
|
|
inherit remote;
|
|
local = address4;
|
|
dev = "enp1s0";
|
|
};
|
|
interfaces.enp1s0.ipv4.addresses = mkOverride 0
|
|
[ { address = address4; prefixLength = 24; } ];
|
|
interfaces.sit.ipv6.addresses = mkOverride 0
|
|
[ { address = address6; prefixLength = 64; } ];
|
|
};
|
|
};
|
|
in {
|
|
name = "Sit";
|
|
# note on firewalling: the two nodes are explicitly asymmetric.
|
|
# client1 sends SIT packets in UDP, but accepts only proto-41 incoming.
|
|
# client2 does the reverse, sending in proto-41 and accepting only UDP incoming.
|
|
# that way we'll notice when either SIT itself or FOU breaks.
|
|
nodes.client1 = args@{ pkgs, ... }:
|
|
mkMerge [
|
|
(node { address4 = "192.168.1.1"; remote = "192.168.1.2"; address6 = "fc00::1"; } args)
|
|
{
|
|
networking = {
|
|
firewall.extraCommands = "iptables -A INPUT -p 41 -j ACCEPT";
|
|
sits.sit.encapsulation = { type = "fou"; port = 9001; };
|
|
};
|
|
}
|
|
];
|
|
nodes.client2 = args@{ pkgs, ... }:
|
|
mkMerge [
|
|
(node { address4 = "192.168.1.2"; remote = "192.168.1.1"; address6 = "fc00::2"; } args)
|
|
{
|
|
networking = {
|
|
firewall.allowedUDPPorts = [ 9001 ];
|
|
fooOverUDP.fou1 = { port = 9001; protocol = 41; };
|
|
};
|
|
}
|
|
];
|
|
testScript = { ... }:
|
|
''
|
|
start_all()
|
|
|
|
with subtest("Wait for networking to be configured"):
|
|
client1.wait_for_unit("network.target")
|
|
client2.wait_for_unit("network.target")
|
|
|
|
# Print diagnostic information
|
|
client1.succeed("ip addr >&2")
|
|
client2.succeed("ip addr >&2")
|
|
|
|
with subtest("Test ipv6"):
|
|
client1.wait_until_succeeds("ping -c 1 fc00::1")
|
|
client1.wait_until_succeeds("ping -c 1 fc00::2")
|
|
|
|
client2.wait_until_succeeds("ping -c 1 fc00::1")
|
|
client2.wait_until_succeeds("ping -c 1 fc00::2")
|
|
'';
|
|
};
|
|
gre = let
|
|
node = { pkgs, ... }: with pkgs.lib; {
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
firewall.extraCommands = "ip6tables -A nixos-fw -p gre -j nixos-fw-accept";
|
|
};
|
|
};
|
|
in {
|
|
name = "GRE";
|
|
nodes.client1 = args@{ pkgs, ... }:
|
|
mkMerge [
|
|
(node args)
|
|
{
|
|
virtualisation.vlans = [ 1 2 4 ];
|
|
networking = {
|
|
greTunnels = {
|
|
greTunnel = {
|
|
local = "192.168.2.1";
|
|
remote = "192.168.2.2";
|
|
dev = "eth2";
|
|
ttl = 225;
|
|
type = "tap";
|
|
};
|
|
gre6Tunnel = {
|
|
local = "fd00:1234:5678:4::1";
|
|
remote = "fd00:1234:5678:4::2";
|
|
dev = "eth3";
|
|
ttl = 255;
|
|
type = "tun6";
|
|
};
|
|
};
|
|
bridges.bridge.interfaces = [ "greTunnel" "eth1" ];
|
|
interfaces.eth1.ipv4.addresses = mkOverride 0 [];
|
|
interfaces.bridge.ipv4.addresses = mkOverride 0 [
|
|
{ address = "192.168.1.1"; prefixLength = 24; }
|
|
];
|
|
interfaces.eth3.ipv6.addresses = [
|
|
{ address = "fd00:1234:5678:4::1"; prefixLength = 64; }
|
|
];
|
|
interfaces.gre6Tunnel.ipv6.addresses = mkOverride 0 [
|
|
{ address = "fc00::1"; prefixLength = 64; }
|
|
];
|
|
};
|
|
}
|
|
];
|
|
nodes.client2 = args@{ pkgs, ... }:
|
|
mkMerge [
|
|
(node args)
|
|
{
|
|
virtualisation.vlans = [ 2 3 4 ];
|
|
networking = {
|
|
greTunnels = {
|
|
greTunnel = {
|
|
local = "192.168.2.2";
|
|
remote = "192.168.2.1";
|
|
dev = "eth1";
|
|
ttl = 225;
|
|
type = "tap";
|
|
};
|
|
gre6Tunnel = {
|
|
local = "fd00:1234:5678:4::2";
|
|
remote = "fd00:1234:5678:4::1";
|
|
dev = "eth3";
|
|
ttl = 255;
|
|
type = "tun6";
|
|
};
|
|
};
|
|
bridges.bridge.interfaces = [ "greTunnel" "eth2" ];
|
|
interfaces.eth2.ipv4.addresses = mkOverride 0 [];
|
|
interfaces.bridge.ipv4.addresses = mkOverride 0 [
|
|
{ address = "192.168.1.2"; prefixLength = 24; }
|
|
];
|
|
interfaces.eth3.ipv6.addresses = [
|
|
{ address = "fd00:1234:5678:4::2"; prefixLength = 64; }
|
|
];
|
|
interfaces.gre6Tunnel.ipv6.addresses = mkOverride 0 [
|
|
{ address = "fc00::2"; prefixLength = 64; }
|
|
];
|
|
};
|
|
}
|
|
];
|
|
testScript = { ... }:
|
|
''
|
|
import json
|
|
start_all()
|
|
|
|
with subtest("Wait for networking to be configured"):
|
|
client1.wait_for_unit("network.target")
|
|
client2.wait_for_unit("network.target")
|
|
|
|
# Print diagnostic information
|
|
client1.succeed("ip addr >&2")
|
|
client2.succeed("ip addr >&2")
|
|
|
|
with subtest("Test GRE tunnel bridge over VLAN"):
|
|
client1.wait_until_succeeds("ping -c 1 192.168.1.2")
|
|
|
|
client2.wait_until_succeeds("ping -c 1 192.168.1.1")
|
|
|
|
client1.wait_until_succeeds("ping -c 1 fc00::2")
|
|
|
|
client2.wait_until_succeeds("ping -c 1 fc00::1")
|
|
|
|
with subtest("Test GRE tunnel TTL"):
|
|
links = json.loads(client1.succeed("ip -details -json link show greTunnel"))
|
|
assert links[0]['linkinfo']['info_data']['ttl'] == 225, "ttl not set for greTunnel"
|
|
|
|
links = json.loads(client2.succeed("ip -details -json link show gre6Tunnel"))
|
|
assert links[0]['linkinfo']['info_data']['ttl'] == 255, "ttl not set for gre6Tunnel"
|
|
'';
|
|
};
|
|
vlan = let
|
|
node = address: { pkgs, ... }: with pkgs.lib; {
|
|
#virtualisation.vlans = [ 1 ];
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
vlans.vlan = {
|
|
id = 1;
|
|
interface = "eth0";
|
|
};
|
|
interfaces.eth0.ipv4.addresses = mkOverride 0 [ ];
|
|
interfaces.eth1.ipv4.addresses = mkOverride 0 [ ];
|
|
interfaces.vlan.ipv4.addresses = mkOverride 0
|
|
[ { inherit address; prefixLength = 24; } ];
|
|
};
|
|
};
|
|
in {
|
|
name = "vlan";
|
|
nodes.client1 = node "192.168.1.1";
|
|
nodes.client2 = node "192.168.1.2";
|
|
testScript = { ... }:
|
|
''
|
|
start_all()
|
|
|
|
with subtest("Wait for networking to be configured"):
|
|
client1.wait_for_unit("network.target")
|
|
client2.wait_for_unit("network.target")
|
|
|
|
with subtest("Test vlan is setup"):
|
|
client1.succeed("ip addr show dev vlan >&2")
|
|
client2.succeed("ip addr show dev vlan >&2")
|
|
'';
|
|
};
|
|
vlan-ping = let
|
|
baseIP = number: "10.10.10.${number}";
|
|
vlanIP = number: "10.1.1.${number}";
|
|
baseInterface = "enp1s0";
|
|
vlanInterface = "vlan42";
|
|
node = number: {pkgs, ... }: with pkgs.lib; {
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
networking = {
|
|
#useNetworkd = networkd;
|
|
useDHCP = false;
|
|
vlans.${vlanInterface} = { id = 42; interface = baseInterface; };
|
|
interfaces.${baseInterface}.ipv4.addresses = mkOverride 0 [{ address = baseIP number; prefixLength = 24; }];
|
|
interfaces.${vlanInterface}.ipv4.addresses = mkOverride 0 [{ address = vlanIP number; prefixLength = 24; }];
|
|
};
|
|
};
|
|
|
|
serverNodeNum = "1";
|
|
clientNodeNum = "2";
|
|
|
|
in {
|
|
name = "vlan-ping";
|
|
nodes.server = node serverNodeNum;
|
|
nodes.client = node clientNodeNum;
|
|
testScript = { ... }:
|
|
''
|
|
start_all()
|
|
|
|
with subtest("Wait for networking to be configured"):
|
|
server.wait_for_unit("network.target")
|
|
client.wait_for_unit("network.target")
|
|
|
|
with subtest("Test ping on base interface in setup"):
|
|
client.succeed("ping -I ${baseInterface} -c 1 ${baseIP serverNodeNum}")
|
|
server.succeed("ping -I ${baseInterface} -c 1 ${baseIP clientNodeNum}")
|
|
|
|
with subtest("Test ping on vlan subinterface in setup"):
|
|
client.succeed("ping -I ${vlanInterface} -c 1 ${vlanIP serverNodeNum}")
|
|
server.succeed("ping -I ${vlanInterface} -c 1 ${vlanIP clientNodeNum}")
|
|
'';
|
|
};
|
|
virtual = {
|
|
name = "Virtual";
|
|
nodes.machine = {
|
|
networking.useNetworkd = networkd;
|
|
networking.useDHCP = false;
|
|
networking.interfaces.tap0 = {
|
|
ipv4.addresses = [ { address = "192.168.1.1"; prefixLength = 24; } ];
|
|
ipv6.addresses = [ { address = "2001:1470:fffd:2096::"; prefixLength = 64; } ];
|
|
virtual = true;
|
|
mtu = 1342;
|
|
macAddress = "02:de:ad:be:ef:01";
|
|
};
|
|
networking.interfaces.tun0 = {
|
|
ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
|
|
ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
|
|
virtual = true;
|
|
mtu = 1343;
|
|
};
|
|
};
|
|
|
|
testScript = ''
|
|
targetList = """
|
|
tap0: tap persist user 0
|
|
tun0: tun persist user 0
|
|
""".strip()
|
|
|
|
with subtest("Wait for networking to come up"):
|
|
machine.start()
|
|
machine.wait_for_unit("network.target")
|
|
|
|
with subtest("Test interfaces set up"):
|
|
list = machine.succeed("ip tuntap list | sort").strip()
|
|
assert (
|
|
list == targetList
|
|
), """
|
|
The list of virtual interfaces does not match the expected one:
|
|
Result:
|
|
{}
|
|
Expected:
|
|
{}
|
|
""".format(
|
|
list, targetList
|
|
)
|
|
with subtest("Test MTU and MAC Address are configured"):
|
|
machine.wait_until_succeeds("ip link show dev tap0 | grep 'mtu 1342'")
|
|
machine.wait_until_succeeds("ip link show dev tun0 | grep 'mtu 1343'")
|
|
assert "02:de:ad:be:ef:01" in machine.succeed("ip link show dev tap0")
|
|
'' # network-addresses-* only exist in scripted networking
|
|
+ optionalString (!networkd) ''
|
|
with subtest("Test interfaces clean up"):
|
|
machine.succeed("systemctl stop network-addresses-tap0")
|
|
machine.sleep(10)
|
|
machine.succeed("systemctl stop network-addresses-tun0")
|
|
machine.sleep(10)
|
|
residue = machine.succeed("ip tuntap list")
|
|
assert (
|
|
residue == ""
|
|
), "Some virtual interface has not been properly cleaned:\n{}".format(residue)
|
|
'';
|
|
};
|
|
privacy = {
|
|
name = "Privacy";
|
|
nodes.router = { ... }: {
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = true;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
interfaces.enp1s0.ipv6.addresses = singleton {
|
|
address = "fd00:1234:5678:1::1";
|
|
prefixLength = 64;
|
|
};
|
|
};
|
|
services.radvd = {
|
|
enable = true;
|
|
config = ''
|
|
interface enp1s0 {
|
|
AdvSendAdvert on;
|
|
AdvManagedFlag on;
|
|
AdvOtherConfigFlag on;
|
|
|
|
prefix fd00:1234:5678:1::/64 {
|
|
AdvAutonomous on;
|
|
AdvOnLink on;
|
|
};
|
|
};
|
|
'';
|
|
};
|
|
};
|
|
nodes.client_with_privacy = { pkgs, ... }: with pkgs.lib; {
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
interfaces.enp1s0 = {
|
|
tempAddress = "default";
|
|
ipv4.addresses = mkOverride 0 [ ];
|
|
ipv6.addresses = mkOverride 0 [ ];
|
|
useDHCP = true;
|
|
};
|
|
};
|
|
};
|
|
nodes.client = { pkgs, ... }: with pkgs.lib; {
|
|
virtualisation.interfaces.enp1s0.vlan = 1;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
interfaces.enp1s0 = {
|
|
tempAddress = "enabled";
|
|
ipv4.addresses = mkOverride 0 [ ];
|
|
ipv6.addresses = mkOverride 0 [ ];
|
|
useDHCP = true;
|
|
};
|
|
};
|
|
};
|
|
testScript = { ... }:
|
|
''
|
|
start_all()
|
|
|
|
client.wait_for_unit("network.target")
|
|
client_with_privacy.wait_for_unit("network.target")
|
|
router.wait_for_unit("network-online.target")
|
|
|
|
with subtest("Wait until we have an ip address"):
|
|
client_with_privacy.wait_until_succeeds(
|
|
"ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'"
|
|
)
|
|
client.wait_until_succeeds("ip addr show dev enp1s0 | grep -q 'fd00:1234:5678:1:'")
|
|
|
|
with subtest("Test vlan 1"):
|
|
client_with_privacy.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
|
|
client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
|
|
|
|
with subtest("Test address used is temporary"):
|
|
client_with_privacy.wait_until_succeeds(
|
|
"! ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"
|
|
)
|
|
|
|
with subtest("Test address used is EUI-64"):
|
|
client.wait_until_succeeds(
|
|
"ip route get fd00:1234:5678:1::1 | grep -q ':[a-f0-9]*ff:fe[a-f0-9]*:'"
|
|
)
|
|
'';
|
|
};
|
|
routes = {
|
|
name = "routes";
|
|
nodes.machine = {
|
|
networking.useNetworkd = networkd;
|
|
networking.useDHCP = false;
|
|
networking.interfaces.eth0 = {
|
|
ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
|
|
ipv6.addresses = [ { address = "2001:1470:fffd:2097::"; prefixLength = 64; } ];
|
|
ipv6.routes = [
|
|
{ address = "fdfd:b3f0::"; prefixLength = 48; }
|
|
{ address = "2001:1470:fffd:2098::"; prefixLength = 64; via = "fdfd:b3f0::1"; }
|
|
];
|
|
ipv4.routes = [
|
|
{ address = "10.0.0.0"; prefixLength = 16; options = {
|
|
mtu = "1500";
|
|
# Explicitly set scope because iproute and systemd-networkd
|
|
# disagree on what the scope should be
|
|
# if the type is the default "unicast"
|
|
scope = "link";
|
|
}; }
|
|
{ address = "192.168.2.0"; prefixLength = 24; via = "192.168.1.1"; }
|
|
];
|
|
};
|
|
virtualisation.vlans = [ ];
|
|
};
|
|
|
|
testScript = ''
|
|
targetIPv4Table = [
|
|
"10.0.0.0/16 proto static scope link mtu 1500",
|
|
"192.168.1.0/24 proto kernel scope link src 192.168.1.2",
|
|
"192.168.2.0/24 via 192.168.1.1 proto static",
|
|
]
|
|
|
|
targetIPv6Table = [
|
|
"2001:1470:fffd:2097::/64 proto kernel metric 256 pref medium",
|
|
"2001:1470:fffd:2098::/64 via fdfd:b3f0::1 proto static metric 1024 pref medium",
|
|
"fdfd:b3f0::/48 proto static metric 1024 pref medium",
|
|
]
|
|
|
|
machine.start()
|
|
machine.wait_for_unit("network.target")
|
|
|
|
with subtest("test routing tables"):
|
|
ipv4Table = machine.succeed("ip -4 route list dev eth0 | head -n3").strip()
|
|
ipv6Table = machine.succeed("ip -6 route list dev eth0 | head -n3").strip()
|
|
assert [
|
|
l.strip() for l in ipv4Table.splitlines()
|
|
] == targetIPv4Table, """
|
|
The IPv4 routing table does not match the expected one:
|
|
Result:
|
|
{}
|
|
Expected:
|
|
{}
|
|
""".format(
|
|
ipv4Table, targetIPv4Table
|
|
)
|
|
assert [
|
|
l.strip() for l in ipv6Table.splitlines()
|
|
] == targetIPv6Table, """
|
|
The IPv6 routing table does not match the expected one:
|
|
Result:
|
|
{}
|
|
Expected:
|
|
{}
|
|
""".format(
|
|
ipv6Table, targetIPv6Table
|
|
)
|
|
|
|
'' + optionalString (!networkd) ''
|
|
with subtest("test clean-up of the tables"):
|
|
machine.succeed("systemctl stop network-addresses-eth0")
|
|
ipv4Residue = machine.succeed("ip -4 route list dev eth0 | head -n-3").strip()
|
|
ipv6Residue = machine.succeed("ip -6 route list dev eth0 | head -n-3").strip()
|
|
assert (
|
|
ipv4Residue == ""
|
|
), "The IPv4 routing table has not been properly cleaned:\n{}".format(ipv4Residue)
|
|
assert (
|
|
ipv6Residue == ""
|
|
), "The IPv6 routing table has not been properly cleaned:\n{}".format(ipv6Residue)
|
|
'';
|
|
};
|
|
rename = if networkd then {
|
|
name = "RenameInterface";
|
|
nodes.machine = { pkgs, ... }: {
|
|
virtualisation.vlans = [ 1 ];
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
};
|
|
systemd.network.links."10-custom_name" = {
|
|
matchConfig.MACAddress = "52:54:00:12:01:01";
|
|
linkConfig.Name = "custom_name";
|
|
};
|
|
};
|
|
testScript = ''
|
|
machine.succeed("udevadm settle")
|
|
print(machine.succeed("ip link show dev custom_name"))
|
|
'';
|
|
} else {
|
|
name = "RenameInterface";
|
|
nodes = { };
|
|
testScript = "";
|
|
};
|
|
# even with disabled networkd, systemd.network.links should work
|
|
# (as it's handled by udev, not networkd)
|
|
link = {
|
|
name = "Link";
|
|
nodes.client = { pkgs, ... }: {
|
|
virtualisation.vlans = [ 1 ];
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
};
|
|
systemd.network.links."50-foo" = {
|
|
matchConfig = {
|
|
Name = "foo";
|
|
Driver = "dummy";
|
|
};
|
|
linkConfig.MTUBytes = "1442";
|
|
};
|
|
};
|
|
testScript = ''
|
|
print(client.succeed("ip l add name foo type dummy"))
|
|
print(client.succeed("stat /etc/systemd/network/50-foo.link"))
|
|
client.succeed("udevadm settle")
|
|
assert "mtu 1442" in client.succeed("ip l show dev foo")
|
|
'';
|
|
};
|
|
wlanInterface = let
|
|
testMac = "06:00:00:00:02:00";
|
|
in {
|
|
name = "WlanInterface";
|
|
nodes.machine = { pkgs, ... }: {
|
|
boot.kernelModules = [ "mac80211_hwsim" ];
|
|
networking.wlanInterfaces = {
|
|
wlan0 = { device = "wlan0"; };
|
|
wap0 = { device = "wlan0"; mac = testMac; };
|
|
};
|
|
};
|
|
testScript = ''
|
|
machine.start()
|
|
machine.wait_for_unit("network.target")
|
|
machine.wait_until_succeeds("ip address show wap0 | grep -q ${testMac}")
|
|
machine.fail("ip address show wlan0 | grep -q ${testMac}")
|
|
'';
|
|
};
|
|
naughtyInterfaceNames = let
|
|
ifnames = [
|
|
# flags of ip-address
|
|
"home" "temporary" "optimistic"
|
|
"bridge_slave" "flush"
|
|
# flags of ip-route
|
|
"up" "type" "nomaster" "address"
|
|
# other
|
|
"very_loong_name" "lowerUpper" "-"
|
|
];
|
|
in {
|
|
name = "naughtyInterfaceNames";
|
|
nodes.machine = { pkgs, ... }: {
|
|
networking.useNetworkd = networkd;
|
|
networking.bridges = listToAttrs
|
|
(flip map ifnames
|
|
(name: { inherit name; value.interfaces = []; }));
|
|
};
|
|
testScript = ''
|
|
machine.start()
|
|
machine.wait_for_unit("network.target")
|
|
for ifname in ${builtins.toJSON ifnames}:
|
|
machine.wait_until_succeeds(f"ip link show dev '{ifname}' | grep -q '{ifname}'")
|
|
'';
|
|
};
|
|
caseSensitiveRenaming = {
|
|
name = "CaseSensitiveRenaming";
|
|
nodes.machine = { pkgs, ... }: {
|
|
virtualisation.interfaces.enCustom.vlan = 11;
|
|
networking = {
|
|
useNetworkd = networkd;
|
|
useDHCP = false;
|
|
};
|
|
};
|
|
testScript = ''
|
|
machine.succeed("udevadm settle")
|
|
print(machine.succeed("ip link show dev enCustom"))
|
|
machine.wait_until_succeeds("ip link show dev enCustom | grep -q 52:54:00:12:0b:01")
|
|
'';
|
|
};
|
|
};
|
|
|
|
in mapAttrs (const (attrs: makeTest (attrs // {
|
|
name = "${attrs.name}-Networking-${if networkd then "Networkd" else "Scripted"}";
|
|
}))) testCases
|