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