selftests: forwarding: lib: add netns support for tc rule handle stats get
[linux-block.git] / tools / testing / selftests / net / forwarding / lib.sh
1 #!/bin/bash
2 # SPDX-License-Identifier: GPL-2.0
3
4 ##############################################################################
5 # Defines
6
7 # Kselftest framework requirement - SKIP code is 4.
8 ksft_skip=4
9
10 # Can be overridden by the configuration file.
11 PING=${PING:=ping}
12 PING6=${PING6:=ping6}
13 MZ=${MZ:=mausezahn}
14 ARPING=${ARPING:=arping}
15 TEAMD=${TEAMD:=teamd}
16 WAIT_TIME=${WAIT_TIME:=5}
17 PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
18 PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no}
19 NETIF_TYPE=${NETIF_TYPE:=veth}
20 NETIF_CREATE=${NETIF_CREATE:=yes}
21 MCD=${MCD:=smcrouted}
22 MC_CLI=${MC_CLI:=smcroutectl}
23 PING_COUNT=${PING_COUNT:=10}
24 PING_TIMEOUT=${PING_TIMEOUT:=5}
25 WAIT_TIMEOUT=${WAIT_TIMEOUT:=20}
26 INTERFACE_TIMEOUT=${INTERFACE_TIMEOUT:=600}
27 LOW_AGEING_TIME=${LOW_AGEING_TIME:=1000}
28 REQUIRE_JQ=${REQUIRE_JQ:=yes}
29 REQUIRE_MZ=${REQUIRE_MZ:=yes}
30 REQUIRE_MTOOLS=${REQUIRE_MTOOLS:=no}
31 STABLE_MAC_ADDRS=${STABLE_MAC_ADDRS:=no}
32 TCPDUMP_EXTRA_FLAGS=${TCPDUMP_EXTRA_FLAGS:=}
33
34 relative_path="${BASH_SOURCE%/*}"
35 if [[ "$relative_path" == "${BASH_SOURCE}" ]]; then
36         relative_path="."
37 fi
38
39 if [[ -f $relative_path/forwarding.config ]]; then
40         source "$relative_path/forwarding.config"
41 fi
42
43 ##############################################################################
44 # Sanity checks
45
46 check_tc_version()
47 {
48         tc -j &> /dev/null
49         if [[ $? -ne 0 ]]; then
50                 echo "SKIP: iproute2 too old; tc is missing JSON support"
51                 exit $ksft_skip
52         fi
53 }
54
55 # Old versions of tc don't understand "mpls_uc"
56 check_tc_mpls_support()
57 {
58         local dev=$1; shift
59
60         tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
61                 matchall action pipe &> /dev/null
62         if [[ $? -ne 0 ]]; then
63                 echo "SKIP: iproute2 too old; tc is missing MPLS support"
64                 return $ksft_skip
65         fi
66         tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
67                 matchall
68 }
69
70 # Old versions of tc produce invalid json output for mpls lse statistics
71 check_tc_mpls_lse_stats()
72 {
73         local dev=$1; shift
74         local ret;
75
76         tc filter add dev $dev ingress protocol mpls_uc pref 1 handle 1 \
77                 flower mpls lse depth 2                                 \
78                 action continue &> /dev/null
79
80         if [[ $? -ne 0 ]]; then
81                 echo "SKIP: iproute2 too old; tc-flower is missing extended MPLS support"
82                 return $ksft_skip
83         fi
84
85         tc -j filter show dev $dev ingress protocol mpls_uc | jq . &> /dev/null
86         ret=$?
87         tc filter del dev $dev ingress protocol mpls_uc pref 1 handle 1 \
88                 flower
89
90         if [[ $ret -ne 0 ]]; then
91                 echo "SKIP: iproute2 too old; tc-flower produces invalid json output for extended MPLS filters"
92                 return $ksft_skip
93         fi
94 }
95
96 check_tc_shblock_support()
97 {
98         tc filter help 2>&1 | grep block &> /dev/null
99         if [[ $? -ne 0 ]]; then
100                 echo "SKIP: iproute2 too old; tc is missing shared block support"
101                 exit $ksft_skip
102         fi
103 }
104
105 check_tc_chain_support()
106 {
107         tc help 2>&1|grep chain &> /dev/null
108         if [[ $? -ne 0 ]]; then
109                 echo "SKIP: iproute2 too old; tc is missing chain support"
110                 exit $ksft_skip
111         fi
112 }
113
114 check_tc_action_hw_stats_support()
115 {
116         tc actions help 2>&1 | grep -q hw_stats
117         if [[ $? -ne 0 ]]; then
118                 echo "SKIP: iproute2 too old; tc is missing action hw_stats support"
119                 exit $ksft_skip
120         fi
121 }
122
123 check_tc_fp_support()
124 {
125         tc qdisc add dev lo mqprio help 2>&1 | grep -q "fp "
126         if [[ $? -ne 0 ]]; then
127                 echo "SKIP: iproute2 too old; tc is missing frame preemption support"
128                 exit $ksft_skip
129         fi
130 }
131
132 check_ethtool_lanes_support()
133 {
134         ethtool --help 2>&1| grep lanes &> /dev/null
135         if [[ $? -ne 0 ]]; then
136                 echo "SKIP: ethtool too old; it is missing lanes support"
137                 exit $ksft_skip
138         fi
139 }
140
141 check_ethtool_mm_support()
142 {
143         ethtool --help 2>&1| grep -- '--show-mm' &> /dev/null
144         if [[ $? -ne 0 ]]; then
145                 echo "SKIP: ethtool too old; it is missing MAC Merge layer support"
146                 exit $ksft_skip
147         fi
148 }
149
150 check_locked_port_support()
151 {
152         if ! bridge -d link show | grep -q " locked"; then
153                 echo "SKIP: iproute2 too old; Locked port feature not supported."
154                 return $ksft_skip
155         fi
156 }
157
158 check_port_mab_support()
159 {
160         if ! bridge -d link show | grep -q "mab"; then
161                 echo "SKIP: iproute2 too old; MacAuth feature not supported."
162                 return $ksft_skip
163         fi
164 }
165
166 if [[ "$(id -u)" -ne 0 ]]; then
167         echo "SKIP: need root privileges"
168         exit $ksft_skip
169 fi
170
171 if [[ "$CHECK_TC" = "yes" ]]; then
172         check_tc_version
173 fi
174
175 require_command()
176 {
177         local cmd=$1; shift
178
179         if [[ ! -x "$(command -v "$cmd")" ]]; then
180                 echo "SKIP: $cmd not installed"
181                 exit $ksft_skip
182         fi
183 }
184
185 if [[ "$REQUIRE_JQ" = "yes" ]]; then
186         require_command jq
187 fi
188 if [[ "$REQUIRE_MZ" = "yes" ]]; then
189         require_command $MZ
190 fi
191 if [[ "$REQUIRE_MTOOLS" = "yes" ]]; then
192         # https://github.com/vladimiroltean/mtools/
193         # patched for IPv6 support
194         require_command msend
195         require_command mreceive
196 fi
197
198 if [[ ! -v NUM_NETIFS ]]; then
199         echo "SKIP: importer does not define \"NUM_NETIFS\""
200         exit $ksft_skip
201 fi
202
203 ##############################################################################
204 # Command line options handling
205
206 count=0
207
208 while [[ $# -gt 0 ]]; do
209         if [[ "$count" -eq "0" ]]; then
210                 unset NETIFS
211                 declare -A NETIFS
212         fi
213         count=$((count + 1))
214         NETIFS[p$count]="$1"
215         shift
216 done
217
218 ##############################################################################
219 # Network interfaces configuration
220
221 create_netif_veth()
222 {
223         local i
224
225         for ((i = 1; i <= NUM_NETIFS; ++i)); do
226                 local j=$((i+1))
227
228                 ip link show dev ${NETIFS[p$i]} &> /dev/null
229                 if [[ $? -ne 0 ]]; then
230                         ip link add ${NETIFS[p$i]} type veth \
231                                 peer name ${NETIFS[p$j]}
232                         if [[ $? -ne 0 ]]; then
233                                 echo "Failed to create netif"
234                                 exit 1
235                         fi
236                 fi
237                 i=$j
238         done
239 }
240
241 create_netif()
242 {
243         case "$NETIF_TYPE" in
244         veth) create_netif_veth
245               ;;
246         *) echo "Can not create interfaces of type \'$NETIF_TYPE\'"
247            exit 1
248            ;;
249         esac
250 }
251
252 declare -A MAC_ADDR_ORIG
253 mac_addr_prepare()
254 {
255         local new_addr=
256         local dev=
257
258         for ((i = 1; i <= NUM_NETIFS; ++i)); do
259                 dev=${NETIFS[p$i]}
260                 new_addr=$(printf "00:01:02:03:04:%02x" $i)
261
262                 MAC_ADDR_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].address')
263                 # Strip quotes
264                 MAC_ADDR_ORIG["$dev"]=${MAC_ADDR_ORIG["$dev"]//\"/}
265                 ip link set dev $dev address $new_addr
266         done
267 }
268
269 mac_addr_restore()
270 {
271         local dev=
272
273         for ((i = 1; i <= NUM_NETIFS; ++i)); do
274                 dev=${NETIFS[p$i]}
275                 ip link set dev $dev address ${MAC_ADDR_ORIG["$dev"]}
276         done
277 }
278
279 if [[ "$NETIF_CREATE" = "yes" ]]; then
280         create_netif
281 fi
282
283 if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then
284         mac_addr_prepare
285 fi
286
287 for ((i = 1; i <= NUM_NETIFS; ++i)); do
288         ip link show dev ${NETIFS[p$i]} &> /dev/null
289         if [[ $? -ne 0 ]]; then
290                 echo "SKIP: could not find all required interfaces"
291                 exit $ksft_skip
292         fi
293 done
294
295 ##############################################################################
296 # Helpers
297
298 # Exit status to return at the end. Set in case one of the tests fails.
299 EXIT_STATUS=0
300 # Per-test return value. Clear at the beginning of each test.
301 RET=0
302
303 check_err()
304 {
305         local err=$1
306         local msg=$2
307
308         if [[ $RET -eq 0 && $err -ne 0 ]]; then
309                 RET=$err
310                 retmsg=$msg
311         fi
312 }
313
314 check_fail()
315 {
316         local err=$1
317         local msg=$2
318
319         if [[ $RET -eq 0 && $err -eq 0 ]]; then
320                 RET=1
321                 retmsg=$msg
322         fi
323 }
324
325 check_err_fail()
326 {
327         local should_fail=$1; shift
328         local err=$1; shift
329         local what=$1; shift
330
331         if ((should_fail)); then
332                 check_fail $err "$what succeeded, but should have failed"
333         else
334                 check_err $err "$what failed"
335         fi
336 }
337
338 log_test()
339 {
340         local test_name=$1
341         local opt_str=$2
342
343         if [[ $# -eq 2 ]]; then
344                 opt_str="($opt_str)"
345         fi
346
347         if [[ $RET -ne 0 ]]; then
348                 EXIT_STATUS=1
349                 printf "TEST: %-60s  [FAIL]\n" "$test_name $opt_str"
350                 if [[ ! -z "$retmsg" ]]; then
351                         printf "\t%s\n" "$retmsg"
352                 fi
353                 if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
354                         echo "Hit enter to continue, 'q' to quit"
355                         read a
356                         [ "$a" = "q" ] && exit 1
357                 fi
358                 return 1
359         fi
360
361         printf "TEST: %-60s  [ OK ]\n" "$test_name $opt_str"
362         return 0
363 }
364
365 log_test_skip()
366 {
367         local test_name=$1
368         local opt_str=$2
369
370         printf "TEST: %-60s  [SKIP]\n" "$test_name $opt_str"
371         return 0
372 }
373
374 log_info()
375 {
376         local msg=$1
377
378         echo "INFO: $msg"
379 }
380
381 busywait()
382 {
383         local timeout=$1; shift
384
385         local start_time="$(date -u +%s%3N)"
386         while true
387         do
388                 local out
389                 out=$("$@")
390                 local ret=$?
391                 if ((!ret)); then
392                         echo -n "$out"
393                         return 0
394                 fi
395
396                 local current_time="$(date -u +%s%3N)"
397                 if ((current_time - start_time > timeout)); then
398                         echo -n "$out"
399                         return 1
400                 fi
401         done
402 }
403
404 not()
405 {
406         "$@"
407         [[ $? != 0 ]]
408 }
409
410 get_max()
411 {
412         local arr=("$@")
413
414         max=${arr[0]}
415         for cur in ${arr[@]}; do
416                 if [[ $cur -gt $max ]]; then
417                         max=$cur
418                 fi
419         done
420
421         echo $max
422 }
423
424 grep_bridge_fdb()
425 {
426         local addr=$1; shift
427         local word
428         local flag
429
430         if [ "$1" == "self" ] || [ "$1" == "master" ]; then
431                 word=$1; shift
432                 if [ "$1" == "-v" ]; then
433                         flag=$1; shift
434                 fi
435         fi
436
437         $@ | grep $addr | grep $flag "$word"
438 }
439
440 wait_for_port_up()
441 {
442         "$@" | grep -q "Link detected: yes"
443 }
444
445 wait_for_offload()
446 {
447         "$@" | grep -q offload
448 }
449
450 wait_for_trap()
451 {
452         "$@" | grep -q trap
453 }
454
455 until_counter_is()
456 {
457         local expr=$1; shift
458         local current=$("$@")
459
460         echo $((current))
461         ((current $expr))
462 }
463
464 busywait_for_counter()
465 {
466         local timeout=$1; shift
467         local delta=$1; shift
468
469         local base=$("$@")
470         busywait "$timeout" until_counter_is ">= $((base + delta))" "$@"
471 }
472
473 setup_wait_dev()
474 {
475         local dev=$1; shift
476         local wait_time=${1:-$WAIT_TIME}; shift
477
478         setup_wait_dev_with_timeout "$dev" $INTERFACE_TIMEOUT $wait_time
479
480         if (($?)); then
481                 check_err 1
482                 log_test setup_wait_dev ": Interface $dev does not come up."
483                 exit 1
484         fi
485 }
486
487 setup_wait_dev_with_timeout()
488 {
489         local dev=$1; shift
490         local max_iterations=${1:-$WAIT_TIMEOUT}; shift
491         local wait_time=${1:-$WAIT_TIME}; shift
492         local i
493
494         for ((i = 1; i <= $max_iterations; ++i)); do
495                 ip link show dev $dev up \
496                         | grep 'state UP' &> /dev/null
497                 if [[ $? -ne 0 ]]; then
498                         sleep 1
499                 else
500                         sleep $wait_time
501                         return 0
502                 fi
503         done
504
505         return 1
506 }
507
508 setup_wait()
509 {
510         local num_netifs=${1:-$NUM_NETIFS}
511         local i
512
513         for ((i = 1; i <= num_netifs; ++i)); do
514                 setup_wait_dev ${NETIFS[p$i]} 0
515         done
516
517         # Make sure links are ready.
518         sleep $WAIT_TIME
519 }
520
521 cmd_jq()
522 {
523         local cmd=$1
524         local jq_exp=$2
525         local jq_opts=$3
526         local ret
527         local output
528
529         output="$($cmd)"
530         # it the command fails, return error right away
531         ret=$?
532         if [[ $ret -ne 0 ]]; then
533                 return $ret
534         fi
535         output=$(echo $output | jq -r $jq_opts "$jq_exp")
536         ret=$?
537         if [[ $ret -ne 0 ]]; then
538                 return $ret
539         fi
540         echo $output
541         # return success only in case of non-empty output
542         [ ! -z "$output" ]
543 }
544
545 pre_cleanup()
546 {
547         if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
548                 echo "Pausing before cleanup, hit any key to continue"
549                 read
550         fi
551
552         if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then
553                 mac_addr_restore
554         fi
555 }
556
557 vrf_prepare()
558 {
559         ip -4 rule add pref 32765 table local
560         ip -4 rule del pref 0
561         ip -6 rule add pref 32765 table local
562         ip -6 rule del pref 0
563 }
564
565 vrf_cleanup()
566 {
567         ip -6 rule add pref 0 table local
568         ip -6 rule del pref 32765
569         ip -4 rule add pref 0 table local
570         ip -4 rule del pref 32765
571 }
572
573 __last_tb_id=0
574 declare -A __TB_IDS
575
576 __vrf_td_id_assign()
577 {
578         local vrf_name=$1
579
580         __last_tb_id=$((__last_tb_id + 1))
581         __TB_IDS[$vrf_name]=$__last_tb_id
582         return $__last_tb_id
583 }
584
585 __vrf_td_id_lookup()
586 {
587         local vrf_name=$1
588
589         return ${__TB_IDS[$vrf_name]}
590 }
591
592 vrf_create()
593 {
594         local vrf_name=$1
595         local tb_id
596
597         __vrf_td_id_assign $vrf_name
598         tb_id=$?
599
600         ip link add dev $vrf_name type vrf table $tb_id
601         ip -4 route add table $tb_id unreachable default metric 4278198272
602         ip -6 route add table $tb_id unreachable default metric 4278198272
603 }
604
605 vrf_destroy()
606 {
607         local vrf_name=$1
608         local tb_id
609
610         __vrf_td_id_lookup $vrf_name
611         tb_id=$?
612
613         ip -6 route del table $tb_id unreachable default metric 4278198272
614         ip -4 route del table $tb_id unreachable default metric 4278198272
615         ip link del dev $vrf_name
616 }
617
618 __addr_add_del()
619 {
620         local if_name=$1
621         local add_del=$2
622         local array
623
624         shift
625         shift
626         array=("${@}")
627
628         for addrstr in "${array[@]}"; do
629                 ip address $add_del $addrstr dev $if_name
630         done
631 }
632
633 __simple_if_init()
634 {
635         local if_name=$1; shift
636         local vrf_name=$1; shift
637         local addrs=("${@}")
638
639         ip link set dev $if_name master $vrf_name
640         ip link set dev $if_name up
641
642         __addr_add_del $if_name add "${addrs[@]}"
643 }
644
645 __simple_if_fini()
646 {
647         local if_name=$1; shift
648         local addrs=("${@}")
649
650         __addr_add_del $if_name del "${addrs[@]}"
651
652         ip link set dev $if_name down
653         ip link set dev $if_name nomaster
654 }
655
656 simple_if_init()
657 {
658         local if_name=$1
659         local vrf_name
660         local array
661
662         shift
663         vrf_name=v$if_name
664         array=("${@}")
665
666         vrf_create $vrf_name
667         ip link set dev $vrf_name up
668         __simple_if_init $if_name $vrf_name "${array[@]}"
669 }
670
671 simple_if_fini()
672 {
673         local if_name=$1
674         local vrf_name
675         local array
676
677         shift
678         vrf_name=v$if_name
679         array=("${@}")
680
681         __simple_if_fini $if_name "${array[@]}"
682         vrf_destroy $vrf_name
683 }
684
685 tunnel_create()
686 {
687         local name=$1; shift
688         local type=$1; shift
689         local local=$1; shift
690         local remote=$1; shift
691
692         ip link add name $name type $type \
693            local $local remote $remote "$@"
694         ip link set dev $name up
695 }
696
697 tunnel_destroy()
698 {
699         local name=$1; shift
700
701         ip link del dev $name
702 }
703
704 vlan_create()
705 {
706         local if_name=$1; shift
707         local vid=$1; shift
708         local vrf=$1; shift
709         local ips=("${@}")
710         local name=$if_name.$vid
711
712         ip link add name $name link $if_name type vlan id $vid
713         if [ "$vrf" != "" ]; then
714                 ip link set dev $name master $vrf
715         fi
716         ip link set dev $name up
717         __addr_add_del $name add "${ips[@]}"
718 }
719
720 vlan_destroy()
721 {
722         local if_name=$1; shift
723         local vid=$1; shift
724         local name=$if_name.$vid
725
726         ip link del dev $name
727 }
728
729 team_create()
730 {
731         local if_name=$1; shift
732         local mode=$1; shift
733
734         require_command $TEAMD
735         $TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}'
736         for slave in "$@"; do
737                 ip link set dev $slave down
738                 ip link set dev $slave master $if_name
739                 ip link set dev $slave up
740         done
741         ip link set dev $if_name up
742 }
743
744 team_destroy()
745 {
746         local if_name=$1; shift
747
748         $TEAMD -t $if_name -k
749 }
750
751 master_name_get()
752 {
753         local if_name=$1
754
755         ip -j link show dev $if_name | jq -r '.[]["master"]'
756 }
757
758 link_stats_get()
759 {
760         local if_name=$1; shift
761         local dir=$1; shift
762         local stat=$1; shift
763
764         ip -j -s link show dev $if_name \
765                 | jq '.[]["stats64"]["'$dir'"]["'$stat'"]'
766 }
767
768 link_stats_tx_packets_get()
769 {
770         link_stats_get $1 tx packets
771 }
772
773 link_stats_rx_errors_get()
774 {
775         link_stats_get $1 rx errors
776 }
777
778 tc_rule_stats_get()
779 {
780         local dev=$1; shift
781         local pref=$1; shift
782         local dir=$1; shift
783         local selector=${1:-.packets}; shift
784
785         tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \
786             | jq ".[1].options.actions[].stats$selector"
787 }
788
789 tc_rule_handle_stats_get()
790 {
791         local id=$1; shift
792         local handle=$1; shift
793         local selector=${1:-.packets}; shift
794         local netns=${1:-""}; shift
795
796         tc $netns -j -s filter show $id \
797             | jq ".[] | select(.options.handle == $handle) | \
798                   .options.actions[0].stats$selector"
799 }
800
801 ethtool_stats_get()
802 {
803         local dev=$1; shift
804         local stat=$1; shift
805
806         ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2
807 }
808
809 ethtool_std_stats_get()
810 {
811         local dev=$1; shift
812         local grp=$1; shift
813         local name=$1; shift
814         local src=$1; shift
815
816         ethtool --json -S $dev --groups $grp -- --src $src | \
817                 jq '.[]."'"$grp"'"."'$name'"'
818 }
819
820 qdisc_stats_get()
821 {
822         local dev=$1; shift
823         local handle=$1; shift
824         local selector=$1; shift
825
826         tc -j -s qdisc show dev "$dev" \
827             | jq '.[] | select(.handle == "'"$handle"'") | '"$selector"
828 }
829
830 qdisc_parent_stats_get()
831 {
832         local dev=$1; shift
833         local parent=$1; shift
834         local selector=$1; shift
835
836         tc -j -s qdisc show dev "$dev" invisible \
837             | jq '.[] | select(.parent == "'"$parent"'") | '"$selector"
838 }
839
840 ipv6_stats_get()
841 {
842         local dev=$1; shift
843         local stat=$1; shift
844
845         cat /proc/net/dev_snmp6/$dev | grep "^$stat" | cut -f2
846 }
847
848 hw_stats_get()
849 {
850         local suite=$1; shift
851         local if_name=$1; shift
852         local dir=$1; shift
853         local stat=$1; shift
854
855         ip -j stats show dev $if_name group offload subgroup $suite |
856                 jq ".[0].stats64.$dir.$stat"
857 }
858
859 humanize()
860 {
861         local speed=$1; shift
862
863         for unit in bps Kbps Mbps Gbps; do
864                 if (($(echo "$speed < 1024" | bc))); then
865                         break
866                 fi
867
868                 speed=$(echo "scale=1; $speed / 1024" | bc)
869         done
870
871         echo "$speed${unit}"
872 }
873
874 rate()
875 {
876         local t0=$1; shift
877         local t1=$1; shift
878         local interval=$1; shift
879
880         echo $((8 * (t1 - t0) / interval))
881 }
882
883 packets_rate()
884 {
885         local t0=$1; shift
886         local t1=$1; shift
887         local interval=$1; shift
888
889         echo $(((t1 - t0) / interval))
890 }
891
892 mac_get()
893 {
894         local if_name=$1
895
896         ip -j link show dev $if_name | jq -r '.[]["address"]'
897 }
898
899 ipv6_lladdr_get()
900 {
901         local if_name=$1
902
903         ip -j addr show dev $if_name | \
904                 jq -r '.[]["addr_info"][] | select(.scope == "link").local' | \
905                 head -1
906 }
907
908 bridge_ageing_time_get()
909 {
910         local bridge=$1
911         local ageing_time
912
913         # Need to divide by 100 to convert to seconds.
914         ageing_time=$(ip -j -d link show dev $bridge \
915                       | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
916         echo $((ageing_time / 100))
917 }
918
919 declare -A SYSCTL_ORIG
920 sysctl_set()
921 {
922         local key=$1; shift
923         local value=$1; shift
924
925         SYSCTL_ORIG[$key]=$(sysctl -n $key)
926         sysctl -qw $key="$value"
927 }
928
929 sysctl_restore()
930 {
931         local key=$1; shift
932
933         sysctl -qw $key="${SYSCTL_ORIG[$key]}"
934 }
935
936 forwarding_enable()
937 {
938         sysctl_set net.ipv4.conf.all.forwarding 1
939         sysctl_set net.ipv6.conf.all.forwarding 1
940 }
941
942 forwarding_restore()
943 {
944         sysctl_restore net.ipv6.conf.all.forwarding
945         sysctl_restore net.ipv4.conf.all.forwarding
946 }
947
948 declare -A MTU_ORIG
949 mtu_set()
950 {
951         local dev=$1; shift
952         local mtu=$1; shift
953
954         MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
955         ip link set dev $dev mtu $mtu
956 }
957
958 mtu_restore()
959 {
960         local dev=$1; shift
961
962         ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
963 }
964
965 tc_offload_check()
966 {
967         local num_netifs=${1:-$NUM_NETIFS}
968
969         for ((i = 1; i <= num_netifs; ++i)); do
970                 ethtool -k ${NETIFS[p$i]} \
971                         | grep "hw-tc-offload: on" &> /dev/null
972                 if [[ $? -ne 0 ]]; then
973                         return 1
974                 fi
975         done
976
977         return 0
978 }
979
980 trap_install()
981 {
982         local dev=$1; shift
983         local direction=$1; shift
984
985         # Some devices may not support or need in-hardware trapping of traffic
986         # (e.g. the veth pairs that this library creates for non-existent
987         # loopbacks). Use continue instead, so that there is a filter in there
988         # (some tests check counters), and so that other filters are still
989         # processed.
990         tc filter add dev $dev $direction pref 1 \
991                 flower skip_sw action trap 2>/dev/null \
992             || tc filter add dev $dev $direction pref 1 \
993                        flower action continue
994 }
995
996 trap_uninstall()
997 {
998         local dev=$1; shift
999         local direction=$1; shift
1000
1001         tc filter del dev $dev $direction pref 1 flower
1002 }
1003
1004 slow_path_trap_install()
1005 {
1006         # For slow-path testing, we need to install a trap to get to
1007         # slow path the packets that would otherwise be switched in HW.
1008         if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
1009                 trap_install "$@"
1010         fi
1011 }
1012
1013 slow_path_trap_uninstall()
1014 {
1015         if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
1016                 trap_uninstall "$@"
1017         fi
1018 }
1019
1020 __icmp_capture_add_del()
1021 {
1022         local add_del=$1; shift
1023         local pref=$1; shift
1024         local vsuf=$1; shift
1025         local tundev=$1; shift
1026         local filter=$1; shift
1027
1028         tc filter $add_del dev "$tundev" ingress \
1029            proto ip$vsuf pref $pref \
1030            flower ip_proto icmp$vsuf $filter \
1031            action pass
1032 }
1033
1034 icmp_capture_install()
1035 {
1036         __icmp_capture_add_del add 100 "" "$@"
1037 }
1038
1039 icmp_capture_uninstall()
1040 {
1041         __icmp_capture_add_del del 100 "" "$@"
1042 }
1043
1044 icmp6_capture_install()
1045 {
1046         __icmp_capture_add_del add 100 v6 "$@"
1047 }
1048
1049 icmp6_capture_uninstall()
1050 {
1051         __icmp_capture_add_del del 100 v6 "$@"
1052 }
1053
1054 __vlan_capture_add_del()
1055 {
1056         local add_del=$1; shift
1057         local pref=$1; shift
1058         local dev=$1; shift
1059         local filter=$1; shift
1060
1061         tc filter $add_del dev "$dev" ingress \
1062            proto 802.1q pref $pref \
1063            flower $filter \
1064            action pass
1065 }
1066
1067 vlan_capture_install()
1068 {
1069         __vlan_capture_add_del add 100 "$@"
1070 }
1071
1072 vlan_capture_uninstall()
1073 {
1074         __vlan_capture_add_del del 100 "$@"
1075 }
1076
1077 __dscp_capture_add_del()
1078 {
1079         local add_del=$1; shift
1080         local dev=$1; shift
1081         local base=$1; shift
1082         local dscp;
1083
1084         for prio in {0..7}; do
1085                 dscp=$((base + prio))
1086                 __icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
1087                                        "skip_hw ip_tos $((dscp << 2))"
1088         done
1089 }
1090
1091 dscp_capture_install()
1092 {
1093         local dev=$1; shift
1094         local base=$1; shift
1095
1096         __dscp_capture_add_del add $dev $base
1097 }
1098
1099 dscp_capture_uninstall()
1100 {
1101         local dev=$1; shift
1102         local base=$1; shift
1103
1104         __dscp_capture_add_del del $dev $base
1105 }
1106
1107 dscp_fetch_stats()
1108 {
1109         local dev=$1; shift
1110         local base=$1; shift
1111
1112         for prio in {0..7}; do
1113                 local dscp=$((base + prio))
1114                 local t=$(tc_rule_stats_get $dev $((dscp + 100)))
1115                 echo "[$dscp]=$t "
1116         done
1117 }
1118
1119 matchall_sink_create()
1120 {
1121         local dev=$1; shift
1122
1123         tc qdisc add dev $dev clsact
1124         tc filter add dev $dev ingress \
1125            pref 10000 \
1126            matchall \
1127            action drop
1128 }
1129
1130 tests_run()
1131 {
1132         local current_test
1133
1134         for current_test in ${TESTS:-$ALL_TESTS}; do
1135                 $current_test
1136         done
1137 }
1138
1139 multipath_eval()
1140 {
1141         local desc="$1"
1142         local weight_rp12=$2
1143         local weight_rp13=$3
1144         local packets_rp12=$4
1145         local packets_rp13=$5
1146         local weights_ratio packets_ratio diff
1147
1148         RET=0
1149
1150         if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1151                 weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
1152                                 | bc -l)
1153         else
1154                 weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
1155                                 | bc -l)
1156         fi
1157
1158         if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
1159                check_err 1 "Packet difference is 0"
1160                log_test "Multipath"
1161                log_info "Expected ratio $weights_ratio"
1162                return
1163         fi
1164
1165         if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1166                 packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
1167                                 | bc -l)
1168         else
1169                 packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
1170                                 | bc -l)
1171         fi
1172
1173         diff=$(echo $weights_ratio - $packets_ratio | bc -l)
1174         diff=${diff#-}
1175
1176         test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
1177         check_err $? "Too large discrepancy between expected and measured ratios"
1178         log_test "$desc"
1179         log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
1180 }
1181
1182 in_ns()
1183 {
1184         local name=$1; shift
1185
1186         ip netns exec $name bash <<-EOF
1187                 NUM_NETIFS=0
1188                 source lib.sh
1189                 $(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
1190         EOF
1191 }
1192
1193 ##############################################################################
1194 # Tests
1195
1196 ping_do()
1197 {
1198         local if_name=$1
1199         local dip=$2
1200         local args=$3
1201         local vrf_name
1202
1203         vrf_name=$(master_name_get $if_name)
1204         ip vrf exec $vrf_name \
1205                 $PING $args $dip -c $PING_COUNT -i 0.1 \
1206                 -w $PING_TIMEOUT &> /dev/null
1207 }
1208
1209 ping_test()
1210 {
1211         RET=0
1212
1213         ping_do $1 $2
1214         check_err $?
1215         log_test "ping$3"
1216 }
1217
1218 ping6_do()
1219 {
1220         local if_name=$1
1221         local dip=$2
1222         local args=$3
1223         local vrf_name
1224
1225         vrf_name=$(master_name_get $if_name)
1226         ip vrf exec $vrf_name \
1227                 $PING6 $args $dip -c $PING_COUNT -i 0.1 \
1228                 -w $PING_TIMEOUT &> /dev/null
1229 }
1230
1231 ping6_test()
1232 {
1233         RET=0
1234
1235         ping6_do $1 $2
1236         check_err $?
1237         log_test "ping6$3"
1238 }
1239
1240 learning_test()
1241 {
1242         local bridge=$1
1243         local br_port1=$2       # Connected to `host1_if`.
1244         local host1_if=$3
1245         local host2_if=$4
1246         local mac=de:ad:be:ef:13:37
1247         local ageing_time
1248
1249         RET=0
1250
1251         bridge -j fdb show br $bridge brport $br_port1 \
1252                 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1253         check_fail $? "Found FDB record when should not"
1254
1255         # Disable unknown unicast flooding on `br_port1` to make sure
1256         # packets are only forwarded through the port after a matching
1257         # FDB entry was installed.
1258         bridge link set dev $br_port1 flood off
1259
1260         ip link set $host1_if promisc on
1261         tc qdisc add dev $host1_if ingress
1262         tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
1263                 flower dst_mac $mac action drop
1264
1265         $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1266         sleep 1
1267
1268         tc -j -s filter show dev $host1_if ingress \
1269                 | jq -e ".[] | select(.options.handle == 101) \
1270                 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
1271         check_fail $? "Packet reached first host when should not"
1272
1273         $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1274         sleep 1
1275
1276         bridge -j fdb show br $bridge brport $br_port1 \
1277                 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1278         check_err $? "Did not find FDB record when should"
1279
1280         $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1281         sleep 1
1282
1283         tc -j -s filter show dev $host1_if ingress \
1284                 | jq -e ".[] | select(.options.handle == 101) \
1285                 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
1286         check_err $? "Packet did not reach second host when should"
1287
1288         # Wait for 10 seconds after the ageing time to make sure FDB
1289         # record was aged-out.
1290         ageing_time=$(bridge_ageing_time_get $bridge)
1291         sleep $((ageing_time + 10))
1292
1293         bridge -j fdb show br $bridge brport $br_port1 \
1294                 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1295         check_fail $? "Found FDB record when should not"
1296
1297         bridge link set dev $br_port1 learning off
1298
1299         $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1300         sleep 1
1301
1302         bridge -j fdb show br $bridge brport $br_port1 \
1303                 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1304         check_fail $? "Found FDB record when should not"
1305
1306         bridge link set dev $br_port1 learning on
1307
1308         tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
1309         tc qdisc del dev $host1_if ingress
1310         ip link set $host1_if promisc off
1311
1312         bridge link set dev $br_port1 flood on
1313
1314         log_test "FDB learning"
1315 }
1316
1317 flood_test_do()
1318 {
1319         local should_flood=$1
1320         local mac=$2
1321         local ip=$3
1322         local host1_if=$4
1323         local host2_if=$5
1324         local err=0
1325
1326         # Add an ACL on `host2_if` which will tell us whether the packet
1327         # was flooded to it or not.
1328         ip link set $host2_if promisc on
1329         tc qdisc add dev $host2_if ingress
1330         tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
1331                 flower dst_mac $mac action drop
1332
1333         $MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
1334         sleep 1
1335
1336         tc -j -s filter show dev $host2_if ingress \
1337                 | jq -e ".[] | select(.options.handle == 101) \
1338                 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
1339         if [[ $? -ne 0 && $should_flood == "true" || \
1340               $? -eq 0 && $should_flood == "false" ]]; then
1341                 err=1
1342         fi
1343
1344         tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1345         tc qdisc del dev $host2_if ingress
1346         ip link set $host2_if promisc off
1347
1348         return $err
1349 }
1350
1351 flood_unicast_test()
1352 {
1353         local br_port=$1
1354         local host1_if=$2
1355         local host2_if=$3
1356         local mac=de:ad:be:ef:13:37
1357         local ip=192.0.2.100
1358
1359         RET=0
1360
1361         bridge link set dev $br_port flood off
1362
1363         flood_test_do false $mac $ip $host1_if $host2_if
1364         check_err $? "Packet flooded when should not"
1365
1366         bridge link set dev $br_port flood on
1367
1368         flood_test_do true $mac $ip $host1_if $host2_if
1369         check_err $? "Packet was not flooded when should"
1370
1371         log_test "Unknown unicast flood"
1372 }
1373
1374 flood_multicast_test()
1375 {
1376         local br_port=$1
1377         local host1_if=$2
1378         local host2_if=$3
1379         local mac=01:00:5e:00:00:01
1380         local ip=239.0.0.1
1381
1382         RET=0
1383
1384         bridge link set dev $br_port mcast_flood off
1385
1386         flood_test_do false $mac $ip $host1_if $host2_if
1387         check_err $? "Packet flooded when should not"
1388
1389         bridge link set dev $br_port mcast_flood on
1390
1391         flood_test_do true $mac $ip $host1_if $host2_if
1392         check_err $? "Packet was not flooded when should"
1393
1394         log_test "Unregistered multicast flood"
1395 }
1396
1397 flood_test()
1398 {
1399         # `br_port` is connected to `host2_if`
1400         local br_port=$1
1401         local host1_if=$2
1402         local host2_if=$3
1403
1404         flood_unicast_test $br_port $host1_if $host2_if
1405         flood_multicast_test $br_port $host1_if $host2_if
1406 }
1407
1408 __start_traffic()
1409 {
1410         local pktsize=$1; shift
1411         local proto=$1; shift
1412         local h_in=$1; shift    # Where the traffic egresses the host
1413         local sip=$1; shift
1414         local dip=$1; shift
1415         local dmac=$1; shift
1416
1417         $MZ $h_in -p $pktsize -A $sip -B $dip -c 0 \
1418                 -a own -b $dmac -t "$proto" -q "$@" &
1419         sleep 1
1420 }
1421
1422 start_traffic_pktsize()
1423 {
1424         local pktsize=$1; shift
1425
1426         __start_traffic $pktsize udp "$@"
1427 }
1428
1429 start_tcp_traffic_pktsize()
1430 {
1431         local pktsize=$1; shift
1432
1433         __start_traffic $pktsize tcp "$@"
1434 }
1435
1436 start_traffic()
1437 {
1438         start_traffic_pktsize 8000 "$@"
1439 }
1440
1441 start_tcp_traffic()
1442 {
1443         start_tcp_traffic_pktsize 8000 "$@"
1444 }
1445
1446 stop_traffic()
1447 {
1448         # Suppress noise from killing mausezahn.
1449         { kill %% && wait %%; } 2>/dev/null
1450 }
1451
1452 declare -A cappid
1453 declare -A capfile
1454 declare -A capout
1455
1456 tcpdump_start()
1457 {
1458         local if_name=$1; shift
1459         local ns=$1; shift
1460
1461         capfile[$if_name]=$(mktemp)
1462         capout[$if_name]=$(mktemp)
1463
1464         if [ -z $ns ]; then
1465                 ns_cmd=""
1466         else
1467                 ns_cmd="ip netns exec ${ns}"
1468         fi
1469
1470         if [ -z $SUDO_USER ] ; then
1471                 capuser=""
1472         else
1473                 capuser="-Z $SUDO_USER"
1474         fi
1475
1476         $ns_cmd tcpdump $TCPDUMP_EXTRA_FLAGS -e -n -Q in -i $if_name \
1477                 -s 65535 -B 32768 $capuser -w ${capfile[$if_name]} \
1478                 > "${capout[$if_name]}" 2>&1 &
1479         cappid[$if_name]=$!
1480
1481         sleep 1
1482 }
1483
1484 tcpdump_stop()
1485 {
1486         local if_name=$1
1487         local pid=${cappid[$if_name]}
1488
1489         $ns_cmd kill "$pid" && wait "$pid"
1490         sleep 1
1491 }
1492
1493 tcpdump_cleanup()
1494 {
1495         local if_name=$1
1496
1497         rm ${capfile[$if_name]} ${capout[$if_name]}
1498 }
1499
1500 tcpdump_show()
1501 {
1502         local if_name=$1
1503
1504         tcpdump -e -n -r ${capfile[$if_name]} 2>&1
1505 }
1506
1507 # return 0 if the packet wasn't seen on host2_if or 1 if it was
1508 mcast_packet_test()
1509 {
1510         local mac=$1
1511         local src_ip=$2
1512         local ip=$3
1513         local host1_if=$4
1514         local host2_if=$5
1515         local seen=0
1516         local tc_proto="ip"
1517         local mz_v6arg=""
1518
1519         # basic check to see if we were passed an IPv4 address, if not assume IPv6
1520         if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
1521                 tc_proto="ipv6"
1522                 mz_v6arg="-6"
1523         fi
1524
1525         # Add an ACL on `host2_if` which will tell us whether the packet
1526         # was received by it or not.
1527         tc qdisc add dev $host2_if ingress
1528         tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \
1529                 flower ip_proto udp dst_mac $mac action drop
1530
1531         $MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q
1532         sleep 1
1533
1534         tc -j -s filter show dev $host2_if ingress \
1535                 | jq -e ".[] | select(.options.handle == 101) \
1536                 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
1537         if [[ $? -eq 0 ]]; then
1538                 seen=1
1539         fi
1540
1541         tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower
1542         tc qdisc del dev $host2_if ingress
1543
1544         return $seen
1545 }
1546
1547 brmcast_check_sg_entries()
1548 {
1549         local report=$1; shift
1550         local slist=("$@")
1551         local sarg=""
1552
1553         for src in "${slist[@]}"; do
1554                 sarg="${sarg} and .source_list[].address == \"$src\""
1555         done
1556         bridge -j -d -s mdb show dev br0 \
1557                 | jq -e ".[].mdb[] | \
1558                          select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null
1559         check_err $? "Wrong *,G entry source list after $report report"
1560
1561         for sgent in "${slist[@]}"; do
1562                 bridge -j -d -s mdb show dev br0 \
1563                         | jq -e ".[].mdb[] | \
1564                                  select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null
1565                 check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)"
1566         done
1567 }
1568
1569 brmcast_check_sg_fwding()
1570 {
1571         local should_fwd=$1; shift
1572         local sources=("$@")
1573
1574         for src in "${sources[@]}"; do
1575                 local retval=0
1576
1577                 mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1
1578                 retval=$?
1579                 if [ $should_fwd -eq 1 ]; then
1580                         check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)"
1581                 else
1582                         check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)"
1583                 fi
1584         done
1585 }
1586
1587 brmcast_check_sg_state()
1588 {
1589         local is_blocked=$1; shift
1590         local sources=("$@")
1591         local should_fail=1
1592
1593         if [ $is_blocked -eq 1 ]; then
1594                 should_fail=0
1595         fi
1596
1597         for src in "${sources[@]}"; do
1598                 bridge -j -d -s mdb show dev br0 \
1599                         | jq -e ".[].mdb[] | \
1600                                  select(.grp == \"$TEST_GROUP\" and .source_list != null) |
1601                                  .source_list[] |
1602                                  select(.address == \"$src\") |
1603                                  select(.timer == \"0.00\")" &>/dev/null
1604                 check_err_fail $should_fail $? "Entry $src has zero timer"
1605
1606                 bridge -j -d -s mdb show dev br0 \
1607                         | jq -e ".[].mdb[] | \
1608                                  select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \
1609                                  .flags[] == \"blocked\")" &>/dev/null
1610                 check_err_fail $should_fail $? "Entry $src has blocked flag"
1611         done
1612 }
1613
1614 mc_join()
1615 {
1616         local if_name=$1
1617         local group=$2
1618         local vrf_name=$(master_name_get $if_name)
1619
1620         # We don't care about actual reception, just about joining the
1621         # IP multicast group and adding the L2 address to the device's
1622         # MAC filtering table
1623         ip vrf exec $vrf_name \
1624                 mreceive -g $group -I $if_name > /dev/null 2>&1 &
1625         mreceive_pid=$!
1626
1627         sleep 1
1628 }
1629
1630 mc_leave()
1631 {
1632         kill "$mreceive_pid" && wait "$mreceive_pid"
1633 }
1634
1635 mc_send()
1636 {
1637         local if_name=$1
1638         local groups=$2
1639         local vrf_name=$(master_name_get $if_name)
1640
1641         ip vrf exec $vrf_name \
1642                 msend -g $groups -I $if_name -c 1 > /dev/null 2>&1
1643 }
1644
1645 start_ip_monitor()
1646 {
1647         local mtype=$1; shift
1648         local ip=${1-ip}; shift
1649
1650         # start the monitor in the background
1651         tmpfile=`mktemp /var/run/nexthoptestXXX`
1652         mpid=`($ip monitor $mtype > $tmpfile & echo $!) 2>/dev/null`
1653         sleep 0.2
1654         echo "$mpid $tmpfile"
1655 }
1656
1657 stop_ip_monitor()
1658 {
1659         local mpid=$1; shift
1660         local tmpfile=$1; shift
1661         local el=$1; shift
1662         local what=$1; shift
1663
1664         sleep 0.2
1665         kill $mpid
1666         local lines=`grep '^\w' $tmpfile | wc -l`
1667         test $lines -eq $el
1668         check_err $? "$what: $lines lines of events, expected $el"
1669         rm -rf $tmpfile
1670 }
1671
1672 hw_stats_monitor_test()
1673 {
1674         local dev=$1; shift
1675         local type=$1; shift
1676         local make_suitable=$1; shift
1677         local make_unsuitable=$1; shift
1678         local ip=${1-ip}; shift
1679
1680         RET=0
1681
1682         # Expect a notification about enablement.
1683         local ipmout=$(start_ip_monitor stats "$ip")
1684         $ip stats set dev $dev ${type}_stats on
1685         stop_ip_monitor $ipmout 1 "${type}_stats enablement"
1686
1687         # Expect a notification about offload.
1688         local ipmout=$(start_ip_monitor stats "$ip")
1689         $make_suitable
1690         stop_ip_monitor $ipmout 1 "${type}_stats installation"
1691
1692         # Expect a notification about loss of offload.
1693         local ipmout=$(start_ip_monitor stats "$ip")
1694         $make_unsuitable
1695         stop_ip_monitor $ipmout 1 "${type}_stats deinstallation"
1696
1697         # Expect a notification about disablement
1698         local ipmout=$(start_ip_monitor stats "$ip")
1699         $ip stats set dev $dev ${type}_stats off
1700         stop_ip_monitor $ipmout 1 "${type}_stats disablement"
1701
1702         log_test "${type}_stats notifications"
1703 }
1704
1705 ipv4_to_bytes()
1706 {
1707         local IP=$1; shift
1708
1709         printf '%02x:' ${IP//./ } |
1710             sed 's/:$//'
1711 }
1712
1713 # Convert a given IPv6 address, `IP' such that the :: token, if present, is
1714 # expanded, and each 16-bit group is padded with zeroes to be 4 hexadecimal
1715 # digits. An optional `BYTESEP' parameter can be given to further separate
1716 # individual bytes of each 16-bit group.
1717 expand_ipv6()
1718 {
1719         local IP=$1; shift
1720         local bytesep=$1; shift
1721
1722         local cvt_ip=${IP/::/_}
1723         local colons=${cvt_ip//[^:]/}
1724         local allcol=:::::::
1725         # IP where :: -> the appropriate number of colons:
1726         local allcol_ip=${cvt_ip/_/${allcol:${#colons}}}
1727
1728         echo $allcol_ip | tr : '\n' |
1729             sed s/^/0000/ |
1730             sed 's/.*\(..\)\(..\)/\1'"$bytesep"'\2/' |
1731             tr '\n' : |
1732             sed 's/:$//'
1733 }
1734
1735 ipv6_to_bytes()
1736 {
1737         local IP=$1; shift
1738
1739         expand_ipv6 "$IP" :
1740 }
1741
1742 u16_to_bytes()
1743 {
1744         local u16=$1; shift
1745
1746         printf "%04x" $u16 | sed 's/^/000/;s/^.*\(..\)\(..\)$/\1:\2/'
1747 }
1748
1749 # Given a mausezahn-formatted payload (colon-separated bytes given as %02x),
1750 # possibly with a keyword CHECKSUM stashed where a 16-bit checksum should be,
1751 # calculate checksum as per RFC 1071, assuming the CHECKSUM field (if any)
1752 # stands for 00:00.
1753 payload_template_calc_checksum()
1754 {
1755         local payload=$1; shift
1756
1757         (
1758             # Set input radix.
1759             echo "16i"
1760             # Push zero for the initial checksum.
1761             echo 0
1762
1763             # Pad the payload with a terminating 00: in case we get an odd
1764             # number of bytes.
1765             echo "${payload%:}:00:" |
1766                 sed 's/CHECKSUM/00:00/g' |
1767                 tr '[:lower:]' '[:upper:]' |
1768                 # Add the word to the checksum.
1769                 sed 's/\(..\):\(..\):/\1\2+\n/g' |
1770                 # Strip the extra odd byte we pushed if left unconverted.
1771                 sed 's/\(..\):$//'
1772
1773             echo "10000 ~ +"    # Calculate and add carry.
1774             echo "FFFF r - p"   # Bit-flip and print.
1775         ) |
1776             dc |
1777             tr '[:upper:]' '[:lower:]'
1778 }
1779
1780 payload_template_expand_checksum()
1781 {
1782         local payload=$1; shift
1783         local checksum=$1; shift
1784
1785         local ckbytes=$(u16_to_bytes $checksum)
1786
1787         echo "$payload" | sed "s/CHECKSUM/$ckbytes/g"
1788 }
1789
1790 payload_template_nbytes()
1791 {
1792         local payload=$1; shift
1793
1794         payload_template_expand_checksum "${payload%:}" 0 |
1795                 sed 's/:/\n/g' | wc -l
1796 }
1797
1798 igmpv3_is_in_get()
1799 {
1800         local GRP=$1; shift
1801         local sources=("$@")
1802
1803         local igmpv3
1804         local nsources=$(u16_to_bytes ${#sources[@]})
1805
1806         # IS_IN ( $sources )
1807         igmpv3=$(:
1808                 )"22:"$(                        : Type - Membership Report
1809                 )"00:"$(                        : Reserved
1810                 )"CHECKSUM:"$(                  : Checksum
1811                 )"00:00:"$(                     : Reserved
1812                 )"00:01:"$(                     : Number of Group Records
1813                 )"01:"$(                        : Record Type - IS_IN
1814                 )"00:"$(                        : Aux Data Len
1815                 )"${nsources}:"$(               : Number of Sources
1816                 )"$(ipv4_to_bytes $GRP):"$(     : Multicast Address
1817                 )"$(for src in "${sources[@]}"; do
1818                         ipv4_to_bytes $src
1819                         echo -n :
1820                     done)"$(                    : Source Addresses
1821                 )
1822         local checksum=$(payload_template_calc_checksum "$igmpv3")
1823
1824         payload_template_expand_checksum "$igmpv3" $checksum
1825 }
1826
1827 igmpv2_leave_get()
1828 {
1829         local GRP=$1; shift
1830
1831         local payload=$(:
1832                 )"17:"$(                        : Type - Leave Group
1833                 )"00:"$(                        : Max Resp Time - not meaningful
1834                 )"CHECKSUM:"$(                  : Checksum
1835                 )"$(ipv4_to_bytes $GRP)"$(      : Group Address
1836                 )
1837         local checksum=$(payload_template_calc_checksum "$payload")
1838
1839         payload_template_expand_checksum "$payload" $checksum
1840 }
1841
1842 mldv2_is_in_get()
1843 {
1844         local SIP=$1; shift
1845         local GRP=$1; shift
1846         local sources=("$@")
1847
1848         local hbh
1849         local icmpv6
1850         local nsources=$(u16_to_bytes ${#sources[@]})
1851
1852         hbh=$(:
1853                 )"3a:"$(                        : Next Header - ICMPv6
1854                 )"00:"$(                        : Hdr Ext Len
1855                 )"00:00:00:00:00:00:"$(         : Options and Padding
1856                 )
1857
1858         icmpv6=$(:
1859                 )"8f:"$(                        : Type - MLDv2 Report
1860                 )"00:"$(                        : Code
1861                 )"CHECKSUM:"$(                  : Checksum
1862                 )"00:00:"$(                     : Reserved
1863                 )"00:01:"$(                     : Number of Group Records
1864                 )"01:"$(                        : Record Type - IS_IN
1865                 )"00:"$(                        : Aux Data Len
1866                 )"${nsources}:"$(               : Number of Sources
1867                 )"$(ipv6_to_bytes $GRP):"$(     : Multicast address
1868                 )"$(for src in "${sources[@]}"; do
1869                         ipv6_to_bytes $src
1870                         echo -n :
1871                     done)"$(                    : Source Addresses
1872                 )
1873
1874         local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
1875         local sudohdr=$(:
1876                 )"$(ipv6_to_bytes $SIP):"$(     : SIP
1877                 )"$(ipv6_to_bytes $GRP):"$(     : DIP is multicast address
1878                 )"${len}:"$(                    : Upper-layer length
1879                 )"00:3a:"$(                     : Zero and next-header
1880                 )
1881         local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
1882
1883         payload_template_expand_checksum "$hbh$icmpv6" $checksum
1884 }
1885
1886 mldv1_done_get()
1887 {
1888         local SIP=$1; shift
1889         local GRP=$1; shift
1890
1891         local hbh
1892         local icmpv6
1893
1894         hbh=$(:
1895                 )"3a:"$(                        : Next Header - ICMPv6
1896                 )"00:"$(                        : Hdr Ext Len
1897                 )"00:00:00:00:00:00:"$(         : Options and Padding
1898                 )
1899
1900         icmpv6=$(:
1901                 )"84:"$(                        : Type - MLDv1 Done
1902                 )"00:"$(                        : Code
1903                 )"CHECKSUM:"$(                  : Checksum
1904                 )"00:00:"$(                     : Max Resp Delay - not meaningful
1905                 )"00:00:"$(                     : Reserved
1906                 )"$(ipv6_to_bytes $GRP):"$(     : Multicast address
1907                 )
1908
1909         local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
1910         local sudohdr=$(:
1911                 )"$(ipv6_to_bytes $SIP):"$(     : SIP
1912                 )"$(ipv6_to_bytes $GRP):"$(     : DIP is multicast address
1913                 )"${len}:"$(                    : Upper-layer length
1914                 )"00:3a:"$(                     : Zero and next-header
1915                 )
1916         local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
1917
1918         payload_template_expand_checksum "$hbh$icmpv6" $checksum
1919 }
1920
1921 bail_on_lldpad()
1922 {
1923         local reason1="$1"; shift
1924         local reason2="$1"; shift
1925
1926         if systemctl is-active --quiet lldpad; then
1927
1928                 cat >/dev/stderr <<-EOF
1929                 WARNING: lldpad is running
1930
1931                         lldpad will likely $reason1, and this test will
1932                         $reason2. Both are not supported at the same time,
1933                         one of them is arbitrarily going to overwrite the
1934                         other. That will cause spurious failures (or, unlikely,
1935                         passes) of this test.
1936                 EOF
1937
1938                 if [[ -z $ALLOW_LLDPAD ]]; then
1939                         cat >/dev/stderr <<-EOF
1940
1941                                 If you want to run the test anyway, please set
1942                                 an environment variable ALLOW_LLDPAD to a
1943                                 non-empty string.
1944                         EOF
1945                         exit 1
1946                 else
1947                         return
1948                 fi
1949         fi
1950 }