Merge branch 'mptcp-pm-in-kernel-increase-limits'

Matthieu Baerts says:

====================
mptcp: pm: in-kernel: increase limits

Allow switching from 8 to 64 for the maximum number of subflows and
accepted ADD_ADDR, and from 8 to 255 for the number of MPTCP endpoints.

The previous limit of 8 subflows makes sense in most cases. Using more
subflows will very likely *not* improve the situation, and could even
decrease the performances. But there are no technical limitations nor
performance impact to raise this limit, so let's do it: this will allow
people with very specific use-cases, and researchers to easily create
more subflows, and measure the performance impact by themselves.

- Patches 1-2: increase subflows and accepted ADD_ADDR limits.

- Patches 3-4: increase endpoints limit.

- Patches 5-7: validate the new limits: 64 subflows, 255 endpoints.

- Patch 8: selftests: use send()/recv() instead of sendto()/recvfrom().
====================

Link: https://patch.msgid.link/20260508-net-next-mptcp-pm-inc-limits-v1-0-c84e3fdf9b6a@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-05-11 18:01:39 -07:00
commit 6d596975c1
4 changed files with 121 additions and 53 deletions

View File

@ -30,6 +30,7 @@ struct pm_nl_pernet {
};
#define MPTCP_PM_ADDR_MAX 8
#define MPTCP_PM_SUBFLOWS_MAX 64
static struct pm_nl_pernet *pm_nl_get_pernet(const struct net *net)
{
@ -201,7 +202,8 @@ fill_remote_addr(struct mptcp_sock *msk, struct mptcp_addr_info *local,
static unsigned int
fill_remote_addresses_fullmesh(struct mptcp_sock *msk,
struct mptcp_addr_info *local,
struct mptcp_addr_info *addrs)
struct mptcp_addr_info *addrs,
int addrs_size)
{
u8 limit_extra_subflows = mptcp_pm_get_limit_extra_subflows(msk);
bool deny_id0 = READ_ONCE(msk->pm.remote_deny_join_id0);
@ -236,7 +238,8 @@ fill_remote_addresses_fullmesh(struct mptcp_sock *msk,
msk->pm.extra_subflows++;
i++;
if (msk->pm.extra_subflows >= limit_extra_subflows)
if (msk->pm.extra_subflows >= limit_extra_subflows ||
i == addrs_size)
break;
}
@ -248,7 +251,8 @@ fill_remote_addresses_fullmesh(struct mptcp_sock *msk,
*/
static unsigned int
fill_remote_addresses_vec(struct mptcp_sock *msk, struct mptcp_addr_info *local,
bool fullmesh, struct mptcp_addr_info *addrs)
bool fullmesh, struct mptcp_addr_info *addrs,
int addrs_size)
{
/* Non-fullmesh: fill in the single entry corresponding to the primary
* MPC subflow remote address, and return 1, corresponding to 1 entry.
@ -257,7 +261,7 @@ fill_remote_addresses_vec(struct mptcp_sock *msk, struct mptcp_addr_info *local,
return fill_remote_addr(msk, local, addrs);
/* Fullmesh endpoint: fill all possible remote addresses */
return fill_remote_addresses_fullmesh(msk, local, addrs);
return fill_remote_addresses_fullmesh(msk, local, addrs, addrs_size);
}
static struct mptcp_pm_addr_entry *
@ -410,7 +414,8 @@ static void mptcp_pm_create_subflow_or_signal_addr(struct mptcp_sock *msk)
else /* local_addr_used is not decr for ID 0 */
msk->pm.local_addr_used++;
nr = fill_remote_addresses_vec(msk, &local.addr, fullmesh, addrs);
nr = fill_remote_addresses_vec(msk, &local.addr, fullmesh,
addrs, ARRAY_SIZE(addrs));
if (nr == 0)
continue;
@ -447,6 +452,7 @@ static unsigned int
fill_local_addresses_vec_fullmesh(struct mptcp_sock *msk,
struct mptcp_addr_info *remote,
struct mptcp_pm_local *locals,
int locals_size,
bool c_flag_case)
{
u8 limit_extra_subflows = mptcp_pm_get_limit_extra_subflows(msk);
@ -488,7 +494,8 @@ fill_local_addresses_vec_fullmesh(struct mptcp_sock *msk,
msk->pm.extra_subflows++;
i++;
if (msk->pm.extra_subflows >= limit_extra_subflows)
if (msk->pm.extra_subflows >= limit_extra_subflows ||
i == locals_size)
break;
}
rcu_read_unlock();
@ -559,7 +566,8 @@ fill_local_laminar_endp(struct mptcp_sock *msk, struct mptcp_addr_info *remote,
static unsigned int
fill_local_addresses_vec_c_flag(struct mptcp_sock *msk,
struct mptcp_addr_info *remote,
struct mptcp_pm_local *locals)
struct mptcp_pm_local *locals,
int locals_size)
{
u8 limit_extra_subflows = mptcp_pm_get_limit_extra_subflows(msk);
struct pm_nl_pernet *pernet = pm_nl_get_pernet_from_msk(msk);
@ -586,7 +594,8 @@ fill_local_addresses_vec_c_flag(struct mptcp_sock *msk,
msk->pm.extra_subflows++;
i++;
if (msk->pm.extra_subflows >= limit_extra_subflows)
if (msk->pm.extra_subflows >= limit_extra_subflows ||
i == locals_size)
break;
}
@ -620,13 +629,14 @@ fill_local_address_any(struct mptcp_sock *msk, struct mptcp_addr_info *remote,
*/
static unsigned int
fill_local_addresses_vec(struct mptcp_sock *msk, struct mptcp_addr_info *remote,
struct mptcp_pm_local *locals)
struct mptcp_pm_local *locals, int locals_size)
{
bool c_flag_case = remote->id && mptcp_pm_add_addr_c_flag_case(msk);
/* If there is at least one MPTCP endpoint with a fullmesh flag */
if (mptcp_pm_get_endp_fullmesh_max(msk))
return fill_local_addresses_vec_fullmesh(msk, remote, locals,
locals_size,
c_flag_case);
/* If there is at least one MPTCP endpoint with a laminar flag */
@ -637,7 +647,8 @@ fill_local_addresses_vec(struct mptcp_sock *msk, struct mptcp_addr_info *remote,
* limits are used -- accepting no ADD_ADDR -- and use subflow endpoints
*/
if (c_flag_case)
return fill_local_addresses_vec_c_flag(msk, remote, locals);
return fill_local_addresses_vec_c_flag(msk, remote, locals,
locals_size);
/* No special case: fill in the single 'IPADDRANY' local address */
return fill_local_address_any(msk, remote, &locals[0]);
@ -672,7 +683,7 @@ static void mptcp_pm_nl_add_addr_received(struct mptcp_sock *msk)
/* connect to the specified remote address, using whatever
* local address the routing configuration will pick.
*/
nr = fill_local_addresses_vec(msk, &remote, locals);
nr = fill_local_addresses_vec(msk, &remote, locals, ARRAY_SIZE(locals));
if (nr == 0)
return;
@ -735,7 +746,7 @@ static int mptcp_pm_nl_append_new_local_addr(struct pm_nl_pernet *pernet,
*/
if (pernet->next_id == MPTCP_PM_MAX_ADDR_ID)
pernet->next_id = 1;
if (pernet->endpoints >= MPTCP_PM_ADDR_MAX) {
if (pernet->endpoints == MPTCP_PM_MAX_ADDR_ID) {
ret = -ERANGE;
goto out;
}
@ -1212,19 +1223,30 @@ int mptcp_pm_nl_del_addr_doit(struct sk_buff *skb, struct genl_info *info)
}
static void mptcp_pm_flush_addrs_and_subflows(struct mptcp_sock *msk,
struct list_head *rm_list)
struct list_head *rm_list,
struct mptcp_pm_addr_entry *entry)
{
struct mptcp_rm_list alist = { .nr = 0 }, slist = { .nr = 0 };
struct mptcp_pm_addr_entry *entry;
struct mptcp_rm_list alist, slist;
bool more;
list_for_each_entry(entry, rm_list, list) {
if (slist.nr < MPTCP_RM_IDS_MAX &&
mptcp_lookup_subflow_by_saddr(&msk->conn_list, &entry->addr))
again:
alist.nr = 0;
slist.nr = 0;
more = false;
entry = list_prepare_entry(entry, rm_list, list);
list_for_each_entry_continue(entry, rm_list, list) {
if (mptcp_lookup_subflow_by_saddr(&msk->conn_list, &entry->addr))
slist.ids[slist.nr++] = mptcp_endp_get_local_id(msk, &entry->addr);
if (alist.nr < MPTCP_RM_IDS_MAX &&
mptcp_remove_anno_list_by_saddr(msk, &entry->addr))
if (mptcp_remove_anno_list_by_saddr(msk, &entry->addr))
alist.ids[alist.nr++] = mptcp_endp_get_local_id(msk, &entry->addr);
if (slist.nr == MPTCP_RM_IDS_MAX ||
alist.nr == MPTCP_RM_IDS_MAX) {
more = !list_is_last(&entry->list, rm_list);
break;
}
}
spin_lock_bh(&msk->pm.lock);
@ -1235,9 +1257,14 @@ static void mptcp_pm_flush_addrs_and_subflows(struct mptcp_sock *msk,
if (slist.nr)
mptcp_pm_rm_subflow(msk, &slist);
/* Reset counters: maybe some subflows have been removed before */
bitmap_fill(msk->pm.id_avail_bitmap, MPTCP_PM_MAX_ADDR_ID + 1);
msk->pm.local_addr_used = 0;
if (!more) {
bitmap_fill(msk->pm.id_avail_bitmap, MPTCP_PM_MAX_ADDR_ID + 1);
msk->pm.local_addr_used = 0;
}
spin_unlock_bh(&msk->pm.lock);
if (more)
goto again;
}
static void mptcp_nl_flush_addrs_list(struct net *net,
@ -1254,7 +1281,7 @@ static void mptcp_nl_flush_addrs_list(struct net *net,
if (!mptcp_pm_is_userspace(msk)) {
lock_sock(sk);
mptcp_pm_flush_addrs_and_subflows(msk, rm_list);
mptcp_pm_flush_addrs_and_subflows(msk, rm_list, NULL);
release_sock(sk);
}
@ -1371,10 +1398,10 @@ static int parse_limit(struct genl_info *info, int id, unsigned int *limit)
return 0;
*limit = nla_get_u32(attr);
if (*limit > MPTCP_PM_ADDR_MAX) {
if (*limit > MPTCP_PM_SUBFLOWS_MAX) {
NL_SET_ERR_MSG_ATTR_FMT(info->extack, attr,
"limit greater than maximum (%u)",
MPTCP_PM_ADDR_MAX);
MPTCP_PM_SUBFLOWS_MAX);
return -EINVAL;
}
return 0;

View File

@ -63,6 +63,7 @@ unset fastclose
unset fullmesh
unset speed
unset bind_addr
unset ifaces_nr
unset join_syn_rej
unset join_csum_ns1
unset join_csum_ns2
@ -146,7 +147,7 @@ init_partial()
# ns1eth4 ns2eth4
local i
for i in $(seq 1 4); do
for i in $(seq 1 "${ifaces_nr:-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
@ -165,7 +166,7 @@ init_partial()
init_shapers()
{
local i
for i in $(seq 1 4); do
for i in $(seq 1 "${ifaces_nr:-4}"); do
tc -n $ns1 qdisc add dev ns1eth$i root netem rate 20mbit delay 1ms
tc -n $ns2 qdisc add dev ns2eth$i root netem rate 20mbit delay 1ms
done
@ -512,6 +513,19 @@ reset_with_tcp_filter()
fi
}
# For kernel supporting limits above 8
# $1: title ; $2,4: addrs limit ns1,2 ; $3,5: subflows limit ns1,2
reset_with_high_limits()
{
reset "${1}" || return 1
if ! pm_nl_set_limits "${ns1}" "${2}" "${3}" 2>/dev/null ||
! pm_nl_set_limits "${ns2}" "${4}" "${5}" 2>/dev/null; then
mark_as_skipped "unable to set the limits to ${*:2}"
return 1
fi
}
# $1: err msg
fail_test()
{
@ -3669,6 +3683,21 @@ fullmesh_tests()
chk_prio_nr 0 1 1 0
chk_rm_nr 0 1
fi
# fullmesh in 8x8 to create 63 additional subflows
if ifaces_nr=8 reset_with_high_limits "fullmesh 8x8" 64 64 64 64; then
# higher chance to lose ADD_ADDR: allow retransmissions
ip netns exec $ns1 sysctl -q net.mptcp.add_addr_timeout=1
local i
for i in $(seq 1 8); do
pm_nl_add_endpoint $ns2 10.0.$i.2 flags subflow,fullmesh
pm_nl_add_endpoint $ns1 10.0.$i.1 flags signal
done
speed=slow \
run_tests $ns1 $ns2 10.0.1.1
chk_join_nr 63 63 63
fi
}
fastclose_tests()

View File

@ -66,6 +66,15 @@ get_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 "${@}"
}
@ -164,6 +173,7 @@ 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
@ -171,25 +181,29 @@ 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"
for i in $(seq 5 9); do
add_endpoint "10.0.1.${i}" flags signal >/dev/null 2>&1
done
check "get_endpoint 9" "$(format_endpoints "9,10.0.1.9,signal")" "hard addr limit"
check "get_endpoint 10" "" "above hard addr limit" 1
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[@]}"
del_endpoint 9
for i in $(seq 10 255); do
add_endpoint 10.0.0.9 id "${i}"
del_endpoint "${i}"
for i in $(seq 5 ${max_endp}); do
add_endpoint "10.0.0.${i}" id "${i}"
done
check "show_endpoints" \
"$(format_endpoints "1,10.0.1.1" \
"3,10.0.1.3,signal backup" \
"4,10.0.1.4,signal" \
"5,10.0.1.5,signal" \
"6,10.0.1.6,signal" \
"7,10.0.1.7,signal" \
"8,10.0.1.8,signal")" "id limit"
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"
@ -202,15 +216,15 @@ if ! mptcp_lib_is_ip_mptcp; then
flush_endpoint
fi
set_limits 9 1 2>/dev/null
set_limits $((max_limits + 1)) 1 2>/dev/null
check "get_limits" "${default_limits}" "rcv addrs above hard limit"
set_limits 1 9 2>/dev/null
set_limits 1 $((max_limits + 1)) 2>/dev/null
check "get_limits" "${default_limits}" "subflows above hard limit"
set_limits 8 8
set_limits ${max_limits} ${max_limits}
flush_endpoint ## to make sure it doesn't affect the limits
check "get_limits" "$(format_limits 8 8)" "set limits"
check "get_limits" "$(format_limits ${max_limits} ${max_limits})" "set limits"
flush_endpoint
add_endpoint 10.0.1.1

View File

@ -217,8 +217,6 @@ static int capture_events(int fd, int event_group)
/* do a netlink command and, if max > 0, fetch the reply ; nh's size >1024B */
static int do_nl_req(int fd, struct nlmsghdr *nh, int len, int max)
{
struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
socklen_t addr_len;
void *data = nh;
int rem, ret;
int err = 0;
@ -230,15 +228,15 @@ static int do_nl_req(int fd, struct nlmsghdr *nh, int len, int max)
}
nh->nlmsg_len = len;
ret = sendto(fd, data, len, 0, (void *)&nladdr, sizeof(nladdr));
ret = send(fd, data, len, 0);
if (ret != len)
error(1, errno, "send netlink: %uB != %uB\n", ret, len);
addr_len = sizeof(nladdr);
rem = ret = recvfrom(fd, data, max, 0, (void *)&nladdr, &addr_len);
ret = recv(fd, data, max, 0);
if (ret < 0)
error(1, errno, "recv netlink: %uB\n", ret);
rem = ret;
/* Beware: the NLMSG_NEXT macro updates the 'rem' argument */
for (; NLMSG_OK(nh, rem); nh = NLMSG_NEXT(nh, rem)) {
if (nh->nlmsg_type == NLMSG_DONE)