#!/bin/bash # NFT_TEST_SKIP(NFT_TEST_SKIP_slow) runtime=30 # allow stand-alone execution as well, e.g. '$0 3600' if [ x"$1" != "x" ] ;then if [ $1 -ge 0 ]; then runtime="$1" else echo "Invalid runtime $1" exit 1 fi fi if [ x = x"$NFT" ] ; then NFT=nft fi if [ "$NFT_TEST_HAS_SOCKET_LIMITS" = y ] ; then # The socket limit /proc/sys/net/core/wmem_max may be unsuitable for # the test. # # Skip it. You may ensure that the limits are suitable and rerun # with NFT_TEST_HAS_SOCKET_LIMITS=n. exit 77 fi if [ -z "${NFT_TEST_HAVE_chain_binding+x}" ] ; then NFT_TEST_HAVE_chain_binding=n mydir="$(dirname "$0")" $NFT --check -f "$mydir/../../features/chain_binding.nft" if [ $? -eq 0 ];then NFT_TEST_HAVE_chain_binding=y else echo "Assuming anonymous chains are not supported" fi fi testns=testns-$(mktemp -u "XXXXXXXX") tmp="" faultname="/proc/self/make-it-fail" tables="foo bar" failslab_defaults() { test -w $faultname || return # Disable fault injection unless process has 'make-it-fail' set echo Y > /sys/kernel/debug/failslab/task-filter # allow all slabs to fail (if process is tagged). find /sys/kernel/slab/ -wholename '*/kmalloc-[0-9]*/failslab' -type f -exec sh -c 'echo 1 > {}' \; # no limit on the number of failures, or clause works around old kernels that reject negative integer. echo -1 > /sys/kernel/debug/failslab/times 2>/dev/null || printf '%#x -1' > /sys/kernel/debug/failslab/times # Set to 2 for full dmesg traces for each injected error echo 0 > /sys/kernel/debug/failslab/verbose } failslab_random() { r=$((RANDOM%2)) if [ $r -eq 0 ]; then echo Y > /sys/kernel/debug/failslab/ignore-gfp-wait else echo N > /sys/kernel/debug/failslab/ignore-gfp-wait fi r=$((RANDOM%5)) echo $r > /sys/kernel/debug/failslab/probability r=$((RANDOM%100)) echo $r > /sys/kernel/debug/failslab/interval # allow a small initial 'success budget'. # failures only appear after this many allocated bytes. r=$((RANDOM%16384)) echo $r > /sys/kernel/debug/$FAILTYPE/space } netns_del() { ip netns pids "$testns" | xargs kill 2>/dev/null ip netns del "$testns" } netns_add() { ip netns add "$testns" ip -netns "$testns" link set lo up } cleanup() { [ "$tmp" = "" ] || rm -f "$tmp" netns_del } nft_with_fault_inject() { file="$1" if [ -w "$faultname" ]; then failslab_random ip netns exec "$testns" bash -c "echo 1 > $faultname ; exec $NFT -f $file" fi ip netns exec "$testns" $NFT -f "$file" } trap cleanup EXIT tmp=$(mktemp) jump_or_goto() { if [ $((RANDOM & 1)) -eq 0 ] ;then echo -n "jump" else echo -n "goto" fi } random_verdict() { max="$1" if [ $max -eq 0 ]; then max=1 fi rnd=$((RANDOM%max)) if [ $rnd -gt 0 ];then jump_or_goto printf " chain%03u" "$((rnd+1))" return fi if [ $((RANDOM & 1)) -eq 0 ] ;then echo "accept" else echo "drop" fi } randsleep() { local s=$((RANDOM%1)) local ms=$((RANDOM%1000)) sleep $s.$ms } randlist() { while [ -r $tmp ]; do randsleep ip netns exec $testns $NFT list ruleset > /dev/null done } randflush() { while [ -r $tmp ]; do randsleep ip netns exec $testns $NFT flush ruleset > /dev/null done } randdeltable() { while [ -r $tmp ]; do randsleep for t in $tables; do r=$((RANDOM%10)) if [ $r -eq 1 ] ;then ip netns exec $testns $NFT delete table inet $t randsleep fi done done } randdelset() { while [ -r $tmp ]; do randsleep for t in $tables; do r=$((RANDOM%10)) s=$((RANDOM%10)) case $r in 0) setname=set_$s ;; 1) setname=sett${s} ;; 2) setname=dmap_${s} ;; 3) setname=dmapt${s} ;; 4) setname=vmap_${s} ;; 5) setname=vmapt${s} ;; *) continue ;; esac if [ $r -eq 1 ] ;then ip netns exec $testns $NFT delete set inet $t $setname fi done done } randdelchain() { while [ -r $tmp ]; do for t in $tables; do local c=$((RANDOM%100)) randsleep chain=$(printf "chain%03u" "$c") local r=$((RANDOM%10)) if [ $r -eq 1 ];then # chain can be invalid/unknown. ip netns exec $testns $NFT delete chain inet $t $chain fi done done } randdisable() { while [ -r $tmp ]; do for t in $tables; do randsleep local r=$((RANDOM%10)) if [ $r -eq 1 ];then ip netns exec $testns $NFT add table inet $t '{flags dormant; }' randsleep ip netns exec $testns $NFT add table inet $t '{ }' fi done done } randdelns() { while [ -r $tmp ]; do randsleep netns_del netns_add randsleep done } random_element_string="" # create a random element. Could cause any of the following: # 1. Invalid set/map # 2. Element already exists in set/map w. create # 3. Element is new but wants to jump to unknown chain # 4. Element already exsists in set/map w. add, but verdict (map data) differs # 5. Element is created/added/deleted from 'flags constant' set. random_elem() { tr=$((RANDOM%2)) t=0 for table in $tables; do if [ $t -ne $tr ]; then t=$((t+1)) continue fi kr=$((RANDOM%2)) k=0 cnt=0 for key in "single" "concat"; do if [ $k -ne $kr ] ;then cnt=$((cnt+2)) k=$((k+1)) continue fi fr=$((RANDOM%2)) f=0 for flags in "" "interval" ; do cnt=$((cnt+1)) if [ $f -ne fkr ] ;then f=$((f+1)) continue fi want="${key}${flags}" e=$((RANDOM%256)) case "$want" in "single") element="10.1.1.$e" ;; "concat") element="10.1.2.$e . $((RANDOM%65536))" ;; "singleinterval") element="10.1.$e.0-10.1.$e.$e" ;; "concatinterval") element="10.1.$e.0-10.1.$e.$e . $((RANDOM%65536))" ;; *) echo "bogus key $want" exit 111 ;; esac # This may result in invalid jump, but thats what we want. count=$(($RANDOM%100)) r=$((RANDOM%7)) case "$r" in 0) random_element_string=" inet $table set_${cnt} { $element }" ;; 1) random_element_string="inet $table sett${cnt} { $element timeout $((RANDOM%60))s }" ;; 2) random_element_string="inet $table dmap_${cnt} { $element : $RANDOM }" ;; 3) random_element_string="inet $table dmapt${cnt} { $element timeout $((RANDOM%60))s : $RANDOM }" ;; 4) random_element_string="inet $table vmap_${cnt} { $element : `random_verdict $count` }" ;; 5) random_element_string="inet $table vmapt${cnt} { $element timeout $((RANDOM%60))s : `random_verdict $count` }" ;; 6) random_element_string="inet $table setc${cnt} { $element }" ;; esac return done done done } randload() { while [ -r $tmp ]; do random_element_string="" r=$((RANDOM%10)) what="" case $r in 1) (echo "flush ruleset"; cat "$tmp" echo "insert rule inet foo INPUT meta nftrace set 1" echo "insert rule inet foo OUTPUT meta nftrace set 1" ) | nft_with_fault_inject "/dev/stdin" ;; 2) what="add" ;; 3) what="create" ;; 4) what="delete" ;; 5) what="destroy" ;; 6) what="get" ;; *) randsleep ;; esac if [ x"$what" = "x" ]; then nft_with_fault_inject "$tmp" else # This can trigger abort path, for various reasons: # invalid set name # key mismatches set specification (concat vs. single value) # attempt to delete non-existent key # attempt to create dupliacte key # attempt to add duplicate key with non-matching value (data) # attempt to add new uniqeue key with a jump to an unknown chain random_elem ( cat "$tmp"; echo "$what element $random_element_string") | nft_with_fault_inject "/dev/stdin" fi done } randmonitor() { while [ -r $tmp ]; do randsleep timeout=$((RANDOM%16)) timeout $((timeout+1)) $NFT monitor > /dev/null done } floodping() { cpunum=$(grep -c processor /proc/cpuinfo) cpunum=$((cpunum+1)) while [ -r $tmp ]; do spawn=$((RANDOM%$cpunum)) # spawn at most $cpunum processes. Or maybe none at all. i=0 while [ $i -lt $spawn ]; do mask=$(printf 0x%x $((1<<$i))) timeout 3 ip netns exec "$testns" taskset $mask ping -4 -fq 127.0.0.1 > /dev/null & timeout 3 ip netns exec "$testns" taskset $mask ping -6 -fq ::1 > /dev/null & i=$((i+1)) done wait randsleep done } stress_all() { # if fault injection is enabled, first a quick test to trigger # abort paths without any parallel deletes/flushes. if [ -w $faultname ] ;then for i in $(seq 1 10);do nft_with_fault_inject "$tmp" done fi randlist & randflush & randdeltable & randdisable & randdelchain & randdelset & randdelns & randload & randmonitor & } gen_anon_chain_jump() { echo -n "insert rule inet $@ " jump_or_goto if [ "$NFT_TEST_HAVE_chain_binding" = n ] ; then echo " defaultchain" return fi echo -n " { " jump_or_goto echo " defaultchain; counter; }" } gen_ruleset() { echo > "$tmp" for table in $tables; do count=$((RANDOM % 100)) if [ $count -lt 1 ];then count=1 fi echo add table inet "$table" >> "$tmp" echo flush table inet "$table" >> "$tmp" echo "add chain inet $table INPUT { type filter hook input priority 0; }" >> "$tmp" echo "add chain inet $table OUTPUT { type filter hook output priority 0; }" >> "$tmp" for c in $(seq 1 $count); do chain=$(printf "chain%03u" "$c") echo "add chain inet $table $chain" >> "$tmp" done echo "add chain inet $table defaultchain" >> "$tmp" for c in $(seq 1 $count); do chain=$(printf "chain%03u" "$c") for BASE in INPUT OUTPUT; do echo "add rule inet $table $BASE counter jump $chain" >> "$tmp" done if [ $((RANDOM%10)) -eq 1 ];then echo "add rule inet $table $chain counter jump defaultchain" >> "$tmp" else echo "add rule inet $table $chain counter return" >> "$tmp" fi done cnt=0 # add a few anonymous sets. rhashtable is convered by named sets below. c=$((RANDOM%$count)) chain=$(printf "chain%03u" "$((c+1))") echo "insert rule inet $table $chain tcp dport 22-26 ip saddr { 1.2.3.4, 5.6.7.8 } counter comment hash_fast" >> "$tmp" echo "insert rule inet $table $chain ip6 saddr { ::1, dead::beef } counter" comment hash >> "$tmp" echo "insert rule inet $table $chain ip saddr { 1.2.3.4 - 5.6.7.8, 127.0.0.1 } comment rbtree" >> "$tmp" # bitmap 1byte, with anon chain jump gen_anon_chain_jump "$table $chain ip protocol { 6, 17 }" >> "$tmp" # bitmap 2byte echo "insert rule inet $table $chain tcp dport != { 22, 23, 80 } goto defaultchain" >> "$tmp" echo "insert rule inet $table $chain tcp dport { 1-1024, 8000-8080 } jump defaultchain comment rbtree" >> "$tmp" # pipapo (concat + set), with goto anonymous chain. gen_anon_chain_jump "$table $chain ip saddr . tcp dport { 1.2.3.4 . 1-1024, 1.2.3.6 - 1.2.3.10 . 8000-8080, 1.2.3.4 . 8080, 1.2.3.6 - 1.2.3.10 . 22 }" >> "$tmp" # add a few anonymous sets. rhashtable is convered by named sets below. c=$((RANDOM%$count)) chain=$(printf "chain%03u" "$((c+1))") echo "insert rule inet $table $chain tcp dport 22-26 ip saddr { 1.2.3.4, 5.6.7.8 } counter comment hash_fast" >> "$tmp" echo "insert rule inet $table $chain ip6 saddr { ::1, dead::beef } counter" comment hash >> "$tmp" echo "insert rule inet $table $chain ip saddr { 1.2.3.4 - 5.6.7.8, 127.0.0.1 } comment rbtree" >> "$tmp" # bitmap 1byte, with anon chain jump gen_anon_chain_jump "$table $chain ip protocol { 6, 17 }" >> "$tmp" # bitmap 2byte echo "insert rule inet $table $chain tcp dport != { 22, 23, 80 } goto defaultchain" >> "$tmp" echo "insert rule inet $table $chain tcp dport { 1-1024, 8000-8080 } jump defaultchain comment rbtree" >> "$tmp" # pipapo (concat + set), with goto anonymous chain. gen_anon_chain_jump "$table $chain ip saddr . tcp dport { 1.2.3.4 . 1-1024, 1.2.3.6 - 1.2.3.10 . 8000-8080, 1.2.3.4 . 8080, 1.2.3.6 - 1.2.3.10 . 22 }" >> "$tmp" # add constant/immutable sets size=$((RANDOM%5120000)) size=$((size+2)) echo "add set inet $table setc1 { typeof tcp dport; size $size; flags constant; elements = { 22, 44 } }" >> "$tmp" echo "add set inet $table setc2 { typeof ip saddr; size $size; flags constant; elements = { 1.2.3.4, 5.6.7.8 } }" >> "$tmp" echo "add set inet $table setc3 { typeof ip6 daddr; size $size; flags constant; elements = { ::1, dead::1 } }" >> "$tmp" echo "add set inet $table setc4 { typeof tcp dport; size $size; flags interval,constant; elements = { 22-44, 55-66 } }" >> "$tmp" echo "add set inet $table setc5 { typeof ip saddr; size $size; flags interval,constant; elements = { 1.2.3.4-5.6.7.8, 10.1.1.1 } }" >> "$tmp" echo "add set inet $table setc6 { typeof ip6 daddr; size $size; flags interval,constant; elements = { ::1, dead::1-dead::3 } }" >> "$tmp" # add named sets with various combinations (plain value, range, concatenated values, concatenated ranges, with timeouts, with data ...) for key in "ip saddr" "ip saddr . tcp dport"; do for flags in "" "flags interval;" ; do timeout=$((RANDOM%10)) timeout=$((timeout+1)) timeout="timeout ${timeout}s" cnt=$((cnt+1)) echo "add set inet $table set_${cnt} { typeof ${key} ; ${flags} }" >> "$tmp" echo "add set inet $table sett${cnt} { typeof ${key} ; $timeout; ${flags} }" >> "$tmp" echo "add map inet $table dmap_${cnt} { typeof ${key} : meta mark ; ${flags} }" >> "$tmp" echo "add map inet $table dmapt${cnt} { typeof ${key} : meta mark ; $timeout ; ${flags} }" >> "$tmp" echo "add map inet $table vmap_${cnt} { typeof ${key} : verdict ; ${flags} }" >> "$tmp" echo "add map inet $table vmapt${cnt} { typeof ${key} : verdict; $timeout ; ${flags} }" >> "$tmp" done done cnt=0 for key in "single" "concat"; do for flags in "" "interval" ; do want="${key}${flags}" cnt=$((cnt+1)) maxip=$((RANDOM%256)) if [ $maxip -eq 0 ];then maxip=1 fi for e in $(seq 1 $maxip);do case "$want" in "single") element="10.1.1.$e" ;; "concat") element="10.1.2.$e . $((RANDOM%65536))" ;; "singleinterval") element="10.1.$e.0-10.1.$e.$e" ;; "concatinterval") element="10.1.$e.0-10.1.$e.$e . $((RANDOM%65536))" ;; *) echo "bogus key $want" exit 111 ;; esac echo "add element inet $table set_${cnt} { $element }" >> "$tmp" echo "add element inet $table sett${cnt} { $element timeout $((RANDOM%60))s }" >> "$tmp" echo "add element inet $table dmap_${cnt} { $element : $RANDOM }" >> "$tmp" echo "add element inet $table dmapt${cnt} { $element timeout $((RANDOM%60))s : $RANDOM }" >> "$tmp" echo "add element inet $table vmap_${cnt} { $element : `random_verdict $count` }" >> "$tmp" echo "add element inet $table vmapt${cnt} { $element timeout $((RANDOM%60))s : `random_verdict $count` }" >> "$tmp" done done done done } run_test() { local time_now=$(date +%s) local time_stop=$((time_now + $runtime)) local regen=30 while [ $time_now -lt $time_stop ]; do if [ $regen -gt 0 ];then sleep 1 time_now=$(date +%s) regen=$((regen-1)) continue fi # This clobbers the previously generated ruleset, this is intentional. gen_ruleset regen=$((RANDOM%60)) regen=$((regen+2)) time_now=$(date +%s) done } netns_add gen_ruleset ip netns exec "$testns" $NFT -f "$tmp" || exit 1 failslab_defaults stress_all 2>/dev/null & randsleep floodping 2> /dev/null & run_test # this stops stress_all rm -f "$tmp" tmp="" sleep 4 if [ "$NFT_TEST_HAVE_chain_binding" = n ] ; then echo "Ran a modified version of the test due to NFT_TEST_HAVE_chain_binding=n" fi