nixos/network-interfaces: Add iplvan support

This commit is contained in:
Svenum
2026-02-27 09:17:46 +01:00
parent fe106deb78
commit 40b42e9f70
7 changed files with 148 additions and 0 deletions
@@ -189,6 +189,8 @@ See <https://github.com/NixOS/nixpkgs/issues/481673>.
- `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:
+3
View File
@@ -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)}
''
+9
View File
@@ -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;
@@ -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
@@ -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: {
@@ -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 = {
@@ -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 {