#! /bin/bash # SPDX-License-Identifier: GPL-2.0 . "$(dirname "${0}")/../lib.sh" . "$(dirname "${0}")/../net_helper.sh" readonly KSFT_PASS=0 readonly KSFT_FAIL=1 readonly KSFT_SKIP=4 # shellcheck disable=SC2155 # declare and assign separately readonly KSFT_TEST="${MPTCP_LIB_KSFT_TEST:-$(basename "${0}" .sh)}" # These variables are used in some selftests, read-only declare -rx MPTCP_LIB_EVENT_CREATED=1 # MPTCP_EVENT_CREATED declare -rx MPTCP_LIB_EVENT_ESTABLISHED=2 # MPTCP_EVENT_ESTABLISHED declare -rx MPTCP_LIB_EVENT_CLOSED=3 # MPTCP_EVENT_CLOSED declare -rx MPTCP_LIB_EVENT_ANNOUNCED=6 # MPTCP_EVENT_ANNOUNCED declare -rx MPTCP_LIB_EVENT_REMOVED=7 # MPTCP_EVENT_REMOVED declare -rx MPTCP_LIB_EVENT_SUB_ESTABLISHED=10 # MPTCP_EVENT_SUB_ESTABLISHED declare -rx MPTCP_LIB_EVENT_SUB_CLOSED=11 # MPTCP_EVENT_SUB_CLOSED declare -rx MPTCP_LIB_EVENT_SUB_PRIORITY=13 # MPTCP_EVENT_SUB_PRIORITY declare -rx MPTCP_LIB_EVENT_LISTENER_CREATED=15 # MPTCP_EVENT_LISTENER_CREATED declare -rx MPTCP_LIB_EVENT_LISTENER_CLOSED=16 # MPTCP_EVENT_LISTENER_CLOSED declare -rx MPTCP_LIB_AF_INET=2 declare -rx MPTCP_LIB_AF_INET6=10 MPTCP_LIB_SUBTESTS=() MPTCP_LIB_SUBTESTS_DUPLICATED=0 MPTCP_LIB_SUBTEST_FLAKY=0 MPTCP_LIB_SUBTESTS_LAST_TS_MS= MPTCP_LIB_TEST_COUNTER=0 MPTCP_LIB_TEST_FORMAT="%02u %-50s" MPTCP_LIB_IP_MPTCP=0 # only if supported (or forced) and not disabled, see no-color.org if { [ -t 1 ] || [ "${SELFTESTS_MPTCP_LIB_COLOR_FORCE:-}" = "1" ]; } && [ "${NO_COLOR:-}" != "1" ]; then readonly MPTCP_LIB_COLOR_RED="\E[1;31m" readonly MPTCP_LIB_COLOR_GREEN="\E[1;32m" readonly MPTCP_LIB_COLOR_YELLOW="\E[1;33m" readonly MPTCP_LIB_COLOR_BLUE="\E[1;34m" readonly MPTCP_LIB_COLOR_RESET="\E[0m" else readonly MPTCP_LIB_COLOR_RED= readonly MPTCP_LIB_COLOR_GREEN= readonly MPTCP_LIB_COLOR_YELLOW= readonly MPTCP_LIB_COLOR_BLUE= readonly MPTCP_LIB_COLOR_RESET= fi # SELFTESTS_MPTCP_LIB_OVERRIDE_FLAKY env var can be set not to ignore errors # from subtests marked as flaky mptcp_lib_override_flaky() { [ "${SELFTESTS_MPTCP_LIB_OVERRIDE_FLAKY:-}" = 1 ] } mptcp_lib_subtest_is_flaky() { [ "${MPTCP_LIB_SUBTEST_FLAKY}" = 1 ] && ! mptcp_lib_override_flaky } # $1: color, $2: text mptcp_lib_print_color() { echo -e "${MPTCP_LIB_START_PRINT:-}${*}${MPTCP_LIB_COLOR_RESET}" } mptcp_lib_print_ok() { mptcp_lib_print_color "${MPTCP_LIB_COLOR_GREEN}${*}" } mptcp_lib_print_warn() { mptcp_lib_print_color "${MPTCP_LIB_COLOR_YELLOW}${*}" } mptcp_lib_print_info() { mptcp_lib_print_color "${MPTCP_LIB_COLOR_BLUE}${*}" } mptcp_lib_print_err() { mptcp_lib_print_color "${MPTCP_LIB_COLOR_RED}${*}" } # shellcheck disable=SC2120 # parameters are optional mptcp_lib_pr_ok() { mptcp_lib_print_ok "[ OK ]${1:+ ${*}}" } mptcp_lib_pr_skip() { mptcp_lib_print_warn "[SKIP]${1:+ ${*}}" } mptcp_lib_pr_fail() { local title cmt if mptcp_lib_subtest_is_flaky; then title="IGNO" cmt=" (flaky)" else title="FAIL" fi mptcp_lib_print_err "[${title}]${cmt}${1:+ ${*}}" } mptcp_lib_pr_info() { mptcp_lib_print_info "INFO: ${*}" } # SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES env var can be set when validating all # features using the last version of the kernel and the selftests to make sure # a test is not being skipped by mistake. mptcp_lib_expect_all_features() { [ "${SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES:-}" = "1" ] } # $1: msg mptcp_lib_fail_if_expected_feature() { if mptcp_lib_expect_all_features; then echo "ERROR: missing feature: ${*}" exit ${KSFT_FAIL} fi return 1 } # $1: file mptcp_lib_has_file() { local f="${1}" if [ -f "${f}" ]; then return 0 fi mptcp_lib_fail_if_expected_feature "${f} file not found" } mptcp_lib_check_mptcp() { if ! mptcp_lib_has_file "/proc/sys/net/mptcp/enabled"; then mptcp_lib_pr_skip "MPTCP support is not available" exit ${KSFT_SKIP} fi } mptcp_lib_check_kallsyms() { if ! mptcp_lib_has_file "/proc/kallsyms"; then mptcp_lib_pr_skip "CONFIG_KALLSYMS is missing" exit ${KSFT_SKIP} fi } # Internal: use mptcp_lib_kallsyms_has() instead __mptcp_lib_kallsyms_has() { local sym="${1}" mptcp_lib_check_kallsyms grep -q " ${sym}" /proc/kallsyms } # $1: part of a symbol to look at, add '$' at the end for full name mptcp_lib_kallsyms_has() { local sym="${1}" if __mptcp_lib_kallsyms_has "${sym}"; then return 0 fi mptcp_lib_fail_if_expected_feature "${sym} symbol not found" } # $1: part of a symbol to look at, add '$' at the end for full name mptcp_lib_kallsyms_doesnt_have() { local sym="${1}" if ! __mptcp_lib_kallsyms_has "${sym}"; then return 0 fi mptcp_lib_fail_if_expected_feature "${sym} symbol has been found" } # !!!AVOID USING THIS!!! # Features might not land in the expected version and features can be backported # # $1: kernel version, e.g. 6.3 mptcp_lib_kversion_ge() { local exp_maj="${1%.*}" local exp_min="${1#*.}" local v maj min # If the kernel has backported features, set this env var to 1: if [ "${SELFTESTS_MPTCP_LIB_NO_KVERSION_CHECK:-}" = "1" ]; then return 0 fi v=$(uname -r | cut -d'.' -f1,2) maj=${v%.*} min=${v#*.} if [ "${maj}" -gt "${exp_maj}" ] || { [ "${maj}" -eq "${exp_maj}" ] && [ "${min}" -ge "${exp_min}" ]; }; then return 0 fi mptcp_lib_fail_if_expected_feature "kernel version ${1} lower than ${v}" } mptcp_lib_subtests_last_ts_reset() { MPTCP_LIB_SUBTESTS_LAST_TS_MS="$(date +%s%3N)" } mptcp_lib_subtests_last_ts_reset __mptcp_lib_result_check_duplicated() { local subtest for subtest in "${MPTCP_LIB_SUBTESTS[@]}"; do if [[ "${subtest}" == *" - ${KSFT_TEST}: ${*%% #*}" ]]; then MPTCP_LIB_SUBTESTS_DUPLICATED=1 mptcp_lib_print_err "Duplicated entry: ${*}" break fi done } __mptcp_lib_result_add() { local result="${1}" local time="time=" local ts_prev_ms shift local id=$((${#MPTCP_LIB_SUBTESTS[@]} + 1)) __mptcp_lib_result_check_duplicated "${*}" # not to add two '#' [[ "${*}" != *"#"* ]] && time="# ${time}" ts_prev_ms="${MPTCP_LIB_SUBTESTS_LAST_TS_MS}" mptcp_lib_subtests_last_ts_reset time+="$((MPTCP_LIB_SUBTESTS_LAST_TS_MS - ts_prev_ms))ms" MPTCP_LIB_SUBTESTS+=("${result} ${id} - ${KSFT_TEST}: ${*} ${time}") } # $1: test name mptcp_lib_result_pass() { __mptcp_lib_result_add "ok" "${1}" } # $1: test name mptcp_lib_result_fail() { if mptcp_lib_subtest_is_flaky; then # It might sound better to use 'not ok # TODO' or 'ok # SKIP', # but some CIs don't understand 'TODO' and treat SKIP as errors. __mptcp_lib_result_add "ok" "${1} # IGNORE Flaky" else __mptcp_lib_result_add "not ok" "${1}" fi } # $1: test name mptcp_lib_result_skip() { __mptcp_lib_result_add "ok" "${1} # SKIP" } # $1: result code ; $2: test name mptcp_lib_result_code() { local ret="${1}" local name="${2}" case "${ret}" in "${KSFT_PASS}") mptcp_lib_result_pass "${name}" ;; "${KSFT_FAIL}") mptcp_lib_result_fail "${name}" ;; "${KSFT_SKIP}") mptcp_lib_result_skip "${name}" ;; *) echo "ERROR: wrong result code: ${ret}" exit ${KSFT_FAIL} ;; esac } mptcp_lib_result_print_all_tap() { local subtest if [ ${#MPTCP_LIB_SUBTESTS[@]} -eq 0 ] || [ "${SELFTESTS_MPTCP_LIB_NO_TAP:-}" = "1" ]; then return fi printf "\nTAP version 13\n" printf "1..%d\n" "${#MPTCP_LIB_SUBTESTS[@]}" for subtest in "${MPTCP_LIB_SUBTESTS[@]}"; do printf "%s\n" "${subtest}" done if [ "${MPTCP_LIB_SUBTESTS_DUPLICATED}" = 1 ] && mptcp_lib_expect_all_features; then mptcp_lib_print_err "Duplicated test entries" exit ${KSFT_FAIL} fi } # get the value of keyword $1 in the line marked by keyword $2 mptcp_lib_get_info_value() { grep "${2}" | sed -n 's/.*\('"${1}"':\)\([0-9a-f:.]*\).*$/\2/p;q' } # $1: info name ; $2: evts_ns ; [$3: event type; [$4: addr]] mptcp_lib_evts_get_info() { grep "${4:-}" "${2}" | mptcp_lib_get_info_value "${1}" "^type:${3:-1}," } # $1: PID mptcp_lib_kill_wait() { [ "${1}" -eq 0 ] && return 0 kill -SIGUSR1 "${1}" > /dev/null 2>&1 kill "${1}" > /dev/null 2>&1 wait "${1}" 2>/dev/null } # $1: IP address mptcp_lib_is_v6() { [ -z "${1##*:*}" ] } # $1: ns, $2: MIB counter mptcp_lib_get_counter() { local ns="${1}" local counter="${2}" local count count=$(ip netns exec "${ns}" nstat -asz "${counter}" | awk 'NR==1 {next} {print $2}') if [ -z "${count}" ]; then mptcp_lib_fail_if_expected_feature "${counter} counter" return 1 fi echo "${count}" } mptcp_lib_make_file() { local name="${1}" local bs="${2}" local size="${3}" dd if=/dev/urandom of="${name}" bs="${bs}" count="${size}" 2> /dev/null echo -e "\nMPTCP_TEST_FILE_END_MARKER" >> "${name}" } # $1: file mptcp_lib_print_file_err() { ls -l "${1}" 1>&2 echo "Trailing bytes are: " tail -c 27 "${1}" } # $1: input file ; $2: output file ; $3: what kind of file mptcp_lib_check_transfer() { local in="${1}" local out="${2}" local what="${3}" if ! cmp "$in" "$out" > /dev/null 2>&1; then mptcp_lib_pr_fail "$what does not match (in, out):" mptcp_lib_print_file_err "$in" mptcp_lib_print_file_err "$out" return 1 fi return 0 } # $1: ns, $2: port mptcp_lib_wait_local_port_listen() { wait_local_port_listen "${@}" "tcp" } mptcp_lib_check_output() { local err="${1}" local cmd="${2}" local expected="${3}" local cmd_ret=0 local out if ! out=$(${cmd} 2>"${err}"); then cmd_ret=${?} fi if [ ${cmd_ret} -ne 0 ]; then mptcp_lib_pr_fail "command execution '${cmd}' stderr" cat "${err}" return 2 elif [ "${out}" = "${expected}" ]; then return 0 else mptcp_lib_pr_fail "expected '${expected}' got '${out}'" return 1 fi } mptcp_lib_check_tools() { local tool for tool in "${@}"; do case "${tool}" in "ip") if ! ip -Version &> /dev/null; then mptcp_lib_pr_skip "Could not run test without ip tool" exit ${KSFT_SKIP} fi ;; "tc") if ! tc -help &> /dev/null; then mptcp_lib_pr_skip "Could not run test without tc tool" exit ${KSFT_SKIP} fi ;; "ss") if ! ss -h | grep -q MPTCP; then mptcp_lib_pr_skip "ss tool does not support MPTCP" exit ${KSFT_SKIP} fi ;; "iptables"* | "ip6tables"*) if ! "${tool}" -V &> /dev/null; then mptcp_lib_pr_skip "Could not run all tests without ${tool}" exit ${KSFT_SKIP} fi ;; *) mptcp_lib_pr_fail "Internal error: unsupported tool: ${tool}" exit ${KSFT_FAIL} ;; esac done } mptcp_lib_ns_init() { if ! setup_ns "${@}"; then mptcp_lib_pr_fail "Failed to setup namespaces ${*}" exit ${KSFT_FAIL} fi local netns for netns in "${@}"; do ip netns exec "${!netns}" sysctl -q net.mptcp.enabled=1 ip netns exec "${!netns}" sysctl -q net.ipv4.conf.all.rp_filter=0 ip netns exec "${!netns}" sysctl -q net.ipv4.conf.default.rp_filter=0 done } mptcp_lib_ns_exit() { cleanup_ns "${@}" local netns for netns in "${@}"; do rm -f /tmp/"${netns}".{nstat,out} done } mptcp_lib_events() { local ns="${1}" local evts="${2}" declare -n pid="${3}" :>"${evts}" mptcp_lib_kill_wait "${pid:-0}" ip netns exec "${ns}" ./pm_nl_ctl events >> "${evts}" 2>&1 & pid=$! } mptcp_lib_print_title() { : "${MPTCP_LIB_TEST_COUNTER:?}" : "${MPTCP_LIB_TEST_FORMAT:?}" # shellcheck disable=SC2059 # the format is in a variable printf "${MPTCP_LIB_TEST_FORMAT}" "$((++MPTCP_LIB_TEST_COUNTER))" "${*}" } # $1: var name ; $2: prev ret mptcp_lib_check_expected_one() { local var="${1}" local exp="e_${var}" local prev_ret="${2}" if [ "${!var}" = "${!exp}" ]; then return 0 fi if [ "${prev_ret}" = "0" ]; then mptcp_lib_pr_fail fi mptcp_lib_print_err "Expected value for '${var}': '${!exp}', got '${!var}'." return 1 } # $@: all var names to check mptcp_lib_check_expected() { local rc=0 local var for var in "${@}"; do mptcp_lib_check_expected_one "${var}" "${rc}" || rc=1 done return "${rc}" } # shellcheck disable=SC2034 # Some variables are used below but indirectly mptcp_lib_verify_listener_events() { local evt=${1} local e_type=${2} local e_family=${3} local e_saddr=${4} local e_sport=${5} local type local family local saddr local sport local rc=0 type=$(mptcp_lib_evts_get_info type "${evt}" "${e_type}") family=$(mptcp_lib_evts_get_info family "${evt}" "${e_type}") if [ "${family}" ] && [ "${family}" = "${AF_INET6}" ]; then saddr=$(mptcp_lib_evts_get_info saddr6 "${evt}" "${e_type}") else saddr=$(mptcp_lib_evts_get_info saddr4 "${evt}" "${e_type}") fi sport=$(mptcp_lib_evts_get_info sport "${evt}" "${e_type}") mptcp_lib_check_expected "type" "family" "saddr" "sport" || rc="${?}" return "${rc}" } mptcp_lib_set_ip_mptcp() { MPTCP_LIB_IP_MPTCP=1 } mptcp_lib_is_ip_mptcp() { [ "${MPTCP_LIB_IP_MPTCP}" = "1" ] } # format: ,,, mptcp_lib_pm_nl_format_endpoints() { local entry id ip flags dev port for entry in "${@}"; do IFS=, read -r id ip flags dev port <<< "${entry}" if mptcp_lib_is_ip_mptcp; then echo -n "${ip}" [ -n "${port}" ] && echo -n " port ${port}" echo -n " id ${id}" [ -n "${flags}" ] && echo -n " ${flags}" [ -n "${dev}" ] && echo -n " dev ${dev}" echo " " # always a space at the end else echo -n "id ${id}" echo -n " flags ${flags//" "/","}" [ -n "${dev}" ] && echo -n " dev ${dev}" echo -n " ${ip}" [ -n "${port}" ] && echo -n " ${port}" echo "" fi done } mptcp_lib_pm_nl_get_endpoint() { local ns=${1} local id=${2} if mptcp_lib_is_ip_mptcp; then ip -n "${ns}" mptcp endpoint show id "${id}" else ip netns exec "${ns}" ./pm_nl_ctl get "${id}" fi } mptcp_lib_pm_nl_set_limits() { local ns=${1} local addrs=${2} local subflows=${3} if mptcp_lib_is_ip_mptcp; then ip -n "${ns}" mptcp limits set add_addr_accepted "${addrs}" subflows "${subflows}" else ip netns exec "${ns}" ./pm_nl_ctl limits "${addrs}" "${subflows}" fi } mptcp_lib_pm_nl_add_endpoint() { local ns=${1} local addr=${2} local flags dev id port local nr=2 local p for p in "${@}"; do case "${p}" in "flags" | "dev" | "id" | "port") eval "${p}"=\$"${nr}" ;; esac nr=$((nr + 1)) done if mptcp_lib_is_ip_mptcp; then # shellcheck disable=SC2086 # blanks in flags, no double quote ip -n "${ns}" mptcp endpoint add "${addr}" ${flags//","/" "} \ ${dev:+dev "${dev}"} ${id:+id "${id}"} ${port:+port "${port}"} else ip netns exec "${ns}" ./pm_nl_ctl add "${addr}" ${flags:+flags "${flags}"} \ ${dev:+dev "${dev}"} ${id:+id "${id}"} ${port:+port "${port}"} fi } mptcp_lib_pm_nl_del_endpoint() { local ns=${1} local id=${2} local addr=${3} if mptcp_lib_is_ip_mptcp; then [ "${id}" -ne 0 ] && addr='' ip -n "${ns}" mptcp endpoint delete id "${id}" ${addr:+"${addr}"} else ip netns exec "${ns}" ./pm_nl_ctl del "${id}" "${addr}" fi } mptcp_lib_pm_nl_flush_endpoint() { local ns=${1} if mptcp_lib_is_ip_mptcp; then ip -n "${ns}" mptcp endpoint flush else ip netns exec "${ns}" ./pm_nl_ctl flush fi } mptcp_lib_pm_nl_show_endpoints() { local ns=${1} if mptcp_lib_is_ip_mptcp; then ip -n "${ns}" mptcp endpoint show else ip netns exec "${ns}" ./pm_nl_ctl dump fi } mptcp_lib_pm_nl_change_endpoint() { local ns=${1} local id=${2} local flags=${3} if mptcp_lib_is_ip_mptcp; then # shellcheck disable=SC2086 # blanks in flags, no double quote ip -n "${ns}" mptcp endpoint change id "${id}" ${flags//","/" "} else ip netns exec "${ns}" ./pm_nl_ctl set id "${id}" flags "${flags}" fi }