xref: /linux/tools/testing/selftests/net/mptcp/mptcp_lib.sh (revision d5859510d35d8e7d63fed5169f1775317f40fb03)
1#! /bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4readonly KSFT_PASS=0
5readonly KSFT_FAIL=1
6readonly KSFT_SKIP=4
7
8# shellcheck disable=SC2155 # declare and assign separately
9readonly KSFT_TEST="${MPTCP_LIB_KSFT_TEST:-$(basename "${0}" .sh)}"
10
11# These variables are used in some selftests, read-only
12declare -rx MPTCP_LIB_EVENT_ANNOUNCED=6         # MPTCP_EVENT_ANNOUNCED
13declare -rx MPTCP_LIB_EVENT_REMOVED=7           # MPTCP_EVENT_REMOVED
14declare -rx MPTCP_LIB_EVENT_SUB_ESTABLISHED=10  # MPTCP_EVENT_SUB_ESTABLISHED
15declare -rx MPTCP_LIB_EVENT_SUB_CLOSED=11       # MPTCP_EVENT_SUB_CLOSED
16declare -rx MPTCP_LIB_EVENT_LISTENER_CREATED=15 # MPTCP_EVENT_LISTENER_CREATED
17declare -rx MPTCP_LIB_EVENT_LISTENER_CLOSED=16  # MPTCP_EVENT_LISTENER_CLOSED
18
19declare -rx MPTCP_LIB_AF_INET=2
20declare -rx MPTCP_LIB_AF_INET6=10
21
22MPTCP_LIB_SUBTESTS=()
23MPTCP_LIB_SUBTESTS_DUPLICATED=0
24MPTCP_LIB_SUBTEST_FLAKY=0
25MPTCP_LIB_TEST_COUNTER=0
26MPTCP_LIB_TEST_FORMAT="%02u %-50s"
27MPTCP_LIB_IP_MPTCP=0
28
29# only if supported (or forced) and not disabled, see no-color.org
30if { [ -t 1 ] || [ "${SELFTESTS_MPTCP_LIB_COLOR_FORCE:-}" = "1" ]; } &&
31   [ "${NO_COLOR:-}" != "1" ]; then
32	readonly MPTCP_LIB_COLOR_RED="\E[1;31m"
33	readonly MPTCP_LIB_COLOR_GREEN="\E[1;32m"
34	readonly MPTCP_LIB_COLOR_YELLOW="\E[1;33m"
35	readonly MPTCP_LIB_COLOR_BLUE="\E[1;34m"
36	readonly MPTCP_LIB_COLOR_RESET="\E[0m"
37else
38	readonly MPTCP_LIB_COLOR_RED=
39	readonly MPTCP_LIB_COLOR_GREEN=
40	readonly MPTCP_LIB_COLOR_YELLOW=
41	readonly MPTCP_LIB_COLOR_BLUE=
42	readonly MPTCP_LIB_COLOR_RESET=
43fi
44
45# SELFTESTS_MPTCP_LIB_OVERRIDE_FLAKY env var can be set not to ignore errors
46# from subtests marked as flaky
47mptcp_lib_override_flaky() {
48	[ "${SELFTESTS_MPTCP_LIB_OVERRIDE_FLAKY:-}" = 1 ]
49}
50
51mptcp_lib_subtest_is_flaky() {
52	[ "${MPTCP_LIB_SUBTEST_FLAKY}" = 1 ] && ! mptcp_lib_override_flaky
53}
54
55# $1: color, $2: text
56mptcp_lib_print_color() {
57	echo -e "${MPTCP_LIB_START_PRINT:-}${*}${MPTCP_LIB_COLOR_RESET}"
58}
59
60mptcp_lib_print_ok() {
61	mptcp_lib_print_color "${MPTCP_LIB_COLOR_GREEN}${*}"
62}
63
64mptcp_lib_print_warn() {
65	mptcp_lib_print_color "${MPTCP_LIB_COLOR_YELLOW}${*}"
66}
67
68mptcp_lib_print_info() {
69	mptcp_lib_print_color "${MPTCP_LIB_COLOR_BLUE}${*}"
70}
71
72mptcp_lib_print_err() {
73	mptcp_lib_print_color "${MPTCP_LIB_COLOR_RED}${*}"
74}
75
76# shellcheck disable=SC2120 # parameters are optional
77mptcp_lib_pr_ok() {
78	mptcp_lib_print_ok "[ OK ]${1:+ ${*}}"
79}
80
81mptcp_lib_pr_skip() {
82	mptcp_lib_print_warn "[SKIP]${1:+ ${*}}"
83}
84
85mptcp_lib_pr_fail() {
86	local title cmt
87
88	if mptcp_lib_subtest_is_flaky; then
89		title="IGNO"
90		cmt=" (flaky)"
91	else
92		title="FAIL"
93	fi
94
95	mptcp_lib_print_err "[${title}]${cmt}${1:+ ${*}}"
96}
97
98mptcp_lib_pr_info() {
99	mptcp_lib_print_info "INFO: ${*}"
100}
101
102# SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES env var can be set when validating all
103# features using the last version of the kernel and the selftests to make sure
104# a test is not being skipped by mistake.
105mptcp_lib_expect_all_features() {
106	[ "${SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES:-}" = "1" ]
107}
108
109# $1: msg
110mptcp_lib_fail_if_expected_feature() {
111	if mptcp_lib_expect_all_features; then
112		echo "ERROR: missing feature: ${*}"
113		exit ${KSFT_FAIL}
114	fi
115
116	return 1
117}
118
119# $1: file
120mptcp_lib_has_file() {
121	local f="${1}"
122
123	if [ -f "${f}" ]; then
124		return 0
125	fi
126
127	mptcp_lib_fail_if_expected_feature "${f} file not found"
128}
129
130mptcp_lib_check_mptcp() {
131	if ! mptcp_lib_has_file "/proc/sys/net/mptcp/enabled"; then
132		mptcp_lib_pr_skip "MPTCP support is not available"
133		exit ${KSFT_SKIP}
134	fi
135}
136
137mptcp_lib_check_kallsyms() {
138	if ! mptcp_lib_has_file "/proc/kallsyms"; then
139		mptcp_lib_pr_skip "CONFIG_KALLSYMS is missing"
140		exit ${KSFT_SKIP}
141	fi
142}
143
144# Internal: use mptcp_lib_kallsyms_has() instead
145__mptcp_lib_kallsyms_has() {
146	local sym="${1}"
147
148	mptcp_lib_check_kallsyms
149
150	grep -q " ${sym}" /proc/kallsyms
151}
152
153# $1: part of a symbol to look at, add '$' at the end for full name
154mptcp_lib_kallsyms_has() {
155	local sym="${1}"
156
157	if __mptcp_lib_kallsyms_has "${sym}"; then
158		return 0
159	fi
160
161	mptcp_lib_fail_if_expected_feature "${sym} symbol not found"
162}
163
164# $1: part of a symbol to look at, add '$' at the end for full name
165mptcp_lib_kallsyms_doesnt_have() {
166	local sym="${1}"
167
168	if ! __mptcp_lib_kallsyms_has "${sym}"; then
169		return 0
170	fi
171
172	mptcp_lib_fail_if_expected_feature "${sym} symbol has been found"
173}
174
175# !!!AVOID USING THIS!!!
176# Features might not land in the expected version and features can be backported
177#
178# $1: kernel version, e.g. 6.3
179mptcp_lib_kversion_ge() {
180	local exp_maj="${1%.*}"
181	local exp_min="${1#*.}"
182	local v maj min
183
184	# If the kernel has backported features, set this env var to 1:
185	if [ "${SELFTESTS_MPTCP_LIB_NO_KVERSION_CHECK:-}" = "1" ]; then
186		return 0
187	fi
188
189	v=$(uname -r | cut -d'.' -f1,2)
190	maj=${v%.*}
191	min=${v#*.}
192
193	if   [ "${maj}" -gt "${exp_maj}" ] ||
194	   { [ "${maj}" -eq "${exp_maj}" ] && [ "${min}" -ge "${exp_min}" ]; }; then
195		return 0
196	fi
197
198	mptcp_lib_fail_if_expected_feature "kernel version ${1} lower than ${v}"
199}
200
201__mptcp_lib_result_check_duplicated() {
202	local subtest
203
204	for subtest in "${MPTCP_LIB_SUBTESTS[@]}"; do
205		if [[ "${subtest}" == *" - ${KSFT_TEST}: ${*%% #*}" ]]; then
206			MPTCP_LIB_SUBTESTS_DUPLICATED=1
207			mptcp_lib_print_err "Duplicated entry: ${*}"
208			break
209		fi
210	done
211}
212
213__mptcp_lib_result_add() {
214	local result="${1}"
215	shift
216
217	local id=$((${#MPTCP_LIB_SUBTESTS[@]} + 1))
218
219	__mptcp_lib_result_check_duplicated "${*}"
220
221	MPTCP_LIB_SUBTESTS+=("${result} ${id} - ${KSFT_TEST}: ${*}")
222}
223
224# $1: test name
225mptcp_lib_result_pass() {
226	__mptcp_lib_result_add "ok" "${1}"
227}
228
229# $1: test name
230mptcp_lib_result_fail() {
231	if mptcp_lib_subtest_is_flaky; then
232		# It might sound better to use 'not ok # TODO' or 'ok # SKIP',
233		# but some CIs don't understand 'TODO' and treat SKIP as errors.
234		__mptcp_lib_result_add "ok" "${1} # IGNORE Flaky"
235	else
236		__mptcp_lib_result_add "not ok" "${1}"
237	fi
238}
239
240# $1: test name
241mptcp_lib_result_skip() {
242	__mptcp_lib_result_add "ok" "${1} # SKIP"
243}
244
245# $1: result code ; $2: test name
246mptcp_lib_result_code() {
247	local ret="${1}"
248	local name="${2}"
249
250	case "${ret}" in
251		"${KSFT_PASS}")
252			mptcp_lib_result_pass "${name}"
253			;;
254		"${KSFT_FAIL}")
255			mptcp_lib_result_fail "${name}"
256			;;
257		"${KSFT_SKIP}")
258			mptcp_lib_result_skip "${name}"
259			;;
260		*)
261			echo "ERROR: wrong result code: ${ret}"
262			exit ${KSFT_FAIL}
263			;;
264	esac
265}
266
267mptcp_lib_result_print_all_tap() {
268	local subtest
269
270	if [ ${#MPTCP_LIB_SUBTESTS[@]} -eq 0 ] ||
271	   [ "${SELFTESTS_MPTCP_LIB_NO_TAP:-}" = "1" ]; then
272		return
273	fi
274
275	printf "\nTAP version 13\n"
276	printf "1..%d\n" "${#MPTCP_LIB_SUBTESTS[@]}"
277
278	for subtest in "${MPTCP_LIB_SUBTESTS[@]}"; do
279		printf "%s\n" "${subtest}"
280	done
281
282	if [ "${MPTCP_LIB_SUBTESTS_DUPLICATED}" = 1 ] &&
283	   mptcp_lib_expect_all_features; then
284		mptcp_lib_print_err "Duplicated test entries"
285		exit ${KSFT_FAIL}
286	fi
287}
288
289# get the value of keyword $1 in the line marked by keyword $2
290mptcp_lib_get_info_value() {
291	grep "${2}" | sed -n 's/.*\('"${1}"':\)\([0-9a-f:.]*\).*$/\2/p;q'
292}
293
294# $1: info name ; $2: evts_ns ; [$3: event type; [$4: addr]]
295mptcp_lib_evts_get_info() {
296	grep "${4:-}" "${2}" | mptcp_lib_get_info_value "${1}" "^type:${3:-1},"
297}
298
299# $1: PID
300mptcp_lib_kill_wait() {
301	[ "${1}" -eq 0 ] && return 0
302
303	kill -SIGUSR1 "${1}" > /dev/null 2>&1
304	kill "${1}" > /dev/null 2>&1
305	wait "${1}" 2>/dev/null
306}
307
308# $1: IP address
309mptcp_lib_is_v6() {
310	[ -z "${1##*:*}" ]
311}
312
313# $1: ns, $2: MIB counter
314mptcp_lib_get_counter() {
315	local ns="${1}"
316	local counter="${2}"
317	local count
318
319	count=$(ip netns exec "${ns}" nstat -asz "${counter}" |
320		awk 'NR==1 {next} {print $2}')
321	if [ -z "${count}" ]; then
322		mptcp_lib_fail_if_expected_feature "${counter} counter"
323		return 1
324	fi
325
326	echo "${count}"
327}
328
329mptcp_lib_make_file() {
330	local name="${1}"
331	local bs="${2}"
332	local size="${3}"
333
334	dd if=/dev/urandom of="${name}" bs="${bs}" count="${size}" 2> /dev/null
335	echo -e "\nMPTCP_TEST_FILE_END_MARKER" >> "${name}"
336}
337
338# $1: file
339mptcp_lib_print_file_err() {
340	ls -l "${1}" 1>&2
341	echo "Trailing bytes are: "
342	tail -c 27 "${1}"
343}
344
345# $1: input file ; $2: output file ; $3: what kind of file
346mptcp_lib_check_transfer() {
347	local in="${1}"
348	local out="${2}"
349	local what="${3}"
350
351	if ! cmp "$in" "$out" > /dev/null 2>&1; then
352		mptcp_lib_pr_fail "$what does not match (in, out):"
353		mptcp_lib_print_file_err "$in"
354		mptcp_lib_print_file_err "$out"
355
356		return 1
357	fi
358
359	return 0
360}
361
362# $1: ns, $2: port
363mptcp_lib_wait_local_port_listen() {
364	local listener_ns="${1}"
365	local port="${2}"
366
367	local port_hex
368	port_hex="$(printf "%04X" "${port}")"
369
370	local _
371	for _ in $(seq 10); do
372		ip netns exec "${listener_ns}" cat /proc/net/tcp* | \
373			awk "BEGIN {rc=1} {if (\$2 ~ /:${port_hex}\$/ && \$4 ~ /0A/) \
374			     {rc=0; exit}} END {exit rc}" &&
375			break
376		sleep 0.1
377	done
378}
379
380mptcp_lib_check_output() {
381	local err="${1}"
382	local cmd="${2}"
383	local expected="${3}"
384	local cmd_ret=0
385	local out
386
387	if ! out=$(${cmd} 2>"${err}"); then
388		cmd_ret=${?}
389	fi
390
391	if [ ${cmd_ret} -ne 0 ]; then
392		mptcp_lib_pr_fail "command execution '${cmd}' stderr"
393		cat "${err}"
394		return 2
395	elif [ "${out}" = "${expected}" ]; then
396		return 0
397	else
398		mptcp_lib_pr_fail "expected '${expected}' got '${out}'"
399		return 1
400	fi
401}
402
403mptcp_lib_check_tools() {
404	local tool
405
406	for tool in "${@}"; do
407		case "${tool}" in
408		"ip")
409			if ! ip -Version &> /dev/null; then
410				mptcp_lib_pr_skip "Could not run test without ip tool"
411				exit ${KSFT_SKIP}
412			fi
413			;;
414		"tc")
415			if ! tc -help &> /dev/null; then
416				mptcp_lib_pr_skip "Could not run test without tc tool"
417				exit ${KSFT_SKIP}
418			fi
419			;;
420		"ss")
421			if ! ss -h | grep -q MPTCP; then
422				mptcp_lib_pr_skip "ss tool does not support MPTCP"
423				exit ${KSFT_SKIP}
424			fi
425			;;
426		"iptables"* | "ip6tables"*)
427			if ! "${tool}" -V &> /dev/null; then
428				mptcp_lib_pr_skip "Could not run all tests without ${tool}"
429				exit ${KSFT_SKIP}
430			fi
431			;;
432		*)
433			mptcp_lib_pr_fail "Internal error: unsupported tool: ${tool}"
434			exit ${KSFT_FAIL}
435			;;
436		esac
437	done
438}
439
440mptcp_lib_ns_init() {
441	local sec rndh
442
443	sec=$(date +%s)
444	rndh=$(printf %x "${sec}")-$(mktemp -u XXXXXX)
445
446	local netns
447	for netns in "${@}"; do
448		eval "${netns}=${netns}-${rndh}"
449
450		ip netns add "${!netns}" || exit ${KSFT_SKIP}
451		ip -net "${!netns}" link set lo up
452		ip netns exec "${!netns}" sysctl -q net.mptcp.enabled=1
453		ip netns exec "${!netns}" sysctl -q net.ipv4.conf.all.rp_filter=0
454		ip netns exec "${!netns}" sysctl -q net.ipv4.conf.default.rp_filter=0
455	done
456}
457
458mptcp_lib_ns_exit() {
459	local netns
460	for netns in "${@}"; do
461		ip netns del "${netns}"
462		rm -f /tmp/"${netns}".{nstat,out}
463	done
464}
465
466mptcp_lib_events() {
467	local ns="${1}"
468	local evts="${2}"
469	declare -n pid="${3}"
470
471	:>"${evts}"
472
473	mptcp_lib_kill_wait "${pid:-0}"
474	ip netns exec "${ns}" ./pm_nl_ctl events >> "${evts}" 2>&1 &
475	pid=$!
476}
477
478mptcp_lib_print_title() {
479	: "${MPTCP_LIB_TEST_COUNTER:?}"
480	: "${MPTCP_LIB_TEST_FORMAT:?}"
481
482	# shellcheck disable=SC2059 # the format is in a variable
483	printf "${MPTCP_LIB_TEST_FORMAT}" "$((++MPTCP_LIB_TEST_COUNTER))" "${*}"
484}
485
486# $1: var name ; $2: prev ret
487mptcp_lib_check_expected_one() {
488	local var="${1}"
489	local exp="e_${var}"
490	local prev_ret="${2}"
491
492	if [ "${!var}" = "${!exp}" ]; then
493		return 0
494	fi
495
496	if [ "${prev_ret}" = "0" ]; then
497		mptcp_lib_pr_fail
498	fi
499
500	mptcp_lib_print_err "Expected value for '${var}': '${!exp}', got '${!var}'."
501	return 1
502}
503
504# $@: all var names to check
505mptcp_lib_check_expected() {
506	local rc=0
507	local var
508
509	for var in "${@}"; do
510		mptcp_lib_check_expected_one "${var}" "${rc}" || rc=1
511	done
512
513	return "${rc}"
514}
515
516# shellcheck disable=SC2034 # Some variables are used below but indirectly
517mptcp_lib_verify_listener_events() {
518	local evt=${1}
519	local e_type=${2}
520	local e_family=${3}
521	local e_saddr=${4}
522	local e_sport=${5}
523	local type
524	local family
525	local saddr
526	local sport
527	local rc=0
528
529	type=$(mptcp_lib_evts_get_info type "${evt}" "${e_type}")
530	family=$(mptcp_lib_evts_get_info family "${evt}" "${e_type}")
531	if [ "${family}" ] && [ "${family}" = "${AF_INET6}" ]; then
532		saddr=$(mptcp_lib_evts_get_info saddr6 "${evt}" "${e_type}")
533	else
534		saddr=$(mptcp_lib_evts_get_info saddr4 "${evt}" "${e_type}")
535	fi
536	sport=$(mptcp_lib_evts_get_info sport "${evt}" "${e_type}")
537
538	mptcp_lib_check_expected "type" "family" "saddr" "sport" || rc="${?}"
539	return "${rc}"
540}
541
542mptcp_lib_set_ip_mptcp() {
543	MPTCP_LIB_IP_MPTCP=1
544}
545
546mptcp_lib_is_ip_mptcp() {
547	[ "${MPTCP_LIB_IP_MPTCP}" = "1" ]
548}
549
550# format: <id>,<ip>,<flags>,<dev>
551mptcp_lib_pm_nl_format_endpoints() {
552	local entry id ip flags dev port
553
554	for entry in "${@}"; do
555		IFS=, read -r id ip flags dev port <<< "${entry}"
556		if mptcp_lib_is_ip_mptcp; then
557			echo -n "${ip}"
558			[ -n "${port}" ] && echo -n " port ${port}"
559			echo -n " id ${id}"
560			[ -n "${flags}" ] && echo -n " ${flags}"
561			[ -n "${dev}" ] && echo -n " dev ${dev}"
562			echo " " # always a space at the end
563		else
564			echo -n "id ${id}"
565			echo -n " flags ${flags//" "/","}"
566			[ -n "${dev}" ] && echo -n " dev ${dev}"
567			echo -n " ${ip}"
568			[ -n "${port}" ] && echo -n " ${port}"
569			echo ""
570		fi
571	done
572}
573
574mptcp_lib_pm_nl_get_endpoint() {
575	local ns=${1}
576	local id=${2}
577
578	if mptcp_lib_is_ip_mptcp; then
579		ip -n "${ns}" mptcp endpoint show id "${id}"
580	else
581		ip netns exec "${ns}" ./pm_nl_ctl get "${id}"
582	fi
583}
584
585mptcp_lib_pm_nl_set_limits() {
586	local ns=${1}
587	local addrs=${2}
588	local subflows=${3}
589
590	if mptcp_lib_is_ip_mptcp; then
591		ip -n "${ns}" mptcp limits set add_addr_accepted "${addrs}" subflows "${subflows}"
592	else
593		ip netns exec "${ns}" ./pm_nl_ctl limits "${addrs}" "${subflows}"
594	fi
595}
596
597mptcp_lib_pm_nl_add_endpoint() {
598	local ns=${1}
599	local addr=${2}
600	local flags dev id port
601	local nr=2
602
603	local p
604	for p in "${@}"; do
605		case "${p}" in
606		"flags" | "dev" | "id" | "port")
607			eval "${p}"=\$"${nr}"
608			;;
609		esac
610
611		nr=$((nr + 1))
612	done
613
614	if mptcp_lib_is_ip_mptcp; then
615		# shellcheck disable=SC2086 # blanks in flags, no double quote
616		ip -n "${ns}" mptcp endpoint add "${addr}" ${flags//","/" "} \
617			${dev:+dev "${dev}"} ${id:+id "${id}"} ${port:+port "${port}"}
618	else
619		ip netns exec "${ns}" ./pm_nl_ctl add "${addr}" ${flags:+flags "${flags}"} \
620			${dev:+dev "${dev}"} ${id:+id "${id}"} ${port:+port "${port}"}
621	fi
622}
623
624mptcp_lib_pm_nl_del_endpoint() {
625	local ns=${1}
626	local id=${2}
627	local addr=${3}
628
629	if mptcp_lib_is_ip_mptcp; then
630		[ "${id}" -ne 0 ] && addr=''
631		ip -n "${ns}" mptcp endpoint delete id "${id}" ${addr:+"${addr}"}
632	else
633		ip netns exec "${ns}" ./pm_nl_ctl del "${id}" "${addr}"
634	fi
635}
636
637mptcp_lib_pm_nl_flush_endpoint() {
638	local ns=${1}
639
640	if mptcp_lib_is_ip_mptcp; then
641		ip -n "${ns}" mptcp endpoint flush
642	else
643		ip netns exec "${ns}" ./pm_nl_ctl flush
644	fi
645}
646
647mptcp_lib_pm_nl_show_endpoints() {
648	local ns=${1}
649
650	if mptcp_lib_is_ip_mptcp; then
651		ip -n "${ns}" mptcp endpoint show
652	else
653		ip netns exec "${ns}" ./pm_nl_ctl dump
654	fi
655}
656
657mptcp_lib_pm_nl_change_endpoint() {
658	local ns=${1}
659	local id=${2}
660	local flags=${3}
661
662	if mptcp_lib_is_ip_mptcp; then
663		# shellcheck disable=SC2086 # blanks in flags, no double quote
664		ip -n "${ns}" mptcp endpoint change id "${id}" ${flags//","/" "}
665	else
666		ip netns exec "${ns}" ./pm_nl_ctl set id "${id}" flags "${flags}"
667	fi
668}
669