* [PATCH mptcp-next v27 1/8] mptcp: fix TFO SIOCINQ overflow in mptcp_inq_hint
2026-11-14 14:00 [PATCH mptcp-next v27 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
@ 2026-11-14 14:00 ` Geliang Tang
2026-11-14 14:00 ` [PATCH mptcp-next v27 2/8] selftests: mptcp: connect: check TFO SIOCINQ overflow Geliang Tang
` (7 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Geliang Tang @ 2026-11-14 14:00 UTC (permalink / raw)
To: mptcp
From: Geliang Tang <tanggeliang@kylinos.cn>
Fix mptcp_inq_hint() to compute available bytes using mptcp_data_avail()
(bytes_received - bytes_consumed) instead of ack_seq minus the head skb's
map_seq.
On passive TFO connections, the TFO skb has map_seq set to -skb->len.
Without the removed __mptcp_fastopen_gen_msk_ackseq() to adjust the TFO
skb's map_seq, mptcp_inq_hint() computes "ack_seq - map_seq = iasn + len",
almost always >= INT_MAX, causing SIOCINQ/FIONREAD and TCP_CM_INQ to
report INT_MAX. This also breaks receive buffer autotuning, as
mptcp_rcv_space_adjust() uses mptcp_inq_hint() to measure consumed data.
The bogus hint prevents mptcp_rcvbuf_grow() from being called until the
entire TFO skb has been read.
Using mptcp_data_avail() avoids this by tracking bytes directly. This
change also affects non-TFO connections after DATA_FIN: with N bytes
unread, both SIOCINQ and TCP_CM_INQ now report N instead of N+1. This
differs from TCP, which reports N+1 because the FIN consumes a sequence
number. The EOF hint of 1 is preserved for the empty-queue case when
the socket is closed or shutdown.
Fixes: f03afb3aeb9d ("mptcp: drop __mptcp_fastopen_gen_msk_ackseq()")
Co-developed-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
net/mptcp/protocol.c | 18 ++++++------------
1 file changed, 6 insertions(+), 12 deletions(-)
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 50de5cedfd0f..f2644e7a6a05 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -2404,22 +2404,16 @@ static bool mptcp_move_skbs(struct sock *sk)
static unsigned int mptcp_inq_hint(const struct sock *sk)
{
const struct mptcp_sock *msk = mptcp_sk(sk);
- const struct sk_buff *skb;
+ u64 hint_val = mptcp_data_avail(msk);
- skb = skb_peek(&sk->sk_receive_queue);
- if (skb) {
- u64 hint_val = READ_ONCE(msk->ack_seq) - MPTCP_SKB_CB(skb)->map_seq;
+ if (hint_val >= INT_MAX)
+ return INT_MAX;
- if (hint_val >= INT_MAX)
- return INT_MAX;
-
- return (unsigned int)hint_val;
- }
-
- if (sk->sk_state == TCP_CLOSE || (sk->sk_shutdown & RCV_SHUTDOWN))
+ if (!hint_val &&
+ (sk->sk_state == TCP_CLOSE || (sk->sk_shutdown & RCV_SHUTDOWN)))
return 1;
- return 0;
+ return (unsigned int)hint_val;
}
static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH mptcp-next v27 2/8] selftests: mptcp: connect: check TFO SIOCINQ overflow
2026-11-14 14:00 [PATCH mptcp-next v27 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
2026-11-14 14:00 ` [PATCH mptcp-next v27 1/8] mptcp: fix TFO SIOCINQ overflow in mptcp_inq_hint Geliang Tang
@ 2026-11-14 14:00 ` Geliang Tang
2026-11-14 14:00 ` [PATCH mptcp-next v27 3/8] mptcp: fix fallback dummy mapping anchor Geliang Tang
` (6 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Geliang Tang @ 2026-11-14 14:00 UTC (permalink / raw)
To: mptcp
From: Geliang Tang <tanggeliang@kylinos.cn>
Add a SIOCINQ check in mptcp_connect's server mode when the MPTFO socket
option is enabled to detect TFO SIOCINQ overflow regressions.
When a TFO connection is accepted, the test waits for the
MPTCP_INFO_FLAG_REMOTE_KEY_RECEIVED flag using poll(), ensuring the third
ACK has been processed. Using poll() instead of usleep() allows immediate
detection of dead connections via POLLHUP/POLLERR, preventing infinite
loops when the connection is reset or the client exits early. The wait
is bounded by poll_timeout (default 10s). It then checks if SIOCINQ
returns INT_MAX, which would indicate the regression where mptcp_inq_hint()
incorrectly returns INT_MAX for passive TFO connections.
Add SOL_MPTCP fallback definition for compatibility with older libc
headers. Handle ENOPROTOOPT and EOPNOTSUPP errors from getsockopt() to
skip the wait for TCP sockets or MPTCP sockets that fell back to TCP.
Print a warning if the remote key does not arrive within the timeout, but
continue with the SIOCINQ check rather than failing the test.
Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
.../selftests/net/mptcp/mptcp_connect.c | 61 +++++++++++++++++++
1 file changed, 61 insertions(+)
diff --git a/tools/testing/selftests/net/mptcp/mptcp_connect.c b/tools/testing/selftests/net/mptcp/mptcp_connect.c
index 178d98d91fea..152976d39d46 100644
--- a/tools/testing/selftests/net/mptcp/mptcp_connect.c
+++ b/tools/testing/selftests/net/mptcp/mptcp_connect.c
@@ -34,6 +34,7 @@
#include <linux/time_types.h>
#include <linux/sockios.h>
#include <linux/compiler.h>
+#include <linux/mptcp.h>
extern int optind;
@@ -43,6 +44,9 @@ extern int optind;
#ifndef TCP_ULP
#define TCP_ULP 31
#endif
+#ifndef SOL_MPTCP
+#define SOL_MPTCP 284
+#endif
static int poll_timeout = 10 * 1000;
static bool listen_mode;
@@ -1176,6 +1180,47 @@ static void maybe_close(int fd)
close(fd);
}
+static int wait_for_remote_key(int fd)
+{
+ struct mptcp_info info;
+ int timeout_ms = 10;
+ struct pollfd pfd;
+ int elapsed = 0;
+ socklen_t olen;
+ int ret;
+
+ pfd.fd = fd;
+ pfd.events = 0;
+
+ while (elapsed < poll_timeout || poll_timeout < 0) {
+ ret = poll(&pfd, 1, timeout_ms);
+ if (ret < 0) {
+ perror("TFO: poll");
+ return -1;
+ }
+
+ if (pfd.revents & (POLLHUP | POLLERR)) {
+ fprintf(stderr, "TFO: connection closed or reset\n");
+ return -1;
+ }
+
+ olen = sizeof(info);
+ if (getsockopt(fd, SOL_MPTCP, MPTCP_INFO, &info, &olen) < 0) {
+ /* Not an MPTCP socket (TCP or fallback), skip wait */
+ if (errno == ENOPROTOOPT || errno == EOPNOTSUPP)
+ return 0;
+ perror("TFO: getsockopt(MPTCP_INFO)");
+ return -1;
+ }
+ if (info.mptcpi_flags & MPTCP_INFO_FLAG_REMOTE_KEY_RECEIVED)
+ return 0;
+
+ elapsed += timeout_ms;
+ }
+ fprintf(stderr, "TFO: timeout waiting for remote key\n");
+ return 0;
+}
+
int main_loop_s(int listensock)
{
struct sockaddr_storage ss;
@@ -1207,6 +1252,22 @@ int main_loop_s(int listensock)
check_sockaddr(pf, &ss, salen);
check_getpeername(remotesock, &ss, salen);
+ if (cfg_sockopt_types.mptfo) {
+ int avail;
+
+ if (wait_for_remote_key(remotesock) < 0) {
+ close(remotesock);
+ return 1;
+ }
+
+ if (ioctl(remotesock, SIOCINQ, &avail) == 0 &&
+ avail == INT_MAX) {
+ fprintf(stderr, "TFO SIOCINQ overflow\n");
+ close(remotesock);
+ return 1;
+ }
+ }
+
if (cfg_input) {
fd = open(cfg_input, O_RDONLY);
if (fd < 0)
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH mptcp-next v27 3/8] mptcp: fix fallback dummy mapping anchor
2026-11-14 14:00 [PATCH mptcp-next v27 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
2026-11-14 14:00 ` [PATCH mptcp-next v27 1/8] mptcp: fix TFO SIOCINQ overflow in mptcp_inq_hint Geliang Tang
2026-11-14 14:00 ` [PATCH mptcp-next v27 2/8] selftests: mptcp: connect: check TFO SIOCINQ overflow Geliang Tang
@ 2026-11-14 14:00 ` Geliang Tang
2026-11-14 14:01 ` [PATCH mptcp-next v27 4/8] mptcp: drop the mptcp_ooo_try_coalesce() helper Geliang Tang
` (5 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Geliang Tang @ 2026-11-14 14:00 UTC (permalink / raw)
To: mptcp
From: Geliang Tang <tanggeliang@kylinos.cn>
Commit 2834f8edd74d ("mptcp: cleanup fallback dummy mapping generation")
anchored map_seq at the DSN of skb->seq but left map_subflow_seq
anchored at copied_seq. This caused mptcp_subflow_get_mapped_dsn() to
return the DSN at skb->seq instead of copied_seq.
When TCP queues a partially overlapping segment (seq < rcv_nxt), the skb
starts before copied_seq, giving offset > 0. mptcp_init_skb() then sets
map_seq to the wrong DSN, causing __mptcp_move_skb() to skip data or
drop the skb, leaving a hole in the stream.
Fix by anchoring map_subflow_seq at TCP_SKB_CB(skb)->seq, ensuring
mptcp_subflow_get_mapped_dsn() returns the correct DSN at copied_seq.
Fixes: 2834f8edd74d ("mptcp: cleanup fallback dummy mapping generation")
Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
net/mptcp/subflow.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index 00677fb3efcd..6f07544bfe40 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -1496,7 +1496,7 @@ static bool subflow_check_data_avail(struct sock *ssk)
skb = skb_peek(&ssk->sk_receive_queue);
subflow->map_valid = 1;
subflow->map_data_len = skb->len;
- subflow->map_subflow_seq = tcp_sk(ssk)->copied_seq - subflow->ssn_offset;
+ subflow->map_subflow_seq = TCP_SKB_CB(skb)->seq - subflow->ssn_offset;
subflow->map_seq = __mptcp_expand_seq(subflow->map_seq,
subflow->iasn +
TCP_SKB_CB(skb)->seq -
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH mptcp-next v27 4/8] mptcp: drop the mptcp_ooo_try_coalesce() helper
2026-11-14 14:00 [PATCH mptcp-next v27 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
` (2 preceding siblings ...)
2026-11-14 14:00 ` [PATCH mptcp-next v27 3/8] mptcp: fix fallback dummy mapping anchor Geliang Tang
@ 2026-11-14 14:01 ` Geliang Tang
2026-11-14 14:01 ` [PATCH mptcp-next v27 5/8] mptcp: drop the cant_coalesce CB field Geliang Tang
` (4 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Geliang Tang @ 2026-11-14 14:01 UTC (permalink / raw)
To: mptcp
From: Paolo Abeni <pabeni@redhat.com>
It's used to save an additional comparison for in-order skbs, but is
also a barrier to remove CB offset. Remove the helper, let
__mptcp_try_coalesce() always perform the sequence check and remove
duplicate checks from the callers.
Co-developed-by: Geliang Tang <geliang@kernel.org>
Signed-off-by: Geliang Tang <geliang@kernel.org>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
net/mptcp/protocol.c | 21 ++++++---------------
1 file changed, 6 insertions(+), 15 deletions(-)
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index f2644e7a6a05..16b81e8dc90e 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -167,7 +167,8 @@ static bool __mptcp_try_coalesce(struct sock *sk, struct sk_buff *to,
{
int limit = READ_ONCE(sk->sk_rcvbuf);
- if (unlikely(MPTCP_SKB_CB(to)->cant_coalesce) ||
+ if (MPTCP_SKB_CB(from)->map_seq != MPTCP_SKB_CB(to)->end_seq ||
+ unlikely(MPTCP_SKB_CB(to)->cant_coalesce) ||
MPTCP_SKB_CB(from)->offset ||
((to->len + from->len) > (limit >> 3)) ||
!skb_try_coalesce(to, from, fragstolen, delta))
@@ -200,15 +201,6 @@ static bool mptcp_try_coalesce(struct sock *sk, struct sk_buff *to,
return true;
}
-static bool mptcp_ooo_try_coalesce(struct mptcp_sock *msk, struct sk_buff *to,
- struct sk_buff *from)
-{
- if (MPTCP_SKB_CB(from)->map_seq != MPTCP_SKB_CB(to)->end_seq)
- return false;
-
- return mptcp_try_coalesce((struct sock *)msk, to, from);
-}
-
/* "inspired" by tcp_rcvbuf_grow(), main difference:
* - mptcp does not maintain a msk-level window clamp
* - returns true when the receive buffer is actually updated
@@ -348,7 +340,7 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
/* with 2 subflows, adding at end of ooo queue is quite likely
* Use of ooo_last_skb avoids the O(Log(N)) rbtree lookup.
*/
- if (mptcp_ooo_try_coalesce(msk, msk->ooo_last_skb, skb)) {
+ if (mptcp_try_coalesce(sk, msk->ooo_last_skb, skb)) {
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOMERGE);
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOQUEUETAIL);
return;
@@ -394,7 +386,7 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA);
goto merge_right;
}
- } else if (mptcp_ooo_try_coalesce(msk, skb1, skb)) {
+ } else if (mptcp_try_coalesce(sk, skb1, skb)) {
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOMERGE);
return;
}
@@ -777,8 +769,7 @@ static void __mptcp_add_backlog(struct sock *sk,
if (!list_empty(&msk->backlog_list))
tail = list_last_entry(&msk->backlog_list, struct sk_buff, list);
- if (tail && MPTCP_SKB_CB(skb)->map_seq == MPTCP_SKB_CB(tail)->end_seq &&
- ssk == tail->sk &&
+ if (tail && ssk == tail->sk &&
__mptcp_try_coalesce(sk, tail, skb, &fragstolen, &delta)) {
skb->truesize -= delta;
kfree_skb_partial(skb, fragstolen);
@@ -902,7 +893,7 @@ static bool __mptcp_ofo_queue(struct mptcp_sock *msk)
end_seq = MPTCP_SKB_CB(skb)->end_seq;
tail = skb_peek_tail(&sk->sk_receive_queue);
- if (!tail || !mptcp_ooo_try_coalesce(msk, tail, skb)) {
+ if (!tail || !mptcp_try_coalesce(sk, tail, skb)) {
int delta = msk->ack_seq - MPTCP_SKB_CB(skb)->map_seq;
/* skip overlapping data, if any */
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH mptcp-next v27 5/8] mptcp: drop the cant_coalesce CB field
2026-11-14 14:00 [PATCH mptcp-next v27 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
` (3 preceding siblings ...)
2026-11-14 14:01 ` [PATCH mptcp-next v27 4/8] mptcp: drop the mptcp_ooo_try_coalesce() helper Geliang Tang
@ 2026-11-14 14:01 ` Geliang Tang
2026-11-14 14:01 ` [PATCH mptcp-next v27 6/8] mptcp: remove CB offset field Geliang Tang
` (3 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Geliang Tang @ 2026-11-14 14:01 UTC (permalink / raw)
To: mptcp
From: Paolo Abeni <pabeni@redhat.com>
Such field is used to ensure in-sequence processing in case of fastopen.
Instead let's perform synchronization of the fastopen skb sequence
when the IASN becomes available with the 3rd ack.
When the cant_coalesce field has been introduced, commit f03afb3aeb9d
("mptcp: drop __mptcp_fastopen_gen_msk_ackseq()") noted that updating the
already queued skb for passive fastopen socket at 3rd ack time would be
difficult and race prone. The main point is that such update don't need
to be synchronously performed at 3rd ack time, but is sufficient to
perform it before the next segment is introduced into the msk.
To such extent, add __mptcp_sync_rcv_sequence() to update the TFO skb
map_seq and end_seq using the IASN stored in msk->tfo_iasn, and call it
from __mptcp_move_skb() before any new skb lands into the msk.
When the 3rd ack arrives while the msk is owned by user space, defer the
sync via MPTCP_SYNC_SEQ in cb_flags. subflow_set_remote_key() stores
tfo_iasn and either calls __mptcp_sync_rcv_sequence() directly (when the
socket is not locked) or sets MPTCP_SYNC_SEQ for deferred processing.
mptcp_move_skbs() and mptcp_release_cb() check MPTCP_SYNC_SEQ at each
iteration before spooling the backlog, using __test_and_clear_bit() since
the cb_flags are protected by the msk data lock. A rcvd_dummy_seq flag
tracks whether the TFO skb still carries the dummy mapping and needs
synchronization.
Co-developed-by: Geliang Tang <geliang@kernel.org>
Signed-off-by: Geliang Tang <geliang@kernel.org>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
net/mptcp/fastopen.c | 6 ++++--
net/mptcp/protocol.c | 40 +++++++++++++++++++++++++++++++++++++---
net/mptcp/protocol.h | 5 ++++-
net/mptcp/subflow.c | 8 ++++++++
4 files changed, 53 insertions(+), 6 deletions(-)
diff --git a/net/mptcp/fastopen.c b/net/mptcp/fastopen.c
index f717750906ff..e18b31028e7e 100644
--- a/net/mptcp/fastopen.c
+++ b/net/mptcp/fastopen.c
@@ -9,6 +9,7 @@
void mptcp_fastopen_subflow_synack_set_params(struct mptcp_subflow_context *subflow,
struct request_sock *req)
{
+ struct mptcp_sock *msk;
struct sock *sk, *ssk;
struct sk_buff *skb;
struct tcp_sock *tp;
@@ -49,15 +50,16 @@ void mptcp_fastopen_subflow_synack_set_params(struct mptcp_subflow_context *subf
MPTCP_SKB_CB(skb)->end_seq = 0;
MPTCP_SKB_CB(skb)->offset = 0;
MPTCP_SKB_CB(skb)->has_rxtstamp = has_rxtstamp;
- MPTCP_SKB_CB(skb)->cant_coalesce = 1;
mptcp_data_lock(sk);
DEBUG_NET_WARN_ON_ONCE(sock_owned_by_user_nocheck(sk));
+ msk = mptcp_sk(sk);
+ msk->rcvd_dummy_seq = true;
mptcp_borrow_fwdmem(sk, skb);
skb_set_owner_r(skb, sk);
__skb_queue_tail(&sk->sk_receive_queue, skb);
- mptcp_sk(sk)->bytes_received += skb->len;
+ msk->bytes_received += skb->len;
sk->sk_data_ready(sk);
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 16b81e8dc90e..41fc2429d2fe 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -168,7 +168,6 @@ static bool __mptcp_try_coalesce(struct sock *sk, struct sk_buff *to,
int limit = READ_ONCE(sk->sk_rcvbuf);
if (MPTCP_SKB_CB(from)->map_seq != MPTCP_SKB_CB(to)->end_seq ||
- unlikely(MPTCP_SKB_CB(to)->cant_coalesce) ||
MPTCP_SKB_CB(from)->offset ||
((to->len + from->len) > (limit >> 3)) ||
!skb_try_coalesce(to, from, fragstolen, delta))
@@ -430,7 +429,6 @@ static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset,
MPTCP_SKB_CB(skb)->end_seq = MPTCP_SKB_CB(skb)->map_seq + copy_len;
MPTCP_SKB_CB(skb)->offset = offset;
MPTCP_SKB_CB(skb)->has_rxtstamp = has_rxtstamp;
- MPTCP_SKB_CB(skb)->cant_coalesce = 0;
__skb_unlink(skb, &ssk->sk_receive_queue);
@@ -438,6 +436,25 @@ static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset,
skb_dst_drop(skb);
}
+void __mptcp_sync_rcv_sequence(struct sock *sk)
+{
+ struct mptcp_sock *msk = mptcp_sk(sk);
+ struct sk_buff *skb;
+
+ if (likely(!msk->rcvd_dummy_seq))
+ return;
+
+ /* User space can have already received the TFO skb. */
+ msk->rcvd_dummy_seq = false;
+ skb = skb_peek_tail(&sk->sk_receive_queue);
+ if (!skb)
+ return;
+
+ MPTCP_SKB_CB(skb)->map_seq = msk->tfo_iasn - skb->len +
+ MPTCP_SKB_CB(skb)->offset;
+ MPTCP_SKB_CB(skb)->end_seq = msk->tfo_iasn;
+}
+
static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb)
{
u64 copy_len = MPTCP_SKB_CB(skb)->end_seq - MPTCP_SKB_CB(skb)->map_seq;
@@ -446,6 +463,12 @@ static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb)
mptcp_borrow_fwdmem(sk, skb);
+ /* Be sure to sync the eventual fastopen dummy mapping before any other
+ * skb lands into the msk.
+ */
+ if (unlikely(msk->rcvd_dummy_seq))
+ __mptcp_sync_rcv_sequence(sk);
+
if (MPTCP_SKB_CB(skb)->map_seq == msk->ack_seq) {
/* in sequence */
insert:
@@ -2377,7 +2400,13 @@ static bool mptcp_move_skbs(struct sock *sk)
u32 moved = 0;
mptcp_data_lock(sk);
- while (mptcp_can_spool_backlog(sk, &skbs)) {
+ while (1) {
+ if (__test_and_clear_bit(MPTCP_SYNC_SEQ, &msk->cb_flags))
+ __mptcp_sync_rcv_sequence(sk);
+
+ if (!mptcp_can_spool_backlog(sk, &skbs))
+ break;
+
mptcp_data_unlock(sk);
enqueued |= __mptcp_move_skbs(sk, &skbs, &moved);
@@ -3676,6 +3705,8 @@ static int mptcp_disconnect(struct sock *sk, int flags)
msk->bytes_retrans = 0;
msk->rcvspace_init = 0;
msk->fastclosing = 0;
+ msk->tfo_iasn = 0;
+ msk->rcvd_dummy_seq = false;
mptcp_init_rtt_est(msk);
/* for fallback's sake */
@@ -3878,6 +3909,9 @@ static void mptcp_release_cb(struct sock *sk)
struct list_head join_list, skbs;
bool spool_bl;
+ if (__test_and_clear_bit(MPTCP_SYNC_SEQ, &msk->cb_flags))
+ __mptcp_sync_rcv_sequence(sk);
+
spool_bl = mptcp_can_spool_backlog(sk, &skbs);
if (!flags && !spool_bl)
break;
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index 3be60da12924..b4e49d719200 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -126,13 +126,13 @@
#define MPTCP_FLUSH_JOIN_LIST 5
#define MPTCP_SYNC_STATE 6
#define MPTCP_SYNC_SNDBUF 7
+#define MPTCP_SYNC_SEQ 8
struct mptcp_skb_cb {
u64 map_seq;
u64 end_seq;
u32 offset;
u8 has_rxtstamp;
- u8 cant_coalesce;
};
#define MPTCP_SKB_CB(__skb) ((struct mptcp_skb_cb *)&((__skb)->cb[0]))
@@ -325,6 +325,7 @@ struct mptcp_sock {
u32 last_ack_recv;
unsigned long timer_ival;
u32 token;
+ u64 tfo_iasn;
unsigned long flags;
unsigned long cb_flags;
bool recovery; /* closing subflow write queue reinjected */
@@ -336,6 +337,7 @@ struct mptcp_sock {
bool use_64bit_ack; /* Set when we received a 64-bit DSN */
bool csum_enabled;
bool allow_infinite_fallback;
+ bool rcvd_dummy_seq;
u8 pending_state; /* A subflow asked to set this sk_state,
* protected by the msk data lock
*/
@@ -1193,6 +1195,7 @@ void mptcp_event_pm_listener(const struct sock *ssk,
enum mptcp_event_type event);
bool mptcp_userspace_pm_active(const struct mptcp_sock *msk);
+void __mptcp_sync_rcv_sequence(struct sock *sk);
void mptcp_fastopen_subflow_synack_set_params(struct mptcp_subflow_context *subflow,
struct request_sock *req);
int mptcp_pm_genl_fill_addr(struct sk_buff *msg,
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index 6f07544bfe40..f9dfb6662e3c 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -519,6 +519,8 @@ static void subflow_set_remote_key(struct mptcp_sock *msk,
struct mptcp_subflow_context *subflow,
const struct mptcp_options_received *mp_opt)
{
+ struct sock *sk = (struct sock *)msk;
+
/* active MPC subflow will reach here multiple times:
* at subflow_finish_connect() time and at 4th ack time
*/
@@ -537,6 +539,12 @@ static void subflow_set_remote_key(struct mptcp_sock *msk,
WRITE_ONCE(msk->ack_seq, subflow->iasn);
WRITE_ONCE(msk->can_ack, true);
atomic64_set(&msk->rcv_wnd_sent, subflow->iasn);
+ msk->tfo_iasn = subflow->iasn;
+
+ if (!sock_owned_by_user(sk))
+ __mptcp_sync_rcv_sequence(sk);
+ else
+ set_bit(MPTCP_SYNC_SEQ, &msk->cb_flags);
}
static void mptcp_propagate_state(struct sock *sk, struct sock *ssk,
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH mptcp-next v27 6/8] mptcp: remove CB offset field
2026-11-14 14:00 [PATCH mptcp-next v27 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
` (4 preceding siblings ...)
2026-11-14 14:01 ` [PATCH mptcp-next v27 5/8] mptcp: drop the cant_coalesce CB field Geliang Tang
@ 2026-11-14 14:01 ` Geliang Tang
2026-11-14 14:29 ` sashiko-bot
2026-11-14 14:01 ` [PATCH mptcp-next v27 7/8] mptcp: sync mptcp skb cb layout with tcp one Geliang Tang
` (2 subsequent siblings)
8 siblings, 1 reply; 12+ messages in thread
From: Geliang Tang @ 2026-11-14 14:01 UTC (permalink / raw)
To: mptcp
From: Paolo Abeni <pabeni@redhat.com>
Instead, use a new msk-level field to track the bytes already consumed
inside each skb, carrying the amount of bytes already copied to
user-space, alike what TCP is already doing.
The newly introduced `copied_seq` field is always accessed under the msk
socket lock, delegating the synchronization with IASN to mptcp_move_skbs()
and the msk release CB, when the socket is owned by the user-space at
remote key reception time. Such synchronization preserves any partial
progress (copy) made on the TFO packet.
In mptcp_init_skb(), map_seq is computed as mptcp_subflow_get_mapped_dsn()
minus offset, effectively encoding the consumed prefix into the sequence
number. When offset > 0, a new MPTCP_SKB_CB_F_OFFSET flag is set to mark
the skb as having a consumed prefix.
Helper functions mptcp_dsn() and mptcp_end_dsn() are introduced to compute
the actual DSN and end DSN from map_seq and skb length.
__mptcp_try_coalesce() refuses to coalesce when either the from or to skb
carries the MPTCP_SKB_CB_F_OFFSET flag, preventing an unvalidated prefix
from being merged into a validated buffer.
__mptcp_recvmsg_mskq() is refactored to take a seq parameter (pointer to
copied_seq or peek_seq) and computes the offset as seq minus map_seq.
Already consumed skbs (where offset >= skb->len) are skipped and freed
for non-MSG_PEEK reads.
Throughout the receive path, direct accesses to MPTCP_SKB_CB(skb)->map_seq
and end_seq are replaced with calls to mptcp_dsn() and mptcp_end_dsn().
Overall this simplifies a bit the __mptcp_recvmsg_mskq() and the
__mptcp_move_skb() code and will also make possible the next patch.
Co-developed-by: Geliang Tang <geliang@kernel.org>
Signed-off-by: Geliang Tang <geliang@kernel.org>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
net/mptcp/fastopen.c | 5 +-
net/mptcp/protocol.c | 110 ++++++++++++++++++++++---------------------
net/mptcp/protocol.h | 16 ++++++-
net/mptcp/subflow.c | 6 ++-
4 files changed, 78 insertions(+), 59 deletions(-)
diff --git a/net/mptcp/fastopen.c b/net/mptcp/fastopen.c
index e18b31028e7e..8dcfcff9087f 100644
--- a/net/mptcp/fastopen.c
+++ b/net/mptcp/fastopen.c
@@ -46,9 +46,9 @@ void mptcp_fastopen_subflow_synack_set_params(struct mptcp_subflow_context *subf
has_rxtstamp = TCP_SKB_CB(skb)->has_rxtstamp;
/* Only the sequence delta is relevant */
- MPTCP_SKB_CB(skb)->map_seq = -skb->len;
+ MPTCP_SKB_CB(skb)->map_seq = -(u64)skb->len;
MPTCP_SKB_CB(skb)->end_seq = 0;
- MPTCP_SKB_CB(skb)->offset = 0;
+ MPTCP_SKB_CB(skb)->flags = 0;
MPTCP_SKB_CB(skb)->has_rxtstamp = has_rxtstamp;
mptcp_data_lock(sk);
@@ -56,6 +56,7 @@ void mptcp_fastopen_subflow_synack_set_params(struct mptcp_subflow_context *subf
msk = mptcp_sk(sk);
msk->rcvd_dummy_seq = true;
+ msk->copied_seq = MPTCP_SKB_CB(skb)->map_seq;
mptcp_borrow_fwdmem(sk, skb);
skb_set_owner_r(skb, sk);
__skb_queue_tail(&sk->sk_receive_queue, skb);
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 41fc2429d2fe..f4ab3bdd368f 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -167,8 +167,9 @@ static bool __mptcp_try_coalesce(struct sock *sk, struct sk_buff *to,
{
int limit = READ_ONCE(sk->sk_rcvbuf);
- if (MPTCP_SKB_CB(from)->map_seq != MPTCP_SKB_CB(to)->end_seq ||
- MPTCP_SKB_CB(from)->offset ||
+ if (MPTCP_SKB_CB(from)->map_seq != mptcp_end_dsn(to) ||
+ (MPTCP_SKB_CB(from)->flags | MPTCP_SKB_CB(to)->flags) &
+ MPTCP_SKB_CB_F_OFFSET ||
((to->len + from->len) > (limit >> 3)) ||
!skb_try_coalesce(to, from, fragstolen, delta))
return false;
@@ -305,8 +306,8 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
u64 seq, end_seq, max_seq;
struct sk_buff *skb1;
- seq = MPTCP_SKB_CB(skb)->map_seq;
- end_seq = MPTCP_SKB_CB(skb)->end_seq;
+ seq = mptcp_dsn(skb);
+ end_seq = mptcp_end_dsn(skb);
max_seq = atomic64_read(&msk->rcv_wnd_sent);
pr_debug("msk=%p seq=%llx limit=%llx empty=%d\n", msk, seq, max_seq,
@@ -346,7 +347,7 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
}
/* Can avoid an rbtree lookup if we are adding skb after ooo_last_skb */
- if (!before64(seq, MPTCP_SKB_CB(msk->ooo_last_skb)->end_seq)) {
+ if (!before64(seq, mptcp_end_dsn(msk->ooo_last_skb))) {
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOQUEUETAIL);
parent = &msk->ooo_last_skb->rbnode;
p = &parent->rb_right;
@@ -362,8 +363,8 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
p = &parent->rb_left;
continue;
}
- if (before64(seq, MPTCP_SKB_CB(skb1)->end_seq)) {
- if (!after64(end_seq, MPTCP_SKB_CB(skb1)->end_seq)) {
+ if (before64(seq, mptcp_end_dsn(skb1))) {
+ if (!after64(end_seq, mptcp_end_dsn(skb1))) {
/* All the bits are present. Drop. */
mptcp_drop(sk, skb);
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA);
@@ -400,7 +401,7 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
merge_right:
/* Remove other segments covered by skb. */
while ((skb1 = skb_rb_next(skb)) != NULL) {
- if (before64(end_seq, MPTCP_SKB_CB(skb1)->end_seq))
+ if (before64(end_seq, mptcp_end_dsn(skb1)))
break;
rb_erase(&skb1->rbnode, &msk->out_of_order_queue);
mptcp_drop(sk, skb1);
@@ -425,9 +426,10 @@ static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset,
* mptcp_subflow_get_mapped_dsn() is based on the current tp->copied_seq
* value
*/
- MPTCP_SKB_CB(skb)->map_seq = mptcp_subflow_get_mapped_dsn(subflow);
+ MPTCP_SKB_CB(skb)->map_seq = mptcp_subflow_get_mapped_dsn(subflow) -
+ offset;
MPTCP_SKB_CB(skb)->end_seq = MPTCP_SKB_CB(skb)->map_seq + copy_len;
- MPTCP_SKB_CB(skb)->offset = offset;
+ MPTCP_SKB_CB(skb)->flags = offset ? MPTCP_SKB_CB_F_OFFSET : 0;
MPTCP_SKB_CB(skb)->has_rxtstamp = has_rxtstamp;
__skb_unlink(skb, &ssk->sk_receive_queue);
@@ -450,15 +452,15 @@ void __mptcp_sync_rcv_sequence(struct sock *sk)
if (!skb)
return;
- MPTCP_SKB_CB(skb)->map_seq = msk->tfo_iasn - skb->len +
- MPTCP_SKB_CB(skb)->offset;
- MPTCP_SKB_CB(skb)->end_seq = msk->tfo_iasn;
+ MPTCP_SKB_CB(skb)->map_seq = msk->tfo_iasn - skb->len;
+ MPTCP_SKB_CB(skb)->end_seq = MPTCP_SKB_CB(skb)->map_seq + skb->len;
}
static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb)
{
u64 copy_len = MPTCP_SKB_CB(skb)->end_seq - MPTCP_SKB_CB(skb)->map_seq;
struct mptcp_sock *msk = mptcp_sk(sk);
+ u64 dsn = mptcp_dsn(skb);
struct sk_buff *tail;
mptcp_borrow_fwdmem(sk, skb);
@@ -469,7 +471,7 @@ static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb)
if (unlikely(msk->rcvd_dummy_seq))
__mptcp_sync_rcv_sequence(sk);
- if (MPTCP_SKB_CB(skb)->map_seq == msk->ack_seq) {
+ if (dsn == msk->ack_seq) {
/* in sequence */
insert:
if (!mptcp_try_rmem_schedule(sk, skb)) {
@@ -487,18 +489,14 @@ static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb)
skb_set_owner_r(skb, sk);
__skb_queue_tail(&sk->sk_receive_queue, skb);
return true;
- } else if (after64(MPTCP_SKB_CB(skb)->map_seq, msk->ack_seq)) {
+ } else if (after64(dsn, msk->ack_seq)) {
mptcp_data_queue_ofo(msk, skb);
return false;
}
/* Partial packet */
if (after64(MPTCP_SKB_CB(skb)->end_seq, msk->ack_seq)) {
- copy_len = MPTCP_SKB_CB(skb)->end_seq - msk->ack_seq;
- MPTCP_SKB_CB(skb)->offset += msk->ack_seq -
- MPTCP_SKB_CB(skb)->map_seq;
- MPTCP_SKB_CB(skb)->map_seq += msk->ack_seq -
- MPTCP_SKB_CB(skb)->map_seq;
+ copy_len = mptcp_end_dsn(skb) - msk->ack_seq;
goto insert;
}
@@ -901,30 +899,26 @@ static bool __mptcp_ofo_queue(struct mptcp_sock *msk)
pr_debug("msk=%p empty=%d\n", msk, RB_EMPTY_ROOT(&msk->out_of_order_queue));
while (p) {
skb = rb_to_skb(p);
- if (after64(MPTCP_SKB_CB(skb)->map_seq, msk->ack_seq))
+ if (after64(mptcp_dsn(skb), msk->ack_seq))
break;
p = rb_next(p);
rb_erase(&skb->rbnode, &msk->out_of_order_queue);
- if (unlikely(!after64(MPTCP_SKB_CB(skb)->end_seq,
- msk->ack_seq))) {
+ if (unlikely(!after64(mptcp_end_dsn(skb), msk->ack_seq))) {
mptcp_drop(sk, skb);
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA);
continue;
}
- end_seq = MPTCP_SKB_CB(skb)->end_seq;
+ end_seq = mptcp_end_dsn(skb);
tail = skb_peek_tail(&sk->sk_receive_queue);
if (!tail || !mptcp_try_coalesce(sk, tail, skb)) {
int delta = msk->ack_seq - MPTCP_SKB_CB(skb)->map_seq;
- /* skip overlapping data, if any */
pr_debug("uncoalesced seq=%llx ack seq=%llx delta=%d\n",
MPTCP_SKB_CB(skb)->map_seq, msk->ack_seq,
delta);
- MPTCP_SKB_CB(skb)->offset += delta;
- MPTCP_SKB_CB(skb)->map_seq += delta;
__skb_queue_tail(&sk->sk_receive_queue, skb);
}
msk->bytes_received += end_seq - msk->ack_seq;
@@ -2209,33 +2203,27 @@ static void mptcp_eat_recv_skb(struct sock *sk, struct sk_buff *skb)
}
static int __mptcp_recvmsg_mskq(struct sock *sk, struct msghdr *msg,
- size_t len, int flags, int copied_total,
+ size_t len, int flags, u64 *seq,
struct scm_timestamping_internal *tss,
int *cmsg_flags, struct sk_buff **last)
{
struct mptcp_sock *msk = mptcp_sk(sk);
struct sk_buff *skb, *tmp;
- int total_data_len = 0;
int copied = 0;
skb_queue_walk_safe(&sk->sk_receive_queue, skb, tmp) {
- u32 delta, offset = MPTCP_SKB_CB(skb)->offset;
+ u64 offset = *seq - MPTCP_SKB_CB(skb)->map_seq;
u32 data_len = skb->len - offset;
u32 count;
int err;
- if (flags & MSG_PEEK) {
- /* skip already peeked skbs */
- if (total_data_len + data_len <= copied_total) {
- total_data_len += data_len;
+ /* Skip the already consumed data. */
+ if (offset >= skb->len) {
+ if (!(flags & MSG_PEEK))
+ mptcp_eat_recv_skb(sk, skb);
+ else
*last = skb;
- continue;
- }
-
- /* skip the already peeked data in the current skb */
- delta = copied_total - total_data_len;
- offset += delta;
- data_len -= delta;
+ continue;
}
count = min_t(size_t, len - copied, data_len);
@@ -2255,14 +2243,12 @@ static int __mptcp_recvmsg_mskq(struct sock *sk, struct msghdr *msg,
}
copied += count;
+ *seq += count;
if (!(flags & MSG_PEEK)) {
msk->bytes_consumed += count;
- if (count < data_len) {
- MPTCP_SKB_CB(skb)->offset += count;
- MPTCP_SKB_CB(skb)->map_seq += count;
+ if (count < data_len)
break;
- }
mptcp_eat_recv_skb(sk, skb);
} else {
@@ -2401,8 +2387,10 @@ static bool mptcp_move_skbs(struct sock *sk)
mptcp_data_lock(sk);
while (1) {
- if (__test_and_clear_bit(MPTCP_SYNC_SEQ, &msk->cb_flags))
+ if (__test_and_clear_bit(MPTCP_SYNC_SEQ, &msk->cb_flags)) {
+ msk->copied_seq += msk->tfo_iasn;
__mptcp_sync_rcv_sequence(sk);
+ }
if (!mptcp_can_spool_backlog(sk, &skbs))
break;
@@ -2442,6 +2430,7 @@ static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
struct mptcp_sock *msk = mptcp_sk(sk);
struct scm_timestamping_internal tss;
int copied = 0, cmsg_flags = 0;
+ u64 peek_seq, *seq;
int target;
long timeo;
@@ -2460,6 +2449,11 @@ static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
len = min_t(size_t, len, INT_MAX);
target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);
+ seq = &msk->copied_seq;
+ if (flags & MSG_PEEK) {
+ peek_seq = msk->copied_seq;
+ seq = &peek_seq;
+ }
if (unlikely(msk->recvmsg_inq))
cmsg_flags = MPTCP_CMSG_INQ;
@@ -2469,7 +2463,7 @@ static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
int err, bytes_read;
bytes_read = __mptcp_recvmsg_mskq(sk, msg, len - copied, flags,
- copied, &tss, &cmsg_flags,
+ seq, &tss, &cmsg_flags,
&last);
if (unlikely(bytes_read < 0)) {
if (!copied)
@@ -2479,8 +2473,11 @@ static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
copied += bytes_read;
- if (!list_empty(&msk->backlog_list) && mptcp_move_skbs(sk))
+ if (!list_empty(&msk->backlog_list) && mptcp_move_skbs(sk)) {
+ if (flags & MSG_PEEK)
+ peek_seq = msk->copied_seq + copied;
continue;
+ }
/* only the MPTCP socket status is relevant here. The exit
* conditions mirror closely tcp_recvmsg()
@@ -2523,6 +2520,10 @@ static int mptcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
copied = copied ? : err;
goto out_err;
}
+
+ /* Recompute peek offset after eventual seq resync. */
+ if (flags & MSG_PEEK)
+ peek_seq = msk->copied_seq + copied;
}
mptcp_cleanup_rbuf(msk, copied);
@@ -3699,6 +3700,7 @@ static int mptcp_disconnect(struct sock *sk, int flags)
mptcp_pm_data_reset(msk);
mptcp_ca_reset(sk);
msk->bytes_consumed = 0;
+ msk->copied_seq = 0;
msk->bytes_acked = 0;
msk->bytes_received = 0;
msk->bytes_sent = 0;
@@ -3909,8 +3911,10 @@ static void mptcp_release_cb(struct sock *sk)
struct list_head join_list, skbs;
bool spool_bl;
- if (__test_and_clear_bit(MPTCP_SYNC_SEQ, &msk->cb_flags))
+ if (__test_and_clear_bit(MPTCP_SYNC_SEQ, &msk->cb_flags)) {
+ msk->copied_seq += msk->tfo_iasn;
__mptcp_sync_rcv_sequence(sk);
+ }
spool_bl = mptcp_can_spool_backlog(sk, &skbs);
if (!flags && !spool_bl)
@@ -4631,7 +4635,7 @@ static struct sk_buff *mptcp_recv_skb(struct sock *sk, u32 *off)
mptcp_move_skbs(sk);
while ((skb = skb_peek(&sk->sk_receive_queue)) != NULL) {
- offset = MPTCP_SKB_CB(skb)->offset;
+ offset = msk->copied_seq - MPTCP_SKB_CB(skb)->map_seq;
if (offset < skb->len) {
*off = offset;
return skb;
@@ -4673,11 +4677,9 @@ static int __mptcp_read_sock(struct sock *sk, read_descriptor_t *desc,
copied += count;
msk->bytes_consumed += count;
- if (count < data_len) {
- MPTCP_SKB_CB(skb)->offset += count;
- MPTCP_SKB_CB(skb)->map_seq += count;
+ msk->copied_seq += count;
+ if (count < data_len)
break;
- }
mptcp_eat_recv_skb(sk, skb);
if (!desc->count)
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index b4e49d719200..b6b23dcc304c 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -128,10 +128,12 @@
#define MPTCP_SYNC_SNDBUF 7
#define MPTCP_SYNC_SEQ 8
+#define MPTCP_SKB_CB_F_OFFSET 0x0100
+
struct mptcp_skb_cb {
u64 map_seq;
u64 end_seq;
- u32 offset;
+ u16 flags;
u8 has_rxtstamp;
};
@@ -311,6 +313,7 @@ struct mptcp_sock {
u64 rcv_data_fin_seq;
u64 bytes_retrans;
u64 bytes_consumed;
+ u64 copied_seq;
int snd_burst;
int old_wspace;
u64 recovery_snd_nxt; /* in recovery mode accept up to this seq;
@@ -1381,6 +1384,17 @@ static inline bool subflow_simultaneous_connect(struct sock *sk)
return sk->sk_state == TCP_SYN_RECV && is_active_ssk(subflow);
}
+static inline u64 mptcp_end_dsn(const struct sk_buff *skb)
+{
+ return MPTCP_SKB_CB(skb)->map_seq + skb->len;
+}
+
+static inline u64 mptcp_dsn(const struct sk_buff *skb)
+{
+ return MPTCP_SKB_CB(skb)->map_seq + skb->len -
+ (MPTCP_SKB_CB(skb)->end_seq - MPTCP_SKB_CB(skb)->map_seq);
+}
+
#ifdef CONFIG_SYN_COOKIES
void subflow_init_req_cookie_join_save(const struct mptcp_subflow_request_sock *subflow_req,
struct sk_buff *skb);
diff --git a/net/mptcp/subflow.c b/net/mptcp/subflow.c
index f9dfb6662e3c..83beaa54b634 100644
--- a/net/mptcp/subflow.c
+++ b/net/mptcp/subflow.c
@@ -541,10 +541,12 @@ static void subflow_set_remote_key(struct mptcp_sock *msk,
atomic64_set(&msk->rcv_wnd_sent, subflow->iasn);
msk->tfo_iasn = subflow->iasn;
- if (!sock_owned_by_user(sk))
+ if (!sock_owned_by_user(sk)) {
+ msk->copied_seq += subflow->iasn;
__mptcp_sync_rcv_sequence(sk);
- else
+ } else {
set_bit(MPTCP_SYNC_SEQ, &msk->cb_flags);
+ }
}
static void mptcp_propagate_state(struct sock *sk, struct sock *ssk,
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH mptcp-next v27 6/8] mptcp: remove CB offset field
2026-11-14 14:01 ` [PATCH mptcp-next v27 6/8] mptcp: remove CB offset field Geliang Tang
@ 2026-11-14 14:29 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-11-14 14:29 UTC (permalink / raw)
To: Geliang Tang, mptcp
Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [High] mptcp: raw map_seq used instead of mptcp_dsn() in
mptcp_data_queue_ofo()
- [Medium] mptcp: raw map_seq and end_seq used in
mptcp_prune_ofo_queue() and tracepoint
--
commit 9093e378164791c8e9a9ecffc602703ba0b1790e
Author: Paolo Abeni <pabeni@redhat.com>
mptcp: remove CB offset field
This commit removes the offset field from struct mptcp_skb_cb, tracking
consumed bytes via msk->copied_seq and encoding any subflow skb offset into
MPTCP_SKB_CB(skb)->map_seq alongside the mptcp_dsn() and mptcp_end_dsn()
helpers.
> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
> index 41fc2429d2fed..f4ab3bdd368f2 100644
> --- a/net/mptcp/protocol.c
> +++ b/net/mptcp/protocol.c
[ ... ]
> @@ -362,8 +363,8 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
> p = &parent->rb_left;
> continue;
> }
> - if (before64(seq, MPTCP_SKB_CB(skb1)->end_seq)) {
> - if (!after64(end_seq, MPTCP_SKB_CB(skb1)->end_seq)) {
> + if (before64(seq, mptcp_end_dsn(skb1))) {
> + if (!after64(end_seq, mptcp_end_dsn(skb1))) {
> /* All the bits are present. Drop. */
> mptcp_drop(sk, skb);
> MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA);
[Severity: High]
Should the RB-tree traversal in mptcp_data_queue_ofo() compare seq against
mptcp_dsn(skb1) instead of MPTCP_SKB_CB(skb1)->map_seq?
net/mptcp/protocol.c:mptcp_data_queue_ofo() {
...
while (*p) {
parent = *p;
skb1 = rb_to_skb(parent);
if (before64(seq, MPTCP_SKB_CB(skb1)->map_seq)) {
p = &parent->rb_left;
continue;
}
if (before64(seq, mptcp_end_dsn(skb1))) {
if (!after64(end_seq, mptcp_end_dsn(skb1))) {
/* All the bits are present. Drop. */
mptcp_drop(sk, skb);
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA);
return;
}
if (after64(seq, MPTCP_SKB_CB(skb1)->map_seq)) {
...
}
When mptcp_init_skb() initializes an skb with a non-zero subflow offset,
MPTCP_SKB_CB(skb1)->map_seq is set to mptcp_dsn(skb1) - offset. If that skb
is queued in msk->out_of_order_queue and a subsequent out-of-order segment
arrives with seq in [mptcp_dsn(skb1) - offset, mptcp_dsn(skb1)), the
before64(seq, MPTCP_SKB_CB(skb1)->map_seq) check is false instead of moving to
rb_left. Any segment in that range with end_seq <= mptcp_end_dsn(skb1) (such
as the segment immediately preceding skb1) is then dropped as duplicate data,
or if end_seq > mptcp_end_dsn(skb1) it is inserted into the right subtree of
skb1 despite having a smaller DSN, stalling the receive queue when
__mptcp_ofo_queue() later stops at mptcp_dsn(skb1) > msk->ack_seq.
[ ... ]
> @@ -425,9 +426,10 @@ static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset,
> * mptcp_subflow_get_mapped_dsn() is based on the current tp->copied_seq
> * value
> */
> - MPTCP_SKB_CB(skb)->map_seq = mptcp_subflow_get_mapped_dsn(subflow);
> + MPTCP_SKB_CB(skb)->map_seq = mptcp_subflow_get_mapped_dsn(subflow) -
> + offset;
> MPTCP_SKB_CB(skb)->end_seq = MPTCP_SKB_CB(skb)->map_seq + copy_len;
> - MPTCP_SKB_CB(skb)->offset = offset;
> + MPTCP_SKB_CB(skb)->flags = offset ? MPTCP_SKB_CB_F_OFFSET : 0;
> MPTCP_SKB_CB(skb)->has_rxtstamp = has_rxtstamp;
[Severity: Medium]
Now that map_seq and end_seq are shifted down by offset, should
mptcp_prune_ofo_queue() and trace_mptcp_rcvbuf_grow() also use mptcp_dsn() and
mptcp_end_dsn()?
net/mptcp/protocol.c:mptcp_prune_ofo_queue() {
...
/* 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;
...
}
When either in_skb or the tail skb in msk->out_of_order_queue has a non-zero
offset, comparing raw map_seq values can stop pruning early (causing
__mptcp_move_skb() or mptcp_data_queue_ofo() to drop an earlier segment under
memory pressure) or prune out-of-order segments whose actual DSN precedes
in_skb. Similarly, the mptcp_rcvbuf_grow tracepoint in
include/trace/events/mptcp.h still computes ooo_space from
MPTCP_SKB_CB(msk->ooo_last_skb)->end_seq - msk->ack_seq instead of
mptcp_end_dsn(msk->ooo_last_skb) - msk->ack_seq.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1794663678.git.tanggeliang@kylinos.cn?part=6
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH mptcp-next v27 7/8] mptcp: sync mptcp skb cb layout with tcp one
2026-11-14 14:00 [PATCH mptcp-next v27 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
` (5 preceding siblings ...)
2026-11-14 14:01 ` [PATCH mptcp-next v27 6/8] mptcp: remove CB offset field Geliang Tang
@ 2026-11-14 14:01 ` Geliang Tang
2026-11-14 14:41 ` sashiko-bot
2026-11-14 14:01 ` [PATCH mptcp-next v27 8/8] mptcp: defer read_sock cleanup to mptcp_worker Geliang Tang
2026-11-14 15:52 ` [PATCH mptcp-next v27 0/8] Reduce the differences between TCP and MPTCP for TLS usage MPTCP CI
8 siblings, 1 reply; 12+ messages in thread
From: Geliang Tang @ 2026-11-14 14:01 UTC (permalink / raw)
To: mptcp
From: Paolo Abeni <pabeni@redhat.com>
The MPTCP protocol uses a significantly different CB layout WRT TCP, as it
includes different information and uses 64 bits for the sequence numbers.
The CB layout changes map_seq and end_seq from u64 to u32, adds a new
map_seq64 field to track the full 64-bit DSN, and aligns the existing flags
field with TCP's tcp_flags offset. Add a build time check to ensure the CB
layout matches TCP's layout for the three shared fields: map_seq, end_seq,
and flags.
The 32-bit sequence numbers are safe because the relative distances remain
small. For OFO queue skbs, sequence numbers are validated against
rcv_wnd_sent in mptcp_data_queue_ofo(), bounded by the TCP-scaled receive
window. For receive queue skbs, no window check is performed, but the
unsigned offset calculations in __mptcp_recvmsg_mskq() and mptcp_recv_skb()
are safe because copied_seq always falls inside or at the edge of the head
skb. This ensures that 32-bit arithmetic (comparisons and subtractions)
produces correct results even when 64-bit values cross the 2^32 boundary.
map_seq64 is computed as mptcp_subflow_get_mapped_dsn() in mptcp_init_skb()
minus offset, and map_seq is the lower 32 bits.
The receive path now uses 32-bit before()/after() helpers for most sequence
comparisons, while map_seq64 is used for window validation and coalescing
checks. __mptcp_try_coalesce() gains a map_seq64 contiguity check to ensure
skbs are truly adjacent in the 64-bit DSN space.
Co-developed-by: Geliang Tang <geliang@kernel.org>
Signed-off-by: Geliang Tang <geliang@kernel.org>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
---
net/mptcp/fastopen.c | 5 +--
net/mptcp/protocol.c | 80 ++++++++++++++++++++++++++------------------
net/mptcp/protocol.h | 10 +++---
3 files changed, 56 insertions(+), 39 deletions(-)
diff --git a/net/mptcp/fastopen.c b/net/mptcp/fastopen.c
index 8dcfcff9087f..952f04dffc9e 100644
--- a/net/mptcp/fastopen.c
+++ b/net/mptcp/fastopen.c
@@ -46,7 +46,8 @@ void mptcp_fastopen_subflow_synack_set_params(struct mptcp_subflow_context *subf
has_rxtstamp = TCP_SKB_CB(skb)->has_rxtstamp;
/* Only the sequence delta is relevant */
- MPTCP_SKB_CB(skb)->map_seq = -(u64)skb->len;
+ MPTCP_SKB_CB(skb)->map_seq64 = -(u64)skb->len;
+ MPTCP_SKB_CB(skb)->map_seq = MPTCP_SKB_CB(skb)->map_seq64;
MPTCP_SKB_CB(skb)->end_seq = 0;
MPTCP_SKB_CB(skb)->flags = 0;
MPTCP_SKB_CB(skb)->has_rxtstamp = has_rxtstamp;
@@ -56,7 +57,7 @@ void mptcp_fastopen_subflow_synack_set_params(struct mptcp_subflow_context *subf
msk = mptcp_sk(sk);
msk->rcvd_dummy_seq = true;
- msk->copied_seq = MPTCP_SKB_CB(skb)->map_seq;
+ msk->copied_seq = MPTCP_SKB_CB(skb)->map_seq64;
mptcp_borrow_fwdmem(sk, skb);
skb_set_owner_r(skb, sk);
__skb_queue_tail(&sk->sk_receive_queue, skb);
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index f4ab3bdd368f..44f4a680a4bd 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -167,14 +167,14 @@ static bool __mptcp_try_coalesce(struct sock *sk, struct sk_buff *to,
{
int limit = READ_ONCE(sk->sk_rcvbuf);
- if (MPTCP_SKB_CB(from)->map_seq != mptcp_end_dsn(to) ||
+ if (MPTCP_SKB_CB(from)->map_seq64 != mptcp_end_dsn(to) ||
(MPTCP_SKB_CB(from)->flags | MPTCP_SKB_CB(to)->flags) &
MPTCP_SKB_CB_F_OFFSET ||
((to->len + from->len) > (limit >> 3)) ||
!skb_try_coalesce(to, from, fragstolen, delta))
return false;
- pr_debug("colesced seq %llx into %llx new len %d new end seq %llx\n",
+ pr_debug("colesced seq %x into %x new len %d new end seq %x\n",
MPTCP_SKB_CB(from)->map_seq, MPTCP_SKB_CB(to)->map_seq,
to->len, MPTCP_SKB_CB(from)->end_seq);
MPTCP_SKB_CB(to)->end_seq = MPTCP_SKB_CB(from)->end_seq;
@@ -256,8 +256,8 @@ static void mptcp_prune_ofo_queue(struct sock *sk,
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))
+ if (after(MPTCP_SKB_CB(in_skb)->map_seq,
+ MPTCP_SKB_CB(skb)->map_seq))
break;
pruned = true;
@@ -303,14 +303,15 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
{
struct sock *sk = (struct sock *)msk;
struct rb_node **p, *parent;
- u64 seq, end_seq, max_seq;
+ u64 end_seq, max_seq;
struct sk_buff *skb1;
+ u32 seq;
- seq = mptcp_dsn(skb);
+ seq = (u32)mptcp_dsn(skb);
end_seq = mptcp_end_dsn(skb);
max_seq = atomic64_read(&msk->rcv_wnd_sent);
- pr_debug("msk=%p seq=%llx limit=%llx empty=%d\n", msk, seq, max_seq,
+ pr_debug("msk=%p seq=%x limit=%llx empty=%d\n", msk, seq, max_seq,
RB_EMPTY_ROOT(&msk->out_of_order_queue));
if (after64(end_seq, max_seq)) {
/* out of window */
@@ -347,7 +348,7 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
}
/* Can avoid an rbtree lookup if we are adding skb after ooo_last_skb */
- if (!before64(seq, mptcp_end_dsn(msk->ooo_last_skb))) {
+ if (!before(seq, (u32)mptcp_end_dsn(msk->ooo_last_skb))) {
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOQUEUETAIL);
parent = &msk->ooo_last_skb->rbnode;
p = &parent->rb_right;
@@ -359,18 +360,18 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
while (*p) {
parent = *p;
skb1 = rb_to_skb(parent);
- if (before64(seq, MPTCP_SKB_CB(skb1)->map_seq)) {
+ if (before(seq, MPTCP_SKB_CB(skb1)->map_seq)) {
p = &parent->rb_left;
continue;
}
- if (before64(seq, mptcp_end_dsn(skb1))) {
- if (!after64(end_seq, mptcp_end_dsn(skb1))) {
+ if (before(seq, (u32)mptcp_end_dsn(skb1))) {
+ if (!after((u32)end_seq, (u32)mptcp_end_dsn(skb1))) {
/* All the bits are present. Drop. */
mptcp_drop(sk, skb);
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA);
return;
}
- if (after64(seq, MPTCP_SKB_CB(skb1)->map_seq)) {
+ if (after(seq, MPTCP_SKB_CB(skb1)->map_seq)) {
/* partial overlap:
* | skb |
* | skb1 |
@@ -401,7 +402,7 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
merge_right:
/* Remove other segments covered by skb. */
while ((skb1 = skb_rb_next(skb)) != NULL) {
- if (before64(end_seq, mptcp_end_dsn(skb1)))
+ if (before((u32)end_seq, (u32)mptcp_end_dsn(skb1)))
break;
rb_erase(&skb1->rbnode, &msk->out_of_order_queue);
mptcp_drop(sk, skb1);
@@ -424,10 +425,11 @@ static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset,
/* the skb map_seq accounts for the skb offset:
* mptcp_subflow_get_mapped_dsn() is based on the current tp->copied_seq
- * value
+ * value; note that end seq number is only available in 32bits format.
*/
- MPTCP_SKB_CB(skb)->map_seq = mptcp_subflow_get_mapped_dsn(subflow) -
- offset;
+ MPTCP_SKB_CB(skb)->map_seq64 = mptcp_subflow_get_mapped_dsn(subflow) -
+ offset;
+ MPTCP_SKB_CB(skb)->map_seq = (u32)MPTCP_SKB_CB(skb)->map_seq64;
MPTCP_SKB_CB(skb)->end_seq = MPTCP_SKB_CB(skb)->map_seq + copy_len;
MPTCP_SKB_CB(skb)->flags = offset ? MPTCP_SKB_CB_F_OFFSET : 0;
MPTCP_SKB_CB(skb)->has_rxtstamp = has_rxtstamp;
@@ -452,13 +454,14 @@ void __mptcp_sync_rcv_sequence(struct sock *sk)
if (!skb)
return;
- MPTCP_SKB_CB(skb)->map_seq = msk->tfo_iasn - skb->len;
+ MPTCP_SKB_CB(skb)->map_seq64 = msk->tfo_iasn - skb->len;
+ MPTCP_SKB_CB(skb)->map_seq = (u32)MPTCP_SKB_CB(skb)->map_seq64;
MPTCP_SKB_CB(skb)->end_seq = MPTCP_SKB_CB(skb)->map_seq + skb->len;
}
static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb)
{
- u64 copy_len = MPTCP_SKB_CB(skb)->end_seq - MPTCP_SKB_CB(skb)->map_seq;
+ u32 copy_len = MPTCP_SKB_CB(skb)->end_seq - MPTCP_SKB_CB(skb)->map_seq;
struct mptcp_sock *msk = mptcp_sk(sk);
u64 dsn = mptcp_dsn(skb);
struct sk_buff *tail;
@@ -495,8 +498,8 @@ static bool __mptcp_move_skb(struct sock *sk, struct sk_buff *skb)
}
/* Partial packet */
- if (after64(MPTCP_SKB_CB(skb)->end_seq, msk->ack_seq)) {
- copy_len = mptcp_end_dsn(skb) - msk->ack_seq;
+ if (after64(mptcp_end_dsn(skb), msk->ack_seq)) {
+ copy_len = (u32)mptcp_end_dsn(skb) - (u32)msk->ack_seq;
goto insert;
}
@@ -891,38 +894,37 @@ static bool __mptcp_ofo_queue(struct mptcp_sock *msk)
{
struct sock *sk = (struct sock *)msk;
struct sk_buff *skb, *tail;
+ u32 seq_delta, ack_seq;
bool moved = false;
struct rb_node *p;
- u64 end_seq;
p = rb_first(&msk->out_of_order_queue);
pr_debug("msk=%p empty=%d\n", msk, RB_EMPTY_ROOT(&msk->out_of_order_queue));
while (p) {
+ ack_seq = msk->ack_seq;
skb = rb_to_skb(p);
- if (after64(mptcp_dsn(skb), msk->ack_seq))
+ if (after((u32)mptcp_dsn(skb), ack_seq))
break;
p = rb_next(p);
rb_erase(&skb->rbnode, &msk->out_of_order_queue);
- if (unlikely(!after64(mptcp_end_dsn(skb), msk->ack_seq))) {
+ if (unlikely(!after((u32)mptcp_end_dsn(skb), ack_seq))) {
mptcp_drop(sk, skb);
MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA);
continue;
}
- end_seq = mptcp_end_dsn(skb);
+ seq_delta = (u32)mptcp_end_dsn(skb) - ack_seq;
tail = skb_peek_tail(&sk->sk_receive_queue);
if (!tail || !mptcp_try_coalesce(sk, tail, skb)) {
- int delta = msk->ack_seq - MPTCP_SKB_CB(skb)->map_seq;
-
- pr_debug("uncoalesced seq=%llx ack seq=%llx delta=%d\n",
- MPTCP_SKB_CB(skb)->map_seq, msk->ack_seq,
- delta);
+ pr_debug("uncoalesced seq=%x ack seq=%x delta=%d\n",
+ MPTCP_SKB_CB(skb)->map_seq, ack_seq,
+ ack_seq - MPTCP_SKB_CB(skb)->map_seq);
__skb_queue_tail(&sk->sk_receive_queue, skb);
}
- msk->bytes_received += end_seq - msk->ack_seq;
- WRITE_ONCE(msk->ack_seq, end_seq);
+ msk->bytes_received += seq_delta;
+ WRITE_ONCE(msk->ack_seq, msk->ack_seq + seq_delta);
moved = true;
}
return moved;
@@ -2212,7 +2214,7 @@ static int __mptcp_recvmsg_mskq(struct sock *sk, struct msghdr *msg,
int copied = 0;
skb_queue_walk_safe(&sk->sk_receive_queue, skb, tmp) {
- u64 offset = *seq - MPTCP_SKB_CB(skb)->map_seq;
+ u32 offset = (u32)(*seq) - MPTCP_SKB_CB(skb)->map_seq;
u32 data_len = skb->len - offset;
u32 count;
int err;
@@ -4635,7 +4637,7 @@ static struct sk_buff *mptcp_recv_skb(struct sock *sk, u32 *off)
mptcp_move_skbs(sk);
while ((skb = skb_peek(&sk->sk_receive_queue)) != NULL) {
- offset = msk->copied_seq - MPTCP_SKB_CB(skb)->map_seq;
+ offset = (u32)msk->copied_seq - MPTCP_SKB_CB(skb)->map_seq;
if (offset < skb->len) {
*off = offset;
return skb;
@@ -4885,11 +4887,23 @@ static int mptcp_napi_poll(struct napi_struct *napi, int budget)
return work_done;
}
+#define CHK_CB_FIELD(mptcp_field, tcp_field) \
+ ({ \
+ BUILD_BUG_ON(offsetof(struct mptcp_skb_cb, mptcp_field) != \
+ offsetof(struct tcp_skb_cb, tcp_field)); \
+ BUILD_BUG_ON(offsetofend(struct mptcp_skb_cb, mptcp_field) != \
+ offsetofend(struct tcp_skb_cb, tcp_field)); \
+ })
+
void __init mptcp_proto_init(void)
{
struct mptcp_delegated_action *delegated;
int cpu;
+ CHK_CB_FIELD(map_seq, seq);
+ CHK_CB_FIELD(end_seq, end_seq);
+ CHK_CB_FIELD(flags, tcp_flags);
+
mptcp_prot.h.hashinfo = tcp_prot.h.hashinfo;
if (percpu_counter_init(&mptcp_sockets_allocated, 0, GFP_KERNEL))
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index b6b23dcc304c..550071fe61ce 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -131,10 +131,12 @@
#define MPTCP_SKB_CB_F_OFFSET 0x0100
struct mptcp_skb_cb {
- u64 map_seq;
- u64 end_seq;
+ u32 map_seq;
+ u32 end_seq;
+ u32 unused;
u16 flags;
u8 has_rxtstamp;
+ u64 map_seq64;
};
#define MPTCP_SKB_CB(__skb) ((struct mptcp_skb_cb *)&((__skb)->cb[0]))
@@ -1386,12 +1388,12 @@ static inline bool subflow_simultaneous_connect(struct sock *sk)
static inline u64 mptcp_end_dsn(const struct sk_buff *skb)
{
- return MPTCP_SKB_CB(skb)->map_seq + skb->len;
+ return MPTCP_SKB_CB(skb)->map_seq64 + skb->len;
}
static inline u64 mptcp_dsn(const struct sk_buff *skb)
{
- return MPTCP_SKB_CB(skb)->map_seq + skb->len -
+ return MPTCP_SKB_CB(skb)->map_seq64 + skb->len -
(MPTCP_SKB_CB(skb)->end_seq - MPTCP_SKB_CB(skb)->map_seq);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH mptcp-next v27 7/8] mptcp: sync mptcp skb cb layout with tcp one
2026-11-14 14:01 ` [PATCH mptcp-next v27 7/8] mptcp: sync mptcp skb cb layout with tcp one Geliang Tang
@ 2026-11-14 14:41 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-11-14 14:41 UTC (permalink / raw)
To: Geliang Tang, mptcp
Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [High] mptcp: use of offset-shifted map_seq instead of mptcp_dsn() in
OoO queue
- [Medium] mptcp: copied_seq offset underflow in recv path for data
after DATA_FIN
--
commit 84c94fa4a2c64ea1454102c7e6fa9ff2a5c5884a
Author: Paolo Abeni <pabeni@redhat.com>
mptcp: sync mptcp skb cb layout with tcp one
This commit aligns the MPTCP skb control buffer layout with TCP's by
shrinking map_seq and end_seq to 32-bit values, adding a 64-bit map_seq64
field, and switching the receive and out-of-order paths to 32-bit sequence
arithmetic where possible.
> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
> index f4ab3bdd368f2..44f4a680a4bdb 100644
> --- a/net/mptcp/protocol.c
> +++ b/net/mptcp/protocol.c
[ ... ]
> @@ -256,8 +256,8 @@ static void mptcp_prune_ofo_queue(struct sock *sk,
> 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))
> + if (after(MPTCP_SKB_CB(in_skb)->map_seq,
> + MPTCP_SKB_CB(skb)->map_seq))
> break;
>
> pruned = true;
[ ... ]
> @@ -359,18 +360,18 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
> while (*p) {
> parent = *p;
> skb1 = rb_to_skb(parent);
> - if (before64(seq, MPTCP_SKB_CB(skb1)->map_seq)) {
> + if (before(seq, MPTCP_SKB_CB(skb1)->map_seq)) {
> p = &parent->rb_left;
> continue;
> }
> - if (before64(seq, mptcp_end_dsn(skb1))) {
> - if (!after64(end_seq, mptcp_end_dsn(skb1))) {
> + if (before(seq, (u32)mptcp_end_dsn(skb1))) {
> + if (!after((u32)end_seq, (u32)mptcp_end_dsn(skb1))) {
> /* All the bits are present. Drop. */
> mptcp_drop(sk, skb);
> MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA);
> return;
> }
> - if (after64(seq, MPTCP_SKB_CB(skb1)->map_seq)) {
> + if (after(seq, MPTCP_SKB_CB(skb1)->map_seq)) {
> /* partial overlap:
> * | skb |
> * | skb1 |
[ ... ]
> @@ -424,10 +425,11 @@ static void mptcp_init_skb(struct sock *ssk, struct sk_buff *skb, int offset,
>
> /* the skb map_seq accounts for the skb offset:
> * mptcp_subflow_get_mapped_dsn() is based on the current tp->copied_seq
> - * value
> + * value; note that end seq number is only available in 32bits format.
> */
> - MPTCP_SKB_CB(skb)->map_seq = mptcp_subflow_get_mapped_dsn(subflow) -
> - offset;
> + MPTCP_SKB_CB(skb)->map_seq64 = mptcp_subflow_get_mapped_dsn(subflow) -
> + offset;
> + MPTCP_SKB_CB(skb)->map_seq = (u32)MPTCP_SKB_CB(skb)->map_seq64;
> MPTCP_SKB_CB(skb)->end_seq = MPTCP_SKB_CB(skb)->map_seq + copy_len;
> MPTCP_SKB_CB(skb)->flags = offset ? MPTCP_SKB_CB_F_OFFSET : 0;
> MPTCP_SKB_CB(skb)->has_rxtstamp = has_rxtstamp;
[Severity: High]
Can comparing against MPTCP_SKB_CB(skb)->map_seq instead of
(u32)mptcp_dsn(skb) in mptcp_data_queue_ofo() and mptcp_prune_ofo_queue()
drop valid out-of-order segments or corrupt the RB-tree ordering?
mptcp_init_skb() subtracts offset from map_seq64 and map_seq, so for any
out-of-order skb with a non-zero subflow offset (for example, when a
subflow skb spans two DSS mappings and the first mapping was already
discarded), MPTCP_SKB_CB(skb1)->map_seq is (u32)mptcp_dsn(skb1) - offset
rather than the starting DSN of the mapped payload.
When a subsequent segment arrives covering the preceding range
[mptcp_dsn(skb1) - offset, mptcp_dsn(skb1)), its seq ((u32)mptcp_dsn(skb))
is not before MPTCP_SKB_CB(skb1)->map_seq. In mptcp_data_queue_ofo(), it
misses the rb_left branch and is either falsely dropped as duplicate data
(if end_seq <= mptcp_end_dsn(skb1)) or inserted into the right subtree of
skb1 despite having a smaller DSN (if end_seq > mptcp_end_dsn(skb1)),
stalling the connection once msk->ack_seq reaches the missing range.
mptcp_prune_ofo_queue() likewise compares raw map_seq values instead of
(u32)mptcp_dsn().
[ ... ]
> @@ -2212,7 +2214,7 @@ static int __mptcp_recvmsg_mskq(struct sock *sk, struct msghdr *msg,
> int copied = 0;
>
> skb_queue_walk_safe(&sk->sk_receive_queue, skb, tmp) {
> - u64 offset = *seq - MPTCP_SKB_CB(skb)->map_seq;
> + u32 offset = (u32)(*seq) - MPTCP_SKB_CB(skb)->map_seq;
> u32 data_len = skb->len - offset;
> u32 count;
> int err;
> @@ -4635,7 +4637,7 @@ static struct sk_buff *mptcp_recv_skb(struct sock *sk, u32 *off)
> mptcp_move_skbs(sk);
>
> while ((skb = skb_peek(&sk->sk_receive_queue)) != NULL) {
> - offset = msk->copied_seq - MPTCP_SKB_CB(skb)->map_seq;
> + offset = (u32)msk->copied_seq - MPTCP_SKB_CB(skb)->map_seq;
> if (offset < skb->len) {
> *off = offset;
> return skb;
[Severity: Medium]
Can offset underflow in __mptcp_recvmsg_mskq() and mptcp_recv_skb() if a
peer sends mapped data at or after DATA_FIN?
When mptcp_check_data_fin() processes an incoming DATA_FIN at
rcv_data_fin_seq, it advances msk->ack_seq to rcv_data_fin_seq + 1 without
advancing msk->copied_seq:
net/mptcp/protocol.c:mptcp_check_data_fin() {
...
if (mptcp_pending_data_fin(sk, &rcv_data_fin_seq)) {
WRITE_ONCE(msk->ack_seq, msk->ack_seq + 1);
WRITE_ONCE(msk->rcv_data_fin, 0);
WRITE_ONCE(sk->sk_shutdown, sk->sk_shutdown | RCV_SHUTDOWN);
...
}
Because __mptcp_move_skb() does not reject segments after DATA_FIN, a
subsequent segment with dsn == rcv_data_fin_seq + 1 matches
dsn == msk->ack_seq, increments msk->bytes_received, and is queued to
sk->sk_receive_queue with map_seq == (u32)(rcv_data_fin_seq + 1). When
__mptcp_recvmsg_mskq() or mptcp_recv_skb() inspects that skb with
msk->copied_seq == rcv_data_fin_seq, offset underflows to 0xffffffff >=
skb->len and mptcp_eat_recv_skb() frees the skb without updating
msk->bytes_consumed, leaving mptcp_data_avail(msk) permanently non-zero
and causing __mptcp_close() to trigger mptcp_do_fastclose().
If the post-DATA_FIN segment instead starts at rcv_data_fin_seq with its
first byte discarded by mptcp_subflow_discard_data() (offset == 1 in
mptcp_init_skb()), map_seq equals (u32)rcv_data_fin_seq, so offset in
__mptcp_recvmsg_mskq() evaluates to 0 instead of 1 and copies the
discarded prefix byte to userspace.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1794663678.git.tanggeliang@kylinos.cn?part=7
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH mptcp-next v27 8/8] mptcp: defer read_sock cleanup to mptcp_worker
2026-11-14 14:00 [PATCH mptcp-next v27 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
` (6 preceding siblings ...)
2026-11-14 14:01 ` [PATCH mptcp-next v27 7/8] mptcp: sync mptcp skb cb layout with tcp one Geliang Tang
@ 2026-11-14 14:01 ` Geliang Tang
2026-11-14 15:52 ` [PATCH mptcp-next v27 0/8] Reduce the differences between TCP and MPTCP for TLS usage MPTCP CI
8 siblings, 0 replies; 12+ messages in thread
From: Geliang Tang @ 2026-11-14 14:01 UTC (permalink / raw)
To: mptcp
From: Geliang Tang <tanggeliang@kylinos.cn>
This patch prepares __mptcp_read_sock() for future MPTCP TLS support,
where it may be reached from BH context. In that case, calling
mptcp_rcv_space_adjust() and mptcp_cleanup_rbuf() is not safe as both
expect process context.
Conditionally defer cleanup to mptcp_worker(): when the caller doesn't
own the socket lock (BH context), accumulate consumed bytes in
msk->read_copied and signal the worker through MPTCP_WORK_READ_COMPLETE.
When the caller owns the lock (process context), perform inline cleanup
to avoid delaying window updates and autotuning.
The existing backlog flush in mptcp_recv_skb() is now gated on
sock_owned_by_user(). This ensures process context callers flush the
backlog before processing skbs, while future BH context callers skip it.
The reason is that BH callers would already hold the msk spinlock, so
mptcp_data_lock() inside mptcp_move_skbs() would self-deadlock.
Initialize msk->read_copied in __mptcp_init_sock() and reset it in
mptcp_disconnect().
Note: The BH call path does not exist in the current tree. This is
groundwork for the upcoming MPTCP TLS series. Currently, the only
in-tree caller is mptcp_splice_read(), which holds lock_sock() and runs
in process context.
Suggested-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: Geliang Tang <tanggeliang@kylinos.cn>
---
net/mptcp/protocol.c | 38 +++++++++++++++++++++++++++++++++-----
net/mptcp/protocol.h | 2 ++
2 files changed, 35 insertions(+), 5 deletions(-)
diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
index 44f4a680a4bd..60ca6864c87e 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -3146,6 +3146,21 @@ static void mptcp_backlog_purge(struct sock *sk)
sk_mem_reclaim(sk);
}
+static void mptcp_read_complete(struct sock *sk)
+{
+ struct mptcp_sock *msk = mptcp_sk(sk);
+ u32 read_copied;
+
+ mptcp_data_lock(sk);
+ read_copied = msk->read_copied;
+ msk->read_copied = 0;
+ mptcp_data_unlock(sk);
+
+ mptcp_rcv_space_adjust(msk, read_copied);
+ if (read_copied > 0)
+ mptcp_cleanup_rbuf(msk, read_copied);
+}
+
static void mptcp_do_fastclose(struct sock *sk)
{
struct mptcp_subflow_context *subflow, *tmp;
@@ -3221,6 +3236,9 @@ static void mptcp_worker(struct work_struct *work)
if (test_and_clear_bit(MPTCP_WORK_RTX, &msk->flags))
__mptcp_retrans(sk);
+ if (test_and_clear_bit(MPTCP_WORK_READ_COMPLETE, &msk->flags))
+ mptcp_read_complete(sk);
+
fail_tout = msk->first ? READ_ONCE(mptcp_subflow_ctx(msk->first)->fail_tout) : 0;
if (fail_tout && time_after(jiffies, fail_tout))
mptcp_mp_fail_no_response(msk);
@@ -3264,6 +3282,7 @@ static void __mptcp_init_sock(struct sock *sk)
msk->last_data_sent = tcp_jiffies32;
msk->last_data_recv = tcp_jiffies32;
msk->last_ack_recv = tcp_jiffies32;
+ msk->read_copied = 0;
mptcp_pm_data_init(msk);
spin_lock_init(&msk->fallback_lock);
@@ -3711,6 +3730,7 @@ static int mptcp_disconnect(struct sock *sk, int flags)
msk->fastclosing = 0;
msk->tfo_iasn = 0;
msk->rcvd_dummy_seq = false;
+ msk->read_copied = 0;
mptcp_init_rtt_est(msk);
/* for fallback's sake */
@@ -4633,7 +4653,7 @@ static struct sk_buff *mptcp_recv_skb(struct sock *sk, u32 *off)
struct sk_buff *skb;
u32 offset;
- if (!list_empty(&msk->backlog_list))
+ if (sock_owned_by_user(sk) && !list_empty(&msk->backlog_list))
mptcp_move_skbs(sk);
while ((skb = skb_peek(&sk->sk_receive_queue)) != NULL) {
@@ -4650,6 +4670,7 @@ static struct sk_buff *mptcp_recv_skb(struct sock *sk, u32 *off)
/*
* Note:
* - It is assumed that the socket was locked by the caller.
+ * - Can be invoked in BH scope.
*/
static int __mptcp_read_sock(struct sock *sk, read_descriptor_t *desc,
sk_read_actor_t recv_actor, bool noack)
@@ -4691,11 +4712,18 @@ static int __mptcp_read_sock(struct sock *sk, read_descriptor_t *desc,
if (noack)
goto out;
- mptcp_rcv_space_adjust(msk, copied);
-
if (copied > 0) {
- mptcp_recv_skb(sk, &offset);
- mptcp_cleanup_rbuf(msk, copied);
+ if (sock_owned_by_user(sk)) {
+ /* Process context: inline cleanup */
+ mptcp_rcv_space_adjust(msk, copied);
+ mptcp_recv_skb(sk, &offset);
+ mptcp_cleanup_rbuf(msk, copied);
+ } else {
+ /* BH context: defer to worker */
+ msk->read_copied += copied;
+ set_bit(MPTCP_WORK_READ_COMPLETE, &msk->flags);
+ mptcp_schedule_work(sk);
+ }
}
out:
return copied;
diff --git a/net/mptcp/protocol.h b/net/mptcp/protocol.h
index 550071fe61ce..5a386a262ab2 100644
--- a/net/mptcp/protocol.h
+++ b/net/mptcp/protocol.h
@@ -117,6 +117,7 @@
#define MPTCP_FALLBACK_DONE 2
#define MPTCP_WORK_CLOSE_SUBFLOW 3
#define MPTCP_RTX_ENABLED 4
+#define MPTCP_WORK_READ_COMPLETE 5
/* MPTCP socket release cb flags */
#define MPTCP_PUSH_PENDING 1
@@ -328,6 +329,7 @@ struct mptcp_sock {
u32 last_data_sent;
u32 last_data_recv;
u32 last_ack_recv;
+ u32 read_copied;
unsigned long timer_ival;
u32 token;
u64 tfo_iasn;
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH mptcp-next v27 0/8] Reduce the differences between TCP and MPTCP for TLS usage
2026-11-14 14:00 [PATCH mptcp-next v27 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
` (7 preceding siblings ...)
2026-11-14 14:01 ` [PATCH mptcp-next v27 8/8] mptcp: defer read_sock cleanup to mptcp_worker Geliang Tang
@ 2026-11-14 15:52 ` MPTCP CI
8 siblings, 0 replies; 12+ messages in thread
From: MPTCP CI @ 2026-11-14 15:52 UTC (permalink / raw)
To: Geliang Tang; +Cc: mptcp
Hi Geliang,
Thank you for your modifications, that's great!
Our CI did some validations and here is its report:
- KVM Validation: normal (except selftest_mptcp_join): Success! ✅
- KVM Validation: normal (only selftest_mptcp_join): Success! ✅
- KVM Validation: debug (except selftest_mptcp_join): Success! ✅
- KVM Validation: debug (only selftest_mptcp_join): Success! ✅
- KVM Validation: btf-normal (only bpftest_all): Success! ✅
- KVM Validation: btf-debug (only bpftest_all): Success! ✅
- Perf: Success! ✅
- Task: https://github.com/multipath-tcp/mptcp_net-next/actions/runs/43603887983
Initiator: Patchew Applier
Commits: https://github.com/multipath-tcp/mptcp_net-next/commits/7919d2e41d50
Patchwork: https://patchwork.kernel.org/project/mptcp/list/?series=1211367
If there are some issues, you can reproduce them using the same environment as
the one used by the CI thanks to a docker image, e.g.:
$ cd [kernel source code]
$ docker run -v "${PWD}:${PWD}:rw" -w "${PWD}" --privileged --rm -it \
--pull always mptcp/mptcp-upstream-virtme-docker:latest \
auto-normal
For more details:
https://github.com/multipath-tcp/mptcp-upstream-virtme-docker
Please note that despite all the efforts that have been already done to have a
stable tests suite when executed on a public CI like here, it is possible some
reported issues are not due to your modifications. Still, do not hesitate to
help us improve that ;-)
Cheers,
MPTCP GH Action bot
Bot operated by Matthieu Baerts (NGI0 Core)
^ permalink raw reply [flat|nested] 12+ messages in thread