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