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
11MPTCP_LIB_SUBTESTS=()
12
13# only if supported (or forced) and not disabled, see no-color.org
14if { [ -t 1 ] || [ "${SELFTESTS_MPTCP_LIB_COLOR_FORCE:-}" = "1" ]; } &&
15   [ "${NO_COLOR:-}" != "1" ]; then
16	readonly MPTCP_LIB_COLOR_RED="\E[1;31m"
17	readonly MPTCP_LIB_COLOR_GREEN="\E[1;32m"
18	readonly MPTCP_LIB_COLOR_YELLOW="\E[1;33m"
19	readonly MPTCP_LIB_COLOR_BLUE="\E[1;34m"
20	readonly MPTCP_LIB_COLOR_RESET="\E[0m"
21else
22	readonly MPTCP_LIB_COLOR_RED=
23	readonly MPTCP_LIB_COLOR_GREEN=
24	readonly MPTCP_LIB_COLOR_YELLOW=
25	readonly MPTCP_LIB_COLOR_BLUE=
26	readonly MPTCP_LIB_COLOR_RESET=
27fi
28
29# $1: color, $2: text
30mptcp_lib_print_color() {
31	echo -e "${MPTCP_LIB_START_PRINT:-}${*}${MPTCP_LIB_COLOR_RESET}"
32}
33
34mptcp_lib_print_ok() {
35	mptcp_lib_print_color "${MPTCP_LIB_COLOR_GREEN}${*}"
36}
37
38mptcp_lib_print_warn() {
39	mptcp_lib_print_color "${MPTCP_LIB_COLOR_YELLOW}${*}"
40}
41
42mptcp_lib_print_info() {
43	mptcp_lib_print_color "${MPTCP_LIB_COLOR_BLUE}${*}"
44}
45
46mptcp_lib_print_err() {
47	mptcp_lib_print_color "${MPTCP_LIB_COLOR_RED}${*}"
48}
49
50# SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES env var can be set when validating all
51# features using the last version of the kernel and the selftests to make sure
52# a test is not being skipped by mistake.
53mptcp_lib_expect_all_features() {
54	[ "${SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES:-}" = "1" ]
55}
56
57# $1: msg
58mptcp_lib_fail_if_expected_feature() {
59	if mptcp_lib_expect_all_features; then
60		echo "ERROR: missing feature: ${*}"
61		exit ${KSFT_FAIL}
62	fi
63
64	return 1
65}
66
67# $1: file
68mptcp_lib_has_file() {
69	local f="${1}"
70
71	if [ -f "${f}" ]; then
72		return 0
73	fi
74
75	mptcp_lib_fail_if_expected_feature "${f} file not found"
76}
77
78mptcp_lib_check_mptcp() {
79	if ! mptcp_lib_has_file "/proc/sys/net/mptcp/enabled"; then
80		echo "SKIP: MPTCP support is not available"
81		exit ${KSFT_SKIP}
82	fi
83}
84
85mptcp_lib_check_kallsyms() {
86	if ! mptcp_lib_has_file "/proc/kallsyms"; then
87		echo "SKIP: CONFIG_KALLSYMS is missing"
88		exit ${KSFT_SKIP}
89	fi
90}
91
92# Internal: use mptcp_lib_kallsyms_has() instead
93__mptcp_lib_kallsyms_has() {
94	local sym="${1}"
95
96	mptcp_lib_check_kallsyms
97
98	grep -q " ${sym}" /proc/kallsyms
99}
100
101# $1: part of a symbol to look at, add '$' at the end for full name
102mptcp_lib_kallsyms_has() {
103	local sym="${1}"
104
105	if __mptcp_lib_kallsyms_has "${sym}"; then
106		return 0
107	fi
108
109	mptcp_lib_fail_if_expected_feature "${sym} symbol not found"
110}
111
112# $1: part of a symbol to look at, add '$' at the end for full name
113mptcp_lib_kallsyms_doesnt_have() {
114	local sym="${1}"
115
116	if ! __mptcp_lib_kallsyms_has "${sym}"; then
117		return 0
118	fi
119
120	mptcp_lib_fail_if_expected_feature "${sym} symbol has been found"
121}
122
123# !!!AVOID USING THIS!!!
124# Features might not land in the expected version and features can be backported
125#
126# $1: kernel version, e.g. 6.3
127mptcp_lib_kversion_ge() {
128	local exp_maj="${1%.*}"
129	local exp_min="${1#*.}"
130	local v maj min
131
132	# If the kernel has backported features, set this env var to 1:
133	if [ "${SELFTESTS_MPTCP_LIB_NO_KVERSION_CHECK:-}" = "1" ]; then
134		return 0
135	fi
136
137	v=$(uname -r | cut -d'.' -f1,2)
138	maj=${v%.*}
139	min=${v#*.}
140
141	if   [ "${maj}" -gt "${exp_maj}" ] ||
142	   { [ "${maj}" -eq "${exp_maj}" ] && [ "${min}" -ge "${exp_min}" ]; }; then
143		return 0
144	fi
145
146	mptcp_lib_fail_if_expected_feature "kernel version ${1} lower than ${v}"
147}
148
149__mptcp_lib_result_add() {
150	local result="${1}"
151	shift
152
153	local id=$((${#MPTCP_LIB_SUBTESTS[@]} + 1))
154
155	MPTCP_LIB_SUBTESTS+=("${result} ${id} - ${KSFT_TEST}: ${*}")
156}
157
158# $1: test name
159mptcp_lib_result_pass() {
160	__mptcp_lib_result_add "ok" "${1}"
161}
162
163# $1: test name
164mptcp_lib_result_fail() {
165	__mptcp_lib_result_add "not ok" "${1}"
166}
167
168# $1: test name
169mptcp_lib_result_skip() {
170	__mptcp_lib_result_add "ok" "${1} # SKIP"
171}
172
173# $1: result code ; $2: test name
174mptcp_lib_result_code() {
175	local ret="${1}"
176	local name="${2}"
177
178	case "${ret}" in
179		"${KSFT_PASS}")
180			mptcp_lib_result_pass "${name}"
181			;;
182		"${KSFT_FAIL}")
183			mptcp_lib_result_fail "${name}"
184			;;
185		"${KSFT_SKIP}")
186			mptcp_lib_result_skip "${name}"
187			;;
188		*)
189			echo "ERROR: wrong result code: ${ret}"
190			exit ${KSFT_FAIL}
191			;;
192	esac
193}
194
195mptcp_lib_result_print_all_tap() {
196	local subtest
197
198	if [ ${#MPTCP_LIB_SUBTESTS[@]} -eq 0 ] ||
199	   [ "${SELFTESTS_MPTCP_LIB_NO_TAP:-}" = "1" ]; then
200		return
201	fi
202
203	printf "\nTAP version 13\n"
204	printf "1..%d\n" "${#MPTCP_LIB_SUBTESTS[@]}"
205
206	for subtest in "${MPTCP_LIB_SUBTESTS[@]}"; do
207		printf "%s\n" "${subtest}"
208	done
209}
210
211# get the value of keyword $1 in the line marked by keyword $2
212mptcp_lib_get_info_value() {
213	grep "${2}" | sed -n 's/.*\('"${1}"':\)\([0-9a-f:.]*\).*$/\2/p;q'
214}
215
216# $1: info name ; $2: evts_ns ; $3: event type
217mptcp_lib_evts_get_info() {
218	mptcp_lib_get_info_value "${1}" "^type:${3:-1}," < "${2}"
219}
220
221# $1: PID
222mptcp_lib_kill_wait() {
223	[ "${1}" -eq 0 ] && return 0
224
225	kill -SIGUSR1 "${1}" > /dev/null 2>&1
226	kill "${1}" > /dev/null 2>&1
227	wait "${1}" 2>/dev/null
228}
229
230# $1: IP address
231mptcp_lib_is_v6() {
232	[ -z "${1##*:*}" ]
233}
234
235# $1: ns, $2: MIB counter
236mptcp_lib_get_counter() {
237	local ns="${1}"
238	local counter="${2}"
239	local count
240
241	count=$(ip netns exec "${ns}" nstat -asz "${counter}" |
242		awk 'NR==1 {next} {print $2}')
243	if [ -z "${count}" ]; then
244		mptcp_lib_fail_if_expected_feature "${counter} counter"
245		return 1
246	fi
247
248	echo "${count}"
249}
250
251mptcp_lib_make_file() {
252	local name="${1}"
253	local bs="${2}"
254	local size="${3}"
255
256	dd if=/dev/urandom of="${name}" bs="${bs}" count="${size}" 2> /dev/null
257	echo -e "\nMPTCP_TEST_FILE_END_MARKER" >> "${name}"
258}
259
260# $1: file
261mptcp_lib_print_file_err() {
262	ls -l "${1}" 1>&2
263	echo "Trailing bytes are: "
264	tail -c 27 "${1}"
265}
266
267# $1: input file ; $2: output file ; $3: what kind of file
268mptcp_lib_check_transfer() {
269	local in="${1}"
270	local out="${2}"
271	local what="${3}"
272
273	if ! cmp "$in" "$out" > /dev/null 2>&1; then
274		echo "[ FAIL ] $what does not match (in, out):"
275		mptcp_lib_print_file_err "$in"
276		mptcp_lib_print_file_err "$out"
277
278		return 1
279	fi
280
281	return 0
282}
283
284# $1: ns, $2: port
285mptcp_lib_wait_local_port_listen() {
286	local listener_ns="${1}"
287	local port="${2}"
288
289	local port_hex
290	port_hex="$(printf "%04X" "${port}")"
291
292	local _
293	for _ in $(seq 10); do
294		ip netns exec "${listener_ns}" cat /proc/net/tcp* | \
295			awk "BEGIN {rc=1} {if (\$2 ~ /:${port_hex}\$/ && \$4 ~ /0A/) \
296			     {rc=0; exit}} END {exit rc}" &&
297			break
298		sleep 0.1
299	done
300}
301