Kea DHCPv4 Host Reservations: Fixed Addresses Done Right - 夜莺博客

Kea DHCPv4 Host Reservations: Fixed Addresses Done Right

Moving from ISC DHCP to Kea breaks host reservations in a way that surprises people: Kea refuses to start if a subnet-level reservation contains an address that is not viable on that subnet. ISC DHCP would quietly ignore it. That strictness is the point — silent misconfiguration is how you end up with a printer that keeps getting a DHCP address — but it changes how you migrate. This guide covers how reservations work in Kea, how to scope them, and the identifier choices that determine whether they match.

What Changed from ISC DHCP

ISC DHCP Kea
Reservations are global only Reservations at global, shared-network and subnet level
One fixed-address6 per host declaration; more requires more declarations Multiple addresses under one ip-addresses list
Starts even if the fixed address is not viable on the subnet Fails to start, with an explicit error
hardware ethernet <value> hw-address: <value>
host-identifier option dhcp6.client-id client-id: <value> or flex-id
fixed-address6 ip-addresses: [ ... ]
fixed-prefix6 prefixes: [ ... ]

ISC's migration tool converts most of this, with one important flag: by default it makes host reservations global, which removes the subnet validation. Run it with -N to place reservations into their proper subnets:

keama -6 -i ./dhcp.conf -o ./kea.conf
keama -N -6 -i ./dhcp.conf -o ./kea.conf    # subnet-scoped HRs

A Minimal Kea Configuration with Reservations

{
  "Dhcp4": {
    "interfaces-config": { "interfaces": [ "eth0" ] },
    "lease-database": {
      "type": "memfile",
      "persist": true,
      "name": "/var/lib/kea/kea-leases4.csv"
    },
    "subnet4": [
      {
        "id": 10,
        "subnet": "192.168.10.0/24",
        "pools": [ { "pool": "192.168.10.100 - 192.168.10.200" } ],
        "option-data": [
          { "name": "routers", "data": "192.168.10.1" },
          { "name": "domain-name-servers", "data": "192.168.10.5, 192.168.10.6" }
        ],
        "reservations": [
          {
            "hostname": "core-switch",
            "hw-address": "aa:bb:cc:dd:ee:01",
            "ip-address": "192.168.10.11"
          },
          {
            "hostname": "nas",
            "hw-address": "aa:bb:cc:dd:ee:02",
            "ip-address": "192.168.10.12",
            "option-data": [
              { "name": "domain-name-servers", "data": "192.168.10.5" }
            ]
          }
        ]
      }
    ],
    "valid-lifetime": 3600
  }
}

Global vs Subnet Reservations

{
  "Dhcp4": {
    "host-reservation-identifiers": [ "hw-address", "client-id" ],
    "reservation-mode": "global",
    "reservations": [
      {
        "hostname": "roaming-laptop",
        "hw-address": "aa:bb:cc:dd:ee:03",
        "ip-address": "192.168.20.50"
      }
    ]
  }
}

Global reservations are not checked against subnets, so they are offered regardless of where the client attaches. That is exactly what you want for a laptop that moves between floors — and exactly what you must avoid for a fixed device, where a typo will not be caught and the device will receive an address from the wrong VLAN.

Choosing the Identifier

  • hw-address — simplest, works for most wired gear. Vulnerable to spoofing; combine with DHCP snooping or 802.1X if that matters.
  • client-id — survives a NIC replacement if the client is stable, but some clients send a different client-id per interface.
  • circuit-id / remote-id — from DHCP option 82 on a relay, ideal for identifying a port rather than a device.
  • flex-id — a premium hook that lets you match on an arbitrary expression such as option[61].hex; the escape hatch for undocumented vendor behaviour.
{
  "Dhcp6": {
    "host-reservation-identifiers": [ "hw-address" ],
    "reservation-mode": "global",
    "reservations": [
      {
        "hostname": "ipv6-host",
        "hw-address": "01:00:80:a2:55:67",
        "ip-addresses": [ "2001:db8:100::4321" ],
        "prefixes": [ "2001:db8:101::/64" ]
      }
    ]
  }
}

The IPv6 hw-address form matches DUID-LL and DUID-LLT values that contain the MAC — with a 01:00:80 prefix in the example, because the DUID type bytes are part of the match string.

Verification and the Classic Mistake

sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.conf     # config test; exit 0 means valid
sudo journalctl -u kea-dhcp4 -f
# look for: DHCP4_SUBNET_SELECTED, DHCP4_RESERVATIONS_LOOKUP_FIRST

The number one reported bug — “my reservation is ignored, the client gets a pool address” — is almost always identifier mismatch: the reservation key is hw-address but the client sends only a client-id, or the MAC in the config has a typo or a case/format difference. Log DHCP4_RESERVATIONS_LOOKUP_FIRST and confirm you see the lookup happening and failing, rather than assuming it never ran.

Production Checklist

  • Run kea-dhcp4 -t in CI before deploying a config.
  • Keep reservations in a file you can diff, and generate the JSON from a source of truth such as NetBox.
  • Reserve inside the subnet but outside the pool range, so a pool lease can never collide.
  • If you run HA (hot-standby or load-balancing), remember that both peers must see the same reservations, or a client gets a different address depending on which server answers.

Related Reading

Deeper dives on the same topics from our archive:

原文链接:https://kb.isc.org/docs/what-are-host-reservations-how-to-use-them