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