Files
R. Christian McDonald 9fd9039c88 tests: Add SLAAC interface identifier cases to ndp.sh
Cover the interface identifier selection in in6_ifadd(): a /64
link-local address provides the identifier, while a /128 link-local
address or no link-local address at all yields the EUI-64 identifier
derived from the MAC address.  Each case asserts the exact
autoconfigured address.  ndp_prefix_len_mismatch reaches the same
fallback with a /127 link-local address, but only checks that some
address was configured.

Stable addresses (RFC 7217) are the default and bypass both paths, so
the host disables them.  The advertisement goes to all nodes, and an
interface only joins that group with its first address, so the case
without a link-local address gives the host a static address from
another prefix.

ra.py now takes the Ethernet source address from --sendif rather than
from a route lookup; for ff02::1 that lookup fails in scapy 2.7.0.

Reviewed by:		pouria
MFC after:		2 weeks
Sponsored by:		Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D60520
2026-10-13 19:31:53 -04:00

1040 lines
30 KiB
Bash
Executable File

#!/usr/bin/env atf-sh
#-
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2021 Alexander V. Chernikov
# Copyright (c) 2026 Pouria Mousavizadeh Tehrani <[email protected]>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
. $(atf_get_srcdir)/../common/vnet.subr
atf_test_case "ndp_add_gu_success" "cleanup"
ndp_add_gu_success_head() {
atf_set descr 'Test static ndp record addition'
atf_set require.user root
}
ndp_add_gu_success_body() {
local epair0 jname
vnet_init
jname="v6t-ndp_add_success"
epair0=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair0}a
jexec ${jname} ndp -i ${epair0}a -- -disabled
jexec ${jname} ifconfig ${epair0}a up
jexec ${jname} ifconfig ${epair0}a inet6 2001:db8::1/64
# wait for DAD to complete
while [ `jexec ${jname} ifconfig | grep inet6 | grep -c tentative` != "0" ]; do
sleep 0.1
done
atf_check jexec ${jname} ndp -s 2001:db8::2 90:10:00:01:02:03
t=`jexec ${jname} ndp -an | grep 2001:db8::2 | awk '{print $1, $2, $3, $4}'`
if [ "${t}" != "2001:db8::2 90:10:00:01:02:03 ${epair0}a permanent" ]; then
atf_fail "Wrong output: ${t}"
fi
echo "T='${t}'"
}
ndp_add_gu_success_cleanup() {
vnet_cleanup
}
atf_test_case "ndp_del_gu_success" "cleanup"
ndp_del_gu_success_head() {
atf_set descr 'Test ndp record deletion'
atf_set require.user root
}
ndp_del_gu_success_body() {
local epair0 jname
vnet_init
jname="v6t-ndp_del_gu_success"
epair0=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair0}a
ndp_if_up ${epair0}a ${jname}
ndp_if_up ${epair0}b
jexec ${jname} ifconfig ${epair0}a inet6 2001:db8::1/64
ifconfig ${epair0}b inet6 2001:db8::2/64
# wait for DAD to complete
while jexec ${jname} ifconfig ${epair0}a inet6 | grep -q tentative ||
ifconfig ${epair0}b inet6 | grep -q tentative; do
sleep 0.1
done
# A reachable peer keeps the entry alive; an unanswered solicitation
# would expire it after nd6_mmaxtries retransmissions (about 3s).
atf_check -o ignore jexec ${jname} ping -c1 -t5 2001:db8::2
atf_check -o match:"2001:db8::2 \(2001:db8::2\) deleted" jexec ${jname} ndp -nd 2001:db8::2
}
ndp_del_gu_success_cleanup() {
vnet_cleanup
}
ndp_if_up()
{
local ifname=$1
local jname=$2
if [ -n "$jname" ]; then
jname="jexec ${jname}"
fi
atf_check ${jname} ifconfig ${ifname} up
atf_check ${jname} ifconfig ${ifname} inet6 -ifdisabled
while ${jname} ifconfig ${ifname} inet6 | grep tentative; do
sleep 0.1
done
}
ndp_if_lladdr()
{
local ifname=$1
local jname=$2
if [ -n "$jname" ]; then
jname="jexec ${jname}"
fi
${jname} ifconfig ${ifname} inet6 | \
awk '/inet6 fe80:/{split($2, addr, "%"); print addr[1]}'
}
atf_test_case "ndp_slaac_default_route" "cleanup"
ndp_slaac_default_route_head() {
atf_set descr 'Test default route installation via SLAAC'
atf_set require.user root
atf_set require.progs python3 scapy
}
ndp_slaac_default_route_body() {
local epair0 jname lladdr
vnet_init
jname="v6t-ndp_slaac_default_route"
epair0=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair0}a
ndp_if_up ${epair0}a ${jname}
ndp_if_up ${epair0}b
atf_check jexec ${jname} ifconfig ${epair0}a inet6 accept_rtadv
# Make sure that NAs from us are flagged as coming from a router.
atf_check -o ignore sysctl net.inet6.ip6.forwarding=1
# Send an RA advertising a prefix.
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src $(ndp_if_lladdr ${epair0}b) \
--prefix "2001:db8:ffff:1000::" --prefixlen 64
# Wait for a default router to appear.
while [ -z "$(jexec ${jname} ndp -r)" ]; do
sleep 0.1
done
atf_check -o match:"^default[[:space:]]+fe80:" \
jexec ${jname} netstat -rn -6
# Get rid of the default route.
atf_check -o ignore jexec ${jname} route -6 flush
atf_check -o not-match:"^default[[:space:]]+fe80:" \
jexec ${jname} netstat -rn -6
# Send another RA, make sure that the default route is installed again.
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src $(ndp_if_lladdr ${epair0}b) \
--prefix "2001:db8:ffff:1000::" --prefixlen 64
while [ -z "$(jexec ${jname} ndp -r)" ]; do
sleep 0.1
done
atf_check -o match:"^default[[:space:]]+fe80:" \
jexec ${jname} netstat -rn -6
}
ndp_slaac_default_route_cleanup() {
vnet_cleanup
}
atf_test_case "ndp_prefix_len_mismatch" "cleanup"
ndp_prefix_len_mismatch_head() {
atf_set descr 'Test RAs on an interface without a /64 lladdr'
atf_set require.user root
atf_set require.progs python3 scapy
}
ndp_prefix_len_mismatch_body() {
vnet_init
epair=$(vnet_mkepair)
vnet_mkjail alcatraz ${epair}a
jexec alcatraz ifconfig ${epair}a inet6 -auto_linklocal
jexec alcatraz ifconfig ${epair}a inet6 -ifdisabled
jexec alcatraz ifconfig ${epair}a inet6 accept_rtadv
# Stable addresses (RFC 7217) do not use the link-local address.
jexec alcatraz ifconfig ${epair}a inet6 -stableaddr
jexec alcatraz ifconfig ${epair}a inet6 fe80::5a9c:fcff:fe10:5d07/127
jexec alcatraz ifconfig ${epair}a up
ifconfig ${epair}b inet6 -ifdisabled
ifconfig ${epair}b up
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair}b \
--dst $(ndp_if_lladdr ${epair}a alcatraz) \
--src $(ndp_if_lladdr ${epair}b) \
--prefix "2001:db8:ffff:1000::" --prefixlen 64
atf_check \
-o match:"inet6 2001:db8:ffff:1000:.* prefixlen 64.*autoconf.*" \
jexec alcatraz ifconfig ${epair}a
}
ndp_prefix_len_mismatch_cleanup() {
vnet_cleanup
}
atf_test_case "ndp_prefix_lifetime" "cleanup"
ndp_prefix_lifetime_head() {
atf_set descr 'Test ndp slaac address lifetime handling'
atf_set require.user root
atf_set require.progs python3 scapy
}
ndp_prefix_lifetime_body() {
local epair0 jname prefix
vnet_init
jname="v6t-ndp_prefix_lifetime"
epair0=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair0}a
ndp_if_up ${epair0}a ${jname}
ndp_if_up ${epair0}b
atf_check jexec ${jname} ifconfig ${epair0}a inet6 accept_rtadv no_dad
prefix="2001:db8:ffff:1000:"
# Send an RA advertising a prefix.
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src $(ndp_if_lladdr ${epair0}b) \
--prefix "2001:db8:ffff:1000::" --prefixlen 64 \
--validlifetime 10 --preferredlifetime 5
# Wait for a default router to appear.
while [ -z "$(jexec ${jname} ndp -r)" ]; do
sleep 0.1
done
atf_check \
-o match:"^default[[:space:]]+fe80:" \
jexec ${jname} netstat -rn -6
atf_check \
-o match:"inet6 ${prefix}.* prefixlen 64 autoconf pltime 5 vltime 10" \
jexec ${jname} ifconfig ${epair0}a
# Wait for the address to become deprecated.
sleep 6
atf_check \
-o match:"inet6 ${prefix}.* prefixlen 64 deprecated autoconf pltime 0 vltime [1-9]+" \
jexec ${jname} ifconfig -L ${epair0}a
# Wait for the address to expire.
sleep 6
atf_check \
-o not-match:"inet6 ${prefix}.*" \
jexec ${jname} ifconfig ${epair0}a
}
ndp_prefix_lifetime_cleanup() {
vnet_cleanup
}
atf_test_case "ndp_prefix_lifetime_extend"
ndp_prefix_lifetime_extend_head() {
atf_set descr 'Test prefix lifetime updates via ifconfig'
atf_set require.user root
atf_set require.progs jq
}
get_prefix_attr() {
local prefix=$1
local attr=$2
local jail=""
if [ -n "$3" ]; then
jail="jexec $3"
fi
${jail} ndp -p --libxo json | \
jq -r '.ndp.["prefix-list"][] |
select(.prefix == "'${prefix}'") | .["'${attr}'"]'
}
# Given a prefix, return its expiry time in seconds.
prefix_expiry() {
get_prefix_attr $1 "expires_sec" $2
}
# Given a prefix, return its valid and preferred lifetimes.
prefix_lifetimes() {
local p v
v=$(get_prefix_attr $1 "valid-lifetime" $2)
p=$(get_prefix_attr $1 "preferred-lifetime" $2)
echo $v $p
}
ndp_prefix_lifetime_extend_body() {
local epair ex1 ex2 ex3 prefix pltime vltime
atf_check -o save:epair ifconfig epair create
epair=$(cat epair)
atf_check ifconfig ${epair} up
prefix="2001:db8:ffff:1000::"
atf_check ifconfig ${epair} inet6 ${prefix}1/64 pltime 5 vltime 10
t=$(prefix_lifetimes ${prefix}/64)
if [ "${t}" != "10 5" ]; then
atf_fail "Unexpected lifetimes: ${t}"
fi
ex1=$(prefix_expiry ${prefix}/64)
if [ "${ex1}" -gt 10 ]; then
atf_fail "Unexpected expiry time: ${ex1}"
fi
# Double the address lifetime and verify that the prefix is
# updated.
atf_check ifconfig ${epair} inet6 ${prefix}1/64 pltime 10 vltime 20
t=$(prefix_lifetimes ${prefix}/64)
if [ "${t}" != "20 10" ]; then
atf_fail "Unexpected lifetimes: ${t}"
fi
ex2=$(prefix_expiry ${prefix}/64)
if [ "${ex2}" -le "${ex1}" ]; then
atf_fail "Expiry time was not extended: ${ex1} <= ${ex2}"
fi
# Add a second address from the same prefix with a shorter
# lifetime, and make sure that the prefix lifetime is not
# shortened.
atf_check ifconfig ${epair} inet6 ${prefix}2/64 pltime 5 vltime 10
t=$(prefix_lifetimes ${prefix}/64)
if [ "${t}" != "20 10" ]; then
atf_fail "Unexpected lifetimes: ${t}"
fi
ex3=$(prefix_expiry ${prefix}/64)
if [ "${ex3}" -le 10 -o "${ex3}" -gt 20 ]; then
atf_fail "Unexpected expiry time: ${ex3}"
fi
}
atf_test_case "ndp_grand_linklayer_event" "cleanup"
ndp_grand_linklayer_event_head() {
atf_set descr 'Test ndp GRAND on link-layer change event'
atf_set require.user root
}
ndp_grand_linklayer_event_body() {
local epair0 jname prefix address mac
vnet_init
jname="v6t-ndp_grand_linklayer_event"
prefix="2001:db8:ffff:1000::"
mac="90:10:00:01:02:03"
epair0=$(vnet_mkepair)
vnet_mkjail ${jname}1 ${epair0}a
vnet_mkjail ${jname}2 ${epair0}b
ndp_if_up ${epair0}a ${jname}1
ndp_if_up ${epair0}b ${jname}2
# with no_dad, grand for new global address will NOT run
atf_check ifconfig -j ${jname}1 ${epair0}a inet6 ${prefix}1 no_dad
atf_check ifconfig -j ${jname}2 ${epair0}b inet6 ${prefix}2 no_dad
atf_check -s exit:1 -e ignore -o ignore \
jexec ${jname}2 ndp -n ${prefix}1
# Create the NCE in jail 2.
atf_check -o ignore jexec ${jname}2 ping -c1 -t1 ${prefix}1
# Check if current mac is received
atf_check -s exit:0 -o ignore jexec ${jname}2 ndp -n ${prefix}1
# change mac address to trigger grand.
atf_check ifconfig -j ${jname}1 ${epair0}a ether ${mac}
# link-local is the first address, thus our address should
# wait a second before sending its NA
atf_check -o not-match:"${prefix}1.*${mac}.*" \
jexec ${jname}2 ndp -n ${prefix}1
sleep 1.1
# Check if mac address automatically updated
atf_check -o match:"${prefix}1.*${mac}.*" \
jexec ${jname}2 ndp -n ${prefix}1
}
ndp_grand_linklayer_event_cleanup() {
vnet_cleanup
}
atf_test_case "ndp_input_validation_hlim" "cleanup"
ndp_input_validation_hlim_head() {
atf_set descr 'Test RFC 4861 section 6.1.2: RA hop limit validation'
atf_set require.user root
atf_set require.progs python3 scapy
}
ndp_input_validation_hlim_body() {
local epair0 jname
vnet_init
jname="v6t-ndp_input_validation_hlim"
epair0=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair0}a
ndp_if_up ${epair0}a ${jname}
ndp_if_up ${epair0}b
atf_check jexec ${jname} ifconfig ${epair0}a inet6 accept_rtadv
# Make sure that NAs from us are flagged as coming from a router.
atf_check -o ignore sysctl net.inet6.ip6.forwarding=1
# Send an invalid RA advertising a prefix.
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src $(ndp_if_lladdr ${epair0}b) \
--hoplimit 254
# Wait to make sure no router would appear.
sleep 0.5
atf_check -o empty jexec ${jname} ndp -r
}
ndp_input_validation_hlim_cleanup() {
vnet_cleanup
}
atf_test_case "ndp_input_validation_src_linklocal" "cleanup"
ndp_input_validation_src_linklocal_head() {
atf_set descr 'Test RFC 4861 section 6.1.2: RA source address must be link-local'
atf_set require.user root
atf_set require.progs python3 scapy
}
ndp_input_validation_src_linklocal_body() {
local epair0 jname
vnet_init
jname="v6t-ndp_input_validation_src_linklocal"
epair0=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair0}a
ndp_if_up ${epair0}a ${jname}
ndp_if_up ${epair0}b
atf_check jexec ${jname} ifconfig ${epair0}a inet6 accept_rtadv
# Make sure that NAs from us are flagged as coming from a router.
atf_check -o ignore sysctl net.inet6.ip6.forwarding=1
# Send an invalid RA with multicast source.
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src ff02::2
# Send an invalid RA with global unicast source.
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src 3fff::1
# Wait to make sure no router would appear.
sleep 0.5
atf_check -o empty jexec ${jname} ndp -r
}
ndp_input_validation_src_linklocal_cleanup() {
vnet_cleanup
}
atf_test_case "ndp_multirouter_pref" "cleanup"
ndp_multirouter_pref_head() {
atf_set descr 'Test RFC 4861 section 6.3.4: multiple routers with different pref'
atf_set require.user root
atf_set require.progs jq python3 scapy
}
ndp_multirouter_pref_body() {
local epair0 jname prefix lladdr advrtrs
vnet_init
jname="v6t-ndp_multirouter_pref"
prefix="2001:db8:ffff:1000::"
epair0=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair0}a
ndp_if_up ${epair0}a ${jname}
ndp_if_up ${epair0}b
atf_check jexec ${jname} ifconfig ${epair0}a inet6 accept_rtadv
# Make sure that NAs from us are flagged as coming from a router.
atf_check -o ignore sysctl net.inet6.ip6.forwarding=1
lladdr="$(ndp_if_lladdr ${epair0}b)"
lladdr="${lladdr%?}a"
# Send an RA with high preference.
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src ${lladdr} \
--rtrpref 1 --prefix ${prefix} \
--validlifetime 10 --preferredlifetime 5
lladdr="${lladdr%?}b"
# Send an RA with medium preference.
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src ${lladdr} \
--rtrpref 0 --prefix ${prefix} \
--validlifetime 10 --preferredlifetime 5
lladdr="${lladdr%?}c"
# Send an RA with low preference.
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src ${lladdr} \
--rtrpref 3 --prefix ${prefix} \
--validlifetime 10 --preferredlifetime 5
# Wait for a default router to appear.
while [ "$(jexec ${jname} ndp -r | wc -l)" -ne 3 ]; do
sleep 0.01
done
atf_check -s exit:0 \
-o match:"^${lladdr%?}a%${epair0}a if=${epair0}a, flags=, pref=high,.*" \
-o match:"^${lladdr%?}b%${epair0}a if=${epair0}a, flags=, pref=medium,.*" \
-o match:"^${lladdr%?}c%${epair0}a if=${epair0}a, flags=, pref=low,.*" \
jexec ${jname} ndp -r
# Make sure a default route is being installed
# XXX: for now, does not matter which router
atf_check -o match:"^default[[:space:]]+${lladdr%?}" \
jexec ${jname} netstat -rn6
# Make sure ndp knows about prefix advertising routers.
advrtrs=$(get_prefix_attr ${prefix}/64 "advertising-routers" "${jname}" | \
jq -r '. | length')
if [ "${advrtrs}" -ne 3 ]; then
atf_fail "Unexpected number of advertising routers: ${advrtrs}"
fi
}
ndp_muiltirouter_pref_cleanup() {
vnet_cleanup
}
atf_test_case "ndp_slaac_twohour_rule" "cleanup"
ndp_slaac_twohour_rule_head() {
atf_set descr 'Test RFC 4862 section 5.5.3 (e): Two hour rule'
atf_set require.user root
atf_set require.progs jq python3 scapy
}
ndp_slaac_twohour_rule_body() {
local epair0 jname prefix ex1 ex2
vnet_init
jname="v6t-ndp_slaac_twohour_rule"
prefix="2001:db8:ffff:1000::"
epair0=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair0}a
ndp_if_up ${epair0}a ${jname}
ndp_if_up ${epair0}b
atf_check jexec ${jname} ifconfig ${epair0}a inet6 accept_rtadv
# Make sure that NAs from us are flagged as coming from a router.
atf_check -o ignore sysctl net.inet6.ip6.forwarding=1
# Send an RA with 1 hour lifetime
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src $(ndp_if_lladdr ${epair0}b) \
--prefix ${prefix} --prefixlen 64 \
--validlifetime 3600 --preferredlifetime 3600
# Wait for a default router to appear.
while [ -z "$(jexec ${jname} ndp -r)" ]; do
sleep 0.01
done
ex1=$(prefix_expiry ${prefix}/64 "${jname}")
# Set the address lifetime to 2 hours and verify that the prefix is updated.
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src $(ndp_if_lladdr ${epair0}b) \
--prefix ${prefix} --prefixlen 64 \
--validlifetime 7200 --preferredlifetime 7200
# Verify that ndp sets the correct value from RA.
ex2=$(prefix_expiry ${prefix}/64 "${jname}")
if [ "${ex2}" -le "${ex1}" ]; then
atf_fail "Unexpected expiry time: ${ex2} <= ${ex1}"
fi
# Verify that address also updated the valid lifetime.
ex2=$(ifconfig -j "${jname}" ${epair0}a inet6 | grep vltime | awk '{print $NF}' )
if [ "${ex2}" -le 3600 ]; then
atf_fail "Unexpected expiry time: ${ex2} <= ${ex1}"
fi
# Set the address lifetime to 1 Hour and verify that
# the address of prefix is NOT updated to 1 hour.
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src $(ndp_if_lladdr ${epair0}b) \
--prefix ${prefix} --prefixlen 64 \
--validlifetime 3600 --preferredlifetime 3600
# Verify that ndp sets the received value from RA.
ex2=$(prefix_expiry ${prefix}/64 "${jname}")
if [ "${ex2}" -gt 3600 ]; then
atf_fail "Unexpected ndp expiry time: ${ex2} > 3600"
fi
# Verify that address NOT updated the valid lifetime.
ex2=$(ifconfig -j "${jname}" ${epair0}a inet6 | grep vltime | awk '{print $NF}' )
if [ "${ex2}" -le 3600 ]; then
atf_fail "Unexpected expiry time: ${ex2} <= 3600"
fi
}
ndp_slaac_twohour_rule_cleanup() {
vnet_cleanup
}
get_iface_prefix_flags() {
local prefix=$1
local iface=$2
local jail=""
if [ -n "$3" ]; then
jail="jexec $3"
fi
${jail} ndp -p --libxo json | \
jq -r '.ndp.["prefix-list"][] |
select((.prefix == "'${prefix}'") and .interface == "'${iface}'") |
.flags'
}
atf_test_case "ndp_slaac_switch_onlink_prefix" "cleanup"
ndp_slaac_switch_onlink_prefix_head() {
atf_set descr 'Test SLAAC onlink prefix switching when prefix received via multiple interfaces'
atf_set require.user root
atf_set require.progs python3 scapy
}
ndp_slaac_switch_onlink_prefix_body() {
local epair0 epair1 jname prefix lladdr1 lladdr2 f1 f2
vnet_init
jname="v6t-ndp_slaac_switch_onlink_prefix"
prefix="2001:db8:ffff:1000::"
epair0=$(vnet_mkepair)
epair1=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair0}a
atf_check ifconfig ${epair1}a vnet ${jname}
ndp_if_up ${epair0}a ${jname}
ndp_if_up ${epair1}a ${jname}
ndp_if_up ${epair0}b
ndp_if_up ${epair1}b
atf_check ifconfig -j ${jname} ${epair0}a inet6 accept_rtadv
atf_check ifconfig -j ${jname} ${epair1}a inet6 accept_rtadv
lladdr0=$(ndp_if_lladdr ${epair0}b)
lladdr1=$(ndp_if_lladdr ${epair1}b)
# Send an RA with high pref from epair0
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src ${lladdr0} \
--rtrpref 1 --prefix ${prefix} \
--validlifetime 10 --preferredlifetime 5
# Send an RA with medium pref from epair1
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair1}b \
--dst $(ndp_if_lladdr ${epair1}a ${jname}) \
--src ${lladdr1} \
--rtrpref 0 --prefix ${prefix} \
--validlifetime 10 --preferredlifetime 5
# Wait for a default router to appear.
while [ -z "$(jexec ${jname} ndp -r)" ]; do
sleep 0.01
done
# Verify that we have a default route to epair0a
atf_check -o match:"^default[[:space:]]+${lladdr0}" \
jexec ${jname} netstat -rn6
# Verify that epair0a is_onlink and epair1a is_detached
f1=$(get_iface_prefix_flags "${prefix}/64" "${epair0}a" "${jname}")
f2=$(get_iface_prefix_flags "${prefix}/64" "${epair1}a" "${jname}")
if [ "${f1}" != "LAO" ]; then
atf_fail "Unexpected prefix flags on epair0a: ${f1}"
fi
if [ "${f2}" != "LAD" ]; then
atf_fail "Unexpected prefix flags on epair1a: ${f2}"
fi
# Send an RA to withdraw prefix from epair0
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src ${lladdr0} \
--rtrpref 1 --rtrltime 0 --prefix ${prefix} \
--validlifetime 0 --preferredlifetime 0
# Verify that epair1a is_onlink and epair0a is not
while [ "$(get_iface_prefix_flags ${prefix}/64 ${epair0}a ${jname})" == "LAO" ];
do
sleep 0.1
done
f2=$(get_iface_prefix_flags "${prefix}/64" "${epair1}a" "${jname}")
if [ "${f2}" != "LAO" ]; then
atf_fail "Unexpected prefix flags on epair1a: ${f2}"
fi
# Verify that we have a default route to epair1a
atf_check -o match:"^default[[:space:]]+${lladdr1}" \
jexec ${jname} netstat -rn6
}
ndp_slaac_switch_onlink_prefix_cleanup() {
vnet_cleanup
}
atf_test_case "ndp_routeinfo_option" "cleanup"
ndp_routeinfo_option_head() {
atf_set descr 'Test RFC 4191: Add route based on received RTI in RA'
atf_set require.user root
atf_set require.progs jq python3 scapy
}
ndp_routeinfo_option_body() {
local epair0 jname prefix lladdr route1 route2
vnet_init
jname="v6t-ndp_routeinfo_option"
prefix="2001:db8:ffff:1000::"
route1="3fff:1000::"
route2="3fff:2000::"
epair0=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair0}a
ndp_if_up ${epair0}a ${jname}
ndp_if_up ${epair0}b
atf_check jexec ${jname} ifconfig ${epair0}a inet6 accept_rtadv
# Make sure that NAs from us are flagged as coming from a router.
atf_check -o ignore sysctl net.inet6.ip6.forwarding=1
lladdr="$(ndp_if_lladdr ${epair0}b)"
# Send an RA with high preference with 3 routes.
# The last rti should overwrite the default route rtiltime to 100 seconds
# and its preference to medium.
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${epair0}b \
--dst $(ndp_if_lladdr ${epair0}a ${jname}) \
--src ${lladdr} \
--rtrpref 1 --rtrltime 1800 --prefix ${prefix} \
--route ${route1} --routelen 32 \
--rtipref 1 --rtiltime 1800 \
--route ${route2} --routelen 48 \
--rtipref 4 --rtiltime 600 \
--route :: --routelen 0 \
--rtipref 4 --rtiltime 100
# Wait for a default router to appear.
while [ -z "$(jexec ${jname} ndp -r)" ]; do
sleep 0.1
done
# Make sure routes from rti option are being installed
atf_check -s exit:0 -o save:netstat_out netstat -j ${jname} -rn6
atf_check -s exit:0 -o match:"^default[[:space:]]+${lladdr}" \
cat netstat_out
# Ensure that route1's and route2's expiration times are correct
# respectively and do not get swapped
expire1="$(grep "^${route1}/32[[:space:]]*${lladdr}" netstat_out | cut -wf5)"
atf_check -s exit:0 test 601 -le ${expire1} -a ${expire1} -le 1800
expire2="$(grep "^${route2}/48[[:space:]]*${lladdr}" netstat_out | cut -wf5)"
atf_check -s exit:0 test ${expire2} -le 600
# Verify the default route lifetime and its preference is overwrited
atf_check -s exit:0 \
-o match:"^${lladdr}%${epair0}a if=${epair0}a, flags=, pref=medium, expire=1m.*" \
jexec ${jname} ndp -r
}
ndp_routeinfo_option_cleanup() {
vnet_cleanup
}
# SLAAC interface identifier selection in in6_ifadd(): a link-local
# address with the advertised prefix length lends its identifier to the
# autoconfigured address, otherwise the identifier is derived from the MAC
# address, which covers PPP links with a /128 link-local address as well as
# interfaces without a link-local address. The MAC address below gives the
# EUI-64 based identifier 11:22ff:fe33:4455. Stable addresses (RFC 7217)
# are switched off on the host, they bypass both paths.
ndp_slaac_prefix="2001:db8:ffff:2000"
ndp_slaac_mac="02:11:22:33:44:55"
ndp_slaac_mac_ifid="11:22ff:fe33:4455"
ndp_slaac_ll_ifid="dead:beef:1:2"
# Prepare ${ifname} in jail ${jname} as a SLAAC host with the test MAC
# address and without an automatic link-local address. DAD is off, so
# that addresses are final once configured. The caller adds the addresses
# its case needs and brings the interface up.
ndp_slaac_host()
{
local ifname=$1 jname=$2
atf_check ifconfig -j ${jname} ${ifname} ether ${ndp_slaac_mac}
atf_check ifconfig -j ${jname} ${ifname} inet6 -auto_linklocal \
-ifdisabled accept_rtadv -stableaddr no_dad
}
# Advertise ${ndp_slaac_prefix}::/64 to all nodes from ${sendif} and wait
# for jail ${jname} to autoconfigure an address from it on ${ifname}. Any
# further arguments are passed to ra.py.
ndp_slaac_advertise()
{
local sendif=$1 jname=$2 ifname=$3 i
shift 3
atf_check -e ignore python3 $(atf_get_srcdir)/ra.py \
--sendif ${sendif} \
--dst ff02::1 \
--src $(ndp_if_lladdr ${sendif}) \
--prefix "${ndp_slaac_prefix}::" --prefixlen 64 "$@"
for i in $(jot 20); do
if ifconfig -j ${jname} ${ifname} inet6 | \
grep -q "inet6 ${ndp_slaac_prefix}:.*autoconf"; then
return
fi
sleep 0.1
done
atf_fail "No address from ${ndp_slaac_prefix}::/64 was autoconfigured on ${ifname}"
}
atf_test_case "ndp_slaac_ifid_lladdr" "cleanup"
ndp_slaac_ifid_lladdr_head() {
atf_set descr 'SLAAC takes the interface ID from a /64 link-local address'
atf_set require.user root
atf_set require.progs python3 scapy
}
ndp_slaac_ifid_lladdr_body() {
local epair jname
vnet_init
jname="v6t-ndp_slaac_ifid_lladdr"
epair=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair}a
ndp_slaac_host ${epair}a ${jname}
atf_check ifconfig -j ${jname} ${epair}a inet6 \
fe80::${ndp_slaac_ll_ifid}/64
atf_check ifconfig -j ${jname} ${epair}a up
ndp_if_up ${epair}b
ndp_slaac_advertise ${epair}b ${jname} ${epair}a
atf_check \
-o match:"inet6 ${ndp_slaac_prefix}:${ndp_slaac_ll_ifid} prefixlen 64.*autoconf" \
-o not-match:"inet6 ${ndp_slaac_prefix}:${ndp_slaac_mac_ifid} " \
ifconfig -j ${jname} ${epair}a inet6
}
ndp_slaac_ifid_lladdr_cleanup() {
vnet_cleanup
}
atf_test_case "ndp_slaac_ifid_no_lladdr" "cleanup"
ndp_slaac_ifid_no_lladdr_head() {
atf_set descr 'SLAAC derives the interface ID from the MAC address without a link-local address'
atf_set require.user root
atf_set require.progs python3 scapy
}
ndp_slaac_ifid_no_lladdr_body() {
local epair jname
vnet_init
jname="v6t-ndp_slaac_ifid_no_lladdr"
epair=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair}a
# An interface joins the all-nodes group, and so receives
# advertisements, only once it has an IPv6 address: give it a
# static one from another prefix in place of the link-local one.
ndp_slaac_host ${epair}a ${jname}
atf_check ifconfig -j ${jname} ${epair}a inet6 \
2001:db8:ffff:1::2/64
atf_check ifconfig -j ${jname} ${epair}a up
ndp_if_up ${epair}b
ndp_slaac_advertise ${epair}b ${jname} ${epair}a
atf_check \
-o match:"inet6 ${ndp_slaac_prefix}:${ndp_slaac_mac_ifid} prefixlen 64.*autoconf" \
-o not-match:"inet6 fe80:" \
ifconfig -j ${jname} ${epair}a inet6
}
ndp_slaac_ifid_no_lladdr_cleanup() {
vnet_cleanup
}
atf_test_case "ndp_slaac_ifid_lladdr_plen128" "cleanup"
ndp_slaac_ifid_lladdr_plen128_head() {
atf_set descr 'SLAAC derives the interface ID from the MAC address when the link-local address is a /128'
atf_set require.user root
atf_set require.progs python3 scapy
}
ndp_slaac_ifid_lladdr_plen128_body() {
local epair jname
vnet_init
jname="v6t-ndp_slaac_ifid_lladdr_plen128"
epair=$(vnet_mkepair)
vnet_mkjail ${jname} ${epair}a
ndp_slaac_host ${epair}a ${jname}
atf_check ifconfig -j ${jname} ${epair}a inet6 \
fe80::${ndp_slaac_ll_ifid}/128
atf_check ifconfig -j ${jname} ${epair}a up
ndp_if_up ${epair}b
ndp_slaac_advertise ${epair}b ${jname} ${epair}a
atf_check \
-o match:"inet6 ${ndp_slaac_prefix}:${ndp_slaac_mac_ifid} prefixlen 64.*autoconf" \
-o not-match:"inet6 ${ndp_slaac_prefix}:${ndp_slaac_ll_ifid} " \
ifconfig -j ${jname} ${epair}a inet6
}
ndp_slaac_ifid_lladdr_plen128_cleanup() {
vnet_cleanup
}
atf_init_test_cases()
{
atf_add_test_case "ndp_add_gu_success"
atf_add_test_case "ndp_del_gu_success"
atf_add_test_case "ndp_slaac_default_route"
atf_add_test_case "ndp_slaac_twohour_rule"
atf_add_test_case "ndp_slaac_switch_onlink_prefix"
atf_add_test_case "ndp_prefix_len_mismatch"
atf_add_test_case "ndp_prefix_lifetime"
atf_add_test_case "ndp_prefix_lifetime_extend"
atf_add_test_case "ndp_grand_linklayer_event"
atf_add_test_case "ndp_input_validation_hlim"
atf_add_test_case "ndp_input_validation_src_linklocal"
atf_add_test_case "ndp_multirouter_pref"
atf_add_test_case "ndp_routeinfo_option"
atf_add_test_case "ndp_slaac_ifid_lladdr"
atf_add_test_case "ndp_slaac_ifid_no_lladdr"
atf_add_test_case "ndp_slaac_ifid_lladdr_plen128"
}