Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
mptcp_connect
mptcp_diag
mptcp_inq
mptcp_sockopt
pm_nl_ctl
*.pcap
@@ -0,0 +1,39 @@
# SPDX-License-Identifier: GPL-2.0
top_srcdir = ../../../../..
CFLAGS += -Wall -Wl,--no-as-needed -O2 -g -I$(top_srcdir)/usr/include $(KHDR_INCLUDES)
CFLAGS += -I$(top_srcdir)/tools/include
TEST_PROGS := \
diag.sh \
mptcp_connect.sh \
mptcp_connect_checksum.sh \
mptcp_connect_mmap.sh \
mptcp_connect_sendfile.sh \
mptcp_connect_splice.sh \
mptcp_join.sh \
mptcp_sockopt.sh \
pm_netlink.sh \
simult_flows.sh \
userspace_pm.sh \
# end of TEST_PROGS
TEST_GEN_FILES := \
mptcp_connect \
mptcp_diag \
mptcp_inq \
mptcp_sockopt \
pm_nl_ctl \
# end of TEST_GEN_FILES
TEST_FILES := \
mptcp_lib.sh \
settings \
# end of TEST_FILES
TEST_INCLUDES := ../lib.sh $(wildcard ../lib/sh/*.sh)
EXTRA_CLEAN := *.pcap
include ../../lib.mk
@@ -0,0 +1,36 @@
CONFIG_INET_DIAG=m
CONFIG_INET_MPTCP_DIAG=m
CONFIG_IP6_NF_FILTER=m
CONFIG_IP6_NF_TARGET_REJECT=m
CONFIG_IP_ADVANCED_ROUTER=y
CONFIG_IP_MULTIPLE_TABLES=y
CONFIG_IP_NF_FILTER=m
CONFIG_IP_NF_MANGLE=m
CONFIG_IP_NF_TARGET_REJECT=m
CONFIG_IPV6=y
CONFIG_IPV6_MULTIPLE_TABLES=y
CONFIG_KALLSYMS=y
CONFIG_MPTCP=y
CONFIG_MPTCP_IPV6=y
CONFIG_NET_ACT_CSUM=m
CONFIG_NET_ACT_PEDIT=m
CONFIG_NET_CLS_ACT=y
CONFIG_NET_CLS_FW=m
CONFIG_NETFILTER=y
CONFIG_NETFILTER_ADVANCED=y
CONFIG_NETFILTER_NETLINK=m
CONFIG_NETFILTER_XTABLES=m
CONFIG_NETFILTER_XTABLES_LEGACY=y
CONFIG_NETFILTER_XT_MATCH_BPF=m
CONFIG_NETFILTER_XT_MATCH_LENGTH=m
CONFIG_NETFILTER_XT_MATCH_STATISTIC=m
CONFIG_NETFILTER_XT_TARGET_MARK=m
CONFIG_NET_SCH_INGRESS=m
CONFIG_NET_SCH_NETEM=m
CONFIG_NF_TABLES=m
CONFIG_NF_TABLES_INET=y
CONFIG_NFT_COMPAT=m
CONFIG_NFT_SOCKET=m
CONFIG_NFT_TPROXY=m
CONFIG_SYN_COOKIES=y
CONFIG_VETH=y
+447
View File
@@ -0,0 +1,447 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Double quotes to prevent globbing and word splitting is recommended in new
# code but we accept it, especially because there were too many before having
# address all other issues detected by shellcheck.
#shellcheck disable=SC2086
. "$(dirname "${0}")/mptcp_lib.sh"
ns=""
timeout_poll=30
timeout_test=$((timeout_poll * 2 + 1))
ret=0
flush_pids()
{
# mptcp_connect in join mode will sleep a bit before completing,
# give it some time
sleep 1.1
ip netns pids "${ns}" | xargs --no-run-if-empty kill -SIGUSR1 &>/dev/null
for _ in $(seq $((timeout_poll * 10))); do
[ -z "$(ip netns pids "${ns}")" ] && break
sleep 0.1
done
}
# This function is used in the cleanup trap
#shellcheck disable=SC2317,SC2329
cleanup()
{
ip netns pids "${ns}" | xargs --no-run-if-empty kill -SIGKILL &>/dev/null
mptcp_lib_ns_exit "${ns}"
}
mptcp_lib_check_mptcp
mptcp_lib_check_tools ip ss
get_msk_inuse()
{
ip netns exec $ns cat /proc/net/protocols | awk '$1~/^MPTCP$/{print $3}'
}
__chk_nr()
{
local command="$1"
local expected=$2
local msg="$3"
local skip="${4-SKIP}"
local nr
nr=$(eval $command)
mptcp_lib_print_title "$msg"
if [ "$nr" != "$expected" ]; then
if [ "$nr" = "$skip" ] && ! mptcp_lib_expect_all_features; then
mptcp_lib_pr_skip "Feature probably not supported"
mptcp_lib_result_skip "${msg}"
else
mptcp_lib_pr_fail "expected $expected found $nr"
mptcp_lib_result_fail "${msg}"
ret=${KSFT_FAIL}
fi
else
mptcp_lib_pr_ok
mptcp_lib_result_pass "${msg}"
fi
}
__chk_msk_nr()
{
local condition=$1
shift 1
__chk_nr "ss -inmHMN $ns | $condition" "$@"
}
chk_msk_nr()
{
__chk_msk_nr "grep -c token:" "$@"
}
chk_listener_nr()
{
local expected=$1
local msg="$2"
__chk_nr "ss -nlHMON $ns | wc -l" "$expected" "$msg - mptcp" 0
__chk_nr "ss -nlHtON $ns | wc -l" "$expected" "$msg - subflows"
}
wait_msk_nr()
{
local condition="grep -c token:"
local expected=$1
local timeout=20
local msg nr
local max=0
local i=0
shift 1
msg=$*
while [ $i -lt $timeout ]; do
nr=$(ss -inmHMN $ns | $condition)
[ $nr == $expected ] && break;
[ $nr -gt $max ] && max=$nr
i=$((i + 1))
sleep 1
done
mptcp_lib_print_title "$msg"
if [ $i -ge $timeout ]; then
mptcp_lib_pr_fail "timeout while expecting $expected max $max last $nr"
mptcp_lib_result_fail "${msg} # timeout"
ret=${KSFT_FAIL}
elif [ $nr != $expected ]; then
mptcp_lib_pr_fail "expected $expected found $nr"
mptcp_lib_result_fail "${msg} # unexpected result"
ret=${KSFT_FAIL}
else
mptcp_lib_pr_ok
mptcp_lib_result_pass "${msg}"
fi
}
chk_msk_fallback_nr()
{
__chk_msk_nr "grep -c fallback" "$@"
}
chk_msk_remote_key_nr()
{
__chk_msk_nr "grep -c remote_key" "$@"
}
__chk_listen()
{
local filter="$1"
local expected=$2
local msg="$3"
__chk_nr "ss -N $ns -Ml '$filter' | grep -c LISTEN" "$expected" "$msg" 0
}
chk_msk_listen()
{
lport=$1
# destination port search should always return empty list
__chk_listen "dport $lport" 0 "listen match for dport $lport"
# should return 'our' mptcp listen socket
__chk_listen "sport $lport" 1 "listen match for sport $lport"
__chk_listen "src inet:0.0.0.0:$lport" 1 "listen match for saddr and sport"
__chk_listen "" 1 "all listen sockets"
nr=$(ss -Ml $filter | wc -l)
}
chk_msk_inuse()
{
local expected=$1
local msg="....chk ${2:-${expected}} msk in use"
local listen_nr
if [ "${expected}" -eq 0 ]; then
msg+=" after flush"
fi
listen_nr=$(ss -N "${ns}" -Ml | grep -c LISTEN)
expected=$((expected + listen_nr))
for _ in $(seq 10); do
if [ "$(get_msk_inuse)" -eq $expected ]; then
break
fi
sleep 0.1
done
__chk_nr get_msk_inuse $expected "${msg}" 0
}
# $1: cestab nr
chk_msk_cestab()
{
local expected=$1
local msg="....chk ${2:-${expected}} cestab"
if [ "${expected}" -eq 0 ]; then
msg+=" after flush"
fi
__chk_nr "mptcp_lib_get_counter ${ns} MPTcpExtMPCurrEstab" \
"${expected}" "${msg}" ""
}
chk_dump_one()
{
local ss_token
local token
local msg
ss_token="$(ss -inmHMN $ns |
mptcp_lib_get_info_value "token" "token")"
token="$(ip netns exec $ns ./mptcp_diag -t $ss_token |\
awk -F':[ \t]+' '/^token/ {print $2}')"
msg="....chk dump_one"
mptcp_lib_print_title "$msg"
if [ -n "$ss_token" ] && [ "$ss_token" = "$token" ]; then
mptcp_lib_pr_ok
mptcp_lib_result_pass "${msg}"
else
mptcp_lib_pr_fail "expected $ss_token found $token"
mptcp_lib_result_fail "${msg}"
ret=${KSFT_FAIL}
fi
}
chk_dump_subflow()
{
local inet_diag_token
local subflow_line
local ss_output
local ss_token
local msg
ss_output=$(ss -tniN $ns)
subflow_line=$(echo "$ss_output" | \
grep -m1 -Eo '[0-9.]+:[0-9].+ +[0-9.]+:[0-9.]+')
ss_token=$(echo "$ss_output" | grep -m1 -Eo 'token:[^ ]+')
inet_diag_token=$(ip netns exec $ns ./mptcp_diag -s "$subflow_line" | \
grep -Eo 'token:[^ ]+')
msg="....chk dump_subflow"
mptcp_lib_print_title "$msg"
if [ -n "$ss_token" ] && [ "$ss_token" = "$inet_diag_token" ]; then
mptcp_lib_pr_ok
mptcp_lib_result_pass "${msg}"
else
mptcp_lib_pr_fail "expected $ss_token found $inet_diag_token"
mptcp_lib_result_fail "${msg}"
ret=${KSFT_FAIL}
fi
}
msk_info_get_value()
{
local port="${1}"
local info="${2}"
ss -N "${ns}" -inHM dport "${port}" | \
mptcp_lib_get_info_value "${info}" "${info}"
}
chk_msk_info()
{
local port="${1}"
local info="${2}"
local cnt="${3}"
local msg="....chk ${info}"
local delta_ms=250 # half what we waited before, just to be sure
local now
now=$(msk_info_get_value "${port}" "${info}")
mptcp_lib_print_title "${msg}"
if { [ -z "${cnt}" ] || [ -z "${now}" ]; } &&
! mptcp_lib_expect_all_features; then
mptcp_lib_pr_skip "Feature probably not supported"
mptcp_lib_result_skip "${msg}"
elif [ "$((cnt + delta_ms))" -lt "${now}" ]; then
mptcp_lib_pr_ok
mptcp_lib_result_pass "${msg}"
else
mptcp_lib_pr_fail "value of ${info} changed by $((now - cnt))ms," \
"expected at least ${delta_ms}ms"
mptcp_lib_result_fail "${msg}"
ret=${KSFT_FAIL}
fi
}
chk_last_time_info()
{
local port="${1}"
local data_sent data_recv ack_recv
data_sent=$(msk_info_get_value "${port}" "last_data_sent")
data_recv=$(msk_info_get_value "${port}" "last_data_recv")
ack_recv=$(msk_info_get_value "${port}" "last_ack_recv")
sleep 0.5 # wait to check after if the timestamps difference
chk_msk_info "${port}" "last_data_sent" "${data_sent}"
chk_msk_info "${port}" "last_data_recv" "${data_recv}"
chk_msk_info "${port}" "last_ack_recv" "${ack_recv}"
}
wait_connected()
{
local listener_ns="${1}"
local port="${2}"
local port_hex i
port_hex="$(printf "%04X" "${port}")"
for i in $(seq 10); do
ip netns exec ${listener_ns} grep -q " 0100007F:${port_hex} " /proc/net/tcp && break
sleep 0.1
done
}
chk_sndbuf()
{
local server_sndbuf client_sndbuf msg
local port=${1}
msg="....chk sndbuf server/client"
server_sndbuf=$(ss -N "${ns}" -inmHM "sport" "${port}" | \
sed -n 's/.*tb\([0-9]\+\).*/\1/p')
client_sndbuf=$(ss -N "${ns}" -inmHM "dport" "${port}" | \
sed -n 's/.*tb\([0-9]\+\).*/\1/p')
mptcp_lib_print_title "${msg}"
if [ -z "${server_sndbuf}" ] || [ -z "${client_sndbuf}" ]; then
mptcp_lib_pr_fail "sndbuf S=${server_sndbuf} C=${client_sndbuf}"
mptcp_lib_result_fail "${msg}"
ret=${KSFT_FAIL}
elif [ "${server_sndbuf}" != "${client_sndbuf}" ]; then
mptcp_lib_pr_fail "sndbuf S=${server_sndbuf} != C=${client_sndbuf}"
mptcp_lib_result_fail "${msg}"
ret=${KSFT_FAIL}
else
mptcp_lib_pr_ok
mptcp_lib_result_pass "${msg}"
fi
}
trap cleanup EXIT
mptcp_lib_ns_init ns
echo "a" | \
timeout ${timeout_test} \
ip netns exec $ns \
./mptcp_connect -p 10000 -l -t ${timeout_poll} -w 20 \
0.0.0.0 >/dev/null &
mptcp_lib_wait_local_port_listen $ns 10000
chk_msk_nr 0 "no msk on netns creation"
chk_msk_listen 10000
echo "b" | \
timeout ${timeout_test} \
ip netns exec $ns \
./mptcp_connect -p 10000 -r 0 -t ${timeout_poll} -w 20 \
127.0.0.1 >/dev/null &
wait_connected $ns 10000
chk_msk_nr 2 "after MPC handshake"
chk_sndbuf 10000
chk_last_time_info 10000
chk_msk_remote_key_nr 2 "....chk remote_key"
chk_msk_fallback_nr 0 "....chk no fallback"
chk_msk_inuse 2
chk_msk_cestab 2
chk_dump_one
chk_dump_subflow
flush_pids
chk_msk_inuse 0 "2->0"
chk_msk_cestab 0 "2->0"
echo "a" | \
timeout ${timeout_test} \
ip netns exec $ns \
./mptcp_connect -p 10001 -l -s TCP -t ${timeout_poll} -w 20 \
0.0.0.0 >/dev/null &
mptcp_lib_wait_local_port_listen $ns 10001
echo "b" | \
timeout ${timeout_test} \
ip netns exec $ns \
./mptcp_connect -p 10001 -r 0 -t ${timeout_poll} -w 20 \
127.0.0.1 >/dev/null &
wait_connected $ns 10001
chk_msk_fallback_nr 1 "check fallback"
chk_msk_inuse 1
chk_msk_cestab 1
flush_pids
chk_msk_inuse 0 "1->0"
chk_msk_cestab 0 "1->0"
NR_CLIENTS=100
for I in $(seq 1 $NR_CLIENTS); do
echo "a" | \
timeout ${timeout_test} \
ip netns exec $ns \
./mptcp_connect -p $((I+10001)) -l -w 20 \
-t ${timeout_poll} 0.0.0.0 >/dev/null &
done
mptcp_lib_wait_local_port_listen $ns $((NR_CLIENTS + 10001))
for I in $(seq 1 $NR_CLIENTS); do
echo "b" | \
timeout ${timeout_test} \
ip netns exec $ns \
./mptcp_connect -p $((I+10001)) -w 20 \
-t ${timeout_poll} 127.0.0.1 >/dev/null &
done
wait_msk_nr $((NR_CLIENTS*2)) "many msk socket present"
chk_msk_inuse $((NR_CLIENTS*2)) "many"
chk_msk_cestab $((NR_CLIENTS*2)) "many"
flush_pids
chk_msk_inuse 0 "many->0"
chk_msk_cestab 0 "many->0"
chk_listener_nr 0 "no listener sockets"
NR_SERVERS=100
for I in $(seq 1 $NR_SERVERS); do
ip netns exec $ns ./mptcp_connect -p $((I + 20001)) \
-t ${timeout_poll} -l 0.0.0.0 >/dev/null 2>&1 &
done
mptcp_lib_wait_local_port_listen $ns $((NR_SERVERS + 20001))
chk_listener_nr $NR_SERVERS "many listener sockets"
# graceful termination
for I in $(seq 1 $NR_SERVERS); do
echo a | ip netns exec $ns ./mptcp_connect -p $((I + 20001)) 127.0.0.1 >/dev/null 2>&1 &
done
flush_pids
mptcp_lib_result_print_all_tap
exit $ret
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,964 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Double quotes to prevent globbing and word splitting is recommended in new
# code but we accept it, especially because there were too many before having
# address all other issues detected by shellcheck.
#shellcheck disable=SC2086
. "$(dirname "${0}")/mptcp_lib.sh"
time_start=$(date +%s)
optstring="S:R:d:e:l:r:h4cm:f:tC"
ret=0
final_ret=0
sin=""
sout=""
cin_disconnect=""
cin=""
cout=""
capture=false
timeout_poll=30
timeout_test=$((timeout_poll * 2 + 1))
ipv6=true
ethtool_random_on=true
tc_delay="$((RANDOM%50))"
tc_loss=$((RANDOM%101))
testmode=""
sndbuf=0
rcvbuf=0
options_log=true
do_tcp=0
checksum=false
filesize=0
connect_per_transfer=1
port=$((10000 - 1))
if [ $tc_loss -eq 100 ];then
tc_loss=1%
elif [ $tc_loss -ge 10 ]; then
tc_loss=0.$tc_loss%
elif [ $tc_loss -ge 1 ]; then
tc_loss=0.0$tc_loss%
else
tc_loss=""
fi
usage() {
echo "Usage: $0 [ -a ]"
echo -e "\t-d: tc/netem delay in milliseconds, e.g. \"-d 10\" (default random)"
echo -e "\t-l: tc/netem loss percentage, e.g. \"-l 0.02\" (default random)"
echo -e "\t-r: tc/netem reorder mode, e.g. \"-r 25% 50% gap 5\", use "-r 0" to disable reordering (default random)"
echo -e "\t-e: ethtool features to disable, e.g.: \"-e tso -e gso\" (default: randomly disable any of tso/gso/gro)"
echo -e "\t-4: IPv4 only: disable IPv6 tests (default: test both IPv4 and IPv6)"
echo -e "\t-c: capture packets for each test using tcpdump (default: no capture)"
echo -e "\t-f: size of file to transfer in bytes (default random)"
echo -e "\t-S: set sndbuf value (default: use kernel default)"
echo -e "\t-R: set rcvbuf value (default: use kernel default)"
echo -e "\t-m: test mode (poll, sendfile; default: poll)"
echo -e "\t-t: also run tests with TCP (use twice to non-fallback tcp)"
echo -e "\t-C: enable the MPTCP data checksum"
}
while getopts "$optstring" option;do
case "$option" in
"h")
usage $0
exit ${KSFT_PASS}
;;
"d")
if [ $OPTARG -ge 0 ];then
tc_delay="$OPTARG"
else
echo "-d requires numeric argument, got \"$OPTARG\"" 1>&2
exit ${KSFT_FAIL}
fi
;;
"e")
ethtool_args="$ethtool_args $OPTARG off"
ethtool_random_on=false
;;
"l")
tc_loss="$OPTARG"
;;
"r")
tc_reorder="$OPTARG"
;;
"4")
ipv6=false
;;
"c")
capture=true
;;
"S")
if [ $OPTARG -ge 0 ];then
sndbuf="$OPTARG"
else
echo "-S requires numeric argument, got \"$OPTARG\"" 1>&2
exit ${KSFT_FAIL}
fi
;;
"R")
if [ $OPTARG -ge 0 ];then
rcvbuf="$OPTARG"
else
echo "-R requires numeric argument, got \"$OPTARG\"" 1>&2
exit ${KSFT_FAIL}
fi
;;
"m")
testmode="$OPTARG"
;;
"f")
filesize="$OPTARG"
;;
"t")
do_tcp=$((do_tcp+1))
;;
"C")
checksum=true
;;
"?")
usage $0
exit ${KSFT_FAIL}
;;
esac
done
ns1=""
ns2=""
ns3=""
ns4=""
TEST_GROUP=""
# This function is used in the cleanup trap
#shellcheck disable=SC2317,SC2329
cleanup()
{
rm -f "$cin_disconnect"
rm -f "$cin" "$cout"
rm -f "$sin" "$sout"
rm -f "$capout"
mptcp_lib_ns_exit "${ns1}" "${ns2}" "${ns3}" "${ns4}"
}
mptcp_lib_check_mptcp
mptcp_lib_check_kallsyms
mptcp_lib_check_tools ip tc
sin=$(mktemp)
sout=$(mktemp)
cin=$(mktemp)
cout=$(mktemp)
capout=$(mktemp)
cin_disconnect="$cin".disconnect
trap cleanup EXIT
mptcp_lib_ns_init ns1 ns2 ns3 ns4
# "$ns1" ns2 ns3 ns4
# ns1eth2 ns2eth1 ns2eth3 ns3eth2 ns3eth4 ns4eth3
# - drop 1% -> reorder 25%
# <- TSO off -
ip link add ns1eth2 netns "$ns1" type veth peer name ns2eth1 netns "$ns2"
ip link add ns2eth3 netns "$ns2" type veth peer name ns3eth2 netns "$ns3"
ip link add ns3eth4 netns "$ns3" type veth peer name ns4eth3 netns "$ns4"
ip -net "$ns1" addr add 10.0.1.1/24 dev ns1eth2
ip -net "$ns1" addr add dead:beef:1::1/64 dev ns1eth2 nodad
ip -net "$ns1" link set ns1eth2 up
ip -net "$ns1" route add default via 10.0.1.2
ip -net "$ns1" route add default via dead:beef:1::2
ip -net "$ns2" addr add 10.0.1.2/24 dev ns2eth1
ip -net "$ns2" addr add dead:beef:1::2/64 dev ns2eth1 nodad
ip -net "$ns2" link set ns2eth1 up
ip -net "$ns2" addr add 10.0.2.1/24 dev ns2eth3
ip -net "$ns2" addr add dead:beef:2::1/64 dev ns2eth3 nodad
ip -net "$ns2" link set ns2eth3 up
ip -net "$ns2" route add default via 10.0.2.2
ip -net "$ns2" route add default via dead:beef:2::2
ip netns exec "$ns2" sysctl -q net.ipv4.ip_forward=1
ip netns exec "$ns2" sysctl -q net.ipv6.conf.all.forwarding=1
ip -net "$ns3" addr add 10.0.2.2/24 dev ns3eth2
ip -net "$ns3" addr add dead:beef:2::2/64 dev ns3eth2 nodad
ip -net "$ns3" link set ns3eth2 up
ip -net "$ns3" addr add 10.0.3.2/24 dev ns3eth4
ip -net "$ns3" addr add dead:beef:3::2/64 dev ns3eth4 nodad
ip -net "$ns3" link set ns3eth4 up
ip -net "$ns3" route add default via 10.0.2.1
ip -net "$ns3" route add default via dead:beef:2::1
ip netns exec "$ns3" sysctl -q net.ipv4.ip_forward=1
ip netns exec "$ns3" sysctl -q net.ipv6.conf.all.forwarding=1
ip -net "$ns4" addr add 10.0.3.1/24 dev ns4eth3
ip -net "$ns4" addr add dead:beef:3::1/64 dev ns4eth3 nodad
ip -net "$ns4" link set ns4eth3 up
ip -net "$ns4" route add default via 10.0.3.2
ip -net "$ns4" route add default via dead:beef:3::2
if $checksum; then
for i in "$ns1" "$ns2" "$ns3" "$ns4";do
ip netns exec $i sysctl -q net.mptcp.checksum_enabled=1
done
fi
if $capture; then
rndh="${ns1:4}"
mptcp_lib_pr_info "Packet capture files will have this prefix: ${rndh}-"
fi
set_ethtool_flags() {
local ns="$1"
local dev="$2"
local flags="$3"
if ip netns exec $ns ethtool -K $dev $flags 2>/dev/null; then
mptcp_lib_pr_info "set $ns dev $dev: ethtool -K $flags"
fi
}
set_random_ethtool_flags() {
local flags=""
local r=$RANDOM
local pick1=$((r & 1))
local pick2=$((r & 2))
local pick3=$((r & 4))
[ $pick1 -ne 0 ] && flags="tso off"
[ $pick2 -ne 0 ] && flags="$flags gso off"
[ $pick3 -ne 0 ] && flags="$flags gro off"
[ -z "$flags" ] && return
set_ethtool_flags "$1" "$2" "$flags"
}
if $ethtool_random_on;then
set_random_ethtool_flags "$ns3" ns3eth2
set_random_ethtool_flags "$ns4" ns4eth3
else
set_ethtool_flags "$ns3" ns3eth2 "$ethtool_args"
set_ethtool_flags "$ns4" ns4eth3 "$ethtool_args"
fi
print_larger_title() {
# here we don't have the time, a bit longer for the alignment
MPTCP_LIB_TEST_FORMAT="%02u %-69s" \
mptcp_lib_print_title "${@}"
}
check_mptcp_disabled()
{
local disabled_ns
mptcp_lib_ns_init disabled_ns
print_larger_title "New MPTCP socket can be blocked via sysctl"
# mainly to cover more code
if ! ip netns exec ${disabled_ns} sysctl net.mptcp >/dev/null; then
mptcp_lib_pr_fail "not able to list net.mptcp sysctl knobs"
mptcp_lib_result_fail "not able to list net.mptcp sysctl knobs"
ret=${KSFT_FAIL}
return 1
fi
# net.mptcp.enabled should be enabled by default
if [ "$(ip netns exec ${disabled_ns} sysctl net.mptcp.enabled | awk '{ print $3 }')" -ne 1 ]; then
mptcp_lib_pr_fail "net.mptcp.enabled sysctl is not 1 by default"
mptcp_lib_result_fail "net.mptcp.enabled sysctl is not 1 by default"
ret=${KSFT_FAIL}
return 1
fi
ip netns exec ${disabled_ns} sysctl -q net.mptcp.enabled=0
local err=0
LC_ALL=C ip netns exec ${disabled_ns} ./mptcp_connect -p 10000 -s MPTCP 127.0.0.1 < "$cin" 2>&1 | \
grep -q "^socket: Protocol not available$" && err=1
mptcp_lib_ns_exit "${disabled_ns}"
if [ ${err} -eq 0 ]; then
mptcp_lib_pr_fail "New MPTCP socket cannot be blocked via sysctl"
mptcp_lib_result_fail "New MPTCP socket cannot be blocked via sysctl"
ret=${KSFT_FAIL}
return 1
fi
mptcp_lib_pr_ok
mptcp_lib_result_pass "New MPTCP socket can be blocked via sysctl"
return 0
}
do_ping()
{
local listener_ns="$1"
local connector_ns="$2"
local connect_addr="$3"
local ping_args="-q -c 1"
local rc=0
if mptcp_lib_is_v6 "${connect_addr}"; then
$ipv6 || return 0
ping_args="${ping_args} -6"
fi
ip netns exec ${connector_ns} ping ${ping_args} $connect_addr >/dev/null || rc=1
if [ $rc -ne 0 ] ; then
mptcp_lib_pr_fail "$listener_ns -> $connect_addr connectivity"
ret=${KSFT_FAIL}
return 1
fi
return 0
}
do_transfer()
{
local listener_ns="$1"
local connector_ns="$2"
local cl_proto="$3"
local srv_proto="$4"
local connect_addr="$5"
local local_addr="$6"
local extra_args="$7"
port=$((port + 1))
if [ "$rcvbuf" -gt 0 ]; then
extra_args+=" -R $rcvbuf"
fi
if [ "$sndbuf" -gt 0 ]; then
extra_args+=" -S $sndbuf"
fi
if [ -n "$testmode" ]; then
extra_args+=" -m $testmode"
fi
if [ -n "$extra_args" ] && $options_log; then
mptcp_lib_pr_info "extra options: $extra_args"
fi
options_log=false
:> "$cout"
:> "$sout"
:> "$capout"
local addr_port
addr_port=$(printf "%s:%d" ${connect_addr} ${port})
local pretty_title
pretty_title="$(printf "%.3s %-5s -> %.3s (%-20s) %-5s" ${connector_ns} ${cl_proto} ${listener_ns} ${addr_port} ${srv_proto})"
mptcp_lib_print_title "${pretty_title}"
local tap_title="${connector_ns:0:3} ${cl_proto} -> ${listener_ns:0:3} (${addr_port}) ${srv_proto}"
if $capture; then
local capuser
if [ -z $SUDO_USER ] ; then
capuser=""
else
capuser="-Z $SUDO_USER"
fi
local capfile="${rndh}-${connector_ns:0:3}-${listener_ns:0:3}-${cl_proto}-${srv_proto}-${connect_addr}-${port}"
local capopt="-i any -s 65535 -B 32768 ${capuser}"
ip netns exec ${listener_ns} tcpdump ${capopt} \
-w "${capfile}-listener.pcap" >> "${capout}" 2>&1 &
local cappid_listener=$!
if [ ${listener_ns} != ${connector_ns} ]; then
ip netns exec ${connector_ns} tcpdump ${capopt} \
-w "${capfile}-connector.pcap" >> "${capout}" 2>&1 &
local cappid_connector=$!
fi
sleep 1
fi
mptcp_lib_nstat_init "${listener_ns}"
if [ ${listener_ns} != ${connector_ns} ]; then
mptcp_lib_nstat_init "${connector_ns}"
fi
ip netns exec ${listener_ns} \
./mptcp_connect -t ${timeout_poll} -l -p $port -s ${srv_proto} \
$extra_args $local_addr < "$sin" > "$sout" &
local spid=$!
mptcp_lib_wait_local_port_listen "${listener_ns}" "${port}"
local start
start=$(date +%s%N)
ip netns exec ${connector_ns} \
./mptcp_connect -t ${timeout_poll} -p $port -s ${cl_proto} \
$extra_args $connect_addr < "$cin" > "$cout" &
local cpid=$!
mptcp_lib_wait_timeout "${timeout_test}" "${listener_ns}" \
"${connector_ns}" "${port}" "${cpid}" "${spid}" &
local timeout_pid=$!
wait $cpid
local retc=$?
wait $spid
local rets=$?
if kill -0 $timeout_pid; then
# Finished before the timeout: kill the background job
mptcp_lib_kill_group_wait $timeout_pid
timeout_pid=0
fi
local stop
stop=$(date +%s%N)
if $capture; then
sleep 1
kill ${cappid_listener}
if [ ${listener_ns} != ${connector_ns} ]; then
kill ${cappid_connector}
fi
fi
mptcp_lib_nstat_get "${listener_ns}"
if [ ${listener_ns} != ${connector_ns} ]; then
mptcp_lib_nstat_get "${connector_ns}"
fi
local duration
duration=$(((stop-start) / 1000000))
printf "(duration %05sms) " "${duration}"
if [ ${rets} -ne 0 ] || [ ${retc} -ne 0 ] || [ ${timeout_pid} -ne 0 ]; then
mptcp_lib_pr_fail "client exit code $retc, server $rets"
mptcp_lib_pr_err_stats "${listener_ns}" "${connector_ns}" "${port}"
echo
cat "$capout"
mptcp_lib_result_fail "${TEST_GROUP}: ${tap_title}"
return 1
fi
mptcp_lib_check_transfer $sin $cout "file received by client"
retc=$?
mptcp_lib_check_transfer $cin $sout "file received by server"
rets=$?
local extra=""
local stat_synrx
local stat_ackrx
local stat_cookietx
local stat_cookierx
local stat_ooo
local stat_tcpfb
stat_synrx=$(mptcp_lib_get_counter "${listener_ns}" "MPTcpExtMPCapableSYNRX")
stat_ackrx=$(mptcp_lib_get_counter "${listener_ns}" "MPTcpExtMPCapableACKRX")
stat_cookietx=$(mptcp_lib_get_counter "${listener_ns}" "TcpExtSyncookiesSent")
stat_cookierx=$(mptcp_lib_get_counter "${listener_ns}" "TcpExtSyncookiesRecv")
stat_ooo=$(mptcp_lib_get_counter "${listener_ns}" "TcpExtTCPOFOQueue")
stat_tcpfb=$(mptcp_lib_get_counter "${listener_ns}" "MPTcpExtMPCapableFallbackACK")
expect_synrx=0
expect_ackrx=0
cookies=$(ip netns exec ${listener_ns} sysctl net.ipv4.tcp_syncookies)
cookies=${cookies##*=}
if [ ${cl_proto} = "MPTCP" ] && [ ${srv_proto} = "MPTCP" ]; then
expect_synrx=${connect_per_transfer}
expect_ackrx=${connect_per_transfer}
fi
if [ ${stat_synrx} -lt ${expect_synrx} ]; then
mptcp_lib_pr_fail "lower MPC SYN rx (${stat_synrx})" \
"than expected (${expect_synrx})"
retc=1
fi
if [ ${stat_ackrx} -lt ${expect_ackrx} ]; then
if [ ${stat_ooo} -eq 0 ]; then
mptcp_lib_pr_fail "lower MPC ACK rx (${stat_ackrx})" \
"than expected (${expect_ackrx})"
rets=1
else
extra+=" [ Note ] fallback due to TCP OoO"
fi
fi
if $checksum; then
local csum_err_s
local csum_err_c
csum_err_s=$(mptcp_lib_get_counter "${listener_ns}" "MPTcpExtDataCsumErr")
csum_err_c=$(mptcp_lib_get_counter "${connector_ns}" "MPTcpExtDataCsumErr")
if [ $csum_err_s -gt 0 ]; then
mptcp_lib_pr_fail "server got ${csum_err_s} data checksum error[s]"
rets=1
fi
if [ $csum_err_c -gt 0 ]; then
mptcp_lib_pr_fail "client got ${csum_err_c} data checksum error[s]"
retc=1
fi
fi
if [ ${stat_ooo} -eq 0 ] && [ ${stat_tcpfb} -gt 0 ]; then
mptcp_lib_pr_fail "unexpected fallback to TCP"
rets=1
fi
if [ $cookies -eq 2 ];then
if [ $stat_cookietx -eq 0 ] ;then
extra+=" WARN: CookieSent: did not advance"
fi
if [ $stat_cookierx -eq 0 ] ;then
extra+=" WARN: CookieRecv: did not advance"
fi
else
if [ $stat_cookietx -gt 0 ] ;then
extra+=" WARN: CookieSent: changed"
fi
if [ $stat_cookierx -gt 0 ] ;then
extra+=" WARN: CookieRecv: changed"
fi
fi
if [ ${stat_synrx} -gt ${expect_synrx} ]; then
extra+=" WARN: SYNRX: expect ${expect_synrx},"
extra+=" got ${stat_synrx} (probably retransmissions)"
fi
if [ ${stat_ackrx} -gt ${expect_ackrx} ]; then
extra+=" WARN: ACKRX: expect ${expect_ackrx},"
extra+=" got ${stat_ackrx} (probably retransmissions)"
fi
if [ $retc -eq 0 ] && [ $rets -eq 0 ]; then
mptcp_lib_pr_ok "${extra:1}"
mptcp_lib_result_pass "${TEST_GROUP}: ${tap_title}"
else
if [ -n "${extra}" ]; then
mptcp_lib_print_warn "${extra:1}"
fi
mptcp_lib_result_fail "${TEST_GROUP}: ${tap_title}"
fi
cat "$capout"
[ $retc -eq 0 ] && [ $rets -eq 0 ]
}
make_file()
{
local name=$1
local who=$2
local SIZE=$filesize
local ksize
local rem
if [ $SIZE -eq 0 ]; then
local MAXSIZE=$((1024 * 1024 * 8))
local MINSIZE=$((1024 * 256))
SIZE=$(((RANDOM * RANDOM + MINSIZE) % MAXSIZE))
fi
ksize=$((SIZE / 1024))
rem=$((SIZE - (ksize * 1024)))
mptcp_lib_make_file $name 1024 $ksize
dd if=/dev/urandom conv=notrunc of="$name" oflag=append bs=1 count=$rem 2> /dev/null
echo "Created $name (size $(stat -c "%s" "$name") B) containing data sent by $who"
}
run_tests_lo()
{
local listener_ns="$1"
local connector_ns="$2"
local connect_addr="$3"
local loopback="$4"
local extra_args="$5"
local lret=0
# skip if test programs are running inside same netns for subsequent runs.
if [ $loopback -eq 0 ] && [ ${listener_ns} = ${connector_ns} ]; then
return 0
fi
# skip if we don't want v6
if ! $ipv6 && mptcp_lib_is_v6 "${connect_addr}"; then
return 0
fi
local local_addr
if mptcp_lib_is_v6 "${connect_addr}"; then
local_addr="::"
else
local_addr="0.0.0.0"
fi
do_transfer ${listener_ns} ${connector_ns} MPTCP MPTCP \
${connect_addr} ${local_addr} "${extra_args}"
lret=$?
if [ $lret -ne 0 ]; then
ret=$lret
return 1
fi
if [ $do_tcp -eq 0 ]; then
# don't bother testing fallback tcp except for loopback case.
if [ ${listener_ns} != ${connector_ns} ]; then
return 0
fi
fi
do_transfer ${listener_ns} ${connector_ns} MPTCP TCP \
${connect_addr} ${local_addr} "${extra_args}"
lret=$?
if [ $lret -ne 0 ]; then
ret=$lret
return 1
fi
do_transfer ${listener_ns} ${connector_ns} TCP MPTCP \
${connect_addr} ${local_addr} "${extra_args}"
lret=$?
if [ $lret -ne 0 ]; then
ret=$lret
return 1
fi
if [ $do_tcp -gt 1 ] ;then
do_transfer ${listener_ns} ${connector_ns} TCP TCP \
${connect_addr} ${local_addr} "${extra_args}"
lret=$?
if [ $lret -ne 0 ]; then
ret=$lret
return 1
fi
fi
return 0
}
run_tests()
{
run_tests_lo $1 $2 $3 0
}
run_test_transparent()
{
local connect_addr="$1"
local msg="$2"
local connector_ns="$ns1"
local listener_ns="$ns2"
local lret=0
local r6flag=""
TEST_GROUP="${msg}"
# skip if we don't want v6
if ! $ipv6 && mptcp_lib_is_v6 "${connect_addr}"; then
return 0
fi
# IP(V6)_TRANSPARENT has been added after TOS support which came with
# the required infrastructure in MPTCP sockopt code. To support TOS, the
# following function has been exported (T). Not great but better than
# checking for a specific kernel version.
if ! mptcp_lib_kallsyms_has "T __ip_sock_set_tos$"; then
mptcp_lib_pr_skip "${msg} not supported by the kernel"
mptcp_lib_result_skip "${TEST_GROUP}"
return
fi
if ! ip netns exec "$listener_ns" nft -f /dev/stdin <<"EOF"
flush ruleset
table inet mangle {
chain divert {
type filter hook prerouting priority -150;
meta l4proto tcp socket transparent 1 meta mark set 1 accept
tcp dport 20000 tproxy to :20000 meta mark set 1 accept
}
}
EOF
then
mptcp_lib_pr_skip "$msg, could not load nft ruleset"
mptcp_lib_fail_if_expected_feature "nft rules"
mptcp_lib_result_skip "${TEST_GROUP}"
return
fi
local local_addr
if mptcp_lib_is_v6 "${connect_addr}"; then
local_addr="::"
r6flag="-6"
else
local_addr="0.0.0.0"
fi
if ! ip -net "$listener_ns" $r6flag rule add fwmark 1 lookup 100; then
ip netns exec "$listener_ns" nft flush ruleset
mptcp_lib_pr_skip "$msg, ip $r6flag rule failed"
mptcp_lib_fail_if_expected_feature "ip rule"
mptcp_lib_result_skip "${TEST_GROUP}"
return
fi
if ! ip -net "$listener_ns" route add local $local_addr/0 dev lo table 100; then
ip netns exec "$listener_ns" nft flush ruleset
ip -net "$listener_ns" $r6flag rule del fwmark 1 lookup 100
mptcp_lib_pr_skip "$msg, ip route add local $local_addr failed"
mptcp_lib_fail_if_expected_feature "ip route"
mptcp_lib_result_skip "${TEST_GROUP}"
return
fi
mptcp_lib_pr_info "test $msg"
port=$((20000 - 1))
local extra_args="-o TRANSPARENT"
do_transfer ${listener_ns} ${connector_ns} MPTCP MPTCP \
${connect_addr} ${local_addr} "${extra_args}"
lret=$?
ip netns exec "$listener_ns" nft flush ruleset
ip -net "$listener_ns" $r6flag rule del fwmark 1 lookup 100
ip -net "$listener_ns" route del local $local_addr/0 dev lo table 100
if [ $lret -ne 0 ]; then
mptcp_lib_pr_fail "$msg, mptcp connection error"
ret=$lret
return 1
fi
mptcp_lib_pr_info "$msg pass"
return 0
}
run_tests_peekmode()
{
local peekmode="$1"
TEST_GROUP="peek mode: ${peekmode}"
mptcp_lib_pr_info "with peek mode: ${peekmode}"
run_tests_lo "$ns1" "$ns1" 10.0.1.1 1 "-P ${peekmode}"
run_tests_lo "$ns1" "$ns1" dead:beef:1::1 1 "-P ${peekmode}"
}
run_tests_mptfo()
{
TEST_GROUP="MPTFO"
if ! mptcp_lib_kallsyms_has "mptcp_fastopen_"; then
mptcp_lib_pr_skip "TFO not supported by the kernel"
mptcp_lib_result_skip "${TEST_GROUP}"
return
fi
mptcp_lib_pr_info "with MPTFO start"
ip netns exec "$ns1" sysctl -q net.ipv4.tcp_fastopen=2
ip netns exec "$ns2" sysctl -q net.ipv4.tcp_fastopen=1
run_tests_lo "$ns1" "$ns2" 10.0.1.1 0 "-o MPTFO"
run_tests_lo "$ns1" "$ns2" 10.0.1.1 0 "-o MPTFO"
run_tests_lo "$ns1" "$ns2" dead:beef:1::1 0 "-o MPTFO"
run_tests_lo "$ns1" "$ns2" dead:beef:1::1 0 "-o MPTFO"
ip netns exec "$ns1" sysctl -q net.ipv4.tcp_fastopen=0
ip netns exec "$ns2" sysctl -q net.ipv4.tcp_fastopen=0
mptcp_lib_pr_info "with MPTFO end"
}
run_tests_disconnect()
{
local old_cin=$cin
local old_sin=$sin
TEST_GROUP="full disconnect"
if ! mptcp_lib_kallsyms_has "mptcp_pm_data_reset$"; then
mptcp_lib_pr_skip "Full disconnect not supported"
mptcp_lib_result_skip "${TEST_GROUP}"
return
fi
cat $cin $cin $cin > "$cin".disconnect
# force do_transfer to cope with the multiple transmissions
sin="$cin.disconnect"
cin="$cin.disconnect"
cin_disconnect="$old_cin"
connect_per_transfer=3
mptcp_lib_pr_info "disconnect"
run_tests_lo "$ns1" "$ns1" 10.0.1.1 1 "-I 3 -i $old_cin"
run_tests_lo "$ns1" "$ns1" dead:beef:1::1 1 "-I 3 -i $old_cin"
# restore previous status
sin=$old_sin
cin=$old_cin
cin_disconnect="$cin".disconnect
connect_per_transfer=1
}
display_time()
{
time_end=$(date +%s)
time_run=$((time_end-time_start))
echo "Time: ${time_run} seconds"
}
log_if_error()
{
local msg="$1"
if [ ${ret} -ne 0 ]; then
mptcp_lib_pr_fail "${msg}"
final_ret=${ret}
ret=${KSFT_PASS}
return ${final_ret}
fi
}
stop_if_error()
{
if ! log_if_error "${@}"; then
display_time
mptcp_lib_result_print_all_tap
exit ${final_ret}
fi
}
make_file "$cin" "client"
make_file "$sin" "server"
mptcp_lib_subtests_last_ts_reset
check_mptcp_disabled
stop_if_error "The kernel configuration is not valid for MPTCP"
print_larger_title "Validating network environment with pings"
for sender in "$ns1" "$ns2" "$ns3" "$ns4";do
do_ping "$ns1" $sender 10.0.1.1
do_ping "$ns1" $sender dead:beef:1::1
do_ping "$ns2" $sender 10.0.1.2
do_ping "$ns2" $sender dead:beef:1::2
do_ping "$ns2" $sender 10.0.2.1
do_ping "$ns2" $sender dead:beef:2::1
do_ping "$ns3" $sender 10.0.2.2
do_ping "$ns3" $sender dead:beef:2::2
do_ping "$ns3" $sender 10.0.3.2
do_ping "$ns3" $sender dead:beef:3::2
do_ping "$ns4" $sender 10.0.3.1
do_ping "$ns4" $sender dead:beef:3::1
done
mptcp_lib_result_code "${ret}" "ping tests"
stop_if_error "Could not even run ping tests"
mptcp_lib_pr_ok
[ -n "$tc_loss" ] && tc -net "$ns2" qdisc add dev ns2eth3 root netem loss random $tc_loss delay ${tc_delay}ms
tc_info="loss of $tc_loss "
test "$tc_delay" -gt 0 && tc_info+="delay $tc_delay ms "
reorder_delay=$((tc_delay / 4))
if [ -z "${tc_reorder}" ]; then
reorder1=$((RANDOM%10))
reorder1=$((100 - reorder1))
reorder2=$((RANDOM%100))
if [ $reorder_delay -gt 0 ] && [ $reorder1 -lt 100 ] && [ $reorder2 -gt 0 ]; then
tc_reorder="reorder ${reorder1}% ${reorder2}%"
tc_info+="$tc_reorder with delay ${reorder_delay}ms "
fi
elif [ "$tc_reorder" = "0" ];then
tc_reorder=""
elif [ "$reorder_delay" -gt 0 ];then
# reordering requires some delay
tc_reorder="reorder $tc_reorder"
tc_info+="$tc_reorder with delay ${reorder_delay}ms "
fi
mptcp_lib_pr_info "Using ${tc_info}on ns3eth4"
tc -net "$ns3" qdisc add dev ns3eth4 root netem delay ${reorder_delay}ms $tc_reorder
TEST_GROUP="loopback v4"
run_tests_lo "$ns1" "$ns1" 10.0.1.1 1
stop_if_error "Could not even run loopback test"
TEST_GROUP="loopback v6"
run_tests_lo "$ns1" "$ns1" dead:beef:1::1 1
stop_if_error "Could not even run loopback v6 test"
TEST_GROUP="multihosts"
for sender in $ns1 $ns2 $ns3 $ns4;do
# ns1<->ns2 is not subject to reordering/tc delays. Use it to test
# mptcp syncookie support.
if [ $sender = $ns1 ]; then
ip netns exec "$ns2" sysctl -q net.ipv4.tcp_syncookies=2
else
ip netns exec "$ns2" sysctl -q net.ipv4.tcp_syncookies=1
fi
run_tests "$ns1" $sender 10.0.1.1
run_tests "$ns1" $sender dead:beef:1::1
run_tests "$ns2" $sender 10.0.1.2
run_tests "$ns2" $sender dead:beef:1::2
run_tests "$ns2" $sender 10.0.2.1
run_tests "$ns2" $sender dead:beef:2::1
run_tests "$ns3" $sender 10.0.2.2
run_tests "$ns3" $sender dead:beef:2::2
run_tests "$ns3" $sender 10.0.3.2
run_tests "$ns3" $sender dead:beef:3::2
run_tests "$ns4" $sender 10.0.3.1
run_tests "$ns4" $sender dead:beef:3::1
log_if_error "Tests with $sender as a sender have failed"
done
run_tests_peekmode "saveWithPeek"
run_tests_peekmode "saveAfterPeek"
log_if_error "Tests with peek mode have failed"
# MPTFO (MultiPath TCP Fatopen tests)
run_tests_mptfo
log_if_error "Tests with MPTFO have failed"
# connect to ns4 ip address, ns2 should intercept/proxy
run_test_transparent 10.0.3.1 "tproxy ipv4"
run_test_transparent dead:beef:3::1 "tproxy ipv6"
log_if_error "Tests with tproxy have failed"
run_tests_disconnect
log_if_error "Tests of the full disconnection have failed"
display_time
mptcp_lib_result_print_all_tap
exit ${final_ret}
@@ -0,0 +1,5 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
MPTCP_LIB_KSFT_TEST="$(basename "${0}" .sh)" \
"$(dirname "${0}")/mptcp_connect.sh" -C "${@}"
@@ -0,0 +1,5 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
MPTCP_LIB_KSFT_TEST="$(basename "${0}" .sh)" \
"$(dirname "${0}")/mptcp_connect.sh" -m mmap "${@}"
@@ -0,0 +1,5 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
MPTCP_LIB_KSFT_TEST="$(basename "${0}" .sh)" \
"$(dirname "${0}")/mptcp_connect.sh" -m sendfile "${@}"
@@ -0,0 +1,5 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
MPTCP_LIB_KSFT_TEST="$(basename "${0}" .sh)" \
"$(dirname "${0}")/mptcp_connect.sh" -m splice "${@}"
@@ -0,0 +1,439 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025, Kylin Software */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <linux/compiler.h>
#include <linux/inet_diag.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/sock_diag.h>
#include <linux/tcp.h>
#ifndef IPPROTO_MPTCP
#define IPPROTO_MPTCP 262
#endif
#define parse_rtattr_nested(tb, max, rta) \
(parse_rtattr_flags((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta), \
NLA_F_NESTED))
struct params {
__u32 target_token;
char subflow_addrs[1024];
};
struct mptcp_info {
__u8 mptcpi_subflows;
__u8 mptcpi_add_addr_signal;
__u8 mptcpi_add_addr_accepted;
__u8 mptcpi_subflows_max;
__u8 mptcpi_add_addr_signal_max;
__u8 mptcpi_add_addr_accepted_max;
__u32 mptcpi_flags;
__u32 mptcpi_token;
__u64 mptcpi_write_seq;
__u64 mptcpi_snd_una;
__u64 mptcpi_rcv_nxt;
__u8 mptcpi_local_addr_used;
__u8 mptcpi_local_addr_max;
__u8 mptcpi_csum_enabled;
__u32 mptcpi_retransmits;
__u64 mptcpi_bytes_retrans;
__u64 mptcpi_bytes_sent;
__u64 mptcpi_bytes_received;
__u64 mptcpi_bytes_acked;
__u8 mptcpi_subflows_total;
__u8 reserved[3];
__u32 mptcpi_last_data_sent;
__u32 mptcpi_last_data_recv;
__u32 mptcpi_last_ack_recv;
};
enum {
MPTCP_SUBFLOW_ATTR_UNSPEC,
MPTCP_SUBFLOW_ATTR_TOKEN_REM,
MPTCP_SUBFLOW_ATTR_TOKEN_LOC,
MPTCP_SUBFLOW_ATTR_RELWRITE_SEQ,
MPTCP_SUBFLOW_ATTR_MAP_SEQ,
MPTCP_SUBFLOW_ATTR_MAP_SFSEQ,
MPTCP_SUBFLOW_ATTR_SSN_OFFSET,
MPTCP_SUBFLOW_ATTR_MAP_DATALEN,
MPTCP_SUBFLOW_ATTR_FLAGS,
MPTCP_SUBFLOW_ATTR_ID_REM,
MPTCP_SUBFLOW_ATTR_ID_LOC,
MPTCP_SUBFLOW_ATTR_PAD,
__MPTCP_SUBFLOW_ATTR_MAX
};
#define MPTCP_SUBFLOW_ATTR_MAX (__MPTCP_SUBFLOW_ATTR_MAX - 1)
#define MPTCP_SUBFLOW_FLAG_MCAP_REM _BITUL(0)
#define MPTCP_SUBFLOW_FLAG_MCAP_LOC _BITUL(1)
#define MPTCP_SUBFLOW_FLAG_JOIN_REM _BITUL(2)
#define MPTCP_SUBFLOW_FLAG_JOIN_LOC _BITUL(3)
#define MPTCP_SUBFLOW_FLAG_BKUP_REM _BITUL(4)
#define MPTCP_SUBFLOW_FLAG_BKUP_LOC _BITUL(5)
#define MPTCP_SUBFLOW_FLAG_FULLY_ESTABLISHED _BITUL(6)
#define MPTCP_SUBFLOW_FLAG_CONNECTED _BITUL(7)
#define MPTCP_SUBFLOW_FLAG_MAPVALID _BITUL(8)
#define rta_getattr(type, value) (*(type *)RTA_DATA(value))
static void __noreturn die_perror(const char *msg)
{
perror(msg);
exit(1);
}
static void die_usage(int r)
{
fprintf(stderr, "Usage:\n"
"mptcp_diag -t <token>\n"
"mptcp_diag -s \"<saddr>:<sport> <daddr>:<dport>\"\n");
exit(r);
}
static void send_query(int fd, struct inet_diag_req_v2 *r, __u32 proto)
{
struct sockaddr_nl nladdr = {
.nl_family = AF_NETLINK
};
struct {
struct nlmsghdr nlh;
struct inet_diag_req_v2 r;
} req = {
.nlh = {
.nlmsg_len = sizeof(req),
.nlmsg_type = SOCK_DIAG_BY_FAMILY,
.nlmsg_flags = NLM_F_REQUEST
},
.r = *r
};
struct rtattr rta_proto;
struct iovec iov[6];
int iovlen = 0;
iov[iovlen++] = (struct iovec) {
.iov_base = &req,
.iov_len = sizeof(req)
};
if (proto == IPPROTO_MPTCP) {
rta_proto.rta_type = INET_DIAG_REQ_PROTOCOL;
rta_proto.rta_len = RTA_LENGTH(sizeof(proto));
iov[iovlen++] = (struct iovec){ &rta_proto, sizeof(rta_proto)};
iov[iovlen++] = (struct iovec){ &proto, sizeof(proto)};
req.nlh.nlmsg_len += RTA_LENGTH(sizeof(proto));
}
struct msghdr msg = {
.msg_name = &nladdr,
.msg_namelen = sizeof(nladdr),
.msg_iov = iov,
.msg_iovlen = iovlen
};
for (;;) {
if (sendmsg(fd, &msg, 0) < 0) {
if (errno == EINTR)
continue;
die_perror("sendmsg");
}
break;
}
}
static void parse_rtattr_flags(struct rtattr *tb[], int max, struct rtattr *rta,
int len, unsigned short flags)
{
unsigned short type;
memset(tb, 0, sizeof(struct rtattr *) * (max + 1));
while (RTA_OK(rta, len)) {
type = rta->rta_type & ~flags;
if (type <= max && !tb[type])
tb[type] = rta;
rta = RTA_NEXT(rta, len);
}
}
static void print_info_msg(struct mptcp_info *info)
{
printf("Token & Flags\n");
printf("token: %x\n", info->mptcpi_token);
printf("flags: %x\n", info->mptcpi_flags);
printf("csum_enabled: %u\n", info->mptcpi_csum_enabled);
printf("\nBasic Info\n");
printf("subflows: %u\n", info->mptcpi_subflows);
printf("subflows_max: %u\n", info->mptcpi_subflows_max);
printf("subflows_total: %u\n", info->mptcpi_subflows_total);
printf("local_addr_used: %u\n", info->mptcpi_local_addr_used);
printf("local_addr_max: %u\n", info->mptcpi_local_addr_max);
printf("add_addr_signal: %u\n", info->mptcpi_add_addr_signal);
printf("add_addr_accepted: %u\n", info->mptcpi_add_addr_accepted);
printf("add_addr_signal_max: %u\n", info->mptcpi_add_addr_signal_max);
printf("add_addr_accepted_max: %u\n", info->mptcpi_add_addr_accepted_max);
printf("\nTransmission Info\n");
printf("write_seq: %llu\n", info->mptcpi_write_seq);
printf("snd_una: %llu\n", info->mptcpi_snd_una);
printf("rcv_nxt: %llu\n", info->mptcpi_rcv_nxt);
printf("last_data_sent: %u\n", info->mptcpi_last_data_sent);
printf("last_data_recv: %u\n", info->mptcpi_last_data_recv);
printf("last_ack_recv: %u\n", info->mptcpi_last_ack_recv);
printf("retransmits: %u\n", info->mptcpi_retransmits);
printf("retransmit bytes: %llu\n", info->mptcpi_bytes_retrans);
printf("bytes_sent: %llu\n", info->mptcpi_bytes_sent);
printf("bytes_received: %llu\n", info->mptcpi_bytes_received);
printf("bytes_acked: %llu\n", info->mptcpi_bytes_acked);
}
/*
* 'print_subflow_info' is from 'mptcp_subflow_info'
* which is a function in 'misc/ss.c' of iproute2.
*/
static void print_subflow_info(struct rtattr *tb[])
{
u_int32_t flags = 0;
printf("It's a mptcp subflow, the subflow info:\n");
if (tb[MPTCP_SUBFLOW_ATTR_FLAGS]) {
char caps[32 + 1] = { 0 }, *cap = &caps[0];
flags = rta_getattr(__u32, tb[MPTCP_SUBFLOW_ATTR_FLAGS]);
if (flags & MPTCP_SUBFLOW_FLAG_MCAP_REM)
*cap++ = 'M';
if (flags & MPTCP_SUBFLOW_FLAG_MCAP_LOC)
*cap++ = 'm';
if (flags & MPTCP_SUBFLOW_FLAG_JOIN_REM)
*cap++ = 'J';
if (flags & MPTCP_SUBFLOW_FLAG_JOIN_LOC)
*cap++ = 'j';
if (flags & MPTCP_SUBFLOW_FLAG_BKUP_REM)
*cap++ = 'B';
if (flags & MPTCP_SUBFLOW_FLAG_BKUP_LOC)
*cap++ = 'b';
if (flags & MPTCP_SUBFLOW_FLAG_FULLY_ESTABLISHED)
*cap++ = 'e';
if (flags & MPTCP_SUBFLOW_FLAG_CONNECTED)
*cap++ = 'c';
if (flags & MPTCP_SUBFLOW_FLAG_MAPVALID)
*cap++ = 'v';
if (flags)
printf(" flags:%s", caps);
}
if (tb[MPTCP_SUBFLOW_ATTR_TOKEN_REM] &&
tb[MPTCP_SUBFLOW_ATTR_TOKEN_LOC] &&
tb[MPTCP_SUBFLOW_ATTR_ID_REM] &&
tb[MPTCP_SUBFLOW_ATTR_ID_LOC])
printf(" token:%04x(id:%u)/%04x(id:%u)",
rta_getattr(__u32, tb[MPTCP_SUBFLOW_ATTR_TOKEN_REM]),
rta_getattr(__u8, tb[MPTCP_SUBFLOW_ATTR_ID_REM]),
rta_getattr(__u32, tb[MPTCP_SUBFLOW_ATTR_TOKEN_LOC]),
rta_getattr(__u8, tb[MPTCP_SUBFLOW_ATTR_ID_LOC]));
if (tb[MPTCP_SUBFLOW_ATTR_MAP_SEQ])
printf(" seq:%llu",
rta_getattr(__u64, tb[MPTCP_SUBFLOW_ATTR_MAP_SEQ]));
if (tb[MPTCP_SUBFLOW_ATTR_MAP_SFSEQ])
printf(" sfseq:%u",
rta_getattr(__u32, tb[MPTCP_SUBFLOW_ATTR_MAP_SFSEQ]));
if (tb[MPTCP_SUBFLOW_ATTR_SSN_OFFSET])
printf(" ssnoff:%u",
rta_getattr(__u32, tb[MPTCP_SUBFLOW_ATTR_SSN_OFFSET]));
if (tb[MPTCP_SUBFLOW_ATTR_MAP_DATALEN])
printf(" maplen:%u",
rta_getattr(__u32, tb[MPTCP_SUBFLOW_ATTR_MAP_DATALEN]));
printf("\n");
}
static void parse_nlmsg(struct nlmsghdr *nlh, __u32 proto)
{
struct inet_diag_msg *r = NLMSG_DATA(nlh);
struct rtattr *tb[INET_DIAG_MAX + 1];
parse_rtattr_flags(tb, INET_DIAG_MAX, (struct rtattr *)(r + 1),
nlh->nlmsg_len - NLMSG_LENGTH(sizeof(*r)),
NLA_F_NESTED);
if (proto == IPPROTO_MPTCP && tb[INET_DIAG_INFO]) {
int len = RTA_PAYLOAD(tb[INET_DIAG_INFO]);
struct mptcp_info *info;
/* workaround fort older kernels with less fields */
if (len < sizeof(*info)) {
info = alloca(sizeof(*info));
memcpy(info, RTA_DATA(tb[INET_DIAG_INFO]), len);
memset((char *)info + len, 0, sizeof(*info) - len);
} else {
info = RTA_DATA(tb[INET_DIAG_INFO]);
}
print_info_msg(info);
}
if (proto == IPPROTO_TCP && tb[INET_DIAG_ULP_INFO]) {
struct rtattr *ulpinfo[INET_ULP_INFO_MAX + 1] = { 0 };
parse_rtattr_nested(ulpinfo, INET_ULP_INFO_MAX,
tb[INET_DIAG_ULP_INFO]);
if (ulpinfo[INET_ULP_INFO_MPTCP]) {
struct rtattr *sfinfo[MPTCP_SUBFLOW_ATTR_MAX + 1] = { 0 };
parse_rtattr_nested(sfinfo, MPTCP_SUBFLOW_ATTR_MAX,
ulpinfo[INET_ULP_INFO_MPTCP]);
print_subflow_info(sfinfo);
} else {
printf("It's a normal TCP!\n");
}
}
}
static void recv_nlmsg(int fd, __u32 proto)
{
char rcv_buff[8192];
struct nlmsghdr *nlh = (struct nlmsghdr *)rcv_buff;
struct sockaddr_nl rcv_nladdr = {
.nl_family = AF_NETLINK
};
struct iovec rcv_iov = {
.iov_base = rcv_buff,
.iov_len = sizeof(rcv_buff)
};
struct msghdr rcv_msg = {
.msg_name = &rcv_nladdr,
.msg_namelen = sizeof(rcv_nladdr),
.msg_iov = &rcv_iov,
.msg_iovlen = 1
};
int len;
len = recvmsg(fd, &rcv_msg, 0);
while (NLMSG_OK(nlh, len)) {
if (nlh->nlmsg_type == NLMSG_DONE) {
printf("NLMSG_DONE\n");
break;
} else if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err;
err = (struct nlmsgerr *)NLMSG_DATA(nlh);
printf("Error %d:%s\n",
-(err->error), strerror(-(err->error)));
break;
}
parse_nlmsg(nlh, proto);
nlh = NLMSG_NEXT(nlh, len);
}
}
static void get_mptcpinfo(__u32 token)
{
struct inet_diag_req_v2 r = {
.sdiag_family = AF_INET,
/* Real proto is set via INET_DIAG_REQ_PROTOCOL */
.sdiag_protocol = IPPROTO_TCP,
.idiag_ext = 1 << (INET_DIAG_INFO - 1),
.id.idiag_cookie[0] = token,
};
__u32 proto = IPPROTO_MPTCP;
int fd;
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
if (fd < 0)
die_perror("Netlink socket");
send_query(fd, &r, proto);
recv_nlmsg(fd, proto);
close(fd);
}
static void get_subflow_info(char *subflow_addrs)
{
struct inet_diag_req_v2 r = {
.sdiag_family = AF_INET,
.sdiag_protocol = IPPROTO_TCP,
.idiag_ext = 1 << (INET_DIAG_INFO - 1),
.id.idiag_cookie[0] = INET_DIAG_NOCOOKIE,
.id.idiag_cookie[1] = INET_DIAG_NOCOOKIE,
};
char saddr[64], daddr[64];
int sport, dport;
int ret;
int fd;
ret = sscanf(subflow_addrs, "%[^:]:%d %[^:]:%d", saddr, &sport, daddr, &dport);
if (ret != 4)
die_perror("IP PORT Pairs has style problems!");
printf("%s:%d -> %s:%d\n", saddr, sport, daddr, dport);
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG);
if (fd < 0)
die_perror("Netlink socket");
r.id.idiag_sport = htons(sport);
r.id.idiag_dport = htons(dport);
inet_pton(AF_INET, saddr, &r.id.idiag_src);
inet_pton(AF_INET, daddr, &r.id.idiag_dst);
send_query(fd, &r, IPPROTO_TCP);
recv_nlmsg(fd, IPPROTO_TCP);
}
static void parse_opts(int argc, char **argv, struct params *p)
{
int c;
if (argc < 2)
die_usage(1);
while ((c = getopt(argc, argv, "ht:s:")) != -1) {
switch (c) {
case 'h':
die_usage(0);
break;
case 't':
sscanf(optarg, "%x", &p->target_token);
break;
case 's':
strncpy(p->subflow_addrs, optarg,
sizeof(p->subflow_addrs) - 1);
break;
default:
die_usage(1);
break;
}
}
}
int main(int argc, char *argv[])
{
struct params p = { 0 };
parse_opts(argc, argv, &p);
if (p.target_token)
get_mptcpinfo(p.target_token);
if (p.subflow_addrs[0] != '\0')
get_subflow_info(p.subflow_addrs);
return 0;
}
@@ -0,0 +1,614 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/random.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netdb.h>
#include <netinet/in.h>
#include <linux/tcp.h>
#include <linux/sockios.h>
#include <linux/compiler.h>
#ifndef IPPROTO_MPTCP
#define IPPROTO_MPTCP 262
#endif
#ifndef SOL_MPTCP
#define SOL_MPTCP 284
#endif
static int pf = AF_INET;
static int proto_tx = IPPROTO_MPTCP;
static int proto_rx = IPPROTO_MPTCP;
static void __noreturn die_perror(const char *msg)
{
perror(msg);
exit(1);
}
static void die_usage(int r)
{
fprintf(stderr, "Usage: mptcp_inq [-6] [ -t tcp|mptcp ] [ -r tcp|mptcp]\n");
exit(r);
}
static void __noreturn xerror(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
exit(1);
}
static const char *getxinfo_strerr(int err)
{
if (err == EAI_SYSTEM)
return strerror(errno);
return gai_strerror(err);
}
static void xgetaddrinfo(const char *node, const char *service,
struct addrinfo *hints,
struct addrinfo **res)
{
int err;
again:
err = getaddrinfo(node, service, hints, res);
if (err) {
const char *errstr;
if (err == EAI_SOCKTYPE) {
hints->ai_protocol = IPPROTO_TCP;
goto again;
}
errstr = getxinfo_strerr(err);
fprintf(stderr, "Fatal: getaddrinfo(%s:%s): %s\n",
node ? node : "", service ? service : "", errstr);
exit(1);
}
}
static int sock_listen_mptcp(const char * const listenaddr,
const char * const port)
{
int sock = -1;
struct addrinfo hints = {
.ai_protocol = IPPROTO_MPTCP,
.ai_socktype = SOCK_STREAM,
.ai_flags = AI_PASSIVE | AI_NUMERICHOST
};
hints.ai_family = pf;
struct addrinfo *a, *addr;
int one = 1;
xgetaddrinfo(listenaddr, port, &hints, &addr);
hints.ai_family = pf;
for (a = addr; a; a = a->ai_next) {
sock = socket(a->ai_family, a->ai_socktype, proto_rx);
if (sock < 0)
continue;
if (-1 == setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one,
sizeof(one)))
perror("setsockopt");
if (bind(sock, a->ai_addr, a->ai_addrlen) == 0)
break; /* success */
perror("bind");
close(sock);
sock = -1;
}
freeaddrinfo(addr);
if (sock < 0)
xerror("could not create listen socket");
if (listen(sock, 20))
die_perror("listen");
return sock;
}
static int sock_connect_mptcp(const char * const remoteaddr,
const char * const port, int proto)
{
struct addrinfo hints = {
.ai_protocol = IPPROTO_MPTCP,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *a, *addr;
int sock = -1;
hints.ai_family = pf;
xgetaddrinfo(remoteaddr, port, &hints, &addr);
for (a = addr; a; a = a->ai_next) {
sock = socket(a->ai_family, a->ai_socktype, proto);
if (sock < 0)
continue;
if (connect(sock, a->ai_addr, a->ai_addrlen) == 0)
break; /* success */
die_perror("connect");
}
if (sock < 0)
xerror("could not create connect socket");
freeaddrinfo(addr);
return sock;
}
static int protostr_to_num(const char *s)
{
if (strcasecmp(s, "tcp") == 0)
return IPPROTO_TCP;
if (strcasecmp(s, "mptcp") == 0)
return IPPROTO_MPTCP;
die_usage(1);
return 0;
}
static void parse_opts(int argc, char **argv)
{
int c;
while ((c = getopt(argc, argv, "h6t:r:")) != -1) {
switch (c) {
case 'h':
die_usage(0);
break;
case '6':
pf = AF_INET6;
break;
case 't':
proto_tx = protostr_to_num(optarg);
break;
case 'r':
proto_rx = protostr_to_num(optarg);
break;
default:
die_usage(1);
break;
}
}
}
/* wait up to timeout milliseconds */
static void wait_for_ack(int fd, int timeout, size_t total)
{
int i;
for (i = 0; i < timeout; i++) {
int nsd, ret, queued = -1;
struct timespec req;
ret = ioctl(fd, TIOCOUTQ, &queued);
if (ret < 0)
die_perror("TIOCOUTQ");
ret = ioctl(fd, SIOCOUTQNSD, &nsd);
if (ret < 0)
die_perror("SIOCOUTQNSD");
if ((size_t)queued > total)
xerror("TIOCOUTQ %u, but only %zu expected\n", queued, total);
assert(nsd <= queued);
if (queued == 0)
return;
/* wait for peer to ack rx of all data */
req.tv_sec = 0;
req.tv_nsec = 1 * 1000 * 1000ul; /* 1ms */
nanosleep(&req, NULL);
}
xerror("still tx data queued after %u ms\n", timeout);
}
static void connect_one_server(int fd, int unixfd)
{
size_t len, i, total, sent;
char buf[4096], buf2[4096];
ssize_t ret;
len = rand() % (sizeof(buf) - 1);
if (len < 128)
len = 128;
for (i = 0; i < len ; i++) {
buf[i] = rand() % 26;
buf[i] += 'A';
}
buf[i] = '\n';
/* un-block server */
ret = read(unixfd, buf2, 4);
assert(ret == 4);
assert(strncmp(buf2, "xmit", 4) == 0);
ret = write(unixfd, &len, sizeof(len));
assert(ret == (ssize_t)sizeof(len));
ret = write(fd, buf, len);
if (ret < 0)
die_perror("write");
if (ret != (ssize_t)len)
xerror("short write");
ret = read(unixfd, buf2, 4);
assert(strncmp(buf2, "huge", 4) == 0);
total = rand() % (16 * 1024 * 1024);
total += (1 * 1024 * 1024);
sent = total;
ret = write(unixfd, &total, sizeof(total));
assert(ret == (ssize_t)sizeof(total));
wait_for_ack(fd, 5000, len);
while (total > 0) {
if (total > sizeof(buf))
len = sizeof(buf);
else
len = total;
ret = write(fd, buf, len);
if (ret < 0)
die_perror("write");
total -= ret;
/* we don't have to care about buf content, only
* number of total bytes sent
*/
}
ret = read(unixfd, buf2, 4);
assert(ret == 4);
assert(strncmp(buf2, "shut", 4) == 0);
wait_for_ack(fd, 5000, sent);
ret = write(fd, buf, 1);
assert(ret == 1);
close(fd);
ret = write(unixfd, "closed", 6);
assert(ret == 6);
close(unixfd);
}
static void get_tcp_inq(struct msghdr *msgh, unsigned int *inqv)
{
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(msgh); cmsg ; cmsg = CMSG_NXTHDR(msgh, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_TCP && cmsg->cmsg_type == TCP_CM_INQ) {
memcpy(inqv, CMSG_DATA(cmsg), sizeof(*inqv));
return;
}
}
xerror("could not find TCP_CM_INQ cmsg type");
}
static void process_one_client(int fd, int unixfd)
{
unsigned int tcp_inq;
size_t expect_len;
char msg_buf[4096];
char buf[4096];
char tmp[16];
struct iovec iov = {
.iov_base = buf,
.iov_len = 1,
};
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = msg_buf,
.msg_controllen = sizeof(msg_buf),
};
ssize_t ret, tot;
ret = write(unixfd, "xmit", 4);
assert(ret == 4);
ret = read(unixfd, &expect_len, sizeof(expect_len));
assert(ret == (ssize_t)sizeof(expect_len));
if (expect_len > sizeof(buf))
xerror("expect len %zu exceeds buffer size", expect_len);
for (;;) {
struct timespec req;
unsigned int queued;
ret = ioctl(fd, FIONREAD, &queued);
if (ret < 0)
die_perror("FIONREAD");
if (queued > expect_len)
xerror("FIONREAD returned %u, but only %zu expected\n",
queued, expect_len);
if (queued == expect_len)
break;
req.tv_sec = 0;
req.tv_nsec = 1000 * 1000ul;
nanosleep(&req, NULL);
}
/* read one byte, expect cmsg to return expected - 1 */
ret = recvmsg(fd, &msg, 0);
if (ret < 0)
die_perror("recvmsg");
if (msg.msg_controllen == 0)
xerror("msg_controllen is 0");
get_tcp_inq(&msg, &tcp_inq);
assert((size_t)tcp_inq == (expect_len - 1));
iov.iov_len = sizeof(buf);
ret = recvmsg(fd, &msg, 0);
if (ret < 0)
die_perror("recvmsg");
/* should have gotten exact remainder of all pending data */
assert(ret == (ssize_t)tcp_inq);
/* should be 0, all drained */
get_tcp_inq(&msg, &tcp_inq);
assert(tcp_inq == 0);
/* request a large swath of data. */
ret = write(unixfd, "huge", 4);
assert(ret == 4);
ret = read(unixfd, &expect_len, sizeof(expect_len));
assert(ret == (ssize_t)sizeof(expect_len));
/* peer should send us a few mb of data */
if (expect_len <= sizeof(buf))
xerror("expect len %zu too small\n", expect_len);
tot = 0;
do {
iov.iov_len = sizeof(buf);
ret = recvmsg(fd, &msg, 0);
if (ret < 0)
die_perror("recvmsg");
tot += ret;
get_tcp_inq(&msg, &tcp_inq);
if (tcp_inq > expect_len - tot)
xerror("inq %d, remaining %d total_len %d\n",
tcp_inq, expect_len - tot, (int)expect_len);
assert(tcp_inq <= expect_len - tot);
} while ((size_t)tot < expect_len);
ret = write(unixfd, "shut", 4);
assert(ret == 4);
/* wait for hangup. Should have received one more byte of data. */
ret = read(unixfd, tmp, sizeof(tmp));
assert(ret == 6);
assert(strncmp(tmp, "closed", 6) == 0);
sleep(1);
iov.iov_len = 1;
ret = recvmsg(fd, &msg, 0);
if (ret < 0)
die_perror("recvmsg");
assert(ret == 1);
get_tcp_inq(&msg, &tcp_inq);
/* tcp_inq should be 1 due to received fin. */
assert(tcp_inq == 1);
iov.iov_len = 1;
ret = recvmsg(fd, &msg, 0);
if (ret < 0)
die_perror("recvmsg");
/* expect EOF */
assert(ret == 0);
get_tcp_inq(&msg, &tcp_inq);
assert(tcp_inq == 1);
close(fd);
}
static int xaccept(int s)
{
int fd = accept(s, NULL, 0);
if (fd < 0)
die_perror("accept");
return fd;
}
static int server(int unixfd)
{
int fd = -1, r, on = 1;
switch (pf) {
case AF_INET:
fd = sock_listen_mptcp("127.0.0.1", "15432");
break;
case AF_INET6:
fd = sock_listen_mptcp("::1", "15432");
break;
default:
xerror("Unknown pf %d\n", pf);
break;
}
r = write(unixfd, "conn", 4);
assert(r == 4);
alarm(15);
r = xaccept(fd);
if (-1 == setsockopt(r, IPPROTO_TCP, TCP_INQ, &on, sizeof(on)))
die_perror("setsockopt");
process_one_client(r, unixfd);
close(fd);
return 0;
}
static int client(int unixfd)
{
int fd = -1;
alarm(15);
switch (pf) {
case AF_INET:
fd = sock_connect_mptcp("127.0.0.1", "15432", proto_tx);
break;
case AF_INET6:
fd = sock_connect_mptcp("::1", "15432", proto_tx);
break;
default:
xerror("Unknown pf %d\n", pf);
}
connect_one_server(fd, unixfd);
return 0;
}
static void init_rng(void)
{
unsigned int foo;
if (getrandom(&foo, sizeof(foo), 0) == -1) {
perror("getrandom");
exit(1);
}
srand(foo);
}
static pid_t xfork(void)
{
pid_t p = fork();
if (p < 0)
die_perror("fork");
else if (p == 0)
init_rng();
return p;
}
static int rcheck(int wstatus, const char *what)
{
if (WIFEXITED(wstatus)) {
if (WEXITSTATUS(wstatus) == 0)
return 0;
fprintf(stderr, "%s exited, status=%d\n", what, WEXITSTATUS(wstatus));
return WEXITSTATUS(wstatus);
} else if (WIFSIGNALED(wstatus)) {
xerror("%s killed by signal %d\n", what, WTERMSIG(wstatus));
} else if (WIFSTOPPED(wstatus)) {
xerror("%s stopped by signal %d\n", what, WSTOPSIG(wstatus));
}
return 111;
}
int main(int argc, char *argv[])
{
int e1, e2, wstatus;
pid_t s, c, ret;
int unixfds[2];
parse_opts(argc, argv);
e1 = socketpair(AF_UNIX, SOCK_DGRAM, 0, unixfds);
if (e1 < 0)
die_perror("pipe");
s = xfork();
if (s == 0) {
close(unixfds[0]);
ret = server(unixfds[1]);
close(unixfds[1]);
return ret;
}
close(unixfds[1]);
/* wait until server bound a socket */
e1 = read(unixfds[0], &e1, 4);
assert(e1 == 4);
c = xfork();
if (c == 0)
return client(unixfds[0]);
close(unixfds[0]);
ret = waitpid(s, &wstatus, 0);
if (ret == -1)
die_perror("waitpid");
e1 = rcheck(wstatus, "server");
ret = waitpid(c, &wstatus, 0);
if (ret == -1)
die_perror("waitpid");
e2 = rcheck(wstatus, "client");
return e1 ? e1 : e2;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,764 @@
#! /bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(dirname "${0}")/../lib.sh"
readonly KSFT_PASS=0
readonly KSFT_FAIL=1
readonly KSFT_SKIP=4
# shellcheck disable=SC2155 # declare and assign separately
readonly KSFT_TEST="${MPTCP_LIB_KSFT_TEST:-$(basename "${0}" .sh)}"
# These variables are used in some selftests, read-only
declare -rx MPTCP_LIB_EVENT_CREATED=1 # MPTCP_EVENT_CREATED
declare -rx MPTCP_LIB_EVENT_ESTABLISHED=2 # MPTCP_EVENT_ESTABLISHED
declare -rx MPTCP_LIB_EVENT_CLOSED=3 # MPTCP_EVENT_CLOSED
declare -rx MPTCP_LIB_EVENT_ANNOUNCED=6 # MPTCP_EVENT_ANNOUNCED
declare -rx MPTCP_LIB_EVENT_REMOVED=7 # MPTCP_EVENT_REMOVED
declare -rx MPTCP_LIB_EVENT_SUB_ESTABLISHED=10 # MPTCP_EVENT_SUB_ESTABLISHED
declare -rx MPTCP_LIB_EVENT_SUB_CLOSED=11 # MPTCP_EVENT_SUB_CLOSED
declare -rx MPTCP_LIB_EVENT_SUB_PRIORITY=13 # MPTCP_EVENT_SUB_PRIORITY
declare -rx MPTCP_LIB_EVENT_LISTENER_CREATED=15 # MPTCP_EVENT_LISTENER_CREATED
declare -rx MPTCP_LIB_EVENT_LISTENER_CLOSED=16 # MPTCP_EVENT_LISTENER_CLOSED
declare -rx MPTCP_LIB_AF_INET=2
declare -rx MPTCP_LIB_AF_INET6=10
MPTCP_LIB_SUBTESTS=()
MPTCP_LIB_SUBTESTS_DUPLICATED=0
MPTCP_LIB_SUBTEST_FLAKY=0
MPTCP_LIB_SUBTESTS_LAST_TS_NS=
MPTCP_LIB_TEST_COUNTER=0
MPTCP_LIB_TEST_FORMAT="%02u %-50s"
MPTCP_LIB_IP_MPTCP=0
# only if supported (or forced) and not disabled, see no-color.org
if { [ -t 1 ] || [ "${SELFTESTS_MPTCP_LIB_COLOR_FORCE:-}" = "1" ]; } &&
[ "${NO_COLOR:-}" != "1" ]; then
readonly MPTCP_LIB_COLOR_RED="\E[1;31m"
readonly MPTCP_LIB_COLOR_GREEN="\E[1;32m"
readonly MPTCP_LIB_COLOR_YELLOW="\E[1;33m"
readonly MPTCP_LIB_COLOR_BLUE="\E[1;34m"
readonly MPTCP_LIB_COLOR_RESET="\E[0m"
else
readonly MPTCP_LIB_COLOR_RED=
readonly MPTCP_LIB_COLOR_GREEN=
readonly MPTCP_LIB_COLOR_YELLOW=
readonly MPTCP_LIB_COLOR_BLUE=
readonly MPTCP_LIB_COLOR_RESET=
fi
# SELFTESTS_MPTCP_LIB_OVERRIDE_FLAKY env var can be set not to ignore errors
# from subtests marked as flaky
mptcp_lib_override_flaky() {
[ "${SELFTESTS_MPTCP_LIB_OVERRIDE_FLAKY:-}" = 1 ]
}
mptcp_lib_subtest_is_flaky() {
[ "${MPTCP_LIB_SUBTEST_FLAKY}" = 1 ] && ! mptcp_lib_override_flaky
}
# $1: color, $2: text
mptcp_lib_print_color() {
echo -e "${MPTCP_LIB_START_PRINT:-}${*}${MPTCP_LIB_COLOR_RESET}"
}
mptcp_lib_print_ok() {
mptcp_lib_print_color "${MPTCP_LIB_COLOR_GREEN}${*}"
}
mptcp_lib_print_warn() {
mptcp_lib_print_color "${MPTCP_LIB_COLOR_YELLOW}${*}"
}
mptcp_lib_print_info() {
mptcp_lib_print_color "${MPTCP_LIB_COLOR_BLUE}${*}"
}
mptcp_lib_print_err() {
mptcp_lib_print_color "${MPTCP_LIB_COLOR_RED}${*}"
}
# shellcheck disable=SC2120 # parameters are optional
mptcp_lib_pr_ok() {
mptcp_lib_print_ok "[ OK ]${1:+ ${*}}"
}
mptcp_lib_pr_skip() {
mptcp_lib_print_warn "[SKIP]${1:+ ${*}}"
}
mptcp_lib_pr_fail() {
local title cmt
if mptcp_lib_subtest_is_flaky; then
title="IGNO"
cmt=" (flaky)"
else
title="FAIL"
fi
mptcp_lib_print_err "[${title}]${cmt}${1:+ ${*}}"
}
mptcp_lib_pr_info() {
mptcp_lib_print_info "INFO: ${*}"
}
mptcp_lib_pr_nstat() {
local ns="${1}"
local hist="/tmp/${ns}.out"
if [ -f "${hist}" ]; then
awk '$2 != 0 { print " "$0 }' "${hist}"
else
ip netns exec "${ns}" nstat -as | grep Tcp
fi
}
# $1-2: listener/connector ns ; $3 port
mptcp_lib_pr_err_stats() {
local lns="${1}"
local cns="${2}"
local port="${3}"
echo -en "${MPTCP_LIB_COLOR_RED}"
{
printf "\nnetns %s (listener) socket stat for %d:\n" "${lns}" "${port}"
ip netns exec "${lns}" ss -Menitam -o "sport = :${port}"
mptcp_lib_pr_nstat "${lns}"
printf "\nnetns %s (connector) socket stat for %d:\n" "${cns}" "${port}"
ip netns exec "${cns}" ss -Menitam -o "dport = :${port}"
[ "${lns}" != "${cns}" ] && mptcp_lib_pr_nstat "${cns}"
} 1>&2
echo -en "${MPTCP_LIB_COLOR_RESET}"
}
# SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES env var can be set when validating all
# features using the last version of the kernel and the selftests to make sure
# a test is not being skipped by mistake.
mptcp_lib_expect_all_features() {
[ "${SELFTESTS_MPTCP_LIB_EXPECT_ALL_FEATURES:-}" = "1" ]
}
# $1: msg
mptcp_lib_fail_if_expected_feature() {
if mptcp_lib_expect_all_features; then
echo "ERROR: missing feature: ${*}"
exit ${KSFT_FAIL}
fi
return 1
}
# $1: file
mptcp_lib_has_file() {
local f="${1}"
if [ -f "${f}" ]; then
return 0
fi
mptcp_lib_fail_if_expected_feature "${f} file not found"
}
mptcp_lib_check_mptcp() {
if ! mptcp_lib_has_file "/proc/sys/net/mptcp/enabled"; then
mptcp_lib_pr_skip "MPTCP support is not available"
exit ${KSFT_SKIP}
fi
}
mptcp_lib_check_kallsyms() {
if ! mptcp_lib_has_file "/proc/kallsyms"; then
mptcp_lib_pr_skip "CONFIG_KALLSYMS is missing"
exit ${KSFT_SKIP}
fi
}
# Internal: use mptcp_lib_kallsyms_has() instead
__mptcp_lib_kallsyms_has() {
local sym="${1}"
mptcp_lib_check_kallsyms
grep -q " ${sym}" /proc/kallsyms
}
# $1: part of a symbol to look at, add '$' at the end for full name
mptcp_lib_kallsyms_has() {
local sym="${1}"
if __mptcp_lib_kallsyms_has "${sym}"; then
return 0
fi
mptcp_lib_fail_if_expected_feature "${sym} symbol not found"
}
# $1: part of a symbol to look at, add '$' at the end for full name
mptcp_lib_kallsyms_doesnt_have() {
local sym="${1}"
if ! __mptcp_lib_kallsyms_has "${sym}"; then
return 0
fi
mptcp_lib_fail_if_expected_feature "${sym} symbol has been found"
}
# !!!AVOID USING THIS!!!
# Features might not land in the expected version and features can be backported
#
# $1: kernel version, e.g. 6.3
mptcp_lib_kversion_ge() {
local exp_maj="${1%.*}"
local exp_min="${1#*.}"
local v maj min
# If the kernel has backported features, set this env var to 1:
if [ "${SELFTESTS_MPTCP_LIB_NO_KVERSION_CHECK:-}" = "1" ]; then
return 0
fi
v=$(uname -r | cut -d'.' -f1,2)
maj=${v%.*}
min=${v#*.}
if [ "${maj}" -gt "${exp_maj}" ] ||
{ [ "${maj}" -eq "${exp_maj}" ] && [ "${min}" -ge "${exp_min}" ]; }; then
return 0
fi
mptcp_lib_fail_if_expected_feature "kernel version ${1} lower than ${v}"
}
mptcp_lib_subtests_last_ts_reset() {
MPTCP_LIB_SUBTESTS_LAST_TS_NS="$(date +%s%N)"
}
mptcp_lib_subtests_last_ts_reset
__mptcp_lib_result_check_duplicated() {
local subtest
for subtest in "${MPTCP_LIB_SUBTESTS[@]}"; do
if [[ "${subtest}" == *" - ${KSFT_TEST}: ${*%% #*}" ]]; then
MPTCP_LIB_SUBTESTS_DUPLICATED=1
mptcp_lib_print_err "Duplicated entry: ${*}"
break
fi
done
}
__mptcp_lib_result_add() {
local result="${1}"
local time="time="
local ts_prev_ns
shift
local id=$((${#MPTCP_LIB_SUBTESTS[@]} + 1))
__mptcp_lib_result_check_duplicated "${*}"
# not to add two '#'
[[ "${*}" != *"#"* ]] && time="# ${time}"
ts_prev_ns="${MPTCP_LIB_SUBTESTS_LAST_TS_NS}"
mptcp_lib_subtests_last_ts_reset
time+="$(((MPTCP_LIB_SUBTESTS_LAST_TS_NS - ts_prev_ns) / 1000000))ms"
MPTCP_LIB_SUBTESTS+=("${result} ${id} - ${KSFT_TEST}: ${*} ${time}")
}
# $1: test name
mptcp_lib_result_pass() {
__mptcp_lib_result_add "ok" "${1}"
}
# $1: test name
mptcp_lib_result_fail() {
if mptcp_lib_subtest_is_flaky; then
# It might sound better to use 'not ok # TODO' or 'ok # SKIP',
# but some CIs don't understand 'TODO' and treat SKIP as errors.
__mptcp_lib_result_add "ok" "${1} # IGNORE Flaky"
else
__mptcp_lib_result_add "not ok" "${1}"
fi
}
# $1: test name
mptcp_lib_result_skip() {
__mptcp_lib_result_add "ok" "${1} # SKIP"
}
# $1: result code ; $2: test name
mptcp_lib_result_code() {
local ret="${1}"
local name="${2}"
case "${ret}" in
"${KSFT_PASS}")
mptcp_lib_result_pass "${name}"
;;
"${KSFT_FAIL}")
mptcp_lib_result_fail "${name}"
;;
"${KSFT_SKIP}")
mptcp_lib_result_skip "${name}"
;;
*)
echo "ERROR: wrong result code: ${ret}"
exit ${KSFT_FAIL}
;;
esac
}
mptcp_lib_result_print_all_tap() {
local subtest
if [ ${#MPTCP_LIB_SUBTESTS[@]} -eq 0 ] ||
[ "${SELFTESTS_MPTCP_LIB_NO_TAP:-}" = "1" ]; then
return
fi
printf "\nTAP version 13\n"
printf "1..%d\n" "${#MPTCP_LIB_SUBTESTS[@]}"
for subtest in "${MPTCP_LIB_SUBTESTS[@]}"; do
printf "%s\n" "${subtest}"
done
if [ "${MPTCP_LIB_SUBTESTS_DUPLICATED}" = 1 ] &&
mptcp_lib_expect_all_features; then
mptcp_lib_print_err "Duplicated test entries"
exit ${KSFT_FAIL}
fi
}
# get the value of keyword $1 in the line marked by keyword $2
mptcp_lib_get_info_value() {
grep "${2}" 2>/dev/null |
sed -n 's/.*\('"${1}"':\)\([0-9a-f:.]*\).*$/\2/p;q'
# the ';q' at the end limits to the first matched entry.
}
# $1: info name ; $2: evts_ns ; [$3: event type; [$4: addr]]
mptcp_lib_evts_get_info() {
grep "${4:-}" "${2}" 2>/dev/null |
mptcp_lib_get_info_value "${1}" "^type:${3:-1},"
}
mptcp_lib_wait_timeout() {
local timeout_test="${1}"
local listener_ns="${2}"
local connector_ns="${3}"
local port="${4}"
shift 4 # rest are PIDs
sleep "${timeout_test}"
mptcp_lib_print_err "timeout"
mptcp_lib_pr_err_stats "${listener_ns}" "${connector_ns}" "${port}"
kill "${@}" 2>/dev/null
}
# $1: PID
mptcp_lib_kill_wait() {
[ "${1}" -eq 0 ] && return 0
kill -SIGUSR1 "${1}" > /dev/null 2>&1
kill "${1}" > /dev/null 2>&1
wait "${1}" 2>/dev/null
}
# $1: PID
mptcp_lib_pid_list_children() {
local curr="${1}"
# evoke 'ps' only once
local pids="${2:-"$(ps o pid,ppid)"}"
echo "${curr}"
local pid
for pid in $(echo "${pids}" | awk "\$2 == ${curr} { print \$1 }"); do
mptcp_lib_pid_list_children "${pid}" "${pids}"
done
}
# $1: PID
mptcp_lib_kill_group_wait() {
# Some users might not have procps-ng: cannot use "kill -- -PID"
mptcp_lib_pid_list_children "${1}" | xargs -r kill &>/dev/null
wait "${1}" 2>/dev/null
}
# $1: IP address
mptcp_lib_is_v6() {
[ -z "${1##*:*}" ]
}
mptcp_lib_nstat_init() {
local ns="${1}"
rm -f "/tmp/${ns}."{nstat,out}
NSTAT_HISTORY="/tmp/${ns}.nstat" ip netns exec "${ns}" nstat -n
}
mptcp_lib_nstat_get() {
local ns="${1}"
# filter out non-*TCP stats, and the rate (last column)
NSTAT_HISTORY="/tmp/${ns}.nstat" ip netns exec "${ns}" nstat -sz |
grep -o ".*Tcp\S\+\s\+[0-9]\+" > "/tmp/${ns}.out"
}
# $1: ns, $2: MIB counter
# Get the counter from the history (mptcp_lib_nstat_{init,get}()) if available.
# If not, get the counter from nstat ignoring any history.
mptcp_lib_get_counter() {
local ns="${1}"
local counter="${2}"
local hist="/tmp/${ns}.out"
local count
if [[ -s "${hist}" && "${counter}" == *"Tcp"* ]]; then
count=$(awk "/^${counter} / {print \$2; exit}" "${hist}")
else
count=$(ip netns exec "${ns}" nstat -asz "${counter}" |
awk 'NR==1 {next} {print $2}')
fi
if [ -z "${count}" ]; then
mptcp_lib_fail_if_expected_feature "${counter} counter"
return 1
fi
echo "${count}"
}
mptcp_lib_make_file() {
local name="${1}"
local bs="${2}"
local size="${3}"
dd if=/dev/urandom of="${name}" bs="${bs}" count="${size}" 2> /dev/null
echo -e "\nMPTCP_TEST_FILE_END_MARKER" >> "${name}"
}
# $1: file
mptcp_lib_print_file_err() {
ls -l "${1}" 1>&2
echo "Trailing bytes are: "
tail -c 32 "${1}" | od -x | head -n2
}
# $1: input file ; $2: output file ; $3: what kind of file
mptcp_lib_check_transfer() {
local in="${1}"
local out="${2}"
local what="${3}"
if ! cmp "$in" "$out" > /dev/null 2>&1; then
mptcp_lib_pr_fail "$what does not match (in, out):"
mptcp_lib_print_file_err "$in"
mptcp_lib_print_file_err "$out"
return 1
fi
return 0
}
# $1: ns, $2: port
mptcp_lib_wait_local_port_listen() {
wait_local_port_listen "${@}" "tcp"
}
# $1: error file, $2: cmd, $3: expected msg, [$4: expected error]
mptcp_lib_check_output() {
local err="${1}"
local cmd="${2}"
local expected="${3}"
local exp_error="${4:-0}"
local cmd_ret=0
local out
out=$(${cmd} 2>"${err}") || cmd_ret=1
if [ "${cmd_ret}" != "${exp_error}" ]; then
mptcp_lib_pr_fail "unexpected returned code for '${cmd}', info:"
if [ "${exp_error}" = 0 ]; then
cat "${err}"
else
echo "${out}"
fi
return 2
elif [ "${out}" = "${expected}" ]; then
return 0
else
mptcp_lib_pr_fail "expected '${expected}' got '${out}'"
return 1
fi
}
mptcp_lib_check_tools() {
local tool
for tool in "${@}"; do
case "${tool}" in
"ip")
if ! ip -Version &> /dev/null; then
mptcp_lib_pr_skip "Could not run test without ip tool"
exit ${KSFT_SKIP}
fi
;;
"tc")
if ! tc -help &> /dev/null; then
mptcp_lib_pr_skip "Could not run test without tc tool"
exit ${KSFT_SKIP}
fi
;;
"ss")
if ! ss -h | grep -q MPTCP; then
mptcp_lib_pr_skip "ss tool does not support MPTCP"
exit ${KSFT_SKIP}
fi
;;
"iptables"* | "ip6tables"*)
if ! "${tool}" -V &> /dev/null; then
mptcp_lib_pr_skip "Could not run all tests without ${tool}"
exit ${KSFT_SKIP}
fi
;;
*)
mptcp_lib_pr_fail "Internal error: unsupported tool: ${tool}"
exit ${KSFT_FAIL}
;;
esac
done
}
mptcp_lib_ns_init() {
if ! setup_ns "${@}"; then
mptcp_lib_pr_fail "Failed to setup namespaces ${*}"
exit ${KSFT_FAIL}
fi
local netns
for netns in "${@}"; do
ip netns exec "${!netns}" sysctl -q net.mptcp.enabled=1
done
}
mptcp_lib_ns_exit() {
cleanup_ns "${@}"
local netns
for netns in "${@}"; do
rm -f /tmp/"${netns}".{nstat,out}
done
}
mptcp_lib_events() {
local ns="${1}"
local evts="${2}"
declare -n pid="${3}"
:>"${evts}"
mptcp_lib_kill_wait "${pid:-0}"
ip netns exec "${ns}" ./pm_nl_ctl events >> "${evts}" 2>&1 &
pid=$!
}
mptcp_lib_print_title() {
: "${MPTCP_LIB_TEST_COUNTER:?}"
: "${MPTCP_LIB_TEST_FORMAT:?}"
# shellcheck disable=SC2059 # the format is in a variable
printf "${MPTCP_LIB_TEST_FORMAT}" "$((++MPTCP_LIB_TEST_COUNTER))" "${*}"
}
# $1: var name ; $2: prev ret
mptcp_lib_check_expected_one() {
local var="${1}"
local exp="e_${var}"
local prev_ret="${2}"
if [ "${!var}" = "${!exp}" ]; then
return 0
fi
if [ "${prev_ret}" = "0" ]; then
mptcp_lib_pr_fail
fi
mptcp_lib_print_err "Expected value for '${var}': '${!exp}', got '${!var}'."
return 1
}
# $@: all var names to check
mptcp_lib_check_expected() {
local rc=0
local var
for var in "${@}"; do
mptcp_lib_check_expected_one "${var}" "${rc}" || rc=1
done
return "${rc}"
}
# shellcheck disable=SC2034 # Some variables are used below but indirectly
mptcp_lib_verify_listener_events() {
local evt=${1}
local e_type=${2}
local e_family=${3}
local e_saddr=${4}
local e_sport=${5}
local type
local family
local saddr
local sport
local rc=0
type=$(mptcp_lib_evts_get_info type "${evt}" "${e_type}")
family=$(mptcp_lib_evts_get_info family "${evt}" "${e_type}")
if [ "${family}" ] && [ "${family}" = "${AF_INET6}" ]; then
saddr=$(mptcp_lib_evts_get_info saddr6 "${evt}" "${e_type}")
else
saddr=$(mptcp_lib_evts_get_info saddr4 "${evt}" "${e_type}")
fi
sport=$(mptcp_lib_evts_get_info sport "${evt}" "${e_type}")
mptcp_lib_check_expected "type" "family" "saddr" "sport" || rc="${?}"
return "${rc}"
}
mptcp_lib_set_ip_mptcp() {
MPTCP_LIB_IP_MPTCP=1
}
mptcp_lib_is_ip_mptcp() {
[ "${MPTCP_LIB_IP_MPTCP}" = "1" ]
}
# format: <id>,<ip>,<flags>,<dev>
mptcp_lib_pm_nl_format_endpoints() {
local entry id ip flags dev port
for entry in "${@}"; do
IFS=, read -r id ip flags dev port <<< "${entry}"
if mptcp_lib_is_ip_mptcp; then
echo -n "${ip}"
[ -n "${port}" ] && echo -n " port ${port}"
echo -n " id ${id}"
[ -n "${flags}" ] && echo -n " ${flags}"
[ -n "${dev}" ] && echo -n " dev ${dev}"
echo " " # always a space at the end
else
echo -n "id ${id}"
echo -n " flags ${flags//" "/","}"
[ -n "${dev}" ] && echo -n " dev ${dev}"
echo -n " ${ip}"
[ -n "${port}" ] && echo -n " ${port}"
echo ""
fi
done
}
mptcp_lib_pm_nl_get_endpoint() {
local ns=${1}
local id=${2}
if mptcp_lib_is_ip_mptcp; then
ip -n "${ns}" mptcp endpoint show id "${id}"
else
ip netns exec "${ns}" ./pm_nl_ctl get "${id}"
fi
}
mptcp_lib_pm_nl_set_limits() {
local ns=${1}
local addrs=${2}
local subflows=${3}
if mptcp_lib_is_ip_mptcp; then
ip -n "${ns}" mptcp limits set add_addr_accepted "${addrs}" subflows "${subflows}"
else
ip netns exec "${ns}" ./pm_nl_ctl limits "${addrs}" "${subflows}"
fi
}
mptcp_lib_pm_nl_add_endpoint() {
local ns=${1}
local addr=${2}
local flags dev id port
local nr=2
local p
for p in "${@}"; do
case "${p}" in
"flags" | "dev" | "id" | "port")
eval "${p}"=\$"${nr}"
;;
esac
nr=$((nr + 1))
done
if mptcp_lib_is_ip_mptcp; then
# shellcheck disable=SC2086 # blanks in flags, no double quote
ip -n "${ns}" mptcp endpoint add "${addr}" ${flags//","/" "} \
${dev:+dev "${dev}"} ${id:+id "${id}"} ${port:+port "${port}"}
else
ip netns exec "${ns}" ./pm_nl_ctl add "${addr}" ${flags:+flags "${flags}"} \
${dev:+dev "${dev}"} ${id:+id "${id}"} ${port:+port "${port}"}
fi
}
mptcp_lib_pm_nl_del_endpoint() {
local ns=${1}
local id=${2}
local addr=${3}
if mptcp_lib_is_ip_mptcp; then
[ "${id}" -ne 0 ] && addr=''
ip -n "${ns}" mptcp endpoint delete id "${id}" ${addr:+"${addr}"}
else
ip netns exec "${ns}" ./pm_nl_ctl del "${id}" "${addr}"
fi
}
mptcp_lib_pm_nl_flush_endpoint() {
local ns=${1}
if mptcp_lib_is_ip_mptcp; then
ip -n "${ns}" mptcp endpoint flush
else
ip netns exec "${ns}" ./pm_nl_ctl flush
fi
}
mptcp_lib_pm_nl_show_endpoints() {
local ns=${1}
if mptcp_lib_is_ip_mptcp; then
ip -n "${ns}" mptcp endpoint show
else
ip netns exec "${ns}" ./pm_nl_ctl dump
fi
}
mptcp_lib_pm_nl_change_endpoint() {
local ns=${1}
local id=${2}
local flags=${3}
if mptcp_lib_is_ip_mptcp; then
# shellcheck disable=SC2086 # blanks in flags, no double quote
ip -n "${ns}" mptcp endpoint change id "${id}" ${flags//","/" "}
else
ip netns exec "${ns}" ./pm_nl_ctl set id "${id}" flags "${flags}"
fi
}
@@ -0,0 +1,885 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netdb.h>
#include <netinet/in.h>
#include <linux/tcp.h>
#include <linux/compiler.h>
static int pf = AF_INET;
#ifndef IPPROTO_MPTCP
#define IPPROTO_MPTCP 262
#endif
#ifndef SOL_MPTCP
#define SOL_MPTCP 284
#endif
#ifndef MPTCP_INFO
struct mptcp_info {
__u8 mptcpi_subflows;
__u8 mptcpi_add_addr_signal;
__u8 mptcpi_add_addr_accepted;
__u8 mptcpi_subflows_max;
__u8 mptcpi_add_addr_signal_max;
__u8 mptcpi_add_addr_accepted_max;
__u32 mptcpi_flags;
__u32 mptcpi_token;
__u64 mptcpi_write_seq;
__u64 mptcpi_snd_una;
__u64 mptcpi_rcv_nxt;
__u8 mptcpi_local_addr_used;
__u8 mptcpi_local_addr_max;
__u8 mptcpi_csum_enabled;
__u32 mptcpi_retransmits;
__u64 mptcpi_bytes_retrans;
__u64 mptcpi_bytes_sent;
__u64 mptcpi_bytes_received;
__u64 mptcpi_bytes_acked;
};
struct mptcp_subflow_data {
__u32 size_subflow_data; /* size of this structure in userspace */
__u32 num_subflows; /* must be 0, set by kernel */
__u32 size_kernel; /* must be 0, set by kernel */
__u32 size_user; /* size of one element in data[] */
} __attribute__((aligned(8)));
struct mptcp_subflow_addrs {
union {
__kernel_sa_family_t sa_family;
struct sockaddr sa_local;
struct sockaddr_in sin_local;
struct sockaddr_in6 sin6_local;
struct __kernel_sockaddr_storage ss_local;
};
union {
struct sockaddr sa_remote;
struct sockaddr_in sin_remote;
struct sockaddr_in6 sin6_remote;
struct __kernel_sockaddr_storage ss_remote;
};
};
#define MPTCP_INFO 1
#define MPTCP_TCPINFO 2
#define MPTCP_SUBFLOW_ADDRS 3
#endif
#ifndef MPTCP_FULL_INFO
struct mptcp_subflow_info {
__u32 id;
struct mptcp_subflow_addrs addrs;
};
struct mptcp_full_info {
__u32 size_tcpinfo_kernel; /* must be 0, set by kernel */
__u32 size_tcpinfo_user;
__u32 size_sfinfo_kernel; /* must be 0, set by kernel */
__u32 size_sfinfo_user;
__u32 num_subflows; /* must be 0, set by kernel (real subflow count) */
__u32 size_arrays_user; /* max subflows that userspace is interested in;
* the buffers at subflow_info/tcp_info
* are respectively at least:
* size_arrays * size_sfinfo_user
* size_arrays * size_tcpinfo_user
* bytes wide
*/
__aligned_u64 subflow_info;
__aligned_u64 tcp_info;
struct mptcp_info mptcp_info;
};
#define MPTCP_FULL_INFO 4
#endif
struct so_state {
struct mptcp_info mi;
struct mptcp_info last_sample;
struct tcp_info tcp_info;
struct mptcp_subflow_addrs addrs;
uint64_t mptcpi_rcv_delta;
uint64_t tcpi_rcv_delta;
bool pkt_stats_avail;
};
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
static void __noreturn die_perror(const char *msg)
{
perror(msg);
exit(1);
}
static void die_usage(int r)
{
fprintf(stderr, "Usage: mptcp_sockopt [-6]\n");
exit(r);
}
static void __noreturn xerror(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
exit(1);
}
static const char *getxinfo_strerr(int err)
{
if (err == EAI_SYSTEM)
return strerror(errno);
return gai_strerror(err);
}
static void xgetaddrinfo(const char *node, const char *service,
struct addrinfo *hints,
struct addrinfo **res)
{
int err;
again:
err = getaddrinfo(node, service, hints, res);
if (err) {
const char *errstr;
if (err == EAI_SOCKTYPE) {
hints->ai_protocol = IPPROTO_TCP;
goto again;
}
errstr = getxinfo_strerr(err);
fprintf(stderr, "Fatal: getaddrinfo(%s:%s): %s\n",
node ? node : "", service ? service : "", errstr);
exit(1);
}
}
static int sock_listen_mptcp(const char * const listenaddr,
const char * const port)
{
int sock = -1;
struct addrinfo hints = {
.ai_protocol = IPPROTO_MPTCP,
.ai_socktype = SOCK_STREAM,
.ai_flags = AI_PASSIVE | AI_NUMERICHOST
};
hints.ai_family = pf;
struct addrinfo *a, *addr;
int one = 1;
xgetaddrinfo(listenaddr, port, &hints, &addr);
hints.ai_family = pf;
for (a = addr; a; a = a->ai_next) {
sock = socket(a->ai_family, a->ai_socktype, IPPROTO_MPTCP);
if (sock < 0)
continue;
if (-1 == setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one,
sizeof(one)))
perror("setsockopt");
if (bind(sock, a->ai_addr, a->ai_addrlen) == 0)
break; /* success */
perror("bind");
close(sock);
sock = -1;
}
freeaddrinfo(addr);
if (sock < 0)
xerror("could not create listen socket");
if (listen(sock, 20))
die_perror("listen");
return sock;
}
static int sock_connect_mptcp(const char * const remoteaddr,
const char * const port, int proto)
{
struct addrinfo hints = {
.ai_protocol = IPPROTO_MPTCP,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *a, *addr;
int sock = -1;
hints.ai_family = pf;
xgetaddrinfo(remoteaddr, port, &hints, &addr);
for (a = addr; a; a = a->ai_next) {
sock = socket(a->ai_family, a->ai_socktype, proto);
if (sock < 0)
continue;
if (connect(sock, a->ai_addr, a->ai_addrlen) == 0)
break; /* success */
die_perror("connect");
}
if (sock < 0)
xerror("could not create connect socket");
freeaddrinfo(addr);
return sock;
}
static void parse_opts(int argc, char **argv)
{
int c;
while ((c = getopt(argc, argv, "h6")) != -1) {
switch (c) {
case 'h':
die_usage(0);
break;
case '6':
pf = AF_INET6;
break;
default:
die_usage(1);
break;
}
}
}
static void do_getsockopt_bogus_sf_data(int fd, int optname)
{
struct mptcp_subflow_data good_data;
struct bogus_data {
struct mptcp_subflow_data d;
char buf[2];
} bd;
socklen_t olen, _olen;
int ret;
memset(&bd, 0, sizeof(bd));
memset(&good_data, 0, sizeof(good_data));
olen = sizeof(good_data);
good_data.size_subflow_data = olen;
ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
assert(ret < 0); /* 0 size_subflow_data */
assert(olen == sizeof(good_data));
bd.d = good_data;
ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
assert(ret == 0);
assert(olen == sizeof(good_data));
assert(bd.d.num_subflows == 1);
assert(bd.d.size_kernel > 0);
assert(bd.d.size_user == 0);
bd.d = good_data;
_olen = rand() % olen;
olen = _olen;
ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
assert(ret < 0); /* bogus olen */
assert(olen == _olen); /* must be unchanged */
bd.d = good_data;
olen = sizeof(good_data);
bd.d.size_kernel = 1;
ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
assert(ret < 0); /* size_kernel not 0 */
bd.d = good_data;
olen = sizeof(good_data);
bd.d.num_subflows = 1;
ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
assert(ret < 0); /* num_subflows not 0 */
/* forward compat check: larger struct mptcp_subflow_data on 'old' kernel */
bd.d = good_data;
olen = sizeof(bd);
bd.d.size_subflow_data = sizeof(bd);
ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &olen);
assert(ret == 0);
/* olen must be truncated to real data size filled by kernel: */
assert(olen == sizeof(good_data));
assert(bd.d.size_subflow_data == sizeof(bd));
bd.d = good_data;
bd.d.size_subflow_data += 1;
bd.d.size_user = 1;
olen = bd.d.size_subflow_data + 1;
_olen = olen;
ret = getsockopt(fd, SOL_MPTCP, optname, &bd, &_olen);
assert(ret == 0);
/* no truncation, kernel should have filled 1 byte of optname payload in buf[1]: */
assert(olen == _olen);
assert(bd.d.size_subflow_data == sizeof(good_data) + 1);
assert(bd.buf[0] == 0);
}
static void do_getsockopt_mptcp_info(struct so_state *s, int fd, size_t w)
{
struct mptcp_info i;
socklen_t olen;
int ret;
olen = sizeof(i);
ret = getsockopt(fd, SOL_MPTCP, MPTCP_INFO, &i, &olen);
if (ret < 0)
die_perror("getsockopt MPTCP_INFO");
s->pkt_stats_avail = olen >= sizeof(i);
s->last_sample = i;
if (s->mi.mptcpi_write_seq == 0)
s->mi = i;
assert(s->mi.mptcpi_write_seq + w == i.mptcpi_write_seq);
s->mptcpi_rcv_delta = i.mptcpi_rcv_nxt - s->mi.mptcpi_rcv_nxt;
}
static void do_getsockopt_tcp_info(struct so_state *s, int fd, size_t r, size_t w)
{
struct my_tcp_info {
struct mptcp_subflow_data d;
struct tcp_info ti[2];
} ti;
int ret, tries = 5;
socklen_t olen;
do {
memset(&ti, 0, sizeof(ti));
ti.d.size_subflow_data = sizeof(struct mptcp_subflow_data);
ti.d.size_user = sizeof(struct tcp_info);
olen = sizeof(ti);
ret = getsockopt(fd, SOL_MPTCP, MPTCP_TCPINFO, &ti, &olen);
if (ret < 0)
xerror("getsockopt MPTCP_TCPINFO (tries %d, %m)");
assert(olen <= sizeof(ti));
assert(ti.d.size_kernel > 0);
assert(ti.d.size_user ==
MIN(ti.d.size_kernel, sizeof(struct tcp_info)));
assert(ti.d.num_subflows == 1);
assert(olen > (socklen_t)sizeof(struct mptcp_subflow_data));
olen -= sizeof(struct mptcp_subflow_data);
assert(olen == ti.d.size_user);
s->tcp_info = ti.ti[0];
if (ti.ti[0].tcpi_bytes_sent == w &&
ti.ti[0].tcpi_bytes_received == r)
goto done;
if (r == 0 && ti.ti[0].tcpi_bytes_sent == w &&
ti.ti[0].tcpi_bytes_received) {
s->tcpi_rcv_delta = ti.ti[0].tcpi_bytes_received;
goto done;
}
/* wait and repeat, might be that tx is still ongoing */
sleep(1);
} while (tries-- > 0);
xerror("tcpi_bytes_sent %" PRIu64 ", want %zu. tcpi_bytes_received %" PRIu64 ", want %zu",
ti.ti[0].tcpi_bytes_sent, w, ti.ti[0].tcpi_bytes_received, r);
done:
do_getsockopt_bogus_sf_data(fd, MPTCP_TCPINFO);
}
static void do_getsockopt_subflow_addrs(struct so_state *s, int fd)
{
struct sockaddr_storage remote, local;
socklen_t olen, rlen, llen;
int ret;
struct my_addrs {
struct mptcp_subflow_data d;
struct mptcp_subflow_addrs addr[2];
} addrs;
memset(&addrs, 0, sizeof(addrs));
memset(&local, 0, sizeof(local));
memset(&remote, 0, sizeof(remote));
addrs.d.size_subflow_data = sizeof(struct mptcp_subflow_data);
addrs.d.size_user = sizeof(struct mptcp_subflow_addrs);
olen = sizeof(addrs);
ret = getsockopt(fd, SOL_MPTCP, MPTCP_SUBFLOW_ADDRS, &addrs, &olen);
if (ret < 0)
die_perror("getsockopt MPTCP_SUBFLOW_ADDRS");
assert(olen <= sizeof(addrs));
assert(addrs.d.size_kernel > 0);
assert(addrs.d.size_user ==
MIN(addrs.d.size_kernel, sizeof(struct mptcp_subflow_addrs)));
assert(addrs.d.num_subflows == 1);
assert(olen > (socklen_t)sizeof(struct mptcp_subflow_data));
olen -= sizeof(struct mptcp_subflow_data);
assert(olen == addrs.d.size_user);
llen = sizeof(local);
ret = getsockname(fd, (struct sockaddr *)&local, &llen);
if (ret < 0)
die_perror("getsockname");
rlen = sizeof(remote);
ret = getpeername(fd, (struct sockaddr *)&remote, &rlen);
if (ret < 0)
die_perror("getpeername");
assert(rlen > 0);
assert(rlen == llen);
assert(remote.ss_family == local.ss_family);
assert(memcmp(&local, &addrs.addr[0].ss_local, sizeof(local)) == 0);
assert(memcmp(&remote, &addrs.addr[0].ss_remote, sizeof(remote)) == 0);
s->addrs = addrs.addr[0];
memset(&addrs, 0, sizeof(addrs));
addrs.d.size_subflow_data = sizeof(struct mptcp_subflow_data);
addrs.d.size_user = sizeof(sa_family_t);
olen = sizeof(addrs.d) + sizeof(sa_family_t);
ret = getsockopt(fd, SOL_MPTCP, MPTCP_SUBFLOW_ADDRS, &addrs, &olen);
assert(ret == 0);
assert(olen == sizeof(addrs.d) + sizeof(sa_family_t));
assert(addrs.addr[0].sa_family == pf);
assert(addrs.addr[0].sa_family == local.ss_family);
assert(memcmp(&local, &addrs.addr[0].ss_local, sizeof(local)) != 0);
assert(memcmp(&remote, &addrs.addr[0].ss_remote, sizeof(remote)) != 0);
do_getsockopt_bogus_sf_data(fd, MPTCP_SUBFLOW_ADDRS);
}
static void do_getsockopt_mptcp_full_info(struct so_state *s, int fd)
{
size_t data_size = sizeof(struct mptcp_full_info);
struct mptcp_subflow_info sfinfo[2];
struct tcp_info tcp_info[2];
struct mptcp_full_info mfi;
socklen_t olen;
int ret;
memset(&mfi, 0, data_size);
memset(tcp_info, 0, sizeof(tcp_info));
memset(sfinfo, 0, sizeof(sfinfo));
mfi.size_tcpinfo_user = sizeof(struct tcp_info);
mfi.size_sfinfo_user = sizeof(struct mptcp_subflow_info);
mfi.size_arrays_user = 2;
mfi.subflow_info = (unsigned long)&sfinfo[0];
mfi.tcp_info = (unsigned long)&tcp_info[0];
olen = data_size;
ret = getsockopt(fd, SOL_MPTCP, MPTCP_FULL_INFO, &mfi, &olen);
if (ret < 0) {
if (errno == EOPNOTSUPP) {
perror("MPTCP_FULL_INFO test skipped");
return;
}
xerror("getsockopt MPTCP_FULL_INFO");
}
assert(olen <= data_size);
assert(mfi.size_tcpinfo_kernel > 0);
assert(mfi.size_tcpinfo_user ==
MIN(mfi.size_tcpinfo_kernel, sizeof(struct tcp_info)));
assert(mfi.size_sfinfo_kernel > 0);
assert(mfi.size_sfinfo_user ==
MIN(mfi.size_sfinfo_kernel, sizeof(struct mptcp_subflow_info)));
assert(mfi.num_subflows == 1);
/* Tolerate future extension to mptcp_info struct and running newer
* test on top of older kernel.
* Anyway any kernel supporting MPTCP_FULL_INFO must at least include
* the following in mptcp_info.
*/
assert(olen > (socklen_t)__builtin_offsetof(struct mptcp_full_info, tcp_info));
assert(mfi.mptcp_info.mptcpi_subflows == 0);
assert(mfi.mptcp_info.mptcpi_bytes_sent == s->last_sample.mptcpi_bytes_sent);
assert(mfi.mptcp_info.mptcpi_bytes_received == s->last_sample.mptcpi_bytes_received);
assert(sfinfo[0].id == 1);
assert(tcp_info[0].tcpi_bytes_sent == s->tcp_info.tcpi_bytes_sent);
assert(tcp_info[0].tcpi_bytes_received == s->tcp_info.tcpi_bytes_received);
assert(!memcmp(&sfinfo->addrs, &s->addrs, sizeof(struct mptcp_subflow_addrs)));
}
static void do_getsockopts(struct so_state *s, int fd, size_t r, size_t w)
{
do_getsockopt_mptcp_info(s, fd, w);
do_getsockopt_tcp_info(s, fd, r, w);
do_getsockopt_subflow_addrs(s, fd);
if (r)
do_getsockopt_mptcp_full_info(s, fd);
}
static void connect_one_server(int fd, int pipefd)
{
char buf[4096], buf2[4096];
size_t len, i, total;
struct so_state s;
bool eof = false;
ssize_t ret;
memset(&s, 0, sizeof(s));
len = rand() % (sizeof(buf) - 1);
if (len < 128)
len = 128;
for (i = 0; i < len ; i++) {
buf[i] = rand() % 26;
buf[i] += 'A';
}
buf[i] = '\n';
do_getsockopts(&s, fd, 0, 0);
/* un-block server */
ret = read(pipefd, buf2, 4);
assert(ret == 4);
close(pipefd);
assert(strncmp(buf2, "xmit", 4) == 0);
ret = write(fd, buf, len);
if (ret < 0)
die_perror("write");
if (ret != (ssize_t)len)
xerror("short write");
total = 0;
do {
ret = read(fd, buf2 + total, sizeof(buf2) - total);
if (ret < 0)
die_perror("read");
if (ret == 0) {
eof = true;
break;
}
total += ret;
} while (total < len);
if (total != len)
xerror("total %lu, len %lu eof %d\n", total, len, eof);
if (memcmp(buf, buf2, len))
xerror("data corruption");
if (s.tcpi_rcv_delta)
assert(s.tcpi_rcv_delta <= total);
do_getsockopts(&s, fd, ret, ret);
if (eof)
total += 1; /* sequence advances due to FIN */
assert(s.mptcpi_rcv_delta == (uint64_t)total);
close(fd);
}
static void process_one_client(int fd, int pipefd)
{
ssize_t ret, ret2, ret3;
struct so_state s;
char buf[4096];
memset(&s, 0, sizeof(s));
do_getsockopts(&s, fd, 0, 0);
ret = write(pipefd, "xmit", 4);
assert(ret == 4);
ret = read(fd, buf, sizeof(buf));
if (ret < 0)
die_perror("read");
assert(s.mptcpi_rcv_delta <= (uint64_t)ret);
if (s.tcpi_rcv_delta)
assert(s.tcpi_rcv_delta == (uint64_t)ret);
ret2 = write(fd, buf, ret);
if (ret2 < 0)
die_perror("write");
/* wait for hangup */
ret3 = read(fd, buf, 1);
if (ret3 != 0)
xerror("expected EOF, got %lu", ret3);
do_getsockopts(&s, fd, ret, ret2);
if (s.mptcpi_rcv_delta != (uint64_t)ret + 1)
xerror("mptcpi_rcv_delta %" PRIu64 ", expect %" PRIu64 ", diff %" PRId64,
s.mptcpi_rcv_delta, ret + 1, s.mptcpi_rcv_delta - (ret + 1));
/* be nice when running on top of older kernel */
if (s.pkt_stats_avail) {
if (s.last_sample.mptcpi_bytes_sent != ret2)
xerror("mptcpi_bytes_sent %" PRIu64 ", expect %" PRIu64
", diff %" PRId64,
s.last_sample.mptcpi_bytes_sent, ret2,
s.last_sample.mptcpi_bytes_sent - ret2);
if (s.last_sample.mptcpi_bytes_received != ret)
xerror("mptcpi_bytes_received %" PRIu64 ", expect %" PRIu64
", diff %" PRId64,
s.last_sample.mptcpi_bytes_received, ret,
s.last_sample.mptcpi_bytes_received - ret);
if (s.last_sample.mptcpi_bytes_acked != ret)
xerror("mptcpi_bytes_acked %" PRIu64 ", expect %" PRIu64
", diff %" PRId64,
s.last_sample.mptcpi_bytes_acked, ret,
s.last_sample.mptcpi_bytes_acked - ret);
}
close(fd);
}
static int xaccept(int s)
{
int fd = accept(s, NULL, 0);
if (fd < 0)
die_perror("accept");
return fd;
}
static int server(int pipefd)
{
int fd = -1, r;
switch (pf) {
case AF_INET:
fd = sock_listen_mptcp("127.0.0.1", "15432");
break;
case AF_INET6:
fd = sock_listen_mptcp("::1", "15432");
break;
default:
xerror("Unknown pf %d\n", pf);
break;
}
r = write(pipefd, "conn", 4);
assert(r == 4);
alarm(15);
r = xaccept(fd);
process_one_client(r, pipefd);
close(fd);
return 0;
}
static void test_ip_tos_sockopt(int fd)
{
uint8_t tos_in, tos_out;
socklen_t s;
int r;
tos_in = rand() & 0xfc;
r = setsockopt(fd, SOL_IP, IP_TOS, &tos_in, sizeof(tos_out));
if (r != 0)
die_perror("setsockopt IP_TOS");
tos_out = 0;
s = sizeof(tos_out);
r = getsockopt(fd, SOL_IP, IP_TOS, &tos_out, &s);
if (r != 0)
die_perror("getsockopt IP_TOS");
if (tos_in != tos_out)
xerror("tos %x != %x socklen_t %d\n", tos_in, tos_out, s);
if (s != 1)
xerror("tos should be 1 byte");
s = 0;
r = getsockopt(fd, SOL_IP, IP_TOS, &tos_out, &s);
if (r != 0)
die_perror("getsockopt IP_TOS 0");
if (s != 0)
xerror("expect socklen_t == 0");
s = -1;
r = getsockopt(fd, SOL_IP, IP_TOS, &tos_out, &s);
if (r != -1 && errno != EINVAL)
die_perror("getsockopt IP_TOS did not indicate -EINVAL");
if (s != -1)
xerror("expect socklen_t == -1");
}
static int client(int pipefd)
{
int fd = -1;
alarm(15);
switch (pf) {
case AF_INET:
fd = sock_connect_mptcp("127.0.0.1", "15432", IPPROTO_MPTCP);
break;
case AF_INET6:
fd = sock_connect_mptcp("::1", "15432", IPPROTO_MPTCP);
break;
default:
xerror("Unknown pf %d\n", pf);
}
test_ip_tos_sockopt(fd);
connect_one_server(fd, pipefd);
return 0;
}
static pid_t xfork(void)
{
pid_t p = fork();
if (p < 0)
die_perror("fork");
return p;
}
static int rcheck(int wstatus, const char *what)
{
if (WIFEXITED(wstatus)) {
if (WEXITSTATUS(wstatus) == 0)
return 0;
fprintf(stderr, "%s exited, status=%d\n", what, WEXITSTATUS(wstatus));
return WEXITSTATUS(wstatus);
} else if (WIFSIGNALED(wstatus)) {
xerror("%s killed by signal %d\n", what, WTERMSIG(wstatus));
} else if (WIFSTOPPED(wstatus)) {
xerror("%s stopped by signal %d\n", what, WSTOPSIG(wstatus));
}
return 111;
}
static void init_rng(void)
{
int fd = open("/dev/urandom", O_RDONLY);
if (fd >= 0) {
unsigned int foo;
ssize_t ret;
/* can't fail */
ret = read(fd, &foo, sizeof(foo));
assert(ret == sizeof(foo));
close(fd);
srand(foo);
} else {
srand(time(NULL));
}
}
int main(int argc, char *argv[])
{
int e1, e2, wstatus;
pid_t s, c, ret;
int pipefds[2];
parse_opts(argc, argv);
init_rng();
e1 = pipe(pipefds);
if (e1 < 0)
die_perror("pipe");
s = xfork();
if (s == 0) {
close(pipefds[0]);
ret = server(pipefds[1]);
close(pipefds[1]);
return ret;
}
close(pipefds[1]);
/* wait until server bound a socket */
e1 = read(pipefds[0], &e1, 4);
assert(e1 == 4);
c = xfork();
if (c == 0)
return client(pipefds[0]);
close(pipefds[0]);
ret = waitpid(s, &wstatus, 0);
if (ret == -1)
die_perror("waitpid");
e1 = rcheck(wstatus, "server");
ret = waitpid(c, &wstatus, 0);
if (ret == -1)
die_perror("waitpid");
e2 = rcheck(wstatus, "client");
return e1 ? e1 : e2;
}
@@ -0,0 +1,371 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Double quotes to prevent globbing and word splitting is recommended in new
# code but we accept it, especially because there were too many before having
# address all other issues detected by shellcheck.
#shellcheck disable=SC2086
. "$(dirname "${0}")/mptcp_lib.sh"
ret=0
sin=""
sout=""
cin=""
cout=""
timeout_poll=30
timeout_test=$((timeout_poll * 2 + 1))
iptables="iptables"
ip6tables="ip6tables"
ns1=""
ns2=""
ns_sbox=""
usage() {
echo "Usage: $0 [ -i ] [ -h ]"
echo -e "\t-i: use 'ip mptcp' instead of 'pm_nl_ctl'"
echo -e "\t-h: help"
}
while getopts "hi" option;do
case "$option" in
"h")
usage "$0"
exit ${KSFT_PASS}
;;
"i")
mptcp_lib_set_ip_mptcp
;;
"?")
usage "$0"
exit ${KSFT_FAIL}
;;
esac
done
add_mark_rules()
{
local ns=$1
local m=$2
local t
for t in ${iptables} ${ip6tables}; do
# just to debug: check we have multiple subflows connection requests
ip netns exec $ns $t -A OUTPUT -p tcp --syn -m mark --mark $m -j ACCEPT
# RST packets might be handled by a internal dummy socket
ip netns exec $ns $t -A OUTPUT -p tcp --tcp-flags RST RST -m mark --mark 0 -j ACCEPT
ip netns exec $ns $t -A OUTPUT -p tcp -m mark --mark $m -j ACCEPT
ip netns exec $ns $t -A OUTPUT -p tcp -m mark --mark 0 -j DROP
done
}
init()
{
mptcp_lib_ns_init ns1 ns2 ns_sbox
local i
for i in $(seq 1 4); do
ip link add ns1eth$i netns "$ns1" type veth peer name ns2eth$i netns "$ns2"
ip -net "$ns1" addr add 10.0.$i.1/24 dev ns1eth$i
ip -net "$ns1" addr add dead:beef:$i::1/64 dev ns1eth$i nodad
ip -net "$ns1" link set ns1eth$i up
ip -net "$ns2" addr add 10.0.$i.2/24 dev ns2eth$i
ip -net "$ns2" addr add dead:beef:$i::2/64 dev ns2eth$i nodad
ip -net "$ns2" link set ns2eth$i up
# let $ns2 reach any $ns1 address from any interface
ip -net "$ns2" route add default via 10.0.$i.1 dev ns2eth$i metric 10$i
mptcp_lib_pm_nl_add_endpoint "${ns1}" "10.0.${i}.1" flags signal
mptcp_lib_pm_nl_add_endpoint "${ns1}" "dead:beef:${i}::1" flags signal
mptcp_lib_pm_nl_add_endpoint "${ns2}" "10.0.${i}.2" flags signal
mptcp_lib_pm_nl_add_endpoint "${ns2}" "dead:beef:${i}::2" flags signal
done
mptcp_lib_pm_nl_set_limits "${ns1}" 8 8
mptcp_lib_pm_nl_set_limits "${ns2}" 8 8
add_mark_rules $ns1 1
add_mark_rules $ns2 2
}
# This function is used in the cleanup trap
#shellcheck disable=SC2317,SC2329
cleanup()
{
mptcp_lib_ns_exit "${ns1}" "${ns2}" "${ns_sbox}"
rm -f "$cin" "$cout"
rm -f "$sin" "$sout"
}
mptcp_lib_check_mptcp
mptcp_lib_check_kallsyms
mptcp_lib_check_tools ip "${iptables}" "${ip6tables}"
check_mark()
{
local ns=$1
local af=$2
local tables=${iptables}
if [ $af -eq 6 ];then
tables=${ip6tables}
fi
local counters values
counters=$(ip netns exec $ns $tables -v -L OUTPUT | grep DROP)
values=${counters%DROP*}
local v
for v in $values; do
if [ $v -ne 0 ]; then
mptcp_lib_pr_fail "got $tables $values in ns $ns," \
"not 0 - not all expected packets marked"
ret=${KSFT_FAIL}
return 1
fi
done
return 0
}
print_title()
{
mptcp_lib_print_title "${@}"
}
do_transfer()
{
local listener_ns="$1"
local connector_ns="$2"
local cl_proto="$3"
local srv_proto="$4"
local connect_addr="$5"
local port=12001
:> "$cout"
:> "$sout"
local mptcp_connect="./mptcp_connect -r 20"
local local_addr ip
if mptcp_lib_is_v6 "${connect_addr}"; then
local_addr="::"
ip=ipv6
else
local_addr="0.0.0.0"
ip=ipv4
fi
cmsg="TIMESTAMPNS"
if mptcp_lib_kallsyms_has "mptcp_ioctl$"; then
cmsg+=",TCPINQ"
fi
mptcp_lib_nstat_init "${listener_ns}"
mptcp_lib_nstat_init "${connector_ns}"
ip netns exec ${listener_ns} \
$mptcp_connect -t ${timeout_poll} -l -M 1 -p $port -s ${srv_proto} -c "${cmsg}" \
${local_addr} < "$sin" > "$sout" &
local spid=$!
mptcp_lib_wait_local_port_listen "${listener_ns}" "${port}"
ip netns exec ${connector_ns} \
$mptcp_connect -t ${timeout_poll} -M 2 -p $port -s ${cl_proto} -c "${cmsg}" \
$connect_addr < "$cin" > "$cout" &
local cpid=$!
mptcp_lib_wait_timeout "${timeout_test}" "${listener_ns}" \
"${connector_ns}" "${port}" "${cpid}" "${spid}" &
local timeout_pid=$!
wait $cpid
local retc=$?
wait $spid
local rets=$?
if kill -0 $timeout_pid; then
# Finished before the timeout: kill the background job
mptcp_lib_kill_group_wait $timeout_pid
timeout_pid=0
fi
mptcp_lib_nstat_get "${listener_ns}"
mptcp_lib_nstat_get "${connector_ns}"
print_title "Transfer ${ip:2}"
if [ ${rets} -ne 0 ] || [ ${retc} -ne 0 ] || [ ${timeout_pid} -ne 0 ]; then
mptcp_lib_pr_fail "client exit code $retc, server $rets"
mptcp_lib_pr_err_stats "${listener_ns}" "${connector_ns}" "${port}"
mptcp_lib_result_fail "transfer ${ip}"
ret=${KSFT_FAIL}
return 1
fi
if ! mptcp_lib_check_transfer $cin $sout "file received by server"; then
rets=1
else
mptcp_lib_pr_ok
fi
mptcp_lib_result_code "${rets}" "transfer ${ip}"
print_title "Mark ${ip:2}"
if [ $local_addr = "::" ];then
check_mark $listener_ns 6 || retc=1
check_mark $connector_ns 6 || retc=1
else
check_mark $listener_ns 4 || retc=1
check_mark $connector_ns 4 || retc=1
fi
mptcp_lib_result_code "${retc}" "mark ${ip}"
if [ $retc -eq 0 ] && [ $rets -eq 0 ];then
mptcp_lib_pr_ok
return 0
fi
mptcp_lib_pr_fail
return 1
}
make_file()
{
local name=$1
local who=$2
local size=$3
mptcp_lib_make_file $name 1024 $size
echo "Created $name (size $size KB) containing data sent by $who"
}
do_mptcp_sockopt_tests()
{
local lret=0
if ! mptcp_lib_kallsyms_has "mptcp_diag_fill_info$"; then
mptcp_lib_pr_skip "MPTCP sockopt not supported"
mptcp_lib_result_skip "sockopt"
return
fi
ip netns exec "$ns_sbox" ./mptcp_sockopt
lret=$?
print_title "SOL_MPTCP sockopt v4"
if [ $lret -ne 0 ]; then
mptcp_lib_pr_fail
mptcp_lib_result_fail "sockopt v4"
ret=$lret
return
fi
mptcp_lib_pr_ok
mptcp_lib_result_pass "sockopt v4"
ip netns exec "$ns_sbox" ./mptcp_sockopt -6
lret=$?
print_title "SOL_MPTCP sockopt v6"
if [ $lret -ne 0 ]; then
mptcp_lib_pr_fail
mptcp_lib_result_fail "sockopt v6"
ret=$lret
return
fi
mptcp_lib_pr_ok
mptcp_lib_result_pass "sockopt v6"
}
run_tests()
{
local listener_ns="$1"
local connector_ns="$2"
local connect_addr="$3"
local lret=0
do_transfer ${listener_ns} ${connector_ns} MPTCP MPTCP ${connect_addr}
lret=$?
if [ $lret -ne 0 ]; then
ret=$lret
return
fi
}
do_tcpinq_test()
{
print_title "TCP_INQ cmsg/ioctl $*"
ip netns exec "$ns_sbox" ./mptcp_inq "$@"
local lret=$?
if [ $lret -ne 0 ];then
ret=$lret
mptcp_lib_pr_fail
mptcp_lib_result_fail "TCP_INQ: $*"
return $lret
fi
mptcp_lib_pr_ok
mptcp_lib_result_pass "TCP_INQ: $*"
return $lret
}
do_tcpinq_tests()
{
local lret=0
if ! mptcp_lib_kallsyms_has "mptcp_ioctl$"; then
mptcp_lib_pr_skip "TCP_INQ not supported"
mptcp_lib_result_skip "TCP_INQ"
return
fi
local args
for args in "-t tcp" "-r tcp"; do
do_tcpinq_test $args
lret=$?
if [ $lret -ne 0 ] ; then
return $lret
fi
do_tcpinq_test -6 $args
lret=$?
if [ $lret -ne 0 ] ; then
return $lret
fi
done
do_tcpinq_test -r tcp -t tcp
return $?
}
sin=$(mktemp)
sout=$(mktemp)
cin=$(mktemp)
cout=$(mktemp)
trap cleanup EXIT
init
make_file "$cin" "client" 1
make_file "$sin" "server" 1
mptcp_lib_subtests_last_ts_reset
run_tests $ns1 $ns2 10.0.1.1
run_tests $ns1 $ns2 dead:beef:1::1
do_mptcp_sockopt_tests
do_tcpinq_tests
mptcp_lib_result_print_all_tap
exit $ret
+298
View File
@@ -0,0 +1,298 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
. "$(dirname "${0}")/mptcp_lib.sh"
ret=0
usage() {
echo "Usage: $0 [ -i ] [ -h ]"
echo -e "\t-i: use 'ip mptcp' instead of 'pm_nl_ctl'"
echo -e "\t-h: help"
}
optstring=hi
while getopts "$optstring" option;do
case "$option" in
"h")
usage "$0"
exit ${KSFT_PASS}
;;
"i")
mptcp_lib_set_ip_mptcp
;;
"?")
usage "$0"
exit ${KSFT_FAIL}
;;
esac
done
ns1=""
err=$(mktemp)
# This function is used in the cleanup trap
#shellcheck disable=SC2317,SC2329
cleanup()
{
rm -f "${err}"
mptcp_lib_ns_exit "${ns1}"
}
mptcp_lib_check_mptcp
mptcp_lib_check_tools ip
trap cleanup EXIT
mptcp_lib_ns_init ns1
format_limits() {
local accept="${1}"
local subflows="${2}"
if mptcp_lib_is_ip_mptcp; then
# with a space at the end
printf "add_addr_accepted %d subflows %d \n" "${accept}" "${subflows}"
else
printf "accept %d\nsubflows %d\n" "${accept}" "${subflows}"
fi
}
get_limits() {
if mptcp_lib_is_ip_mptcp; then
ip -n "${ns1}" mptcp limits
else
ip netns exec "${ns1}" ./pm_nl_ctl limits
fi
}
get_limits_nb() {
if mptcp_lib_is_ip_mptcp; then
ip -n "${ns1}" mptcp limits | awk '{ print $2" "$4 }'
else
ip netns exec "${ns1}" ./pm_nl_ctl limits | \
awk '{ printf "%s ", $2 }'
fi
}
format_endpoints() {
mptcp_lib_pm_nl_format_endpoints "${@}"
}
# This function is invoked indirectly
#shellcheck disable=SC2317,SC2329
get_endpoint() {
mptcp_lib_pm_nl_get_endpoint "${ns1}" "${@}"
}
change_address() {
local addr=${1}
local flags=${2}
if mptcp_lib_is_ip_mptcp; then
ip -n "${ns1}" mptcp endpoint change "${addr}" "${flags}"
else
ip netns exec "${ns1}" ./pm_nl_ctl set "${addr}" flags "${flags}"
fi
}
set_limits()
{
mptcp_lib_pm_nl_set_limits "${ns1}" "${@}"
}
add_endpoint()
{
mptcp_lib_pm_nl_add_endpoint "${ns1}" "${@}"
}
del_endpoint()
{
mptcp_lib_pm_nl_del_endpoint "${ns1}" "${@}"
}
flush_endpoint()
{
mptcp_lib_pm_nl_flush_endpoint "${ns1}"
}
show_endpoints()
{
mptcp_lib_pm_nl_show_endpoints "${ns1}"
}
change_endpoint()
{
mptcp_lib_pm_nl_change_endpoint "${ns1}" "${@}"
}
check()
{
local cmd="$1"
local expected="$2"
local msg="$3"
local exp_error="$4"
local rc=0
mptcp_lib_print_title "$msg"
mptcp_lib_check_output "${err}" "${cmd}" "${expected}" "${exp_error}" ||
rc=${?}
if [ ${rc} -eq 2 ]; then
mptcp_lib_result_fail "${msg} # error ${rc}"
ret=${KSFT_FAIL}
elif [ ${rc} -eq 0 ]; then
mptcp_lib_print_ok "[ OK ]"
mptcp_lib_result_pass "${msg}"
elif [ ${rc} -eq 1 ]; then
mptcp_lib_result_fail "${msg} # different output"
ret=${KSFT_FAIL}
fi
}
mptcp_lib_subtests_last_ts_reset
check "show_endpoints" "" "defaults addr list"
default_limits="$(get_limits)"
if mptcp_lib_expect_all_features; then
check "get_limits" "$(format_limits 0 2)" "defaults limits"
fi
add_endpoint 10.0.1.1
add_endpoint 10.0.1.2 flags subflow dev lo
add_endpoint 10.0.1.3 flags signal,backup
check "get_endpoint 1" "$(format_endpoints "1,10.0.1.1")" "simple add/get addr"
check "show_endpoints" \
"$(format_endpoints "1,10.0.1.1" \
"2,10.0.1.2,subflow,lo" \
"3,10.0.1.3,signal backup")" "dump addrs"
del_endpoint 2
check "get_endpoint 2" "" "simple del addr" 1
check "show_endpoints" \
"$(format_endpoints "1,10.0.1.1" \
"3,10.0.1.3,signal backup")" "dump addrs after del"
add_endpoint 10.0.1.2 id 2
add_endpoint 10.0.1.3 2>/dev/null
check "get_endpoint 4" "" "duplicate addr" 1
add_endpoint 10.0.1.4 flags signal
check "get_endpoint 4" "$(format_endpoints "4,10.0.1.4,signal")" "id addr increment"
read -r -a default_limits_nb <<< "$(get_limits_nb)"
# limits have been increased: from 8 to 64 for subflows/add_addr & 255 for endp
if mptcp_lib_expect_all_features || set_limits 9 9 2>/dev/null; then
max_endp=255
max_limits=64
else
max_endp=8
max_limits=8
fi
set_limits "${default_limits_nb[@]}"
for i in $(seq 5 ${max_endp}); do
add_endpoint "10.0.0.${i}" id "${i}"
done
check "get_endpoint ${max_endp}" \
"$(format_endpoints "${max_endp},10.0.0.${max_endp}")" "id limit"
if add_endpoint '10.0.0.1' &>/dev/null; then
hardlimit="no error"
else
hardlimit="error"
fi
check "echo ${hardlimit}" "error" "above hard addr limit"
flush_endpoint
check "show_endpoints" "" "flush addrs"
# "unknown" flag is only supported by pm_nl_ctl
if ! mptcp_lib_is_ip_mptcp; then
add_endpoint 10.0.1.1 flags unknown
check "show_endpoints" "$(format_endpoints "1,10.0.1.1")" \
"ignore unknown flags"
flush_endpoint
fi
set_limits $((max_limits + 1)) 1 2>/dev/null
check "get_limits" "${default_limits}" "rcv addrs above hard limit"
set_limits 1 $((max_limits + 1)) 2>/dev/null
check "get_limits" "${default_limits}" "subflows above hard limit"
set_limits ${max_limits} ${max_limits}
flush_endpoint ## to make sure it doesn't affect the limits
check "get_limits" "$(format_limits ${max_limits} ${max_limits})" "set limits"
flush_endpoint
add_endpoint 10.0.1.1
add_endpoint 10.0.1.2
add_endpoint 10.0.1.3 id 100
add_endpoint 10.0.1.4
add_endpoint 10.0.1.5 id 254
add_endpoint 10.0.1.6
add_endpoint 10.0.1.7
add_endpoint 10.0.1.8
check "show_endpoints" \
"$(format_endpoints "1,10.0.1.1" \
"2,10.0.1.2" \
"3,10.0.1.7" \
"4,10.0.1.8" \
"100,10.0.1.3" \
"101,10.0.1.4" \
"254,10.0.1.5" \
"255,10.0.1.6")" "set ids"
flush_endpoint
add_endpoint 10.0.0.1
add_endpoint 10.0.0.2 id 254
add_endpoint 10.0.0.3
add_endpoint 10.0.0.4
add_endpoint 10.0.0.5 id 253
add_endpoint 10.0.0.6
add_endpoint 10.0.0.7
add_endpoint 10.0.0.8
check "show_endpoints" \
"$(format_endpoints "1,10.0.0.1" \
"2,10.0.0.4" \
"3,10.0.0.6" \
"4,10.0.0.7" \
"5,10.0.0.8" \
"253,10.0.0.5" \
"254,10.0.0.2" \
"255,10.0.0.3")" "wrap-around ids"
flush_endpoint
add_endpoint 10.0.1.1 flags subflow
change_address 10.0.1.1 backup
check "show_endpoints" "$(format_endpoints "1,10.0.1.1,subflow backup")" \
"set flags (backup)"
change_address 10.0.1.1 nobackup
check "show_endpoints" "$(format_endpoints "1,10.0.1.1,subflow")" \
" (nobackup)"
# fullmesh support has been added later
change_endpoint 1 fullmesh 2>/dev/null
if show_endpoints | grep -q "fullmesh" ||
mptcp_lib_expect_all_features; then
check "show_endpoints" "$(format_endpoints "1,10.0.1.1,subflow fullmesh")" \
" (fullmesh)"
change_endpoint 1 nofullmesh
check "show_endpoints" "$(format_endpoints "1,10.0.1.1,subflow")" \
" (nofullmesh)"
change_endpoint 1 backup,fullmesh
check "show_endpoints" "$(format_endpoints "1,10.0.1.1,subflow backup fullmesh")" \
" (backup,fullmesh)"
else
for st in fullmesh nofullmesh backup,fullmesh; do
st=" (${st})"
mptcp_lib_print_title "${st}"
mptcp_lib_pr_skip
mptcp_lib_result_skip "${st}"
done
fi
mptcp_lib_result_print_all_tap
exit $ret
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
timeout=1800
@@ -0,0 +1,315 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Double quotes to prevent globbing and word splitting is recommended in new
# code but we accept it, especially because there were too many before having
# address all other issues detected by shellcheck.
#shellcheck disable=SC2086
. "$(dirname "${0}")/mptcp_lib.sh"
ns1=""
ns2=""
ns3=""
capture=false
timeout_poll=30
timeout_test=$((timeout_poll * 2 + 1))
# a bit more space: because we have more to display
MPTCP_LIB_TEST_FORMAT="%02u %-60s"
ret=0
bail=0
slack=50
large=""
small=""
sout=""
cout=""
capout=""
size=0
usage() {
echo "Usage: $0 [ -b ] [ -c ] [ -d ] [ -i]"
echo -e "\t-b: bail out after first error, otherwise runs all testcases"
echo -e "\t-c: capture packets for each test using tcpdump (default: no capture)"
echo -e "\t-d: debug this script"
echo -e "\t-i: use 'ip mptcp' instead of 'pm_nl_ctl'"
}
# This function is used in the cleanup trap
#shellcheck disable=SC2317,SC2329
cleanup()
{
rm -f "$cout" "$sout"
rm -f "$large" "$small"
rm -f "$capout"
mptcp_lib_ns_exit "${ns1}" "${ns2}" "${ns3}"
}
mptcp_lib_check_mptcp
mptcp_lib_check_tools ip tc
# "$ns1" ns2 ns3
# ns1eth1 ns2eth1 ns2eth3 ns3eth1
# netem
# ns1eth2 ns2eth2
# netem
setup()
{
large=$(mktemp)
small=$(mktemp)
sout=$(mktemp)
cout=$(mktemp)
capout=$(mktemp)
size=$((2 * 2048 * 4096))
dd if=/dev/zero of=$small bs=4096 count=20 >/dev/null 2>&1
dd if=/dev/zero of=$large bs=4096 count=$((size / 4096)) >/dev/null 2>&1
trap cleanup EXIT
mptcp_lib_ns_init ns1 ns2 ns3
ip link add ns1eth1 netns "$ns1" type veth peer name ns2eth1 netns "$ns2"
ip link add ns1eth2 netns "$ns1" type veth peer name ns2eth2 netns "$ns2"
ip link add ns2eth3 netns "$ns2" type veth peer name ns3eth1 netns "$ns3"
ip -net "$ns1" addr add 10.0.1.1/24 dev ns1eth1
ip -net "$ns1" addr add dead:beef:1::1/64 dev ns1eth1 nodad
ip -net "$ns1" link set ns1eth1 up mtu 1500 gso_max_segs 0
ip -net "$ns1" route add default via 10.0.1.2
ip -net "$ns1" route add default via dead:beef:1::2
ip -net "$ns1" addr add 10.0.2.1/24 dev ns1eth2
ip -net "$ns1" addr add dead:beef:2::1/64 dev ns1eth2 nodad
ip -net "$ns1" link set ns1eth2 up mtu 1500 gso_max_segs 0
ip -net "$ns1" route add default via 10.0.2.2 metric 101
ip -net "$ns1" route add default via dead:beef:2::2 metric 101
mptcp_lib_pm_nl_set_limits "${ns1}" 1 1
mptcp_lib_pm_nl_add_endpoint "${ns1}" 10.0.2.1 dev ns1eth2 flags subflow
ip -net "$ns2" addr add 10.0.1.2/24 dev ns2eth1
ip -net "$ns2" addr add dead:beef:1::2/64 dev ns2eth1 nodad
ip -net "$ns2" link set ns2eth1 up mtu 1500 gso_max_segs 0
ip -net "$ns2" addr add 10.0.2.2/24 dev ns2eth2
ip -net "$ns2" addr add dead:beef:2::2/64 dev ns2eth2 nodad
ip -net "$ns2" link set ns2eth2 up mtu 1500 gso_max_segs 0
ip -net "$ns2" addr add 10.0.3.2/24 dev ns2eth3
ip -net "$ns2" addr add dead:beef:3::2/64 dev ns2eth3 nodad
ip -net "$ns2" link set ns2eth3 up mtu 1500 gso_max_segs 0
ip netns exec "$ns2" sysctl -q net.ipv4.ip_forward=1
ip netns exec "$ns2" sysctl -q net.ipv6.conf.all.forwarding=1
ip -net "$ns3" addr add 10.0.3.3/24 dev ns3eth1
ip -net "$ns3" addr add dead:beef:3::3/64 dev ns3eth1 nodad
ip -net "$ns3" link set ns3eth1 up mtu 1500 gso_max_segs 0
ip -net "$ns3" route add default via 10.0.3.2
ip -net "$ns3" route add default via dead:beef:3::2
mptcp_lib_pm_nl_set_limits "${ns3}" 1 1
# debug build can slow down measurably the test program
# we use quite tight time limit on the run-time, to ensure
# maximum B/W usage.
# Use kmemleak/lockdep/kasan/prove_locking presence as a rough
# estimate for this being a debug kernel and increase the
# maximum run-time accordingly. Observed run times for CI builds
# running selftests, including kbuild, were used to determine the
# amount of time to add.
grep -q ' kmemleak_init$\| lockdep_init$\| kasan_init$\| prove_locking$' /proc/kallsyms && slack=$((slack+550))
}
do_transfer()
{
local cin=$1
local sin=$2
local max_time=$3
local port
port=$((10000+MPTCP_LIB_TEST_COUNTER))
:> "$cout"
:> "$sout"
:> "$capout"
if $capture; then
local capuser
local rndh="${ns1:4}"
if [ -z $SUDO_USER ] ; then
capuser=""
else
capuser="-Z $SUDO_USER"
fi
local capfile="${rndh}-${port}"
local capopt="-i any -s 65535 -B 32768 ${capuser}"
ip netns exec ${ns3} tcpdump ${capopt} -w "${capfile}-listener.pcap" >> "${capout}" 2>&1 &
local cappid_listener=$!
ip netns exec ${ns1} tcpdump ${capopt} -w "${capfile}-connector.pcap" >> "${capout}" 2>&1 &
local cappid_connector=$!
sleep 1
fi
mptcp_lib_nstat_init "${ns3}"
mptcp_lib_nstat_init "${ns1}"
ip netns exec ${ns3} \
./mptcp_connect -jt ${timeout_poll} -l -p $port -T $max_time \
0.0.0.0 < "$sin" > "$sout" &
local spid=$!
mptcp_lib_wait_local_port_listen "${ns3}" "${port}"
ip netns exec ${ns1} \
./mptcp_connect -jt ${timeout_poll} -p $port -T $max_time \
10.0.3.3 < "$cin" > "$cout" &
local cpid=$!
mptcp_lib_wait_timeout "${timeout_test}" "${ns3}" "${ns1}" "${port}" \
"${cpid}" "${spid}" &
local timeout_pid=$!
wait $cpid
local retc=$?
wait $spid
local rets=$?
if kill -0 $timeout_pid; then
# Finished before the timeout: kill the background job
mptcp_lib_kill_group_wait $timeout_pid
timeout_pid=0
fi
if $capture; then
sleep 1
kill ${cappid_listener}
kill ${cappid_connector}
fi
mptcp_lib_nstat_get "${ns3}"
mptcp_lib_nstat_get "${ns1}"
cmp $sin $cout > /dev/null 2>&1
local cmps=$?
cmp $cin $sout > /dev/null 2>&1
local cmpc=$?
if [ $retc -eq 0 ] && [ $rets -eq 0 ] &&
[ $cmpc -eq 0 ] && [ $cmps -eq 0 ] &&
[ $timeout_pid -eq 0 ]; then
printf "%-16s" " max $max_time "
mptcp_lib_pr_ok
cat "$capout"
return 0
fi
mptcp_lib_pr_fail "client exit code $retc, server $rets"
mptcp_lib_pr_err_stats "${ns3}" "${ns1}" "${port}"
ls -l $sin $cout
ls -l $cin $sout
cat "$capout"
return 1
}
run_test()
{
local rate1=$1
local rate2=$2
local delay1=$3
local delay2=$4
local limit1=$5
local limit2=$6
local lret
local dev
shift 6
local msg=$*
[ $delay1 -gt 0 ] && delay1="delay ${delay1}ms" || delay1=""
[ $delay2 -gt 0 ] && delay2="delay ${delay2}ms" || delay2=""
for dev in ns1eth1 ns1eth2; do
tc -n $ns1 qdisc del dev $dev root >/dev/null 2>&1
done
for dev in ns2eth1 ns2eth2; do
tc -n $ns2 qdisc del dev $dev root >/dev/null 2>&1
done
# keep the queued pkts number low, or the RTT estimator will see
# increasing latency over time.
tc -n $ns1 qdisc add dev ns1eth1 root netem rate ${rate1}mbit $delay1 limit ${limit1}
tc -n $ns1 qdisc add dev ns1eth2 root netem rate ${rate2}mbit $delay2 limit ${limit2}
tc -n $ns2 qdisc add dev ns2eth1 root netem rate ${rate1}mbit $delay1 limit ${limit1}
tc -n $ns2 qdisc add dev ns2eth2 root netem rate ${rate2}mbit $delay2 limit ${limit2}
# time is measured in ms, account for transfer size, aggregated link speed
# and header overhead (10%)
# ms byte -> bit 10% mbit -> kbit -> bit 10%
local time=$((1000 * size * 8 * 10 / ((rate1 + rate2) * 1000 * 1000 * 9) ))
# mptcp_connect will do some sleeps to allow the mp_join handshake
# completion (see mptcp_connect): 200ms on each side, add some slack
time=$((time + 400 + slack))
mptcp_lib_print_title "$msg"
do_transfer $small $large $time
lret=$?
mptcp_lib_result_code "${lret}" "${msg}"
if [ $lret -ne 0 ] && ! mptcp_lib_subtest_is_flaky; then
ret=$lret
[ $bail -eq 0 ] || exit $ret
fi
msg+=" - reverse direction"
mptcp_lib_print_title "${msg}"
do_transfer $large $small $time
lret=$?
mptcp_lib_result_code "${lret}" "${msg}"
if [ $lret -ne 0 ] && ! mptcp_lib_subtest_is_flaky; then
ret=$lret
[ $bail -eq 0 ] || exit $ret
fi
}
while getopts "bcdhi" option;do
case "$option" in
"h")
usage $0
exit ${KSFT_PASS}
;;
"b")
bail=1
;;
"c")
capture=true
;;
"d")
set -x
;;
"i")
mptcp_lib_set_ip_mptcp
;;
"?")
usage $0
exit ${KSFT_FAIL}
;;
esac
done
setup
mptcp_lib_subtests_last_ts_reset
run_test 10 10 0 0 20 20 "balanced bwidth"
run_test 10 10 1 25 20 50 "balanced bwidth with unbalanced delay"
# we still need some additional infrastructure to pass the following test-cases
MPTCP_LIB_SUBTEST_FLAKY=1 run_test 10 3 0 0 30 20 "unbalanced bwidth"
run_test 10 3 1 25 40 30 "unbalanced bwidth with unbalanced delay"
run_test 10 3 25 1 50 30 "unbalanced bwidth with opposed, unbalanced delay"
mptcp_lib_result_print_all_tap
exit $ret
@@ -0,0 +1,945 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
# Double quotes to prevent globbing and word splitting is recommended in new
# code but we accept it.
#shellcheck disable=SC2086
# Some variables are used below but indirectly, see verify_*_event()
#shellcheck disable=SC2034
. "$(dirname "${0}")/mptcp_lib.sh"
mptcp_lib_check_mptcp
mptcp_lib_check_kallsyms
if ! mptcp_lib_has_file '/proc/sys/net/mptcp/pm_type'; then
echo "userspace pm tests are not supported by the kernel: SKIP"
exit ${KSFT_SKIP}
fi
mptcp_lib_check_tools ip
ANNOUNCED=${MPTCP_LIB_EVENT_ANNOUNCED}
REMOVED=${MPTCP_LIB_EVENT_REMOVED}
SUB_ESTABLISHED=${MPTCP_LIB_EVENT_SUB_ESTABLISHED}
SUB_CLOSED=${MPTCP_LIB_EVENT_SUB_CLOSED}
LISTENER_CREATED=${MPTCP_LIB_EVENT_LISTENER_CREATED}
LISTENER_CLOSED=${MPTCP_LIB_EVENT_LISTENER_CLOSED}
AF_INET=${MPTCP_LIB_AF_INET}
AF_INET6=${MPTCP_LIB_AF_INET6}
file=""
server_evts=""
client_evts=""
server_evts_pid=0
client_evts_pid=0
client4_pid=0
server4_pid=0
client6_pid=0
server6_pid=0
client4_token=""
server4_token=""
client6_token=""
server6_token=""
client4_port=0;
client6_port=0;
app4_port=50002
new4_port=50003
app6_port=50004
client_addr_id=${RANDOM:0:2}
server_addr_id=${RANDOM:0:2}
ns1=""
ns2=""
ret=0
test_name=""
# a bit more space: because we have more to display
MPTCP_LIB_TEST_FORMAT="%02u %-68s"
print_title()
{
mptcp_lib_pr_info "${1}"
}
# $1: test name
print_test()
{
test_name="${1}"
mptcp_lib_print_title "${test_name}"
}
test_pass()
{
mptcp_lib_pr_ok
mptcp_lib_result_pass "${test_name}"
}
test_skip()
{
mptcp_lib_pr_skip
mptcp_lib_result_skip "${test_name}"
}
# $1: msg
test_fail()
{
if [ ${#} -gt 0 ]
then
mptcp_lib_pr_fail "${@}"
fi
ret=${KSFT_FAIL}
mptcp_lib_result_fail "${test_name}"
}
# This function is used in the cleanup trap
#shellcheck disable=SC2317,SC2329
cleanup()
{
print_title "Cleanup"
# Terminate the MPTCP connection and related processes
local pid
for pid in $client4_pid $server4_pid $client6_pid $server6_pid\
$server_evts_pid $client_evts_pid
do
mptcp_lib_kill_wait $pid
done
mptcp_lib_ns_exit "${ns1}" "${ns2}"
rm -rf $file $client_evts $server_evts
mptcp_lib_pr_info "Done"
}
trap cleanup EXIT
# Create and configure network namespaces for testing
print_title "Init"
mptcp_lib_ns_init ns1 ns2
# check path_manager and pm_type sysctl mapping
if [ -f /proc/sys/net/mptcp/path_manager ]; then
ip netns exec "$ns1" sysctl -q net.mptcp.path_manager=userspace
pm_type="$(ip netns exec "$ns1" sysctl -n net.mptcp.pm_type)"
if [ "${pm_type}" != "1" ]; then
test_fail "unexpected pm_type: ${pm_type}"
mptcp_lib_result_print_all_tap
exit ${KSFT_FAIL}
fi
ip netns exec "$ns1" sysctl -q net.mptcp.path_manager=error 2>/dev/null
pm_type="$(ip netns exec "$ns1" sysctl -n net.mptcp.pm_type)"
if [ "${pm_type}" != "1" ]; then
test_fail "unexpected pm_type after error: ${pm_type}"
mptcp_lib_result_print_all_tap
exit ${KSFT_FAIL}
fi
ip netns exec "$ns1" sysctl -q net.mptcp.pm_type=0
pm_name="$(ip netns exec "$ns1" sysctl -n net.mptcp.path_manager)"
if [ "${pm_name}" != "kernel" ]; then
test_fail "unexpected path-manager: ${pm_name}"
mptcp_lib_result_print_all_tap
exit ${KSFT_FAIL}
fi
fi
for i in "$ns1" "$ns2" ;do
ip netns exec "$i" sysctl -q net.mptcp.pm_type=1
done
# "$ns1" ns2
# ns1eth2 ns2eth1
ip link add ns1eth2 netns "$ns1" type veth peer name ns2eth1 netns "$ns2"
# Add IPv4/v6 addresses to the namespaces
ip -net "$ns1" addr add 10.0.1.1/24 dev ns1eth2
ip -net "$ns1" addr add 10.0.2.1/24 dev ns1eth2
ip -net "$ns1" addr add dead:beef:1::1/64 dev ns1eth2 nodad
ip -net "$ns1" addr add dead:beef:2::1/64 dev ns1eth2 nodad
ip -net "$ns1" link set ns1eth2 up
ip -net "$ns2" addr add 10.0.1.2/24 dev ns2eth1
ip -net "$ns2" addr add 10.0.2.2/24 dev ns2eth1
ip -net "$ns2" addr add dead:beef:1::2/64 dev ns2eth1 nodad
ip -net "$ns2" addr add dead:beef:2::2/64 dev ns2eth1 nodad
ip -net "$ns2" link set ns2eth1 up
file=$(mktemp)
mptcp_lib_make_file "$file" 2 1
# Capture netlink events over the two network namespaces running
# the MPTCP client and server
client_evts=$(mktemp)
mptcp_lib_events "${ns2}" "${client_evts}" client_evts_pid
server_evts=$(mktemp)
mptcp_lib_events "${ns1}" "${server_evts}" server_evts_pid
sleep 0.5
mptcp_lib_subtests_last_ts_reset
print_test "Created network namespaces ns1, ns2"
test_pass
make_connection()
{
local is_v6=$1
local app_port=$app4_port
local connect_addr="10.0.1.1"
local client_addr="10.0.1.2"
local listen_addr="0.0.0.0"
if [ "$is_v6" = "v6" ]
then
connect_addr="dead:beef:1::1"
client_addr="dead:beef:1::2"
listen_addr="::"
app_port=$app6_port
else
is_v6="v4"
fi
# set this on the client side only: will not affect the rest
ip netns exec "$ns2" sysctl -q net.mptcp.allow_join_initial_addr_port=0
:>"$client_evts"
:>"$server_evts"
# Run the server
ip netns exec "$ns1" \
./mptcp_connect -s MPTCP -w 300 -p $app_port -l $listen_addr > /dev/null 2>&1 &
local server_pid=$!
mptcp_lib_wait_local_port_listen "${ns1}" "${app_port}"
# Run the client, transfer $file and stay connected to the server
# to conduct tests
ip netns exec "$ns2" \
./mptcp_connect -s MPTCP -w 300 -m sendfile -p $app_port $connect_addr\
2>&1 > /dev/null < "$file" &
local client_pid=$!
sleep 1
# Capture client/server attributes from MPTCP connection netlink events
local client_token
local client_port
local client_serverside
local client_nojoin
local server_token
local server_serverside
local server_nojoin
client_token=$(mptcp_lib_evts_get_info token "$client_evts")
client_port=$(mptcp_lib_evts_get_info sport "$client_evts")
client_serverside=$(mptcp_lib_evts_get_info server_side "$client_evts")
client_nojoin=$(mptcp_lib_evts_get_info deny_join_id0 "$client_evts")
server_token=$(mptcp_lib_evts_get_info token "$server_evts")
server_serverside=$(mptcp_lib_evts_get_info server_side "$server_evts")
server_nojoin=$(mptcp_lib_evts_get_info deny_join_id0 "$server_evts")
print_test "Established IP${is_v6} MPTCP Connection ns2 => ns1"
if [ "${client_token}" != "" ] && [ "${server_token}" != "" ] &&
[ "${client_serverside:-0}" = 0 ] && [ "${server_serverside:-0}" = 1 ] &&
[ "${client_nojoin:-0}" = 0 ] && [ "${server_nojoin:-0}" = 1 ]
then
test_pass
print_title "Connection info: ${client_addr}:${client_port} -> ${connect_addr}:${app_port}"
else
test_fail "Expected tokens (c:${client_token} - s:${server_token}), server (c:${client_serverside} - s:${server_serverside}), nojoin (c:${client_nojoin} - s:${server_nojoin})"
mptcp_lib_result_print_all_tap
exit ${KSFT_FAIL}
fi
if [ "$is_v6" = "v6" ]
then
client6_token=$client_token
server6_token=$server_token
client6_port=$client_port
client6_pid=$client_pid
server6_pid=$server_pid
else
client4_token=$client_token
server4_token=$server_token
client4_port=$client_port
client4_pid=$client_pid
server4_pid=$server_pid
fi
}
# $@: all var names to check
check_expected()
{
if mptcp_lib_check_expected "${@}"
then
test_pass
return 0
fi
test_fail
return 1
}
verify_announce_event()
{
local evt=$1
local e_type=$2
local e_token=$3
local e_addr=$4
local e_id=$5
local e_dport=$6
local e_af=$7
local type
local token
local addr
local dport
local id
type=$(mptcp_lib_evts_get_info type "$evt" $e_type)
token=$(mptcp_lib_evts_get_info token "$evt" $e_type)
if [ "$e_af" = "v6" ]
then
addr=$(mptcp_lib_evts_get_info daddr6 "$evt" $e_type)
else
addr=$(mptcp_lib_evts_get_info daddr4 "$evt" $e_type)
fi
dport=$(mptcp_lib_evts_get_info dport "$evt" $e_type)
id=$(mptcp_lib_evts_get_info rem_id "$evt" $e_type)
check_expected "type" "token" "addr" "dport" "id"
}
test_announce()
{
print_title "Announce tests"
# Capture events on the network namespace running the server
:>"$server_evts"
# ADD_ADDR using an invalid token should result in no action
local invalid_token=$(( client4_token - 1))
ip netns exec "$ns2" ./pm_nl_ctl ann 10.0.2.2 token $invalid_token id\
$client_addr_id dev ns2eth1 > /dev/null 2>&1
local type
type=$(mptcp_lib_evts_get_info type "$server_evts")
print_test "ADD_ADDR 10.0.2.2 (ns2) => ns1, invalid token"
if [ "$type" = "" ]
then
test_pass
else
test_fail "type defined: ${type}"
fi
# ADD_ADDR from the client to server machine reusing the subflow port
:>"$server_evts"
ip netns exec "$ns2"\
./pm_nl_ctl ann 10.0.2.2 token "$client4_token" id $client_addr_id dev\
ns2eth1
print_test "ADD_ADDR id:client 10.0.2.2 (ns2) => ns1, reuse port"
sleep 0.5
verify_announce_event $server_evts $ANNOUNCED $server4_token "10.0.2.2" $client_addr_id \
"$client4_port"
# ADD_ADDR6 from the client to server machine reusing the subflow port
:>"$server_evts"
ip netns exec "$ns2" ./pm_nl_ctl ann\
dead:beef:2::2 token "$client6_token" id $client_addr_id dev ns2eth1
print_test "ADD_ADDR6 id:client dead:beef:2::2 (ns2) => ns1, reuse port"
sleep 0.5
verify_announce_event "$server_evts" "$ANNOUNCED" "$server6_token" "dead:beef:2::2"\
"$client_addr_id" "$client6_port" "v6"
# ADD_ADDR from the client to server machine using a new port
:>"$server_evts"
client_addr_id=$((client_addr_id+1))
ip netns exec "$ns2" ./pm_nl_ctl ann 10.0.2.2 token "$client4_token" id\
$client_addr_id dev ns2eth1 port $new4_port
print_test "ADD_ADDR id:client+1 10.0.2.2 (ns2) => ns1, new port"
sleep 0.5
verify_announce_event "$server_evts" "$ANNOUNCED" "$server4_token" "10.0.2.2"\
"$client_addr_id" "$new4_port"
# Capture events on the network namespace running the client
:>"$client_evts"
# ADD_ADDR from the server to client machine reusing the subflow port
ip netns exec "$ns1" ./pm_nl_ctl ann 10.0.2.1 token "$server4_token" id\
$server_addr_id dev ns1eth2
print_test "ADD_ADDR id:server 10.0.2.1 (ns1) => ns2, reuse port"
sleep 0.5
verify_announce_event "$client_evts" "$ANNOUNCED" "$client4_token" "10.0.2.1"\
"$server_addr_id" "$app4_port"
# ADD_ADDR6 from the server to client machine reusing the subflow port
:>"$client_evts"
ip netns exec "$ns1" ./pm_nl_ctl ann dead:beef:2::1 token "$server6_token" id\
$server_addr_id dev ns1eth2
print_test "ADD_ADDR6 id:server dead:beef:2::1 (ns1) => ns2, reuse port"
sleep 0.5
verify_announce_event "$client_evts" "$ANNOUNCED" "$client6_token" "dead:beef:2::1"\
"$server_addr_id" "$app6_port" "v6"
# ADD_ADDR from the server to client machine using a new port
:>"$client_evts"
server_addr_id=$((server_addr_id+1))
ip netns exec "$ns1" ./pm_nl_ctl ann 10.0.2.1 token "$server4_token" id\
$server_addr_id dev ns1eth2 port $new4_port
print_test "ADD_ADDR id:server+1 10.0.2.1 (ns1) => ns2, new port"
sleep 0.5
verify_announce_event "$client_evts" "$ANNOUNCED" "$client4_token" "10.0.2.1"\
"$server_addr_id" "$new4_port"
}
verify_remove_event()
{
local evt=$1
local e_type=$2
local e_token=$3
local e_id=$4
local type
local token
local id
type=$(mptcp_lib_evts_get_info type "$evt" $e_type)
token=$(mptcp_lib_evts_get_info token "$evt" $e_type)
id=$(mptcp_lib_evts_get_info rem_id "$evt" $e_type)
check_expected "type" "token" "id"
}
test_remove()
{
print_title "Remove tests"
# Capture events on the network namespace running the server
:>"$server_evts"
# RM_ADDR using an invalid token should result in no action
local invalid_token=$(( client4_token - 1 ))
ip netns exec "$ns2" ./pm_nl_ctl rem token $invalid_token id\
$client_addr_id > /dev/null 2>&1
print_test "RM_ADDR id:client ns2 => ns1, invalid token"
local type
type=$(mptcp_lib_evts_get_info type "$server_evts")
if [ "$type" = "" ]
then
test_pass
else
test_fail "unexpected type: ${type}"
fi
# RM_ADDR using an invalid addr id should result in no action
local invalid_id=$(( client_addr_id + 1 ))
ip netns exec "$ns2" ./pm_nl_ctl rem token "$client4_token" id\
$invalid_id > /dev/null 2>&1
print_test "RM_ADDR id:client+1 ns2 => ns1, invalid id"
type=$(mptcp_lib_evts_get_info type "$server_evts")
if [ "$type" = "" ]
then
test_pass
else
test_fail "unexpected type: ${type}"
fi
# RM_ADDR from the client to server machine
:>"$server_evts"
ip netns exec "$ns2" ./pm_nl_ctl rem token "$client4_token" id\
$client_addr_id
print_test "RM_ADDR id:client ns2 => ns1"
sleep 0.5
verify_remove_event "$server_evts" "$REMOVED" "$server4_token" "$client_addr_id"
# RM_ADDR from the client to server machine
:>"$server_evts"
client_addr_id=$(( client_addr_id - 1 ))
ip netns exec "$ns2" ./pm_nl_ctl rem token "$client4_token" id\
$client_addr_id
print_test "RM_ADDR id:client-1 ns2 => ns1"
sleep 0.5
verify_remove_event "$server_evts" "$REMOVED" "$server4_token" "$client_addr_id"
# RM_ADDR6 from the client to server machine
:>"$server_evts"
ip netns exec "$ns2" ./pm_nl_ctl rem token "$client6_token" id\
$client_addr_id
print_test "RM_ADDR6 id:client-1 ns2 => ns1"
sleep 0.5
verify_remove_event "$server_evts" "$REMOVED" "$server6_token" "$client_addr_id"
# Capture events on the network namespace running the client
:>"$client_evts"
# RM_ADDR from the server to client machine
ip netns exec "$ns1" ./pm_nl_ctl rem token "$server4_token" id\
$server_addr_id
print_test "RM_ADDR id:server ns1 => ns2"
sleep 0.5
verify_remove_event "$client_evts" "$REMOVED" "$client4_token" "$server_addr_id"
# RM_ADDR from the server to client machine
:>"$client_evts"
server_addr_id=$(( server_addr_id - 1 ))
ip netns exec "$ns1" ./pm_nl_ctl rem token "$server4_token" id\
$server_addr_id
print_test "RM_ADDR id:server-1 ns1 => ns2"
sleep 0.5
verify_remove_event "$client_evts" "$REMOVED" "$client4_token" "$server_addr_id"
# RM_ADDR6 from the server to client machine
:>"$client_evts"
ip netns exec "$ns1" ./pm_nl_ctl rem token "$server6_token" id\
$server_addr_id
print_test "RM_ADDR6 id:server-1 ns1 => ns2"
sleep 0.5
verify_remove_event "$client_evts" "$REMOVED" "$client6_token" "$server_addr_id"
}
verify_subflow_events()
{
local evt=$1
local e_type=$2
local e_token=$3
local e_family=$4
local e_saddr=$5
local e_daddr=$6
local e_dport=$7
local e_locid=$8
local e_remid=$9
shift 2
local e_from=$8
local e_to=$9
local type
local token
local family
local saddr
local daddr
local dport
local locid
local remid
local info
local e_dport_txt
# only display the fixed ports
if [ "${e_dport}" -ge "${app4_port}" ] && [ "${e_dport}" -le "${app6_port}" ]; then
e_dport_txt=":${e_dport}"
fi
info="${e_saddr} (${e_from}) => ${e_daddr}${e_dport_txt} (${e_to})"
if [ "$e_type" = "$SUB_ESTABLISHED" ]
then
if [ "$e_family" = "$AF_INET6" ]
then
print_test "CREATE_SUBFLOW6 ${info}"
else
print_test "CREATE_SUBFLOW ${info}"
fi
else
if [ "$e_family" = "$AF_INET6" ]
then
print_test "DESTROY_SUBFLOW6 ${info}"
else
print_test "DESTROY_SUBFLOW ${info}"
fi
fi
type=$(mptcp_lib_evts_get_info type "$evt" $e_type)
token=$(mptcp_lib_evts_get_info token "$evt" $e_type)
family=$(mptcp_lib_evts_get_info family "$evt" $e_type)
dport=$(mptcp_lib_evts_get_info dport "$evt" $e_type)
locid=$(mptcp_lib_evts_get_info loc_id "$evt" $e_type)
remid=$(mptcp_lib_evts_get_info rem_id "$evt" $e_type)
if [ "$family" = "$AF_INET6" ]
then
saddr=$(mptcp_lib_evts_get_info saddr6 "$evt" $e_type)
daddr=$(mptcp_lib_evts_get_info daddr6 "$evt" $e_type)
else
saddr=$(mptcp_lib_evts_get_info saddr4 "$evt" $e_type)
daddr=$(mptcp_lib_evts_get_info daddr4 "$evt" $e_type)
fi
check_expected "type" "token" "daddr" "dport" "family" "saddr" "locid" "remid"
}
test_subflows()
{
print_title "Subflows v4 or v6 only tests"
# Capture events on the network namespace running the server
:>"$server_evts"
# Attempt to add a listener at 10.0.2.2:<subflow-port>
ip netns exec "$ns2" ./pm_nl_ctl listen 10.0.2.2\
"$client4_port" &
local listener_pid=$!
# ADD_ADDR from client to server machine reusing the subflow port
ip netns exec "$ns2" ./pm_nl_ctl ann 10.0.2.2 token "$client4_token" id\
$client_addr_id
sleep 0.5
# CREATE_SUBFLOW from server to client machine
:>"$server_evts"
ip netns exec "$ns1" ./pm_nl_ctl csf lip 10.0.2.1 lid 23 rip 10.0.2.2\
rport "$client4_port" token "$server4_token"
sleep 0.5
verify_subflow_events $server_evts $SUB_ESTABLISHED $server4_token $AF_INET "10.0.2.1" \
"10.0.2.2" "$client4_port" "23" "$client_addr_id" "ns1" "ns2"
# Delete the listener from the client ns, if one was created
mptcp_lib_kill_wait $listener_pid
local sport
sport=$(mptcp_lib_evts_get_info sport "$server_evts" $SUB_ESTABLISHED)
# DESTROY_SUBFLOW from server to client machine
:>"$server_evts"
ip netns exec "$ns1" ./pm_nl_ctl dsf lip 10.0.2.1 lport "$sport" rip 10.0.2.2 rport\
"$client4_port" token "$server4_token"
sleep 0.5
verify_subflow_events "$server_evts" "$SUB_CLOSED" "$server4_token" "$AF_INET" "10.0.2.1"\
"10.0.2.2" "$client4_port" "23" "$client_addr_id" "ns1" "ns2"
# RM_ADDR from client to server machine
ip netns exec "$ns2" ./pm_nl_ctl rem id $client_addr_id token\
"$client4_token"
sleep 0.5
# Attempt to add a listener at dead:beef:2::2:<subflow-port>
ip netns exec "$ns2" ./pm_nl_ctl listen dead:beef:2::2\
"$client6_port" &
listener_pid=$!
# ADD_ADDR6 from client to server machine reusing the subflow port
:>"$server_evts"
ip netns exec "$ns2" ./pm_nl_ctl ann dead:beef:2::2 token "$client6_token" id\
$client_addr_id
sleep 0.5
# CREATE_SUBFLOW6 from server to client machine
:>"$server_evts"
ip netns exec "$ns1" ./pm_nl_ctl csf lip dead:beef:2::1 lid 23 rip\
dead:beef:2::2 rport "$client6_port" token "$server6_token"
sleep 0.5
verify_subflow_events "$server_evts" "$SUB_ESTABLISHED" "$server6_token" "$AF_INET6"\
"dead:beef:2::1" "dead:beef:2::2" "$client6_port" "23"\
"$client_addr_id" "ns1" "ns2"
# Delete the listener from the client ns, if one was created
mptcp_lib_kill_wait $listener_pid
sport=$(mptcp_lib_evts_get_info sport "$server_evts" $SUB_ESTABLISHED)
# DESTROY_SUBFLOW6 from server to client machine
:>"$server_evts"
ip netns exec "$ns1" ./pm_nl_ctl dsf lip dead:beef:2::1 lport "$sport" rip\
dead:beef:2::2 rport "$client6_port" token "$server6_token"
sleep 0.5
verify_subflow_events "$server_evts" "$SUB_CLOSED" "$server6_token" "$AF_INET6"\
"dead:beef:2::1" "dead:beef:2::2" "$client6_port" "23"\
"$client_addr_id" "ns1" "ns2"
# RM_ADDR from client to server machine
ip netns exec "$ns2" ./pm_nl_ctl rem id $client_addr_id token\
"$client6_token"
sleep 0.5
# Attempt to add a listener at 10.0.2.2:<new-port>
ip netns exec "$ns2" ./pm_nl_ctl listen 10.0.2.2\
$new4_port &
listener_pid=$!
# ADD_ADDR from client to server machine using a new port
:>"$server_evts"
ip netns exec "$ns2" ./pm_nl_ctl ann 10.0.2.2 token "$client4_token" id\
$client_addr_id port $new4_port
sleep 0.5
# CREATE_SUBFLOW from server to client machine
:>"$server_evts"
ip netns exec "$ns1" ./pm_nl_ctl csf lip 10.0.2.1 lid 23 rip 10.0.2.2 rport\
$new4_port token "$server4_token"
sleep 0.5
verify_subflow_events "$server_evts" "$SUB_ESTABLISHED" "$server4_token" "$AF_INET"\
"10.0.2.1" "10.0.2.2" "$new4_port" "23"\
"$client_addr_id" "ns1" "ns2"
# Delete the listener from the client ns, if one was created
mptcp_lib_kill_wait $listener_pid
sport=$(mptcp_lib_evts_get_info sport "$server_evts" $SUB_ESTABLISHED)
# DESTROY_SUBFLOW from server to client machine
:>"$server_evts"
ip netns exec "$ns1" ./pm_nl_ctl dsf lip 10.0.2.1 lport "$sport" rip 10.0.2.2 rport\
$new4_port token "$server4_token"
sleep 0.5
verify_subflow_events "$server_evts" "$SUB_CLOSED" "$server4_token" "$AF_INET" "10.0.2.1"\
"10.0.2.2" "$new4_port" "23" "$client_addr_id" "ns1" "ns2"
# RM_ADDR from client to server machine
ip netns exec "$ns2" ./pm_nl_ctl rem id $client_addr_id token\
"$client4_token"
# Capture events on the network namespace running the client
:>"$client_evts"
# Attempt to add a listener at 10.0.2.1:<subflow-port>
ip netns exec "$ns1" ./pm_nl_ctl listen 10.0.2.1\
$app4_port &
listener_pid=$!
# ADD_ADDR from server to client machine reusing the subflow port
ip netns exec "$ns1" ./pm_nl_ctl ann 10.0.2.1 token "$server4_token" id\
$server_addr_id
sleep 0.5
# CREATE_SUBFLOW from client to server machine
:>"$client_evts"
ip netns exec "$ns2" ./pm_nl_ctl csf lip 10.0.2.2 lid 23 rip 10.0.2.1 rport\
$app4_port token "$client4_token"
sleep 0.5
verify_subflow_events $client_evts $SUB_ESTABLISHED $client4_token $AF_INET "10.0.2.2"\
"10.0.2.1" "$app4_port" "23" "$server_addr_id" "ns2" "ns1"
# Delete the listener from the server ns, if one was created
mptcp_lib_kill_wait $listener_pid
sport=$(mptcp_lib_evts_get_info sport "$client_evts" $SUB_ESTABLISHED)
# DESTROY_SUBFLOW from client to server machine
:>"$client_evts"
ip netns exec "$ns2" ./pm_nl_ctl dsf lip 10.0.2.2 lport "$sport" rip 10.0.2.1 rport\
$app4_port token "$client4_token"
sleep 0.5
verify_subflow_events "$client_evts" "$SUB_CLOSED" "$client4_token" "$AF_INET" "10.0.2.2"\
"10.0.2.1" "$app4_port" "23" "$server_addr_id" "ns2" "ns1"
# RM_ADDR from server to client machine
ip netns exec "$ns1" ./pm_nl_ctl rem id $server_addr_id token\
"$server4_token"
sleep 0.5
# Attempt to add a listener at dead:beef:2::1:<subflow-port>
ip netns exec "$ns1" ./pm_nl_ctl listen dead:beef:2::1\
$app6_port &
listener_pid=$!
# ADD_ADDR6 from server to client machine reusing the subflow port
:>"$client_evts"
ip netns exec "$ns1" ./pm_nl_ctl ann dead:beef:2::1 token "$server6_token" id\
$server_addr_id
sleep 0.5
# CREATE_SUBFLOW6 from client to server machine
:>"$client_evts"
ip netns exec "$ns2" ./pm_nl_ctl csf lip dead:beef:2::2 lid 23 rip\
dead:beef:2::1 rport $app6_port token "$client6_token"
sleep 0.5
verify_subflow_events "$client_evts" "$SUB_ESTABLISHED" "$client6_token"\
"$AF_INET6" "dead:beef:2::2"\
"dead:beef:2::1" "$app6_port" "23"\
"$server_addr_id" "ns2" "ns1"
# Delete the listener from the server ns, if one was created
mptcp_lib_kill_wait $listener_pid
sport=$(mptcp_lib_evts_get_info sport "$client_evts" $SUB_ESTABLISHED)
# DESTROY_SUBFLOW6 from client to server machine
:>"$client_evts"
ip netns exec "$ns2" ./pm_nl_ctl dsf lip dead:beef:2::2 lport "$sport" rip\
dead:beef:2::1 rport $app6_port token "$client6_token"
sleep 0.5
verify_subflow_events $client_evts $SUB_CLOSED $client6_token $AF_INET6 "dead:beef:2::2"\
"dead:beef:2::1" "$app6_port" "23" "$server_addr_id" "ns2" "ns1"
# RM_ADDR6 from server to client machine
ip netns exec "$ns1" ./pm_nl_ctl rem id $server_addr_id token\
"$server6_token"
sleep 0.5
# Attempt to add a listener at 10.0.2.1:<new-port>
ip netns exec "$ns1" ./pm_nl_ctl listen 10.0.2.1\
$new4_port &
listener_pid=$!
# ADD_ADDR from server to client machine using a new port
:>"$client_evts"
ip netns exec "$ns1" ./pm_nl_ctl ann 10.0.2.1 token "$server4_token" id\
$server_addr_id port $new4_port
sleep 0.5
# CREATE_SUBFLOW from client to server machine
:>"$client_evts"
ip netns exec "$ns2" ./pm_nl_ctl csf lip 10.0.2.2 lid 23 rip 10.0.2.1 rport\
$new4_port token "$client4_token"
sleep 0.5
verify_subflow_events "$client_evts" "$SUB_ESTABLISHED" "$client4_token" "$AF_INET"\
"10.0.2.2" "10.0.2.1" "$new4_port" "23" "$server_addr_id" "ns2" "ns1"
# Delete the listener from the server ns, if one was created
mptcp_lib_kill_wait $listener_pid
sport=$(mptcp_lib_evts_get_info sport "$client_evts" $SUB_ESTABLISHED)
# DESTROY_SUBFLOW from client to server machine
:>"$client_evts"
ip netns exec "$ns2" ./pm_nl_ctl dsf lip 10.0.2.2 lport "$sport" rip 10.0.2.1 rport\
$new4_port token "$client4_token"
sleep 0.5
verify_subflow_events "$client_evts" "$SUB_CLOSED" "$client4_token" "$AF_INET" "10.0.2.2"\
"10.0.2.1" "$new4_port" "23" "$server_addr_id" "ns2" "ns1"
# RM_ADDR from server to client machine
ip netns exec "$ns1" ./pm_nl_ctl rem id $server_addr_id token\
"$server4_token"
}
test_subflows_v4_v6_mix()
{
print_title "Subflows v4 and v6 mix tests"
# Attempt to add a listener at 10.0.2.1:<subflow-port>
ip netns exec "$ns1" ./pm_nl_ctl listen 10.0.2.1\
$app6_port &
local listener_pid=$!
# ADD_ADDR4 from server to client machine reusing the subflow port on
# the established v6 connection
:>"$client_evts"
ip netns exec "$ns1" ./pm_nl_ctl ann 10.0.2.1 token "$server6_token" id\
$server_addr_id dev ns1eth2
print_test "ADD_ADDR4 id:server 10.0.2.1 (ns1) => ns2, reuse port"
sleep 0.5
verify_announce_event "$client_evts" "$ANNOUNCED" "$client6_token" "10.0.2.1"\
"$server_addr_id" "$app6_port"
# CREATE_SUBFLOW from client to server machine
:>"$client_evts"
ip netns exec "$ns2" ./pm_nl_ctl csf lip 10.0.2.2 lid 23 rip 10.0.2.1 rport\
$app6_port token "$client6_token"
sleep 0.5
verify_subflow_events "$client_evts" "$SUB_ESTABLISHED" "$client6_token"\
"$AF_INET" "10.0.2.2" "10.0.2.1" "$app6_port" "23"\
"$server_addr_id" "ns2" "ns1"
# Delete the listener from the server ns, if one was created
mptcp_lib_kill_wait $listener_pid
sport=$(mptcp_lib_evts_get_info sport "$client_evts" $SUB_ESTABLISHED)
# DESTROY_SUBFLOW from client to server machine
:>"$client_evts"
ip netns exec "$ns2" ./pm_nl_ctl dsf lip 10.0.2.2 lport "$sport" rip 10.0.2.1 rport\
$app6_port token "$client6_token"
sleep 0.5
verify_subflow_events "$client_evts" "$SUB_CLOSED" "$client6_token" \
"$AF_INET" "10.0.2.2" "10.0.2.1" "$app6_port" "23"\
"$server_addr_id" "ns2" "ns1"
# RM_ADDR from server to client machine
ip netns exec "$ns1" ./pm_nl_ctl rem id $server_addr_id token\
"$server6_token"
sleep 0.5
}
test_prio()
{
print_title "Prio tests"
local count
# Send MP_PRIO signal from client to server machine
ip netns exec "$ns2" ./pm_nl_ctl set 10.0.1.2 port "$client4_port" flags backup token "$client4_token" rip 10.0.1.1 rport "$app4_port"
sleep 0.5
# Check TX
print_test "MP_PRIO TX"
count=$(mptcp_lib_get_counter "$ns2" "MPTcpExtMPPrioTx")
if [ -z "$count" ]; then
test_skip
elif [ $count != 1 ]; then
test_fail "Count != 1: ${count}"
else
test_pass
fi
# Check RX
print_test "MP_PRIO RX"
count=$(mptcp_lib_get_counter "$ns1" "MPTcpExtMPPrioRx")
if [ -z "$count" ]; then
test_skip
elif [ $count != 1 ]; then
test_fail "Count != 1: ${count}"
else
test_pass
fi
}
verify_listener_events()
{
if mptcp_lib_verify_listener_events "${@}"; then
test_pass
else
test_fail
fi
}
test_listener()
{
print_title "Listener tests"
if ! mptcp_lib_kallsyms_has "mptcp_event_pm_listener$"; then
print_test "LISTENER events"
test_skip
return
fi
# Capture events on the network namespace running the client
:>$client_evts
# Attempt to add a listener at 10.0.2.2:<subflow-port>
ip netns exec $ns2 ./pm_nl_ctl listen 10.0.2.2\
$client4_port &
local listener_pid=$!
sleep 0.5
print_test "CREATE_LISTENER 10.0.2.2 (client port)"
verify_listener_events $client_evts $LISTENER_CREATED $AF_INET 10.0.2.2 $client4_port
# ADD_ADDR from client to server machine reusing the subflow port
ip netns exec $ns2 ./pm_nl_ctl ann 10.0.2.2 token $client4_token id\
$client_addr_id
sleep 0.5
# CREATE_SUBFLOW from server to client machine
ip netns exec $ns1 ./pm_nl_ctl csf lip 10.0.2.1 lid 23 rip 10.0.2.2\
rport $client4_port token $server4_token
sleep 0.5
# Delete the listener from the client ns, if one was created
mptcp_lib_kill_wait $listener_pid
sleep 0.5
print_test "CLOSE_LISTENER 10.0.2.2 (client port)"
verify_listener_events $client_evts $LISTENER_CLOSED $AF_INET 10.0.2.2 $client4_port
}
print_title "Make connections"
make_connection
make_connection "v6"
print_title "Will be using address IDs ${client_addr_id} (client) and ${server_addr_id} (server)"
test_announce
test_remove
test_subflows
test_subflows_v4_v6_mix
test_prio
test_listener
mptcp_lib_result_print_all_tap
exit ${ret}