selftests: mptcp: join: validate extra bind cases

By design, an MPTCP connection will not accept extra subflows where no
MPTCP listening sockets can accept such requests.

In other words, it means that if the 'server' listens on a specific
address / device, it cannot accept MP_JOIN sent to a different address /
device. Except if there is another MPTCP listening socket accepting
them.

This is what the new tests are validating:

 - Forcing a bind on the main v4/v6 address, and checking that MP_JOIN
   to announced addresses are not accepted.

 - Also forcing a bind on the main v4/v6 address, but before, another
   listening socket is created to accept additional subflows. Note that
   'mptcpize run nc -l' -- or something else only doing: socket(MPTCP),
   bind(<IP>), listen(0) -- would be enough, but here mptcp_connect is
   reused not to depend on another tool just for that.

 - Same as the previous one, but using v6 link-local addresses: this is
   a bit particular because it is required to specify the outgoing
   network interface when connecting to a link-local address announced
   by the other peer. When using the routing rules, this doesn't work
   (the outgoing interface is not known) ; but it does work with a
   'laminar' endpoint having a specified interface.

Note that extra small modifications are needed for these tests to work:

 - mptcp_connect's check_getpeername_connect() check should strip the
   specified interface when comparing addresses.

 - With IPv6 link-local addresses, it is required to wait for them to
   be ready (no longer in 'tentative' mode) before using them, otherwise
   the bind() will not be allowed.

Link: https://github.com/multipath-tcp/mptcp_net-next/issues/591
Reviewed-by: Geliang Tang <geliang@kernel.org>
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
Link: https://patch.msgid.link/20251101-net-next-mptcp-fm-endp-nb-bind-v1-4-b4166772d6bb@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Matthieu Baerts (NGI0) 2025-11-01 18:56:54 +01:00 committed by Jakub Kicinski
parent 4a6220a453
commit 5c59df126b
2 changed files with 161 additions and 2 deletions

View File

@ -1064,6 +1064,8 @@ static void check_getpeername_connect(int fd)
socklen_t salen = sizeof(ss);
char a[INET6_ADDRSTRLEN];
char b[INET6_ADDRSTRLEN];
const char *iface;
size_t len;
if (getpeername(fd, (struct sockaddr *)&ss, &salen) < 0) {
perror("getpeername");
@ -1073,7 +1075,13 @@ static void check_getpeername_connect(int fd)
xgetnameinfo((struct sockaddr *)&ss, salen,
a, sizeof(a), b, sizeof(b));
if (strcmp(cfg_host, a) || strcmp(cfg_port, b))
iface = strchr(cfg_host, '%');
if (iface)
len = iface - cfg_host;
else
len = strlen(cfg_host) + 1;
if (strncmp(cfg_host, a, len) || strcmp(cfg_port, b))
fprintf(stderr, "%s: %s vs %s, %s vs %s\n", __func__,
cfg_host, a, cfg_port, b);
}

View File

