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