#!/bin/sh - # # Copyright 2021 John-Mark Gurney. # # 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. # # # ls testinterfaces.sh | entr sh -c 'fsync testinterfaces.sh && sh testinterfaces.sh run ue0 ue1' # ifconfig ue0 -vnet testjail; ifconfig ue1 -vnet checkjail # # Tests to add: # Multicast filter programming # bad checksum filtering # TSO/LRO functionality # polling? # TOE? # WOL # vlan hw tso # vlan hwfilter # hardware timestamp (may not be testable) # testjail=testjail checkjail=checkjail # Function to setup jails as needed for tests. setup() { if ! jail -c persist=1 path=/ name=testjail vnet=new vnet.interface="$testiface"; then echo failed to start test jail exit 1 fi if ! jail -c persist=1 path=/ name=checkjail vnet=new vnet.interface="$checkiface"; then echo failed to start check jail exit 1 fi run test ifconfig lo0 up run check ifconfig lo0 up } # Verify that jails are present checkjails() { if ! jls -j "$testjail" >/dev/null 2>/dev/null; then echo "test jail not present." return 1 fi if ! jls -j "$checkjail" >/dev/null 2>/dev/null; then echo "check jail not present." return 1 fi } # Destroy jails cleanup() { jail -r "$testjail" 2>/dev/null jail -r "$checkjail" 2>/dev/null } # Subroutin for down'ing the interface, and removing any addresses from it. # There are issues where if the addresses are not cleared, the same address # won't always work. cleaniface() { jailname="$1" ifacename="$2" run "$jailname" ifconfig $ifacename down run "$jailname" ifconfig $ifacename | grep inet | while read a b c; do if [ x"$a" = x"inet" -o x"$a" = x"inet6" ]; then #if [ x"$a" = x"inet" ]; then run "$jailname" ifconfig $ifacename "$a" "$b" remove fi done run "$jailname" ifconfig $ifacename } # Setup the temp directory, switch to it, and more starttest() { tmpdir=$(mktemp -d -t testiface) if [ $? != 0 ]; then echo "failed to make temp directory" exit 2 fi oldpwd=$(pwd) cd "$tmpdir" run test tcpdump -n -p -i $(getiface test) -w "$tmpdir"/test.tcpdump 2> /dev/null & testdumppid="$!" # XXX - -p should be used on both interfaces run check tcpdump -n -i $(getiface check) -w "$tmpdir"/check.tcpdump 2> /dev/null & checkdumppid="$!" echo "$testdumppid $checkdumppid" } endtest() { echo "$testdumppid" "$checkdumppid" if ! kill "$testdumppid" "$checkdumppid"; then echo "unable to kill off tcpdumps" fi if ! wait "$testdumppid" "$checkdumppid"; then echo "tcpdumps failed to exit" fi cd "$oldpwd" # only attempt to clean up when successful if [ "$1" -eq 0 ]; then echo rm -rf "$tmpdir" else echo "test failed, data in $tmpdir" fi } # Run a command in a jail. The first arg is either check or test. # Remainder of the args are passed for execution. run() { if [ x"$1" = x"check" ]; then jid="$checkjail" elif [ x"$1" = x"test" ]; then jid="$testjail" else echo Invalid: "$1" >&2 exit 1 fi shift jexec "$jid" "$@" } # Return the interface that is in the specified jail. # The first arg is either check or test. getiface() { if [ x"$1" = x"check" ]; then echo "$checkiface" elif [ x"$1" = x"test" ]; then echo "$testiface" else echo Invalid: "$1" >&2 exit 1 fi } # Run ifconfig on the base interface in a jail. # The first arg is either check or test. # Remainder of the args are passed to ifconfig for execution. ifjail() { j="$1" shift iface=$(getiface "$j") run "$j" ifconfig "$iface" "$@" } # Convert an ifconfig option to the options returned by ifconfig iftoopt() { case "$1" in -txcsum6|txcsum6) echo TXCSUM_IPV6 ;; -rxcsum6|rxcsum6) echo RXCSUM_IPV6 ;; -txcsum|txcsum) echo TXCSUM ;; -rxcsum|rxcsum) echo RXCSUM ;; -vlanhwtag|vlanhwtag) echo VLAN_HWTAGGING ;; *) echo "Unknown option: $1" >&2 exit 5 ;; esac } # Get the list of capabilities or options for the interface in the jail. # The first arg is either check or test. # If the second arg is specified, it must be one of cap or opt, # specifying which of the capabilities or options to be returned # respectively. If this arg is not specified, it defaults to # capabilities. getcaps() { if [ x"$2" = x"" -o x"$2" = x"cap" ]; then ifoptcap=capabilities elif [ x"$2" = x"opt" ]; then ifoptcap=options else echo "Invalid getcaps arg: $2" >&2 exit 1 fi run "$1" ifconfig -m $(getiface "$1") | awk '$1 ~ /^'"$ifoptcap"'=/ { split($0, a, "<"); split(a[2], b, ">"); split(b[1], caps, ","); for (i in caps) print caps[i] }' | sort } # Verify that the interface in a jail is capable of the spefified feature. # The first arg is either check or test. # The second arg is the capability as printed in the options line. # This is checked against the capabilities line printed by -m. # Return 0 if present, return 1 if not present hascap() { getcaps "$1" | grep "^$(iftoopt "$2")$" >/dev/null } # Verify that the interface in a jail has a specific capability enabled. # The first arg is either check or test. # The second arg is the capability as printed in the options line. # This is checked against the options line. # Return 0 if present, return 1 if not present verifycap() { if [ x"${2#-}" = x"${2}" ]; then # without leading hyphen, present == success presentretval="0" absentretval="1" else # with leading hyphen, not present == success presentretval="1" absentretval="0" fi if getcaps "$1" opt | grep "^$(iftoopt "$2")$" >/dev/null; then return $presentretval else return $absentretval fi } # Make sure that the carrier on both interface is up before returning. # There is currently no timeout. waitcarrier() { local i for i in test check; do while :; do # make sure carrier is present and any IPv6 addresses # are no longer tentative if ifjail "$i" | grep 'status: active' >/dev/null && ( ! ifjail "$i" | grep 'tentative' >/dev/null); then break fi sleep .5 done done } ################################## ######### START OF TESTS ######### ################################## # # Run tests verify that vlan hardware tagging works (or at least doesn't # break anything. There isn't an easy way to verify that packets in the # kernel get/do not get the mbuf tag. (dtrace MAY be an option) hwvlantest() { local i err err=0 for i in "-vlanhwtag" "vlanhwtag"; do if ! hascap test "$i"; then echo "skipping: $i" continue fi echo "testing: $i" ifjail test "$i" verifycap test "$i" || return 1 ifjail check -vlanhwtag verifycap check -vlanhwtag || return 1 cleaniface test $testiface cleaniface check $checkiface sleep .5 ifjail test up ifjail check up # make sure they don't exist run test ifconfig $testiface.42 destroy 2>/dev/null run check ifconfig $checkiface.42 destroy 2>/dev/null run test ifconfig $testiface.42 create 172.30.5.5/24 run check ifconfig $checkiface.42 create 172.30.5.4/24 waitcarrier #run check tcpdump -XXX -p -q -c 2 -n -i "$checkiface" ip & #run test tcpdump -XXX -p -q -c 2 -n -i "$testiface" ip & #run test ifconfig -a if ! run test ping -o -c 4 -t 5 -i .5 172.30.5.4 >/dev/null; then echo FAILED on "$i"!!!! #run test ifconfig -a err=1 fi #run test ifconfig $testiface.42 destroy #run check ifconfig $checkiface.42 destroy if [ x"$err" != x"0" ]; then return 1 fi done } # Internal function for testing checksum. # The first argument is the flag for the interface under test. # If it ends with a 6, then IPv6 will be used for testing. csuminternal() { local i i="$1" #if ! hascap test "$i"; then # echo "skipping $i, test interface not capable" # continue #fi echo "testing: $i" if [ x"$i" = x"${i%6}" ]; then # IPv4 testnet="172.29.5.5/24" checknet="172.29.5.4/24" checkaddr="172.29.5.4" pingcmd="ping" ncopt="" else # IPv6 testnet="inet6 fc00:0b5d:041c:7e37::7e37/64" checknet="inet6 fc00:0b5d:041c:7e37::c43c/64" checkaddr="fc00:0b5d:041c:7e37::c43c" pingcmd="ping6" ncopt="-6" fi cleaniface test $testiface cleaniface check $checkiface ifjail test "$i" verifycap test "$i" || return 1 ifjail check -txcsum -rxcsum -txcsum6 -rxcsum6 verifycap check "-txcsum" || return 1 verifycap check "-rxcsum" || return 1 sleep .5 ifjail test up ifjail check up run test ifconfig $testiface $testnet run check ifconfig $checkiface $checknet waitcarrier sleep .5 #run check tcpdump -XXX -p -q -n -i "$checkiface" not ip6 & #run test tcpdump -XXX -p -q -n -i "$testiface" not ip6 & #run test ifconfig -a #run check ifconfig -a # make sure connectivity works if ! run test "$pingcmd" -o -c 4 -t 5 -i .5 "$checkaddr" >/dev/null; then ifjail test ifjail check echo ping FAILED on "$i"!!!! return 1 fi mkfifo /tmp/tififo.$$ run check sh -c 'cat /tmp/tififo.'$$' | nc '"$ncopt"' -l 3333 | cat > /tmp/tififo.'$$ 2>/dev/null & sleep .5 value="foobar $$ TCP $i" res=$( (echo "$value"; sleep .5) | run test nc -w 2 -N "$checkaddr" 3333) # We cannot use kill %% to kill the nc -l process as it only # kills jexec, and NOT the jail process # The following is heavy, but should be the only one running run check killall nc >/dev/null 2>/dev/null rm /tmp/tififo.$$ if [ x"$res" != x"$value" ]; then echo TCP FAILED on "$i"!!!! return 1 fi mkfifo /tmp/tififo.$$ run check sh -c 'cat /tmp/tififo.'$$' | nc '"$ncopt"' -u -l 3333 | cat > /tmp/tififo.'$$ 2>/dev/null & sleep .5 value="foobar $$ UDP $i" res=$( (echo "$value"; sleep .5) | run test nc -u -w 2 -N "$checkaddr" 3333) # We cannot use kill %% to kill the nc -l process as it only # kills jexec, and NOT the jail process # The following is heavy, but should be the only one running run check killall nc >/dev/null 2>/dev/null rm /tmp/tififo.$$ if [ x"$res" != x"$value" ]; then echo UDP FAILED on "$i"!!!! return 1 fi # make sure that checksum validation works by sending invalid packets # this includes using dtrace to verify the results from the driver } # Iterate through the various hardware checksum off loading flags. csumtest() { local i #for i in "-rxcsum6" "rxcsum6" "-txcsum6" "txcsum6" "-rxcsum" "rxcsum" "-txcsum" "txcsum"; do for i in "-rxcsum" "rxcsum" "-txcsum" "txcsum"; do if ! csuminternal "$i"; then return 1 fi done } # Verify that packets can be sent and received w/ a larger MTU. # This test is a bit tricky in that it sets both interfaces to the # larger MTU. Better option would be to set both sides to the larger # MTU, but add a route w/ the -mtu specified for the return (or transmit) # side so that we aren't testing both sides at the same time. mtutest() { local i err oldtestmtu=$(ifjail test | awk '{ print $6; exit 0 }') oldcheckmtu=$(ifjail check | awk '{ print $6; exit 0 }') err=0 for i in 1500 1504 2025 4074 7000 8000 9000 9216; do echo "testing mtu: $i" if ! ifjail test mtu "$i" 2>/dev/null; then echo "Failed to set mtu $i on test interface, skipping..." continue fi if ! ifjail check mtu "$i" 2>/dev/null; then echo "Failed to set mtu $i on check interface." echo "Please use different check interface." return 1 fi cleaniface test $testiface cleaniface check $checkiface #run test arp -an #run check arp -an #run test ifconfig $testiface #run check ifconfig $checkiface sleep .5 ifjail test up ifjail check up waitcarrier run test ifconfig $testiface 172.29.5.5/24 run check ifconfig $checkiface 172.29.5.4/24 # Subtract off ethernet header, ICMP header and ethernet CRC if ! run test ping -D -s $(($i - 8 - 20)) -o -c 4 -t 5 -i .5 172.29.5.4 >/dev/null; then echo failed for MTU size $i err=1 break fi done # restore MTU ifjail test mtu $oldtestmtu ifjail check mtu $oldcheckmtu if [ x"$err" != x"0" ]; then return 1 fi } runtest() { test="$1" starttest eval "$test"test res="$?" endtest "$res" if [ "$res" -ne 0 ]; then echo "$test"test failed. exit 1 fi } usage() { echo "Usage: $0 {init,run,deinit,$(echo "$alltests" | sed -e 's/ /,/g')} " echo "" echo "The ifaceundertest is the name of the interface which featres are being" echo "tested." echo "" echo "The checinterface is the name of the interface used to validate that the" echo "test interface operatates correctly. Features of this interface will" echo "be turned off to prevent any of it's hardware offloading capabilities" echo "interfering with the test." } cmd="$1" testiface="$2" checkiface="$3" if [ -z "$cmd" ]; then echo "command must be specified!" echo "" usage exit 1 fi if [ -z "$testiface" -o -z "$checkiface" ]; then echo "both interfaces must be specified!" echo "" usage exit 1 fi alltests="mtu csum hwvlan" case "$cmd" in init) setup ;; mtu|csum|hwvlan) if ! checkjails; then exit 1 fi echo starting $cmd, test $testiface, check $checkiface runtest "$cmd" echo "done" ;; run) if ! checkjails; then exit 1 fi echo starting, test $testiface, check $checkiface for i in $alltests; do runtest "$i" done echo "done" ;; deinit) cleanup ;; *) echo "invalid cmd: $cmd" echo usage exit 1 ;; esac