Skip to content

kea: Add systemd units and fix keactrl config paths (#1667) - #1668

Open
dcasota wants to merge 1 commit into
vmware:5.0from
dcasota:fix/kea-keactrl-paths-and-units-5.0
Open

kea: Add systemd units and fix keactrl config paths (#1667)#1668
dcasota wants to merge 1 commit into
vmware:5.0from
dcasota:fix/kea-keactrl-paths-and-units-5.0

Conversation

@dcasota

@dcasota dcasota commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #1667: kea ships no systemd unit, and keactrl looks for its
configuration under /usr/etc instead of /etc.

The report is accurate, and the second defect is wider than reported

Verified against the exact published binary,
kea-3.1.9-3.ph5.x86_64.rpm from photon_updates_5.0_x86_64.

No systemd unit. The package ships 334 files and none of them is a unit:

$ rpm -qlp kea-3.1.9-3.ph5.x86_64.rpm | grep -iE 'systemd|\.service|unit'
(no output)

Wrong config path, reproduced verbatim from the shipped script:

$ keactrl status
ERROR/keactrl: keactrl configuration file doesn't exist in /usr//etc/kea/keactrl.conf.

Two clarifications on top of the report:

The doubled slash is a fingerprint, not the fault. Linux collapses interior
//, so /usr//etc resolves to /usr/etc. The defect is that an already
absolute sysconfdir gets ${prefix}/ prepended; squashing the slash would
change nothing.

It is eight substitutions across two installed files, not one, because
localstatedir is corrupted the same way:

file line value should be
/usr/sbin/keactrl 127 ${prefix}//var/run/kea /var/run/kea
/usr/sbin/keactrl 339 ${prefix}//var/log/kea.log /var/log/kea.log
/usr/sbin/keactrl 368 ${prefix}//var /var
/usr/sbin/keactrl 369 ${prefix}//etc/kea/keactrl.conf /etc/kea/keactrl.conf
/etc/kea/keactrl.conf 14-17 ${prefix}//etc/kea/kea-*.conf /etc/kea/kea-*.conf

So the obvious workaround does not work either — pointing keactrl at the
real file with -c gets one step further and then fails on the server
configs, because the installed keactrl.conf carries the same corruption:

$ keactrl status -c /etc/kea/keactrl.conf
Kea DHCPv4 configuration file: /usr//etc/kea/kea-dhcp4.conf
ERROR/keactrl: Configuration file for Kea does not exist: /usr//etc/kea/kea-dhcp4.conf.

It also means the package cannot reach its own directories: the spec creates
/var/log/kea (0750 kea:kea) and the tmpfiles unit creates /run/kea, while
the script writes to /usr/var/log and /usr/var/run.

Root cause

src/bin/keactrl/meson.build string-concatenates a literal ${prefix}/ in
front of the directory options:

keactrl_conf_data.set('sysconfdir',    '${prefix}/' + SYSCONFDIR)
keactrl_conf_data.set('runstatedir',   '${prefix}/' + LOCALSTATEDIR + '/run')
keactrl_conf_data.set('localstatedir', '${prefix}/' + LOCALSTATEDIR)

That assumes the autotools convention, where those options are
prefix-relative. Meson does not follow it. BUILTIN_DIR_NOPREFIX_OPTIONS in
mesonbuild/options.py maps sysconfdir to an absolute /etc and
localstatedir to an absolute /var whenever the prefix is /usr:

BUILTIN_DIR_NOPREFIX_OPTIONS = {
    OptionKey('sysconfdir'):     {'/usr': '/etc'},
    OptionKey('localstatedir'):  {'/usr': '/var', '/usr/local': '/var/local'},

This is worth being precise about, because it rules out the fix you would
reach for first: the trigger is not the --sysconfdir=/etc that the
%meson macro passes.
Meson substitutes /etc for any prefix=/usr build
whether or not the flag is given, so no change to the spec's -D flags can
fix this. Measured with meson 1.12.0 on the real templates:

flags unpatched patched
--prefix=/usr --sysconfdir=/etc --localstatedir=/var ${prefix}//etc/kea/keactrl.conf /etc/kea/keactrl.conf
--prefix=/usr (no dir flags) ${prefix}//etc/kea/keactrl.conf /etc/kea/keactrl.conf
--prefix=/usr/local ${prefix}/etc/kea/keactrl.conf ${prefix}/etc/kea/keactrl.conf
--prefix=/opt/kea ${prefix}/etc/kea/keactrl.conf ${prefix}/etc/kea/keactrl.conf

For every prefix other than /usr, both generated files are byte-identical to
an unpatched build — verified with cmp on keactrl and keactrl.conf.

sbindir is unaffected, which is why ${prefix}/sbin/kea-dhcp4 is correct in
the same file: meson relativizes sbindir under the prefix, so the
concatenation happens to be right for that one.

What this changes

0001-keactrl-Use-absolute-sysconfdir-and-localstatedir.patch uses the
option directly when it is already absolute, and keeps the ${prefix}/
indirection when it is prefix-relative:

if SYSCONFDIR.startswith('/')
    keactrl_conf_data.set('sysconfdir', SYSCONFDIR)
else
    keactrl_conf_data.set('sysconfdir', '${prefix}/' + SYSCONFDIR)
endif

The indirection is what makes a self-contained install relocatable — prefix
is a live shell variable in the generated scripts — so it has to survive for
the prefixes where meson leaves the options relative. Only the /usr case,
where meson forces an absolute value, changes.

This is an upstream bug and the patch is upstreamable as-is.

Three systemd unitskea-dhcp4, kea-dhcp6, kea-dhcp-ddns, matching
the three daemons the package actually installs. Kea 3.x has no control-agent
binary, so there is no kea-ctrl-agent.service.

The units follow the Fedora/CentOS layout, with one deliberate and necessary
divergence: Type=simple, not Type=notify. Distributions that use
Type=notify carry an out-of-tree sd-daemon patch and build with
-Dsystemd=enabled; upstream Kea has no such option and no systemd
integration at all. Verified against the shipped Photon binary — no
libsystemd in NEEDED, no sd_notify symbol, no NOTIFY_SOCKET string. A
Type=notify unit here would hang until TimeoutStartSec and then be killed.

ConfigurationDirectory= is also omitted, because /etc/kea is RPM-owned
root:root 0755 and systemd would chown it to kea:kea 0750. The
RuntimeDirectory/StateDirectory/LogsDirectory settings are kept: they
match the ownership and modes the spec and tmpfiles already establish
(0750 kea:kea), so they are consistent rather than conflicting.

A disable-by-default preset, 50-kea.preset. This one is not cosmetic.
Photon's preset files are an allow-list with no catch-all rule, and systemd
enables a unit that matches no rule. Measured on Photon 5.0:

$ systemctl --root=$T preset zz-probe.service
Created symlink '.../multi-user.target.wants/zz-probe.service' -> ...
$ systemctl --root=$T is-enabled zz-probe.service
enabled

So shipping the units without a preset would mean %systemd_post silently
enables three DHCP servers on every install and upgrade of kea. An
unrequested DHCP server answering on a production network is a genuine
hazard, not just noise. containerd, docker, lvm2, cyrus-sasl and
postgresql18 all ship disable presets for exactly this reason; this
follows containerd.spec's idiom (Source2: disable-<name>-by-default.preset
installed to %{_presetdir}/50-%{name}.preset). Operators enable the servers
explicitly, as they did with dhcp.service before.

The spec changes follow the house idiom used by nginx.spec — units as
Source, %systemd_post/%systemd_preun/%systemd_postun_with_restart,
BuildRequires: systemd-rpm-macros, Requires(pre): systemd-rpm-macros. The
existing %post already ran systemd-tmpfiles --create, so %systemd_post
is appended to it rather than added as a second %post.

Constellations

The bug is a property of the build prefix, not of the image or hypervisor, so
it reproduces identically on minimal and full ISOs, on generic and VMware
hypervisors, and with or without STIG hardening. What does vary is the
subrelease:

constellation before after
5.0 subrelease 90 not affected — SPECS/90/dhcp still builds ISC dhcp-server with dhcp.service; kea.spec is skipped by build_if unchanged
5.0 subrelease 91 affected fixed
5.0 subrelease 92 (mainline) affected fixed
4.0 not affected — no SPECS/kea on the branch unchanged
6.0 / dev not affected — no SPECS/kea; still ship SPECS/dhcp unconditionally unchanged

kea.spec lives only at top-level SPECS/ behind
%global build_if %{photon_subrelease} >= 91, so a single change covers both
91 and 92, and subrelease 90 is untouched by construction. The
vmware/5.0-9.1.1 snapshot branch carries a byte-identical SPECS/kea and
would need the same commit if it is still serviced.

The patch is prefix-agnostic: output for prefix=/usr/local and
prefix=/opt/kea is byte-identical to an unpatched build, so it cannot
regress a self-contained or relocated install.

Testing

Built as kea-3.1.9-5.ph5 in a clean photon:5.0 container
(rpmbuild -bb --define "photon_subrelease 92"), then installed and exercised
in a second clean container. Source tarball SHA-512 matches the
archive_sha512sum in config.yaml.

Build is clean — no unpackaged files, all six subpackages produced.

The four corrupted paths in /usr/sbin/keactrl:

before                                     after
127  pid_file_dir="${prefix}//var/run/kea"       pid_file_dir="/var/run/kea"
339  KEA_LOGGER_DESTINATION="${prefix}//var/…"   KEA_LOGGER_DESTINATION="/var/log/kea.log"
368  localstatedir="${prefix}//var"              localstatedir="/var"
369  keactrl_conf="${prefix}//etc/kea/…"         keactrl_conf="/etc/kea/keactrl.conf"

The four in the installed /etc/kea/keactrl.conf:

kea_dhcp4_config_file="/etc/kea/kea-dhcp4.conf"
kea_dhcp6_config_file="/etc/kea/kea-dhcp6.conf"
kea_dhcp_ddns_config_file="/etc/kea/kea-dhcp-ddns.conf"
kea_netconf_config_file="/etc/kea/kea-netconf.conf"

Grepping both installed artifacts for ${prefix}/ or /usr// returns nothing.

The reported symptom is gone — this is the command from the issue:

$ keactrl status
DHCPv4 server: inactive
DHCPv6 server: inactive
DHCP DDNS: inactive
Kea DHCPv4 configuration file: /etc/kea/kea-dhcp4.conf
Kea DHCPv6 configuration file: /etc/kea/kea-dhcp6.conf
Kea DHCP DDNS configuration file: /etc/kea/kea-dhcp-ddns.conf
keactrl configuration file: /etc/kea/keactrl.conf

The shipped default configs parse under all three daemons
(kea-dhcp4 -t, kea-dhcp6 -t, kea-dhcp-ddns -t — all
result: success(0)).

The preset works, and a control run shows it is required:

with 50-kea.preset:     kea-dhcp4 disabled, kea-dhcp6 disabled, kea-dhcp-ddns disabled
preset file removed:    kea-dhcp4 enabled     <- what would ship without it

Not covered here: a live DHCP lease exchange, which needs a real network
rather than a container, and the %systemd_postun_with_restart upgrade path
from 3.1.9-3.

Upstream status — unfixed everywhere, including tip

Checked against ISC directly rather than assuming, because if upstream had
already fixed this, the right change would be a version bump instead of a
patch. It has not.

src/bin/keactrl/meson.build is byte-identical to the 3.1.9 copy in every
one of these:

ISC ref sysconfdir line vs 3.1.9
master @ c69ece1cf15a (2026-08-31) '${prefix}/' + SYSCONFDIR identical
Kea-3.3.1 '${prefix}/' + SYSCONFDIR identical
Kea-3.3.0 '${prefix}/' + SYSCONFDIR identical
Kea-3.2.0 '${prefix}/' + SYSCONFDIR identical

So upgrading Kea would not fix this — 3.2.0 and 3.3.x are affected the same
way. A search of the ISC issue tracker for keactrl sysconfdir, keactrl prefix and usr//etc finds no open issue covering it. The patch in this PR
applies cleanly to master unmodified (patch -p1 --dry-run, exit 0), so it
can be sent upstream as-is.

Unrelated to this bug, but noticed while checking: Kea 3.1.9 is a
development-series release. ISC's version
policy
makes even-numbered minor versions
(3.0.x, 3.2.x) the stable production branches and odd-numbered ones (3.1.x)
development releases, maintained only until the next stable branch opens.
Photon 5.0 is shipping a development release in a production update repo, and
3.2.0 has since been released. Worth a look independently of this fix.

Out of scope, but worth a separate look

kea.spec carries Obsoletes: dhcp-server with no matching Provides:, and
dhcpcd.spec does the same for dhcp-client. There is no Provides: for
either anywhere on the branch. That is what makes tdnf update perform the
swap in the first place, and it leaves anything with Requires: dhcp-server
unsatisfiable rather than satisfied by the replacement. The client side was
handled by rewriting the consumer instead (cloud-init.spec now requires
dhcpcd); no equivalent was done for dhcp-server. I have not touched it
here, since it is a packaging-policy decision rather than part of this bug.

@dcasota

dcasota commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

@bo-gan-broadcom could you please review the PR suggested? The latest kea releases have the issue as well. I've opened an issue ticket at https://gitlab.isc.org/isc-projects/kea/-/work_items/4790 .

@dcasota
dcasota force-pushed the fix/kea-keactrl-paths-and-units-5.0 branch from c4f835e to 1821b5a Compare August 31, 2026 19:41
kea 3.1.9 obsoletes dhcp-server but is not usable in its place: it ships no
systemd unit, and keactrl looks for its configuration under /usr/etc.

  $ keactrl status
  ERROR/keactrl: keactrl configuration file doesn't exist in
  /usr//etc/kea/keactrl.conf.

src/bin/keactrl/meson.build string-concatenates a literal '${prefix}/' in
front of the sysconfdir and localstatedir options, which assumes the
autotools convention that they are prefix-relative. Meson does not follow
it: BUILTIN_DIR_NOPREFIX_OPTIONS maps sysconfdir to an absolute /etc and
localstatedir to an absolute /var whenever prefix is /usr, whether or not
the values are passed on the command line. This is therefore not fixable by
changing the %meson flags.

Eight substitutions across two installed files are affected, not just the
one in the bug report: the keactrl.conf location, KEA_LOGGER_DESTINATION,
the PID directory, localstatedir, and all four kea_*_config_file entries
inside the installed keactrl.conf. Pointing keactrl at the real file with
-c does not help, because keactrl.conf carries the same corrupted paths.

Patch keactrl's meson.build to use the option directly when it is already
absolute, and keep the '${prefix}/' indirection when it is prefix-relative.
That indirection is what makes a self-contained install relocatable, so it
must survive for the prefixes where meson leaves the options relative: with
prefix=/usr/local and prefix=/opt/kea both generated files stay
byte-identical to an unpatched build.

Add units for the three daemons the package installs. They use Type=simple
rather than Type=notify: upstream Kea has no systemd integration at all, and
the shipped binaries carry no libsystemd dependency and no sd_notify symbol,
so a Type=notify unit would hang until TimeoutStartSec and be killed.

Also ship a disable-by-default preset. Photon's presets are an allow-list
with no catch-all rule, and systemd enables a unit that matches no rule, so
without this %systemd_post would enable three DHCP servers on every install.

kea.spec is gated on %{photon_subrelease} >= 91, so this covers subreleases
91 and 92. Subrelease 90 is unaffected: it still builds ISC dhcp-server from
SPECS/90/dhcp, which has its own unit.

Change-Id: Ieb172db51a45884c7fec40123ba0dfd3b98fe05a
Signed-off-by: Daniel Casota <dcasota@gmail.com>
@dcasota
dcasota force-pushed the fix/kea-keactrl-paths-and-units-5.0 branch from 1821b5a to 9b6f8af Compare September 1, 2026 10:08
@aabusair aabusair closed this Sep 2, 2026
@aabusair aabusair reopened this Sep 2, 2026
@dcasota

dcasota commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

I have read the DCO Document and I hereby sign the DCO for this and all future contributions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Photon 5.0: obsolete dhcp-server gets replaced with broken kea package

2 participants