mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-24 01:43:00 +02:00
Merge pull request #1350 from kernelkit/change-dhcpv6-client
Migrate to odhcp6c for dhpcv6 client support
This commit is contained in:
@@ -1016,12 +1016,7 @@ CONFIG_UDHCPC=y
|
||||
CONFIG_FEATURE_UDHCPC_ARPING=y
|
||||
CONFIG_FEATURE_UDHCPC_SANITIZEOPT=y
|
||||
CONFIG_UDHCPC_DEFAULT_SCRIPT="/usr/share/udhcpc/default.script"
|
||||
CONFIG_UDHCPC6_DEFAULT_SCRIPT="/usr/share/udhcpc/default6.script"
|
||||
CONFIG_UDHCPC6=y
|
||||
CONFIG_FEATURE_UDHCPC6_RFC3646=y
|
||||
CONFIG_FEATURE_UDHCPC6_RFC4704=y
|
||||
CONFIG_FEATURE_UDHCPC6_RFC4833=y
|
||||
CONFIG_FEATURE_UDHCPC6_RFC5970=y
|
||||
# CONFIG_UDHCPC6 is not set
|
||||
|
||||
#
|
||||
# Common options for DHCP applets
|
||||
|
||||
Executable
+242
@@ -0,0 +1,242 @@
|
||||
#!/bin/sh
|
||||
# DHCPv6 client state update script for odhcp6c
|
||||
# This script expects a system with resolvconf (openresolv) and iproute2
|
||||
|
||||
[ -z "$1" ] && echo "Error: should be called from odhcp6c" && exit 1
|
||||
|
||||
interface="$1"
|
||||
state="$2"
|
||||
RESOLV_CONF="/run/resolvconf/interfaces/${interface}-ipv6.conf"
|
||||
NTPFILE="/run/chrony/dhcp-sources.d/${interface}-ipv6.sources"
|
||||
|
||||
[ -n "$metric" ] || metric=5
|
||||
|
||||
log()
|
||||
{
|
||||
logger -I $$ -t odhcp6c -p user.notice "${interface}: $*"
|
||||
}
|
||||
|
||||
dbg()
|
||||
{
|
||||
logger -I $$ -t odhcp6c -p user.debug "${interface}: $*"
|
||||
}
|
||||
|
||||
err()
|
||||
{
|
||||
logger -I $$ -t odhcp6c -p user.err "${interface}: $*"
|
||||
}
|
||||
|
||||
teardown_interface()
|
||||
{
|
||||
ip -6 route flush dev "$interface"
|
||||
ip -6 address flush dev "$interface" scope global
|
||||
}
|
||||
|
||||
setup_interface()
|
||||
{
|
||||
# Merge RA addresses with DHCP addresses
|
||||
for entry in $RA_ADDRESSES; do
|
||||
duplicate=0
|
||||
addr="${entry%%/*}"
|
||||
for dentry in $ADDRESSES; do
|
||||
daddr="${dentry%%/*}"
|
||||
[ "$addr" = "$daddr" ] && duplicate=1
|
||||
done
|
||||
[ "$duplicate" = "0" ] && ADDRESSES="$ADDRESSES $entry"
|
||||
done
|
||||
|
||||
# Add addresses
|
||||
for entry in $ADDRESSES; do
|
||||
addr="${entry%%,*}"
|
||||
entry="${entry#*,}"
|
||||
preferred="${entry%%,*}"
|
||||
entry="${entry#*,}"
|
||||
valid="${entry%%,*}"
|
||||
|
||||
ip -6 address add "$addr" dev "$interface" preferred_lft "$preferred" valid_lft "$valid" proto dhcp
|
||||
log "assigned address $addr (preferred=$preferred, valid=$valid)"
|
||||
done
|
||||
|
||||
# Add routes from RA
|
||||
for entry in $RA_ROUTES; do
|
||||
addr="${entry%%,*}"
|
||||
entry="${entry#*,}"
|
||||
gw="${entry%%,*}"
|
||||
entry="${entry#*,}"
|
||||
valid="${entry%%,*}"
|
||||
entry="${entry#*,}"
|
||||
metric="${entry%%,*}"
|
||||
|
||||
if [ -n "$gw" ]; then
|
||||
ip -6 route add "$addr" via "$gw" metric "$metric" dev "$interface" from "::/128"
|
||||
else
|
||||
ip -6 route add "$addr" metric "$metric" dev "$interface"
|
||||
fi
|
||||
|
||||
# Add routes for delegated prefixes
|
||||
for prefix in $PREFIXES; do
|
||||
paddr="${prefix%%,*}"
|
||||
[ -n "$gw" ] && ip -6 route add "$addr" via "$gw" metric "$metric" dev "$interface" from "$paddr"
|
||||
done
|
||||
done
|
||||
}
|
||||
|
||||
handle_prefixes()
|
||||
{
|
||||
# $PREFIXES format: "prefix/len,preferred,valid[,class=N][,excluded=...] ..."
|
||||
for entry in $PREFIXES; do
|
||||
addr="${entry%%,*}"
|
||||
entry="${entry#*,}"
|
||||
preferred="${entry%%,*}"
|
||||
entry="${entry#*,}"
|
||||
valid="${entry%%,*}"
|
||||
|
||||
log "received delegated prefix $addr (preferred=$preferred, valid=$valid)"
|
||||
|
||||
# Add unreachable route to prevent routing loops
|
||||
ip -6 route add unreachable "$addr" 2>/dev/null
|
||||
|
||||
# Future: Distribute to downstream interfaces
|
||||
done
|
||||
}
|
||||
|
||||
handle_dns()
|
||||
{
|
||||
truncate -s 0 "$RESOLV_CONF"
|
||||
|
||||
# Combine DHCPv6 DNS ($RDNSS) and RA DNS ($RA_DNS), deduplicating
|
||||
all_dns=""
|
||||
for server in $RDNSS $RA_DNS; do
|
||||
# Simple deduplication: only add if not already in list
|
||||
case " $all_dns " in
|
||||
*" $server "*) ;;
|
||||
*) all_dns="$all_dns $server" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Domain search list (DHCPv6 option 24)
|
||||
if [ -n "$DOMAINS" ]; then
|
||||
dbg "adding search domains: $DOMAINS"
|
||||
echo "search $DOMAINS # $interface" >> "$RESOLV_CONF"
|
||||
fi
|
||||
|
||||
# DNS servers
|
||||
for server in $all_dns; do
|
||||
[ -z "$server" ] && continue
|
||||
dbg "adding dns $server"
|
||||
echo "nameserver $server # $interface" >> "$RESOLV_CONF"
|
||||
done
|
||||
|
||||
if [ -n "$all_dns" ]; then
|
||||
resolvconf -u
|
||||
fi
|
||||
}
|
||||
|
||||
handle_ntp()
|
||||
{
|
||||
# DHCPv6 option 56 (NTP server) is provided as $OPTION_56 in hex format
|
||||
# Format: sub-option-code (2 bytes) + length (2 bytes) + data
|
||||
# Sub-option 1 = NTP server address (16 bytes IPv6)
|
||||
#
|
||||
# This is complex to parse in shell. For now, we attempt basic parsing
|
||||
# and fall back to logging a warning if the format is unexpected.
|
||||
|
||||
if [ -n "$OPTION_56" ]; then
|
||||
# Remove all non-hex characters (spaces, colons, etc.) and convert to lowercase
|
||||
hex=$(echo "$OPTION_56" | tr -d '[:space:]:-' | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
truncate -s 0 "$NTPFILE"
|
||||
ntp_found=0
|
||||
|
||||
# Parse option 56: iterate through sub-options
|
||||
# Each sub-option: 2 bytes code + 2 bytes length + data
|
||||
pos=0
|
||||
while [ $pos -lt ${#hex} ]; do
|
||||
# Need at least 4 hex chars (2 bytes) for sub-option code
|
||||
[ $((${#hex} - pos)) -lt 4 ] && break
|
||||
|
||||
# Extract sub-option code (2 bytes = 4 hex chars)
|
||||
subopt_code=$(echo "$hex" | cut -c $((pos+1))-$((pos+4)))
|
||||
pos=$((pos + 4))
|
||||
|
||||
# Need 4 more hex chars for length
|
||||
[ $((${#hex} - pos)) -lt 4 ] && break
|
||||
|
||||
# Extract length (2 bytes = 4 hex chars)
|
||||
subopt_len_hex=$(echo "$hex" | cut -c $((pos+1))-$((pos+4)))
|
||||
subopt_len=$(printf "%d" "0x$subopt_len_hex")
|
||||
pos=$((pos + 4))
|
||||
|
||||
# Sub-option 1 = NTP server address (should be 16 bytes for IPv6)
|
||||
if [ "$subopt_code" = "0001" ] && [ "$subopt_len" -eq 16 ]; then
|
||||
# Extract 16 bytes (32 hex chars) for IPv6 address
|
||||
addr_hex=$(echo "$hex" | cut -c $((pos+1))-$((pos+32)))
|
||||
|
||||
# Convert hex to IPv6 address format
|
||||
# Format: 0123456789abcdef0123456789abcdef -> 0123:4567:89ab:cdef:0123:4567:89ab:cdef
|
||||
ipv6=$(echo "$addr_hex" | sed 's/\(....\)\(....\)\(....\)\(....\)\(....\)\(....\)\(....\)\(....\)/\1:\2:\3:\4:\5:\6:\7:\8/')
|
||||
|
||||
dbg "got NTP server $ipv6"
|
||||
echo "server $ipv6 iburst" >> "$NTPFILE"
|
||||
ntp_found=1
|
||||
fi
|
||||
|
||||
# Skip this sub-option's data
|
||||
pos=$((pos + subopt_len * 2))
|
||||
done
|
||||
|
||||
if [ "$ntp_found" -eq 1 ]; then
|
||||
chronyc reload sources >/dev/null 2>&1
|
||||
else
|
||||
dbg "option 56 received but no NTP server addresses found (consider using option 31/SNTP)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
log "state: $state"
|
||||
|
||||
(
|
||||
flock 9
|
||||
case "$state" in
|
||||
started)
|
||||
# Initial state - clean up any stale config
|
||||
teardown_interface
|
||||
;;
|
||||
|
||||
bound)
|
||||
# Fresh lease - tear down and set up from scratch
|
||||
teardown_interface
|
||||
setup_interface
|
||||
handle_prefixes
|
||||
handle_dns
|
||||
handle_ntp
|
||||
;;
|
||||
|
||||
informed|updated|rebound|ra-updated)
|
||||
# Update existing configuration
|
||||
setup_interface
|
||||
[ -n "$PREFIXES" ] && handle_prefixes
|
||||
handle_dns
|
||||
handle_ntp
|
||||
;;
|
||||
|
||||
unbound|stopped)
|
||||
# Lost server or client stopped
|
||||
teardown_interface
|
||||
rm -f "$RESOLV_CONF"
|
||||
rm -f "$NTPFILE"
|
||||
resolvconf -u
|
||||
chronyc reload sources >/dev/null 2>&1
|
||||
;;
|
||||
esac
|
||||
) 9>/tmp/odhcp6c.lock.${interface}
|
||||
rm -f /tmp/odhcp6c.lock.${interface}
|
||||
|
||||
# Run hooks
|
||||
HOOK_DIR="/usr/libexec/odhcp6c.d"
|
||||
for hook in "${HOOK_DIR}/"*; do
|
||||
[ -f "${hook}" -a -x "${hook}" ] || continue
|
||||
"${hook}" "$interface" "$state"
|
||||
done
|
||||
|
||||
exit 0
|
||||
@@ -1,111 +0,0 @@
|
||||
#!/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
|
||||
|
||||
# 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
|
||||
@@ -35,6 +35,7 @@ BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch32/li
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_ODHCP6C=y
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_JQ=y
|
||||
|
||||
@@ -35,6 +35,7 @@ BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch32/li
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_ODHCP6C=y
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_JQ=y
|
||||
|
||||
@@ -32,6 +32,7 @@ BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_ODHCP6C=y
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_JQ=y
|
||||
|
||||
@@ -31,6 +31,7 @@ BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_ODHCP6C=y
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
|
||||
@@ -37,6 +37,7 @@ BR2_LINUX_KERNEL_DTB_KEEP_DIRNAME=y
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="$(BR2_EXTERNAL_INFIX_PATH)/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_ODHCP6C=y
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_JQ=y
|
||||
|
||||
@@ -32,6 +32,7 @@ BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/lin
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_ODHCP6C=y
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_JQ=y
|
||||
|
||||
@@ -32,6 +32,7 @@ BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/lin
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_ODHCP6C=y
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
|
||||
@@ -29,6 +29,9 @@ All notable changes to the project are documented in this file.
|
||||
- Upgrade netopeer2 (NETCONF) to 2.7.0
|
||||
- Add RIPv2 routing support, issue #582
|
||||
- Add NTP server support, issue #904
|
||||
- Migrate DHCPv6 client to odhcp6c for improved Router Advertisement
|
||||
integration. Adds support for hybrid RA+DHCPv6 deployments where SLAAC
|
||||
assigns addresses and DHCPv6 provides DNS (common ISP scenario)
|
||||
- Add support for configurable OSPF debug logging, issue #1281. Debug options
|
||||
can now be enabled per category (bfd, packet, ism, nsm, default-information,
|
||||
nssa). All debug options are disabled by default to prevent log flooding in
|
||||
|
||||
@@ -212,16 +212,15 @@ static void infer_options_v4(sr_session_ctx_t *session, const char *xpath)
|
||||
|
||||
/*
|
||||
* 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.
|
||||
* Note: odhcp6c uses numeric option codes for all options.
|
||||
*/
|
||||
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 */
|
||||
"dns-server", /* option 23 */
|
||||
"domain-search", /* option 24 */
|
||||
"client-fqdn", /* option 39 */
|
||||
"ntp-server" /* option 56 */
|
||||
};
|
||||
size_t i;
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
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 ";
|
||||
/* odhcp6c format: -R -r <comma-separated list> */
|
||||
const char *defaults = "-R -r 23,24,39,56 ";
|
||||
|
||||
return strdup(defaults);
|
||||
}
|
||||
@@ -28,7 +29,9 @@ static char *fallback_options_v6(const char *ifname)
|
||||
static char *dhcp_options_v6(const char *ifname, struct lyd_node *cfg, bool *request_pd)
|
||||
{
|
||||
struct lyd_node *option;
|
||||
char opt_list[512] = { 0 };
|
||||
char *options = NULL;
|
||||
bool has_options = false;
|
||||
|
||||
*request_pd = false;
|
||||
|
||||
@@ -39,13 +42,34 @@ static char *dhcp_options_v6(const char *ifname, struct lyd_node *cfg, bool *req
|
||||
const char *name = lyd_get_value(id);
|
||||
int num = dhcp_option_lookup(id);
|
||||
|
||||
/* ia-pd option requires -d flag, not -O 25 */
|
||||
/* ia-pd option requires -P flag, not -r 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);
|
||||
/* Options with values not yet supported with odhcp6c */
|
||||
if (val || hex) {
|
||||
WARN("DHCPv6 client %s: option values not yet supported with odhcp6c, ignoring %s",
|
||||
ifname, name);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Build comma-separated list for -r flag */
|
||||
if (has_options)
|
||||
strlcat(opt_list, ",", sizeof(opt_list));
|
||||
|
||||
char num_str[8];
|
||||
snprintf(num_str, sizeof(num_str), "%d", num);
|
||||
strlcat(opt_list, num_str, sizeof(opt_list));
|
||||
has_options = true;
|
||||
}
|
||||
|
||||
if (has_options) {
|
||||
size_t len = strlen(opt_list) + 16;
|
||||
options = malloc(len);
|
||||
if (options)
|
||||
snprintf(options, len, "-R -r %s ", opt_list);
|
||||
}
|
||||
|
||||
return options ?: fallback_options_v6(ifname);
|
||||
@@ -57,27 +81,31 @@ static void add_v6(const char *ifname, struct lyd_node *cfg)
|
||||
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 };
|
||||
const char *addr_mode = "-N try"; /* Default: stateful mode */
|
||||
char prefix_del[16] = { 0 };
|
||||
bool request_pd = false;
|
||||
FILE *fp;
|
||||
|
||||
/* Information-only mode (stateless DHCPv6) */
|
||||
if (lydx_is_enabled(cfg, "information-only"))
|
||||
strlcpy(info_only, "-l ", sizeof(info_only));
|
||||
addr_mode = "-N none";
|
||||
|
||||
/* Custom DUID: odhcp6c uses -c instead of -x 1: */
|
||||
if (duid && duid[0]) {
|
||||
size_t len = strlen(duid) + 32;
|
||||
size_t len = strlen(duid) + 16;
|
||||
|
||||
client_duid = malloc(len);
|
||||
if (!client_duid)
|
||||
goto generr;
|
||||
|
||||
snprintf(client_duid, len, "-x 1:%s ", duid);
|
||||
snprintf(client_duid, len, "-c %s ", duid);
|
||||
}
|
||||
|
||||
options = dhcp_options_v6(ifname, cfg, &request_pd);
|
||||
|
||||
/* Prefix delegation: use -P 0 for auto */
|
||||
if (request_pd)
|
||||
strlcpy(prefix_del, "-d ", sizeof(prefix_del));
|
||||
strlcpy(prefix_del, "-P 0 ", sizeof(prefix_del));
|
||||
|
||||
fp = fopenf("w", "/etc/finit.d/available/dhcpv6-client-%s.conf", ifname);
|
||||
if (!fp) {
|
||||
@@ -89,14 +117,15 @@ static void add_v6(const char *ifname, struct lyd_node *cfg)
|
||||
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"
|
||||
" [2345] odhcp6c -e -p /run/dhcpv6-client-%s.pid \\\n"
|
||||
" -s /usr/libexec/odhcp6c.sh \\\n"
|
||||
" %s %s%s%s \\\n"
|
||||
" %s \\\n"
|
||||
" -- DHCPv6 client @%s\n",
|
||||
ifname, ifname, ifname,
|
||||
info_only, prefix_del,
|
||||
options ? "-o " : "", options ?: "",
|
||||
ifname, client_duid ?: "", ifname);
|
||||
addr_mode, prefix_del,
|
||||
options ?: "", client_duid ?: "",
|
||||
ifname, ifname);
|
||||
fclose(fp);
|
||||
action = "enable";
|
||||
err:
|
||||
|
||||
@@ -9,6 +9,7 @@ Tests verifying DHCPv4/DHCPv6 client and server functionality:
|
||||
- DHCPv4 client hostname management and priority
|
||||
- Basic DHCPv6 client operation with address assignment
|
||||
- DHCPv6 client with prefix delegation (IA_PD)
|
||||
- DHCPv6 SLAAC/RA (Stateless)
|
||||
- Basic DHCPv4 server operation and lease assignment
|
||||
- DHCPv4 server with host-specific IP reservations
|
||||
- DHCPv4 server with multiple subnet configurations
|
||||
@@ -37,6 +38,10 @@ include::client6_prefix_delegation/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::client6_slaac_ra/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::server_basic/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
@@ -15,6 +15,7 @@ image::topology.svg[DHCPv6 Basic topology, align=center, scaledwidth=75%]
|
||||
|
||||
. Set up topology and attach to target DUT
|
||||
. Configure DHCPv6 client
|
||||
. Verify DHCPv6 client is running
|
||||
. Verify client lease for {CLIENT}
|
||||
. Verify client default route ::/0
|
||||
. Verify client domain name resolution
|
||||
|
||||
@@ -12,6 +12,19 @@ import infamy.iface as iface
|
||||
import infamy.route as route
|
||||
from infamy.util import until
|
||||
|
||||
|
||||
def checkrun():
|
||||
"""Check DHCPv6 client is running"""
|
||||
res = tgtssh.runsh(f"pgrep -f 'odhcp6c.*{port}'")
|
||||
return res.returncode == 0
|
||||
|
||||
|
||||
def check_dns_resolution():
|
||||
"""Check if DNS resolution works by pinging FQDN"""
|
||||
rc = tgtssh.runsh(f"ping -6 -c1 -w5 {VERIFY}")
|
||||
return rc.returncode == 0
|
||||
|
||||
|
||||
with infamy.Test() as test:
|
||||
SERVER = '2001:db8::1'
|
||||
CLIENT = '2001:db8::42'
|
||||
@@ -53,14 +66,17 @@ with infamy.Test() as test:
|
||||
}
|
||||
client.put_config_dict("ietf-interfaces", config)
|
||||
|
||||
with test.step("Verify DHCPv6 client is running"):
|
||||
until(checkrun, attempts=10)
|
||||
|
||||
with test.step(f"Verify client lease for {CLIENT}"):
|
||||
until(lambda: iface.address_exist(client, port, CLIENT, prefix_length=128))
|
||||
until(lambda: iface.address_exist(client, port, CLIENT, prefix_length=128), attempts=30)
|
||||
|
||||
with test.step("Verify client default route ::/0"):
|
||||
until(lambda: route.ipv6_route_exist(client, "::/0"), attempts=20)
|
||||
|
||||
with test.step("Verify client domain name resolution"):
|
||||
rc = tgtssh.runsh(f"ping -6 -c1 -w20 {VERIFY}")
|
||||
assert rc.returncode == 0, f"Client failed: ping {VERIFY}"
|
||||
# DNS configuration may take a moment, especially on ARM hardware
|
||||
until(check_dns_resolution, attempts=20)
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -15,8 +15,8 @@ import time
|
||||
|
||||
def checkrun(dut):
|
||||
"""Check DUT is running DHCPv6 client"""
|
||||
res = dut.runsh(f"pgrep -f 'udhcpc6.*{port}'")
|
||||
# print(f"Checking for udhcpc6: {res.stdout}")
|
||||
res = dut.runsh(f"pgrep -f 'odhcp6c.*{port}'")
|
||||
# print(f"Checking for odhcp6c: {res.stdout}")
|
||||
if res.stdout.strip() != "":
|
||||
return True
|
||||
return False
|
||||
@@ -24,8 +24,8 @@ def checkrun(dut):
|
||||
|
||||
def checklog(dut):
|
||||
"""Check syslog for prefix delegation message"""
|
||||
rc = dut.runsh("tail -10 /log/syslog | grep 'received delegated prefix'")
|
||||
# print(f"DHCPv6 client logs:\n{res.stdout}")
|
||||
rc = dut.runsh("tail -50 /log/syslog | grep 'received delegated prefix'")
|
||||
# print(f"DHCPv6 client logs:\n{rc.stdout}")
|
||||
if rc.stdout.strip() != "":
|
||||
return True
|
||||
return False
|
||||
@@ -74,9 +74,10 @@ with infamy.Test() as test:
|
||||
client.put_config_dict("ietf-interfaces", config)
|
||||
|
||||
with test.step("Verify DHCPv6 client is running"):
|
||||
until(lambda: checkrun(tgtssh))
|
||||
until(lambda: checkrun(tgtssh), attempts=20)
|
||||
|
||||
with test.step("Verify prefix delegation in logs"):
|
||||
until(lambda: checklog(tgtssh))
|
||||
# Prefix delegation may take longer on ARM hardware
|
||||
until(lambda: checklog(tgtssh), attempts=30)
|
||||
|
||||
test.succeed()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
test.adoc
|
||||
@@ -0,0 +1,30 @@
|
||||
=== DHCPv6 SLAAC/RA (Stateless)
|
||||
|
||||
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/dhcp/client6_slaac_ra]
|
||||
|
||||
==== Description
|
||||
|
||||
Verify DHCPv6 client works in stateless mode (information-only) where:
|
||||
- Router Advertisements (RA) provide the IPv6 address via SLAAC
|
||||
- DHCPv6 provides DNS servers and domain search options only
|
||||
|
||||
This is a common ISP deployment scenario where the router sends RAs for
|
||||
address autoconfiguration, and DHCPv6 is only used for providing additional
|
||||
configuration like DNS servers.
|
||||
|
||||
The test verifies that odhcp6c correctly integrates both RA and DHCPv6
|
||||
information, which is something the old udhcpc6 client could not do.
|
||||
|
||||
==== Topology
|
||||
|
||||
image::topology.svg[DHCPv6 SLAAC/RA (Stateless) topology, align=center, scaledwidth=75%]
|
||||
|
||||
==== Sequence
|
||||
|
||||
. Set up topology and attach to target DUT
|
||||
. Configure DHCPv6 client in information-only mode
|
||||
. Verify DHCPv6 client is running
|
||||
. Verify client got SLAAC address from RA
|
||||
. Verify client domain name resolution
|
||||
|
||||
|
||||
Executable
+97
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""DHCPv6 with SLAAC/RA (Stateless DHCPv6)
|
||||
|
||||
Verify DHCPv6 client works in stateless mode (information-only) where:
|
||||
- Router Advertisements (RA) provide the IPv6 address via SLAAC
|
||||
- DHCPv6 provides DNS servers and domain search options only
|
||||
|
||||
This is a common ISP deployment scenario where the router sends RAs for
|
||||
address autoconfiguration, and DHCPv6 is only used for providing additional
|
||||
configuration like DNS servers.
|
||||
|
||||
The test verifies that odhcp6c correctly integrates both RA and DHCPv6
|
||||
information, which is something the old udhcpc6 client could not do.
|
||||
|
||||
"""
|
||||
|
||||
import infamy
|
||||
import infamy.dhcp
|
||||
import infamy.iface as iface
|
||||
from infamy.util import until
|
||||
|
||||
|
||||
def checkrun(dut):
|
||||
"""Check DUT is running DHCPv6 client"""
|
||||
rc = dut.runsh(f"pgrep -f 'odhcp6c.*{port}'")
|
||||
if rc.stdout.strip() != "":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def check_dns_resolution():
|
||||
"""Check if DNS resolution works by pinging FQDN"""
|
||||
rc = tgtssh.runsh(f"ping -6 -c1 -w5 {VERIFY}")
|
||||
return rc.returncode == 0
|
||||
|
||||
|
||||
def check_slaac_address():
|
||||
"""Check if SLAAC address was assigned"""
|
||||
addrs = iface.get_ipv6_address(client, port)
|
||||
return addrs is not None and len(addrs) > 0
|
||||
|
||||
|
||||
with infamy.Test() as test:
|
||||
SERVER = '2001:db8::1'
|
||||
DOMAIN = 'example.com'
|
||||
VERIFY = 'server.' + DOMAIN
|
||||
|
||||
with test.step("Set up topology and attach to target DUT"):
|
||||
env = infamy.Env()
|
||||
client = env.attach("client", "mgmt")
|
||||
tgtssh = env.attach("client", "mgmt", "ssh")
|
||||
_, host = env.ltop.xlate("host", "data")
|
||||
_, port = env.ltop.xlate("client", "data")
|
||||
|
||||
with infamy.IsolatedMacVlan(host) as netns:
|
||||
netns.addip(SERVER, prefix_length=64, proto="ipv6")
|
||||
# Stateless DHCPv6: RA provides address, DHCPv6 provides DNS
|
||||
with infamy.dhcp.Server6Dnsmasq(netns,
|
||||
start=None, # No DHCPv6 addresses
|
||||
end=None, # Stateless mode
|
||||
dns=SERVER,
|
||||
domain=DOMAIN,
|
||||
address=SERVER):
|
||||
|
||||
with test.step("Configure DHCPv6 client in information-only mode"):
|
||||
config = {
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": f"{port}",
|
||||
"ipv6": {
|
||||
"enabled": True,
|
||||
"infix-dhcpv6-client:dhcp": {
|
||||
"information-only": True, # Stateless DHCPv6
|
||||
"option": [
|
||||
{"id": "dns-server"},
|
||||
{"id": "domain-search"}
|
||||
]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
client.put_config_dict("ietf-interfaces", config)
|
||||
|
||||
with test.step("Verify DHCPv6 client is running"):
|
||||
until(lambda: checkrun(tgtssh), attempts=20)
|
||||
|
||||
with test.step("Verify client got SLAAC address from RA"):
|
||||
# Should get address from RA (SLAAC), not DHCPv6
|
||||
# Address will be in 2001:db8::/64 range
|
||||
until(check_slaac_address, attempts=30)
|
||||
|
||||
with test.step("Verify client domain name resolution"):
|
||||
# This proves DNS came from DHCPv6 (information-only)
|
||||
until(check_dns_resolution, attempts=30)
|
||||
|
||||
test.succeed()
|
||||
@@ -0,0 +1,23 @@
|
||||
graph "1x2" {
|
||||
layout="neato";
|
||||
overlap="false";
|
||||
esep="+100";
|
||||
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <mgmt> mgmt | <data> data }",
|
||||
pos="0,20!",
|
||||
requires="controller",
|
||||
];
|
||||
|
||||
client [
|
||||
label="{ <mgmt> mgmt | <data> data } | client",
|
||||
pos="200,20!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
host:mgmt -- client:mgmt [requires="mgmt", color=lightgrey]
|
||||
host:data -- client:data [color=black, taillabel="2001:db8::1/64\nRA+DHCPv6", headlabel="SLAAC addr\nDNS via DHCPv6"]
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<!-- Title: 1x2 Pages: 1 -->
|
||||
<svg width="474pt" height="55pt"
|
||||
viewBox="0.00 0.00 474.04 55.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 51)">
|
||||
<title>1x2</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-51 470.04,-51 470.04,4 -4,4"/>
|
||||
<!-- host -->
|
||||
<g id="node1" class="node">
|
||||
<title>host</title>
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-46.5 100,-46.5 100,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="25" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||
<polyline fill="none" stroke="black" points="50,-0.5 50,-46.5 "/>
|
||||
<text text-anchor="middle" x="75" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="50,-23.5 100,-23.5 "/>
|
||||
<text text-anchor="middle" x="75" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
</g>
|
||||
<!-- client -->
|
||||
<g id="node2" class="node">
|
||||
<title>client</title>
|
||||
<polygon fill="none" stroke="black" points="350.04,-0.5 350.04,-46.5 466.04,-46.5 466.04,-0.5 350.04,-0.5"/>
|
||||
<text text-anchor="middle" x="375.04" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="350.04,-23.5 400.04,-23.5 "/>
|
||||
<text text-anchor="middle" x="375.04" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
<polyline fill="none" stroke="black" points="400.04,-0.5 400.04,-46.5 "/>
|
||||
<text text-anchor="middle" x="433.04" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">client</text>
|
||||
</g>
|
||||
<!-- host--client -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt--client:mgmt</title>
|
||||
<path fill="none" stroke="lightgrey" stroke-width="2" d="M100,-35.5C100,-35.5 350.04,-35.5 350.04,-35.5"/>
|
||||
</g>
|
||||
<!-- host--client -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:data--client:data</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M100,-11.5C100,-11.5 350.04,-11.5 350.04,-11.5"/>
|
||||
<text text-anchor="middle" x="288.04" y="-30.3" font-family="DejaVu Serif, Book" font-size="14.00">SLAAC addr</text>
|
||||
<text text-anchor="middle" x="288.04" y="-15.3" font-family="DejaVu Serif, Book" font-size="14.00">DNS via DHCPv6</text>
|
||||
<text text-anchor="middle" x="154.5" y="-30.3" font-family="DejaVu Serif, Book" font-size="14.00">2001:db8::1/64</text>
|
||||
<text text-anchor="middle" x="154.5" y="-15.3" font-family="DejaVu Serif, Book" font-size="14.00">RA+DHCPv6</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
@@ -19,3 +19,6 @@
|
||||
|
||||
- name: DHCPv6 Prefix Delegation
|
||||
case: client6_prefix_delegation/test.py
|
||||
|
||||
- name: DHCPv6 SLAAC/RA (Stateless)
|
||||
case: client6_slaac_ra/test.py
|
||||
|
||||
Reference in New Issue
Block a user