Commit a2cd0a2c authored by Leon Schuermann's avatar Leon Schuermann
Browse files

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.
parent a169dcc1
Loading
Loading
Loading
Loading
+25 −2
Original line number Diff line number Diff line
@@ -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}