blob: 273511ef2b437548307cdfc79067ca762901aeca [file] [log] [blame]
Ido Schimmel73bae672018-02-28 12:25:06 +02001#!/bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4##############################################################################
5# Defines
6
7# Can be overridden by the configuration file.
8PING=${PING:=ping}
9PING6=${PING6:=ping6}
Ido Schimmeld4deb012018-02-28 12:25:07 +020010MZ=${MZ:=mausezahn}
Ido Schimmel73bae672018-02-28 12:25:06 +020011WAIT_TIME=${WAIT_TIME:=5}
12PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no}
13PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no}
14
15if [[ -f forwarding.config ]]; then
16 source forwarding.config
17fi
18
19##############################################################################
20# Sanity checks
21
David Ahern198979b2018-03-01 13:49:30 -080022check_tc_version()
23{
24 tc -j &> /dev/null
25 if [[ $? -ne 0 ]]; then
26 echo "SKIP: iproute2 too old; tc is missing JSON support"
27 exit 1
28 fi
29
30 tc filter help 2>&1 | grep block &> /dev/null
31 if [[ $? -ne 0 ]]; then
32 echo "SKIP: iproute2 too old; tc is missing shared block support"
33 exit 1
34 fi
35}
36
Ido Schimmel73bae672018-02-28 12:25:06 +020037if [[ "$(id -u)" -ne 0 ]]; then
38 echo "SKIP: need root privileges"
39 exit 0
40fi
41
David Ahern198979b2018-03-01 13:49:30 -080042if [[ "$CHECK_TC" = "yes" ]]; then
43 check_tc_version
Jiri Pirko4908e242018-02-28 12:25:19 +020044fi
45
Ido Schimmel73bae672018-02-28 12:25:06 +020046if [[ ! -x "$(command -v jq)" ]]; then
47 echo "SKIP: jq not installed"
David Ahern198979b2018-03-01 13:49:30 -080048 exit 1
Ido Schimmel73bae672018-02-28 12:25:06 +020049fi
50
Ido Schimmeld4deb012018-02-28 12:25:07 +020051if [[ ! -x "$(command -v $MZ)" ]]; then
52 echo "SKIP: $MZ not installed"
53 exit 0
54fi
55
Ido Schimmel73bae672018-02-28 12:25:06 +020056if [[ ! -v NUM_NETIFS ]]; then
57 echo "SKIP: importer does not define \"NUM_NETIFS\""
58 exit 0
59fi
60
61##############################################################################
Jiri Pirko781fe632018-02-28 12:25:15 +020062# Command line options handling
63
64count=0
65
66while [[ $# -gt 0 ]]; do
67 if [[ "$count" -eq "0" ]]; then
68 unset NETIFS
69 declare -A NETIFS
70 fi
71 count=$((count + 1))
72 NETIFS[p$count]="$1"
73 shift
74done
75
76##############################################################################
Ido Schimmel73bae672018-02-28 12:25:06 +020077# Network interfaces configuration
78
David Ahern190f8872018-03-04 17:37:47 -080079create_netif_veth()
80{
81 local i
82
83 for i in $(eval echo {1..$NUM_NETIFS}); do
84 local j=$((i+1))
85
86 ip link show dev ${NETIFS[p$i]} &> /dev/null
87 if [[ $? -ne 0 ]]; then
88 ip link add ${NETIFS[p$i]} type veth \
89 peer name ${NETIFS[p$j]}
90 if [[ $? -ne 0 ]]; then
91 echo "Failed to create netif"
92 exit 1
93 fi
94 fi
95 i=$j
96 done
97}
98
99create_netif()
100{
101 case "$NETIF_TYPE" in
102 veth) create_netif_veth
103 ;;
104 *) echo "Can not create interfaces of type \'$NETIF_TYPE\'"
105 exit 1
106 ;;
107 esac
108}
109
110if [[ "$NETIF_CREATE" = "yes" ]]; then
111 create_netif
112fi
113
Ido Schimmel73bae672018-02-28 12:25:06 +0200114for i in $(eval echo {1..$NUM_NETIFS}); do
115 ip link show dev ${NETIFS[p$i]} &> /dev/null
116 if [[ $? -ne 0 ]]; then
117 echo "SKIP: could not find all required interfaces"
118 exit 0
119 fi
120done
121
122##############################################################################
123# Helpers
124
125# Exit status to return at the end. Set in case one of the tests fails.
126EXIT_STATUS=0
127# Per-test return value. Clear at the beginning of each test.
128RET=0
129
130check_err()
131{
132 local err=$1
133 local msg=$2
134
135 if [[ $RET -eq 0 && $err -ne 0 ]]; then
136 RET=$err
137 retmsg=$msg
138 fi
139}
140
141check_fail()
142{
143 local err=$1
144 local msg=$2
145
146 if [[ $RET -eq 0 && $err -eq 0 ]]; then
147 RET=1
148 retmsg=$msg
149 fi
150}
151
152log_test()
153{
154 local test_name=$1
155 local opt_str=$2
156
157 if [[ $# -eq 2 ]]; then
158 opt_str="($opt_str)"
159 fi
160
161 if [[ $RET -ne 0 ]]; then
162 EXIT_STATUS=1
163 printf "TEST: %-60s [FAIL]\n" "$test_name $opt_str"
164 if [[ ! -z "$retmsg" ]]; then
165 printf "\t%s\n" "$retmsg"
166 fi
167 if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
168 echo "Hit enter to continue, 'q' to quit"
169 read a
170 [ "$a" = "q" ] && exit 1
171 fi
172 return 1
173 fi
174
175 printf "TEST: %-60s [PASS]\n" "$test_name $opt_str"
176 return 0
177}
178
Ido Schimmel3d578d82018-02-28 12:25:11 +0200179log_info()
180{
181 local msg=$1
182
183 echo "INFO: $msg"
184}
185
Ido Schimmel73bae672018-02-28 12:25:06 +0200186setup_wait()
187{
188 for i in $(eval echo {1..$NUM_NETIFS}); do
189 while true; do
190 ip link show dev ${NETIFS[p$i]} up \
191 | grep 'state UP' &> /dev/null
192 if [[ $? -ne 0 ]]; then
193 sleep 1
194 else
195 break
196 fi
197 done
198 done
199
200 # Make sure links are ready.
201 sleep $WAIT_TIME
202}
203
204pre_cleanup()
205{
206 if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
207 echo "Pausing before cleanup, hit any key to continue"
208 read
209 fi
210}
211
212vrf_prepare()
213{
214 ip -4 rule add pref 32765 table local
215 ip -4 rule del pref 0
216 ip -6 rule add pref 32765 table local
217 ip -6 rule del pref 0
218}
219
220vrf_cleanup()
221{
222 ip -6 rule add pref 0 table local
223 ip -6 rule del pref 32765
224 ip -4 rule add pref 0 table local
225 ip -4 rule del pref 32765
226}
227
228__last_tb_id=0
229declare -A __TB_IDS
230
231__vrf_td_id_assign()
232{
233 local vrf_name=$1
234
235 __last_tb_id=$((__last_tb_id + 1))
236 __TB_IDS[$vrf_name]=$__last_tb_id
237 return $__last_tb_id
238}
239
240__vrf_td_id_lookup()
241{
242 local vrf_name=$1
243
244 return ${__TB_IDS[$vrf_name]}
245}
246
247vrf_create()
248{
249 local vrf_name=$1
250 local tb_id
251
252 __vrf_td_id_assign $vrf_name
253 tb_id=$?
254
255 ip link add dev $vrf_name type vrf table $tb_id
256 ip -4 route add table $tb_id unreachable default metric 4278198272
257 ip -6 route add table $tb_id unreachable default metric 4278198272
258}
259
260vrf_destroy()
261{
262 local vrf_name=$1
263 local tb_id
264
265 __vrf_td_id_lookup $vrf_name
266 tb_id=$?
267
268 ip -6 route del table $tb_id unreachable default metric 4278198272
269 ip -4 route del table $tb_id unreachable default metric 4278198272
270 ip link del dev $vrf_name
271}
272
273__addr_add_del()
274{
275 local if_name=$1
276 local add_del=$2
277 local array
278
279 shift
280 shift
281 array=("${@}")
282
283 for addrstr in "${array[@]}"; do
284 ip address $add_del $addrstr dev $if_name
285 done
286}
287
288simple_if_init()
289{
290 local if_name=$1
291 local vrf_name
292 local array
293
294 shift
295 vrf_name=v$if_name
296 array=("${@}")
297
298 vrf_create $vrf_name
299 ip link set dev $if_name master $vrf_name
300 ip link set dev $vrf_name up
301 ip link set dev $if_name up
302
303 __addr_add_del $if_name add "${array[@]}"
304}
305
306simple_if_fini()
307{
308 local if_name=$1
309 local vrf_name
310 local array
311
312 shift
313 vrf_name=v$if_name
314 array=("${@}")
315
316 __addr_add_del $if_name del "${array[@]}"
317
318 ip link set dev $if_name down
319 vrf_destroy $vrf_name
320}
321
322master_name_get()
323{
324 local if_name=$1
325
326 ip -j link show dev $if_name | jq -r '.[]["master"]'
327}
328
Ido Schimmel3d578d82018-02-28 12:25:11 +0200329link_stats_tx_packets_get()
330{
331 local if_name=$1
332
333 ip -j -s link show dev $if_name | jq '.[]["stats64"]["tx"]["packets"]'
334}
335
Jiri Pirko4e4272d2018-02-28 12:25:14 +0200336mac_get()
337{
338 local if_name=$1
339
340 ip -j link show dev $if_name | jq -r '.[]["address"]'
341}
342
Ido Schimmeld4deb012018-02-28 12:25:07 +0200343bridge_ageing_time_get()
344{
345 local bridge=$1
346 local ageing_time
347
348 # Need to divide by 100 to convert to seconds.
349 ageing_time=$(ip -j -d link show dev $bridge \
350 | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
351 echo $((ageing_time / 100))
352}
353
Ido Schimmel7b7bc872018-02-28 12:25:09 +0200354forwarding_enable()
355{
356 ipv4_fwd=$(sysctl -n net.ipv4.conf.all.forwarding)
357 ipv6_fwd=$(sysctl -n net.ipv6.conf.all.forwarding)
358
359 sysctl -q -w net.ipv4.conf.all.forwarding=1
360 sysctl -q -w net.ipv6.conf.all.forwarding=1
361}
362
363forwarding_restore()
364{
365 sysctl -q -w net.ipv6.conf.all.forwarding=$ipv6_fwd
366 sysctl -q -w net.ipv4.conf.all.forwarding=$ipv4_fwd
367}
368
Jiri Pirko2f19f212018-02-28 12:25:13 +0200369tc_offload_check()
370{
371 for i in $(eval echo {1..$NUM_NETIFS}); do
372 ethtool -k ${NETIFS[p$i]} \
373 | grep "hw-tc-offload: on" &> /dev/null
374 if [[ $? -ne 0 ]]; then
375 return 1
376 fi
377 done
378
379 return 0
380}
381
Ido Schimmel73bae672018-02-28 12:25:06 +0200382##############################################################################
383# Tests
384
385ping_test()
386{
387 local if_name=$1
388 local dip=$2
389 local vrf_name
390
391 RET=0
392
393 vrf_name=$(master_name_get $if_name)
394 ip vrf exec $vrf_name $PING $dip -c 10 -i 0.1 -w 2 &> /dev/null
395 check_err $?
396 log_test "ping"
397}
398
399ping6_test()
400{
401 local if_name=$1
402 local dip=$2
403 local vrf_name
404
405 RET=0
406
407 vrf_name=$(master_name_get $if_name)
408 ip vrf exec $vrf_name $PING6 $dip -c 10 -i 0.1 -w 2 &> /dev/null
409 check_err $?
410 log_test "ping6"
411}
Ido Schimmeld4deb012018-02-28 12:25:07 +0200412
413learning_test()
414{
415 local bridge=$1
416 local br_port1=$2 # Connected to `host1_if`.
417 local host1_if=$3
418 local host2_if=$4
419 local mac=de:ad:be:ef:13:37
420 local ageing_time
421
422 RET=0
423
424 bridge -j fdb show br $bridge brport $br_port1 \
425 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
426 check_fail $? "Found FDB record when should not"
427
428 # Disable unknown unicast flooding on `br_port1` to make sure
429 # packets are only forwarded through the port after a matching
430 # FDB entry was installed.
431 bridge link set dev $br_port1 flood off
432
433 tc qdisc add dev $host1_if ingress
434 tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
435 flower dst_mac $mac action drop
436
437 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
438 sleep 1
439
440 tc -j -s filter show dev $host1_if ingress \
441 | jq -e ".[] | select(.options.handle == 101) \
442 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
443 check_fail $? "Packet reached second host when should not"
444
445 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
446 sleep 1
447
448 bridge -j fdb show br $bridge brport $br_port1 \
449 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
450 check_err $? "Did not find FDB record when should"
451
452 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
453 sleep 1
454
455 tc -j -s filter show dev $host1_if ingress \
456 | jq -e ".[] | select(.options.handle == 101) \
457 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
458 check_err $? "Packet did not reach second host when should"
459
460 # Wait for 10 seconds after the ageing time to make sure FDB
461 # record was aged-out.
462 ageing_time=$(bridge_ageing_time_get $bridge)
463 sleep $((ageing_time + 10))
464
465 bridge -j fdb show br $bridge brport $br_port1 \
466 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
467 check_fail $? "Found FDB record when should not"
468
469 bridge link set dev $br_port1 learning off
470
471 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
472 sleep 1
473
474 bridge -j fdb show br $bridge brport $br_port1 \
475 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
476 check_fail $? "Found FDB record when should not"
477
478 bridge link set dev $br_port1 learning on
479
480 tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
481 tc qdisc del dev $host1_if ingress
482
483 bridge link set dev $br_port1 flood on
484
485 log_test "FDB learning"
486}
Ido Schimmel236dd502018-02-28 12:25:08 +0200487
488flood_test_do()
489{
490 local should_flood=$1
491 local mac=$2
492 local ip=$3
493 local host1_if=$4
494 local host2_if=$5
495 local err=0
496
497 # Add an ACL on `host2_if` which will tell us whether the packet
498 # was flooded to it or not.
499 tc qdisc add dev $host2_if ingress
500 tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
501 flower dst_mac $mac action drop
502
503 $MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
504 sleep 1
505
506 tc -j -s filter show dev $host2_if ingress \
507 | jq -e ".[] | select(.options.handle == 101) \
508 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
509 if [[ $? -ne 0 && $should_flood == "true" || \
510 $? -eq 0 && $should_flood == "false" ]]; then
511 err=1
512 fi
513
514 tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
515 tc qdisc del dev $host2_if ingress
516
517 return $err
518}
519
520flood_unicast_test()
521{
522 local br_port=$1
523 local host1_if=$2
524 local host2_if=$3
525 local mac=de:ad:be:ef:13:37
526 local ip=192.0.2.100
527
528 RET=0
529
530 bridge link set dev $br_port flood off
531
532 flood_test_do false $mac $ip $host1_if $host2_if
533 check_err $? "Packet flooded when should not"
534
535 bridge link set dev $br_port flood on
536
537 flood_test_do true $mac $ip $host1_if $host2_if
538 check_err $? "Packet was not flooded when should"
539
540 log_test "Unknown unicast flood"
541}
542
543flood_multicast_test()
544{
545 local br_port=$1
546 local host1_if=$2
547 local host2_if=$3
548 local mac=01:00:5e:00:00:01
549 local ip=239.0.0.1
550
551 RET=0
552
553 bridge link set dev $br_port mcast_flood off
554
555 flood_test_do false $mac $ip $host1_if $host2_if
556 check_err $? "Packet flooded when should not"
557
558 bridge link set dev $br_port mcast_flood on
559
560 flood_test_do true $mac $ip $host1_if $host2_if
561 check_err $? "Packet was not flooded when should"
562
563 log_test "Unregistered multicast flood"
564}
565
566flood_test()
567{
568 # `br_port` is connected to `host2_if`
569 local br_port=$1
570 local host1_if=$2
571 local host2_if=$3
572
573 flood_unicast_test $br_port $host1_if $host2_if
574 flood_multicast_test $br_port $host1_if $host2_if
575}