From cb5d804a88e596ad9dfeab87137c647b4b57dfc8 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Tue, 28 Oct 2025 13:34:28 +0100 Subject: [PATCH] confd: add dhcp-server validation of address pool and/or static host A valid DHCP server setup for a subnet is one of pool and/or at least one static host entry/lease. If pool is enabled the pool must have a start and an end address. To allow setting up a DHCP server with no pool and at least one static host entry/lease, we make the pool a presence container, otherwise the pool will always be set and trigger the below inference. When an interactive CLI/Web user enables the address pool we infer a default range .100-.250, but only for /24, C-class networks. This is what most users know and expect. The YANG model now validates that: - If an address pool is created, both start-address and end-address must be set - Each subnet must have either a pool or at least one static host entry - The pool container is now a presence container, so "no pool" fully deletes it Fixes #1121 Signed-off-by: Joachim Wiberg --- doc/ChangeLog.md | 6 ++- src/confd/src/infix-dhcp-server.c | 42 +++++++++++++++++-- src/confd/yang/confd.inc | 2 +- src/confd/yang/confd/infix-dhcp-server.yang | 16 +++++++ ...yang => infix-dhcp-server@2025-10-28.yang} | 0 5 files changed, 60 insertions(+), 6 deletions(-) rename src/confd/yang/confd/{infix-dhcp-server@2025-01-29.yang => infix-dhcp-server@2025-10-28.yang} (100%) diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 743d909c..484a2b39 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -3,7 +3,7 @@ Change Log All notable changes to the project are documented in this file. -[v25.10.0][UNRELEASED] - +[v25.10.0][] - 2025-10-31 ------------------------- ### Changes @@ -31,6 +31,10 @@ All notable changes to the project are documented in this file. - Fix #981: copying any file, including `running-config`, to the persistent back-end store for `startup-config`, does not take +- Fix #1121: Ensure DHCP server does not crash if no address pool is set. This + change infers a pool range (only) for /24 networks, and only when a pool is + enabled. YANG validation for this and other use-cases is also included. As + an unforeseen bonus, Infix now also support non-pool (static lease) setups - Fix #1146: Possible to set longer containers names than the system supports. Root cause, a limit of 15 characters implicitly imposed by the service mgmt daemon, Finit. The length has not been increased to 64 characters (min: 2) diff --git a/src/confd/src/infix-dhcp-server.c b/src/confd/src/infix-dhcp-server.c index 91302b10..2e921b92 100644 --- a/src/confd/src/infix-dhcp-server.c +++ b/src/confd/src/infix-dhcp-server.c @@ -223,10 +223,12 @@ static void add(const char *subnet, struct lyd_node *cfg) start = lydx_get_cattr(node, "start-address"); end = lydx_get_cattr(node, "end-address"); - fprintf(fp, "\n# Subnet pool %s - %s\n", start, end); - fprintf(fp, "dhcp-range=%s%sset:%s,%s,%s,%s\n", - ifname ?: "", ifname ? "," : "", tag, - start, end, lydx_get_cattr(node, "lease-time")); + if (start && end) { + fprintf(fp, "\n# Subnet pool %s - %s\n", start, end); + fprintf(fp, "dhcp-range=%s%sset:%s,%s,%s,%s\n", + ifname ?: "", ifname ? "," : "", tag, + start, end, lydx_get_cattr(node, "lease-time")); + } } err: @@ -400,6 +402,7 @@ static int cand(sr_session_ctx_t *session, uint32_t sub_id, const char *module, "router", "dns-server", }; + sr_val_t *subnets = NULL; size_t i, cnt = 0; if (event != SR_EV_UPDATE && event != SR_EV_CHANGE) @@ -413,6 +416,37 @@ static int cand(sr_session_ctx_t *session, uint32_t sub_id, const char *module, srx_set_item(session, &inferred, 0, fmt, opt[i]); } + /* Infer pool: .100 to .250 for /24 networks */ + if (sr_get_items(session, CFG_XPATH "/subnet/subnet", 0, 0, &subnets, &cnt) == 0) { + for (i = 0; i < cnt; i++) { + const char *pool_xpathfmt = CFG_XPATH "/subnet[subnet='%s']/pool"; + const char *host_xpathfmt = CFG_XPATH "/subnet[subnet='%s']/host"; + const char *subnet = subnets[i].data.string_val; + sr_val_t pool_val = { .type = SR_STRING_T }; + char start_addr[16], end_addr[16]; + unsigned int a, b, c, d, len; + size_t pool_cnt = 0, host_cnt = 0; + + if (sscanf(subnet, "%u.%u.%u.%u/%u", &a, &b, &c, &d, &len) != 5 || len != 24) + continue; + + /* Don't auto-infer if pool or static hosts already exist */ + if (!srx_nitems(session, &pool_cnt, pool_xpathfmt, subnet) && pool_cnt) + continue; + if (!srx_nitems(session, &host_cnt, host_xpathfmt, subnet) && host_cnt) + continue; + + snprintf(start_addr, sizeof(start_addr), "%u.%u.%u.100", a, b, c); + snprintf(end_addr, sizeof(end_addr), "%u.%u.%u.250", a, b, c); + + pool_val.data.string_val = start_addr; + srx_set_item(session, &pool_val, 0, CFG_XPATH "/subnet[subnet='%s']/pool/start-address", subnet); + pool_val.data.string_val = end_addr; + srx_set_item(session, &pool_val, 0, CFG_XPATH "/subnet[subnet='%s']/pool/end-address", subnet); + } + sr_free_values(subnets, cnt); + } + return 0; } diff --git a/src/confd/yang/confd.inc b/src/confd/yang/confd.inc index 298145df..c868a70c 100644 --- a/src/confd/yang/confd.inc +++ b/src/confd/yang/confd.inc @@ -27,7 +27,7 @@ MODULES=( "infix-lldp@2025-05-05.yang" "infix-dhcp-common@2025-01-29.yang" "infix-dhcp-client@2025-01-29.yang" - "infix-dhcp-server@2025-01-29.yang" + "infix-dhcp-server@2025-10-28.yang" "infix-firewall@2025-04-26.yang" "infix-firewall-services@2025-04-26.yang" "infix-firewall-icmp-types@2025-04-26.yang" diff --git a/src/confd/yang/confd/infix-dhcp-server.yang b/src/confd/yang/confd/infix-dhcp-server.yang index 8bf0e78f..84d6242d 100644 --- a/src/confd/yang/confd/infix-dhcp-server.yang +++ b/src/confd/yang/confd/infix-dhcp-server.yang @@ -20,6 +20,13 @@ module infix-dhcp-server { contact "kernelkit@googlegroups.com"; description "This module implements a DHCPv4 server"; + revision 2025-10-28 { + description "Make pool a presence container and add pool validation. + Also, require each subnet to have either a pool or + at least one static host entry."; + reference "internal"; + } + revision 2025-01-29 { description "Initial revision adapted for Infix from DHCPv6 model."; reference "internal"; @@ -132,6 +139,10 @@ module infix-dhcp-server { description "Subnet specific settings, including static host entries."; key "subnet"; + must "pool or host" { + error-message "Subnet must have either a pool or at least one static host entry."; + } + leaf subnet { description "Subnet to serve DHCP leases from."; type inet:ipv4-prefix; @@ -160,8 +171,13 @@ module infix-dhcp-server { } container pool { + presence "Enable dynamic DHCP address pool for this subnet."; description "IP address pool for this subnet."; + must "start-address and end-address" { + error-message "Both start-address and end-address must be set if pool is configured."; + } + leaf start-address { description "The start address of the DHCP address pool."; type inet:ipv4-address; diff --git a/src/confd/yang/confd/infix-dhcp-server@2025-01-29.yang b/src/confd/yang/confd/infix-dhcp-server@2025-10-28.yang similarity index 100% rename from src/confd/yang/confd/infix-dhcp-server@2025-01-29.yang rename to src/confd/yang/confd/infix-dhcp-server@2025-10-28.yang