mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-01 21:33:02 +02:00
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:
@@ -0,0 +1 @@
|
||||
/usr/share/udhcpc/default.script
|
||||
@@ -0,0 +1 @@
|
||||
/usr/share/udhcpc/default6.script
|
||||
+112
@@ -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
|
||||
@@ -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 \
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,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:"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user