xref: /linux/tools/testing/selftests/net/mptcp/mptcp_lib.sh (revision d23c41366e85f149b48323d66adc36c4a9f18cbd)
1#! /bin/bash
2# SPDX-License-Identifier: GPL-2.0
3
4. "$(dirname "${0}")/../lib.sh"
5
6readonly KSFT_PASS=0
7readonly KSFT_FAIL=1
8readonly KSFT_SKIP=4
9
10# shellcheck disable=SC2155 # declare and assign separately
11readonly KSFT_TEST="${MPTCP_LIB_KSFT_TEST:-$(basename "${0}" .sh)}"
12
13# These variables are used in some selftests, read-only
14declare -rx MPTCP_LIB_EVENT_CREATED=1           # MPTCP_EVENT_CREATED
15declare -rx MPTCP_LIB_EVENT_ESTABLISHED=2       # MPTCP_EVENT_ESTABLISHED
16declare -rx MPTCP_LIB_EVENT_CLOSED=3            # MPTCP_EVENT_CLOSED
17declare -rx MPTCP_LIB_EVENT_ANNOUNCED=6         # MPTCP_EVENT_ANNOUNCED
18declare -rx MPTCP_LIB_EVENT_REMOVED=7           # MPTCP_EVENT_REMOVED
19declare -rx MPTCP_LIB_EVENT_SUB_ESTABLISHED=10  # MPTCP_EVENT_SUB_ESTABLISHED
20declare -rx MPTCP_LIB_EVENT_SUB_CLOSED=11       # MPTCP_EVENT_SUB_CLOSED
21declare -rx MPTCP_LIB_EVENT_SUB_PRIORITY=13     # MPTCP_EVENT_SUB_PRIORITY
22declare -rx MPTCP_LIB_EVENT_LISTENER_CREATED=15 # MPTCP_EVENT_LISTENER_CREATED
23declare -rx MPTCP_LIB_EVENT_LISTENER_CLOSED=16  # MPTCP_EVENT_LISTENER_CLOSED
24
25declare -rx MPTCP_LIB_AF_INET=2
26declare -rx MPTCP_LIB_AF_INET6=10
27
28MPTCP_LIB_SUBTESTS=()
29MPTCP_LIB_SUBTESTS_DUPLICATED=0
30MPTCP_LIB_SUBTEST_FLAKY=0
31MPTCP_LIB_SUBTESTS_LAST_TS_NS=
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
109mptcp_lib_pr_nstat() {
110	local ns="${1}"
111	local cache="/tmp/${ns}.out"
112	local hist="/tmp/${ns}.nstat"
113
114	if [ -f "${cache}" ]; then
115		awk '$2 != 0 { print "  "$0 }' "${cache}"
116	else
117		NSTAT_HISTORY="${hist}" ip netns exec "${ns}" nstat -s |
118			grep Tcp
119	fi
120}
121
122# $1-2: listener/connector ns ; $3 port
123mptcp_lib_pr_err_stats() {
124	local lns="${1}"
125	local cns="${2}"
126	local port="${3}"
127
128	echo -en "${MPTCP_LIB_COLOR_RED}"
129	{
130		printf "\nnetns %s (listener) socket stat for %d:\n" "${lns}" "${port}"
131		ip netns exec "${lns}" ss -Menitam -o "sport = :${port}"
132		mptcp_lib_pr_nstat "${lns}"
133
134		printf "\nnetns %s (connector) socket stat for %d:\n" "${cns}" "${port}"
135		ip netns exec "${cns}" ss -Menitam -o "dport = :${port}"
136		[ "${lns}" != "${cns}" ] && mptcp_lib_pr_nstat "${cns}"
137	} 1>&2
138	echo -en "${MPTCP_LIB_COLOR_RESET}"
139}
140
141# SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES env var can be set when validating all
142# features using the last version of the kernel and the selftests to make sure
143# a test is not being skipped by mistake.
144mptcp_lib_expect_all_features() {
145	[ "${SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES:-}" = "1" ]
146}
147
148# $1: msg
149mptcp_lib_fail_if_expected_feature() {
150	if mptcp_lib_expect_all_features; then
151		echo "ERROR: missing feature: ${*}"
152		exit ${KSFT_FAIL}
153	fi
154
155	return 1
156}
157
158# $1: file
159mptcp_lib_has_file() {
160	local f="${1}"
161
162	if [ -f "${f}" ]; then
163		return 0
164	fi
165
166	mptcp_lib_fail_if_expected_feature "${f} file not found"
167}
168
169mptcp_lib_check_mptcp() {
170	if ! mptcp_lib_has_file "/proc/sys/net/mptcp/enabled"; then
171		mptcp_lib_pr_skip "MPTCP support is not available"
172		exit ${KSFT_SKIP}
173	fi
174}
175
176mptcp_lib_check_kallsyms() {
177	if ! mptcp_lib_has_file "/proc/kallsyms"; then
178		mptcp_lib_pr_skip "CONFIG_KALLSYMS is missing"
179		exit ${KSFT_SKIP}
180	fi
181}
182
183# Internal: use mptcp_lib_kallsyms_has() instead
184__mptcp_lib_kallsyms_has() {
185	local sym="${1}"
186
187	mptcp_lib_check_kallsyms
188
189	grep -q " ${sym}" /proc/kallsyms
190}
191
192# $1: part of a symbol to look at, add '$' at the end for full name
193mptcp_lib_kallsyms_has() {
194	local sym="${1}"
195
196	if __mptcp_lib_kallsyms_has "${sym}"; then
197		return 0
198	fi
199
200	mptcp_lib_fail_if_expected_feature "${sym} symbol not found"
201}
202
203# $1: part of a symbol to look at, add '$' at the end for full name
204mptcp_lib_kallsyms_doesnt_have() {
205	local sym="${1}"
206
207	if ! __mptcp_lib_kallsyms_has "${sym}"; then
208		return 0
209	fi
210
211	mptcp_lib_fail_if_expected_feature "${sym} symbol has been found"
212}
213
214# !!!AVOID USING THIS!!!
215# Features might not land in the expected version and features can be backported
216#
217# $1: kernel version, e.g. 6.3
218mptcp_lib_kversion_ge() {
219	local exp_maj="${1%.*}"
220	local exp_min="${1#*.}"
221	local v maj min
222
223	# If the kernel has backported features, set this env var to 1:
224	if [ "${SELFTESTS_MPTCP_LIB_NO_KVERSION_CHECK:-}" = "1" ]; then
225		return 0
226	fi
227
228	v=$(uname -r | cut -d'.' -f1,2)
229	maj=${v%.*}
230	min=${v#*.}
231
232	if   [ "${maj}" -gt "${exp_maj}" ] ||
233	   { [ "${maj}" -eq "${exp_maj}" ] && [ "${min}" -ge "${exp_min}" ]; }; then
234		return 0
235	fi
236
237	mptcp_lib_fail_if_expected_feature "kernel version ${1} lower than ${v}"
238}
239
240mptcp_lib_subtests_last_ts_reset() {
241	MPTCP_LIB_SUBTESTS_LAST_TS_NS="$(date +%s%N)"
242}
243mptcp_lib_subtests_last_ts_reset
244
245__mptcp_lib_result_check_duplicated() {
246	local subtest
247
248	for subtest in "${MPTCP_LIB_SUBTESTS[@]}"; do
249		if [[ "${subtest}" == *" - ${KSFT_TEST}: ${*%% #*}" ]]; then
250			MPTCP_LIB_SUBTESTS_DUPLICATED=1
251			mptcp_lib_print_err "Duplicated entry: ${*}"
252			break
253		fi
254	done
255}
256
257__mptcp_lib_result_add() {
258	local result="${1}"
259	local time="time="
260	local ts_prev_ns
261	shift
262
263	local id=$((${#MPTCP_LIB_SUBTESTS[@]} + 1))
264
265	__mptcp_lib_result_check_duplicated "${*}"
266
267	# not to add two '#'
268	[[ "${*}" != *"#"* ]] && time="# ${time}"
269
270	ts_prev_ns="${MPTCP_LIB_SUBTESTS_LAST_TS_NS}"
271	mptcp_lib_subtests_last_ts_reset
272	time+="$(((MPTCP_LIB_SUBTESTS_LAST_TS_NS - ts_prev_ns) / 1000000))ms"
273
274	MPTCP_LIB_SUBTESTS+=("${result} ${id} - ${KSFT_TEST}: ${*} ${time}")
275}
276
277# $1: test name
278mptcp_lib_result_pass() {
279	__mptcp_lib_result_add "ok" "${1}"
280}
281
282# $1: test name
283mptcp_lib_result_fail() {
284	if mptcp_lib_subtest_is_flaky; then
285		# It might sound better to use 'not ok # TODO' or 'ok # SKIP',
286		# but some CIs don't understand 'TODO' and treat SKIP as errors.
287		__mptcp_lib_result_add "ok" "${1} # IGNORE Flaky"
288	else
289		__mptcp_lib_result_add "not ok" "${1}"
290	fi
291}
292
293# $1: test name
294mptcp_lib_result_skip() {
295	__mptcp_lib_result_add "ok" "${1} # SKIP"
296}
297
298# $1: result code ; $2: test name
299mptcp_lib_result_code() {
300	local ret="${1}"
301	local name="${2}"
302
303	case "${ret}" in
304		"${KSFT_PASS}")
305			mptcp_lib_result_pass "${name}"
306			;;
307		"${KSFT_FAIL}")
308			mptcp_lib_result_fail "${name}"
309			;;
310		"${KSFT_SKIP}")
311			mptcp_lib_result_skip "${name}"
312			;;
313		*)
314			echo "ERROR: wrong result code: ${ret}"
315			exit ${KSFT_FAIL}
316			;;
317	esac
318}
319
320mptcp_lib_result_print_all_tap() {
321	local subtest
322
323	if [ ${#MPTCP_LIB_SUBTESTS[@]} -eq 0 ] ||
324	   [ "${SELFTESTS_MPTCP_LIB_NO_TAP:-}" = "1" ]; then
325		return
326	fi
327
328	printf "\nTAP version 13\n"
329	printf "1..%d\n" "${#MPTCP_LIB_SUBTESTS[@]}"
330
331	for subtest in "${MPTCP_LIB_SUBTESTS[@]}"; do
332		printf "%s\n" "${subtest}"
333	done
334
335	if [ "${MPTCP_LIB_SUBTESTS_DUPLICATED}" = 1 ] &&
336	   mptcp_lib_expect_all_features; then
337		mptcp_lib_print_err "Duplicated test entries"
338		exit ${KSFT_FAIL}
339	fi
340}
341
342# get the value of keyword $1 in the line marked by keyword $2
343mptcp_lib_get_info_value() {
344	grep "${2}" 2>/dev/null |
345		sed -n 's/.*\('"${1}"':\)\([0-9a-f:.]*\).*$/\2/p;q'
346		# the ';q' at the end limits to the first matched entry.
347}
348
349# $1: info name ; $2: evts_ns ; [$3: event type; [$4: addr]]
350mptcp_lib_evts_get_info() {
351	grep "${4:-}" "${2}" 2>/dev/null |
352		mptcp_lib_get_info_value "${1}" "^type:${3:-1},"
353}
354
355mptcp_lib_wait_timeout() {
356	local timeout_test="${1}"
357	local listener_ns="${2}"
358	local connector_ns="${3}"
359	local port="${4}"
360	shift 4 # rest are PIDs
361
362	sleep "${timeout_test}"
363	mptcp_lib_print_err "timeout"
364	mptcp_lib_pr_err_stats "${listener_ns}" "${connector_ns}" "${port}"
365	kill "${@}" 2>/dev/null
366}
367
368# $1: PID
369mptcp_lib_kill_wait() {
370	[ "${1}" -eq 0 ] && return 0
371
372	kill -SIGUSR1 "${1}" > /dev/null 2>&1
373	kill "${1}" > /dev/null 2>&1
374	wait "${1}" 2>/dev/null
375}
376
377# $1: PID
378mptcp_lib_pid_list_children() {
379	local curr="${1}"
380	# evoke 'ps' only once
381	local pids="${2:-"$(ps o pid,ppid)"}"
382
383	echo "${curr}"
384
385	local pid
386	for pid in $(echo "${pids}" | awk "\$2 == ${curr} { print \$1 }"); do
387		mptcp_lib_pid_list_children "${pid}" "${pids}"
388	done
389}
390
391# $1: PID
392mptcp_lib_kill_group_wait() {
393	# Some users might not have procps-ng: cannot use "kill -- -PID"
394	mptcp_lib_pid_list_children "${1}" | xargs -r kill &>/dev/null
395	wait "${1}" 2>/dev/null
396}
397
398# $1: IP address
399mptcp_lib_is_v6() {
400	[ -z "${1##*:*}" ]
401}
402
403mptcp_lib_nstat_init() {
404	local ns="${1}"
405
406	rm -f "/tmp/${ns}."{nstat,out}
407	NSTAT_HISTORY="/tmp/${ns}.nstat" ip netns exec "${ns}" nstat -n
408}
409
410mptcp_lib_nstat_get() {
411	local ns="${1}"
412
413	# filter out non-*TCP stats, and the rate (last column)
414	NSTAT_HISTORY="/tmp/${ns}.nstat" ip netns exec "${ns}" nstat -sz |
415		grep -o ".*Tcp\S\+\s\+[0-9]\+" > "/tmp/${ns}.out"
416}
417
418# $1: ns, $2: MIB counter
419# Get the counter from the cache (mptcp_lib_nstat_{init,get}()) if available.
420# If not, get the counter from nstat ignoring any cache, but using the history.
421mptcp_lib_get_counter() {
422	local ns="${1}"
423	local counter="${2}"
424	local cache="/tmp/${ns}.out"
425	local hist="/tmp/${ns}.nstat"
426	local count
427
428	if [[ -s "${cache}" && "${counter}" == *"Tcp"* ]]; then
429		count=$(awk "/^${counter} / {print \$2; exit}" "${cache}")
430	else
431		count=$(NSTAT_HISTORY="${hist}" ip netns exec "${ns}" \
432			nstat -sz "${counter}" |
433				awk 'NR==1 {next} {print $2}')
434	fi
435	if [ -z "${count}" ]; then
436		mptcp_lib_fail_if_expected_feature "${counter} counter"
437		return 1
438	fi
439
440	echo "${count}"
441}
442
443mptcp_lib_make_file() {
444	local name="${1}"
445	local bs="${2}"
446	local size="${3}"
447
448	dd if=/dev/urandom of="${name}" bs="${bs}" count="${size}" 2> /dev/null
449	echo -e "\nMPTCP_TEST_FILE_END_MARKER" >> "${name}"
450}
451
452# $1: file
453mptcp_lib_print_file_err() {
454	ls -l "${1}" 1>&2
455	echo "Trailing bytes are: "
456	tail -c 32 "${1}" | od -x | head -n2
457}
458
459# $1: input file ; $2: output file ; $3: what kind of file
460mptcp_lib_check_transfer() {
461	local in="${1}"
462	local out="${2}"
463	local what="${3}"
464
465	if ! cmp "$in" "$out" > /dev/null 2>&1; then
466		mptcp_lib_pr_fail "$what does not match (in, out):"
467		mptcp_lib_print_file_err "$in"
468		mptcp_lib_print_file_err "$out"
469
470		return 1
471	fi
472
473	return 0
474}
475
476# $1: ns, $2: port
477mptcp_lib_wait_local_port_listen() {
478	wait_local_port_listen "${@}" "tcp"
479}
480
481# $1: error file, $2: cmd, $3: expected msg, [$4: expected error]
482mptcp_lib_check_output() {
483	local err="${1}"
484	local cmd="${2}"
485	local expected="${3}"
486	local exp_error="${4:-0}"
487	local cmd_ret=0
488	local out
489
490	out=$(${cmd} 2>"${err}") || cmd_ret=1
491
492	if [ "${cmd_ret}" != "${exp_error}" ]; then
493		mptcp_lib_pr_fail "unexpected returned code for '${cmd}', info:"
494		if [ "${exp_error}" = 0 ]; then
495			cat "${err}"
496		else
497			echo "${out}"
498		fi
499		return 2
500	elif [ "${out}" = "${expected}" ]; then
501		return 0
502	else
503		mptcp_lib_pr_fail "expected '${expected}' got '${out}'"
504		return 1
505	fi
506}
507
508mptcp_lib_check_tools() {
509	local tool
510
511	for tool in "${@}"; do
512		case "${tool}" in
513		"ip")
514			if ! ip -Version &> /dev/null; then
515				mptcp_lib_pr_skip "Could not run test without ip tool"
516				exit ${KSFT_SKIP}
517			fi
518			;;
519		"tc")
520			if ! tc -help &> /dev/null; then
521				mptcp_lib_pr_skip "Could not run test without tc tool"
522				exit ${KSFT_SKIP}
523			fi
524			;;
525		"ss")
526			if ! ss -h | grep -q MPTCP; then
527				mptcp_lib_pr_skip "ss tool does not support MPTCP"
528				exit ${KSFT_SKIP}
529			fi
530			;;
531		"iptables"* | "ip6tables"*)
532			if ! "${tool}" -V &> /dev/null; then
533				mptcp_lib_pr_skip "Could not run all tests without ${tool}"
534				exit ${KSFT_SKIP}
535			fi
536			;;
537		*)
538			mptcp_lib_pr_fail "Internal error: unsupported tool: ${tool}"
539			exit ${KSFT_FAIL}
540			;;
541		esac
542	done
543}
544
545mptcp_lib_ns_init() {
546	if ! setup_ns "${@}"; then
547		mptcp_lib_pr_fail "Failed to setup namespaces ${*}"
548		exit ${KSFT_FAIL}
549	fi
550
551	local netns
552	for netns in "${@}"; do
553		ip netns exec "${!netns}" sysctl -q net.mptcp.enabled=1
554	done
555}
556
557mptcp_lib_ns_exit() {
558	cleanup_ns "${@}"
559
560	local netns
561	for netns in "${@}"; do
562		rm -f /tmp/"${netns}".{nstat,out}
563	done
564}
565
566mptcp_lib_events() {
567	local ns="${1}"
568	local evts="${2}"
569	declare -n pid="${3}"
570
571	:>"${evts}"
572
573	mptcp_lib_kill_wait "${pid:-0}"
574	ip netns exec "${ns}" ./pm_nl_ctl events >> "${evts}" 2>&1 &
575	pid=$!
576}
577
578mptcp_lib_print_title() {
579	: "${MPTCP_LIB_TEST_COUNTER:?}"
580	: "${MPTCP_LIB_TEST_FORMAT:?}"
581
582	# shellcheck disable=SC2059 # the format is in a variable
583	printf "${MPTCP_LIB_TEST_FORMAT}" "$((++MPTCP_LIB_TEST_COUNTER))" "${*}"
584}
585
586# $1: var name ; $2: prev ret
587mptcp_lib_check_expected_one() {
588	local var="${1}"
589	local exp="e_${var}"
590	local prev_ret="${2}"
591
592	if [ "${!var}" = "${!exp}" ]; then
593		return 0
594	fi
595
596	if [ "${prev_ret}" = "0" ]; then
597		mptcp_lib_pr_fail
598	fi
599
600	mptcp_lib_print_err "Expected value for '${var}': '${!exp}', got '${!var}'."
601	return 1
602}
603
604# $@: all var names to check
605mptcp_lib_check_expected() {
606	local rc=0
607	local var
608
609	for var in "${@}"; do
610		mptcp_lib_check_expected_one "${var}" "${rc}" || rc=1
611	done
612
613	return "${rc}"
614}
615
616# shellcheck disable=SC2034 # Some variables are used below but indirectly
617mptcp_lib_verify_listener_events() {
618	local evt=${1}
619	local e_type=${2}
620	local e_family=${3}
621	local e_saddr=${4}
622	local e_sport=${5}
623	local type
624	local family
625	local saddr
626	local sport
627	local rc=0
628
629	type=$(mptcp_lib_evts_get_info type "${evt}" "${e_type}")
630	family=$(mptcp_lib_evts_get_info family "${evt}" "${e_type}")
631	if [ "${family}" ] && [ "${family}" = "${AF_INET6}" ]; then
632		saddr=$(mptcp_lib_evts_get_info saddr6 "${evt}" "${e_type}")
633	else
634		saddr=$(mptcp_lib_evts_get_info saddr4 "${evt}" "${e_type}")
635	fi
636	sport=$(mptcp_lib_evts_get_info sport "${evt}" "${e_type}")
637
638	mptcp_lib_check_expected "type" "family" "saddr" "sport" || rc="${?}"
639	return "${rc}"
640}
641
642mptcp_lib_set_ip_mptcp() {
643	MPTCP_LIB_IP_MPTCP=1
644}
645
646mptcp_lib_is_ip_mptcp() {
647	[ "${MPTCP_LIB_IP_MPTCP}" = "1" ]
648}
649
650# format: <id>,<ip>,<flags>,<dev>
651mptcp_lib_pm_nl_format_endpoints() {
652	local entry id ip flags dev port
653
654	for entry in "${@}"; do
655		IFS=, read -r id ip flags dev port <<< "${entry}"
656		if mptcp_lib_is_ip_mptcp; then
657			echo -n "${ip}"
658			[ -n "${port}" ] && echo -n " port ${port}"
659			echo -n " id ${id}"
660			[ -n "${flags}" ] && echo -n " ${flags}"
661			[ -n "${dev}" ] && echo -n " dev ${dev}"
662			echo " " # always a space at the end
663		else
664			echo -n "id ${id}"
665			echo -n " flags ${flags//" "/","}"
666			[ -n "${dev}" ] && echo -n " dev ${dev}"
667			echo -n " ${ip}"
668			[ -n "${port}" ] && echo -n " ${port}"
669			echo ""
670		fi
671	done
672}
673
674mptcp_lib_pm_nl_get_endpoint() {
675	local ns=${1}
676	local id=${2}
677
678	if mptcp_lib_is_ip_mptcp; then
679		ip -n "${ns}" mptcp endpoint show id "${id}"
680	else
681		ip netns exec "${ns}" ./pm_nl_ctl get "${id}"
682	fi
683}
684
685mptcp_lib_pm_nl_set_limits() {
686	local ns=${1}
687	local addrs=${2}
688	local subflows=${3}
689
690	if mptcp_lib_is_ip_mptcp; then
691		ip -n "${ns}" mptcp limits set add_addr_accepted "${addrs}" subflows "${subflows}"
692	else
693		ip netns exec "${ns}" ./pm_nl_ctl limits "${addrs}" "${subflows}"
694	fi
695}
696
697mptcp_lib_pm_nl_add_endpoint() {
698	local ns=${1}
699	local addr=${2}
700	local flags dev id port
701	local nr=2
702
703	local p
704	for p in "${@}"; do
705		case "${p}" in
706		"flags" | "dev" | "id" | "port")
707			eval "${p}"=\$"${nr}"
708			;;
709		esac
710
711		nr=$((nr + 1))
712	done
713
714	if mptcp_lib_is_ip_mptcp; then
715		# shellcheck disable=SC2086 # blanks in flags, no double quote
716		ip -n "${ns}" mptcp endpoint add "${addr}" ${flags//","/" "} \
717			${dev:+dev "${dev}"} ${id:+id "${id}"} ${port:+port "${port}"}
718	else
719		ip netns exec "${ns}" ./pm_nl_ctl add "${addr}" ${flags:+flags "${flags}"} \
720			${dev:+dev "${dev}"} ${id:+id "${id}"} ${port:+port "${port}"}
721	fi
722}
723
724mptcp_lib_pm_nl_del_endpoint() {
725	local ns=${1}
726	local id=${2}
727	local addr=${3}
728
729	if mptcp_lib_is_ip_mptcp; then
730		[ "${id}" -ne 0 ] && addr=''
731		ip -n "${ns}" mptcp endpoint delete id "${id}" ${addr:+"${addr}"}
732	else
733		ip netns exec "${ns}" ./pm_nl_ctl del "${id}" "${addr}"
734	fi
735}
736
737mptcp_lib_pm_nl_flush_endpoint() {
738	local ns=${1}
739
740	if mptcp_lib_is_ip_mptcp; then
741		ip -n "${ns}" mptcp endpoint flush
742	else
743		ip netns exec "${ns}" ./pm_nl_ctl flush
744	fi
745}
746
747mptcp_lib_pm_nl_show_endpoints() {
748	local ns=${1}
749
750	if mptcp_lib_is_ip_mptcp; then
751		ip -n "${ns}" mptcp endpoint show
752	else
753		ip netns exec "${ns}" ./pm_nl_ctl dump
754	fi
755}
756
757mptcp_lib_pm_nl_change_endpoint() {
758	local ns=${1}
759	local id=${2}
760	local flags=${3}
761
762	if mptcp_lib_is_ip_mptcp; then
763		# shellcheck disable=SC2086 # blanks in flags, no double quote
764		ip -n "${ns}" mptcp endpoint change id "${id}" ${flags//","/" "}
765	else
766		ip netns exec "${ns}" ./pm_nl_ctl set id "${id}" flags "${flags}"
767	fi
768}
769