1#!/bin/sh 2# 3# Copyright (c) 2018-2020 Retina b.v. 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 3. Neither the name of the University nor the names of its contributors 15# may be used to endorse or promote products derived from this software 16# without specific prior written permission. 17# 18# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28# SUCH DAMAGE. 29# 30 31set -e 32 33progname="$(basename $0 .sh)" 34entries_lst="/tmp/$progname.entries.lst" 35entries2_lst="/tmp/$progname.entries2.lst" 36 37HOOKS=3 38HOOKSADD=42 39ITERATIONS=7 40SUBITERATIONS=71 41 42find_iface () { 43 # Figure out the first ethernet interface 44 ifconfig -u -l ether | awk '{print $1}' 45} 46 47loaded_modules='' 48load_modules () { 49 for kmod in "$@"; do 50 if ! kldstat -q -m $kmod; then 51 test_comment "Loading $kmod..." 52 kldload $kmod 53 loaded_modules="$loaded_modules $kmod" 54 fi 55 done 56} 57unload_modules () { 58 for kmod in $loaded_modules; do 59 # These cannot be unloaded 60 test $kmod = 'ng_ether' -o $kmod = 'ng_socket' \ 61 && continue 62 63 test_comment "Unloading $kmod..." 64 kldunload $kmod 65 done 66 loaded_modules='' 67} 68 69configure_nodes () { 70 ngctl mkpeer $eth: macfilter lower ether # Connect the lower hook of the ether instance $eth to the ether hook of a new macfilter instance 71 ngctl name $eth:lower MF # Give the macfilter instance a name 72 ngctl mkpeer $eth: one2many upper one # Connect the upper hook of the ether instance $eth to the one hook of a new one2many instance 73 ngctl name $eth:upper O2M # Give the one2many instance a name 74 ngctl msg O2M: setconfig "{ xmitAlg=3 failAlg=1 enabledLinks=[ 1 1 ] }" # XMIT_FAILOVER -> send replies always out many0 75 76 ngctl connect MF: O2M: default many0 # Connect macfilter:default to the many0 hook of a one2many instance 77 for i in $(seq 1 1 $HOOKS); do 78 ngctl connect MF: O2M: out$i many$i 79 done 80} 81 82deconfigure_nodes () { 83 ngctl shutdown MF: 84 ngctl shutdown O2M: 85} 86 87cleanup () { 88 test_title "Cleaning up" 89 90 deconfigure_nodes 91 unload_modules 92 93 rm -f $entries_lst $entries2_lst 94} 95 96TSTNR=0 97TSTFAILS=0 98TSTSUCCS=0 99 100_test_next () { TSTNR=$((TSTNR + 1)); } 101_test_succ () { TSTSUCCS=$((TSTSUCCS + 1)); } 102_test_fail () { TSTFAILS=$((TSTFAILS + 1)); } 103 104test_cnt () { echo "1..${1:-$TSTNR}"; } 105test_title () { 106 local msg="$1" 107 108 printf '### %s ' "$msg" 109 printf '#%.0s' $(seq $((80 - ${#msg} - 5))) 110 printf "\n" 111} 112test_comment () { echo "# $1"; } 113test_bailout () { echo "Bail out!${1+:- $1}"; exit 1; } 114test_bail_on_fail () { test $TSTFAILS -eq 0 || test_bailout $1; } 115test_ok () { 116 local msg="$1" 117 118 _test_next 119 _test_succ 120 echo "ok $TSTNR - $msg" 121 122 return 0 123} 124test_not_ok () { 125 local msg="$1" 126 127 _test_next 128 _test_fail 129 echo "not ok $TSTNR - $msg" 130 131 return 1 132} 133test_eq () { 134 local v1="$1" v2="$2" msg="$3" 135 136 if [ "$v1" = "$v2" ]; then 137 test_ok "$v1 $msg" 138 else 139 test_not_ok "$v1 vs $v2 $msg" 140 fi 141} 142test_ne () { 143 local v1="$1" v2="$2" msg="$3" 144 145 if [ "$v1" != "$v2" ]; then 146 test_ok "$v1 $msg" 147 else 148 test_not_ok "$v1 vs $v2 $msg" 149 fi 150} 151test_lt () { 152 local v1=$1 v2=$2 msg="$3" 153 154 if [ "$v1" -lt "$v2" ]; then 155 test_ok "$v1 $msg" 156 else 157 test_not_ok "$v1 >= $v2 $msg" 158 fi 159} 160test_le () { 161 local v1=$1 v2=$2 msg="$3" 162 163 if [ "$v1" -le "$v2" ]; then 164 test_ok "$v1 $msg" 165 else 166 test_not_ok "$v1 >= $v2 $msg" 167 fi 168} 169test_gt () { 170 local v1=$1 v2=$2 msg="$3" 171 172 if [ "$v1" -gt "$v2" ]; then 173 test_ok "$v1 $msg" 174 else 175 test_not_ok "$v1 <= $v2 $msg" 176 fi 177} 178test_ge () { 179 local v1=$1 v2=$2 msg="$3" 180 181 if [ "$v1" -ge "$v2" ]; then 182 test_ok "$v1 $msg" 183 else 184 test_not_ok "$v1 <= $v2 $msg" 185 fi 186} 187test_failure () { 188 msg=$1 189 shift 190 if ! "$@"; then 191 test_ok "$msg - \"$@\" failed as expected" 192 else 193 test_not_ok "$msg - expected \"$@\" to fail but succeeded" 194 fi 195} 196test_success () { 197 msg=$1 198 shift 199 if ! "$@"; then 200 test_not_ok "$msg - \"$@\" failed unexpectedly" 201 else 202 test_ok "$msg - \"$@\" succeeded" 203 fi 204} 205 206gethooks () { 207 ngctl msg MF: 'gethooks' \ 208 | perl -ne '$h{$1}=1 while s/hookname="(.*?)"//; sub END {print join(":", sort keys %h)."\n"}' 209} 210 211countmacs () { 212 local hookname=${1:-'[^"]*'} 213 214 ngctl msg MF: 'gethooks' \ 215 | perl -ne 'sub BEGIN {$c=0} $c += $1 while s/hookname="'$hookname'" hookid=\d+ maccnt=(\d+)//; sub END {print "$c\n"}' 216} 217randomedge () { 218 local edge="out$(seq 0 1 $HOOKS | sort -R | head -1)" 219 test $edge = 'out0' \ 220 && echo default \ 221 || echo $edge 222} 223genmac () { 224 echo "00:00:00:00:$(printf "%02x" $1):$(printf "%02x" $2)" 225} 226 227 228 229################################################################################ 230### Start ###################################################################### 231################################################################################ 232 233test_title "Setting up system..." 234load_modules netgraph ng_socket ng_ether ng_macfilter ng_one2many 235eth=$(find_iface) 236if [ -z "$eth" ]; then 237 echo "1..0 # SKIP could not find a valid interface" 238 echo "Available interfaces:" 239 ifconfig 240 exit 1 241fi 242test_comment "Using $eth..." 243 244 245test_title "Configuring netgraph nodes..." 246configure_nodes 247 248trap 'exit 99' 1 2 3 13 14 15 249trap 'cleanup' EXIT 250 251created_hooks=$(gethooks) 252rc=0 253 254# Update this number when adding new tests 255test_cnt 46 256 257 258################################################################################ 259### Tests ###################################################################### 260################################################################################ 261 262################################################################################ 263test_title "Test: Duplicate default hook" 264test_failure "duplicate connect of default hook" ngctl connect MF: O2M: default many99 265 266################################################################################ 267test_title "Test: Add and remove hooks" 268test_success "connect MF:xxx1 to O2M:many$((HOOKS + 1))" ngctl connect MF: O2M: xxx1 many$((HOOKS + 1)) 269test_success "connect MF:xxx2 to O2M:many$((HOOKS + 2))" ngctl connect MF: O2M: xxx2 many$((HOOKS + 2)) 270test_success "connect MF:xxx3 to O2M:many$((HOOKS + 3))" ngctl connect MF: O2M: xxx3 many$((HOOKS + 3)) 271hooks=$(gethooks) 272test_eq $created_hooks:xxx1:xxx2:xxx3 $hooks 'hooks after adding xxx1-3' 273 274test_success "rmhook MF:xxx$i" ngctl rmhook MF: xxx1 275hooks=$(gethooks) 276test_eq $created_hooks:xxx2:xxx3 $hooks 'hooks after removing xxx1' 277test_success "rmhook MF:xxx$i" ngctl rmhook MF: xxx2 278hooks=$(gethooks) 279test_eq $created_hooks:xxx3 $hooks 'hooks after removing xxx2' 280test_success "rmhook MF:xxx$i" ngctl rmhook MF: xxx3 281hooks=$(gethooks) 282test_eq $created_hooks $hooks 'hooks after removing xxx3' 283 284test_bail_on_fail 285 286################################################################################ 287test_title "Test: Add many hooks" 288added_hooks="" 289for i in $(seq 10 1 $HOOKSADD); do 290 added_hooks="$added_hooks:xxx$i" 291 ngctl connect MF: O2M: xxx$i many$((HOOKS + i)) 292done 293hooks=$(gethooks) 294test_eq $created_hooks$added_hooks $hooks 'hooks after adding many hooks' 295 296for h in $(echo $added_hooks | perl -ne 'chomp; %h=map { $_=>1 } split /:/; print "$_\n" for grep {$_} keys %h'); do 297 ngctl rmhook MF: $h 298done 299hooks=$(gethooks) 300test_eq $created_hooks $hooks 'hooks after adding many hooks' 301 302test_bail_on_fail 303 304 305################################################################################ 306test_title "Test: Adding many MACs..." 307I=1 308for i in $(seq $ITERATIONS | sort -R); do 309 test_comment "Iteration $I/$ITERATIONS..." 310 for j in $(seq 0 1 $SUBITERATIONS); do 311 test $i = 2 && edge='out2' || edge='out1' 312 ether=$(genmac $j $i) 313 314 ngctl msg MF: 'direct' "{ hookname=\"$edge\" ether=$ether }" 315 done 316 I=$((I + 1)) 317done 318 319n=$(countmacs out1) 320n2=$(( ( ITERATIONS - 1 ) * ( SUBITERATIONS + 1 ) )) 321test_eq $n $n2 'MACs in table for out1' 322n=$(countmacs out2) 323n2=$(( 1 * ( SUBITERATIONS + 1 ) )) 324test_eq $n $n2 'MACs in table for out2' 325n=$(countmacs out3) 326n2=0 327test_eq $n $n2 'MACs in table for out3' 328 329test_bail_on_fail 330 331 332################################################################################ 333test_title "Test: Changing hooks for MACs..." 334for i in $(seq $ITERATIONS); do 335 edge='out3' 336 ether=$(genmac 0 $i) 337 338 ngctl msg MF: 'direct' "{ hookname=\"$edge\" ether=$ether }" 339done 340 341n=$(countmacs out1) 342n2=$(( ( ITERATIONS - 1 ) * ( SUBITERATIONS + 1 - 1 ) )) 343test_eq $n $n2 'MACs in table for out1' 344n=$(countmacs out2) 345n2=$(( 1 * ( SUBITERATIONS + 1 - 1 ) )) 346test_eq $n $n2 'MACs in table for out2' 347n=$(countmacs out3) 348n2=$ITERATIONS 349test_eq $n $n2 'MACs in table for out3' 350 351test_bail_on_fail 352 353 354################################################################################ 355test_title "Test: Removing all MACs one by one..." 356I=1 357for i in $(seq $ITERATIONS | sort -R); do 358 test_comment "Iteration $I/$ITERATIONS..." 359 for j in $(seq 0 1 $SUBITERATIONS | sort -R); do 360 edge="default" 361 ether=$(genmac $j $i) 362 363 ngctl msg MF: 'direct' "{ hookname=\"$edge\" ether=$ether }" 364 done 365 I=$(($I + 1)) 366done 367n=$(countmacs) 368n2=0 369test_eq $n $n2 'MACs in table' 370 371test_bail_on_fail 372 373 374################################################################################ 375test_title "Test: Randomly adding MACs on random hooks..." 376rm -f $entries_lst 377for i in $(seq $ITERATIONS); do 378 test_comment "Iteration $i/$ITERATIONS..." 379 for j in $(seq 0 1 $SUBITERATIONS | sort -R); do 380 edge=$(randomedge) 381 ether=$(genmac $j $i) 382 383 ngctl msg MF: 'direct' "{ hookname=\"$edge\" ether=$ether }" 384 385 echo $ether $edge >> $entries_lst 386 done 387 388 n=$(countmacs) 389done 390 391n=$(countmacs out1) 392n2=$(grep -c ' out1' $entries_lst) 393test_eq $n $n2 'MACs in table for out1' 394n=$(countmacs out2) 395n2=$(grep -c ' out2' $entries_lst) 396test_eq $n $n2 'MACs in table for out2' 397n=$(countmacs out3) 398n2=$(grep -c ' out3' $entries_lst) 399test_eq $n $n2 'MACs in table for out3' 400 401test_bail_on_fail 402 403 404################################################################################ 405test_title "Test: Randomly changing MAC assignments..." 406rm -f $entries2_lst 407for i in $(seq $ITERATIONS); do 408 test_comment "Iteration $i/$ITERATIONS..." 409 cat $entries_lst | while read ether edge; do 410 edge2=$(randomedge) 411 412 ngctl msg MF: 'direct' "{ hookname=\"$edge2\" ether=$ether }" 413 414 echo $ether $edge2 >> $entries2_lst 415 done 416 417 n=$(countmacs out1) 418 n2=$(grep -c ' out1' $entries2_lst) 419 test_eq $n $n2 'MACs in table for out1' 420 n=$(countmacs out2) 421 n2=$(grep -c ' out2' $entries2_lst) 422 test_eq $n $n2 'MACs in table for out2' 423 n=$(countmacs out3) 424 n2=$(grep -c ' out3' $entries2_lst) 425 test_eq $n $n2 'MACs in table for out3' 426 427 test_bail_on_fail 428 429 mv $entries2_lst $entries_lst 430done 431 432 433################################################################################ 434test_title "Test: Resetting macfilter..." 435test_success "**** reset failed" ngctl msg MF: reset 436test_eq $(countmacs) 0 'MACs in table' 437 438test_bail_on_fail 439 440 441################################################################################ 442 443exit 0 444