mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 04:33: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_ARPING=y
|
||||||
CONFIG_FEATURE_UDHCPC_SANITIZEOPT=y
|
CONFIG_FEATURE_UDHCPC_SANITIZEOPT=y
|
||||||
CONFIG_UDHCPC_DEFAULT_SCRIPT="/usr/share/udhcpc/default.script"
|
CONFIG_UDHCPC_DEFAULT_SCRIPT="/usr/share/udhcpc/default.script"
|
||||||
CONFIG_UDHCPC6_DEFAULT_SCRIPT="/usr/share/udhcpc/default6.script"
|
# CONFIG_UDHCPC6 is not set
|
||||||
CONFIG_UDHCPC6=y
|
|
||||||
CONFIG_FEATURE_UDHCPC6_RFC3646=y
|
|
||||||
CONFIG_FEATURE_UDHCPC6_RFC4704=y
|
|
||||||
CONFIG_FEATURE_UDHCPC6_RFC4833=y
|
|
||||||
CONFIG_FEATURE_UDHCPC6_RFC5970=y
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Common options for DHCP applets
|
# 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_INSTALL_TARGET=y
|
||||||
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
||||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||||
|
BR2_PACKAGE_ODHCP6C=y
|
||||||
BR2_PACKAGE_STRACE=y
|
BR2_PACKAGE_STRACE=y
|
||||||
BR2_PACKAGE_STRESS_NG=y
|
BR2_PACKAGE_STRESS_NG=y
|
||||||
BR2_PACKAGE_JQ=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_INSTALL_TARGET=y
|
||||||
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
||||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||||
|
BR2_PACKAGE_ODHCP6C=y
|
||||||
BR2_PACKAGE_STRACE=y
|
BR2_PACKAGE_STRACE=y
|
||||||
BR2_PACKAGE_STRESS_NG=y
|
BR2_PACKAGE_STRESS_NG=y
|
||||||
BR2_PACKAGE_JQ=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_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
|
||||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||||
|
BR2_PACKAGE_ODHCP6C=y
|
||||||
BR2_PACKAGE_STRACE=y
|
BR2_PACKAGE_STRACE=y
|
||||||
BR2_PACKAGE_STRESS_NG=y
|
BR2_PACKAGE_STRESS_NG=y
|
||||||
BR2_PACKAGE_JQ=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_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
|
||||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||||
|
BR2_PACKAGE_ODHCP6C=y
|
||||||
BR2_PACKAGE_STRACE=y
|
BR2_PACKAGE_STRACE=y
|
||||||
BR2_PACKAGE_STRESS_NG=y
|
BR2_PACKAGE_STRESS_NG=y
|
||||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
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_INSTALL_TARGET=y
|
||||||
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
||||||
BR2_PACKAGE_BUSYBOX_CONFIG="$(BR2_EXTERNAL_INFIX_PATH)/board/common/busybox_defconfig"
|
BR2_PACKAGE_BUSYBOX_CONFIG="$(BR2_EXTERNAL_INFIX_PATH)/board/common/busybox_defconfig"
|
||||||
|
BR2_PACKAGE_ODHCP6C=y
|
||||||
BR2_PACKAGE_STRACE=y
|
BR2_PACKAGE_STRACE=y
|
||||||
BR2_PACKAGE_STRESS_NG=y
|
BR2_PACKAGE_STRESS_NG=y
|
||||||
BR2_PACKAGE_JQ=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_INSTALL_TARGET=y
|
||||||
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
|
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
|
||||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||||
|
BR2_PACKAGE_ODHCP6C=y
|
||||||
BR2_PACKAGE_STRACE=y
|
BR2_PACKAGE_STRACE=y
|
||||||
BR2_PACKAGE_STRESS_NG=y
|
BR2_PACKAGE_STRESS_NG=y
|
||||||
BR2_PACKAGE_JQ=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_INSTALL_TARGET=y
|
||||||
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
|
BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
|
||||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||||
|
BR2_PACKAGE_ODHCP6C=y
|
||||||
BR2_PACKAGE_STRACE=y
|
BR2_PACKAGE_STRACE=y
|
||||||
BR2_PACKAGE_STRESS_NG=y
|
BR2_PACKAGE_STRESS_NG=y
|
||||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
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
|
- Upgrade netopeer2 (NETCONF) to 2.7.0
|
||||||
- Add RIPv2 routing support, issue #582
|
- Add RIPv2 routing support, issue #582
|
||||||
- Add NTP server support, issue #904
|
- 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
|
- Add support for configurable OSPF debug logging, issue #1281. Debug options
|
||||||
can now be enabled per category (bfd, packet, ism, nsm, default-information,
|
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
|
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
|
* Default DHCPv6 options
|
||||||
* Note: udhcpc6 only supports dns-server (dns) and domain-search (search) with string names.
|
* Note: odhcp6c uses numeric option codes for all options.
|
||||||
* Other options use numeric codes, which dhcp_compose_option() handles automatically.
|
|
||||||
*/
|
*/
|
||||||
static void infer_options_v6(sr_session_ctx_t *session, const char *xpath)
|
static void infer_options_v6(sr_session_ctx_t *session, const char *xpath)
|
||||||
{
|
{
|
||||||
const char *opt[] = {
|
const char *opt[] = {
|
||||||
"dns-server", /* option 23 - udhcpc6: -O dns */
|
"dns-server", /* option 23 */
|
||||||
"domain-search", /* option 24 - udhcpc6: -O search */
|
"domain-search", /* option 24 */
|
||||||
"client-fqdn", /* option 39 - udhcpc6: -O 39 (equivalent to DHCPv4 hostname) */
|
"client-fqdn", /* option 39 */
|
||||||
"ntp-server" /* option 56 - udhcpc6: -O 56 */
|
"ntp-server" /* option 56 */
|
||||||
};
|
};
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
static char *fallback_options_v6(const char *ifname)
|
static char *fallback_options_v6(const char *ifname)
|
||||||
{
|
{
|
||||||
/* dns-server, domain-search, client-fqdn, ntp-server */
|
/* 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);
|
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)
|
static char *dhcp_options_v6(const char *ifname, struct lyd_node *cfg, bool *request_pd)
|
||||||
{
|
{
|
||||||
struct lyd_node *option;
|
struct lyd_node *option;
|
||||||
|
char opt_list[512] = { 0 };
|
||||||
char *options = NULL;
|
char *options = NULL;
|
||||||
|
bool has_options = false;
|
||||||
|
|
||||||
*request_pd = 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);
|
const char *name = lyd_get_value(id);
|
||||||
int num = dhcp_option_lookup(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"))) {
|
if (num == 25 || (name && !strcmp(name, "ia-pd"))) {
|
||||||
*request_pd = true;
|
*request_pd = true;
|
||||||
continue; /* Don't add to options string */
|
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);
|
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");
|
const char *duid = lydx_get_cattr(cfg, "duid");
|
||||||
char *client_duid = NULL, *options = NULL;
|
char *client_duid = NULL, *options = NULL;
|
||||||
const char *action = "disable";
|
const char *action = "disable";
|
||||||
char info_only[4] = { 0 };
|
const char *addr_mode = "-N try"; /* Default: stateful mode */
|
||||||
char prefix_del[4] = { 0 };
|
char prefix_del[16] = { 0 };
|
||||||
bool request_pd = false;
|
bool request_pd = false;
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
|
|
||||||
|
/* Information-only mode (stateless DHCPv6) */
|
||||||
if (lydx_is_enabled(cfg, "information-only"))
|
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]) {
|
if (duid && duid[0]) {
|
||||||
size_t len = strlen(duid) + 32;
|
size_t len = strlen(duid) + 16;
|
||||||
|
|
||||||
client_duid = malloc(len);
|
client_duid = malloc(len);
|
||||||
if (!client_duid)
|
if (!client_duid)
|
||||||
goto generr;
|
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);
|
options = dhcp_options_v6(ifname, cfg, &request_pd);
|
||||||
|
|
||||||
|
/* Prefix delegation: use -P 0 for auto */
|
||||||
if (request_pd)
|
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);
|
fp = fopenf("w", "/etc/finit.d/available/dhcpv6-client-%s.conf", ifname);
|
||||||
if (!fp) {
|
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, "# Generated by Infix confd\n");
|
||||||
fprintf(fp, "metric=%s\n", metric);
|
fprintf(fp, "metric=%s\n", metric);
|
||||||
fprintf(fp, "service <!> name:dhcpv6-client :%s <net/%s/running> \\\n"
|
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"
|
" [2345] odhcp6c -e -p /run/dhcpv6-client-%s.pid \\\n"
|
||||||
" %s%s%s%s \\\n"
|
" -s /usr/libexec/odhcp6c.sh \\\n"
|
||||||
" -i %s %s \\\n"
|
" %s %s%s%s \\\n"
|
||||||
|
" %s \\\n"
|
||||||
" -- DHCPv6 client @%s\n",
|
" -- DHCPv6 client @%s\n",
|
||||||
ifname, ifname, ifname,
|
ifname, ifname, ifname,
|
||||||
info_only, prefix_del,
|
addr_mode, prefix_del,
|
||||||
options ? "-o " : "", options ?: "",
|
options ?: "", client_duid ?: "",
|
||||||
ifname, client_duid ?: "", ifname);
|
ifname, ifname);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
action = "enable";
|
action = "enable";
|
||||||
err:
|
err:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ Tests verifying DHCPv4/DHCPv6 client and server functionality:
|
|||||||
- DHCPv4 client hostname management and priority
|
- DHCPv4 client hostname management and priority
|
||||||
- Basic DHCPv6 client operation with address assignment
|
- Basic DHCPv6 client operation with address assignment
|
||||||
- DHCPv6 client with prefix delegation (IA_PD)
|
- DHCPv6 client with prefix delegation (IA_PD)
|
||||||
|
- DHCPv6 SLAAC/RA (Stateless)
|
||||||
- Basic DHCPv4 server operation and lease assignment
|
- Basic DHCPv4 server operation and lease assignment
|
||||||
- DHCPv4 server with host-specific IP reservations
|
- DHCPv4 server with host-specific IP reservations
|
||||||
- DHCPv4 server with multiple subnet configurations
|
- 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[]
|
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
|
. Set up topology and attach to target DUT
|
||||||
. Configure DHCPv6 client
|
. Configure DHCPv6 client
|
||||||
|
. Verify DHCPv6 client is running
|
||||||
. Verify client lease for {CLIENT}
|
. Verify client lease for {CLIENT}
|
||||||
. Verify client default route ::/0
|
. Verify client default route ::/0
|
||||||
. Verify client domain name resolution
|
. Verify client domain name resolution
|
||||||
|
|||||||
@@ -12,6 +12,19 @@ import infamy.iface as iface
|
|||||||
import infamy.route as route
|
import infamy.route as route
|
||||||
from infamy.util import until
|
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:
|
with infamy.Test() as test:
|
||||||
SERVER = '2001:db8::1'
|
SERVER = '2001:db8::1'
|
||||||
CLIENT = '2001:db8::42'
|
CLIENT = '2001:db8::42'
|
||||||
@@ -53,14 +66,17 @@ with infamy.Test() as test:
|
|||||||
}
|
}
|
||||||
client.put_config_dict("ietf-interfaces", config)
|
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}"):
|
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"):
|
with test.step("Verify client default route ::/0"):
|
||||||
until(lambda: route.ipv6_route_exist(client, "::/0"), attempts=20)
|
until(lambda: route.ipv6_route_exist(client, "::/0"), attempts=20)
|
||||||
|
|
||||||
with test.step("Verify client domain name resolution"):
|
with test.step("Verify client domain name resolution"):
|
||||||
rc = tgtssh.runsh(f"ping -6 -c1 -w20 {VERIFY}")
|
# DNS configuration may take a moment, especially on ARM hardware
|
||||||
assert rc.returncode == 0, f"Client failed: ping {VERIFY}"
|
until(check_dns_resolution, attempts=20)
|
||||||
|
|
||||||
test.succeed()
|
test.succeed()
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import time
|
|||||||
|
|
||||||
def checkrun(dut):
|
def checkrun(dut):
|
||||||
"""Check DUT is running DHCPv6 client"""
|
"""Check DUT is running DHCPv6 client"""
|
||||||
res = dut.runsh(f"pgrep -f 'udhcpc6.*{port}'")
|
res = dut.runsh(f"pgrep -f 'odhcp6c.*{port}'")
|
||||||
# print(f"Checking for udhcpc6: {res.stdout}")
|
# print(f"Checking for odhcp6c: {res.stdout}")
|
||||||
if res.stdout.strip() != "":
|
if res.stdout.strip() != "":
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -24,8 +24,8 @@ def checkrun(dut):
|
|||||||
|
|
||||||
def checklog(dut):
|
def checklog(dut):
|
||||||
"""Check syslog for prefix delegation message"""
|
"""Check syslog for prefix delegation message"""
|
||||||
rc = dut.runsh("tail -10 /log/syslog | grep 'received delegated prefix'")
|
rc = dut.runsh("tail -50 /log/syslog | grep 'received delegated prefix'")
|
||||||
# print(f"DHCPv6 client logs:\n{res.stdout}")
|
# print(f"DHCPv6 client logs:\n{rc.stdout}")
|
||||||
if rc.stdout.strip() != "":
|
if rc.stdout.strip() != "":
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -74,9 +74,10 @@ with infamy.Test() as test:
|
|||||||
client.put_config_dict("ietf-interfaces", config)
|
client.put_config_dict("ietf-interfaces", config)
|
||||||
|
|
||||||
with test.step("Verify DHCPv6 client is running"):
|
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"):
|
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()
|
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
|
- name: DHCPv6 Prefix Delegation
|
||||||
case: client6_prefix_delegation/test.py
|
case: client6_prefix_delegation/test.py
|
||||||
|
|
||||||
|
- name: DHCPv6 SLAAC/RA (Stateless)
|
||||||
|
case: client6_slaac_ra/test.py
|
||||||
|
|||||||
Reference in New Issue
Block a user