blob: e2a4ee8946ef38dc1dd18bcb6e50d4cff03644b7 [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
116setup_wait()
117{
118 for i in $(eval echo {1..$NUM_NETIFS}); do
119 while true; do
120 ip link show dev ${NETIFS[p$i]} up \
121 | grep 'state UP' &> /dev/null
122 if [[ $? -ne 0 ]]; then
123 sleep 1
124 else
125 break
126 fi
127 done
128 done
129
130 # Make sure links are ready.
131 sleep $WAIT_TIME
132}
133
134pre_cleanup()
135{
136 if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then
137 echo "Pausing before cleanup, hit any key to continue"
138 read
139 fi
140}
141
142vrf_prepare()
143{
144 ip -4 rule add pref 32765 table local
145 ip -4 rule del pref 0
146 ip -6 rule add pref 32765 table local
147 ip -6 rule del pref 0
148}
149
150vrf_cleanup()
151{
152 ip -6 rule add pref 0 table local
153 ip -6 rule del pref 32765
154 ip -4 rule add pref 0 table local
155 ip -4 rule del pref 32765
156}
157
158__last_tb_id=0
159declare -A __TB_IDS
160
161__vrf_td_id_assign()
162{
163 local vrf_name=$1
164
165 __last_tb_id=$((__last_tb_id + 1))
166 __TB_IDS[$vrf_name]=$__last_tb_id
167 return $__last_tb_id
168}
169
170__vrf_td_id_lookup()
171{
172 local vrf_name=$1
173
174 return ${__TB_IDS[$vrf_name]}
175}
176
177vrf_create()
178{
179 local vrf_name=$1
180 local tb_id
181
182 __vrf_td_id_assign $vrf_name
183 tb_id=$?
184
185 ip link add dev $vrf_name type vrf table $tb_id
186 ip -4 route add table $tb_id unreachable default metric 4278198272
187 ip -6 route add table $tb_id unreachable default metric 4278198272
188}
189
190vrf_destroy()
191{
192 local vrf_name=$1
193 local tb_id
194
195 __vrf_td_id_lookup $vrf_name
196 tb_id=$?
197
198 ip -6 route del table $tb_id unreachable default metric 4278198272
199 ip -4 route del table $tb_id unreachable default metric 4278198272
200 ip link del dev $vrf_name
201}
202
203__addr_add_del()
204{
205 local if_name=$1
206 local add_del=$2
207 local array
208
209 shift
210 shift
211 array=("${@}")
212
213 for addrstr in "${array[@]}"; do
214 ip address $add_del $addrstr dev $if_name
215 done
216}
217
218simple_if_init()
219{
220 local if_name=$1
221 local vrf_name
222 local array
223
224 shift
225 vrf_name=v$if_name
226 array=("${@}")
227
228 vrf_create $vrf_name
229 ip link set dev $if_name master $vrf_name
230 ip link set dev $vrf_name up
231 ip link set dev $if_name up
232
233 __addr_add_del $if_name add "${array[@]}"
234}
235
236simple_if_fini()
237{
238 local if_name=$1
239 local vrf_name
240 local array
241
242 shift
243 vrf_name=v$if_name
244 array=("${@}")
245
246 __addr_add_del $if_name del "${array[@]}"
247
248 ip link set dev $if_name down
249 vrf_destroy $vrf_name
250}
251
252master_name_get()
253{
254 local if_name=$1
255
256 ip -j link show dev $if_name | jq -r '.[]["master"]'
257}
258
Ido Schimmeld4deb012018-02-28 12:25:07 +0200259bridge_ageing_time_get()
260{
261 local bridge=$1
262 local ageing_time
263
264 # Need to divide by 100 to convert to seconds.
265 ageing_time=$(ip -j -d link show dev $bridge \
266 | jq '.[]["linkinfo"]["info_data"]["ageing_time"]')
267 echo $((ageing_time / 100))
268}
269
Ido Schimmel73bae672018-02-28 12:25:06 +0200270##############################################################################
271# Tests
272
273ping_test()
274{
275 local if_name=$1
276 local dip=$2
277 local vrf_name
278
279 RET=0
280
281 vrf_name=$(master_name_get $if_name)
282 ip vrf exec $vrf_name $PING $dip -c 10 -i 0.1 -w 2 &> /dev/null
283 check_err $?
284 log_test "ping"
285}
286
287ping6_test()
288{
289 local if_name=$1
290 local dip=$2
291 local vrf_name
292
293 RET=0
294
295 vrf_name=$(master_name_get $if_name)
296 ip vrf exec $vrf_name $PING6 $dip -c 10 -i 0.1 -w 2 &> /dev/null
297 check_err $?
298 log_test "ping6"
299}
Ido Schimmeld4deb012018-02-28 12:25:07 +0200300
301learning_test()
302{
303 local bridge=$1
304 local br_port1=$2 # Connected to `host1_if`.
305 local host1_if=$3
306 local host2_if=$4
307 local mac=de:ad:be:ef:13:37
308 local ageing_time
309
310 RET=0
311
312 bridge -j fdb show br $bridge brport $br_port1 \
313 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
314 check_fail $? "Found FDB record when should not"
315
316 # Disable unknown unicast flooding on `br_port1` to make sure
317 # packets are only forwarded through the port after a matching
318 # FDB entry was installed.
319 bridge link set dev $br_port1 flood off
320
321 tc qdisc add dev $host1_if ingress
322 tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \
323 flower dst_mac $mac action drop
324
325 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
326 sleep 1
327
328 tc -j -s filter show dev $host1_if ingress \
329 | jq -e ".[] | select(.options.handle == 101) \
330 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
331 check_fail $? "Packet reached second host when should not"
332
333 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
334 sleep 1
335
336 bridge -j fdb show br $bridge brport $br_port1 \
337 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
338 check_err $? "Did not find FDB record when should"
339
340 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q
341 sleep 1
342
343 tc -j -s filter show dev $host1_if ingress \
344 | jq -e ".[] | select(.options.handle == 101) \
345 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
346 check_err $? "Packet did not reach second host when should"
347
348 # Wait for 10 seconds after the ageing time to make sure FDB
349 # record was aged-out.
350 ageing_time=$(bridge_ageing_time_get $bridge)
351 sleep $((ageing_time + 10))
352
353 bridge -j fdb show br $bridge brport $br_port1 \
354 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
355 check_fail $? "Found FDB record when should not"
356
357 bridge link set dev $br_port1 learning off
358
359 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q
360 sleep 1
361
362 bridge -j fdb show br $bridge brport $br_port1 \
363 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null
364 check_fail $? "Found FDB record when should not"
365
366 bridge link set dev $br_port1 learning on
367
368 tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower
369 tc qdisc del dev $host1_if ingress
370
371 bridge link set dev $br_port1 flood on
372
373 log_test "FDB learning"
374}
Ido Schimmel236dd502018-02-28 12:25:08 +0200375
376flood_test_do()
377{
378 local should_flood=$1
379 local mac=$2
380 local ip=$3
381 local host1_if=$4
382 local host2_if=$5
383 local err=0
384
385 # Add an ACL on `host2_if` which will tell us whether the packet
386 # was flooded to it or not.
387 tc qdisc add dev $host2_if ingress
388 tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \
389 flower dst_mac $mac action drop
390
391 $MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q
392 sleep 1
393
394 tc -j -s filter show dev $host2_if ingress \
395 | jq -e ".[] | select(.options.handle == 101) \
396 | select(.options.actions[0].stats.packets == 1)" &> /dev/null
397 if [[ $? -ne 0 && $should_flood == "true" || \
398 $? -eq 0 && $should_flood == "false" ]]; then
399 err=1
400 fi
401
402 tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower
403 tc qdisc del dev $host2_if ingress
404
405 return $err
406}
407
408flood_unicast_test()
409{
410 local br_port=$1
411 local host1_if=$2
412 local host2_if=$3
413 local mac=de:ad:be:ef:13:37
414 local ip=192.0.2.100
415
416 RET=0
417
418 bridge link set dev $br_port flood off
419
420 flood_test_do false $mac $ip $host1_if $host2_if
421 check_err $? "Packet flooded when should not"
422
423 bridge link set dev $br_port flood on
424
425 flood_test_do true $mac $ip $host1_if $host2_if
426 check_err $? "Packet was not flooded when should"
427
428 log_test "Unknown unicast flood"
429}
430
431flood_multicast_test()
432{
433 local br_port=$1
434 local host1_if=$2
435 local host2_if=$3
436 local mac=01:00:5e:00:00:01
437 local ip=239.0.0.1
438
439 RET=0
440
441 bridge link set dev $br_port mcast_flood off
442
443 flood_test_do false $mac $ip $host1_if $host2_if
444 check_err $? "Packet flooded when should not"
445
446 bridge link set dev $br_port mcast_flood on
447
448 flood_test_do true $mac $ip $host1_if $host2_if
449 check_err $? "Packet was not flooded when should"
450
451 log_test "Unregistered multicast flood"
452}
453
454flood_test()
455{
456 # `br_port` is connected to `host2_if`
457 local br_port=$1
458 local host1_if=$2
459 local host2_if=$3
460
461 flood_unicast_test $br_port $host1_if $host2_if
462 flood_multicast_test $br_port $host1_if $host2_if
463}