From 17965bf4ccda27fad5994fd4e0f51cad3034f97f Mon Sep 17 00:00:00 2001 From: Ejub Sabic Date: Thu, 23 Apr 2026 19:19:24 +0200 Subject: [PATCH 1/3] test: add DHCPv4 client hostname resend regression test Signed-off-by: Ejub Sabic --- test/case/dhcp/Readme.adoc | 5 + .../dhcp/client_hostname_resend/Readme.adoc | 1 + .../dhcp/client_hostname_resend/test.adoc | 30 +++++ test/case/dhcp/client_hostname_resend/test.py | 104 ++++++++++++++++++ .../dhcp/client_hostname_resend/topology.dot | 22 ++++ .../dhcp/client_hostname_resend/topology.svg | 33 ++++++ test/case/dhcp/dhcp_client.yaml | 3 + 7 files changed, 198 insertions(+) create mode 120000 test/case/dhcp/client_hostname_resend/Readme.adoc create mode 100644 test/case/dhcp/client_hostname_resend/test.adoc create mode 100755 test/case/dhcp/client_hostname_resend/test.py create mode 100644 test/case/dhcp/client_hostname_resend/topology.dot create mode 100644 test/case/dhcp/client_hostname_resend/topology.svg diff --git a/test/case/dhcp/Readme.adoc b/test/case/dhcp/Readme.adoc index a128d72eb..fc29a9d47 100644 --- a/test/case/dhcp/Readme.adoc +++ b/test/case/dhcp/Readme.adoc @@ -7,6 +7,7 @@ Tests verifying DHCPv4/DHCPv6 client and server functionality: - DHCPv4 client with default gateway assignment - DHCPv4 client with static route configuration - DHCPv4 client hostname management and priority + - DHCPv4 client hostname option is resent after system hostname changes - Basic DHCPv6 client operation with address assignment - DHCPv6 client with prefix delegation (IA_PD) - DHCPv6 SLAAC/RA (Stateless) @@ -30,6 +31,10 @@ include::client_hostname/Readme.adoc[] <<< +include::client_hostname_resend/Readme.adoc[] + +<<< + include::client6_basic/Readme.adoc[] <<< diff --git a/test/case/dhcp/client_hostname_resend/Readme.adoc b/test/case/dhcp/client_hostname_resend/Readme.adoc new file mode 120000 index 000000000..ae32c8412 --- /dev/null +++ b/test/case/dhcp/client_hostname_resend/Readme.adoc @@ -0,0 +1 @@ +test.adoc \ No newline at end of file diff --git a/test/case/dhcp/client_hostname_resend/test.adoc b/test/case/dhcp/client_hostname_resend/test.adoc new file mode 100644 index 000000000..b744523a3 --- /dev/null +++ b/test/case/dhcp/client_hostname_resend/test.adoc @@ -0,0 +1,30 @@ +=== DHCP Hostname Resend + +ifdef::topdoc[:imagesdir: {topdoc}../../test/case/dhcp/client_hostname_resend] + +==== Description + +Verify that updating the system hostname regenerates the DHCP client +Finit service file so subsequent DHCP requests advertise the current +hostname (option 12, RFC 2132). + +Regression test for a bug where the DHCP client callback only reacts +on diffs in infix-dhcp-client, so a standalone change of +ietf-system:system/hostname leaves the previously written +/etc/finit.d/available/dhcp-client-.conf untouched and the +running udhcpc keeps announcing the old name. + +==== Topology + +image::topology.svg[DHCP Hostname Resend topology, align=center, scaledwidth=75%] + +==== Sequence + +. Set up topology and attach to target DUT +. Configure initial hostname '{HOSTNM_A}' +. Enable DHCP client sending hostname option +. Verify Finit service carries hostname '{HOSTNM_A}' +. Update system hostname to '{HOSTNM_B}' +. Verify Finit service is regenerated with '{HOSTNM_B}' + + diff --git a/test/case/dhcp/client_hostname_resend/test.py b/test/case/dhcp/client_hostname_resend/test.py new file mode 100755 index 000000000..748f8c5bd --- /dev/null +++ b/test/case/dhcp/client_hostname_resend/test.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +"""DHCP Hostname Resend + +Verify that updating the system hostname regenerates the DHCP client +Finit service file so subsequent DHCP requests advertise the current +hostname (option 12, RFC 2132). + +Regression test for a bug where the DHCP client callback only reacts +on diffs in infix-dhcp-client, so a standalone change of +ietf-system:system/hostname leaves the previously written +/etc/finit.d/available/dhcp-client-.conf untouched and the +running udhcpc keeps announcing the old name. + +""" + +import infamy +from infamy.util import until + + +def finit_conf(ssh, ifname): + """Return the contents of the generated DHCP client Finit service file.""" + path = f"/etc/finit.d/available/dhcp-client-{ifname}.conf" + cmd = ssh.runsh(f"cat {path} 2>/dev/null") + return cmd.stdout + + +def udhcpc_cmdline(ssh, ifname): + """Return the NUL-separated argv of the running udhcpc for ifname.""" + pidfile = f"/run/dhcp-client-{ifname}.pid" + cmd = ssh.runsh( + f"p=$(cat {pidfile} 2>/dev/null); " + f"[ -n \"$p\" ] && tr '\\0' ' ' < /proc/$p/cmdline" + ) + return cmd.stdout + + +def running_hostname(ssh, ifname): + """Extract the hostname udhcpc is currently announcing (-x hostname:).""" + for tok in udhcpc_cmdline(ssh, ifname).split(): + if tok.startswith("hostname:"): + return tok.split(":", 1)[1] + return None + + +with infamy.Test() as test: + HOSTNM_A = "infix-resend-a" + HOSTNM_B = "infix-resend-b" + + with test.step("Set up topology and attach to target DUT"): + env = infamy.Env() + client = env.attach("client", "mgmt") + clissh = env.attach("client", "mgmt", "ssh") + _, port = env.ltop.xlate("client", "mgmt") + + with test.step(f"Configure initial hostname '{HOSTNM_A}'"): + client.put_config_dict("ietf-system", { + "system": { + "hostname": HOSTNM_A + } + }) + until(lambda: client.get_data("/ietf-system:system") + .get("system", {}).get("hostname") == HOSTNM_A) + + with test.step("Enable DHCP client sending hostname option"): + client.put_config_dict("ietf-interfaces", { + "interfaces": { + "interface": [{ + "name": port, + "ipv4": { + "infix-dhcp-client:dhcp": { + "option": [ + {"id": "vendor-class", "value": "infamy"}, + {"id": "hostname", "value": "auto"}, + {"id": "netmask"}, + {"id": "router"} + ] + } + } + }] + } + }) + + with test.step(f"Verify running udhcpc announces hostname '{HOSTNM_A}'"): + until(lambda: running_hostname(clissh, port) == HOSTNM_A) + + with test.step(f"Update system hostname to '{HOSTNM_B}'"): + client.put_config_dict("ietf-system", { + "system": { + "hostname": HOSTNM_B + } + }) + until(lambda: client.get_data("/ietf-system:system") + .get("system", {}).get("hostname") == HOSTNM_B) + + with test.step(f"Verify running udhcpc announces hostname '{HOSTNM_B}'"): + try: + until(lambda: running_hostname(clissh, port) == HOSTNM_B, + attempts=15) + except Exception: + cur = running_hostname(clissh, port) + print(f"udhcpc still announcing hostname '{cur}', expected '{HOSTNM_B}'") + test.fail() + + test.succeed() diff --git a/test/case/dhcp/client_hostname_resend/topology.dot b/test/case/dhcp/client_hostname_resend/topology.dot new file mode 100644 index 000000000..d2b1b2bcb --- /dev/null +++ b/test/case/dhcp/client_hostname_resend/topology.dot @@ -0,0 +1,22 @@ +graph "1x1" { + layout="neato"; + overlap="false"; + esep="+100"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="host | { mgmt }", + pos="0,20!", + requires="controller", + ]; + + client [ + label="{ mgmt } | client", + pos="200,20!", + requires="infix", + ]; + + host:mgmt -- client:mgmt [requires="mgmt", color=lightgrey] +} diff --git a/test/case/dhcp/client_hostname_resend/topology.svg b/test/case/dhcp/client_hostname_resend/topology.svg new file mode 100644 index 000000000..20a632e9f --- /dev/null +++ b/test/case/dhcp/client_hostname_resend/topology.svg @@ -0,0 +1,33 @@ + + + + + + +1x1 + + + +host + +host + +mgmt + + + +client + +mgmt + +client + + + +host:mgmt--client:mgmt + + + + diff --git a/test/case/dhcp/dhcp_client.yaml b/test/case/dhcp/dhcp_client.yaml index 73254d787..ff4827516 100644 --- a/test/case/dhcp/dhcp_client.yaml +++ b/test/case/dhcp/dhcp_client.yaml @@ -14,6 +14,9 @@ - name: DHCP Hostname Priority case: client_hostname/test.py +- name: DHCP Hostname Resend + case: client_hostname_resend/test.py + - name: DHCPv6 Basic case: client6_basic/test.py From 08ca47fdab28ceaca71be5b49681f0dcb34e8a58 Mon Sep 17 00:00:00 2001 From: Ejub Sabic Date: Fri, 24 Apr 2026 10:08:59 +0200 Subject: [PATCH 2/3] fix: dhcp client host name resend Signed-off-by: Ejub Sabic --- src/confd/src/dhcp-client.c | 14 ++++++++++++++ test/case/dhcp/client_hostname_resend/test.py | 18 +++++------------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/confd/src/dhcp-client.c b/src/confd/src/dhcp-client.c index 2ef8d1cc4..652b2323b 100644 --- a/src/confd/src/dhcp-client.c +++ b/src/confd/src/dhcp-client.c @@ -180,6 +180,20 @@ int dhcp_client_change(sr_session_ctx_t *session, struct lyd_node *config, struc ifaces = lydx_get_descendant(config, "interfaces", "interface", NULL); difaces = lydx_get_descendant(diff, "interfaces", "interface", NULL); + if (diff && lydx_get_xpathf(diff, "/ietf-system:system/hostname")) { + LYX_LIST_FOR_EACH(ifaces, iface, "interface") { + const char *ifname = lydx_get_cattr(iface, "name"); + + ipv4 = lydx_get_descendant(lyd_child(iface), "ipv4", NULL); + if (!ipv4) + continue; + + dhcp = lydx_get_descendant(lyd_child(ipv4), "dhcp", NULL); + if (dhcp) + add(ifname, dhcp); + } + } + /* find the modified interfaces */ LYX_LIST_FOR_EACH(difaces, diface, "interface") { const char *ifname = lydx_get_cattr(diface, "name"); diff --git a/test/case/dhcp/client_hostname_resend/test.py b/test/case/dhcp/client_hostname_resend/test.py index 748f8c5bd..b510a1f18 100755 --- a/test/case/dhcp/client_hostname_resend/test.py +++ b/test/case/dhcp/client_hostname_resend/test.py @@ -1,15 +1,14 @@ #!/usr/bin/env python3 """DHCP Hostname Resend -Verify that updating the system hostname regenerates the DHCP client -Finit service file so subsequent DHCP requests advertise the current -hostname (option 12, RFC 2132). +Verify that updating the system hostname restarts the DHCP client so +subsequent DHCP requests advertise the current hostname (option 12, +RFC 2132). Regression test for a bug where the DHCP client callback only reacts on diffs in infix-dhcp-client, so a standalone change of -ietf-system:system/hostname leaves the previously written -/etc/finit.d/available/dhcp-client-.conf untouched and the -running udhcpc keeps announcing the old name. +ietf-system:system/hostname leaves the running udhcpc untouched with +the old '-x hostname:' argument from when it was first started. """ @@ -17,13 +16,6 @@ from infamy.util import until -def finit_conf(ssh, ifname): - """Return the contents of the generated DHCP client Finit service file.""" - path = f"/etc/finit.d/available/dhcp-client-{ifname}.conf" - cmd = ssh.runsh(f"cat {path} 2>/dev/null") - return cmd.stdout - - def udhcpc_cmdline(ssh, ifname): """Return the NUL-separated argv of the running udhcpc for ifname.""" pidfile = f"/run/dhcp-client-{ifname}.pid" From 4f2683090e47f4ba50842420b80a19a001cc9ef9 Mon Sep 17 00:00:00 2001 From: Ejub Sabic Date: Tue, 28 Apr 2026 18:06:49 +0200 Subject: [PATCH 3/3] doc: updated documentation for issue #1439 Signed-off-by: Ejub Sabic --- doc/ChangeLog.md | 1 + test/case/dhcp/client_hostname_resend/test.adoc | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 9f79193fb..0dda4878a 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -23,6 +23,7 @@ All notable changes to the project are documented in this file. `92.2` instead of `92.246.137.39` - Fix #1466: `show container` showing no output for containers whose command line includes environment variables +- Fix #1439: changing hostname does not regenerate DHCP client conf until restart [BPI-R4]: https://docs.banana-pi.org/en/BPI-R4/BananaPi_BPI-R4 diff --git a/test/case/dhcp/client_hostname_resend/test.adoc b/test/case/dhcp/client_hostname_resend/test.adoc index b744523a3..92838425e 100644 --- a/test/case/dhcp/client_hostname_resend/test.adoc +++ b/test/case/dhcp/client_hostname_resend/test.adoc @@ -4,15 +4,14 @@ ifdef::topdoc[:imagesdir: {topdoc}../../test/case/dhcp/client_hostname_resend] ==== Description -Verify that updating the system hostname regenerates the DHCP client -Finit service file so subsequent DHCP requests advertise the current -hostname (option 12, RFC 2132). +Verify that updating the system hostname restarts the DHCP client so +subsequent DHCP requests advertise the current hostname (option 12, +RFC 2132). Regression test for a bug where the DHCP client callback only reacts on diffs in infix-dhcp-client, so a standalone change of -ietf-system:system/hostname leaves the previously written -/etc/finit.d/available/dhcp-client-.conf untouched and the -running udhcpc keeps announcing the old name. +ietf-system:system/hostname leaves the running udhcpc untouched with +the old '-x hostname:' argument from when it was first started. ==== Topology @@ -23,8 +22,8 @@ image::topology.svg[DHCP Hostname Resend topology, align=center, scaledwidth=75% . Set up topology and attach to target DUT . Configure initial hostname '{HOSTNM_A}' . Enable DHCP client sending hostname option -. Verify Finit service carries hostname '{HOSTNM_A}' +. Verify running udhcpc announces hostname '{HOSTNM_A}' . Update system hostname to '{HOSTNM_B}' -. Verify Finit service is regenerated with '{HOSTNM_B}' +. Verify running udhcpc announces hostname '{HOSTNM_B}'