diff --git a/nixos/doc/manual/release-notes/rl-2605.section.md b/nixos/doc/manual/release-notes/rl-2605.section.md index 9b4195948cce..d969f74ce454 100644 --- a/nixos/doc/manual/release-notes/rl-2605.section.md +++ b/nixos/doc/manual/release-notes/rl-2605.section.md @@ -189,6 +189,8 @@ See . - `services.openssh` now supports generating host SSH keys by setting `services.openssh.generateHostKeys = true` while leaving `services.openssh.enable` disabled. This is particularly useful for systems that have no need of an SSH daemon but want SSH host keys for other purposes such as using agenix or sops-nix. +- IPVLAN interfaces can now be configured through the `networking.ipvlans` option in the networking module. + - `services.caddy` now supports setting `httpPort` and `httpsPort` and opening them in the firewall via `openFirewall`. - The latest available version of Nextcloud is v33 (available as `pkgs.nextcloud33`). The installation logic is as follows: diff --git a/nixos/lib/systemd-network-units.nix b/nixos/lib/systemd-network-units.nix index ffaefa972767..72752bd093f1 100644 --- a/nixos/lib/systemd-network-units.nix +++ b/nixos/lib/systemd-network-units.nix @@ -153,6 +153,9 @@ in + optionalString (def.macvlan != [ ]) '' ${concatStringsSep "\n" (map (s: "MACVLAN=${s}") def.macvlan)} '' + + optionalString (def.ipvlan != [ ]) '' + ${concatStringsSep "\n" (map (s: "IPVLAN=${s}") def.ipvlan)} + '' + optionalString (def.macvtap != [ ]) '' ${concatStringsSep "\n" (map (s: "MACVTAP=${s}") def.macvtap)} '' diff --git a/nixos/modules/system/boot/networkd.nix b/nixos/modules/system/boot/networkd.nix index 2385fa5c19a7..c8dadd09e9ce 100644 --- a/nixos/modules/system/boot/networkd.nix +++ b/nixos/modules/system/boot/networkd.nix @@ -3037,6 +3037,15 @@ let ''; }; + ipvlan = mkOption { + default = [ ]; + type = types.listOf types.str; + description = '' + A list of ipvlan interfaces to be added to the network section of the + unit. See {manpage}`systemd.network(5)` for details. + ''; + }; + macvtap = mkOption { default = [ ]; type = types.listOf types.str; diff --git a/nixos/modules/tasks/network-interfaces-scripted.nix b/nixos/modules/tasks/network-interfaces-scripted.nix index e4408b6771d2..4b457e078e77 100644 --- a/nixos/modules/tasks/network-interfaces-scripted.nix +++ b/nixos/modules/tasks/network-interfaces-scripted.nix @@ -21,6 +21,7 @@ let attrValues cfg.vswitches ) ++ concatMap (i: [ i.interface ]) (attrValues cfg.macvlans) + ++ concatMap (i: [ i.interface ]) (attrValues cfg.ipvlans) ++ concatMap (i: [ i.interface ]) (attrValues cfg.vlans); # We must escape interfaces due to the systemd interpretation @@ -106,6 +107,7 @@ let || (hasAttr dev cfg.bridges) || (hasAttr dev cfg.bonds) || (hasAttr dev cfg.macvlans) + || (hasAttr dev cfg.ipvlans) || (hasAttr dev cfg.sits) || (hasAttr dev cfg.ipips) || (hasAttr dev cfg.vlans) @@ -590,6 +592,39 @@ let } ); + createIpvlanDevice = + n: v: + nameValuePair "${n}-netdev" ( + let + deps = deviceDependency v.interface; + in + { + description = "IPVLAN Interface ${n}"; + wantedBy = [ + "network.target" + "network-setup.service" + (subsystemDevice n) + ]; + bindsTo = deps; + after = [ "network-pre.target" ] ++ deps; + before = [ "network-setup.service" ]; + serviceConfig.Type = "oneshot"; + serviceConfig.RemainAfterExit = true; + path = [ pkgs.iproute2 ]; + script = '' + # Remove Dead Interfaces + ip link show dev "${n}" >/dev/null 2>&1 && ip link delete dev "${n}" + ip link add link "${v.interface}" name "${n}" type ipvlan \ + ${optionalString (v.mode != null) "mode ${v.mode}"} \ + ${optionalString (v.flags != null) "${v.flags}"} + ip link set dev "${n}" up + ''; + postStop = '' + ip link delete dev "${n}" || true + ''; + } + ); + createFouEncapsulation = n: v: nameValuePair "${n}-fou-encap" ( @@ -803,6 +838,7 @@ let // mapAttrs' createVswitchDevice cfg.vswitches // mapAttrs' createBondDevice cfg.bonds // mapAttrs' createMacvlanDevice cfg.macvlans + // mapAttrs' createIpvlanDevice cfg.ipvlans // mapAttrs' createFouEncapsulation cfg.fooOverUDP // mapAttrs' createSitDevice cfg.sits // mapAttrs' createIpipDevice cfg.ipips diff --git a/nixos/modules/tasks/network-interfaces-systemd.nix b/nixos/modules/tasks/network-interfaces-systemd.nix index 9e9a76cfbf1d..d6568b85512e 100644 --- a/nixos/modules/tasks/network-interfaces-systemd.nix +++ b/nixos/modules/tasks/network-interfaces-systemd.nix @@ -396,6 +396,24 @@ in } ) )) + (mkMerge ( + flip mapAttrsToList cfg.ipvlans ( + name: ipvlan: { + netdevs."40-${name}" = { + netdevConfig = { + Name = name; + Kind = "ipvlan"; + }; + ipvlanConfig = + optionalAttrs (ipvlan.mode != null) { Mode = lib.toUpper ipvlan.mode; } + // optionalAttrs (ipvlan.flags != null) { Flags = ipvlan.flags; }; + }; + networks."40-${ipvlan.interface}" = { + ipvlan = [ name ]; + }; + } + ) + )) (mkMerge ( flip mapAttrsToList cfg.fooOverUDP ( name: fou: { diff --git a/nixos/modules/tasks/network-interfaces.nix b/nixos/modules/tasks/network-interfaces.nix index a5a0a2e93ee6..57722ec603c9 100644 --- a/nixos/modules/tasks/network-interfaces.nix +++ b/nixos/modules/tasks/network-interfaces.nix @@ -1069,6 +1069,54 @@ in }); }; + networking.ipvlans = mkOption { + default = { }; + example = literalExpression '' + { + wan = { + interface = "enp2s0"; + mode = "l2"; + flags = "vepa"; + }; + } + ''; + description = '' + This option allows you to define ipvlan interfaces which should + be automatically created. + ''; + type = + with types; + attrsOf (submodule { + options = { + + interface = mkOption { + example = "enp4s0"; + type = types.str; + description = "The interface the ipvlan will transmit packets through."; + }; + + mode = mkOption { + default = "l2"; + type = types.enum [ + "l2" + "l3" + "l3s" + ]; + description = "The mode of the interface."; + }; + + flags = mkOption { + default = null; + type = types.nullOr types.str; + example = "vepa"; + description = "The flags of the ipvlan device."; + }; + + }; + + }); + }; + networking.fooOverUDP = mkOption { default = { }; example = { diff --git a/nixos/tests/networking/networkd-and-scripted.nix b/nixos/tests/networking/networkd-and-scripted.nix index 8acf4e41bf5d..99e67649ceb3 100644 --- a/nixos/tests/networking/networkd-and-scripted.nix +++ b/nixos/tests/networking/networkd-and-scripted.nix @@ -436,6 +436,38 @@ let router.wait_until_succeeds("ping -c 1 192.168.1.3") ''; }; + ipvlan = { + name = "IPVLAN"; + nodes.client = { + virtualisation.interfaces.enp1s0.vlan = 1; + networking = { + useNetworkd = networkd; + useDHCP = false; + ipvlans = { + ipvlan1.interface = "enp1s0"; + ipvlan2.interface = "enp1s0"; + }; + }; + }; + testScript = '' + with subtest("Wait for networking to come up"): + client.wait_for_unit("network.target") + + with subtest("Can move IPVLANs to separate network namespaces"): + client.succeed("ip netns add ns1 && ip link set dev ipvlan1 netns ns1") + client.succeed("ip netns add ns2 && ip link set dev ipvlan2 netns ns2") + + with subtest("Can configure the IPVLAN interfaces"): + client.succeed("ip netns exec ns1 ip addr add 192.168.1.1/24 dev ipvlan1") + client.succeed("ip netns exec ns2 ip addr add 192.168.1.2/24 dev ipvlan2") + client.succeed("ip netns exec ns1 ip link set dev ipvlan1 up") + client.succeed("ip netns exec ns2 ip link set dev ipvlan2 up") + + with subtest("IPVLAN interfaces can ping each other"): + client.succeed("ip netns exec ns1 ping -c 1 192.168.1.2") + client.succeed("ip netns exec ns2 ping -c 1 192.168.1.1") + ''; + }; fou = { name = "foo-over-udp"; nodes.machine = clientConfig {