From 331f60be6452a6b181b86adc4f17338b8d6053d7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 2 Nov 2025 11:36:35 +0100 Subject: [PATCH 1/8] confd: minor, silence libyang warning when built w/o containers Silence confd[3582]: libyang[0]: Invalid argument ctx_node (lyd_find_xpath()) warning in syslog. Signed-off-by: Joachim Wiberg --- src/confd/src/core.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/confd/src/core.c b/src/confd/src/core.c index 8ba0ba985..415114d09 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -221,8 +221,10 @@ static int change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *mod goto free_diff; /* infix-containers */ +#ifdef CONTAINERS if ((rc = infix_containers_change(session, config, diff, event, confd))) goto free_diff; +#endif /* ietf-hardware */ if ((rc = ietf_hardware_change(session, config, diff, event, confd))) @@ -385,9 +387,11 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv) rc = ietf_system_rpc_init(&confd); if (rc) goto err; +#ifdef CONTAINERS rc = infix_containers_rpc_init(&confd); if (rc) goto err; +#endif rc = infix_dhcp_server_rpc_init(&confd); if (rc) goto err; From d53a35b867d26dfbe7a3496b1586312929aa29e8 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 2 Nov 2025 11:28:51 +0100 Subject: [PATCH 2/8] confd: relocate /dhcp-client to /interfaces/interface/ipv4/dhcp Please note, this change drops not only the global enabled flag, but also the per-interface enabled flag, converting it to a presence container. The name of the container is also shortened from dhcp-client -> dhcp. A pattern that expected to be reused also for the DHCPv6 client. Fixes #1109 Signed-off-by: Joachim Wiberg --- .../bananapi,bpi-r3/etc/factory-config.cfg | 47 ++++------- .../etc/factory-config.cfg | 47 ++++------- .../etc/factory-config.cfg | 47 ++++------- doc/ChangeLog.md | 4 + doc/networking.md | 31 +++++++- doc/scripting-sysrepocfg.md | 44 ++++------- package/confd/confd.mk | 2 +- src/confd/bin/gen-interfaces | 20 ++--- src/confd/configure.ac | 3 +- .../migrate/1.6/10-dhcp-client-to-ipv4.sh | 38 +++++++++ src/confd/share/migrate/1.6/Makefile.am | 2 + src/confd/share/migrate/Makefile.am | 2 +- src/confd/src/infix-containers.c | 1 + src/confd/src/infix-dhcp-client.c | 70 +++++++++-------- src/confd/yang/confd.inc | 2 +- src/confd/yang/confd/infix-dhcp-client.yang | 39 ++++------ ...yang => infix-dhcp-client@2025-11-02.yang} | 0 .../case/ietf_routing/route_pref_dhcp/test.py | 38 ++++----- test/case/infix_dhcp/client_basic/test.py | 11 ++- .../case/infix_dhcp/client_default_gw/test.py | 18 +++-- test/case/infix_dhcp/client_routes/test.py | 20 +++-- test/case/infix_dhcp/server_basic/test.py | 20 +++-- test/case/infix_dhcp/server_host/test.py | 46 ++++++----- test/case/infix_dhcp/server_subnets/test.py | 78 +++++++++++-------- 24 files changed, 328 insertions(+), 302 deletions(-) create mode 100755 src/confd/share/migrate/1.6/10-dhcp-client-to-ipv4.sh create mode 100644 src/confd/share/migrate/1.6/Makefile.am rename src/confd/yang/confd/{infix-dhcp-client@2025-01-29.yang => infix-dhcp-client@2025-11-02.yang} (100%) diff --git a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg index aa8740c3a..eda410f60 100644 --- a/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg +++ b/board/aarch64/bananapi-bpi-r3/rootfs/usr/share/product/bananapi,bpi-r3/etc/factory-config.cfg @@ -99,7 +99,20 @@ { "name": "wan", "type": "infix-if-type:ethernet", - "ietf-ip:ipv6": {} + "ietf-ip:ipv6": {}, + "ietf-ip:ipv4": { + "infix-dhcp-client:dhcp": { + "option": [ + {"id": "ntp-server"}, + {"id": "broadcast"}, + {"id": "domain"}, + {"id": "hostname"}, + {"id": "dns-server"}, + {"id": "router"}, + {"id": "netmask"} + ] + } + } }, { "name": "wifi0", @@ -221,36 +234,6 @@ }, "infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo=" }, - "infix-dhcp-client:dhcp-client": { - "client-if": [ - { - "if-name": "wan", - "option": [ - { - "id": "ntp-server" - }, - { - "id": "broadcast" - }, - { - "id": "domain" - }, - { - "id": "hostname" - }, - { - "id": "dns-server" - }, - { - "id": "router" - }, - { - "id": "netmask" - } - ] - } - ] - }, "infix-dhcp-server:dhcp-server": { "option": [ { @@ -311,7 +294,7 @@ ] }, "infix-meta:meta": { - "version": "1.5" + "version": "1.6" }, "infix-services:mdns": { "enabled": true diff --git a/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg b/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg index f4511ef18..527a9f352 100644 --- a/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg +++ b/board/aarch64/friendlyarm-nanopi-r2s/rootfs/usr/share/product/friendlyarm,nanopi-r2s/etc/factory-config.cfg @@ -58,7 +58,20 @@ { "name": "wan", "type": "infix-if-type:ethernet", - "ietf-ip:ipv6": {} + "ietf-ip:ipv6": {}, + "ietf-ip:ipv4": { + "infix-dhcp-client:dhcp": { + "option": [ + {"id": "ntp-server"}, + {"id": "broadcast"}, + {"id": "domain"}, + {"id": "hostname"}, + {"id": "dns-server"}, + {"id": "router"}, + {"id": "netmask"} + ] + } + } } ] }, @@ -172,36 +185,6 @@ }, "infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo=" }, - "infix-dhcp-client:dhcp-client": { - "client-if": [ - { - "if-name": "wan", - "option": [ - { - "id": "ntp-server" - }, - { - "id": "broadcast" - }, - { - "id": "domain" - }, - { - "id": "hostname" - }, - { - "id": "dns-server" - }, - { - "id": "router" - }, - { - "id": "netmask" - } - ] - } - ] - }, "infix-dhcp-server:dhcp-server": { "option": [ { @@ -262,7 +245,7 @@ ] }, "infix-meta:meta": { - "version": "1.5" + "version": "1.6" }, "infix-services:mdns": { "enabled": true diff --git a/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg b/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg index 42ae38cc6..3d9c69bab 100644 --- a/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg +++ b/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-model-b/etc/factory-config.cfg @@ -37,7 +37,20 @@ }, { "name": "eth0", - "type": "infix-if-type:ethernet" + "type": "infix-if-type:ethernet", + "ietf-ip:ipv4": { + "infix-dhcp-client:dhcp": { + "option": [ + {"id": "netmask"}, + {"id": "broadcast"}, + {"id": "router"}, + {"id": "domain"}, + {"id": "hostname"}, + {"id": "dns-server"}, + {"id": "ntp-server"} + ] + } + } }, { "name": "wifi0", @@ -157,38 +170,8 @@ }, "infix-system:motd-banner": "Li0tLS0tLS0uCnwgIC4gLiAgfCBJbmZpeCBPUyDigJQgSW1tdXRhYmxlLkZyaWVuZGx5LlNlY3VyZQp8LS4gdiAuLXwgaHR0cHM6Ly9rZXJuZWxraXQub3JnCictJy0tLSctJwo=" }, - "infix-dhcp-client:dhcp-client": { - "client-if": [ - { - "if-name": "eth0", - "option": [ - { - "id": "netmask" - }, - { - "id": "broadcast" - }, - { - "id": "router" - }, - { - "id": "domain" - }, - { - "id": "hostname" - }, - { - "id": "dns-server" - }, - { - "id": "ntp-server" - } - ] - } - ] - }, "infix-meta:meta": { - "version": "1.5" + "version": "1.6" }, "infix-services:mdns": { "enabled": true diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 49569077e..3c92e5731 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -8,6 +8,10 @@ All notable changes to the project are documented in this file. ### Changes +- The DHCP client configuration has moved from `/infix-dhcp-client:dhcp-client` + to `/interfaces/interface[name]/ipv4/infix-dhcp-client:dhcp`, issue #1109. + The configuration is automatically migrated on upgrade. The DHCP client is + now enabled using a presence container instead of a separate `enabled` leaf - Improvements to `sdcard.img` generation, useful for developers mostly: - The NanoPi R2S bootloader is now automatically built and uploaded to the [`latest-boot` release][lastest-boot] tag diff --git a/doc/networking.md b/doc/networking.md index b6db43fd7..ec7e4ad67 100644 --- a/doc/networking.md +++ b/doc/networking.md @@ -1038,9 +1038,9 @@ will be used, otherwise it falls back to the default algorithm. ![Using DHCP for IPv4 address assignment](img/ip-address-example-ipv4-dhcp.svg) admin@example:/> configure - admin@example:/config/> edit dhcp-client - admin@example:/config/dhcp-client/> set client-if eth0 - admin@example:/config/dhcp-client/> leave + admin@example:/config/> edit interface eth0 ipv4 + admin@example:/config/interface/eth0/ipv4/> set dhcp + admin@example:/config/interface/eth0/ipv4/> leave admin@example:/> show interfaces INTERFACE PROTOCOL STATE DATA eth0 ethernet UP 02:00:00:00:00:00 @@ -1053,6 +1053,31 @@ will be used, otherwise it falls back to the default algorithm. The resulting address (10.1.2.100/24) is of type *dhcp*. +To configure DHCP client options, such as sending a specific hostname to the +server, you can specify options with values: + +``` +admin@example:/> configure +admin@example:/config/> edit interface eth0 ipv4 dhcp +admin@example:/config/interface/eth0/ipv4/dhcp/> set option hostname value myhost +admin@example:/config/interface/eth0/ipv4/dhcp/> show +option hostname { + value myhost; +} +admin@example:/config/interface/eth0/ipv4/dhcp/> leave +admin@example:/> +``` + +> [!TIP] +> The special value `auto` can be used with the hostname option to +> automatically use the configured system hostname. + +Other useful DHCP options include: + +- `client-id` - Send a specific client identifier to the server +- `route-preference` - Set the administrative distance for DHCP-learned routes (default: 5) + +For advanced usage with vendor-specific options, see the YANG model. #### Disabling IPv6 link-local address(es) diff --git a/doc/scripting-sysrepocfg.md b/doc/scripting-sysrepocfg.md index 312fd6ab4..078a2e537 100644 --- a/doc/scripting-sysrepocfg.md +++ b/doc/scripting-sysrepocfg.md @@ -173,11 +173,13 @@ Enabling DHCPv4 client on interface *e0*, with current default options. ``` ~$ cat /tmp/file.json { - "infix-dhcp-client:dhcp-client": { - "enabled": true, - "client-if": [ + "ietf-interfaces:interfaces": { + "interface": [ { - "if-name": "e0" + "name": "e0", + "ietf-ip:ipv4": { + "infix-dhcp-client:dhcp": {} + } } ] } @@ -187,41 +189,27 @@ Enabling DHCPv4 client on interface *e0*, with current default options. ~$ ``` -Disabling DHCPv4 client. +Disabling DHCPv4 client on interface *e0* (remove the dhcp container). ``` ~$ cat /tmp/file.json { - "infix-dhcp-client:dhcp-client": { - "enabled": false - } -} -~$ scp file.json admin@example.local:/tmp/file.json -~$ ssh admin@example.local 'sysrepocfg -E /tmp/file.json -fjson -d running' -~$ -``` - -Configuration for client interface *e0* remains, but does not apply as -DHCPv4 is disabled. - -``` -admin@example:~$ sysrepocfg -X -fjson -d running -x "/infix-dhcp-client:dhcp-client" -{ - "infix-dhcp-client:dhcp-client": { - "enabled": false, - "client-if": [ + "ietf-interfaces:interfaces": { + "interface": [ { - "if-name": "e0" + "name": "e0", + "ietf-ip:ipv4": {} } ] } } -admin@example:~$ +~$ scp file.json admin@example.local:/tmp/file.json +~$ ssh admin@example.local 'sysrepocfg -E /tmp/file.json -fjson -d running' +~$ ``` -To fully remove the DHCPv4 client configuration or a specific -*client-if* with sysrepocfg, one would need to read out the full -configuration, remove relevant parts and read back. +To fully remove the DHCPv4 client configuration, remove the `infix-dhcp-client:dhcp` +container from the interface's ipv4 configuration. ## Enable/Disable IPv6 diff --git a/package/confd/confd.mk b/package/confd/confd.mk index 96024a1f0..ed74875bf 100644 --- a/package/confd/confd.mk +++ b/package/confd/confd.mk @@ -4,7 +4,7 @@ # ################################################################################ -CONFD_VERSION = 1.5 +CONFD_VERSION = 1.6 CONFD_SITE_METHOD = local CONFD_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/confd CONFD_LICENSE = BSD-3-Clause diff --git a/src/confd/bin/gen-interfaces b/src/confd/bin/gen-interfaces index cad922c23..e84feeae0 100755 --- a/src/confd/bin/gen-interfaces +++ b/src/confd/bin/gen-interfaces @@ -172,6 +172,14 @@ if [ -n "$bridge" ]; then "infix-ip:autoconf": { "enabled": $ipv4 } +EOF + if [ "$dhcp" = "true" ]; then + cat < "$temp" && mv "$temp" "$file" diff --git a/src/confd/share/migrate/1.6/Makefile.am b/src/confd/share/migrate/1.6/Makefile.am new file mode 100644 index 000000000..87a472ed0 --- /dev/null +++ b/src/confd/share/migrate/1.6/Makefile.am @@ -0,0 +1,2 @@ +migratedir = $(pkgdatadir)/migrate/1.6 +dist_migrate_DATA = 10-dhcp-client-to-ipv4.sh diff --git a/src/confd/share/migrate/Makefile.am b/src/confd/share/migrate/Makefile.am index 63b1adf73..c5d75c31b 100644 --- a/src/confd/share/migrate/Makefile.am +++ b/src/confd/share/migrate/Makefile.am @@ -1,2 +1,2 @@ -SUBDIRS = 1.0 1.1 1.2 1.3 1.4 1.5 +SUBDIRS = 1.0 1.1 1.2 1.3 1.4 1.5 1.6 migratedir = $(pkgdatadir)/migrate diff --git a/src/confd/src/infix-containers.c b/src/confd/src/infix-containers.c index e73f325c0..359ef320c 100644 --- a/src/confd/src/infix-containers.c +++ b/src/confd/src/infix-containers.c @@ -356,6 +356,7 @@ int infix_containers_change(sr_session_ctx_t *session, struct lyd_node *config, if (diff && !lydx_get_xpathf(diff, CFG_XPATH)) return SR_ERR_OK; + switch (event) { case SR_EV_DONE: break; diff --git a/src/confd/src/infix-dhcp-client.c b/src/confd/src/infix-dhcp-client.c index 5310d6bfc..e43bd0318 100644 --- a/src/confd/src/infix-dhcp-client.c +++ b/src/confd/src/infix-dhcp-client.c @@ -14,7 +14,7 @@ #include "core.h" #define ARPING_MSEC 1000 #define MODULE "infix-dhcp-client" -#define XPATH "/infix-dhcp-client:dhcp-client" +#define XPATH "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/infix-dhcp-client:dhcp" #define CACHE_TEMPLATE "/var/lib/misc/%s.cache" static char *ip_cache(const char *ifname, char *str, size_t len) @@ -278,57 +278,61 @@ static void del(const char *ifname) systemf("initctl -bfq delete dhcp-client-%s", ifname); } -int infix_dhcp_client_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd) +int infix_dhcp_client_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, + sr_event_t event, struct confd *confd) { - struct lyd_node *global, *cifs, *difs, *cif, *dif; + struct lyd_node *ifaces, *difaces, *iface, *diface, *ipv4, *dhcp, *ddhcp; sr_error_t err = 0; - int ena = 0; - switch (event) { - case SR_EV_DONE: - break; - case SR_EV_CHANGE: - case SR_EV_ABORT: - default: + if (event != SR_EV_DONE) return SR_ERR_OK; - } - global = lydx_get_descendant(config, "dhcp-client", NULL); - ena = lydx_is_enabled(global, "enabled"); + ifaces = lydx_get_descendant(config, "interfaces", "interface", NULL); + difaces = lydx_get_descendant(diff, "interfaces", "interface", NULL); - cifs = lydx_get_descendant(config, "dhcp-client", "client-if", NULL); - difs = lydx_get_descendant(diff, "dhcp-client", "client-if", NULL); + /* find the modified interfaces */ + LYX_LIST_FOR_EACH(difaces, diface, "interface") { + const char *ifname = lydx_get_cattr(diface, "name"); + struct lyd_node *dipv4; + char *out; - /* find the modified one, delete or recreate only that */ - LYX_LIST_FOR_EACH(difs, dif, "client-if") { - const char *ifname = lydx_get_cattr(dif, "if-name"); + lyd_print_mem(&out, diface, LYD_JSON, + LYD_PRINT_WITHSIBLINGS | LYD_PRINT_WD_ALL); + + dipv4 = lydx_get_descendant(lyd_child(diface), "ipv4", NULL); + if (!dipv4) + continue; + + ddhcp = lydx_get_descendant(lyd_child(dipv4), "dhcp", NULL); + if (!ddhcp) + continue; - if (lydx_get_op(dif) == LYDX_OP_DELETE) { + /* Check if dhcp container was deleted */ + if (lydx_get_op(ddhcp) == LYDX_OP_DELETE) { del(ifname); continue; } - LYX_LIST_FOR_EACH(cifs, cif, "client-if") { - if (strcmp(ifname, lydx_get_cattr(cif, "if-name"))) + /* Find corresponding interface in config to check if dhcp is present */ + LYX_LIST_FOR_EACH(ifaces, iface, "interface") { + if (strcmp(ifname, lydx_get_cattr(iface, "name"))) continue; - if (!ena || !lydx_is_enabled(cif, "enabled")) + ipv4 = lydx_get_descendant(lyd_child(iface), "ipv4", NULL); + if (!ipv4) { + del(ifname); + break; + } + + dhcp = lydx_get_descendant(lyd_child(ipv4), "dhcp", NULL); + if (!dhcp) del(ifname); else - add(ifname, cif); + add(ifname, dhcp); break; } } - if (!ena) { - LYX_LIST_FOR_EACH(cifs, cif, "client-if") { - const char *ifname = lydx_get_cattr(cif, "if-name"); - - INFO("DHCP client globally disabled, stopping client on %s ...", ifname); - del(ifname); - } - } - return err; } @@ -368,7 +372,7 @@ static int cand(sr_session_ctx_t *session, uint32_t sub_id, const char *module, return SR_ERR_OK; } - err = sr_dup_changes_iter(session, XPATH "/client-if//*", &iter); + err = sr_dup_changes_iter(session, XPATH "//*", &iter); if (err) return err; diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 03b03a70a..9685faded 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -26,7 +26,7 @@ MODULES=( "ieee802-dot1ab-lldp@2022-03-15.yang" "infix-lldp@2025-05-05.yang" "infix-dhcp-common@2025-01-29.yang" - "infix-dhcp-client@2025-01-29.yang" + "infix-dhcp-client@2025-11-02.yang" "infix-dhcp-server@2025-10-28.yang" "infix-firewall@2025-04-26.yang" "infix-firewall-services@2025-04-26.yang" diff --git a/src/confd/yang/confd/infix-dhcp-client.yang b/src/confd/yang/confd/infix-dhcp-client.yang index 872e7af1b..9abe21fad 100644 --- a/src/confd/yang/confd/infix-dhcp-client.yang +++ b/src/confd/yang/confd/infix-dhcp-client.yang @@ -6,6 +6,9 @@ module infix-dhcp-client { import ietf-interfaces { prefix if; } + import ietf-ip { + prefix ip; + } import infix-dhcp-common { prefix dhcp; } @@ -13,6 +16,14 @@ module infix-dhcp-client { contact "kernelkit@googlegroups.com"; description "This module implements a DHCPv4 client"; + revision 2025-11-02 { + description "Migrate DHCP client to ietf-ip augment. + - Relocate from /dhcp-client to /interfaces/interface/ipv4/dhcp + - Presence container instead of enabled leaf, matching autoconf + - Drop client-if list wrapper, not needed anymore + - Drop global enabled flag"; + reference "internal, issue #1109"; + } revision 2025-01-29 { description "Consolidate DHCP options between client and server models. - Rename option attribute 'name' -> 'id' @@ -51,30 +62,10 @@ module infix-dhcp-client { * Data Nodes */ - container dhcp-client { - description "DHCPv4 client configuration"; - - leaf enabled { - type boolean; - default "true"; - description "Globally enables the DHCP client function."; - } - - list client-if { - description "List of interfaces requesting DHCPv4 configuration."; - key "if-name"; - - leaf if-name { - type if:interface-ref; - mandatory true; - description "Name of the interface."; - } - - leaf enabled { - type boolean; - default "true"; - description "Enable DHCP client for this interface."; - } + augment "/if:interfaces/if:interface/ip:ipv4" { + container dhcp { + presence "Enable DHCP client for this interface."; + description "DHCPv4 client configuration"; leaf client-id { type string; diff --git a/src/confd/yang/confd/infix-dhcp-client@2025-01-29.yang b/src/confd/yang/confd/infix-dhcp-client@2025-11-02.yang similarity index 100% rename from src/confd/yang/confd/infix-dhcp-client@2025-01-29.yang rename to src/confd/yang/confd/infix-dhcp-client@2025-11-02.yang diff --git a/test/case/ietf_routing/route_pref_dhcp/test.py b/test/case/ietf_routing/route_pref_dhcp/test.py index d98b1ab6a..f9ba39e5f 100755 --- a/test/case/ietf_routing/route_pref_dhcp/test.py +++ b/test/case/ietf_routing/route_pref_dhcp/test.py @@ -17,7 +17,7 @@ from infamy.util import until, parallel from infamy.netns import IsolatedMacVlans -def configure_interface(name, ip=None, prefix_length=None, forwarding=True): +def configure_interface(name, ip=None, prefix_length=None, forwarding=True, dhcp=False): interface_config = { "name": name, "enabled": True, @@ -28,6 +28,19 @@ def configure_interface(name, ip=None, prefix_length=None, forwarding=True): } if ip and prefix_length: interface_config["ipv4"]["address"].append({"ip": ip, "prefix-length": prefix_length}) + if dhcp: + interface_config["ipv4"]["infix-dhcp-client:dhcp"] = { + "option": [ + {"id": "broadcast"}, + {"id": "dns-server"}, + {"id": "domain"}, + {"id": "hostname"}, + {"id": "ntp-server"}, + {"id": "router"}, + {"id": "netmask"} + ], + "route-preference": 5 + } return interface_config def config_target1(target, data1, data2, link): @@ -35,7 +48,7 @@ def config_target1(target, data1, data2, link): "ietf-interfaces": { "interfaces": { "interface": [ - configure_interface(data1), + configure_interface(data1, dhcp=True), configure_interface(data2, "192.168.30.1", 24), configure_interface(link, "192.168.50.1", 24) ] @@ -61,27 +74,6 @@ def config_target1(target, data1, data2, link): ] } } - }, - "infix-dhcp-client": { - "dhcp-client": { - "enabled": True, - "client-if": [ - { - "if-name": data1, - "enabled": True, - "option": [ - {"id": "broadcast"}, - {"id": "dns-server"}, - {"id": "domain"}, - {"id": "hostname"}, - {"id": "ntp-server"}, - {"id": "router"}, - {"id": "netmask"} - ], - "route-preference": 5 - } - ] - } } }) diff --git a/test/case/infix_dhcp/client_basic/test.py b/test/case/infix_dhcp/client_basic/test.py index afe0a3915..822b49fcd 100755 --- a/test/case/infix_dhcp/client_basic/test.py +++ b/test/case/infix_dhcp/client_basic/test.py @@ -24,13 +24,16 @@ with infamy.dhcp.Server(netns, ip=ADDRESS): _, port = env.ltop.xlate("client", "data") config = { - "dhcp-client": { - "client-if": [{ - "if-name": f"{port}" + "interfaces": { + "interface": [{ + "name": f"{port}", + "ipv4": { + "infix-dhcp-client:dhcp": {} + } }] } } - client.put_config_dict("infix-dhcp-client", config) + client.put_config_dict("ietf-interfaces", config) with test.step("Verify client lease for 10.0.0.42"): until(lambda: iface.address_exist(client, port, ADDRESS)) diff --git a/test/case/infix_dhcp/client_default_gw/test.py b/test/case/infix_dhcp/client_default_gw/test.py index 2837b0c0c..7d16cd567 100755 --- a/test/case/infix_dhcp/client_default_gw/test.py +++ b/test/case/infix_dhcp/client_default_gw/test.py @@ -26,16 +26,20 @@ with infamy.dhcp.Server(netns, router=ROUTER): _, port = env.ltop.xlate("client", "data") config = { - "dhcp-client": { - "client-if": [{ - "if-name": f"{port}", - "option": [ - {"id": "router"} - ] + "interfaces": { + "interface": [{ + "name": f"{port}", + "ipv4": { + "infix-dhcp-client:dhcp": { + "option": [ + {"id": "router"} + ] + } + } }] } } - client.put_config_dict("infix-dhcp-client", config) + client.put_config_dict("ietf-interfaces", config) with test.step("Verify DHCP client has default route via 192.168.0.254"): until(lambda: route.ipv4_route_exist(client, "0.0.0.0/0", ROUTER)) diff --git a/test/case/infix_dhcp/client_routes/test.py b/test/case/infix_dhcp/client_routes/test.py index 62d139cc1..26736d6d8 100755 --- a/test/case/infix_dhcp/client_routes/test.py +++ b/test/case/infix_dhcp/client_routes/test.py @@ -59,14 +59,18 @@ with test.step("Enabling DHCP client, allow option 3 and 121"): _, port = env.ltop.xlate("client", "data") - client.put_config_dict("infix-dhcp-client", { - "dhcp-client": { - "client-if": [{ - "if-name": f"{port}", - "option": [ - {"id": "router"}, - {"id": "classless-static-route"} - ] + client.put_config_dict("ietf-interfaces", { + "interfaces": { + "interface": [{ + "name": f"{port}", + "ipv4": { + "infix-dhcp-client:dhcp": { + "option": [ + {"id": "router"}, + {"id": "classless-static-route"} + ] + } + } }] } }) diff --git a/test/case/infix_dhcp/server_basic/test.py b/test/case/infix_dhcp/server_basic/test.py index 42ff3fb37..164f5f9b6 100755 --- a/test/case/infix_dhcp/server_basic/test.py +++ b/test/case/infix_dhcp/server_basic/test.py @@ -68,14 +68,18 @@ def verify_hostname(dut, hostname): }}) client.put_config_dicts({ - "infix-dhcp-client": { - "dhcp-client": { - "client-if": [{ - "if-name": client["link"], - "option": [ - {"id": "hostname"}, - {"id": "domain"} - ] + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": client["link"], + "ipv4": { + "infix-dhcp-client:dhcp": { + "option": [ + {"id": "hostname"}, + {"id": "domain"} + ] + } + } }] } }, diff --git a/test/case/infix_dhcp/server_host/test.py b/test/case/infix_dhcp/server_host/test.py index 32f622d73..03187716a 100755 --- a/test/case/infix_dhcp/server_host/test.py +++ b/test/case/infix_dhcp/server_host/test.py @@ -116,15 +116,19 @@ "ietf-system": { "system": {"hostname": HOSTNM1} }, - "infix-dhcp-client": { - "dhcp-client": { - "client-if": [{ - "if-name": client1["link"], - "option": [ - {"id": "router"}, - {"id": "client-id", "hex": HOSTCID1}, - {"id": 121} - ] + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": client1["link"], + "ipv4": { + "infix-dhcp-client:dhcp": { + "option": [ + {"id": "router"}, + {"id": "client-id", "hex": HOSTCID1}, + {"id": 121} + ] + } + } }] } }, @@ -134,16 +138,20 @@ "ietf-system": { "system": {"hostname": HOSTNM2} }, - "infix-dhcp-client": { - "dhcp-client": { - "client-if": [{ - "if-name": client2["link"], - "client-id": HOSTCID2, - "option": [ - {"id": "router"}, - {"id": "hostname"}, - {"id": 121} - ] + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": client2["link"], + "ipv4": { + "infix-dhcp-client:dhcp": { + "client-id": HOSTCID2, + "option": [ + {"id": "router"}, + {"id": "hostname"}, + {"id": 121} + ] + } + } }] } }, diff --git a/test/case/infix_dhcp/server_subnets/test.py b/test/case/infix_dhcp/server_subnets/test.py index d7b380e45..c4267d6cf 100755 --- a/test/case/infix_dhcp/server_subnets/test.py +++ b/test/case/infix_dhcp/server_subnets/test.py @@ -200,17 +200,21 @@ def has_system_servers(dut, dns, ntp=None): # the server is behaving correctly. client1.put_config_dicts({ - "infix-dhcp-client": { - "dhcp-client": { - "client-if": [{ - "if-name": client1["server"], - "option": [ - {"id": "hostname", "value": "auto"}, - {"id": "router"}, - {"id": "dns-server"}, - {"id": "ntp-server"}, - {"id": 121} - ] + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": client1["server"], + "ipv4": { + "infix-dhcp-client:dhcp": { + "option": [ + {"id": "hostname", "value": "auto"}, + {"id": "router"}, + {"id": "dns-server"}, + {"id": "ntp-server"}, + {"id": 121} + ] + } + } }] } }, @@ -222,17 +226,21 @@ def has_system_servers(dut, dns, ntp=None): }}) client2.put_config_dicts({ - "infix-dhcp-client": { - "dhcp-client": { - "client-if": [{ - "if-name": client2["server"], - "option": [ - {"id": "hostname", "value": "auto"}, - {"id": "router"}, - {"id": "dns-server"}, - {"id": "ntp-server"}, - {"id": 121} - ] + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": client2["server"], + "ipv4": { + "infix-dhcp-client:dhcp": { + "option": [ + {"id": "hostname", "value": "auto"}, + {"id": "router"}, + {"id": "dns-server"}, + {"id": "ntp-server"}, + {"id": 121} + ] + } + } }] } }, @@ -244,17 +252,21 @@ def has_system_servers(dut, dns, ntp=None): }}) client3.put_config_dicts({ - "infix-dhcp-client": { - "dhcp-client": { - "client-if": [{ - "if-name": client3["server"], - "option": [ - {"id": "hostname", "value": "auto"}, - {"id": "router"}, - {"id": "dns-server"}, - {"id": "ntp-server"}, - {"id": 121} - ] + "ietf-interfaces": { + "interfaces": { + "interface": [{ + "name": client3["server"], + "ipv4": { + "infix-dhcp-client:dhcp": { + "option": [ + {"id": "hostname", "value": "auto"}, + {"id": "router"}, + {"id": "dns-server"}, + {"id": "ntp-server"}, + {"id": 121} + ] + } + } }] } }, From 918353dcc77b5e9a8333153d3d79ecfc2fc45944 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 2 Nov 2025 14:07:26 +0100 Subject: [PATCH 3/8] confd: drop 'enabled' node from IPv4 autoconf container Neither the IPv6 autonconf container, nor the recently moved DHCP client container have an 'enabled' flag. Signed-off-by: Joachim Wiberg --- doc/ChangeLog.md | 2 ++ doc/networking.md | 17 ++++++------ src/confd/bin/gen-interfaces | 14 +++++++--- .../migrate/1.6/20-autoconf-to-presence.sh | 26 +++++++++++++++++++ src/confd/share/migrate/1.6/Makefile.am | 3 ++- src/confd/src/ietf-ip.c | 7 +++-- src/confd/yang/confd.inc | 2 +- src/confd/yang/confd/infix-ip.yang | 10 +++---- ...24-09-16.yang => infix-ip@2025-11-02.yang} | 0 .../ietf_interfaces/ipv4_autoconf/test.py | 7 ++--- test/case/use_case/ospf_container/test.py | 1 - 11 files changed, 60 insertions(+), 29 deletions(-) create mode 100755 src/confd/share/migrate/1.6/20-autoconf-to-presence.sh rename src/confd/yang/confd/{infix-ip@2024-09-16.yang => infix-ip@2025-11-02.yang} (100%) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 3c92e5731..9a006a10b 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -12,6 +12,8 @@ All notable changes to the project are documented in this file. to `/interfaces/interface[name]/ipv4/infix-dhcp-client:dhcp`, issue #1109. The configuration is automatically migrated on upgrade. The DHCP client is now enabled using a presence container instead of a separate `enabled` leaf +- The `enabled` nore for IPv4 autoconf (ZeroConf) has been dropped, `autoconf` + is now a presence container. Configuration automatically migrated on upgrade - Improvements to `sdcard.img` generation, useful for developers mostly: - The NanoPi R2S bootloader is now automatically built and uploaded to the [`latest-boot` release][lastest-boot] tag diff --git a/doc/networking.md b/doc/networking.md index ec7e4ad67..64e0d7d5b 100644 --- a/doc/networking.md +++ b/doc/networking.md @@ -996,7 +996,7 @@ default. admin@example:/> configure admin@example:/config/> edit interface eth0 ipv4 admin@example:/config/interface/eth0/ipv4/> set address 10.0.1.1 prefix-length 24 - admin@example:/config/interface/eth0/ipv4/> set autoconf enabled true + admin@example:/config/interface/eth0/ipv4/> set autoconf admin@example:/config/interface/eth0/ipv4/> diff +interfaces { + interface eth0 { @@ -1004,9 +1004,7 @@ default. + address 10.0.1.1 { + prefix-length 24; + } - + autoconf { - + enabled true; - + } + + autoconf; + } + } +} @@ -1022,15 +1020,18 @@ default. ipv6 ::1/128 (static) admin@example:/> -As shown, the link-local IPv4 address is configured with `set autconf -enabled true`. The resulting address (169.254.1.3/16) is of type -*random* ([ietf-ip.yang][2]). +As shown, the link-local IPv4 address is configured with `set autoconf`. +The presence of the `autoconf` container enables IPv4 link-local address +assignment. The resulting address (169.254.1.3/16) is of type *random* +([ietf-ip.yang][2]). The IPv4LL client also supports a `request-address` setting which can be used to "seed" the client's starting address. If the address is free it will be used, otherwise it falls back to the default algorithm. - admin@example:/config/interface/eth0/ipv4/> set autoconf request-address 169.254.1.2 + admin@example:/config/interface/eth0/ipv4/> edit autoconf + admin@example:/config/interface/eth0/ipv4/autoconf/> set request-address 169.254.1.2 + admin@example:/config/interface/eth0/ipv4/autoconf/> leave #### Use of DHCP for IPv4 address assignment diff --git a/src/confd/bin/gen-interfaces b/src/confd/bin/gen-interfaces index e84feeae0..19d343855 100755 --- a/src/confd/bin/gen-interfaces +++ b/src/confd/bin/gen-interfaces @@ -169,14 +169,20 @@ if [ -n "$bridge" ]; then "name": "$bridge", "type": "infix-if-type:bridge", "ietf-ip:ipv4": { - "infix-ip:autoconf": { - "enabled": $ipv4 - } EOF - if [ "$dhcp" = "true" ]; then + if [ "$ipv4" = "true" ]; then cat < "$temp" && mv "$temp" "$file" diff --git a/src/confd/share/migrate/1.6/Makefile.am b/src/confd/share/migrate/1.6/Makefile.am index 87a472ed0..c3a250a5a 100644 --- a/src/confd/share/migrate/1.6/Makefile.am +++ b/src/confd/share/migrate/1.6/Makefile.am @@ -1,2 +1,3 @@ migratedir = $(pkgdatadir)/migrate/1.6 -dist_migrate_DATA = 10-dhcp-client-to-ipv4.sh +dist_migrate_DATA = 10-dhcp-client-to-ipv4.sh \ + 20-autoconf-to-presence.sh diff --git a/src/confd/src/ietf-ip.c b/src/confd/src/ietf-ip.c index a428967b1..adcf9cb86 100644 --- a/src/confd/src/ietf-ip.c +++ b/src/confd/src/ietf-ip.c @@ -94,7 +94,7 @@ int netdag_gen_ipv4_autoconf(struct dagger *net, struct lyd_node *cif, * for various reasons: was bridge port, ipv4 was disabled... */ zcip = lydx_get_child(ipconf, "autoconf"); - if (zcip && lydx_is_enabled(zcip, "enabled")) { + if (zcip) { struct lyd_node *node; const char *addr; int diff = 0; @@ -105,9 +105,8 @@ int netdag_gen_ipv4_autoconf(struct dagger *net, struct lyd_node *cif, if (node) { const struct lyd_node *tmp; - tmp = lydx_get_child(node, "enabled"); - if (tmp) - diff++; + /* presence container created or deleted */ + diff++; tmp = lydx_get_child(node, "request-address"); if (tmp) diff++; diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 9685faded..cb0056564 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -20,7 +20,7 @@ MODULES=( "ietf-hardware@2018-03-13.yang -e hardware-state -e hardware-sensor" "infix-hardware@2025-10-30.yang" "ieee802-dot1q-types@2022-10-29.yang" - "infix-ip@2024-09-16.yang" + "infix-ip@2025-11-02.yang" "infix-if-type@2025-02-12.yang" "infix-routing@2024-11-27.yang" "ieee802-dot1ab-lldp@2022-03-15.yang" diff --git a/src/confd/yang/confd/infix-ip.yang b/src/confd/yang/confd/infix-ip.yang index 88a1c93fc..094a972ae 100644 --- a/src/confd/yang/confd/infix-ip.yang +++ b/src/confd/yang/confd/infix-ip.yang @@ -18,6 +18,10 @@ module infix-ip { description "This module augments ietf-ip with Infix extensions and deviations."; + revision 2025-11-02 { + description "Change autoconf to presence container, removing enabled leaf."; + reference "Internal, issue #1109."; + } revision 2024-09-16 { description "Add support for IPv4LL request-address."; reference "Internal."; @@ -36,14 +40,10 @@ module infix-ip { */ augment "/if:interfaces/if:interface/ip:ipv4" { container autoconf { + presence "Enable IPv4 link-local address autoconfiguration for this interface."; description "Parameters to control the autoconfiguration of IPv4 address."; reference "RFC 3927: Dynamic Configuration of IPv4 Link-Local Addresses"; - leaf enabled { - description "Use a ZeroConf/IPv4LL agent to retrieve an 169.254/16 address."; - type boolean; - } - leaf request-address { description "Try to acquire the specified IP address, if available. diff --git a/src/confd/yang/confd/infix-ip@2024-09-16.yang b/src/confd/yang/confd/infix-ip@2025-11-02.yang similarity index 100% rename from src/confd/yang/confd/infix-ip@2024-09-16.yang rename to src/confd/yang/confd/infix-ip@2025-11-02.yang diff --git a/test/case/ietf_interfaces/ipv4_autoconf/test.py b/test/case/ietf_interfaces/ipv4_autoconf/test.py index a1e59f709..5555758ab 100755 --- a/test/case/ietf_interfaces/ipv4_autoconf/test.py +++ b/test/case/ietf_interfaces/ipv4_autoconf/test.py @@ -57,9 +57,7 @@ def no_linklocal(target, iface): "name": tport, "enabled": True, "ipv4": { - "autoconf": { - "enabled": True - } + "infix-ip:autoconf": {} } } ] @@ -79,8 +77,7 @@ def no_linklocal(target, iface): "name": tport, "enabled": True, "ipv4": { - "autoconf": { - "enabled": True, + "infix-ip:autoconf": { "request-address": "169.254.42.42" } } diff --git a/test/case/use_case/ospf_container/test.py b/test/case/use_case/ospf_container/test.py index 1568f8d99..1ae865e9e 100755 --- a/test/case/use_case/ospf_container/test.py +++ b/test/case/use_case/ospf_container/test.py @@ -212,7 +212,6 @@ def config_generic(target, router, ring1, ring2, link): "enabled": True, "forwarding": True, "infix-ip:autoconf": { - "enabled": True, "request-address": "169.254.1.1" } }, From 1e80b20856e82ae5253d3789c9ab1353684f55f1 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 2 Nov 2025 21:19:02 +0100 Subject: [PATCH 4/8] board/common: utilize new finit-style logging for factory reset Signed-off-by: Joachim Wiberg --- board/common/rootfs/usr/libexec/infix/mnt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/board/common/rootfs/usr/libexec/infix/mnt b/board/common/rootfs/usr/libexec/infix/mnt index d46af0c64..fdc71f1a0 100755 --- a/board/common/rootfs/usr/libexec/infix/mnt +++ b/board/common/rootfs/usr/libexec/infix/mnt @@ -44,12 +44,15 @@ check_factory() factory_reset() { find /sys/class/leds/ -type l -exec sh -c 'echo 100 > $0/brightness' {} \; - logger $opt -p user.crit -t "$nm" "Resetting to factory defaults." + print_start "Resetting to factory defaults." + logger $opt -p user.notice -t "$nm" "Resetting to factory defaults." rm -rf /mnt/cfg/* /mnt/var/* - - logger $opt -p user.crit -t "$nm" "Factory reset complete." sync + + logger $opt -p user.notice -t "$nm" "Factory reset complete." + print_end 0 "Factory reset complete." + print_restore } is_mmc() From 70d51420c9012a744a5f951b0ad101e605760646 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 2 Nov 2025 18:41:32 +0100 Subject: [PATCH 5/8] board/common: fix scary boot warnings, follow-up to 45efa94 In 45efa94 we introduced new mount options for the: 'aux', 'cfg', and 'var' partitions. To save boot time, we also disabled the periodic fsck check, which does not affect journal replay or other Ext4 safety or integrity checks. The latter involved calling tune2fs at runtime, but this caused the following to appear for the 'aux' partition, which is an ext2 for systems that need to change U-Boot settings, e.g., to set MAC address on systems without a VPD. EXT4-fs: Mount option(s) incompatible with ext2 To make matters worse, the new mount option(s), e.g., 'commit=30', gave us another set of warnings from the kernel. This was due to the fstype being 'auto', so the kernel objected to options not being applicable to every filesystem it tried before ending up with extfs: squashfs: Unknown parameter 'commit' vfat: Unknown parameter 'commit' exfat: Unknown parameter 'commit' fuseblk: Unknown parameter 'commit' btrfs: Unknown parameter 'errors' This commit explicitly sets aux to ext2, and the others to ext4, and it now checks before calling tune2fs that the partition/disk image is ext4. Signed-off-by: Joachim Wiberg --- board/common/rootfs/etc/fstab | 12 ++++++------ board/common/rootfs/usr/libexec/infix/mnt | 6 ++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/board/common/rootfs/etc/fstab b/board/common/rootfs/etc/fstab index fc5850c79..d84206d61 100644 --- a/board/common/rootfs/etc/fstab +++ b/board/common/rootfs/etc/fstab @@ -15,9 +15,9 @@ cfgfs /config configfs nofail,noauto 0 0 # The chosen backing storage for the overlays placed on /cfg, /etc, # /home, /root, and /var, are determined dynamically by /usr/libexec/infix/mnt # depending on the available devices. -mnttmp /mnt/tmp tmpfs defaults 0 0 -LABEL=aux /mnt/aux auto noatime,nodiratime,noauto,commit=30,errors=remount-ro 0 0 -LABEL=var /mnt/var auto noatime,nodiratime,noauto,commit=30,errors=remount-ro 0 0 -LABEL=cfg /mnt/cfg auto noatime,nodiratime,noauto,commit=30,errors=remount-ro 0 0 -hostfs /mnt/host 9p cache=none,msize=16384,noauto 0 0 -/usr/libexec/infix/mnt# /cfg helper none 0 0 +mnttmp /mnt/tmp tmpfs defaults 0 0 +LABEL=aux /mnt/aux ext2 noatime,nodiratime,noauto,errors=remount-ro 0 0 +LABEL=var /mnt/var ext4 noatime,nodiratime,noauto,commit=30,errors=remount-ro 0 0 +LABEL=cfg /mnt/cfg ext4 noatime,nodiratime,noauto,commit=30,errors=remount-ro 0 0 +hostfs /mnt/host 9p cache=none,msize=16384,noauto 0 0 +/usr/libexec/infix/mnt# /cfg helper none 0 0 diff --git a/board/common/rootfs/usr/libexec/infix/mnt b/board/common/rootfs/usr/libexec/infix/mnt index fdc71f1a0..852ead56f 100755 --- a/board/common/rootfs/usr/libexec/infix/mnt +++ b/board/common/rootfs/usr/libexec/infix/mnt @@ -292,8 +292,10 @@ mount_rw() # TODO: Also look for UBI partitions - # - tune2fs -c 0 -i 0 LABEL="$1" 2>/dev/null + # Disable periodic fsck, yet keeping safety checks on ext4 + if grep "LABEL=$label" /etc/fstab |grep -q ext4; then + tune2fs -c 0 -i 0 LABEL="$1" 2>/dev/null + fi mount LABEL="$1" 2>/dev/null && return 0 return 1 From d2e55eadf825d343f496f074695a45448b1a280e Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 2 Nov 2025 18:39:06 +0100 Subject: [PATCH 6/8] board/common: check if partition/LABEL is available Check if partition/LABEL is available before trying to run tune2fs and mount commands on it. This change masks the kernel output: LABEL=var: Can't lookup blockdev commne when running `make run` on x86_64 builds. Signed-off-by: Joachim Wiberg --- board/common/rootfs/usr/libexec/infix/mnt | 43 +++++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/board/common/rootfs/usr/libexec/infix/mnt b/board/common/rootfs/usr/libexec/infix/mnt index 852ead56f..4421613f3 100755 --- a/board/common/rootfs/usr/libexec/infix/mnt +++ b/board/common/rootfs/usr/libexec/infix/mnt @@ -108,15 +108,18 @@ find_partition_by_label() esac disk="/dev/$devname" + + # + # 1. Try GPT/MBR partition label using sgdisk + # result=$(sgdisk -p "$disk" 2>/dev/null | awk -v label="$label" -v devname="$devname" ' - /^ *[0-9]/ { + /^ *[0-9]/ { if ($7 == label) { - if (devname ~ /^(mmcblk|nvme|loop)/) { - print devname "p" $1 - } else { - print devname $1 - } - exit 0 + if (devname ~ /^(mmcblk|nvme|loop)/) + print devname "p" $1; + else + print devname $1; + exit 0; } } ') @@ -125,7 +128,30 @@ find_partition_by_label() echo "$result" return 0 fi + + # + # 2. Fallback: Check if the whole disk is an ext4/ext2/ext3 filesystem + # + + # Check for ext4/ext2/ext3 magic number (0xEF53) at offset 1080 (1024+56). + magic_number=$(dd if="$disk" bs=1 skip=1080 count=2 2>/dev/null | od -t x2 -A n | tr -d ' \n') + + # Check for both Little-Endian ('53ef') and Big-Endian ('ef53') interpretations of 0xEF53. + # This supports bi-endian architectures like MIPS that may run in BE mode, + # as well as the LE mode which is standard for RISC-V and ext filesystems. + if [ "$magic_number" = "ef53" ] || [ "$magic_number" = "53ef" ]; then + + # Read the volume label from offset 1144 (1024+120) + fslabel=$(dd if="$disk" bs=1 skip=1144 count=16 2>/dev/null | tr -d '\000') + logger $opt -p user.notice -t "$nm" "Found label $fslabel on disk $disk ..." + + if [ "$fslabel" = "$label" ]; then + echo "$devname" + return 0 + fi + fi done + return 1 } @@ -276,6 +302,9 @@ mount_rw() # If something is already setup, leave it be. mountpoint -q "/$1" && return 0 + # If partition doesn't exist (var is optional), signal caller. + find_partition_by_label "$1" >/dev/null || return 1 + # Check if /var has been resized to fill the sdcard/eMMC if [ "$1" = "var" ] && is_mmc; then if [ -f /mnt/aux/resized ] || [ -f /mnt/aux/resized.failed ]; then From cb633a78d6d0a992cb042aec9db5dacc183ff57b Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 2 Nov 2025 21:02:06 +0100 Subject: [PATCH 7/8] confd: minor coding style fixes and refactoring Collapse, simplify, and break up ietf_keystore_change() into multiple functions. Signed-off-by: Joachim Wiberg --- src/confd/src/core.c | 21 ++-- src/confd/src/core.h | 1 - src/confd/src/ietf-keystore.c | 217 ++++++++++++++++++---------------- 3 files changed, 131 insertions(+), 108 deletions(-) diff --git a/src/confd/src/core.c b/src/confd/src/core.c index 415114d09..9b29b4883 100644 --- a/src/confd/src/core.c +++ b/src/confd/src/core.c @@ -8,8 +8,8 @@ struct confd confd; -int core_startup_save(sr_session_ctx_t *session, uint32_t sub_id, const char *module, - const char *xpath, sr_event_t event, unsigned request_id, void *priv) +static int core_startup_save(sr_session_ctx_t *session, uint32_t sub_id, const char *module, + const char *xpath, sr_event_t event, unsigned request_id, void *priv) { sr_event_t last_event = -1; static unsigned int last_request = -1; @@ -260,16 +260,21 @@ static int change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *mod AUDIT("The new configuration has been applied."); } + free_diff: - lyd_free_tree(diff); - return rc; + lyd_free_tree(diff); + return rc; } -static inline int subscribe_module(char *model, struct confd *confd, int flags) { - return sr_module_change_subscribe(confd->session, model, "//.", change_cb, confd, - CB_PRIO_PRIMARY, SR_SUBSCR_CHANGE_ALL_MODULES | SR_SUBSCR_DEFAULT | flags, &confd->sub) && +static inline int subscribe_module(char *model, struct confd *confd, int flags) +{ + ERROR("core: subscribing to models"); + return sr_module_change_subscribe(confd->session, model, "//.", change_cb, confd, + CB_PRIO_PRIMARY, SR_SUBSCR_CHANGE_ALL_MODULES | + SR_SUBSCR_DEFAULT | flags, &confd->sub) && sr_module_change_subscribe(confd->startup, model, "//.", core_startup_save, NULL, - CB_PRIO_PASSIVE, SR_SUBSCR_PASSIVE | SR_SUBSCR_CHANGE_ALL_MODULES, &confd->sub); + CB_PRIO_PASSIVE, SR_SUBSCR_CHANGE_ALL_MODULES | + SR_SUBSCR_PASSIVE, &confd->sub); } int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv) diff --git a/src/confd/src/core.h b/src/confd/src/core.h index 0317f83ec..d7c2f2e0d 100644 --- a/src/confd/src/core.h +++ b/src/confd/src/core.h @@ -135,7 +135,6 @@ struct confd { struct dagger netdag; }; -int core_startup_save (sr_session_ctx_t *, uint32_t, const char *, const char *, sr_event_t, unsigned, void *); static inline int register_change(sr_session_ctx_t *session, const char *module, const char *xpath, int flags, sr_module_change_cb cb, void *arg, sr_subscription_ctx_t **sub) diff --git a/src/confd/src/ietf-keystore.c b/src/confd/src/ietf-keystore.c index a87303ee5..fa1d786fe 100644 --- a/src/confd/src/ietf-keystore.c +++ b/src/confd/src/ietf-keystore.c @@ -51,89 +51,122 @@ static char *filerd(const char *fn, size_t len) return buf; } -int ietf_keystore_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd) +static int gen_hostkey(const char *name, struct lyd_node *change) { - struct lyd_node *changes, *change; - sr_val_t *list = NULL; + const char *private_key, *public_key; int rc = SR_ERR_OK; + + private_key = lydx_get_cattr(change, "cleartext-private-key"); + public_key = lydx_get_cattr(change, "public-key"); + + if (mkdir(SSH_HOSTKEYS_NEXT, 0600) && (errno != EEXIST)) { + ERRNO("Failed creating %s", SSH_HOSTKEYS_NEXT); + rc = SR_ERR_INTERNAL; + } + + if (systemf("/usr/libexec/infix/mksshkey %s %s %s %s", name, SSH_HOSTKEYS_NEXT, public_key, private_key)) + rc = SR_ERR_INTERNAL; + + return rc; +} + +static int keystore_update(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff) +{ + const char *xpath = "/ietf-keystore:keystore/asymmetric-keys/asymmetric-key"; + sr_val_t *list = NULL; size_t count = 0; - if (diff && !lydx_find_xpathf(diff, XPATH_KEYSTORE_)) - return SR_ERR_OK; + int rc; - switch (event) { - case SR_EV_UPDATE: - rc = sr_get_items(session, "/ietf-keystore:keystore/asymmetric-keys/asymmetric-key", 0, 0, &list, &count); - if (rc != SR_ERR_OK) { - ERROR("Cannot find any asymmetric keys in configuration"); - return 0; + rc = sr_get_items(session, xpath, 0, 0, &list, &count); + if (rc != SR_ERR_OK) { + ERROR("Cannot find any asymmetric keys in configuration"); + return 0; + } + + for (size_t i = 0; i < count; i++) { + char *name = srx_get_str(session, "%s/name", list[i].xpath); + char *public_key_format, *private_key_format; + char *pub_key = NULL, *priv_key = NULL; + sr_val_t *entry = &list[i]; + + if (srx_isset(session, "%s/cleartext-private-key", entry->xpath) || + srx_isset(session, "%s/public-key", entry->xpath)) + continue; + + public_key_format = srx_get_str(session, "%s/public-key-format", entry->xpath); + if (!public_key_format) + continue; + + private_key_format = srx_get_str(session, "%s/private-key-format", entry->xpath); + if (!private_key_format) { + free(public_key_format); + continue; } + if (strcmp(private_key_format, "infix-crypto-types:rsa-private-key-format") || + strcmp(public_key_format, "infix-crypto-types:ssh-public-key-format")) + continue; - for (size_t i = 0; i < count; ++i) { - sr_val_t *entry = &list[i]; - - if (!srx_isset(session, "%s/cleartext-private-key", entry->xpath) && !srx_isset(session, "%s/public-key", entry->xpath)) { - char *private_key_format, *public_key_format; - - public_key_format = srx_get_str(session, "%s/public-key-format", entry->xpath); - if (!public_key_format) - continue; - private_key_format = srx_get_str(session, "%s/private-key-format", entry->xpath); - if (!private_key_format) { - free(public_key_format); - continue; - } - - if (!strcmp(private_key_format, "infix-crypto-types:rsa-private-key-format") && - !strcmp(public_key_format, "infix-crypto-types:ssh-public-key-format")) { - char *pub_key = NULL, *priv_key = NULL, *name; - - name = srx_get_str(session, "%s/name", entry->xpath); - NOTE("SSH key (%s) does not exist, generating...", name); - if (systemf("/usr/libexec/infix/mkkeys %s %s", SSH_PRIVATE_KEY, SSH_PUBLIC_KEY)) { - ERROR("Failed to generate SSH keys for %s", name); - goto next; - } - - priv_key = filerd(SSH_PRIVATE_KEY, filesz(SSH_PRIVATE_KEY)); - if (!priv_key) - goto next; - - pub_key = filerd(SSH_PUBLIC_KEY, filesz(SSH_PUBLIC_KEY)); - if (!pub_key) - goto next; - - rc = srx_set_str(session, priv_key, 0, "%s/cleartext-private-key", entry->xpath); - if (rc) { - ERROR("Failed setting private key for %s... rc: %d", name, rc); - goto next; - } - rc = srx_set_str(session, pub_key, 0, "%s/public-key", entry->xpath); - if (rc != SR_ERR_OK) { - ERROR("Failed setting public key for %s... rc: %d", name, rc); - goto next; - } - next: - if (erase(SSH_PRIVATE_KEY)) - ERRNO("Failed removing SSH server private key"); - if (erase(SSH_PUBLIC_KEY)) - ERRNO("Failed removing SSH server public key"); - - if (priv_key) - free(priv_key); - - if (pub_key) - free(pub_key); - - free(name); - } - free(public_key_format); - free(private_key_format); - } + NOTE("SSH key (%s) does not exist, generating...", name); + if (systemf("/usr/libexec/infix/mkkeys %s %s", SSH_PRIVATE_KEY, SSH_PUBLIC_KEY)) { + ERROR("Failed generating SSH keys for %s", name); + goto next; } - if (list) - sr_free_values(list, count); + priv_key = filerd(SSH_PRIVATE_KEY, filesz(SSH_PRIVATE_KEY)); + if (!priv_key) + goto next; + + pub_key = filerd(SSH_PUBLIC_KEY, filesz(SSH_PUBLIC_KEY)); + if (!pub_key) + goto next; + + rc = srx_set_str(session, priv_key, 0, "%s/cleartext-private-key", entry->xpath); + if (rc) { + ERROR("Failed setting private key for %s... rc: %d", name, rc); + goto next; + } + + rc = srx_set_str(session, pub_key, 0, "%s/public-key", entry->xpath); + if (rc != SR_ERR_OK) { + ERROR("Failed setting public key for %s... rc: %d", name, rc); + goto next; + } + next: + if (erase(SSH_PRIVATE_KEY)) + ERRNO("Failed removing SSH server private key"); + if (erase(SSH_PUBLIC_KEY)) + ERRNO("Failed removing SSH server public key"); + + if (priv_key) + free(priv_key); + + if (pub_key) + free(pub_key); + + free(name); + free(public_key_format); + free(private_key_format); + } + + if (list) + sr_free_values(list, count); + + return 0; +} + +int ietf_keystore_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, + sr_event_t event, struct confd *confd) +{ + struct lyd_node *changes, *change; + int rc = SR_ERR_OK; + + if (diff && !lydx_find_xpathf(diff, XPATH_KEYSTORE_)) + return SR_ERR_OK; + + switch (event) { + case SR_EV_UPDATE: + rc = keystore_update(session, config, diff); break; case SR_EV_CHANGE: case SR_EV_ENABLED: @@ -142,49 +175,35 @@ int ietf_keystore_change(sr_session_ctx_t *session, struct lyd_node *config, str rmrf(SSH_HOSTKEYS_NEXT); return SR_ERR_OK; case SR_EV_DONE: - if(fexist(SSH_HOSTKEYS_NEXT)) { - if(rmrf(SSH_HOSTKEYS)) { + if (fexist(SSH_HOSTKEYS_NEXT)) { + if (rmrf(SSH_HOSTKEYS)) ERRNO("Failed to remove old SSH hostkeys: %d", errno); - } - if (rename(SSH_HOSTKEYS_NEXT, SSH_HOSTKEYS)) ERRNO("Failed switching to new %s", SSH_HOSTKEYS); } return SR_ERR_OK; - default: return SR_ERR_OK; } changes = lydx_get_descendant(config, "keystore", "asymmetric-keys", "asymmetric-key", NULL); - LYX_LIST_FOR_EACH(changes, change, "asymmetric-key") { - const char *name, *private_key_type, *public_key_type; - const char *private_key, *public_key; + const char *name = lydx_get_cattr(change, "name"); + const char *type; - name = lydx_get_cattr(change, "name"); - private_key_type = lydx_get_cattr(change, "private-key-format"); - public_key_type = lydx_get_cattr(change, "public-key-format"); - - if (strcmp(private_key_type, "infix-crypto-types:rsa-private-key-format")) { - INFO("Private key %s is not of SSH type", name); + type = lydx_get_cattr(change, "private-key-format"); + if (strcmp(type, "infix-crypto-types:rsa-private-key-format")) { + INFO("Private key %s is not of SSH type (%s)", name, type); continue; } - if (strcmp(public_key_type, "infix-crypto-types:ssh-public-key-format")) { - INFO("Public key %s is not of SSH type", name); + type = lydx_get_cattr(change, "public-key-format"); + if (strcmp(type, "infix-crypto-types:ssh-public-key-format")) { + INFO("Public key %s is not of SSH type (%s)", name, type); continue; } - private_key = lydx_get_cattr(change, "cleartext-private-key"); - public_key = lydx_get_cattr(change, "public-key"); - - if (mkdir(SSH_HOSTKEYS_NEXT, 0600) && (errno != EEXIST)) { - ERRNO("Failed creating %s", SSH_HOSTKEYS_NEXT); - rc = SR_ERR_INTERNAL; - } - if(systemf("/usr/libexec/infix/mksshkey %s %s %s %s", name, SSH_HOSTKEYS_NEXT, public_key, private_key)) - rc = SR_ERR_INTERNAL; + gen_hostkey(name, change); } return rc; From 226ea49e7c11e149c000dd6670463e373e886f92 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 2 Nov 2025 21:05:34 +0100 Subject: [PATCH 8/8] confd: fix annoying warning in log This fixes the annoying libyang warning after commit da29771. confd[3375]: libyang[0]: Invalid argument ctx_node (lyd_find_xpath()). This happens when the diff is used in the wrong event when it is NULL. Signed-off-by: Joachim Wiberg --- src/confd/src/infix-dhcp-server.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/confd/src/infix-dhcp-server.c b/src/confd/src/infix-dhcp-server.c index 2a5431749..ffff18407 100644 --- a/src/confd/src/infix-dhcp-server.c +++ b/src/confd/src/infix-dhcp-server.c @@ -300,9 +300,6 @@ int infix_dhcp_server_change(sr_session_ctx_t *session, struct lyd_node *config, int enabled = 0, added = 0, deleted = 0; sr_error_t err = 0; - if (!lydx_get_xpathf(diff, CFG_XPATH)) - return SR_ERR_OK; - switch (event) { case SR_EV_DONE: break; @@ -312,6 +309,9 @@ int infix_dhcp_server_change(sr_session_ctx_t *session, struct lyd_node *config, return SR_ERR_OK; } + if (!lydx_get_xpathf(diff, CFG_XPATH)) + return SR_ERR_OK; + global = lydx_get_descendant(config, "dhcp-server", NULL); enabled = lydx_is_enabled(global, "enabled");