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