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.
This commit is contained in:
Leon Schuermann 2025-11-17 23:13:09 -05:00
commit a2cd0a2cf7

View file

@ -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}