From a2cd0a2cf7ea53ec71fd799c0e132acb09429a91 Mon Sep 17 00:00:00 2001 From: Leon Schuermann Date: Mon, 17 Nov 2025 23:13:09 -0500 Subject: [PATCH] nixos-containers: allow hard-coding container veth MAC address When using a NixOS container with `privateNetwork = true;` (i.e., a veth network device), it automatically gets assigned a random, locally administered unicast MAC address. While this is fine for many purposes, when attaching this container to a larger Layer 2 network where it interacts with other services, like an external DHCP server or IPv6 gateway sending out router advertisements, the MAC address of the container matters. This commit thus adds a `macAddress` option to containers. If set, this MAC address will be assigned to the container-side of the `veth` interface very early during container boot (before executing the stage 2 init script). This is crucial to ensure that no services run in the container using the prior, random MAC automatically assigned to the `veth` device. Otherweise, I've had problems using systemd units or the activation scripts to set the address early enough during container boot to use it, for example, for IPv6 SLAAC address assignment. --- .../virtualisation/nixos-containers.nix | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/nixos/modules/virtualisation/nixos-containers.nix b/nixos/modules/virtualisation/nixos-containers.nix index ce86487bbbc3..1b94a8a0ff7f 100644 --- a/nixos/modules/virtualisation/nixos-containers.nix +++ b/nixos/modules/virtualisation/nixos-containers.nix @@ -55,8 +55,13 @@ let # Initialise the container side of the veth pair. if [[ -n "''${HOST_ADDRESS-}" ]] || [[ -n "''${HOST_ADDRESS6-}" ]] || [[ -n "''${LOCAL_ADDRESS-}" ]] || [[ -n "''${LOCAL_ADDRESS6-}" ]] || - [[ -n "''${HOST_BRIDGE-}" ]]; then + [[ -n "''${HOST_BRIDGE-}" ]] || [[ -n "''${LOCAL_MAC_ADDRESS-}" ]]; then ip link set host0 name eth0 + + if [[ -n "''${LOCAL_MAC_ADDRESS-}" ]]; then + ip link set dev eth0 address "$LOCAL_MAC_ADDRESS" + fi + ip link set dev eth0 up if [[ -n "''${LOCAL_ADDRESS-}" ]]; then @@ -138,7 +143,8 @@ let fi if [[ -n "''${HOST_ADDRESS-}" ]] || [[ -n "''${LOCAL_ADDRESS-}" ]] || - [[ -n "''${HOST_ADDRESS6-}" ]] || [[ -n "''${LOCAL_ADDRESS6-}" ]]; then + [[ -n "''${HOST_ADDRESS6-}" ]] || [[ -n "''${LOCAL_ADDRESS6-}" ]] || + [[ -n "''${LOCAL_MAC_ADDRESS-}" ]]; then extraFlags+=("--network-veth") fi @@ -205,6 +211,7 @@ let --setenv LOCAL_ADDRESS="''${LOCAL_ADDRESS-}" \ --setenv HOST_ADDRESS6="''${HOST_ADDRESS6-}" \ --setenv LOCAL_ADDRESS6="''${LOCAL_ADDRESS6-}" \ + --setenv LOCAL_MAC_ADDRESS="''${LOCAL_MAC_ADDRESS-}" \ --setenv HOST_PORT="''${HOST_PORT-}" \ --setenv PATH="$PATH" \ ${optionalString cfg.ephemeral "--ephemeral"} \ @@ -487,6 +494,18 @@ let ''; }; + localMacAddress = mkOption { + type = types.nullOr (lib.types.strMatching "([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}"); + default = null; + example = "de:b7:73:01:10:90"; + description = '' + The MAC address assigned to the interface in the container. This address + is assigned early during container boot, and can thus be reliably used + for setups like IPv6 SLAAC with router advertisements. If this option is + not specified, the veth devices gets assigned a random, + locally-administered unicast MAC address. + ''; + }; }; dummyConfig = { @@ -499,6 +518,7 @@ let hostAddress6 = null; localAddress = null; localAddress6 = null; + localmacAddress = null; tmpfs = null; }; @@ -1108,6 +1128,9 @@ in ${optionalString (cfg.localAddress6 != null) '' LOCAL_ADDRESS6=${cfg.localAddress6} ''} + ${optionalString (cfg.localMacAddress != null) '' + LOCAL_MAC_ADDRESS=${cfg.localMacAddress} + ''} ''} ${optionalString (cfg.networkNamespace != null) '' NETWORK_NAMESPACE_PATH=${cfg.networkNamespace}