@ -62,6 +62,7 @@ unset sflags
unset fastclose
unset fullmesh
unset speed
unset bind_addr
unset join_syn_rej
unset join_csum_ns1
unset join_csum_ns2
@ -645,6 +646,27 @@ wait_mpj()
done
}
wait_ll_ready()
{
local ns="${1}"
local i
for i in $(seq 50); do
ip -n "${ns}" -6 addr show scope link | grep "inet6 fe80" |
grep -qw "tentative" || break
sleep 0.1
done
}
get_ll_addr()
{
local ns="${1}"
local iface="${2}"
ip -n "${ns}" -6 addr show dev "${iface}" scope link |
grep "inet6 fe80" | sed 's#.*\(fe80::.*\)/.*#\1#'
}
kill_events_pids()
{
mptcp_lib_kill_wait $evts_ns1_pid
@ -951,6 +973,7 @@ do_transfer()
local FAILING_LINKS=${FAILING_LINKS:-""}
local fastclose=${fastclose:-""}
local speed=${speed:-"fast"}
local bind_addr=${bind_addr:-"::"}
local listener_in="${sin}"
local connector_in="${cin}"
port=$(get_port)
@ -1006,7 +1029,7 @@ do_transfer()
timeout ${timeout_test} \
ip netns exec ${listener_ns} \
./mptcp_connect -t ${timeout_poll} -l -p ${port} -s ${srv_proto} \
${extra_srv_args} "::" < "${listener_in}" > "${sout}" &
${extra_srv_args} "${bind_addr}" < "${listener_in}" > "${sout}" &
local spid=$!
mptcp_lib_wait_local_port_listen "${listener_ns}" "${port}"
@ -3229,6 +3252,133 @@ add_addr_ports_tests()
fi
}
bind_tests()
{
# bind to one address should not allow extra subflows to other addresses
if reset "bind main address v4, no join v4"; then
pm_nl_set_limits $ns1 0 2
pm_nl_set_limits $ns2 2 2
pm_nl_add_endpoint $ns1 10.0.2.1 flags signal
bind_addr="10.0.1.1" \
run_tests $ns1 $ns2 10.0.1.1
join_syn_tx=1 \
chk_join_nr 0 0 0
chk_add_nr 1 1
fi
# bind to one address should not allow extra subflows to other addresses
if reset "bind main address v6, no join v6"; then
pm_nl_set_limits $ns1 0 2
pm_nl_set_limits $ns2 2 2
pm_nl_add_endpoint $ns1 dead:beef:2::1 flags signal
bind_addr="dead:beef:1::1" \
run_tests $ns1 $ns2 dead:beef:1::1
join_syn_tx=1 \
chk_join_nr 0 0 0
chk_add_nr 1 1
fi
# multiple binds to allow extra subflows to other addresses
if reset "multiple bind to allow joins v4"; then
local extra_bind
pm_nl_set_limits $ns1 0 2
pm_nl_set_limits $ns2 2 2
pm_nl_add_endpoint $ns1 10.0.2.1 flags signal
# Launching another app listening on a different address
# Note: it could be a totally different app, e.g. nc, socat, ...
ip netns exec ${ns1} ./mptcp_connect -l -t -1 -p "$(get_port)" \
-s MPTCP 10.0.2.1 &
extra_bind=$!
bind_addr="10.0.1.1" \
run_tests $ns1 $ns2 10.0.1.1
chk_join_nr 1 1 1
chk_add_nr 1 1
kill ${extra_bind}
fi
# multiple binds to allow extra subflows to other addresses
if reset "multiple bind to allow joins v6"; then
local extra_bind
pm_nl_set_limits $ns1 0 2
pm_nl_set_limits $ns2 2 2
pm_nl_add_endpoint $ns1 dead:beef:2::1 flags signal
# Launching another app listening on a different address
# Note: it could be a totally different app, e.g. nc, socat, ...
ip netns exec ${ns1} ./mptcp_connect -l -t -1 -p "$(get_port)" \
-s MPTCP dead:beef:2::1 &
extra_bind=$!
bind_addr="dead:beef:1::1" \
run_tests $ns1 $ns2 dead:beef:1::1
chk_join_nr 1 1 1
chk_add_nr 1 1
kill ${extra_bind}
fi
# multiple binds to allow extra subflows to other addresses: v6 LL case
if reset "multiple bind to allow joins v6 link-local routing"; then
local extra_bind ns1ll1 ns1ll2
ns1ll1="$(get_ll_addr $ns1 ns1eth1)"
ns1ll2="$(get_ll_addr $ns1 ns1eth2)"
pm_nl_set_limits $ns1 0 2
pm_nl_set_limits $ns2 2 2
pm_nl_add_endpoint $ns1 "${ns1ll2}" flags signal
wait_ll_ready $ns1 # to be able to bind
wait_ll_ready $ns2 # also needed to bind on the client side
ip netns exec ${ns1} ./mptcp_connect -l -t -1 -p "$(get_port)" \
-s MPTCP "${ns1ll2}%ns1eth2" &
extra_bind=$!
bind_addr="${ns1ll1}%ns1eth1" \
run_tests $ns1 $ns2 "${ns1ll1}%ns2eth1"
# it is not possible to connect to the announced LL addr without
# specifying the outgoing interface.
join_connect_err=1 \
chk_join_nr 0 0 0
chk_add_nr 1 1
kill ${extra_bind}
fi
# multiple binds to allow extra subflows to v6 LL addresses: laminar
if reset "multiple bind to allow joins v6 link-local laminar" &&
continue_if mptcp_lib_kallsyms_has "mptcp_pm_get_endp_laminar_max$"; then
local extra_bind ns1ll1 ns1ll2 ns2ll2
ns1ll1="$(get_ll_addr $ns1 ns1eth1)"
ns1ll2="$(get_ll_addr $ns1 ns1eth2)"
ns2ll2="$(get_ll_addr $ns2 ns2eth2)"
pm_nl_set_limits $ns1 0 2
pm_nl_set_limits $ns2 2 2
pm_nl_add_endpoint $ns1 "${ns1ll2}" flags signal
pm_nl_add_endpoint $ns2 "${ns2ll2}" flags laminar dev ns2eth2
wait_ll_ready $ns1 # to be able to bind
wait_ll_ready $ns2 # also needed to bind on the client side
ip netns exec ${ns1} ./mptcp_connect -l -t -1 -p "$(get_port)" \
-s MPTCP "${ns1ll2}%ns1eth2" &
extra_bind=$!
bind_addr="${ns1ll1}%ns1eth1" \
run_tests $ns1 $ns2 "${ns1ll1}%ns2eth1"
chk_join_nr 1 1 1
chk_add_nr 1 1
kill ${extra_bind}
fi
}
syncookies_tests()
{
# single subflow, syncookies
@ -4183,6 +4333,7 @@ all_tests_sorted=(
M@mixed_tests
b@backup_tests
p@add_addr_ports_tests
B@bind_tests
k@syncookies_tests
S@checksum_tests
d@deny_join_id0_tests