diff --git a/test/case/dhcp/Readme.adoc b/test/case/dhcp/Readme.adoc index a128d72e..fc29a9d4 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 00000000..ae32c841 --- /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 00000000..b744523a --- /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 00000000..748f8c5b --- /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 00000000..d2b1b2bc --- /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 00000000..20a632e9 --- /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 73254d78..ff482751 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