1#!/bin/bash 2# SPDX-License-Identifier: GPL-2.0 3 4############################################################################## 5# Defines 6 7# Kselftest framework requirement - SKIP code is 4. 8ksft_skip=4 9 10# Can be overridden by the configuration file. 11PING=${PING:=ping} 12PING6=${PING6:=ping6} 13MZ=${MZ:=mausezahn} 14ARPING=${ARPING:=arping} 15TEAMD=${TEAMD:=teamd} 16WAIT_TIME=${WAIT_TIME:=5} 17PAUSE_ON_FAIL=${PAUSE_ON_FAIL:=no} 18PAUSE_ON_CLEANUP=${PAUSE_ON_CLEANUP:=no} 19NETIF_TYPE=${NETIF_TYPE:=veth} 20NETIF_CREATE=${NETIF_CREATE:=yes} 21MCD=${MCD:=smcrouted} 22MC_CLI=${MC_CLI:=smcroutectl} 23PING_COUNT=${PING_COUNT:=10} 24PING_TIMEOUT=${PING_TIMEOUT:=5} 25WAIT_TIMEOUT=${WAIT_TIMEOUT:=20} 26INTERFACE_TIMEOUT=${INTERFACE_TIMEOUT:=600} 27LOW_AGEING_TIME=${LOW_AGEING_TIME:=1000} 28REQUIRE_JQ=${REQUIRE_JQ:=yes} 29REQUIRE_MZ=${REQUIRE_MZ:=yes} 30REQUIRE_MTOOLS=${REQUIRE_MTOOLS:=no} 31STABLE_MAC_ADDRS=${STABLE_MAC_ADDRS:=no} 32TCPDUMP_EXTRA_FLAGS=${TCPDUMP_EXTRA_FLAGS:=} 33 34relative_path="${BASH_SOURCE%/*}" 35if [[ "$relative_path" == "${BASH_SOURCE}" ]]; then 36 relative_path="." 37fi 38 39if [[ -f $relative_path/forwarding.config ]]; then 40 source "$relative_path/forwarding.config" 41fi 42 43############################################################################## 44# Sanity checks 45 46check_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" 56check_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 71check_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 96check_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 105check_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 114check_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 123check_ethtool_lanes_support() 124{ 125 ethtool --help 2>&1| grep lanes &> /dev/null 126 if [[ $? -ne 0 ]]; then 127 echo "SKIP: ethtool too old; it is missing lanes support" 128 exit $ksft_skip 129 fi 130} 131 132check_locked_port_support() 133{ 134 if ! bridge -d link show | grep -q " locked"; then 135 echo "SKIP: iproute2 too old; Locked port feature not supported." 136 return $ksft_skip 137 fi 138} 139 140check_port_mab_support() 141{ 142 if ! bridge -d link show | grep -q "mab"; then 143 echo "SKIP: iproute2 too old; MacAuth feature not supported." 144 return $ksft_skip 145 fi 146} 147 148if [[ "$(id -u)" -ne 0 ]]; then 149 echo "SKIP: need root privileges" 150 exit $ksft_skip 151fi 152 153if [[ "$CHECK_TC" = "yes" ]]; then 154 check_tc_version 155fi 156 157require_command() 158{ 159 local cmd=$1; shift 160 161 if [[ ! -x "$(command -v "$cmd")" ]]; then 162 echo "SKIP: $cmd not installed" 163 exit $ksft_skip 164 fi 165} 166 167if [[ "$REQUIRE_JQ" = "yes" ]]; then 168 require_command jq 169fi 170if [[ "$REQUIRE_MZ" = "yes" ]]; then 171 require_command $MZ 172fi 173if [[ "$REQUIRE_MTOOLS" = "yes" ]]; then 174 # https://github.com/vladimiroltean/mtools/ 175 # patched for IPv6 support 176 require_command msend 177 require_command mreceive 178fi 179 180if [[ ! -v NUM_NETIFS ]]; then 181 echo "SKIP: importer does not define \"NUM_NETIFS\"" 182 exit $ksft_skip 183fi 184 185############################################################################## 186# Command line options handling 187 188count=0 189 190while [[ $# -gt 0 ]]; do 191 if [[ "$count" -eq "0" ]]; then 192 unset NETIFS 193 declare -A NETIFS 194 fi 195 count=$((count + 1)) 196 NETIFS[p$count]="$1" 197 shift 198done 199 200############################################################################## 201# Network interfaces configuration 202 203create_netif_veth() 204{ 205 local i 206 207 for ((i = 1; i <= NUM_NETIFS; ++i)); do 208 local j=$((i+1)) 209 210 ip link show dev ${NETIFS[p$i]} &> /dev/null 211 if [[ $? -ne 0 ]]; then 212 ip link add ${NETIFS[p$i]} type veth \ 213 peer name ${NETIFS[p$j]} 214 if [[ $? -ne 0 ]]; then 215 echo "Failed to create netif" 216 exit 1 217 fi 218 fi 219 i=$j 220 done 221} 222 223create_netif() 224{ 225 case "$NETIF_TYPE" in 226 veth) create_netif_veth 227 ;; 228 *) echo "Can not create interfaces of type \'$NETIF_TYPE\'" 229 exit 1 230 ;; 231 esac 232} 233 234declare -A MAC_ADDR_ORIG 235mac_addr_prepare() 236{ 237 local new_addr= 238 local dev= 239 240 for ((i = 1; i <= NUM_NETIFS; ++i)); do 241 dev=${NETIFS[p$i]} 242 new_addr=$(printf "00:01:02:03:04:%02x" $i) 243 244 MAC_ADDR_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].address') 245 # Strip quotes 246 MAC_ADDR_ORIG["$dev"]=${MAC_ADDR_ORIG["$dev"]//\"/} 247 ip link set dev $dev address $new_addr 248 done 249} 250 251mac_addr_restore() 252{ 253 local dev= 254 255 for ((i = 1; i <= NUM_NETIFS; ++i)); do 256 dev=${NETIFS[p$i]} 257 ip link set dev $dev address ${MAC_ADDR_ORIG["$dev"]} 258 done 259} 260 261if [[ "$NETIF_CREATE" = "yes" ]]; then 262 create_netif 263fi 264 265if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then 266 mac_addr_prepare 267fi 268 269for ((i = 1; i <= NUM_NETIFS; ++i)); do 270 ip link show dev ${NETIFS[p$i]} &> /dev/null 271 if [[ $? -ne 0 ]]; then 272 echo "SKIP: could not find all required interfaces" 273 exit $ksft_skip 274 fi 275done 276 277############################################################################## 278# Helpers 279 280# Exit status to return at the end. Set in case one of the tests fails. 281EXIT_STATUS=0 282# Per-test return value. Clear at the beginning of each test. 283RET=0 284 285check_err() 286{ 287 local err=$1 288 local msg=$2 289 290 if [[ $RET -eq 0 && $err -ne 0 ]]; then 291 RET=$err 292 retmsg=$msg 293 fi 294} 295 296check_fail() 297{ 298 local err=$1 299 local msg=$2 300 301 if [[ $RET -eq 0 && $err -eq 0 ]]; then 302 RET=1 303 retmsg=$msg 304 fi 305} 306 307check_err_fail() 308{ 309 local should_fail=$1; shift 310 local err=$1; shift 311 local what=$1; shift 312 313 if ((should_fail)); then 314 check_fail $err "$what succeeded, but should have failed" 315 else 316 check_err $err "$what failed" 317 fi 318} 319 320log_test() 321{ 322 local test_name=$1 323 local opt_str=$2 324 325 if [[ $# -eq 2 ]]; then 326 opt_str="($opt_str)" 327 fi 328 329 if [[ $RET -ne 0 ]]; then 330 EXIT_STATUS=1 331 printf "TEST: %-60s [FAIL]\n" "$test_name $opt_str" 332 if [[ ! -z "$retmsg" ]]; then 333 printf "\t%s\n" "$retmsg" 334 fi 335 if [ "${PAUSE_ON_FAIL}" = "yes" ]; then 336 echo "Hit enter to continue, 'q' to quit" 337 read a 338 [ "$a" = "q" ] && exit 1 339 fi 340 return 1 341 fi 342 343 printf "TEST: %-60s [ OK ]\n" "$test_name $opt_str" 344 return 0 345} 346 347log_test_skip() 348{ 349 local test_name=$1 350 local opt_str=$2 351 352 printf "TEST: %-60s [SKIP]\n" "$test_name $opt_str" 353 return 0 354} 355 356log_info() 357{ 358 local msg=$1 359 360 echo "INFO: $msg" 361} 362 363busywait() 364{ 365 local timeout=$1; shift 366 367 local start_time="$(date -u +%s%3N)" 368 while true 369 do 370 local out 371 out=$("$@") 372 local ret=$? 373 if ((!ret)); then 374 echo -n "$out" 375 return 0 376 fi 377 378 local current_time="$(date -u +%s%3N)" 379 if ((current_time - start_time > timeout)); then 380 echo -n "$out" 381 return 1 382 fi 383 done 384} 385 386not() 387{ 388 "$@" 389 [[ $? != 0 ]] 390} 391 392get_max() 393{ 394 local arr=("$@") 395 396 max=${arr[0]} 397 for cur in ${arr[@]}; do 398 if [[ $cur -gt $max ]]; then 399 max=$cur 400 fi 401 done 402 403 echo $max 404} 405 406grep_bridge_fdb() 407{ 408 local addr=$1; shift 409 local word 410 local flag 411 412 if [ "$1" == "self" ] || [ "$1" == "master" ]; then 413 word=$1; shift 414 if [ "$1" == "-v" ]; then 415 flag=$1; shift 416 fi 417 fi 418 419 $@ | grep $addr | grep $flag "$word" 420} 421 422wait_for_port_up() 423{ 424 "$@" | grep -q "Link detected: yes" 425} 426 427wait_for_offload() 428{ 429 "$@" | grep -q offload 430} 431 432wait_for_trap() 433{ 434 "$@" | grep -q trap 435} 436 437until_counter_is() 438{ 439 local expr=$1; shift 440 local current=$("$@") 441 442 echo $((current)) 443 ((current $expr)) 444} 445 446busywait_for_counter() 447{ 448 local timeout=$1; shift 449 local delta=$1; shift 450 451 local base=$("$@") 452 busywait "$timeout" until_counter_is ">= $((base + delta))" "$@" 453} 454 455setup_wait_dev() 456{ 457 local dev=$1; shift 458 local wait_time=${1:-$WAIT_TIME}; shift 459 460 setup_wait_dev_with_timeout "$dev" $INTERFACE_TIMEOUT $wait_time 461 462 if (($?)); then 463 check_err 1 464 log_test setup_wait_dev ": Interface $dev does not come up." 465 exit 1 466 fi 467} 468 469setup_wait_dev_with_timeout() 470{ 471 local dev=$1; shift 472 local max_iterations=${1:-$WAIT_TIMEOUT}; shift 473 local wait_time=${1:-$WAIT_TIME}; shift 474 local i 475 476 for ((i = 1; i <= $max_iterations; ++i)); do 477 ip link show dev $dev up \ 478 | grep 'state UP' &> /dev/null 479 if [[ $? -ne 0 ]]; then 480 sleep 1 481 else 482 sleep $wait_time 483 return 0 484 fi 485 done 486 487 return 1 488} 489 490setup_wait() 491{ 492 local num_netifs=${1:-$NUM_NETIFS} 493 local i 494 495 for ((i = 1; i <= num_netifs; ++i)); do 496 setup_wait_dev ${NETIFS[p$i]} 0 497 done 498 499 # Make sure links are ready. 500 sleep $WAIT_TIME 501} 502 503cmd_jq() 504{ 505 local cmd=$1 506 local jq_exp=$2 507 local jq_opts=$3 508 local ret 509 local output 510 511 output="$($cmd)" 512 # it the command fails, return error right away 513 ret=$? 514 if [[ $ret -ne 0 ]]; then 515 return $ret 516 fi 517 output=$(echo $output | jq -r $jq_opts "$jq_exp") 518 ret=$? 519 if [[ $ret -ne 0 ]]; then 520 return $ret 521 fi 522 echo $output 523 # return success only in case of non-empty output 524 [ ! -z "$output" ] 525} 526 527pre_cleanup() 528{ 529 if [ "${PAUSE_ON_CLEANUP}" = "yes" ]; then 530 echo "Pausing before cleanup, hit any key to continue" 531 read 532 fi 533 534 if [[ "$STABLE_MAC_ADDRS" = "yes" ]]; then 535 mac_addr_restore 536 fi 537} 538 539vrf_prepare() 540{ 541 ip -4 rule add pref 32765 table local 542 ip -4 rule del pref 0 543 ip -6 rule add pref 32765 table local 544 ip -6 rule del pref 0 545} 546 547vrf_cleanup() 548{ 549 ip -6 rule add pref 0 table local 550 ip -6 rule del pref 32765 551 ip -4 rule add pref 0 table local 552 ip -4 rule del pref 32765 553} 554 555__last_tb_id=0 556declare -A __TB_IDS 557 558__vrf_td_id_assign() 559{ 560 local vrf_name=$1 561 562 __last_tb_id=$((__last_tb_id + 1)) 563 __TB_IDS[$vrf_name]=$__last_tb_id 564 return $__last_tb_id 565} 566 567__vrf_td_id_lookup() 568{ 569 local vrf_name=$1 570 571 return ${__TB_IDS[$vrf_name]} 572} 573 574vrf_create() 575{ 576 local vrf_name=$1 577 local tb_id 578 579 __vrf_td_id_assign $vrf_name 580 tb_id=$? 581 582 ip link add dev $vrf_name type vrf table $tb_id 583 ip -4 route add table $tb_id unreachable default metric 4278198272 584 ip -6 route add table $tb_id unreachable default metric 4278198272 585} 586 587vrf_destroy() 588{ 589 local vrf_name=$1 590 local tb_id 591 592 __vrf_td_id_lookup $vrf_name 593 tb_id=$? 594 595 ip -6 route del table $tb_id unreachable default metric 4278198272 596 ip -4 route del table $tb_id unreachable default metric 4278198272 597 ip link del dev $vrf_name 598} 599 600__addr_add_del() 601{ 602 local if_name=$1 603 local add_del=$2 604 local array 605 606 shift 607 shift 608 array=("${@}") 609 610 for addrstr in "${array[@]}"; do 611 ip address $add_del $addrstr dev $if_name 612 done 613} 614 615__simple_if_init() 616{ 617 local if_name=$1; shift 618 local vrf_name=$1; shift 619 local addrs=("${@}") 620 621 ip link set dev $if_name master $vrf_name 622 ip link set dev $if_name up 623 624 __addr_add_del $if_name add "${addrs[@]}" 625} 626 627__simple_if_fini() 628{ 629 local if_name=$1; shift 630 local addrs=("${@}") 631 632 __addr_add_del $if_name del "${addrs[@]}" 633 634 ip link set dev $if_name down 635 ip link set dev $if_name nomaster 636} 637 638simple_if_init() 639{ 640 local if_name=$1 641 local vrf_name 642 local array 643 644 shift 645 vrf_name=v$if_name 646 array=("${@}") 647 648 vrf_create $vrf_name 649 ip link set dev $vrf_name up 650 __simple_if_init $if_name $vrf_name "${array[@]}" 651} 652 653simple_if_fini() 654{ 655 local if_name=$1 656 local vrf_name 657 local array 658 659 shift 660 vrf_name=v$if_name 661 array=("${@}") 662 663 __simple_if_fini $if_name "${array[@]}" 664 vrf_destroy $vrf_name 665} 666 667tunnel_create() 668{ 669 local name=$1; shift 670 local type=$1; shift 671 local local=$1; shift 672 local remote=$1; shift 673 674 ip link add name $name type $type \ 675 local $local remote $remote "$@" 676 ip link set dev $name up 677} 678 679tunnel_destroy() 680{ 681 local name=$1; shift 682 683 ip link del dev $name 684} 685 686vlan_create() 687{ 688 local if_name=$1; shift 689 local vid=$1; shift 690 local vrf=$1; shift 691 local ips=("${@}") 692 local name=$if_name.$vid 693 694 ip link add name $name link $if_name type vlan id $vid 695 if [ "$vrf" != "" ]; then 696 ip link set dev $name master $vrf 697 fi 698 ip link set dev $name up 699 __addr_add_del $name add "${ips[@]}" 700} 701 702vlan_destroy() 703{ 704 local if_name=$1; shift 705 local vid=$1; shift 706 local name=$if_name.$vid 707 708 ip link del dev $name 709} 710 711team_create() 712{ 713 local if_name=$1; shift 714 local mode=$1; shift 715 716 require_command $TEAMD 717 $TEAMD -t $if_name -d -c '{"runner": {"name": "'$mode'"}}' 718 for slave in "$@"; do 719 ip link set dev $slave down 720 ip link set dev $slave master $if_name 721 ip link set dev $slave up 722 done 723 ip link set dev $if_name up 724} 725 726team_destroy() 727{ 728 local if_name=$1; shift 729 730 $TEAMD -t $if_name -k 731} 732 733master_name_get() 734{ 735 local if_name=$1 736 737 ip -j link show dev $if_name | jq -r '.[]["master"]' 738} 739 740link_stats_get() 741{ 742 local if_name=$1; shift 743 local dir=$1; shift 744 local stat=$1; shift 745 746 ip -j -s link show dev $if_name \ 747 | jq '.[]["stats64"]["'$dir'"]["'$stat'"]' 748} 749 750link_stats_tx_packets_get() 751{ 752 link_stats_get $1 tx packets 753} 754 755link_stats_rx_errors_get() 756{ 757 link_stats_get $1 rx errors 758} 759 760tc_rule_stats_get() 761{ 762 local dev=$1; shift 763 local pref=$1; shift 764 local dir=$1; shift 765 local selector=${1:-.packets}; shift 766 767 tc -j -s filter show dev $dev ${dir:-ingress} pref $pref \ 768 | jq ".[1].options.actions[].stats$selector" 769} 770 771tc_rule_handle_stats_get() 772{ 773 local id=$1; shift 774 local handle=$1; shift 775 local selector=${1:-.packets}; shift 776 777 tc -j -s filter show $id \ 778 | jq ".[] | select(.options.handle == $handle) | \ 779 .options.actions[0].stats$selector" 780} 781 782ethtool_stats_get() 783{ 784 local dev=$1; shift 785 local stat=$1; shift 786 787 ethtool -S $dev | grep "^ *$stat:" | head -n 1 | cut -d: -f2 788} 789 790qdisc_stats_get() 791{ 792 local dev=$1; shift 793 local handle=$1; shift 794 local selector=$1; shift 795 796 tc -j -s qdisc show dev "$dev" \ 797 | jq '.[] | select(.handle == "'"$handle"'") | '"$selector" 798} 799 800qdisc_parent_stats_get() 801{ 802 local dev=$1; shift 803 local parent=$1; shift 804 local selector=$1; shift 805 806 tc -j -s qdisc show dev "$dev" invisible \ 807 | jq '.[] | select(.parent == "'"$parent"'") | '"$selector" 808} 809 810ipv6_stats_get() 811{ 812 local dev=$1; shift 813 local stat=$1; shift 814 815 cat /proc/net/dev_snmp6/$dev | grep "^$stat" | cut -f2 816} 817 818hw_stats_get() 819{ 820 local suite=$1; shift 821 local if_name=$1; shift 822 local dir=$1; shift 823 local stat=$1; shift 824 825 ip -j stats show dev $if_name group offload subgroup $suite | 826 jq ".[0].stats64.$dir.$stat" 827} 828 829humanize() 830{ 831 local speed=$1; shift 832 833 for unit in bps Kbps Mbps Gbps; do 834 if (($(echo "$speed < 1024" | bc))); then 835 break 836 fi 837 838 speed=$(echo "scale=1; $speed / 1024" | bc) 839 done 840 841 echo "$speed${unit}" 842} 843 844rate() 845{ 846 local t0=$1; shift 847 local t1=$1; shift 848 local interval=$1; shift 849 850 echo $((8 * (t1 - t0) / interval)) 851} 852 853packets_rate() 854{ 855 local t0=$1; shift 856 local t1=$1; shift 857 local interval=$1; shift 858 859 echo $(((t1 - t0) / interval)) 860} 861 862mac_get() 863{ 864 local if_name=$1 865 866 ip -j link show dev $if_name | jq -r '.[]["address"]' 867} 868 869ipv6_lladdr_get() 870{ 871 local if_name=$1 872 873 ip -j addr show dev $if_name | \ 874 jq -r '.[]["addr_info"][] | select(.scope == "link").local' | \ 875 head -1 876} 877 878bridge_ageing_time_get() 879{ 880 local bridge=$1 881 local ageing_time 882 883 # Need to divide by 100 to convert to seconds. 884 ageing_time=$(ip -j -d link show dev $bridge \ 885 | jq '.[]["linkinfo"]["info_data"]["ageing_time"]') 886 echo $((ageing_time / 100)) 887} 888 889declare -A SYSCTL_ORIG 890sysctl_set() 891{ 892 local key=$1; shift 893 local value=$1; shift 894 895 SYSCTL_ORIG[$key]=$(sysctl -n $key) 896 sysctl -qw $key="$value" 897} 898 899sysctl_restore() 900{ 901 local key=$1; shift 902 903 sysctl -qw $key="${SYSCTL_ORIG[$key]}" 904} 905 906forwarding_enable() 907{ 908 sysctl_set net.ipv4.conf.all.forwarding 1 909 sysctl_set net.ipv6.conf.all.forwarding 1 910} 911 912forwarding_restore() 913{ 914 sysctl_restore net.ipv6.conf.all.forwarding 915 sysctl_restore net.ipv4.conf.all.forwarding 916} 917 918declare -A MTU_ORIG 919mtu_set() 920{ 921 local dev=$1; shift 922 local mtu=$1; shift 923 924 MTU_ORIG["$dev"]=$(ip -j link show dev $dev | jq -e '.[].mtu') 925 ip link set dev $dev mtu $mtu 926} 927 928mtu_restore() 929{ 930 local dev=$1; shift 931 932 ip link set dev $dev mtu ${MTU_ORIG["$dev"]} 933} 934 935tc_offload_check() 936{ 937 local num_netifs=${1:-$NUM_NETIFS} 938 939 for ((i = 1; i <= num_netifs; ++i)); do 940 ethtool -k ${NETIFS[p$i]} \ 941 | grep "hw-tc-offload: on" &> /dev/null 942 if [[ $? -ne 0 ]]; then 943 return 1 944 fi 945 done 946 947 return 0 948} 949 950trap_install() 951{ 952 local dev=$1; shift 953 local direction=$1; shift 954 955 # Some devices may not support or need in-hardware trapping of traffic 956 # (e.g. the veth pairs that this library creates for non-existent 957 # loopbacks). Use continue instead, so that there is a filter in there 958 # (some tests check counters), and so that other filters are still 959 # processed. 960 tc filter add dev $dev $direction pref 1 \ 961 flower skip_sw action trap 2>/dev/null \ 962 || tc filter add dev $dev $direction pref 1 \ 963 flower action continue 964} 965 966trap_uninstall() 967{ 968 local dev=$1; shift 969 local direction=$1; shift 970 971 tc filter del dev $dev $direction pref 1 flower 972} 973 974slow_path_trap_install() 975{ 976 # For slow-path testing, we need to install a trap to get to 977 # slow path the packets that would otherwise be switched in HW. 978 if [ "${tcflags/skip_hw}" != "$tcflags" ]; then 979 trap_install "$@" 980 fi 981} 982 983slow_path_trap_uninstall() 984{ 985 if [ "${tcflags/skip_hw}" != "$tcflags" ]; then 986 trap_uninstall "$@" 987 fi 988} 989 990__icmp_capture_add_del() 991{ 992 local add_del=$1; shift 993 local pref=$1; shift 994 local vsuf=$1; shift 995 local tundev=$1; shift 996 local filter=$1; shift 997 998 tc filter $add_del dev "$tundev" ingress \ 999 proto ip$vsuf pref $pref \ 1000 flower ip_proto icmp$vsuf $filter \ 1001 action pass 1002} 1003 1004icmp_capture_install() 1005{ 1006 __icmp_capture_add_del add 100 "" "$@" 1007} 1008 1009icmp_capture_uninstall() 1010{ 1011 __icmp_capture_add_del del 100 "" "$@" 1012} 1013 1014icmp6_capture_install() 1015{ 1016 __icmp_capture_add_del add 100 v6 "$@" 1017} 1018 1019icmp6_capture_uninstall() 1020{ 1021 __icmp_capture_add_del del 100 v6 "$@" 1022} 1023 1024__vlan_capture_add_del() 1025{ 1026 local add_del=$1; shift 1027 local pref=$1; shift 1028 local dev=$1; shift 1029 local filter=$1; shift 1030 1031 tc filter $add_del dev "$dev" ingress \ 1032 proto 802.1q pref $pref \ 1033 flower $filter \ 1034 action pass 1035} 1036 1037vlan_capture_install() 1038{ 1039 __vlan_capture_add_del add 100 "$@" 1040} 1041 1042vlan_capture_uninstall() 1043{ 1044 __vlan_capture_add_del del 100 "$@" 1045} 1046 1047__dscp_capture_add_del() 1048{ 1049 local add_del=$1; shift 1050 local dev=$1; shift 1051 local base=$1; shift 1052 local dscp; 1053 1054 for prio in {0..7}; do 1055 dscp=$((base + prio)) 1056 __icmp_capture_add_del $add_del $((dscp + 100)) "" $dev \ 1057 "skip_hw ip_tos $((dscp << 2))" 1058 done 1059} 1060 1061dscp_capture_install() 1062{ 1063 local dev=$1; shift 1064 local base=$1; shift 1065 1066 __dscp_capture_add_del add $dev $base 1067} 1068 1069dscp_capture_uninstall() 1070{ 1071 local dev=$1; shift 1072 local base=$1; shift 1073 1074 __dscp_capture_add_del del $dev $base 1075} 1076 1077dscp_fetch_stats() 1078{ 1079 local dev=$1; shift 1080 local base=$1; shift 1081 1082 for prio in {0..7}; do 1083 local dscp=$((base + prio)) 1084 local t=$(tc_rule_stats_get $dev $((dscp + 100))) 1085 echo "[$dscp]=$t " 1086 done 1087} 1088 1089matchall_sink_create() 1090{ 1091 local dev=$1; shift 1092 1093 tc qdisc add dev $dev clsact 1094 tc filter add dev $dev ingress \ 1095 pref 10000 \ 1096 matchall \ 1097 action drop 1098} 1099 1100tests_run() 1101{ 1102 local current_test 1103 1104 for current_test in ${TESTS:-$ALL_TESTS}; do 1105 $current_test 1106 done 1107} 1108 1109multipath_eval() 1110{ 1111 local desc="$1" 1112 local weight_rp12=$2 1113 local weight_rp13=$3 1114 local packets_rp12=$4 1115 local packets_rp13=$5 1116 local weights_ratio packets_ratio diff 1117 1118 RET=0 1119 1120 if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then 1121 weights_ratio=$(echo "scale=2; $weight_rp12 / $weight_rp13" \ 1122 | bc -l) 1123 else 1124 weights_ratio=$(echo "scale=2; $weight_rp13 / $weight_rp12" \ 1125 | bc -l) 1126 fi 1127 1128 if [[ "$packets_rp12" -eq "0" || "$packets_rp13" -eq "0" ]]; then 1129 check_err 1 "Packet difference is 0" 1130 log_test "Multipath" 1131 log_info "Expected ratio $weights_ratio" 1132 return 1133 fi 1134 1135 if [[ "$weight_rp12" -gt "$weight_rp13" ]]; then 1136 packets_ratio=$(echo "scale=2; $packets_rp12 / $packets_rp13" \ 1137 | bc -l) 1138 else 1139 packets_ratio=$(echo "scale=2; $packets_rp13 / $packets_rp12" \ 1140 | bc -l) 1141 fi 1142 1143 diff=$(echo $weights_ratio - $packets_ratio | bc -l) 1144 diff=${diff#-} 1145 1146 test "$(echo "$diff / $weights_ratio > 0.15" | bc -l)" -eq 0 1147 check_err $? "Too large discrepancy between expected and measured ratios" 1148 log_test "$desc" 1149 log_info "Expected ratio $weights_ratio Measured ratio $packets_ratio" 1150} 1151 1152in_ns() 1153{ 1154 local name=$1; shift 1155 1156 ip netns exec $name bash <<-EOF 1157 NUM_NETIFS=0 1158 source lib.sh 1159 $(for a in "$@"; do printf "%q${IFS:0:1}" "$a"; done) 1160 EOF 1161} 1162 1163############################################################################## 1164# Tests 1165 1166ping_do() 1167{ 1168 local if_name=$1 1169 local dip=$2 1170 local args=$3 1171 local vrf_name 1172 1173 vrf_name=$(master_name_get $if_name) 1174 ip vrf exec $vrf_name \ 1175 $PING $args $dip -c $PING_COUNT -i 0.1 \ 1176 -w $PING_TIMEOUT &> /dev/null 1177} 1178 1179ping_test() 1180{ 1181 RET=0 1182 1183 ping_do $1 $2 1184 check_err $? 1185 log_test "ping$3" 1186} 1187 1188ping6_do() 1189{ 1190 local if_name=$1 1191 local dip=$2 1192 local args=$3 1193 local vrf_name 1194 1195 vrf_name=$(master_name_get $if_name) 1196 ip vrf exec $vrf_name \ 1197 $PING6 $args $dip -c $PING_COUNT -i 0.1 \ 1198 -w $PING_TIMEOUT &> /dev/null 1199} 1200 1201ping6_test() 1202{ 1203 RET=0 1204 1205 ping6_do $1 $2 1206 check_err $? 1207 log_test "ping6$3" 1208} 1209 1210learning_test() 1211{ 1212 local bridge=$1 1213 local br_port1=$2 # Connected to `host1_if`. 1214 local host1_if=$3 1215 local host2_if=$4 1216 local mac=de:ad:be:ef:13:37 1217 local ageing_time 1218 1219 RET=0 1220 1221 bridge -j fdb show br $bridge brport $br_port1 \ 1222 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 1223 check_fail $? "Found FDB record when should not" 1224 1225 # Disable unknown unicast flooding on `br_port1` to make sure 1226 # packets are only forwarded through the port after a matching 1227 # FDB entry was installed. 1228 bridge link set dev $br_port1 flood off 1229 1230 ip link set $host1_if promisc on 1231 tc qdisc add dev $host1_if ingress 1232 tc filter add dev $host1_if ingress protocol ip pref 1 handle 101 \ 1233 flower dst_mac $mac action drop 1234 1235 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 1236 sleep 1 1237 1238 tc -j -s filter show dev $host1_if ingress \ 1239 | jq -e ".[] | select(.options.handle == 101) \ 1240 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 1241 check_fail $? "Packet reached first host when should not" 1242 1243 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 1244 sleep 1 1245 1246 bridge -j fdb show br $bridge brport $br_port1 \ 1247 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 1248 check_err $? "Did not find FDB record when should" 1249 1250 $MZ $host2_if -c 1 -p 64 -b $mac -t ip -q 1251 sleep 1 1252 1253 tc -j -s filter show dev $host1_if ingress \ 1254 | jq -e ".[] | select(.options.handle == 101) \ 1255 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 1256 check_err $? "Packet did not reach second host when should" 1257 1258 # Wait for 10 seconds after the ageing time to make sure FDB 1259 # record was aged-out. 1260 ageing_time=$(bridge_ageing_time_get $bridge) 1261 sleep $((ageing_time + 10)) 1262 1263 bridge -j fdb show br $bridge brport $br_port1 \ 1264 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 1265 check_fail $? "Found FDB record when should not" 1266 1267 bridge link set dev $br_port1 learning off 1268 1269 $MZ $host1_if -c 1 -p 64 -a $mac -t ip -q 1270 sleep 1 1271 1272 bridge -j fdb show br $bridge brport $br_port1 \ 1273 | jq -e ".[] | select(.mac == \"$mac\")" &> /dev/null 1274 check_fail $? "Found FDB record when should not" 1275 1276 bridge link set dev $br_port1 learning on 1277 1278 tc filter del dev $host1_if ingress protocol ip pref 1 handle 101 flower 1279 tc qdisc del dev $host1_if ingress 1280 ip link set $host1_if promisc off 1281 1282 bridge link set dev $br_port1 flood on 1283 1284 log_test "FDB learning" 1285} 1286 1287flood_test_do() 1288{ 1289 local should_flood=$1 1290 local mac=$2 1291 local ip=$3 1292 local host1_if=$4 1293 local host2_if=$5 1294 local err=0 1295 1296 # Add an ACL on `host2_if` which will tell us whether the packet 1297 # was flooded to it or not. 1298 ip link set $host2_if promisc on 1299 tc qdisc add dev $host2_if ingress 1300 tc filter add dev $host2_if ingress protocol ip pref 1 handle 101 \ 1301 flower dst_mac $mac action drop 1302 1303 $MZ $host1_if -c 1 -p 64 -b $mac -B $ip -t ip -q 1304 sleep 1 1305 1306 tc -j -s filter show dev $host2_if ingress \ 1307 | jq -e ".[] | select(.options.handle == 101) \ 1308 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 1309 if [[ $? -ne 0 && $should_flood == "true" || \ 1310 $? -eq 0 && $should_flood == "false" ]]; then 1311 err=1 1312 fi 1313 1314 tc filter del dev $host2_if ingress protocol ip pref 1 handle 101 flower 1315 tc qdisc del dev $host2_if ingress 1316 ip link set $host2_if promisc off 1317 1318 return $err 1319} 1320 1321flood_unicast_test() 1322{ 1323 local br_port=$1 1324 local host1_if=$2 1325 local host2_if=$3 1326 local mac=de:ad:be:ef:13:37 1327 local ip=192.0.2.100 1328 1329 RET=0 1330 1331 bridge link set dev $br_port flood off 1332 1333 flood_test_do false $mac $ip $host1_if $host2_if 1334 check_err $? "Packet flooded when should not" 1335 1336 bridge link set dev $br_port flood on 1337 1338 flood_test_do true $mac $ip $host1_if $host2_if 1339 check_err $? "Packet was not flooded when should" 1340 1341 log_test "Unknown unicast flood" 1342} 1343 1344flood_multicast_test() 1345{ 1346 local br_port=$1 1347 local host1_if=$2 1348 local host2_if=$3 1349 local mac=01:00:5e:00:00:01 1350 local ip=239.0.0.1 1351 1352 RET=0 1353 1354 bridge link set dev $br_port mcast_flood off 1355 1356 flood_test_do false $mac $ip $host1_if $host2_if 1357 check_err $? "Packet flooded when should not" 1358 1359 bridge link set dev $br_port mcast_flood on 1360 1361 flood_test_do true $mac $ip $host1_if $host2_if 1362 check_err $? "Packet was not flooded when should" 1363 1364 log_test "Unregistered multicast flood" 1365} 1366 1367flood_test() 1368{ 1369 # `br_port` is connected to `host2_if` 1370 local br_port=$1 1371 local host1_if=$2 1372 local host2_if=$3 1373 1374 flood_unicast_test $br_port $host1_if $host2_if 1375 flood_multicast_test $br_port $host1_if $host2_if 1376} 1377 1378__start_traffic() 1379{ 1380 local pktsize=$1; shift 1381 local proto=$1; shift 1382 local h_in=$1; shift # Where the traffic egresses the host 1383 local sip=$1; shift 1384 local dip=$1; shift 1385 local dmac=$1; shift 1386 1387 $MZ $h_in -p $pktsize -A $sip -B $dip -c 0 \ 1388 -a own -b $dmac -t "$proto" -q "$@" & 1389 sleep 1 1390} 1391 1392start_traffic_pktsize() 1393{ 1394 local pktsize=$1; shift 1395 1396 __start_traffic $pktsize udp "$@" 1397} 1398 1399start_tcp_traffic_pktsize() 1400{ 1401 local pktsize=$1; shift 1402 1403 __start_traffic $pktsize tcp "$@" 1404} 1405 1406start_traffic() 1407{ 1408 start_traffic_pktsize 8000 "$@" 1409} 1410 1411start_tcp_traffic() 1412{ 1413 start_tcp_traffic_pktsize 8000 "$@" 1414} 1415 1416stop_traffic() 1417{ 1418 # Suppress noise from killing mausezahn. 1419 { kill %% && wait %%; } 2>/dev/null 1420} 1421 1422declare -A cappid 1423declare -A capfile 1424declare -A capout 1425 1426tcpdump_start() 1427{ 1428 local if_name=$1; shift 1429 local ns=$1; shift 1430 1431 capfile[$if_name]=$(mktemp) 1432 capout[$if_name]=$(mktemp) 1433 1434 if [ -z $ns ]; then 1435 ns_cmd="" 1436 else 1437 ns_cmd="ip netns exec ${ns}" 1438 fi 1439 1440 if [ -z $SUDO_USER ] ; then 1441 capuser="" 1442 else 1443 capuser="-Z $SUDO_USER" 1444 fi 1445 1446 $ns_cmd tcpdump $TCPDUMP_EXTRA_FLAGS -e -n -Q in -i $if_name \ 1447 -s 65535 -B 32768 $capuser -w ${capfile[$if_name]} \ 1448 > "${capout[$if_name]}" 2>&1 & 1449 cappid[$if_name]=$! 1450 1451 sleep 1 1452} 1453 1454tcpdump_stop() 1455{ 1456 local if_name=$1 1457 local pid=${cappid[$if_name]} 1458 1459 $ns_cmd kill "$pid" && wait "$pid" 1460 sleep 1 1461} 1462 1463tcpdump_cleanup() 1464{ 1465 local if_name=$1 1466 1467 rm ${capfile[$if_name]} ${capout[$if_name]} 1468} 1469 1470tcpdump_show() 1471{ 1472 local if_name=$1 1473 1474 tcpdump -e -n -r ${capfile[$if_name]} 2>&1 1475} 1476 1477# return 0 if the packet wasn't seen on host2_if or 1 if it was 1478mcast_packet_test() 1479{ 1480 local mac=$1 1481 local src_ip=$2 1482 local ip=$3 1483 local host1_if=$4 1484 local host2_if=$5 1485 local seen=0 1486 local tc_proto="ip" 1487 local mz_v6arg="" 1488 1489 # basic check to see if we were passed an IPv4 address, if not assume IPv6 1490 if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then 1491 tc_proto="ipv6" 1492 mz_v6arg="-6" 1493 fi 1494 1495 # Add an ACL on `host2_if` which will tell us whether the packet 1496 # was received by it or not. 1497 tc qdisc add dev $host2_if ingress 1498 tc filter add dev $host2_if ingress protocol $tc_proto pref 1 handle 101 \ 1499 flower ip_proto udp dst_mac $mac action drop 1500 1501 $MZ $host1_if $mz_v6arg -c 1 -p 64 -b $mac -A $src_ip -B $ip -t udp "dp=4096,sp=2048" -q 1502 sleep 1 1503 1504 tc -j -s filter show dev $host2_if ingress \ 1505 | jq -e ".[] | select(.options.handle == 101) \ 1506 | select(.options.actions[0].stats.packets == 1)" &> /dev/null 1507 if [[ $? -eq 0 ]]; then 1508 seen=1 1509 fi 1510 1511 tc filter del dev $host2_if ingress protocol $tc_proto pref 1 handle 101 flower 1512 tc qdisc del dev $host2_if ingress 1513 1514 return $seen 1515} 1516 1517brmcast_check_sg_entries() 1518{ 1519 local report=$1; shift 1520 local slist=("$@") 1521 local sarg="" 1522 1523 for src in "${slist[@]}"; do 1524 sarg="${sarg} and .source_list[].address == \"$src\"" 1525 done 1526 bridge -j -d -s mdb show dev br0 \ 1527 | jq -e ".[].mdb[] | \ 1528 select(.grp == \"$TEST_GROUP\" and .source_list != null $sarg)" &>/dev/null 1529 check_err $? "Wrong *,G entry source list after $report report" 1530 1531 for sgent in "${slist[@]}"; do 1532 bridge -j -d -s mdb show dev br0 \ 1533 | jq -e ".[].mdb[] | \ 1534 select(.grp == \"$TEST_GROUP\" and .src == \"$sgent\")" &>/dev/null 1535 check_err $? "Missing S,G entry ($sgent, $TEST_GROUP)" 1536 done 1537} 1538 1539brmcast_check_sg_fwding() 1540{ 1541 local should_fwd=$1; shift 1542 local sources=("$@") 1543 1544 for src in "${sources[@]}"; do 1545 local retval=0 1546 1547 mcast_packet_test $TEST_GROUP_MAC $src $TEST_GROUP $h2 $h1 1548 retval=$? 1549 if [ $should_fwd -eq 1 ]; then 1550 check_fail $retval "Didn't forward traffic from S,G ($src, $TEST_GROUP)" 1551 else 1552 check_err $retval "Forwarded traffic for blocked S,G ($src, $TEST_GROUP)" 1553 fi 1554 done 1555} 1556 1557brmcast_check_sg_state() 1558{ 1559 local is_blocked=$1; shift 1560 local sources=("$@") 1561 local should_fail=1 1562 1563 if [ $is_blocked -eq 1 ]; then 1564 should_fail=0 1565 fi 1566 1567 for src in "${sources[@]}"; do 1568 bridge -j -d -s mdb show dev br0 \ 1569 | jq -e ".[].mdb[] | \ 1570 select(.grp == \"$TEST_GROUP\" and .source_list != null) | 1571 .source_list[] | 1572 select(.address == \"$src\") | 1573 select(.timer == \"0.00\")" &>/dev/null 1574 check_err_fail $should_fail $? "Entry $src has zero timer" 1575 1576 bridge -j -d -s mdb show dev br0 \ 1577 | jq -e ".[].mdb[] | \ 1578 select(.grp == \"$TEST_GROUP\" and .src == \"$src\" and \ 1579 .flags[] == \"blocked\")" &>/dev/null 1580 check_err_fail $should_fail $? "Entry $src has blocked flag" 1581 done 1582} 1583 1584mc_join() 1585{ 1586 local if_name=$1 1587 local group=$2 1588 local vrf_name=$(master_name_get $if_name) 1589 1590 # We don't care about actual reception, just about joining the 1591 # IP multicast group and adding the L2 address to the device's 1592 # MAC filtering table 1593 ip vrf exec $vrf_name \ 1594 mreceive -g $group -I $if_name > /dev/null 2>&1 & 1595 mreceive_pid=$! 1596 1597 sleep 1 1598} 1599 1600mc_leave() 1601{ 1602 kill "$mreceive_pid" && wait "$mreceive_pid" 1603} 1604 1605mc_send() 1606{ 1607 local if_name=$1 1608 local groups=$2 1609 local vrf_name=$(master_name_get $if_name) 1610 1611 ip vrf exec $vrf_name \ 1612 msend -g $groups -I $if_name -c 1 > /dev/null 2>&1 1613} 1614 1615start_ip_monitor() 1616{ 1617 local mtype=$1; shift 1618 local ip=${1-ip}; shift 1619 1620 # start the monitor in the background 1621 tmpfile=`mktemp /var/run/nexthoptestXXX` 1622 mpid=`($ip monitor $mtype > $tmpfile & echo $!) 2>/dev/null` 1623 sleep 0.2 1624 echo "$mpid $tmpfile" 1625} 1626 1627stop_ip_monitor() 1628{ 1629 local mpid=$1; shift 1630 local tmpfile=$1; shift 1631 local el=$1; shift 1632 local what=$1; shift 1633 1634 sleep 0.2 1635 kill $mpid 1636 local lines=`grep '^\w' $tmpfile | wc -l` 1637 test $lines -eq $el 1638 check_err $? "$what: $lines lines of events, expected $el" 1639 rm -rf $tmpfile 1640} 1641 1642hw_stats_monitor_test() 1643{ 1644 local dev=$1; shift 1645 local type=$1; shift 1646 local make_suitable=$1; shift 1647 local make_unsuitable=$1; shift 1648 local ip=${1-ip}; shift 1649 1650 RET=0 1651 1652 # Expect a notification about enablement. 1653 local ipmout=$(start_ip_monitor stats "$ip") 1654 $ip stats set dev $dev ${type}_stats on 1655 stop_ip_monitor $ipmout 1 "${type}_stats enablement" 1656 1657 # Expect a notification about offload. 1658 local ipmout=$(start_ip_monitor stats "$ip") 1659 $make_suitable 1660 stop_ip_monitor $ipmout 1 "${type}_stats installation" 1661 1662 # Expect a notification about loss of offload. 1663 local ipmout=$(start_ip_monitor stats "$ip") 1664 $make_unsuitable 1665 stop_ip_monitor $ipmout 1 "${type}_stats deinstallation" 1666 1667 # Expect a notification about disablement 1668 local ipmout=$(start_ip_monitor stats "$ip") 1669 $ip stats set dev $dev ${type}_stats off 1670 stop_ip_monitor $ipmout 1 "${type}_stats disablement" 1671 1672 log_test "${type}_stats notifications" 1673} 1674 1675ipv4_to_bytes() 1676{ 1677 local IP=$1; shift 1678 1679 printf '%02x:' ${IP//./ } | 1680 sed 's/:$//' 1681} 1682 1683# Convert a given IPv6 address, `IP' such that the :: token, if present, is 1684# expanded, and each 16-bit group is padded with zeroes to be 4 hexadecimal 1685# digits. An optional `BYTESEP' parameter can be given to further separate 1686# individual bytes of each 16-bit group. 1687expand_ipv6() 1688{ 1689 local IP=$1; shift 1690 local bytesep=$1; shift 1691 1692 local cvt_ip=${IP/::/_} 1693 local colons=${cvt_ip//[^:]/} 1694 local allcol=::::::: 1695 # IP where :: -> the appropriate number of colons: 1696 local allcol_ip=${cvt_ip/_/${allcol:${#colons}}} 1697 1698 echo $allcol_ip | tr : '\n' | 1699 sed s/^/0000/ | 1700 sed 's/.*\(..\)\(..\)/\1'"$bytesep"'\2/' | 1701 tr '\n' : | 1702 sed 's/:$//' 1703} 1704 1705ipv6_to_bytes() 1706{ 1707 local IP=$1; shift 1708 1709 expand_ipv6 "$IP" : 1710} 1711 1712u16_to_bytes() 1713{ 1714 local u16=$1; shift 1715 1716 printf "%04x" $u16 | sed 's/^/000/;s/^.*\(..\)\(..\)$/\1:\2/' 1717} 1718 1719# Given a mausezahn-formatted payload (colon-separated bytes given as %02x), 1720# possibly with a keyword CHECKSUM stashed where a 16-bit checksum should be, 1721# calculate checksum as per RFC 1071, assuming the CHECKSUM field (if any) 1722# stands for 00:00. 1723payload_template_calc_checksum() 1724{ 1725 local payload=$1; shift 1726 1727 ( 1728 # Set input radix. 1729 echo "16i" 1730 # Push zero for the initial checksum. 1731 echo 0 1732 1733 # Pad the payload with a terminating 00: in case we get an odd 1734 # number of bytes. 1735 echo "${payload%:}:00:" | 1736 sed 's/CHECKSUM/00:00/g' | 1737 tr '[:lower:]' '[:upper:]' | 1738 # Add the word to the checksum. 1739 sed 's/\(..\):\(..\):/\1\2+\n/g' | 1740 # Strip the extra odd byte we pushed if left unconverted. 1741 sed 's/\(..\):$//' 1742 1743 echo "10000 ~ +" # Calculate and add carry. 1744 echo "FFFF r - p" # Bit-flip and print. 1745 ) | 1746 dc | 1747 tr '[:upper:]' '[:lower:]' 1748} 1749 1750payload_template_expand_checksum() 1751{ 1752 local payload=$1; shift 1753 local checksum=$1; shift 1754 1755 local ckbytes=$(u16_to_bytes $checksum) 1756 1757 echo "$payload" | sed "s/CHECKSUM/$ckbytes/g" 1758} 1759 1760payload_template_nbytes() 1761{ 1762 local payload=$1; shift 1763 1764 payload_template_expand_checksum "${payload%:}" 0 | 1765 sed 's/:/\n/g' | wc -l 1766} 1767 1768igmpv3_is_in_get() 1769{ 1770 local GRP=$1; shift 1771 local sources=("$@") 1772 1773 local igmpv3 1774 local nsources=$(u16_to_bytes ${#sources[@]}) 1775 1776 # IS_IN ( $sources ) 1777 igmpv3=$(: 1778 )"22:"$( : Type - Membership Report 1779 )"00:"$( : Reserved 1780 )"CHECKSUM:"$( : Checksum 1781 )"00:00:"$( : Reserved 1782 )"00:01:"$( : Number of Group Records 1783 )"01:"$( : Record Type - IS_IN 1784 )"00:"$( : Aux Data Len 1785 )"${nsources}:"$( : Number of Sources 1786 )"$(ipv4_to_bytes $GRP):"$( : Multicast Address 1787 )"$(for src in "${sources[@]}"; do 1788 ipv4_to_bytes $src 1789 echo -n : 1790 done)"$( : Source Addresses 1791 ) 1792 local checksum=$(payload_template_calc_checksum "$igmpv3") 1793 1794 payload_template_expand_checksum "$igmpv3" $checksum 1795} 1796 1797igmpv2_leave_get() 1798{ 1799 local GRP=$1; shift 1800 1801 local payload=$(: 1802 )"17:"$( : Type - Leave Group 1803 )"00:"$( : Max Resp Time - not meaningful 1804 )"CHECKSUM:"$( : Checksum 1805 )"$(ipv4_to_bytes $GRP)"$( : Group Address 1806 ) 1807 local checksum=$(payload_template_calc_checksum "$payload") 1808 1809 payload_template_expand_checksum "$payload" $checksum 1810} 1811 1812mldv2_is_in_get() 1813{ 1814 local SIP=$1; shift 1815 local GRP=$1; shift 1816 local sources=("$@") 1817 1818 local hbh 1819 local icmpv6 1820 local nsources=$(u16_to_bytes ${#sources[@]}) 1821 1822 hbh=$(: 1823 )"3a:"$( : Next Header - ICMPv6 1824 )"00:"$( : Hdr Ext Len 1825 )"00:00:00:00:00:00:"$( : Options and Padding 1826 ) 1827 1828 icmpv6=$(: 1829 )"8f:"$( : Type - MLDv2 Report 1830 )"00:"$( : Code 1831 )"CHECKSUM:"$( : Checksum 1832 )"00:00:"$( : Reserved 1833 )"00:01:"$( : Number of Group Records 1834 )"01:"$( : Record Type - IS_IN 1835 )"00:"$( : Aux Data Len 1836 )"${nsources}:"$( : Number of Sources 1837 )"$(ipv6_to_bytes $GRP):"$( : Multicast address 1838 )"$(for src in "${sources[@]}"; do 1839 ipv6_to_bytes $src 1840 echo -n : 1841 done)"$( : Source Addresses 1842 ) 1843 1844 local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6)) 1845 local sudohdr=$(: 1846 )"$(ipv6_to_bytes $SIP):"$( : SIP 1847 )"$(ipv6_to_bytes $GRP):"$( : DIP is multicast address 1848 )"${len}:"$( : Upper-layer length 1849 )"00:3a:"$( : Zero and next-header 1850 ) 1851 local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6}) 1852 1853 payload_template_expand_checksum "$hbh$icmpv6" $checksum 1854} 1855 1856mldv1_done_get() 1857{ 1858 local SIP=$1; shift 1859 local GRP=$1; shift 1860 1861 local hbh 1862 local icmpv6 1863 1864 hbh=$(: 1865 )"3a:"$( : Next Header - ICMPv6 1866 )"00:"$( : Hdr Ext Len 1867 )"00:00:00:00:00:00:"$( : Options and Padding 1868 ) 1869 1870 icmpv6=$(: 1871 )"84:"$( : Type - MLDv1 Done 1872 )"00:"$( : Code 1873 )"CHECKSUM:"$( : Checksum 1874 )"00:00:"$( : Max Resp Delay - not meaningful 1875 )"00:00:"$( : Reserved 1876 )"$(ipv6_to_bytes $GRP):"$( : Multicast address 1877 ) 1878 1879 local len=$(u16_to_bytes $(payload_template_nbytes $icmpv6)) 1880 local sudohdr=$(: 1881 )"$(ipv6_to_bytes $SIP):"$( : SIP 1882 )"$(ipv6_to_bytes $GRP):"$( : DIP is multicast address 1883 )"${len}:"$( : Upper-layer length 1884 )"00:3a:"$( : Zero and next-header 1885 ) 1886 local checksum=$(payload_template_calc_checksum ${sudohdr}${icmpv6}) 1887 1888 payload_template_expand_checksum "$hbh$icmpv6" $checksum 1889} 1890