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