confd: add support for dhcpv6 client

Basic DHCPv6 support as a per-interface ipv6 setting, not in line with
the IETF YANG model, which has this as a root container.

Note, this also fixes DHCPv4 option inference after the relocation of
/dhcp-client to /interfaces, in 764bd8e.

Fixes #1110

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2025-11-18 13:28:15 +01:00
parent 8ea9644c81
commit a568f31309
20 changed files with 786 additions and 234 deletions
+1
View File
@@ -0,0 +1 @@
/usr/share/udhcpc/default.script
+1
View File
@@ -0,0 +1 @@
/usr/share/udhcpc/default6.script
+112
View File
@@ -0,0 +1,112 @@
#!/bin/sh
# This script expects a system with resolvconf (openresolv) and iproute2
[ -z "$1" ] && echo "Error: should be called from udhcpc6" && exit 1
ACTION="$1"
RESOLV_CONF="/run/resolvconf/interfaces/${interface}-ipv6.conf"
NTPFILE="/run/chrony/dhcp-sources.d/${interface}-ipv6.sources"
[ -n "$metric" ] || metric=5
if [ -z "${IF_WAIT_DELAY}" ]; then
IF_WAIT_DELAY=10
fi
log()
{
logger -I $$ -t udhcpc6 -p user.notice "${interface}: $*"
}
dbg()
{
logger -I $$ -t udhcpc6 -p user.debug "${interface}: $*"
}
err()
{
logger -I $$ -t udhcpc6 -p user.err "${interface}: $*"
}
clr_dhcpv6_addresses()
{
addrs=$(ip -j addr show dev $interface \
| jq -c '.[0].addr_info[] | select(.family == "inet6") | select(.protocol == "dhcp")')
for addr in $addrs; do
ip="$(echo "$addr" | jq -r '."local"')"
prefix="$(echo "$addr" | jq -r '."prefixlen"')"
log "removing $ip/$prefix"
ip addr del "$ip/$prefix" dev "$interface"
done
}
log "action $ACTION"
case "$ACTION" in
deconfig)
clr_dhcpv6_addresses
/bin/ip link set dev $interface up
# drop info from this interface
rm -f "$RESOLV_CONF"
rm -f "$NTPFILE"
;;
leasefail|nak)
# DHCPv6 lease failed - log it
err "DHCPv6 lease failed"
;;
renew|bound)
# Add IPv6 address if provided (stateful DHCPv6)
if [ -n "$ipv6" ]; then
if /bin/ip addr add dev $interface $ipv6 proto dhcp; then
log "assigned address $ipv6"
fi
fi
# Handle delegated prefix (prefix delegation)
if [ -n "$ipv6prefix" ]; then
log "received delegated prefix $ipv6prefix"
# Prefix delegation handling could be added here
# For now, just log it - actual routing/distribution
# would need additional configuration
fi
# drop info from this interface
truncate -s 0 "$RESOLV_CONF"
# DHCPv6 domain search list (option 24)
if [ -n "$search" ]; then
dbg "adding search $search"
echo "search $search # $interface" >> $RESOLV_CONF
fi
# DHCPv6 DNS servers (option 23)
if [ -n "$dns" ]; then
for i in $dns ; do
dbg "adding dns $i"
echo "nameserver $i # $interface" >> $RESOLV_CONF
done
resolvconf -u
fi
# NTP servers (option 56)
if [ -n "$ntpsrv" ]; then
truncate -s 0 "$NTPFILE"
for srv in $ntpsrv; do
dbg "got NTP server $srv"
echo "server $srv iburst" >> "$NTPFILE"
done
chronyc reload sources >/dev/null
fi
;;
esac
HOOK_DIR="$0.d"
for hook in "${HOOK_DIR}/"*; do
[ -f "${hook}" -a -x "${hook}" ] || continue
"${hook}" "$ACTION"
done
exit 0
+1
View File
@@ -45,6 +45,7 @@ confd_plugin_la_SOURCES = \
ietf-routing.c \
infix-dhcp-common.c \
infix-dhcp-client.c \
infix-dhcpv6-client.c \
infix-dhcp-server.c \
infix-factory.c \
infix-firewall.c \
+4 -4
View File
@@ -198,6 +198,10 @@ static int change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *mod
if ((rc = infix_dhcp_client_change(session, config, diff, event, confd)))
goto free_diff;
/* infix-dhcpv6-client*/
if ((rc = infix_dhcpv6_client_change(session, config, diff, event, confd)))
goto free_diff;
/* ietf-keystore */
if ((rc = ietf_keystore_change(session, config, diff, event, confd)))
goto free_diff;
@@ -428,10 +432,6 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
goto err;
rc = infix_dhcp_server_candidate_init(&confd);
if (rc)
goto err;
rc = infix_dhcp_client_candidate_init(&confd);
if (rc)
goto err;
/* YOUR_INIT GOES HERE */
+3 -4
View File
@@ -204,12 +204,11 @@ static inline int infix_containers_rpc_init(struct confd *confd) { return 0; }
static inline int infix_containers_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd) { return 0; }
#endif
/* infix-dhcp-common.c */
int dhcp_option_lookup(const struct lyd_node *id);
/* infix-dhcp-client.c */
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_candidate_init(struct confd *confd);
/* infix-dhcpv6-client.c */
int infix_dhcpv6_client_change(sr_session_ctx_t *session, struct lyd_node *config, struct lyd_node *diff, sr_event_t event, struct confd *confd);
/* infix-dhcp-server.c */
int infix_dhcp_server_candidate_init(struct confd *confd);
+4
View File
@@ -160,6 +160,10 @@ static int ifchange_cand(sr_session_ctx_t *session, uint32_t sub_id, const char
if (err)
break;
err = ifchange_cand_infer_dhcp(session, new->xpath);
if (err)
break;
err = cni_ifchange_cand_infer_type(session, new->xpath);
if (err)
break;
+3
View File
@@ -144,6 +144,9 @@ int ifchange_cand_infer_vlan(sr_session_ctx_t *session, const char *path);
int vlan_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
int vlan_add_deps(struct lyd_node *cif);
/* infix-dhcp-common.c */
int ifchange_cand_infer_dhcp(sr_session_ctx_t *session, const char *path);
/* infix-if-vxlan.c */
int vxlan_gen(struct lyd_node *dif, struct lyd_node *cif, FILE *ip);
+4 -222
View File
@@ -12,6 +12,8 @@
#include <srx/srx_val.h>
#include "core.h"
#include "infix-dhcp-common.h"
#define ARPING_MSEC 1000
#define MODULE "infix-dhcp-client"
#define XPATH "/ietf-interfaces:interfaces/interface/ietf-ip:ipv4/infix-dhcp-client:dhcp"
@@ -48,140 +50,6 @@ static char *ip_cache(const char *ifname, char *str, size_t len)
return str;
}
static char *hostname(struct lyd_node *cfg, char *str, size_t len)
{
struct lyd_node *node;
const char *hostname;
char *ptr;
node = lydx_get_xpathf(cfg, "/ietf-system:system/hostname");
if (!node)
return NULL;
hostname = lyd_get_value(node);
if (!hostname || hostname[0] == 0)
return NULL;
ptr = strchr(hostname, '.');
if (ptr)
*ptr = 0;
snprintf(str, len, "-x hostname:%s ", hostname);
return str;
}
static char *fqdn(const char *val, char *str, size_t len)
{
snprintf(str, len, "-F \"%s\" ", val);
return str;
}
static char *os_name_version(char *str, size_t len)
{
char *val;
if (!str || !len)
return NULL;
str[0] = 0;
val = fgetkey("/etc/os-release", "NAME");
if (val)
snprintf(str, len, "-V \"%.32s ", val);
val = fgetkey("/etc/os-release", "VERSION");
if (val) {
strlcat(str, val, len);
strlcat(str, "\"", len);
}
if (strlen(str) > 0 && str[strlen(str) - 1] != '"') {
str[0] = 0;
return NULL;
}
return str;
}
static char *compose_option(struct lyd_node *cfg, const char *ifname, struct lyd_node *id,
const char *val, const char *hex, char *option, size_t len)
{
const char *name = lyd_get_value(id);
int num = dhcp_option_lookup(id);
if (num == -1) {
ERROR("Failed looking up DHCP client %s option %s, skipping.", ifname, name);
return NULL;
}
if (val || hex) {
switch (num) {
case 81: /* fqdn */
if (!val)
return NULL;
return fqdn(val, option, len);
case 12: /* hostname */
if (val && !strcmp(val, "auto"))
return hostname(cfg, option, len);
/* fallthrough */
default:
if (hex) {
snprintf(option, len, "-x %d:", num);
strlcat(option, hex, len);
strlcat(option, " ", len);
} else {
/* string value */
snprintf(option, len, "-x %d:'\"%s\"' ", num, val);
}
break;
}
} else {
struct { int num; char *(*cb)(const char *, char *, size_t); } opt[] = {
{ 50, ip_cache }, /* address */
{ 81, NULL }, /* fqdn */
};
for (size_t i = 0; i < NELEMS(opt); i++) {
if (num != opt[i].num)
continue;
if (!opt[i].cb || !opt[i].cb(ifname, option, len))
return NULL;
return option;
}
snprintf(option, len, "-O %d ", num);
}
return option;
}
static char *compose_options(struct lyd_node *cfg, const char *ifname, char **options,
struct lyd_node *id, const char *val, const char *hex)
{
char opt[300];
if (!compose_option(cfg, ifname, id, val, hex, opt, sizeof(opt)))
return *options;
if (*options) {
char *opts;
opts = realloc(*options, strlen(*options) + strlen(opt) + 1);
if (!opts) {
ERROR("failed reallocating options: %s", strerror(errno));
free(*options);
return NULL;
}
*options = strcat(opts, opt);
} else
*options = strdup(opt);
return *options;
}
static char *fallback_options(const char *ifname)
{
@@ -206,7 +74,7 @@ static char *dhcp_options(const char *ifname, struct lyd_node *cfg)
const char *val = lydx_get_cattr(option, "value");
const char *hex = lydx_get_cattr(option, "hex");
options = compose_options(cfg, ifname, &options, id, val, hex);
options = dhcp_compose_options(cfg, ifname, &options, id, val, hex, ip_cache);
}
return options ?: fallback_options(ifname);
@@ -244,7 +112,7 @@ static void add(const char *ifname, struct lyd_node *cfg)
options = dhcp_options(ifname, cfg);
os_name_version(vendor, sizeof(vendor));
dhcp_os_name_version(vendor, sizeof(vendor));
fp = fopenf("w", "/etc/finit.d/available/dhcp-client-%s.conf", ifname);
if (!fp) {
@@ -331,89 +199,3 @@ int infix_dhcp_client_change(sr_session_ctx_t *session, struct lyd_node *config,
return err;
}
/*
* Default DHCP options for udhcpc, from networking/udhcp/common.c OPTION_REQ
*/
static void infer_options(sr_session_ctx_t *session, const char *xpath)
{
const char *opt[] = {
"netmask",
"broadcast",
"router",
"domain",
"hostname", /* server may use this to register our current name */
"dns-server",
"ntp-server" /* will not be activated unless ietf-system also is */
};
size_t i;
for (i = 0; i < NELEMS(opt); i++)
srx_set_item(session, NULL, 0, "%s/option[id='%s']", xpath, opt[i]);
}
static int cand(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_change_iter_t *iter;
sr_change_oper_t op;
sr_val_t *old, *new;
sr_error_t err;
switch (event) {
case SR_EV_UPDATE:
case SR_EV_CHANGE:
break;
default:
return SR_ERR_OK;
}
err = sr_dup_changes_iter(session, XPATH "//*", &iter);
if (err)
return err;
while (sr_get_change_next(session, iter, &op, &old, &new) == SR_ERR_OK) {
char *xpath, *ptr;
size_t cnt = 0;
switch (op) {
case SR_OP_CREATED:
case SR_OP_MODIFIED:
break;
default:
continue;
}
xpath = strdupa(new->xpath);
if (!xpath) {
ERRNO("Failed strdupa()");
return SR_ERR_SYS;
}
if ((ptr = strstr(xpath, "]/")) == NULL)
continue;
ptr[1] = 0;
err = srx_nitems(session, &cnt, "%s/option", xpath);
if (err || cnt) {
continue;
}
infer_options(session, xpath);
}
sr_free_change_iter(iter);
return SR_ERR_OK;
}
int infix_dhcp_client_candidate_init(struct confd *confd)
{
int rc;
REGISTER_CHANGE(confd->cand, MODULE, XPATH"//.", SR_SUBSCR_UPDATE, cand, confd, &confd->sub);
return SR_ERR_OK;
fail:
ERROR("init failed: %s", sr_strerror(rc));
return rc;
}
+242
View File
@@ -1,6 +1,18 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <ctype.h>
#include <pwd.h>
#include <sys/utsname.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <srx/common.h>
#include <srx/lyx.h>
#include <srx/srx_val.h>
#include <libyang/libyang.h>
#include "core.h"
#include "infix-dhcp-common.h"
int dhcp_option_lookup(const struct lyd_node *id)
{
@@ -43,8 +55,238 @@ int dhcp_option_lookup(const struct lyd_node *id)
val = strtol(name, &endptr, 10);
if (*endptr == 0 && val > 0 && val < 255)
return (int)val;
} else if (type->basetype == LY_TYPE_UINT16) {
char *endptr;
long val;
val = strtol(name, &endptr, 10);
if (*endptr == 0 && val > 0 && val < 65536)
return (int)val;
}
}
return -1;
}
char *dhcp_hostname(struct lyd_node *cfg, char *str, size_t len)
{
struct lyd_node *node;
const char *hostname;
char *ptr;
node = lydx_get_xpathf(cfg, "/ietf-system:system/hostname");
if (!node)
return NULL;
hostname = lyd_get_value(node);
if (!hostname || hostname[0] == 0)
return NULL;
ptr = strchr(hostname, '.');
if (ptr)
*ptr = 0;
snprintf(str, len, "-x hostname:%s ", hostname);
return str;
}
char *dhcp_fqdn(const char *val, char *str, size_t len)
{
snprintf(str, len, "-F \"%s\" ", val);
return str;
}
char *dhcp_os_name_version(char *str, size_t len)
{
char *val;
if (!str || !len)
return NULL;
str[0] = 0;
val = fgetkey("/etc/os-release", "NAME");
if (val)
snprintf(str, len, "-V \"%.32s ", val);
val = fgetkey("/etc/os-release", "VERSION");
if (val) {
strlcat(str, val, len);
strlcat(str, "\"", len);
}
if (strlen(str) > 0 && str[strlen(str) - 1] != '"') {
str[0] = 0;
return NULL;
}
return str;
}
char *dhcp_compose_option(struct lyd_node *cfg, const char *ifname, struct lyd_node *id,
const char *val, const char *hex, char *option, size_t len,
char *(*ip_cache_cb)(const char *, char *, size_t))
{
const char *name = lyd_get_value(id);
int num = dhcp_option_lookup(id);
if (num == -1) {
ERROR("Failed looking up DHCP client %s option %s, skipping.", ifname, name);
return NULL;
}
if (val || hex) {
switch (num) {
case 81: /* fqdn */
if (!val)
return NULL;
return dhcp_fqdn(val, option, len);
case 12: /* hostname */
if (val && !strcmp(val, "auto"))
return dhcp_hostname(cfg, option, len);
/* fallthrough */
default:
if (hex) {
snprintf(option, len, "-x %d:", num);
strlcat(option, hex, len);
strlcat(option, " ", len);
} else {
/* string value */
snprintf(option, len, "-x %d:'\"%s\"' ", num, val);
}
break;
}
} else {
struct { int num; char *(*cb)(const char *, char *, size_t); } opt[] = {
{ 50, ip_cache_cb }, /* address */
{ 81, NULL }, /* fqdn */
};
for (size_t i = 0; i < NELEMS(opt); i++) {
if (num != opt[i].num)
continue;
if (!opt[i].cb || !opt[i].cb(ifname, option, len))
return NULL;
return option;
}
snprintf(option, len, "-O %d ", num);
}
return option;
}
char *dhcp_compose_options(struct lyd_node *cfg, const char *ifname, char **options,
struct lyd_node *id, const char *val, const char *hex,
char *(*ip_cache_cb)(const char *, char *, size_t))
{
char opt[300];
if (!dhcp_compose_option(cfg, ifname, id, val, hex, opt, sizeof(opt), ip_cache_cb))
return *options;
if (*options) {
char *opts;
opts = realloc(*options, strlen(*options) + strlen(opt) + 1);
if (!opts) {
ERROR("failed reallocating options: %s", strerror(errno));
free(*options);
return NULL;
}
*options = strcat(opts, opt);
} else
*options = strdup(opt);
return *options;
}
/*
* Default DHCP options for udhcpc, from networking/udhcp/common.c OPTION_REQ
*/
static void infer_options_v4(sr_session_ctx_t *session, const char *xpath)
{
const char *opt[] = {
"netmask",
"broadcast",
"router",
"domain",
"hostname", /* server may use this to register our current name */
"dns-server",
"ntp-server" /* will not be activated unless ietf-system also is */
};
size_t i;
for (i = 0; i < NELEMS(opt); i++)
srx_set_item(session, NULL, 0, "%s/option[id='%s']", xpath, opt[i]);
}
/*
* Default DHCPv6 options
* Note: udhcpc6 only supports dns-server (dns) and domain-search (search) with string names.
* Other options use numeric codes, which dhcp_compose_option() handles automatically.
*/
static void infer_options_v6(sr_session_ctx_t *session, const char *xpath)
{
const char *opt[] = {
"dns-server", /* option 23 - udhcpc6: -O dns */
"domain-search", /* option 24 - udhcpc6: -O search */
"client-fqdn", /* option 39 - udhcpc6: -O 39 (equivalent to DHCPv4 hostname) */
"ntp-server" /* option 56 - udhcpc6: -O 56 */
};
size_t i;
for (i = 0; i < NELEMS(opt); i++)
srx_set_item(session, NULL, 0, "%s/option[id='%s']", xpath, opt[i]);
}
/*
* Called from ietf-interfaces.c ifchange_cand() to infer DHCP options
* for both DHCPv4 and DHCPv6 client configurations
*/
int ifchange_cand_infer_dhcp(sr_session_ctx_t *session, const char *xpath)
{
sr_error_t err = SR_ERR_OK;
char *path, *ptr;
size_t cnt = 0;
/* Extract path up to and including the dhcp container */
path = strdup(xpath);
if (!path)
return SR_ERR_SYS;
/* Find the dhcp container in the path */
ptr = strstr(path, ":dhcp");
if (!ptr) {
free(path);
return SR_ERR_OK;
}
/* Move past ":dhcp" to find end of container name */
ptr += 5; /* strlen(":dhcp") */
/* If there's more after dhcp (like /arping), truncate it */
if (*ptr == '/')
*ptr = '\0';
/* Check if options already exist */
err = srx_nitems(session, &cnt, "%s/option", path);
if (err || cnt) {
ERROR("%s(): no %s/options err %d cnt %zu", __func__, path, err, cnt);
goto out;
}
/* Infer options based on IPv4 or IPv6 */
if (strstr(path, ":ipv4/"))
infer_options_v4(session, path);
else if (strstr(path, ":ipv6/"))
infer_options_v6(session, path);
out:
free(path);
return err;
}
+20
View File
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#ifndef INFIX_DHCP_COMMON_H_
#define INFIX_DHCP_COMMON_H_
#include <libyang/libyang.h>
/* DHCP option lookup and composition helpers */
int dhcp_option_lookup(const struct lyd_node *id);
char *dhcp_hostname(struct lyd_node *cfg, char *str, size_t len);
char *dhcp_fqdn(const char *val, char *str, size_t len);
char *dhcp_os_name_version(char *str, size_t len);
char *dhcp_compose_option(struct lyd_node *cfg, const char *ifname, struct lyd_node *id,
const char *val, const char *hex, char *option, size_t len,
char *(*ip_cache_cb)(const char *, char *, size_t));
char *dhcp_compose_options(struct lyd_node *cfg, const char *ifname, char **options,
struct lyd_node *id, const char *val, const char *hex,
char *(*ip_cache_cb)(const char *, char *, size_t));
#endif /* INFIX_DHCP_COMMON_H_ */
+2 -1
View File
@@ -2,13 +2,14 @@
#include <assert.h>
#include <ctype.h>
#include <ifaddrs.h>
#include <srx/common.h>
#include <srx/lyx.h>
#include <srx/srx_val.h>
#include "core.h"
#include <ifaddrs.h>
#include "infix-dhcp-common.h"
#define MODULE "infix-dhcp-server"
#define ROOT_XPATH "/infix-dhcp-server:"
+167
View File
@@ -0,0 +1,167 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <assert.h>
#include <ctype.h>
#include <pwd.h>
#include <sys/utsname.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <srx/common.h>
#include <srx/lyx.h>
#include <srx/srx_val.h>
#include "core.h"
#include "infix-dhcp-common.h"
#define MODULE "infix-dhcpv6-client"
#define XPATH "/ietf-interfaces:interfaces/interface/ietf-ip:ipv6/infix-dhcpv6-client:dhcp"
static char *fallback_options_v6(const char *ifname)
{
/* dns-server, domain-search, client-fqdn, ntp-server */
const char *defaults = "-O 23 -O 24 -O 39 -O 56 ";
return strdup(defaults);
}
static char *dhcp_options_v6(const char *ifname, struct lyd_node *cfg, bool *request_pd)
{
struct lyd_node *option;
char *options = NULL;
*request_pd = false;
LYX_LIST_FOR_EACH(lyd_child(cfg), option, "option") {
struct lyd_node *id = lydx_get_child(option, "id");
const char *val = lydx_get_cattr(option, "value");
const char *hex = lydx_get_cattr(option, "hex");
const char *name = lyd_get_value(id);
int num = dhcp_option_lookup(id);
/* ia-pd option requires -d flag, not -O 25 */
if (num == 25 || (name && !strcmp(name, "ia-pd"))) {
*request_pd = true;
continue; /* Don't add to options string */
}
options = dhcp_compose_options(cfg, ifname, &options, id, val, hex, NULL);
}
return options ?: fallback_options_v6(ifname);
}
static void add_v6(const char *ifname, struct lyd_node *cfg)
{
const char *metric = lydx_get_cattr(cfg, "route-preference");
const char *duid = lydx_get_cattr(cfg, "duid");
char *client_duid = NULL, *options = NULL;
const char *action = "disable";
char info_only[4] = { 0 };
char prefix_del[4] = { 0 };
bool request_pd = false;
FILE *fp;
if (lydx_is_enabled(cfg, "information-only"))
strlcpy(info_only, "-l ", sizeof(info_only));
if (duid && duid[0]) {
size_t len = strlen(duid) + 32;
client_duid = malloc(len);
if (!client_duid)
goto generr;
snprintf(client_duid, len, "-x 1:%s ", duid);
}
options = dhcp_options_v6(ifname, cfg, &request_pd);
if (request_pd)
strlcpy(prefix_del, "-d ", sizeof(prefix_del));
fp = fopenf("w", "/etc/finit.d/available/dhcpv6-client-%s.conf", ifname);
if (!fp) {
generr:
ERRNO("failed creating DHCPv6 client %s: %s", ifname, strerror(errno));
goto err;
}
fprintf(fp, "# Generated by Infix confd\n");
fprintf(fp, "metric=%s\n", metric);
fprintf(fp, "service <!> name:dhcpv6-client :%s <net/%s/running> \\\n"
" [2345] udhcpc6 -f -p /run/dhcpv6-client-%s.pid -t 3 -T 5 -A 30 -S -R \\\n"
" %s%s%s%s \\\n"
" -i %s %s \\\n"
" -- DHCPv6 client @%s\n",
ifname, ifname, ifname,
info_only, prefix_del,
options ? "-o " : "", options ?: "",
ifname, client_duid ?: "", ifname);
fclose(fp);
action = "enable";
err:
systemf("initctl -bfqn %s dhcpv6-client-%s", action, ifname);
if (options)
free(options);
if (client_duid)
free(client_duid);
}
static void del_v6(const char *ifname)
{
systemf("initctl -bfq delete dhcpv6-client-%s", ifname);
}
int infix_dhcpv6_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 *ifaces, *difaces, *iface, *diface, *ipv6, *dhcp, *ddhcp;
sr_error_t err = 0;
if (event != SR_EV_DONE)
return SR_ERR_OK;
ifaces = lydx_get_descendant(config, "interfaces", "interface", NULL);
difaces = lydx_get_descendant(diff, "interfaces", "interface", NULL);
/* find the modified interfaces */
LYX_LIST_FOR_EACH(difaces, diface, "interface") {
const char *ifname = lydx_get_cattr(diface, "name");
struct lyd_node *dipv6;
dipv6 = lydx_get_descendant(lyd_child(diface), "ipv6", NULL);
if (!dipv6)
continue;
ddhcp = lydx_get_descendant(lyd_child(dipv6), "dhcp", NULL);
if (!ddhcp)
continue;
/* Check if dhcp container was deleted */
if (lydx_get_op(ddhcp) == LYDX_OP_DELETE) {
del_v6(ifname);
continue;
}
/* 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;
ipv6 = lydx_get_descendant(lyd_child(iface), "ipv6", NULL);
if (!ipv6) {
del_v6(ifname);
break;
}
dhcp = lydx_get_descendant(lyd_child(ipv6), "dhcp", NULL);
if (!dhcp)
del_v6(ifname);
else
add_v6(ifname, dhcp);
break;
}
}
return err;
}
+3 -2
View File
@@ -29,8 +29,9 @@ MODULES=(
"infix-routing@2025-11-12.yang"
"ieee802-dot1ab-lldp@2022-03-15.yang"
"infix-lldp@2025-05-05.yang"
"infix-dhcp-common@2025-01-29.yang"
"infix-dhcp-client@2025-11-02.yang"
"infix-dhcp-common@2025-11-09.yang"
"infix-dhcp-client@2025-11-09.yang"
"infix-dhcpv6-client@2025-11-09.yang"
"infix-dhcp-server@2025-10-28.yang"
"infix-firewall@2025-04-26.yang"
"infix-firewall-services@2025-04-26.yang"
+5 -1
View File
@@ -1,6 +1,6 @@
module infix-dhcp-client {
yang-version 1.1;
namespace "urn:ietf:params:xml:ns:yang:dhcp-client";
namespace "urn:infix:params:xml:ns:yang:dhcp-client";
prefix dhcp-client;
import ietf-interfaces {
@@ -16,6 +16,10 @@ module infix-dhcp-client {
contact "kernelkit@googlegroups.com";
description "This module implements a DHCPv4 client";
revision 2025-11-09 {
description "Fix namespace to use infix instead of ietf.";
reference "internal";
}
revision 2025-11-02 {
description "Migrate DHCP client to ietf-ip augment.
- Relocate from /dhcp-client to /interfaces/interface/ipv4/dhcp
@@ -7,6 +7,10 @@ module infix-dhcp-common {
contact "kernelkit@googlegroups.com";
description "Shared types between DHCP server and client.";
revision 2025-11-09 {
description "Add DHCPv6 options typedef.";
reference "internal";
}
revision 2025-01-29 {
description "Initial revision.";
reference "internal";
@@ -113,4 +117,73 @@ module infix-dhcp-common {
pattern '([0-9a-fA-F]{2}:)*[0-9a-fA-F]{2}';
}
}
typedef duid {
description "DHCPv6 Unique Identifier (DUID), RFC 8415 section 11.
A DUID consists of a two-octet type code and a variable length
identifier field. The minimum length is 4 octets (type + data).
Common DUID types:
- DUID-LLT (type 1): Link-layer address plus time
- DUID-EN (type 2): Enterprise number
- DUID-LL (type 3): Link-layer address
- DUID-UUID (type 4): UUID
Format: Colon-separated hex octets, minimum 4 octets.
Example: 00:03:00:01:08:00:27:fe:8c:2a (DUID-LL with Ethernet MAC)";
type string {
pattern '([0-9a-fA-F]{2}:){3,}[0-9a-fA-F]{2}';
/* Minimum 4 octets: ([0-9a-fA-F]{2}:){3,} matches at least 3 colons,
* plus final [0-9a-fA-F]{2} makes 4 octets total */
}
}
typedef options-v6 {
description "DHCPv6 options for client.";
type union {
type uint16 {
range "1..65535";
}
type enumeration {
enum client-id {
value 1;
description "Client Identifier (DUID), RFC 8415.
Automatically generated by default.";
}
enum server-id {
value 2;
description "Server Identifier (DUID), RFC 8415.";
}
enum dns-server {
value 23;
description "DNS recursive name servers, RFC 3646.";
}
enum domain-search {
value 24;
description "Domain search list, RFC 3646.";
}
enum ia-pd {
value 25;
description "Identity Association for Prefix Delegation, RFC 8415.";
}
enum ntp-server {
value 56;
description "NTP time servers, RFC 5908.";
}
enum client-fqdn {
value 39;
description "Client Fully Qualified Domain Name, RFC 4704.";
}
enum information-refresh-time {
value 32;
description "Information refresh time for stateless DHCPv6, RFC 8415.";
}
enum sntp-server {
value 31;
description "Simple Network Time Protocol servers, RFC 4075.";
}
}
}
}
}
@@ -0,0 +1,140 @@
module infix-dhcpv6-client {
yang-version 1.1;
namespace "urn:infix:params:xml:ns:yang:dhcpv6-client";
prefix dhcpv6-client;
import ietf-interfaces {
prefix if;
}
import ietf-ip {
prefix ip;
}
import infix-dhcp-common {
prefix dhcp;
}
organization "KernelKit";
contact "kernelkit@googlegroups.com";
description "This module implements a DHCPv6 client";
revision 2025-11-09 {
description "Initial revision:
- Augment for /interfaces/interface/ipv6/dhcp
- Support for DHCPv6 options, DUID, prefix delegation";
reference "rfc8415";
}
/*
* Typedefs
*/
typedef route-preference {
type uint32;
description "This type is used for selecting route preference (distance).";
}
/*
* DHCPv6 Client Configuration
*/
augment "/if:interfaces/if:interface/ip:ipv6" {
container dhcp {
presence "Enable DHCPv6 client for this interface.";
description "DHCPv6 client configuration";
leaf duid {
type dhcp:duid;
description "Optional DHCPv6 Unique Identifier (DUID), option 1, RFC 8415.
When omitted, the client automatically generates a DUID
based on link-layer address plus time (DUID-LLT) or link-layer
address only (DUID-LL), depending on the implementation.
This setting allows manual configuration of the DUID in
hexadecimal format. The DUID type and content are encoded
as defined in RFC 8415 section 11.
For full control of the DUID format, use the generic option
list instead, which supports entering raw HEX data.
Example: 00:03:00:01:08:00:27:fe:8c:2a
(DUID-LL with Ethernet address)";
}
leaf information-only {
type boolean;
must "not(. = 'true') or ../../ip:autoconf/ip:create-global-addresses = 'true' " +
"or ../../ip:autoconf/ip:create-temporary-addresses = 'true'" {
error-message "IPv6 autoconf (SLAAC) must be enabled when information-only is true";
error-app-tag "invalid-dhcpv6-config";
}
description "Use stateless DHCPv6 (information-request mode).
When enabled, the client requests configuration information
(like DNS servers) but does not request IPv6 addresses or
prefixes. This is used with SLAAC (Stateless Address
Autoconfiguration) when only additional configuration is
needed from DHCPv6.";
}
list option {
key "id";
description "List of DHCPv6 options to request (and accept).
The default is an empty list, meaning all supported options. To
request specific options, add them here. For prefix delegation,
include the ia-pd option in the list.";
must "not(id = 'client-fqdn' and hex)" {
error-message "Client FQDN option must use string format";
error-app-tag "invalid-fqdn-format";
}
leaf id {
type dhcp:options-v6;
description "DHCPv6 option to request from, or inform server of.";
}
choice value {
case value {
leaf value {
description "Optional string value to send to server.
Example:
option:client-fqdn, value:host.example.com
This will send option 39 with the specified FQDN,
requesting the server to update DNS records.";
type string;
}
}
case hex {
leaf hex {
description "Optional binary value in hexadecimal format.
This allows full control over the option payload,
useful for vendor-specific options or custom data.
Example:
option:client-id, hex:00:03:00:01:08:00:27:fe:8c:2a
This would set a DUID-LL (type 3) with the specified
link-layer address.";
type dhcp:octet-string;
}
}
}
}
leaf route-preference {
type route-preference;
default 5;
description
"The preference (administrative distance) that all DHCPv6 routes
are installed with. The default preference (5) is higher (less worth)
than static routes, but lower than those learned via dynamic routing
protocols, like OSPF.";
}
}
}
}
@@ -0,0 +1 @@
infix-dhcpv6-client.yang