057c3d0ad62054f767bb2785232bbb25d8f1cf24
[platform/kernel/linux-rpi.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
795         tc -j -s filter show $id \
796             | jq ".[] | select(.options.handle == $handle) | \
797                   .options.actions[0].stats$selector"
798 }
799
800 ethtool_stats_get()
801 {
802         local dev=$1; shift
803         local stat=$1; shift
804
805         ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2
806 }
807
808 ethtool_std_stats_get()
809 {
810         local dev=$1; shift
811         local grp=$1; shift
812         local name=$1; shift
813         local src=$1; shift
814
815         ethtool --json -S $dev --groups $grp -- --src $src | \
816                 jq '.[]."'"$grp"'"."'$name'"'
817 }
818
819 qdisc_stats_get()
820 {
821         local dev=$1; shift
822         local handle=$1; shift
823         local selector=$1; shift
824
825         tc -j -s qdisc show dev "$dev" \
826             | jq '.[] | select(.handle == "'"$handle"'") | '"$selector"
827 }
828
829 qdisc_parent_stats_get()
830 {
831         local dev=$1; shift
832         local parent=$1; shift
833         local selector=$1; shift
834
835         tc -j -s qdisc show dev "$dev" invisible \
836             | jq '.[] | select(.parent == "'"$parent"'") | '"$selector"
837 }
838
839 ipv6_stats_get()
840 {
841         local dev=$1; shift
842         local stat=$1; shift
843
844         cat /proc/net/dev_snmp6/$dev | grep "^$stat" | cut -f2
845 }
846
847 hw_stats_get()
848 {
849         local suite=$1; shift
850         local if_name=$1; shift
851         local dir=$1; shift
852         local stat=$1; shift
853
854         ip -j stats show dev $if_name group offload subgroup $suite |
855                 jq ".[0].stats64.$dir.$stat"
856 }
857
858 humanize()
859 {
860         local speed=$1; shift
861
862         for unit in bps Kbps Mbps Gbps; do
863                 if (($(echo "$speed < 1024" | bc))); then
864                         break
865                 fi
866
867                 speed=$(echo "scale=1; $speed / 1024" | bc)
868         done
869
870         echo "$speed${unit}"
871 }
872
873 rate()
874 {
875         local t0=$1; shift
876         local t1=$1; shift
877         local interval=$1; shift
878
879         echo $((8 * (t1 - t0) / interval))
880 }
881
882 packets_rate()
883 {
884         local t0=$1; shift
885         local t1=$1; shift
886         local interval=$1; shift
887
888         echo $(((t1 - t0) / interval))
889 }
890
891 mac_get()
892 {
893         local if_name=$1
894
895         ip -j link show dev $if_name | jq -r '.[]["address"]'
896 }
897
898 ipv6_lladdr_get()
899 {
900         local if_name=$1
901
902         ip -j addr show dev $if_name | \
903                 jq -r '.[]["addr_info"][] | select(.scope == "link").local' | \
904                 head -1
905 }
906
907 bridge_ageing_time_get()
908 {
909         local bridge=$1
910         local ageing_time
911
912         # Need to divide by 100 to convert to seconds.
913         ageing_time=$(ip -j -d link show dev $bridge \
914                       | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
915         echo $((ageing_time / 100))
916 }
917
918 declare -A SYSCTL_ORIG
919 sysctl_set()
920 {
921         local key=$1; shift
922         local value=$1; shift
923
924         SYSCTL_ORIG[$key]=$(sysctl -n $key)
925         sysctl -qw $key="$value"
926 }
927
928 sysctl_restore()
929 {
930         local key=$1; shift
931
932         sysctl -qw $key="${SYSCTL_ORIG[$key]}"
933 }
934
935 forwarding_enable()
936 {
937         sysctl_set net.ipv4.conf.all.forwarding 1
938         sysctl_set net.ipv6.conf.all.forwarding 1
939 }
940
941 forwarding_restore()
942 {
943         sysctl_restore net.ipv6.conf.all.forwarding
944         sysctl_restore net.ipv4.conf.all.forwarding
945 }
946
947 declare -A MTU_ORIG
948 mtu_set()
949 {
950         local dev=$1; shift
951         local mtu=$1; shift
952
953         MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu')
954         ip link set dev $dev mtu $mtu
955 }
956
957 mtu_restore()
958 {
959         local dev=$1; shift
960
961         ip link set dev $dev mtu ${MTU_ORIG["$dev"]}
962 }
963
964 tc_offload_check()
965 {
966         local num_netifs=${1:-$NUM_NETIFS}
967
968         for ((i = 1; i <= num_netifs; ++i)); do
969                 ethtool -k ${NETIFS[p$i]} \
970                         | grep "hw-tc-offload: on" &> /dev/null
971                 if [[ $? -ne 0 ]]; then
972                         return 1
973                 fi
974         done
975
976         return 0
977 }
978
979 trap_install()
980 {
981         local dev=$1; shift
982         local direction=$1; shift
983
984         # Some devices may not support or need in-hardware trapping of traffic
985         # (e.g. the veth pairs that this library creates for non-existent
986         # loopbacks). Use continue instead, so that there is a filter in there
987         # (some tests check counters), and so that other filters are still
988         # processed.
989         tc filter add dev $dev $direction pref 1 \
990                 flower skip_sw action trap 2>/dev/null \
991             || tc filter add dev $dev $direction pref 1 \
992                        flower action continue
993 }
994
995 trap_uninstall()
996 {
997         local dev=$1; shift
998         local direction=$1; shift
999
1000         tc filter del dev $dev $direction pref 1 flower
1001 }
1002
1003 slow_path_trap_install()
1004 {
1005         # For slow-path testing, we need to install a trap to get to
1006         # slow path the packets that would otherwise be switched in HW.
1007         if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
1008                 trap_install "$@"
1009         fi
1010 }
1011
1012 slow_path_trap_uninstall()
1013 {
1014         if [ "${tcflags/skip_hw}" != "$tcflags" ]; then
1015                 trap_uninstall "$@"
1016         fi
1017 }
1018
1019 __icmp_capture_add_del()
1020 {
1021         local add_del=$1; shift
1022         local pref=$1; shift
1023         local vsuf=$1; shift
1024         local tundev=$1; shift
1025         local filter=$1; shift
1026
1027         tc filter $add_del dev "$tundev" ingress \
1028            proto ip$vsuf pref $pref \
1029            flower ip_proto icmp$vsuf $filter \
1030            action pass
1031 }
1032
1033 icmp_capture_install()
1034 {
1035         __icmp_capture_add_del add 100 "" "$@"
1036 }
1037
1038 icmp_capture_uninstall()
1039 {
1040         __icmp_capture_add_del del 100 "" "$@"
1041 }
1042
1043 icmp6_capture_install()
1044 {
1045         __icmp_capture_add_del add 100 v6 "$@"
1046 }
1047
1048 icmp6_capture_uninstall()
1049 {
1050         __icmp_capture_add_del del 100 v6 "$@"
1051 }
1052
1053 __vlan_capture_add_del()
1054 {
1055         local add_del=$1; shift
1056         local pref=$1; shift
1057         local dev=$1; shift
1058         local filter=$1; shift
1059
1060         tc filter $add_del dev "$dev" ingress \
1061            proto 802.1q pref $pref \
1062            flower $filter \
1063            action pass
1064 }
1065
1066 vlan_capture_install()
1067 {
1068         __vlan_capture_add_del add 100 "$@"
1069 }
1070
1071 vlan_capture_uninstall()
1072 {
1073         __vlan_capture_add_del del 100 "$@"
1074 }
1075
1076 __dscp_capture_add_del()
1077 {
1078         local add_del=$1; shift
1079         local dev=$1; shift
1080         local base=$1; shift
1081         local dscp;
1082
1083         for prio in {0..7}; do
1084                 dscp=$((base + prio))
1085                 __icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \
1086                                        "skip_hw ip_tos $((dscp << 2))"
1087         done
1088 }
1089
1090 dscp_capture_install()
1091 {
1092         local dev=$1; shift
1093         local base=$1; shift
1094
1095         __dscp_capture_add_del add $dev $base
1096 }
1097
1098 dscp_capture_uninstall()
1099 {
1100         local dev=$1; shift
1101         local base=$1; shift
1102
1103         __dscp_capture_add_del del $dev $base
1104 }
1105
1106 dscp_fetch_stats()
1107 {
1108         local dev=$1; shift
1109         local base=$1; shift
1110
1111         for prio in {0..7}; do
1112                 local dscp=$((base + prio))
1113                 local t=$(tc_rule_stats_get $dev $((dscp + 100)))
1114                 echo "[$dscp]=$t "
1115         done
1116 }
1117
1118 matchall_sink_create()
1119 {
1120         local dev=$1; shift
1121
1122         tc qdisc add dev $dev clsact
1123         tc filter add dev $dev ingress \
1124            pref 10000 \
1125            matchall \
1126            action drop
1127 }
1128
1129 tests_run()
1130 {
1131         local current_test
1132
1133         for current_test in ${TESTS:-$ALL_TESTS}; do
1134                 $current_test
1135         done
1136 }
1137
1138 multipath_eval()
1139 {
1140         local desc="$1"
1141         local weight_rp12=$2
1142         local weight_rp13=$3
1143         local packets_rp12=$4
1144         local packets_rp13=$5
1145         local weights_ratio packets_ratio diff
1146
1147         RET=0
1148
1149         if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1150                 weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \
1151                                 | bc -l)
1152         else
1153                 weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \
1154                                 | bc -l)
1155         fi
1156
1157         if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then
1158                check_err 1 "Packet difference is 0"
1159                log_test "Multipath"
1160                log_info "Expected ratio $weights_ratio"
1161                return
1162         fi
1163
1164         if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then
1165                 packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \
1166                                 | bc -l)
1167         else
1168                 packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \
1169                                 | bc -l)
1170         fi
1171
1172         diff=$(echo $weights_ratio - $packets_ratio | bc -l)
1173         diff=${diff#-}
1174
1175         test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0
1176         check_err $? "Too large discrepancy between expected and measured ratios"
1177         log_test "$desc"
1178         log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio"
1179 }
1180
1181 in_ns()
1182 {
1183         local name=$1; shift
1184
1185         ip netns exec $name bash <<-EOF
1186                 NUM_NETIFS=0
1187                 source lib.sh
1188                 $(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done)
1189         EOF
1190 }
1191
1192 ##############################################################################
1193 # Tests
1194
1195 ping_do()
1196 {
1197         local if_name=$1
1198         local dip=$2
1199         local args=$3
1200         local vrf_name
1201
1202         vrf_name=$(master_name_get $if_name)
1203         ip vrf exec $vrf_name \
1204                 $PING $args $dip -c $PING_COUNT -i 0.1 \
1205                 -w $PING_TIMEOUT &> /dev/null
1206 }
1207
1208 ping_test()
1209 {
1210         RET=0
1211
1212         ping_do $1 $2
1213         check_err $?
1214         log_test "ping$3"
1215 }
1216
1217 ping6_do()
1218 {
1219         local if_name=$1
1220         local dip=$2
1221         local args=$3
1222         local vrf_name
1223
1224         vrf_name=$(master_name_get $if_name)
1225         ip vrf exec $vrf_name \
1226                 $PING6 $args $dip -c $PING_COUNT -i 0.1 \
1227                 -w $PING_TIMEOUT &> /dev/null
1228 }
1229
1230 ping6_test()
1231 {
1232         RET=0
1233
1234         ping6_do $1 $2
1235         check_err $?
1236         log_test "ping6$3"
1237 }
1238
1239 learning_test()
1240 {
1241         local bridge=$1
1242         local br_port1=$2       # Connected to `host1_if`.
1243         local host1_if=$3
1244         local host2_if=$4
1245         local mac=de:ad:be:ef:13:37
1246         local ageing_time
1247
1248         RET=0
1249
1250         bridge -j fdb show br $bridge brport $br_port1 \
1251                 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1252         check_fail $? "Found FDB record when should not"
1253
1254         # Disable unknown unicast flooding on `br_port1` to make sure
1255         # packets are only forwarded through the port after a matching
1256         # FDB entry was installed.
1257         bridge link set dev $br_port1 flood off
1258
1259         ip link set $host1_if promisc on
1260         tc qdisc add dev $host1_if ingress
1261         tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
1262                 flower dst_mac $mac action drop
1263
1264         $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1265         sleep 1
1266
1267         tc -j -s filter show dev $host1_if ingress \
1268                 | jq -e ".[] | select(.options.handle == 101) \
1269                 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
1270         check_fail $? "Packet reached first host when should not"
1271
1272         $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1273         sleep 1
1274
1275         bridge -j fdb show br $bridge brport $br_port1 \
1276                 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1277         check_err $? "Did not find FDB record when should"
1278
1279         $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
1280         sleep 1
1281
1282         tc -j -s filter show dev $host1_if ingress \
1283                 | jq -e ".[] | select(.options.handle == 101) \
1284                 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
1285         check_err $? "Packet did not reach second host when should"
1286
1287         # Wait for 10 seconds after the ageing time to make sure FDB
1288         # record was aged-out.
1289         ageing_time=$(bridge_ageing_time_get $bridge)
1290         sleep $((ageing_time + 10))
1291
1292         bridge -j fdb show br $bridge brport $br_port1 \
1293                 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1294         check_fail $? "Found FDB record when should not"
1295
1296         bridge link set dev $br_port1 learning off
1297
1298         $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
1299         sleep 1
1300
1301         bridge -j fdb show br $bridge brport $br_port1 \
1302                 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
1303         check_fail $? "Found FDB record when should not"
1304
1305         bridge link set dev $br_port1 learning on
1306
1307         tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
1308         tc qdisc del dev $host1_if ingress
1309         ip link set $host1_if promisc off
1310
1311         bridge link set dev $br_port1 flood on
1312
1313         log_test "FDB learning"
1314 }
1315
1316 flood_test_do()
1317 {
1318         local should_flood=$1
1319         local mac=$2
1320         local ip=$3
1321         local host1_if=$4
1322         local host2_if=$5
1323         local err=0
1324
1325         # Add an ACL on `host2_if` which will tell us whether the packet
1326         # was flooded to it or not.
1327         ip link set $host2_if promisc on
1328         tc qdisc add dev $host2_if ingress
1329         tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
1330                 flower dst_mac $mac action drop
1331
1332         $MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
1333         sleep 1
1334
1335         tc -j -s filter show dev $host2_if ingress \
1336                 | jq -e ".[] | select(.options.handle == 101) \
1337                 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
1338         if [[ $? -ne 0 && $should_flood == "true" || \
1339               $? -eq 0 && $should_flood == "false" ]]; then
1340                 err=1
1341         fi
1342
1343         tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
1344         tc qdisc del dev $host2_if ingress
1345         ip link set $host2_if promisc off
1346
1347         return $err
1348 }
1349
1350 flood_unicast_test()
1351 {
1352         local br_port=$1
1353         local host1_if=$2
1354         local host2_if=$3
1355         local mac=de:ad:be:ef:13:37
1356         local ip=192.0.2.100
1357
1358         RET=0
1359
1360         bridge link set dev $br_port flood off
1361
1362         flood_test_do false $mac $ip $host1_if $host2_if
1363         check_err $? "Packet flooded when should not"
1364
1365         bridge link set dev $br_port flood on
1366
1367         flood_test_do true $mac $ip $host1_if $host2_if
1368         check_err $? "Packet was not flooded when should"
1369
1370         log_test "Unknown unicast flood"
1371 }
1372
1373 flood_multicast_test()
1374 {
1375         local br_port=$1
1376         local host1_if=$2
1377         local host2_if=$3
1378         local mac=01:00:5e:00:00:01
1379         local ip=239.0.0.1
1380
1381         RET=0
1382
1383         bridge link set dev $br_port mcast_flood off
1384
1385         flood_test_do false $mac $ip $host1_if $host2_if
1386         check_err $? "Packet flooded when should not"
1387
1388         bridge link set dev $br_port mcast_flood on
1389
1390         flood_test_do true $mac $ip $host1_if $host2_if
1391         check_err $? "Packet was not flooded when should"
1392
1393         log_test "Unregistered multicast flood"
1394 }
1395
1396 flood_test()
1397 {
1398         # `br_port` is connected to `host2_if`
1399         local br_port=$1
1400         local host1_if=$2
1401         local host2_if=$3
1402
1403         flood_unicast_test $br_port $host1_if $host2_if
1404         flood_multicast_test $br_port $host1_if $host2_if
1405 }
1406
1407 __start_traffic()
1408 {
1409         local pktsize=$1; shift
1410         local proto=$1; shift
1411         local h_in=$1; shift    # Where the traffic egresses the host
1412         local sip=$1; shift
1413         local dip=$1; shift
1414         local dmac=$1; shift
1415
1416         $MZ $h_in -p $pktsize -A $sip -B $dip -c 0 \
1417                 -a own -b $dmac -t "$proto" -q "$@" &
1418         sleep 1
1419 }
1420
1421 start_traffic_pktsize()
1422 {
1423         local pktsize=$1; shift
1424
1425         __start_traffic $pktsize udp "$@"
1426 }
1427
1428 start_tcp_traffic_pktsize()
1429 {
1430         local pktsize=$1; shift
1431
1432         __start_traffic $pktsize tcp "$@"
1433 }
1434
1435 start_traffic()
1436 {
1437         start_traffic_pktsize 8000 "$@"
1438 }
1439
1440 start_tcp_traffic()
1441 {
1442         start_tcp_traffic_pktsize 8000 "$@"
1443 }
1444
1445 stop_traffic()
1446 {
1447         # Suppress noise from killing mausezahn.
1448         { kill %% && wait %%; } 2>/dev/null
1449 }
1450
1451 declare -A cappid
1452 declare -A capfile
1453 declare -A capout
1454
1455 tcpdump_start()
1456 {
1457         local if_name=$1; shift
1458         local ns=$1; shift
1459
1460         capfile[$if_name]=$(mktemp)
1461         capout[$if_name]=$(mktemp)
1462
1463         if [ -z $ns ]; then
1464                 ns_cmd=""
1465         else
1466                 ns_cmd="ip netns exec ${ns}"
1467         fi
1468
1469         if [ -z $SUDO_USER ] ; then
1470                 capuser=""
1471         else
1472                 capuser="-Z $SUDO_USER"
1473         fi
1474
1475         $ns_cmd tcpdump $TCPDUMP_EXTRA_FLAGS -e -n -Q in -i $if_name \
1476                 -s 65535 -B 32768 $capuser -w ${capfile[$if_name]} \
1477                 > "${capout[$if_name]}" 2>&1 &
1478         cappid[$if_name]=$!
1479
1480         sleep 1
1481 }
1482
1483 tcpdump_stop()
1484 {
1485         local if_name=$1
1486         local pid=${cappid[$if_name]}
1487
1488         $ns_cmd kill "$pid" && wait "$pid"
1489         sleep 1
1490 }
1491
1492 tcpdump_cleanup()
1493 {
1494         local if_name=$1
1495
1496         rm ${capfile[$if_name]} ${capout[$if_name]}
1497 }
1498
1499 tcpdump_show()
1500 {
1501         local if_name=$1
1502
1503         tcpdump -e -n -r ${capfile[$if_name]} 2>&1
1504 }
1505
1506 # return 0 if the packet wasn't seen on host2_if or 1 if it was
1507 mcast_packet_test()
1508 {
1509         local mac=$1
1510         local src_ip=$2
1511         local ip=$3
1512         local host1_if=$4
1513         local host2_if=$5
1514         local seen=0
1515         local tc_proto="ip"
1516         local mz_v6arg=""
1517
1518         # basic check to see if we were passed an IPv4 address, if not assume IPv6
1519         if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
1520                 tc_proto="ipv6"
1521                 mz_v6arg="-6"
1522         fi
1523
1524         # Add an ACL on `host2_if` which will tell us whether the packet
1525         # was received by it or not.
1526         tc qdisc add dev $host2_if ingress
1527         tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \
1528                 flower ip_proto udp dst_mac $mac action drop
1529
1530         $MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q
1531         sleep 1
1532
1533         tc -j -s filter show dev $host2_if ingress \
1534                 | jq -e ".[] | select(.options.handle == 101) \
1535                 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
1536         if [[ $? -eq 0 ]]; then
1537                 seen=1
1538         fi
1539
1540         tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower
1541         tc qdisc del dev $host2_if ingress
1542
1543         return $seen
1544 }
1545
1546 brmcast_check_sg_entries()
1547 {
1548         local report=$1; shift
1549         local slist=("$@")
1550         local sarg=""
1551
1552         for src in "${slist[@]}"; do
1553                 sarg="${sarg} and .source_list[].address == \"$src\""
1554         done
1555         bridge -j -d -s mdb show dev br0 \
1556                 | jq -e ".[].mdb[] | \
1557                          select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null
1558         check_err $? "Wrong *,G entry source list after $report report"
1559
1560         for sgent in "${slist[@]}"; do
1561                 bridge -j -d -s mdb show dev br0 \
1562                         | jq -e ".[].mdb[] | \
1563                                  select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null
1564                 check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)"
1565         done
1566 }
1567
1568 brmcast_check_sg_fwding()
1569 {
1570         local should_fwd=$1; shift
1571         local sources=("$@")
1572
1573         for src in "${sources[@]}"; do
1574                 local retval=0
1575
1576                 mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1
1577                 retval=$?
1578                 if [ $should_fwd -eq 1 ]; then
1579                         check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)"
1580                 else
1581                         check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)"
1582                 fi
1583         done
1584 }
1585
1586 brmcast_check_sg_state()
1587 {
1588         local is_blocked=$1; shift
1589         local sources=("$@")
1590         local should_fail=1
1591
1592         if [ $is_blocked -eq 1 ]; then
1593                 should_fail=0
1594         fi
1595
1596         for src in "${sources[@]}"; do
1597                 bridge -j -d -s mdb show dev br0 \
1598                         | jq -e ".[].mdb[] | \
1599                                  select(.grp == \"$TEST_GROUP\" and .source_list != null) |
1600                                  .source_list[] |
1601                                  select(.address == \"$src\") |
1602                                  select(.timer == \"0.00\")" &>/dev/null
1603                 check_err_fail $should_fail $? "Entry $src has zero timer"
1604
1605                 bridge -j -d -s mdb show dev br0 \
1606                         | jq -e ".[].mdb[] | \
1607                                  select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \
1608                                  .flags[] == \"blocked\")" &>/dev/null
1609                 check_err_fail $should_fail $? "Entry $src has blocked flag"
1610         done
1611 }
1612
1613 mc_join()
1614 {
1615         local if_name=$1
1616         local group=$2
1617         local vrf_name=$(master_name_get $if_name)
1618
1619         # We don't care about actual reception, just about joining the
1620         # IP multicast group and adding the L2 address to the device's
1621         # MAC filtering table
1622         ip vrf exec $vrf_name \
1623                 mreceive -g $group -I $if_name > /dev/null 2>&1 &
1624         mreceive_pid=$!
1625
1626         sleep 1
1627 }
1628
1629 mc_leave()
1630 {
1631         kill "$mreceive_pid" && wait "$mreceive_pid"
1632 }
1633
1634 mc_send()
1635 {
1636         local if_name=$1
1637         local groups=$2
1638         local vrf_name=$(master_name_get $if_name)
1639
1640         ip vrf exec $vrf_name \
1641                 msend -g $groups -I $if_name -c 1 > /dev/null 2>&1
1642 }
1643
1644 start_ip_monitor()
1645 {
1646         local mtype=$1; shift
1647         local ip=${1-ip}; shift
1648
1649         # start the monitor in the background
1650         tmpfile=`mktemp /var/run/nexthoptestXXX`
1651         mpid=`($ip monitor $mtype > $tmpfile & echo $!) 2>/dev/null`
1652         sleep 0.2
1653         echo "$mpid $tmpfile"
1654 }
1655
1656 stop_ip_monitor()
1657 {
1658         local mpid=$1; shift
1659         local tmpfile=$1; shift
1660         local el=$1; shift
1661         local what=$1; shift
1662
1663         sleep 0.2
1664         kill $mpid
1665         local lines=`grep '^\w' $tmpfile | wc -l`
1666         test $lines -eq $el
1667         check_err $? "$what: $lines lines of events, expected $el"
1668         rm -rf $tmpfile
1669 }
1670
1671 hw_stats_monitor_test()
1672 {
1673         local dev=$1; shift
1674         local type=$1; shift
1675         local make_suitable=$1; shift
1676         local make_unsuitable=$1; shift
1677         local ip=${1-ip}; shift
1678
1679         RET=0
1680
1681         # Expect a notification about enablement.
1682         local ipmout=$(start_ip_monitor stats "$ip")
1683         $ip stats set dev $dev ${type}_stats on
1684         stop_ip_monitor $ipmout 1 "${type}_stats enablement"
1685
1686         # Expect a notification about offload.
1687         local ipmout=$(start_ip_monitor stats "$ip")
1688         $make_suitable
1689         stop_ip_monitor $ipmout 1 "${type}_stats installation"
1690
1691         # Expect a notification about loss of offload.
1692         local ipmout=$(start_ip_monitor stats "$ip")
1693         $make_unsuitable
1694         stop_ip_monitor $ipmout 1 "${type}_stats deinstallation"
1695
1696         # Expect a notification about disablement
1697         local ipmout=$(start_ip_monitor stats "$ip")
1698         $ip stats set dev $dev ${type}_stats off
1699         stop_ip_monitor $ipmout 1 "${type}_stats disablement"
1700
1701         log_test "${type}_stats notifications"
1702 }
1703
1704 ipv4_to_bytes()
1705 {
1706         local IP=$1; shift
1707
1708         printf '%02x:' ${IP//./ } |
1709             sed 's/:$//'
1710 }
1711
1712 # Convert a given IPv6 address, `IP' such that the :: token, if present, is
1713 # expanded, and each 16-bit group is padded with zeroes to be 4 hexadecimal
1714 # digits. An optional `BYTESEP' parameter can be given to further separate
1715 # individual bytes of each 16-bit group.
1716 expand_ipv6()
1717 {
1718         local IP=$1; shift
1719         local bytesep=$1; shift
1720
1721         local cvt_ip=${IP/::/_}
1722         local colons=${cvt_ip//[^:]/}
1723         local allcol=:::::::
1724         # IP where :: -> the appropriate number of colons:
1725         local allcol_ip=${cvt_ip/_/${allcol:${#colons}}}
1726
1727         echo $allcol_ip | tr : '\n' |
1728             sed s/^/0000/ |
1729             sed 's/.*\(..\)\(..\)/\1'"$bytesep"'\2/' |
1730             tr '\n' : |
1731             sed 's/:$//'
1732 }
1733
1734 ipv6_to_bytes()
1735 {
1736         local IP=$1; shift
1737
1738         expand_ipv6 "$IP" :
1739 }
1740
1741 u16_to_bytes()
1742 {
1743         local u16=$1; shift
1744
1745         printf "%04x" $u16 | sed 's/^/000/;s/^.*\(..\)\(..\)$/\1:\2/'
1746 }
1747
1748 # Given a mausezahn-formatted payload (colon-separated bytes given as %02x),
1749 # possibly with a keyword CHECKSUM stashed where a 16-bit checksum should be,
1750 # calculate checksum as per RFC 1071, assuming the CHECKSUM field (if any)
1751 # stands for 00:00.
1752 payload_template_calc_checksum()
1753 {
1754         local payload=$1; shift
1755
1756         (
1757             # Set input radix.
1758             echo "16i"
1759             # Push zero for the initial checksum.
1760             echo 0
1761
1762             # Pad the payload with a terminating 00: in case we get an odd
1763             # number of bytes.
1764             echo "${payload%:}:00:" |
1765                 sed 's/CHECKSUM/00:00/g' |
1766                 tr '[:lower:]' '[:upper:]' |
1767                 # Add the word to the checksum.
1768                 sed 's/\(..\):\(..\):/\1\2+\n/g' |
1769                 # Strip the extra odd byte we pushed if left unconverted.
1770                 sed 's/\(..\):$//'
1771
1772             echo "10000 ~ +"    # Calculate and add carry.
1773             echo "FFFF r - p"   # Bit-flip and print.
1774         ) |
1775             dc |
1776             tr '[:upper:]' '[:lower:]'
1777 }
1778
1779 payload_template_expand_checksum()
1780 {
1781         local payload=$1; shift
1782         local checksum=$1; shift
1783
1784         local ckbytes=$(u16_to_bytes $checksum)
1785
1786         echo "$payload" | sed "s/CHECKSUM/$ckbytes/g"
1787 }
1788
1789 payload_template_nbytes()
1790 {
1791         local payload=$1; shift
1792
1793         payload_template_expand_checksum "${payload%:}" 0 |
1794                 sed 's/:/\n/g' | wc -l
1795 }
1796
1797 igmpv3_is_in_get()
1798 {
1799         local GRP=$1; shift
1800         local sources=("$@")
1801
1802         local igmpv3
1803         local nsources=$(u16_to_bytes ${#sources[@]})
1804
1805         # IS_IN ( $sources )
1806         igmpv3=$(:
1807                 )"22:"$(                        : Type - Membership Report
1808                 )"00:"$(                        : Reserved
1809                 )"CHECKSUM:"$(                  : Checksum
1810                 )"00:00:"$(                     : Reserved
1811                 )"00:01:"$(                     : Number of Group Records
1812                 )"01:"$(                        : Record Type - IS_IN
1813                 )"00:"$(                        : Aux Data Len
1814                 )"${nsources}:"$(               : Number of Sources
1815                 )"$(ipv4_to_bytes $GRP):"$(     : Multicast Address
1816                 )"$(for src in "${sources[@]}"; do
1817                         ipv4_to_bytes $src
1818                         echo -n :
1819                     done)"$(                    : Source Addresses
1820                 )
1821         local checksum=$(payload_template_calc_checksum "$igmpv3")
1822
1823         payload_template_expand_checksum "$igmpv3" $checksum
1824 }
1825
1826 igmpv2_leave_get()
1827 {
1828         local GRP=$1; shift
1829
1830         local payload=$(:
1831                 )"17:"$(                        : Type - Leave Group
1832                 )"00:"$(                        : Max Resp Time - not meaningful
1833                 )"CHECKSUM:"$(                  : Checksum
1834                 )"$(ipv4_to_bytes $GRP)"$(      : Group Address
1835                 )
1836         local checksum=$(payload_template_calc_checksum "$payload")
1837
1838         payload_template_expand_checksum "$payload" $checksum
1839 }
1840
1841 mldv2_is_in_get()
1842 {
1843         local SIP=$1; shift
1844         local GRP=$1; shift
1845         local sources=("$@")
1846
1847         local hbh
1848         local icmpv6
1849         local nsources=$(u16_to_bytes ${#sources[@]})
1850
1851         hbh=$(:
1852                 )"3a:"$(                        : Next Header - ICMPv6
1853                 )"00:"$(                        : Hdr Ext Len
1854                 )"00:00:00:00:00:00:"$(         : Options and Padding
1855                 )
1856
1857         icmpv6=$(:
1858                 )"8f:"$(                        : Type - MLDv2 Report
1859                 )"00:"$(                        : Code
1860                 )"CHECKSUM:"$(                  : Checksum
1861                 )"00:00:"$(                     : Reserved
1862                 )"00:01:"$(                     : Number of Group Records
1863                 )"01:"$(                        : Record Type - IS_IN
1864                 )"00:"$(                        : Aux Data Len
1865                 )"${nsources}:"$(               : Number of Sources
1866                 )"$(ipv6_to_bytes $GRP):"$(     : Multicast address
1867                 )"$(for src in "${sources[@]}"; do
1868                         ipv6_to_bytes $src
1869                         echo -n :
1870                     done)"$(                    : Source Addresses
1871                 )
1872
1873         local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
1874         local sudohdr=$(:
1875                 )"$(ipv6_to_bytes $SIP):"$(     : SIP
1876                 )"$(ipv6_to_bytes $GRP):"$(     : DIP is multicast address
1877                 )"${len}:"$(                    : Upper-layer length
1878                 )"00:3a:"$(                     : Zero and next-header
1879                 )
1880         local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
1881
1882         payload_template_expand_checksum "$hbh$icmpv6" $checksum
1883 }
1884
1885 mldv1_done_get()
1886 {
1887         local SIP=$1; shift
1888         local GRP=$1; shift
1889
1890         local hbh
1891         local icmpv6
1892
1893         hbh=$(:
1894                 )"3a:"$(                        : Next Header - ICMPv6
1895                 )"00:"$(                        : Hdr Ext Len
1896                 )"00:00:00:00:00:00:"$(         : Options and Padding
1897                 )
1898
1899         icmpv6=$(:
1900                 )"84:"$(                        : Type - MLDv1 Done
1901                 )"00:"$(                        : Code
1902                 )"CHECKSUM:"$(                  : Checksum
1903                 )"00:00:"$(                     : Max Resp Delay - not meaningful
1904                 )"00:00:"$(                     : Reserved
1905                 )"$(ipv6_to_bytes $GRP):"$(     : Multicast address
1906                 )
1907
1908         local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6))
1909         local sudohdr=$(:
1910                 )"$(ipv6_to_bytes $SIP):"$(     : SIP
1911                 )"$(ipv6_to_bytes $GRP):"$(     : DIP is multicast address
1912                 )"${len}:"$(                    : Upper-layer length
1913                 )"00:3a:"$(                     : Zero and next-header
1914                 )
1915         local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6})
1916
1917         payload_template_expand_checksum "$hbh$icmpv6" $checksum
1918 }
1919
1920 bail_on_lldpad()
1921 {
1922         local reason1="$1"; shift
1923         local reason2="$1"; shift
1924
1925         if systemctl is-active --quiet lldpad; then
1926
1927                 cat >/dev/stderr <<-EOF
1928                 WARNING: lldpad is running
1929
1930                         lldpad will likely $reason1, and this test will
1931                         $reason2. Both are not supported at the same time,
1932                         one of them is arbitrarily going to overwrite the
1933                         other. That will cause spurious failures (or, unlikely,
1934                         passes) of this test.
1935                 EOF
1936
1937                 if [[ -z $ALLOW_LLDPAD ]]; then
1938                         cat >/dev/stderr <<-EOF
1939
1940                                 If you want to run the test anyway, please set
1941                                 an environment variable ALLOW_LLDPAD to a
1942                                 non-empty string.
1943                         EOF
1944                         exit 1
1945                 else
1946                         return
1947                 fi
1948         fi
1949 }