test: add DHCPv4 client hostname resend regression test

Signed-off-by: Ejub Sabic <ejub1946@outlook.com>
This commit is contained in:
Ejub Sabic
2026-04-28 18:00:28 +02:00
parent 82ffb0f4bc
commit 17965bf4cc
7 changed files with 198 additions and 0 deletions
+5
View File
@@ -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[]
<<<
+1
View File
@@ -0,0 +1 @@
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-<iface>.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}'
+104
View File
@@ -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-<iface>.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:<name>)."""
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()
@@ -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> mgmt }",
pos="0,20!",
requires="controller",
];
client [
label="{ <mgmt> mgmt } | client",
pos="200,20!",
requires="infix",
];
host:mgmt -- client:mgmt [requires="mgmt", color=lightgrey]
}
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: 1x1 Pages: 1 -->
<svg width="474pt" height="45pt"
viewBox="0.00 0.00 474.04 45.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 41)">
<title>1x1</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-41 470.04,-41 470.04,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-0.5 0,-36.5 100,-36.5 100,-0.5 0,-0.5"/>
<text text-anchor="middle" x="25" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="50,-0.5 50,-36.5 "/>
<text text-anchor="middle" x="75" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
</g>
<!-- client -->
<g id="node2" class="node">
<title>client</title>
<polygon fill="none" stroke="black" points="350.04,-0.5 350.04,-36.5 466.04,-36.5 466.04,-0.5 350.04,-0.5"/>
<text text-anchor="middle" x="375.04" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="400.04,-0.5 400.04,-36.5 "/>
<text text-anchor="middle" x="433.04" y="-14.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">client</text>
</g>
<!-- host&#45;&#45;client -->
<g id="edge1" class="edge">
<title>host:mgmt&#45;&#45;client:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M100,-18.5C100,-18.5 350.04,-18.5 350.04,-18.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

+3
View File
@@ -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