containers: Do not wait for udev for network devices

Test that adding physical devices to containers works, find that network setup
then doesn't work because there is no udev in the container to tell systemd
that the device is present.
Fixed by not depending on the device in the container.

Activate the new container test for release

Bonds, bridges and other network devices need the underlying not as
dependency when used inside the container. Because the device is already
there.

But the address configuration needs the aggregated device itself.

+25 -8
nixos/modules/tasks/network-interfaces-scripted.nix
···
systemd.services =
let
networkLocalCommands = {
after = [ "network-setup.service" ];
bindsTo = [ "network-setup.service" ];
···
# order before network-setup because the routes that are configured
# there may need ip addresses configured
before = [ "network-setup.service" ];
-
bindsTo = [ (subsystemDevice i.name) ];
-
after = [ (subsystemDevice i.name) "network-pre.target" ];
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
path = [ pkgs.iproute ];
···
createBridgeDevice = n: v: nameValuePair "${n}-netdev"
(let
-
deps = map subsystemDevice v.interfaces;
in
{ description = "Bridge Interface ${n}";
wantedBy = [ "network-setup.service" (subsystemDevice n) ];
···
createVswitchDevice = n: v: nameValuePair "${n}-netdev"
(let
-
deps = map subsystemDevice v.interfaces;
ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
in
{ description = "Open vSwitch Interface ${n}";
···
createBondDevice = n: v: nameValuePair "${n}-netdev"
(let
-
deps = map subsystemDevice v.interfaces;
in
{ description = "Bond Interface ${n}";
wantedBy = [ "network-setup.service" (subsystemDevice n) ];
···
createMacvlanDevice = n: v: nameValuePair "${n}-netdev"
(let
-
deps = [ (subsystemDevice v.interface) ];
in
{ description = "Vlan Interface ${n}";
wantedBy = [ "network-setup.service" (subsystemDevice n) ];
···
createSitDevice = n: v: nameValuePair "${n}-netdev"
(let
-
deps = optional (v.dev != null) (subsystemDevice v.dev);
in
{ description = "6-to-4 Tunnel Interface ${n}";
wantedBy = [ "network-setup.service" (subsystemDevice n) ];
···
createVlanDevice = n: v: nameValuePair "${n}-netdev"
(let
-
deps = [ (subsystemDevice v.interface) ];
in
{ description = "Vlan Interface ${n}";
wantedBy = [ "network-setup.service" (subsystemDevice n) ];
···
systemd.services =
let
+
deviceDependency = dev:
+
if (config.boot.isContainer == false)
+
then
+
# Trust udev when not in the container
+
[ (subsystemDevice dev) ]
+
else
+
# When in the container, check whether the interface is built from other definitions
+
if (hasAttr dev cfg.bridges) ||
+
(hasAttr dev cfg.bonds) ||
+
(hasAttr dev cfg.macvlans) ||
+
(hasAttr dev cfg.sits) ||
+
(hasAttr dev cfg.vlans) ||
+
(hasAttr dev cfg.vswitches) ||
+
(hasAttr dev cfg.wlanInterfaces)
+
then [ "${dev}-netdev.service" ]
+
else [];
+
networkLocalCommands = {
after = [ "network-setup.service" ];
bindsTo = [ "network-setup.service" ];
···
# order before network-setup because the routes that are configured
# there may need ip addresses configured
before = [ "network-setup.service" ];
+
bindsTo = deviceDependency i.name;
+
after = [ "network-pre.target" ] ++ (deviceDependency i.name);
serviceConfig.Type = "oneshot";
serviceConfig.RemainAfterExit = true;
path = [ pkgs.iproute ];
···
createBridgeDevice = n: v: nameValuePair "${n}-netdev"
(let
+
deps = if config.boot.isContainer then [] else map subsystemDevice v.interfaces;
in
{ description = "Bridge Interface ${n}";
wantedBy = [ "network-setup.service" (subsystemDevice n) ];
···
createVswitchDevice = n: v: nameValuePair "${n}-netdev"
(let
+
deps = if config.boot.isContainer then [] else map subsystemDevice v.interfaces;
ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules;
in
{ description = "Open vSwitch Interface ${n}";
···
createBondDevice = n: v: nameValuePair "${n}-netdev"
(let
+
deps = if config.boot.isContainer then [] else map subsystemDevice v.interfaces;
in
{ description = "Bond Interface ${n}";
wantedBy = [ "network-setup.service" (subsystemDevice n) ];
···
createMacvlanDevice = n: v: nameValuePair "${n}-netdev"
(let
+
deps = deviceDependency v.interface;
in
{ description = "Vlan Interface ${n}";
wantedBy = [ "network-setup.service" (subsystemDevice n) ];
···
createSitDevice = n: v: nameValuePair "${n}-netdev"
(let
+
deps = optional (v.dev != null) (deviceDependency v.dev);
in
{ description = "6-to-4 Tunnel Interface ${n}";
wantedBy = [ "network-setup.service" (subsystemDevice n) ];
···
createVlanDevice = n: v: nameValuePair "${n}-netdev"
(let
+
deps = deviceDependency v.interface;
in
{ description = "Vlan Interface ${n}";
wantedBy = [ "network-setup.service" (subsystemDevice n) ];
+9 -4
nixos/modules/tasks/network-interfaces.nix
···
generate a random 32-bit ID using the following commands:
<literal>cksum /etc/machine-id | while read c rest; do printf "%x" $c; done</literal>
-
(this derives it from the machine-id that systemd generates) or
-
<literal>head -c4 /dev/urandom | od -A none -t x4</literal>
'';
};
···
'';
};
} // (listToAttrs (flip map interfaces (i:
nameValuePair "network-link-${i.name}"
{ description = "Link configuration of ${i.name}";
wantedBy = [ "network-interfaces.target" ];
before = [ "network-interfaces.target" ];
-
bindsTo = [ (subsystemDevice i.name) ];
-
after = [ (subsystemDevice i.name) "network-pre.target" ];
path = [ pkgs.iproute ];
serviceConfig = {
Type = "oneshot";
···
generate a random 32-bit ID using the following commands:
<literal>cksum /etc/machine-id | while read c rest; do printf "%x" $c; done</literal>
+
(this derives it from the machine-id that systemd generates) or
+
<literal>head -c4 /dev/urandom | od -A none -t x4</literal>
'';
};
···
'';
};
} // (listToAttrs (flip map interfaces (i:
+
let
+
deviceDependency = if config.boot.isContainer
+
then []
+
else [ (subsystemDevice i.name) ];
+
in
nameValuePair "network-link-${i.name}"
{ description = "Link configuration of ${i.name}";
wantedBy = [ "network-interfaces.target" ];
before = [ "network-interfaces.target" ];
+
bindsTo = deviceDependency;
+
after = [ "network-pre.target" ] ++ deviceDependency;
path = [ pkgs.iproute ];
serviceConfig = {
Type = "oneshot";
+1
nixos/release.nix
···
tests.containers-bridge = callTest tests/containers-bridge.nix {};
tests.containers-imperative = callTest tests/containers-imperative.nix {};
tests.containers-extra_veth = callTest tests/containers-extra_veth.nix {};
tests.docker = hydraJob (import tests/docker.nix { system = "x86_64-linux"; });
tests.dnscrypt-proxy = callTest tests/dnscrypt-proxy.nix { system = "x86_64-linux"; };
tests.ecryptfs = callTest tests/ecryptfs.nix {};
···
tests.containers-bridge = callTest tests/containers-bridge.nix {};
tests.containers-imperative = callTest tests/containers-imperative.nix {};
tests.containers-extra_veth = callTest tests/containers-extra_veth.nix {};
+
tests.containers-physical_interfaces = callTest tests/containers-physical_interfaces.nix {};
tests.docker = hydraJob (import tests/docker.nix { system = "x86_64-linux"; });
tests.dnscrypt-proxy = callTest tests/dnscrypt-proxy.nix { system = "x86_64-linux"; };
tests.ecryptfs = callTest tests/ecryptfs.nix {};
+66
nixos/tests/containers-physical_interfaces.nix
···
···
+
+
import ./make-test.nix ({ pkgs, ...} : {
+
name = "containers-physical_interfaces";
+
meta = with pkgs.stdenv.lib.maintainers; {
+
maintainers = [ kampfschlaefer ];
+
};
+
+
nodes = {
+
server = { config, pkgs, ... }:
+
{
+
virtualisation.memorySize = 256;
+
virtualisation.vlans = [ 1 ];
+
+
containers.server = {
+
privateNetwork = true;
+
interfaces = [ "eth1" ];
+
+
config = {
+
networking.interfaces.eth1 = {
+
ip4 = [ { address = "10.10.0.1"; prefixLength = 24; } ];
+
};
+
networking.firewall.enable = false;
+
};
+
};
+
};
+
client = { config, pkgs, ... }: {
+
virtualisation.memorySize = 256;
+
virtualisation.vlans = [ 1 ];
+
+
containers.client = {
+
privateNetwork = true;
+
interfaces = [ "eth1" ];
+
+
config = {
+
networking.bridges.br0.interfaces = [ "eth1" ];
+
networking.interfaces.br0 = {
+
ip4 = [ { address = "10.10.0.2"; prefixLength = 24; } ];
+
};
+
networking.firewall.enable = false;
+
};
+
};
+
};
+
};
+
+
testScript = ''
+
startAll;
+
+
$server->waitForUnit("default.target");
+
$server->execute("ip link >&2");
+
+
$server->succeed("ip link show dev eth1 >&2");
+
+
$server->succeed("nixos-container start server");
+
$server->waitForUnit("container\@server");
+
$server->succeed("systemctl -M server list-dependencies network-addresses-eth1.service >&2");
+
+
$server->succeed("nixos-container run server -- ip a show dev eth1 >&2");
+
+
$client->waitForUnit("default.target");
+
$client->succeed("nixos-container start client");
+
$client->waitForUnit("container\@client");
+
$client->succeed("systemctl -M client list-dependencies network-addresses-br0.service >&2");
+
$client->succeed("systemctl -M client status -n 30 -l network-addresses-br0.service");
+
$client->succeed("nixos-container run client -- ping -w 10 -c 1 -n 10.10.0.1");
+
'';
+
})