Files
linux-net/tools/testing/selftests/net/mptcp/mptcp_lib.sh
Matthieu Baerts (NGI0) a17c5aa3a3 selftests: mptcp: print trailing bytes with od
This is better than printing random bytes in the terminal.

Note that Jakub suggested 'hexdump', but Mat found out this tool is not
often installed by default. 'od' can do a similar job, and it is in the
POSIX specs and available in coreutils, so it should be on more systems.

While at it, display a few more bytes, just to fill in the two lines.
And no need to display the 3rd only line showing the next number of
bytes: 0000040.

Suggested-by: Jakub Kicinski <kuba@kernel.org>
Suggested-by: Mat Martineau <martineau@kernel.org>
Reviewed-by: Mat Martineau <martineau@kernel.org>
Reviewed-by: Geliang Tang <geliang@kernel.org>
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
Link: https://patch.msgid.link/20250912-net-mptcp-fix-sft-connect-v1-4-d40e77cbbf02@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2025-09-15 18:10:37 -07:00

696 lines
16 KiB
Bash

#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(dirname "${0}")/../lib.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: ${*}"
}
# $1-2: listener/connector ns ; $3 port ; $4-5 listener/connector stat file
mptcp_lib_pr_err_stats() {
local lns="${1}"
local cns="${2}"
local port="${3}"
local lstat="${4}"
local cstat="${5}"
echo -en "${MPTCP_LIB_COLOR_RED}"
{
printf "\nnetns %s (listener) socket stat for %d:\n" "${lns}" "${port}"
ip netns exec "${lns}" ss -Menitam -o "sport = :${port}"
cat "${lstat}"
printf "\nnetns %s (connector) socket stat for %d:\n" "${cns}" "${port}"
ip netns exec "${cns}" ss -Menitam -o "dport = :${port}"
[ "${lstat}" != "${cstat}" ] && cat "${cstat}"
} 1>&2
echo -en "${MPTCP_LIB_COLOR_RESET}"
}
# 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}" 2>/dev/null |
sed -n 's/.*\('"${1}"':\)\([0-9a-f:.]*\).*$/\2/p;q'
# the ';q' at the end limits to the first matched entry.
}
# $1: info name ; $2: evts_ns ; [$3: event type; [$4: addr]]
mptcp_lib_evts_get_info() {
grep "${4:-}" "${2}" 2>/dev/null |
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 32 "${1}" | od -x | head -n2
}
# $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
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: <id>,<ip>,<flags>,<dev>
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
}