mptcp: implemented OoO queue pruning

When moving incoming skbs in the msk receive queue and the latter
is above limits, prune it as needed quite alike what TCP is doing
at the subflow level. The main difference relies in the stop condition:
since MPTCP does not perform collapsing, it's better off dropping the
bare minimum to fit the (newer) incoming packet.

Signed-off-by: Paolo Abeni <pabeni@redhat.com>
Tested-by: Gang Yan <yangang@kylinos.cn>
Reviewed-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
Signed-off-by: Matthieu Baerts (NGI0) <matttbe@kernel.org>
Link: https://patch.msgid.link/20260807-net-next-mptcp-oooq-pruning-v3-7-dbc1eb853cc3@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Paolo Abeni 2026-08-07 15:49:07 +02:00 committed by Jakub Kicinski
parent 996643574c
commit e468d37118
3 changed files with 73 additions and 10 deletions

View File

@ -87,6 +87,7 @@ static const struct snmp_mib mptcp_snmp_list[] = {
SNMP_MIB_ITEM("WinProbe", MPTCP_MIB_WINPROBE),
SNMP_MIB_ITEM("BacklogDrop", MPTCP_MIB_BACKLOGDROP),
SNMP_MIB_ITEM("RcvPruned", MPTCP_MIB_RCVPRUNED),
SNMP_MIB_ITEM("OFOPruned", MPTCP_MIB_OFOPRUNED),
};
/* mptcp_mib_alloc - allocate percpu mib counters

View File

@ -90,6 +90,7 @@ enum linux_mptcp_mib_field {
MPTCP_MIB_WINPROBE, /* MPTCP-level zero window probe */
MPTCP_MIB_BACKLOGDROP, /* Backlog over memory limit */
MPTCP_MIB_RCVPRUNED, /* Dropped due to memory constraints */
MPTCP_MIB_OFOPRUNED, /* MPTCP-level OoO queue pruned */
__MPTCP_MIB_MAX
};

View File

@ -242,6 +242,65 @@ static bool mptcp_rcvbuf_grow(struct sock *sk, u32 newval)
return false;
}
/* "Inspired" from the TCP version; main difference: stop as soon as the MPTCP
* socket is under memory limit.
*/
static void mptcp_prune_ofo_queue(struct sock *sk,
const struct sk_buff *in_skb)
{
struct mptcp_sock *msk = mptcp_sk(sk);
struct rb_node *node, *prev;
bool pruned = false;
u64 mem;
if (RB_EMPTY_ROOT(&msk->out_of_order_queue))
return;
node = &msk->ooo_last_skb->rbnode;
do {
struct sk_buff *skb = rb_to_skb(node);
/* Stop pruning if the incoming skb would land in OoO tail. */
if (after64(MPTCP_SKB_CB(in_skb)->map_seq,
MPTCP_SKB_CB(skb)->map_seq))
break;
pruned = true;
prev = rb_prev(node);
rb_erase(node, &msk->out_of_order_queue);
mptcp_drop(sk, skb);
msk->ooo_last_skb = rb_to_skb(prev);
mem = (unsigned int)sk_rmem_alloc_get(sk);
if (mem <= sk->sk_rcvbuf)
break;
node = prev;
} while (node);
if (pruned)
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOPRUNED);
}
/* The stack can't drop packets for fallback socket at the msk level, or the
* stream will break.
*/
static bool mptcp_can_ingest(const struct sock *sk)
{
return unlikely(sk_rmem_alloc_get(sk) <= READ_ONCE(sk->sk_rcvbuf)) ||
__mptcp_check_fallback(mptcp_sk(sk));
}
static bool mptcp_try_rmem_schedule(struct sock *sk, const struct sk_buff *skb)
{
if (!mptcp_can_ingest(sk)) {
mptcp_prune_ofo_queue(sk, skb);
return mptcp_can_ingest(sk);
}
return true;
}
/* "inspired" by tcp_data_queue_ofo(), main differences:
* - use mptcp seqs
* - don't cope with sacks
@ -253,6 +312,12 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
u64 seq, end_seq, max_seq;
struct sk_buff *skb1;
if (!mptcp_try_rmem_schedule(sk, skb)) {
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED);
mptcp_drop(sk, skb);
return;
}
seq = MPTCP_SKB_CB(skb)->map_seq;
end_seq = MPTCP_SKB_CB(skb)->end_seq;
max_seq = atomic64_read(&msk->rcv_wnd_sent);
@ -387,19 +452,15 @@ static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb)
mptcp_borrow_fwdmem(sk, skb);
/* Can't drop packets for fallback socket this late, or the stream
* will break.
*/
if (unlikely(sk_rmem_alloc_get(sk) > READ_ONCE(sk->sk_rcvbuf)) &&
!__mptcp_check_fallback(msk)) {
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED);
mptcp_drop(sk, skb);
return false;
}
if (MPTCP_SKB_CB(skb)->map_seq == msk->ack_seq) {
/* in sequence */
insert:
if (!mptcp_try_rmem_schedule(sk, skb)) {
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_RCVPRUNED);
mptcp_drop(sk, skb);
return false;
}
msk->bytes_received += copy_len;
WRITE_ONCE(msk->ack_seq, msk->ack_seq + copy_len);
tail = skb_peek_tail(&sk->sk_receive_queue);