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