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 <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2025-10-28 19:34:53 +01:00
parent d80186a556
commit cb5d804a88
5 changed files with 60 additions and 6 deletions
+5 -1
View File
@@ -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)
+38 -4
View File
@@ -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;
}
+1 -1
View File
@@ -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"
@@ -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;