blob: d0af5210936002d66016c4e05ccf12ce760a3251 [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
79for i in $(eval echo {1..$NUM_NETIFS}); do
80 ip link show dev ${NETIFS[p$i]} &> /dev/null
81 if [[ $? -ne 0 ]]; then
82 echo "SKIP: could not find all required interfaces"
83 exit 0
84 fi
85done
86
87##############################################################################
88# Helpers
89
90# Exit status to return at the end. Set in case one of the tests fails.
91EXIT_STATUS=0
92# Per-test return value. Clear at the beginning of each test.
93RET=0
94
95check_err()
96{
97 local err=$1
98 local msg=$2
99
100 if [[ $RET -eq 0 && $err -ne 0 ]]; then
101 RET=$err
102 retmsg=$msg
103 fi
104}
105
106check_fail()
107{
108 local err=$1
109 local msg=$2
110
111 if [[ $RET -eq 0 && $err -eq 0 ]]; then
112 RET=1
113 retmsg=$msg
114 fi
115}
116
117log_test()
118{
119 local test_name=$1
120 local opt_str=$2
121
122 if [[ $# -eq 2 ]]; then
123 opt_str="($opt_str)"
124 fi
125
126 if [[ $RET -ne 0 ]]; then
127 EXIT_STATUS=1
128 printf "TEST: %-60s [FAIL]\n" "$test_name $opt_str"
129 if [[ ! -z "$retmsg" ]]; then
130 printf "\t%s\n" "$retmsg"
131 fi
132 if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
133 echo "Hit enter to continue, 'q' to quit"
134 read a
135 [ "$a" = "q" ] && exit 1
136 fi
137 return 1
138 fi
139
140 printf "TEST: %-60s [PASS]\n" "$test_name $opt_str"
141 return 0
142}
143
Ido Schimmel3d578d82018-02-28 12:25:11 +0200144log_info()
145{
146 local msg=$1
147
148 echo "INFO: $msg"
149}
150
Ido Schimmel73bae672018-02-28 12:25:06 +0200151setup_wait()
152{
153 for i in $(eval echo {1..$NUM_NETIFS}); do
154 while true; do
155 ip link show dev ${NETIFS[p$i]} up \
156 | grep 'state UP' &> /dev/null
157 if [[ $? -ne 0 ]]; then
158 sleep 1
159 else
160 break
161 fi
162 done
163 done
164
165 # Make sure links are ready.
166 sleep $WAIT_TIME
167}
168
169pre_cleanup()
170{
171 if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
172 echo "Pausing before cleanup, hit any key to continue"
173 read
174 fi
175}
176
177vrf_prepare()
178{
179 ip -4 rule add pref 32765 table local
180 ip -4 rule del pref 0
181 ip -6 rule add pref 32765 table local
182 ip -6 rule del pref 0
183}
184
185vrf_cleanup()
186{
187 ip -6 rule add pref 0 table local
188 ip -6 rule del pref 32765
189 ip -4 rule add pref 0 table local
190 ip -4 rule del pref 32765
191}
192
193__last_tb_id=0
194declare -A __TB_IDS
195
196__vrf_td_id_assign()
197{
198 local vrf_name=$1
199
200 __last_tb_id=$((__last_tb_id + 1))
201 __TB_IDS[$vrf_name]=$__last_tb_id
202 return $__last_tb_id
203}
204
205__vrf_td_id_lookup()
206{
207 local vrf_name=$1
208
209 return ${__TB_IDS[$vrf_name]}
210}
211
212vrf_create()
213{
214 local vrf_name=$1
215 local tb_id
216
217 __vrf_td_id_assign $vrf_name
218 tb_id=$?
219
220 ip link add dev $vrf_name type vrf table $tb_id
221 ip -4 route add table $tb_id unreachable default metric 4278198272
222 ip -6 route add table $tb_id unreachable default metric 4278198272
223}
224
225vrf_destroy()
226{
227 local vrf_name=$1
228 local tb_id
229
230 __vrf_td_id_lookup $vrf_name
231 tb_id=$?
232
233 ip -6 route del table $tb_id unreachable default metric 4278198272
234 ip -4 route del table $tb_id unreachable default metric 4278198272
235 ip link del dev $vrf_name
236}
237
238__addr_add_del()
239{
240 local if_name=$1
241 local add_del=$2
242 local array
243
244 shift
245 shift
246 array=("${@}")
247
248 for addrstr in "${array[@]}"; do
249 ip address $add_del $addrstr dev $if_name
250 done
251}
252
253simple_if_init()
254{
255 local if_name=$1
256 local vrf_name
257 local array
258
259 shift
260 vrf_name=v$if_name
261 array=("${@}")
262
263 vrf_create $vrf_name
264 ip link set dev $if_name master $vrf_name
265 ip link set dev $vrf_name up
266 ip link set dev $if_name up
267
268 __addr_add_del $if_name add "${array[@]}"
269}
270
271simple_if_fini()
272{
273 local if_name=$1
274 local vrf_name
275 local array
276
277 shift
278 vrf_name=v$if_name
279 array=("${@}")
280
281 __addr_add_del $if_name del "${array[@]}"
282
283 ip link set dev $if_name down
284 vrf_destroy $vrf_name
285}
286
287master_name_get()
288{
289 local if_name=$1
290
291 ip -j link show dev $if_name | jq -r '.[]["master"]'
292}
293
Ido Schimmel3d578d82018-02-28 12:25:11 +0200294link_stats_tx_packets_get()
295{
296 local if_name=$1
297
298 ip -j -s link show dev $if_name | jq '.[]["stats64"]["tx"]["packets"]'
299}
300
Jiri Pirko4e4272d2018-02-28 12:25:14 +0200301mac_get()
302{
303 local if_name=$1
304
305 ip -j link show dev $if_name | jq -r '.[]["address"]'
306}
307
Ido Schimmeld4deb012018-02-28 12:25:07 +0200308bridge_ageing_time_get()
309{
310 local bridge=$1
311 local ageing_time
312
313 # Need to divide by 100 to convert to seconds.
314 ageing_time=$(ip -j -d link show dev $bridge \
315 | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
316 echo $((ageing_time / 100))
317}
318
Ido Schimmel7b7bc872018-02-28 12:25:09 +0200319forwarding_enable()
320{
321 ipv4_fwd=$(sysctl -n net.ipv4.conf.all.forwarding)
322 ipv6_fwd=$(sysctl -n net.ipv6.conf.all.forwarding)
323
324 sysctl -q -w net.ipv4.conf.all.forwarding=1
325 sysctl -q -w net.ipv6.conf.all.forwarding=1
326}
327
328forwarding_restore()
329{
330 sysctl -q -w net.ipv6.conf.all.forwarding=$ipv6_fwd
331 sysctl -q -w net.ipv4.conf.all.forwarding=$ipv4_fwd
332}
333
Jiri Pirko2f19f212018-02-28 12:25:13 +0200334tc_offload_check()
335{
336 for i in $(eval echo {1..$NUM_NETIFS}); do
337 ethtool -k ${NETIFS[p$i]} \
338 | grep "hw-tc-offload: on" &> /dev/null
339 if [[ $? -ne 0 ]]; then
340 return 1
341 fi
342 done
343
344 return 0
345}
346
Ido Schimmel73bae672018-02-28 12:25:06 +0200347##############################################################################
348# Tests
349
350ping_test()
351{
352 local if_name=$1
353 local dip=$2
354 local vrf_name
355
356 RET=0
357
358 vrf_name=$(master_name_get $if_name)
359 ip vrf exec $vrf_name $PING $dip -c 10 -i 0.1 -w 2 &> /dev/null
360 check_err $?
361 log_test "ping"
362}
363
364ping6_test()
365{
366 local if_name=$1
367 local dip=$2
368 local vrf_name
369
370 RET=0
371
372 vrf_name=$(master_name_get $if_name)
373 ip vrf exec $vrf_name $PING6 $dip -c 10 -i 0.1 -w 2 &> /dev/null
374 check_err $?
375 log_test "ping6"
376}
Ido Schimmeld4deb012018-02-28 12:25:07 +0200377
378learning_test()
379{
380 local bridge=$1
381 local br_port1=$2 # Connected to `host1_if`.
382 local host1_if=$3
383 local host2_if=$4
384 local mac=de:ad:be:ef:13:37
385 local ageing_time
386
387 RET=0
388
389 bridge -j fdb show br $bridge brport $br_port1 \
390 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
391 check_fail $? "Found FDB record when should not"
392
393 # Disable unknown unicast flooding on `br_port1` to make sure
394 # packets are only forwarded through the port after a matching
395 # FDB entry was installed.
396 bridge link set dev $br_port1 flood off
397
398 tc qdisc add dev $host1_if ingress
399 tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
400 flower dst_mac $mac action drop
401
402 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
403 sleep 1
404
405 tc -j -s filter show dev $host1_if ingress \
406 | jq -e ".[] | select(.options.handle == 101) \
407 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
408 check_fail $? "Packet reached second host when should not"
409
410 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
411 sleep 1
412
413 bridge -j fdb show br $bridge brport $br_port1 \
414 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
415 check_err $? "Did not find FDB record when should"
416
417 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
418 sleep 1
419
420 tc -j -s filter show dev $host1_if ingress \
421 | jq -e ".[] | select(.options.handle == 101) \
422 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
423 check_err $? "Packet did not reach second host when should"
424
425 # Wait for 10 seconds after the ageing time to make sure FDB
426 # record was aged-out.
427 ageing_time=$(bridge_ageing_time_get $bridge)
428 sleep $((ageing_time + 10))
429
430 bridge -j fdb show br $bridge brport $br_port1 \
431 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
432 check_fail $? "Found FDB record when should not"
433
434 bridge link set dev $br_port1 learning off
435
436 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
437 sleep 1
438
439 bridge -j fdb show br $bridge brport $br_port1 \
440 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
441 check_fail $? "Found FDB record when should not"
442
443 bridge link set dev $br_port1 learning on
444
445 tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
446 tc qdisc del dev $host1_if ingress
447
448 bridge link set dev $br_port1 flood on
449
450 log_test "FDB learning"
451}
Ido Schimmel236dd502018-02-28 12:25:08 +0200452
453flood_test_do()
454{
455 local should_flood=$1
456 local mac=$2
457 local ip=$3
458 local host1_if=$4
459 local host2_if=$5
460 local err=0
461
462 # Add an ACL on `host2_if` which will tell us whether the packet
463 # was flooded to it or not.
464 tc qdisc add dev $host2_if ingress
465 tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
466 flower dst_mac $mac action drop
467
468 $MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
469 sleep 1
470
471 tc -j -s filter show dev $host2_if ingress \
472 | jq -e ".[] | select(.options.handle == 101) \
473 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
474 if [[ $? -ne 0 && $should_flood == "true" || \
475 $? -eq 0 && $should_flood == "false" ]]; then
476 err=1
477 fi
478
479 tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
480 tc qdisc del dev $host2_if ingress
481
482 return $err
483}
484
485flood_unicast_test()
486{
487 local br_port=$1
488 local host1_if=$2
489 local host2_if=$3
490 local mac=de:ad:be:ef:13:37
491 local ip=192.0.2.100
492
493 RET=0
494
495 bridge link set dev $br_port flood off
496
497 flood_test_do false $mac $ip $host1_if $host2_if
498 check_err $? "Packet flooded when should not"
499
500 bridge link set dev $br_port flood on
501
502 flood_test_do true $mac $ip $host1_if $host2_if
503 check_err $? "Packet was not flooded when should"
504
505 log_test "Unknown unicast flood"
506}
507
508flood_multicast_test()
509{
510 local br_port=$1
511 local host1_if=$2
512 local host2_if=$3
513 local mac=01:00:5e:00:00:01
514 local ip=239.0.0.1
515
516 RET=0
517
518 bridge link set dev $br_port mcast_flood off
519
520 flood_test_do false $mac $ip $host1_if $host2_if
521 check_err $? "Packet flooded when should not"
522
523 bridge link set dev $br_port mcast_flood on
524
525 flood_test_do true $mac $ip $host1_if $host2_if
526 check_err $? "Packet was not flooded when should"
527
528 log_test "Unregistered multicast flood"
529}
530
531flood_test()
532{
533 # `br_port` is connected to `host2_if`
534 local br_port=$1
535 local host1_if=$2
536 local host2_if=$3
537
538 flood_unicast_test $br_port $host1_if $host2_if
539 flood_multicast_test $br_port $host1_if $host2_if
540}