I've written an Internet Draft on how to effectively disable IPv4 on dual stack hosts, while still satisifying their need for having an IPv4 address assigned via DHCPv4. It takes advantage of how IPv4 hosts use subnet masks, by using a 255.255.255.255 or /32 subnet mask.
The Internet Draft is available at Nearly IPv6 Only Dual Stack Hosts
The following is an bare bones example Kea DHCP DHCPv4 server configuration file that implements what is described in the Internet Draft.
//
// kea-dhcp4.conf.255sm.opt108.conf
//
// Bare bones example Kea DHCPv4 configuration implementing IETF
// Internet Draft:
//
// Nearly IPv6 Only Dual Stack Hosts
// draft-smith-v6ops-nearly-ipv6-only-dualstack-hosts
//
// https://datatracker.ietf.org/doc/draft-smith-v6ops-nearly-ipv6-only-dualstack-hosts/
//
// To use:
// 1. Ensure the Kea DHCP server is installed, including the
// 'hooks' libraries. On Fedora 43, that means installing
// both of the 'kea' and 'kea-hooks' packages.
// 2. As root, copy into /etc/kea and rename as kea-dhcp4.conf
// (Or copy as kea-dhcp4.conf.255sm.opt108.conf into /etc/kea
// and then symlink /etc/kea/kea-dhcp4.conf to point to
// kea-dhcp4.conf.255sm.opt108.conf/)
// 3. Change the ownership of the file (kea-dhcp4.conf or symlinked
// kea-dhcp4.conf.255sm.opt108.conf to root:kea via
// 'chown root:kea /etc/kea/<filename>'
// 4. Update the "interfaces" variable in "interfaces-config" to
// suit which host interface is going to be used for the DHCPv4
// server.
// 5. Update the "valid-lifetime" and possibly the "renew-timer",
// and "rebind-timer" timers for the IPv4 address leases. The
// current "valid-lifetime" is 600 seconds or 10 minutes for testing.
// 6. Replace the documentation 203.0.113.0/24 subnet in the config
// with a suitable local subnet and prefix length on the 2 & 3
// interface.
// 7. Change the pool range if necessary to suit the local subnet.
// 8. Update the "interface" variable in "subnet4" to the same
// host interface name as in 2.
// 9. Update the "v6-only-preferred" "data" seconds value. It is
// currently is 150 seconds for testing, which is 1/4th of the
// 10 minute "valid-lifetime", corresponding to the default
// "renew-timer" timer value.
// 10. If necessary, update the host's firewall so it can receive
// DHCPv4 requests from clients (destination UDP port 67).
// 11. Start kea-dhcpv4 using the local system method e.g.
// 'systemctl start kea-dhcp4' for a systemd based distribution.
//
{
"Dhcp4": {
// Add names of your network interfaces to listen on.
"interfaces-config": {
// See section 8.2.4 for more details. You probably want to add just
// interface name (e.g. "eth0" or specific IPv4 address on that
// interface name (e.g. "eth0/203.0.113.1").
"interfaces": [ "wlp1s0" ]
// Kea DHCPv4 server by default listens using raw sockets. This ensures
// all packets, including those sent by directly connected clients
// that don't have IPv4 address yet, are received. However, if your
// traffic is always relayed, it is often better to use regular
// UDP sockets. If you want to do that, uncomment this line:
// "dhcp-socket-type": "udp"
},
// Kea supports control channel, which is a way to receive management
// commands while the server is running. This is a Unix domain socket that
// receives commands formatted in JSON, e.g. config-set (which sets new
// configuration), config-reload (which tells Kea to reload its
// configuration from file), statistic-get (to retrieve statistics) and many
// more. For detailed description, see Sections 8.8, 16 and 15.
"control-socket": {
"socket-type": "unix",
"socket-name": "kea4-ctrl-socket"
},
// Use Memfile lease database backend to store leases in a CSV file.
"lease-database": {
// Memfile is the simplest and easiest backend to use. It's an in-memory
// C++ database that stores its state in CSV file.
"type": "memfile",
"lfc-interval": 3600
},
// Setup reclamation of the expired leases and leases affinity.
// Expired leases will be reclaimed every 10 seconds. Every 25
// seconds reclaimed leases, which have expired more than 3600
// seconds ago, will be removed. The limits for leases reclamation
// are 100 leases or 250 ms for a single cycle. A warning message
// will be logged if there are still expired leases in the
// database after 5 consecutive reclamation cycles.
// If both "flush-reclaimed-timer-wait-time" and "hold-reclaimed-time" are
// not 0, when the client sends a release message the lease is expired
// instead of being deleted from the lease storage.
"expired-leases-processing": {
"reclaim-timer-wait-time": 10,
"flush-reclaimed-timer-wait-time": 25,
"hold-reclaimed-time": 3600,
"max-reclaim-leases": 100,
"max-reclaim-time": 250,
"unwarned-reclaim-cycles": 5
},
// Global timers specified here apply to all subnets, unless there are
// subnet specific values defined in particular subnets.
//"renew-timer": 900,
//"rebind-timer": 1800,
//"valid-lifetime": 3600,
"valid-lifetime": 600, // seconds or 10 minutes for testing
"hooks-libraries": [
{
// This hook overrides the DHCPv4 Option 1 Subnet Mask
// option value that is automatically set by Kea.
// Kea automatically derives the Subnet Mask option
// value from the 'subnet' prefix length (below), and
// does not currently allow it to be set via normal
// DHCPv4 option value setting in the global or
// "subnet4" "option-data" sections.
"library": "/usr/lib64/kea/hooks/libdhcp_flex_option.so",
"parameters": {
"options": [
{
"code": 1,
"supersede": "255.255.255.255"
}
]
}
}
],
"subnet4": [
{
"id": 1,
"subnet": "203.0.113.0/24",
"pools": [ { "pool": "203.0.113.100 - 203.0.113.200" } ],
"interface": "wlp1s0",
"option-data": [
{
"name": "v6-only-preferred",
"data": "150" // Seconds. Same as 600 second lease renew time
}
],
}
],
"loggers": [
{
"name": "kea-dhcp4",
"output-options": [
{
//"output": "kea-dhcp4.log"
"output": "syslog"
}
],
// This specifies the severity of log messages to keep. Supported values
// are: FATAL, ERROR, WARN, INFO, DEBUG
"severity": "INFO",
//"severity": "DEBUG",
// If DEBUG level is specified, this value is used. 0 is least verbose,
// 99 is most verbose. Be cautious, Kea can generate lots and lots
// of logs if told to do so.
"debuglevel": 0
}
]
}
}