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