Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 14 additions & 0 deletions src/confd/src/dhcp-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
5 changes: 5 additions & 0 deletions test/case/dhcp/Readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -30,6 +31,10 @@ include::client_hostname/Readme.adoc[]

<<<

include::client_hostname_resend/Readme.adoc[]

<<<

include::client6_basic/Readme.adoc[]

<<<
Expand Down
1 change: 1 addition & 0 deletions test/case/dhcp/client_hostname_resend/Readme.adoc
29 changes: 29 additions & 0 deletions test/case/dhcp/client_hostname_resend/test.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
=== DHCP Hostname Resend

ifdef::topdoc[:imagesdir: {topdoc}../../test/case/dhcp/client_hostname_resend]

==== Description

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 running udhcpc untouched with
the old '-x hostname:' argument from when it was first started.

==== 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 running udhcpc announces hostname '{HOSTNM_A}'
. Update system hostname to '{HOSTNM_B}'
. Verify running udhcpc announces hostname '{HOSTNM_B}'


96 changes: 96 additions & 0 deletions test/case/dhcp/client_hostname_resend/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""DHCP Hostname Resend

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 running udhcpc untouched with
the old '-x hostname:' argument from when it was first started.

"""

import infamy
from infamy.util import until


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()
22 changes: 22 additions & 0 deletions test/case/dhcp/client_hostname_resend/topology.dot
Original file line number Diff line number Diff line change
@@ -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]
}
33 changes: 33 additions & 0 deletions test/case/dhcp/client_hostname_resend/topology.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions test/case/dhcp/dhcp_client.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down