MPTCP Linux Development
 help / color / mirror / Atom feed
* [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage
@ 2026-11-13  9:58 Geliang Tang
  2026-11-13  9:58 ` [PATCH mptcp-net v26 1/8] mptcp: fix TFO SIOCINQ overflow in mptcp_inq_hint Geliang Tang
                   ` (8 more replies)
  0 siblings, 9 replies; 12+ messages in thread
From: Geliang Tang @ 2026-11-13  9:58 UTC (permalink / raw)
  To: mptcp

From: Geliang Tang <tanggeliang@kylinos.cn>

v26:
 - patch 2: move error printing into wait_for_remote_key() for accurate
   diagnostics. Simplify return values: 0 for success/timeout/skip,
   -1 for fatal errors.
 - patch 5: add offset parameter to __mptcp_sync_rcv_sequence() to correctly
   handle partial reads of TFO skb before sync.
 - patch 6: add DATA_FIN validation in __mptcp_move_skb() to reject skbs
   with map_seq64 >= rcv_data_fin_seq, preventing offset calculation underflow
   when a protocol-violating peer sends data after DATA_FIN.
 - patch 7: update DATA_FIN check to use map_seq64 instead of map_seq.

v25:
 - patch 2: replace usleep() with poll() in wait_for_remote_key() to
   detect dead connections via POLLHUP/POLLERR, preventing infinite loops
   when the connection is reset or the client exits early.
 - patch 6: instead of rewinding map_seq by offset (which caused OFO
   merge_right to replace validated data with unvalidated prefix), keep
   map_seq pointing to the actual start of new data (mapped_dsn). Set
   end_seq = map_seq + copy_len to exclude consumed prefix bytes. Add
   mptcp_offset() helper to compute prefix length on-the-fly as
   skb->len - (end_seq - map_seq), used in recvmsg path to skip prefix
   bytes. Remove mapping boundary pull logic (no longer needed). Remove
   OFO traversal F_OFFSET check (no longer needed with non-rewound
   map_seq). Keep F_OFFSET flag only for __mptcp_try_coalesce() to
   prevent coalescing skbs with prefix. Change MPTCP_SKB_CB_F_OFFSET from
   0x01 to 0x0100 to avoid collision with TCPHDR_FIN when flags field
   aliases tcp_flags.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1794304840.git.tanggeliang@kylinos.cn/

v24:
 - patch 1: fix mptcp_inq_hint() EOF hint logic. Only return 1 when
   queue is empty AND (TCP_CLOSE OR RCV_SHUTDOWN), not unconditionally
   when RCV_SHUTDOWN is set. This avoids returning N+1 instead of N when
   there is unread data after local shutdown or MP_FASTCLOSE.
 - patch 2: fix IPv4 TCP socket handling in wait_for_remote_key() by
   checking EOPNOTSUPP in addition to ENOPROTOOPT. Use poll_timeout
   instead of fixed 100ms timeout to avoid false failures on slow CI
   systems. Return different values for success/timeout/error to provide
   better diagnostics. Timeout is now a warning, not a failure, allowing
   the SIOCINQ check to proceed.
 - patch 6: fix pskb_pull() truesize corruption in mptcp_init_skb() when
   offset > skb_headlen(). Use skb_orphan() before pskb_pull() to allow
   pskb_expand_head() to properly update truesize, then recompute truesize
   manually and reattach with skb_set_owner_r(). Separate linear pull
   (skb_pull) from paged pull (pskb_pull) handling. Add MPTCP_MIB_RCVPRUNED
   counter for failure tracking. Fix failure path in
   __mptcp_move_skbs_from_subflow() to increment seq and maintain TCP/MPTCP
   state consistency, allowing MPTCP retransmission to fill gaps.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1794215614.git.tanggeliang@kylinos.cn/

v23:
 - patch 2: add SOL_MPTCP fallback definition for compatibility with
   older libc headers. Handle ENOPROTOOPT error from getsockopt() to
   skip the wait for TCP sockets or MPTCP sockets that fell back to TCP.
 - patch 5: move end_seq calculation change from patch 6 to patch 5,
   using MPTCP_SKB_CB(skb)->map_seq + skb->len instead of msk->tfo_iasn
   in __mptcp_sync_rcv_sequence().
 - patch 6: fix error handling in __mptcp_move_skbs_from_subflow() when
   mptcp_init_skb() fails. Instead of using continue which skips updating
   seq and more_data_avail, update seq and jump to the cleanup path to
   ensure tp->copied_seq and more_data_avail are properly updated.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1793969127.git.tanggeliang@kylinos.cn/

v22:
 - Squashed "mptcp: fix TFO map_seq sign extension in fastopen" into patch
   6 "mptcp: remove CB offset field" (series reduced from 9 to 8 patches).
 - patch 1: add RCV_SHUTDOWN check to increment hint_val by 1 when
   DATA_FIN is received, matching the previous ack_seq - map_seq behavior
   that included the FIN byte.
 - patch 2: replace fixed 10ms sleep with MPTCP_INFO polling for
   MPTCP_INFO_FLAG_REMOTE_KEY_RECEIVED flag. Add timeout handling with
   retries to ensure third ACK is processed before checking SIOCINQ.
 - patch 6: use before() for wrap-safe sequence comparison in mapping
   boundary detection. Use pskb_pull() instead of skb_pull() to handle
   non-linear skbs safely. Add bool return value to indicate
   success/failure.
 - patch 8: restore backlog flush in mptcp_recv_skb() with
   sock_owned_by_user() check to maintain existing behavior for process
   context callers while avoiding deadlock for future BH context callers.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1793964796.git.tanggeliang@kylinos.cn/

v21:
 - patch 1: new patch. Fix mptcp_inq_hint() to use mptcp_data_avail()
   which computes bytes_received - bytes_consumed instead of
   ack_seq - map_seq. This fixes INT_MAX overflow on passive TFO
   connections before the MPC 3rd ACK arrives.
 - patch 2: new patch. Add selftest to check TFO SIOCINQ doesn't return
   INT_MAX after accept().
 - patch 4: new patch. Fix fallback dummy mapping anchor in
   subflow_check_data_avail() by anchoring map_subflow_seq at
   TCP_SKB_CB(skb)->seq instead of copied_seq. This ensures
   mptcp_subflow_get_mapped_dsn() returns the correct DSN at copied_seq.
 - patch 7: added mapping boundary detection in mptcp_init_skb(). When
   offset > 0 and skb starts before current mapping, prefix bytes are
   removed via skb_pull() to prevent data corruption. Removed redundant
   fallback initialization in mptcp_propagate_state().
 - patch 9: kept msk_owned_by_me() assertion (reverted the
   DEBUG_NET_WARN_ON_ONCE replacement from v20).
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1793870710.git.tanggeliang@kylinos.cn/

v20:
 - patch 2: fix Medium issues from review. Move MPTCP_SYNC_SEQ check
   inside the loop under mptcp_data_lock() protection to avoid race with
   concurrent non-atomic cb_flags updates. Change from atomic
   test_and_clear_bit() to non-atomic __test_and_clear_bit() since we're
   under lock. Add msk->tfo_iasn = 0 reset in mptcp_disconnect() to
   prevent stale tfo_iasn from corrupting passive TFO fallback.
 - patch 3: new patch. Fix TFO map_seq sign extension in fastopen.c by
   casting to u64 before negation: -(u64)skb->len instead of -skb->len.
 - patch 4: updated from v19 patch 3. Moved the -(u64)skb->len fix to
   patch 3. Added comment in mptcp_init_skb() explaining why offset > 0
   with out-of-order mapping is correct: mptcp_subflow_get_mapped_dsn()
   returns the DSN for the byte at tp->copied_seq (position 'offset' in
   the skb), and consumed prefix bytes are part of the same logical DSN
   range.
 - patch 6: updated from v19 patch 5. Replace msk_owned_by_me() assertion
   with DEBUG_NET_WARN_ON_ONCE using lockdep_sock_is_held() for both
   process and BH contexts. Add conditional defer logic: inline cleanup
   when caller owns socket lock, defer to worker when caller doesn't.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1793695518.git.tanggeliang@kylinos.cn/

v19:
 - Replace mptcp_iasn() computed function with msk->tfo_iasn immutable
   snapshot (set in subflow_set_remote_key()), removing tfo_skb_len field
   and the DATA_FIN off-by-one concern.
 - Move MPTCP_SYNC_SEQ handling (test_and_clear_bit + copied_seq update)
   out of __mptcp_sync_rcv_sequence() into callers, since the function
   can be called without the socket lock held.
 - Patch 2: move MPTCP_SYNC_SEQ check in mptcp_release_cb() from
   post-loop to loop start (before backlog spooling). Add the same
   check in mptcp_move_skbs() to sync copied_seq before backlog
   processing when user holds the socket lock.
 - Patch 4: clamp max_seq to ack_seq in mptcp_data_queue_ofo() to
   prevent 64-bit after64() wrap-around when ack_seq > rcv_wnd_sent.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1793361629.git.tanggeliang@kylinos.cn/

v18:
 - Re-add patch 2 "mptcp: drop the cant_coalesce CB field".
 - Patch 3: simplify TFO sync by relying on patch 2's __mptcp_sync_rcv_sequence()
   instead of complex copied_seq checks. Use copied_seq += mptcp_iasn(msk).
 - Patch 4: move subflow.c changes to patch 2, use map_seq64 for TFO init.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1793349369.git.geliang@kernel.org/

v17:
 - Patch 2: add TFO synchronization: subflow_set_remote_key() preserves
   partial read progress by checking copied_seq against the initial TFO
   value before updating. Add WRITE_ONCE/READ_ONCE for ordering between
   BH (3rd ACK processing) and user-space readers. Add underflow guard
   (copied_seq >= map_seq) before offset subtraction in
   __mptcp_recvmsg_mskq() to protect MSG_PEEK traversals. Skip skbs
   entirely before copied_seq. Update subflow.c to set both map_seq
   (u32) and map_seq64 (u64) for TFO.
 - Patch 3: adapt to the new map_seq (u32) + map_seq64 (u64) layout
   introduced in patch 2.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1793326853.git.tanggeliang@kylinos.cn/

v16:
 - Simplified the series to retain only the core changes required by both
   kTLS and sockmap: drop the CB offset field, add copied_seq tracking,
   and sync the MPTCP skb CB layout with the TCP one.
 - Dropped patch 2, leaving the fastopen code unchanged.
 - Moved patches 6-11 into a separate series "prepare interfaces for kTLS
   and sockmap support".
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1793255126.git.tanggeliang@kylinos.cn/

v15:
 - Patch 2: drop mptcp_data_lock() in mptcp_inq_hint(), use READ_ONCE()
   and __test_and_clear_bit() instead. This issue is unrelated to this
   series and will be addressed in a separate patch later.
 - Patch 5: Guard mptcp_cleanup_rbuf() with read_copied > 0. Initialize/
   reset msk->read_copied. Drop redundant mptcp_recv_skb() in
   __mptcp_read_sock().
 - Patch 6: simplify memory ordering, drop smp_mb()/smp_rmb(), keep
   existing smp_mb__before_atomic(). Check SOCK_DONE inline.
 - Patch 9: drop tcp_sock_rate_check_app_limited() split, call
   tcp_rate_check_app_limited() directly as Paolo suggested. Add
   write_seq != snd_nxt early bail-out.
 - Patch 10: wrap assertion behind expect_all_features().
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1792718041.git.tanggeliang@kylinos.cn/

v14:
 - v13 can no longer be applied to the export branch, so it has been
   rebased here with the following corrections:
 - Patch 2: move locking in mptcp_inq_hint() to avoid races with ack_seq
   change from patch 3 to here. Update commit log.
 - Patch 3: add mptcp_data_lock() in __mptcp_move_skb() around copied_seq
   update. Remove local delta variable in __mptcp_ofo_queue(). Initialize
   msk->copied_seq to 0 in mptcp_propagate_state() fallback path.
 - Patch 4: add map_seq64 comparison in __mptcp_try_coalesce() for 64-bit
   sequence consistency.
 - New patch 11: rename mptcp_check_readable to mptcp_stream_is_readable,
   and align with TCP by calling sk_is_readable() for KTLS support.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1790734083.git.tanggeliang@kylinos.cn/

v13:
 - use smp_mb instead of smp_mb__before_atomic in mptcp_check_data_fin.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1788245079.git.tanggeliang@kylinos.cn/

v12:
 - Move sock_set_flag(SOCK_DONE) to after smp_mb__before_atomic() in
   mptcp_check_data_fin() and reuse the existing barrier rather than adding
   a new smp_wmb().
 - Sample SOCK_DONE before smp_rmb() in mptcp_inq_hint() and mptcp_inq(),
   so the smp_rmb() only orders SOCK_DONE read relative to the subsequent
   ack_seq/copied_seq reads.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1788171508.git.tanggeliang@kylinos.cn/

v11:
 - __mptcp_move_skb(): gate __mptcp_sync_rcv_sequence() on
   test_and_clear_bit(MPTCP_SYNC_SEQ) so the dummy mapping is only re-based
   after ack_seq = iasn.
 - a new patch to set and test SOCK_DONE.
 - use SOCK_DONE in mptcp_inq_hint() and mptcp_inq().
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1788099847.git.tanggeliang@kylinos.cn/

v10:
 - Drop "mptcp: use atomic64_t for locklessly accessed u64 fields"
   (v9 patch 1) and the squash to bpf_burst test (v9 patch 11).
   Series reduced from 11 to 9 patches.
 - Revert all atomic64_read()/atomic64_set() on msk->ack_seq back to
   plain reads and WRITE_ONCE() in patches 1-4. Remove the local u64
   ack_seq variables that only existed to hold atomic64_read() results.
 - mptcp_inq_hint: move test_bit(MPTCP_SYNC_SEQ) inside mptcp_data_lock()
   to close the race between the flag check and the ack_seq read. Revert
   atomic64_read() to plain msk->ack_seq under the lock.
 - mptcp_inq (patch 6): use READ_ONCE() for both ack_seq and copied_seq
   instead of atomic64_read(). Add comment for the FIN subtraction,
   mirroring tcp_inq(). Update commit log.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1787888115.git.tanggeliang@kylinos.cn/

v9:
 - Extend atomic64_t conversion from only msk->ack_seq to all locklessly
   accessed u64 fields: write_seq, snd_nxt, snd_una, wnd_end,
   bytes_received, bytes_consumed.
 - Add mptcp_data_lock() in mptcp_inq_hint() to read ack_seq and
   copied_seq atomically.
 - Add read_copied field and drain it under mptcp_data_lock() in
   mptcp_read_complete() to fix the BH vs worker data race.
 - New patches: mptcp_inq/peek_len and sendmsg_locked proto_ops.
 - Squash to "selftests/bpf: Add bpf_burst scheduler & test".
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1787827525.git.tanggeliang@kylinos.cn/

v8:
 - check MPTCP_SYNC_SEQ in mptcp_inq_hint:
       if (hint_val >= INT_MAX) {
               if (test_bit(MPTCP_SYNC_SEQ, &msk->cb_flags))
                       return 0;
               return INT_MAX;
       }
 - rename mptcp_sock_rate_check_app_limited to
   mptcp_rate_check_app_limited.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1787644449.git.tanggeliang@kylinos.cn/

v7:
 - Patch 1 is a new one to fix the existing issue Sashiko mentioned -
   using atomic64_t for msk->ack_seq.
 - Fix the map_subflow_seq setting in fallback mode in patch 4.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1787537436.git.tanggeliang@kylinos.cn/

v6:
 - Memory ordering for MPTCP_SYNC_SEQ: Add smp_wmb() before set_bit() in
   subflow_set_remote_key() and smp_rmb() after test_and_clear_bit() in
   __mptcp_move_skb() and mptcp_release_cb() to prevent data races on
   weakly-ordered architectures, ensuring ack_seq updates are visible
   before the flag is set and readers see the updated sequence after
   observing the flag.
 - Lockdep nested locking: Use lock_sock_fast_nested(ssk) instead of
   lock_sock_fast(ssk) in mptcp_sock_rate_check_app_limited() to suppress
   false positive recursive locking warnings when acquiring subflow socket
   locks while holding the parent MPTCP socket lock.
 - Receive window advertisement order: Swap mptcp_rcv_space_adjust() and
   mptcp_cleanup_rbuf() in mptcp_read_complete() to expand the receive
   buffer before evaluating window updates, ensuring ACKs advertise the
   new (larger) window size instead of delaying the advertisement until
   the next cycle.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1787446274.git.tanggeliang@kylinos.cn/

v5:
 - only patch 3 changed.
 - Fallback offset underflow: Initialize MPTCP sequence space to 0 in
   mptcp_propagate_state() when mp_opt == NULL, ensuring SKB's map_seq
   starts from 0 to match msk->copied_seq and prevent offset calculation
   underflow in fallback mode.
 - Stale peek_seq: Move peek_seq recomputation into the mptcp_move_skbs()
   continue path and add another recomputation after sk_wait_data(),
   ensuring peek_seq stays synchronized with msk->copied_seq updates from
   MPTCP_SYNC_SEQ processing in both paths.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1787368526.git.tanggeliang@kylinos.cn/

v4:
 - mptcp_recvmsg: move the peek_seq recompute from after sk_wait_data()
   to before the mptcp_move_skbs() continue check.
   mptcp_move_skbs() -> __mptcp_move_skb() may shift the head skb's
   map_seq to the IASN frame via __mptcp_sync_rcv_sequence(); if
   'continue' then exits the loop, the recompute is skipped, and the
   next __mptcp_recvmsg_mskq() computes offset with peek_seq in the old
   frame minus map_seq in the new frame. The underflow makes the offset
   test fail and the skb is skipped (and mptcp_recv_skb() actually frees
   the TFO skb via mptcp_eat_recv_skb()).
 - Make all three MPTCP_SYNC_SEQ bit accesses atomic:
   - subflow_set_remote_key:   __set_bit            -> set_bit
   - __mptcp_move_skb:         __test_and_clear_bit -> test_and_clear_bit
   - mptcp_release_cb:         __test_and_clear_bit -> test_and_clear_bit
   The flag is set in BH under mptcp_data_lock() (msk slock held) and
   cleared in user context under lock_sock() slow path (only owned=1,
   slock not held). The non-atomic RMW on the two sides races, which can
   lose the IASN sync.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1787295147.git.tanggeliang@kylinos.cn/

v3:
 - Patch 2, handle "offset" in __mptcp_sync_rcv_sequence().
 - Patch 3, update __mptcp_move_skb() in response to Sashiko comments:

 	if (__test_and_clear_bit(MPTCP_SYNC_SEQ, &msk->cb_flags))
		msk->copied_seq += mptcp_iasn(msk);

 - Patch 4, replace after64() to after() in mptcp_prune_ofo_queue(). The
   pre-existing issue raised by Sashiko regarding changing the type of
   ack_seq to atomic64_t is not addressed in this series.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1786445142.git.tanggeliang@kylinos.cn/

v2:
 - Patch 3, updated in response to Sashiko comments:

        if (unlikely(msk->rcvd_dummy_seq)) {
                msk->copied_seq += mptcp_iasn(msk);
                __mptcp_sync_rcv_sequence(sk);
                /* Release cb() would otherwise re-base copied_seq
                 * again.
                 */
                test_and_clear_bit(MPTCP_SYNC_SEQ, &msk->cb_flags);
        }

        /* Skip the already peeked data. */
        if (offset >= skb->len) {
                *last = skb;
                continue;
        }
 - Patch 5, replaced with Paolo's patch.
 - Patch 6, 7, new patches addressing app-limited conditions.
 - The patch "trim the duplicated skb head at receive enqueue" has been
   dropped, as it is no longer needed after Paolo updated the "mptcp:
   out-of-order queue pruning" series.
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1786158416.git.tanggeliang@kylinos.cn/

v1:
 - https://patchwork.kernel.org/project/mptcp/cover/cover.1785150300.git.tanggeliang@kylinos.cn/

The goal of this series is to reduce the differences between TCP and MPTCP
for TLS usage, in preparation for adding TLS over MPTCP support in the
future.

In previous versions [1], a struct tls_prot_ops was defined to represent
the interface differences between TCP and MPTCP, which contained the
following callbacks:

	struct sk_buff *(*recv_skb)(struct sock *sk, u32 *off);
	bool (*lock_is_held)(struct sock *sk);
	void (*read_done)(struct sock *sk, size_t len);
	u32 (*get_skb_seq)(struct sk_buff *skb);
	int (*skb_get_header)(const struct sk_buff *skb, int offset,
	                      void *to, int len);
	bool (*epollin_ready)(const struct sock *sk);
	void (*check_app_limited)(struct sock *sk);

In reality, some of these callbacks are unnecessary. This series aims to
eliminate the get_skb_seq(), skb_get_header(), and lock_is_held()
callbacks.

The first four patches come from Paolo's "mptcp: address stall under memory
pressure" series v5 [2], with only minor cleanup from my side.

They remove the CB offset field and sync the MPTCP skb CB layout with the
TCP one, so that we can obtain the TCP or MPTCP sequence number in a
unified way, e.g.:

	struct tls_skb_cb {
	    u32 seq;
	};

	#define TLS_SKB_CB(__skb) ((struct tls_skb_cb *)&((__skb)->cb[0]))

This eliminates the need for a separate get_skb_seq() callback.

Building on the removal of the CB offset field, I also added patch 5 that
trims the duplicated skb head at receive enqueue. With that in place, KTLS
can retrieve the record header via skb_copy_bits() directly, so there is no
longer any need for a dedicated MPTCP helper like mptcp_skb_get_header().
The skb_get_header() callback can thus be removed.

Patch 6 defers sk_data_ready to the worker, which avoids recursive locking
when TLS calls back into MPTCP under mptcp_data_lock(). With this change,
the lock_is_held() callback is no longer needed and can be removed.

[1]
https://patchwork.kernel.org/project/mptcp/cover/cover.1782123118.git.tanggeliang@kylinos.cn/
[2]
https://patchwork.kernel.org/project/mptcp/cover/cover.1778446731.git.pabeni@redhat.com/

Geliang Tang (4):
  mptcp: fix TFO SIOCINQ overflow in mptcp_inq_hint
  selftests: mptcp: connect: check TFO SIOCINQ overflow
  mptcp: fix fallback dummy mapping anchor
  mptcp: defer read_sock cleanup to mptcp_worker

Paolo Abeni (4):
  mptcp: drop the mptcp_ooo_try_coalesce() helper
  mptcp: drop the cant_coalesce CB field
  mptcp: remove CB offset field
  mptcp: sync mptcp skb cb layout with tcp one

 net/mptcp/fastopen.c                          |  16 +-
 net/mptcp/protocol.c                          | 274 +++++++++++-------
 net/mptcp/protocol.h                          |  18 +-
 net/mptcp/subflow.c                           |  12 +-
 .../selftests/net/mptcp/mptcp_connect.c       |  61 ++++
 5 files changed, 271 insertions(+), 110 deletions(-)

-- 
2.55.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH mptcp-net v26 1/8] mptcp: fix TFO SIOCINQ overflow in mptcp_inq_hint
  2026-11-13  9:58 [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
@ 2026-11-13  9:58 ` Geliang Tang
  2026-11-13  9:58 ` [PATCH mptcp-next v26 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-13  9:58 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 v26 2/8] selftests: mptcp: connect: check TFO SIOCINQ overflow
  2026-11-13  9:58 [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
  2026-11-13  9:58 ` [PATCH mptcp-net v26 1/8] mptcp: fix TFO SIOCINQ overflow in mptcp_inq_hint Geliang Tang
@ 2026-11-13  9:58 ` Geliang Tang
  2026-11-13  9:58 ` [PATCH mptcp-net v26 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-13  9:58 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-net v26 3/8] mptcp: fix fallback dummy mapping anchor
  2026-11-13  9:58 [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
  2026-11-13  9:58 ` [PATCH mptcp-net v26 1/8] mptcp: fix TFO SIOCINQ overflow in mptcp_inq_hint Geliang Tang
  2026-11-13  9:58 ` [PATCH mptcp-next v26 2/8] selftests: mptcp: connect: check TFO SIOCINQ overflow Geliang Tang
@ 2026-11-13  9:58 ` Geliang Tang
  2026-11-13  9:58 ` [PATCH mptcp-next v26 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-13  9:58 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 v26 4/8] mptcp: drop the mptcp_ooo_try_coalesce() helper
  2026-11-13  9:58 [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
                   ` (2 preceding siblings ...)
  2026-11-13  9:58 ` [PATCH mptcp-net v26 3/8] mptcp: fix fallback dummy mapping anchor Geliang Tang
@ 2026-11-13  9:58 ` Geliang Tang
  2026-11-13  9:58 ` [PATCH mptcp-next v26 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-13  9:58 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 v26 5/8] mptcp: drop the cant_coalesce CB field
  2026-11-13  9:58 [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
                   ` (3 preceding siblings ...)
  2026-11-13  9:58 ` [PATCH mptcp-next v26 4/8] mptcp: drop the mptcp_ooo_try_coalesce() helper Geliang Tang
@ 2026-11-13  9:58 ` Geliang Tang
  2026-11-13  9:58 ` [PATCH mptcp-next v26 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-13  9:58 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 v26 6/8] mptcp: remove CB offset field
  2026-11-13  9:58 [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
                   ` (4 preceding siblings ...)
  2026-11-13  9:58 ` [PATCH mptcp-next v26 5/8] mptcp: drop the cant_coalesce CB field Geliang Tang
@ 2026-11-13  9:58 ` Geliang Tang
  2026-11-13 10:28   ` sashiko-bot
  2026-11-13  9:58 ` [PATCH mptcp-next v26 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-13  9:58 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. A mapping boundary check using before() for proper 32-bit
modular arithmetic detects when the skb spans multiple DSS mappings
(offset > 0 and skb starts before the current mapping). In that case,
the consumed prefix bytes belong to an old mapping and must be removed
before setting map_seq. The function now returns bool to indicate success.

When removing the prefix, we handle linear and paged data separately.
For offset within linear data where offset <= skb_headlen, use skb_pull()
which is safe and doesn't modify truesize. For offset in paged data where
offset > skb_headlen, use pskb_pull() which may expand the skb head. To
avoid truesize corruption in pskb_expand_head(), we first orphan the skb
to disconnect it from the socket, perform the pull, recompute truesize
manually, then reattach with skb_set_owner_r(). This ensures proper memory
accounting.

When mptcp_init_skb() fails, the caller increments seq to skip the
problematic skb, records the failure via MPTCP_MIB_RCVPRUNED, and jumps
to cleanup to ensure tp->copied_seq and more_data_avail are properly
updated. This maintains TCP/MPTCP state consistency, and MPTCP-level
retransmission will fill any resulting gaps.

When the skb stays within a single mapping and offset > 0, map_seq is
rewound to cover the already-consumed prefix. A new MPTCP_SKB_CB_F_OFFSET
flag marks such skbs. __mptcp_try_coalesce() refuses to coalesce when the
from skb carries this flag, preventing an unvalidated prefix from being
merged into a validated buffer. mptcp_data_queue_ofo() skips the
rb_replace_node() replacement when the new skb covers an existing OFO
entry and carries the flag, so that previously validated data is not
overwritten by an unvalidated prefix; the new skb is inserted after the
existing entry instead, and the prefix is naturally skipped when the OFO
queue is later processed.

To prevent offset calculation underflow when a protocol-violating peer
sends data after DATA_FIN, __mptcp_move_skb() rejects skbs whose map_seq
is at or beyond rcv_data_fin_seq when DATA_FIN has been received. Since
copied_seq tracks only payload bytes and cannot advance past the DATA_FIN
sequence number, accepting data after DATA_FIN would cause the read
position calculation (copied_seq - map_seq) to underflow.

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 |   9 ++--
 net/mptcp/protocol.c | 117 ++++++++++++++++++++++++-------------------
 net/mptcp/protocol.h |   5 +-
 net/mptcp/subflow.c  |   6 ++-
 4 files changed, 79 insertions(+), 58 deletions(-)

diff --git a/net/mptcp/fastopen.c b/net/mptcp/fastopen.c
index e18b31028e7e..48298bfe85a4 100644
--- a/net/mptcp/fastopen.c
+++ b/net/mptcp/fastopen.c
@@ -45,10 +45,12 @@ void mptcp_fastopen_subflow_synack_set_params(struct mptcp_subflow_context *subf
 	subflow->ssn_offset += skb->len;
 	has_rxtstamp = TCP_SKB_CB(skb)->has_rxtstamp;
 
-	/* Only the sequence delta is relevant */
-	MPTCP_SKB_CB(skb)->map_seq = -skb->len;
+	/* The TFO segment data sits before the IASN; before receiving
+	 * the remote key, IASN is assumed being 0.
+	 */
+	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 +58,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..99dfcd0488a4 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_SKB_CB(to)->map_seq +
+					   to->len ||
+	    MPTCP_SKB_CB(from)->flags & MPTCP_SKB_CB_F_OFFSET ||
 	    ((to->len + from->len) > (limit >> 3)) ||
 	    !skb_try_coalesce(to, from, fragstolen, delta))
 		return false;
@@ -306,7 +307,7 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
 	struct sk_buff *skb1;
 
 	seq = MPTCP_SKB_CB(skb)->map_seq;
-	end_seq = MPTCP_SKB_CB(skb)->end_seq;
+	end_seq = MPTCP_SKB_CB(skb)->map_seq + skb->len;
 	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,8 @@ 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_SKB_CB(msk->ooo_last_skb)->map_seq +
+		      msk->ooo_last_skb->len)) {
 		MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOQUEUETAIL);
 		parent = &msk->ooo_last_skb->rbnode;
 		p = &parent->rb_right;
@@ -362,8 +364,9 @@ 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_SKB_CB(skb1)->map_seq + skb1->len)) {
+			if (!after64(end_seq,
+				     MPTCP_SKB_CB(skb1)->map_seq + skb1->len)) {
 				/* All the bits are present. Drop. */
 				mptcp_drop(sk, skb);
 				MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA);
@@ -400,7 +403,8 @@ 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_SKB_CB(skb1)->map_seq + skb1->len))
 			break;
 		rb_erase(&skb1->rbnode, &msk->out_of_order_queue);
 		mptcp_drop(sk, skb1);
@@ -425,9 +429,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,17 +455,24 @@ 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;
+	u64 dsn = MPTCP_SKB_CB(skb)->map_seq + skb->len - copy_len;
 	struct mptcp_sock *msk = mptcp_sk(sk);
 	struct sk_buff *tail;
 
+	if (READ_ONCE(msk->rcv_data_fin) &&
+	    !before64(MPTCP_SKB_CB(skb)->map_seq,
+		      READ_ONCE(msk->rcv_data_fin_seq))) {
+		mptcp_drop(sk, skb);
+		return false;
+	}
+
 	mptcp_borrow_fwdmem(sk, skb);
 
 	/* Be sure to sync the eventual fastopen dummy mapping before any other
@@ -469,7 +481,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,7 +499,7 @@ 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;
 	}
@@ -495,10 +507,6 @@ 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_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;
 		goto insert;
 	}
 
@@ -896,35 +904,35 @@ static bool __mptcp_ofo_queue(struct mptcp_sock *msk)
 	bool moved = false;
 	struct rb_node *p;
 	u64 end_seq;
+	u64 dsn;
 
 	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) {
 		skb = rb_to_skb(p);
-		if (after64(MPTCP_SKB_CB(skb)->map_seq, msk->ack_seq))
+		dsn = MPTCP_SKB_CB(skb)->map_seq + skb->len -
+		      (MPTCP_SKB_CB(skb)->end_seq - MPTCP_SKB_CB(skb)->map_seq);
+		if (after64(dsn, 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,
+		if (unlikely(!after64(MPTCP_SKB_CB(skb)->map_seq + skb->len,
 				      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_SKB_CB(skb)->map_seq + skb->len;
 		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 +2217,24 @@ 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;
-				*last = skb;
-				continue;
-			}
-
-			/* skip the already peeked data in the current skb */
-			delta = copied_total - total_data_len;
-			offset += delta;
-			data_len -= delta;
+		/* Skip the already peeked data. */
+		if (offset >= skb->len) {
+			*last = skb;
+			continue;
 		}
 
 		count = min_t(size_t, len - copied, data_len);
@@ -2255,14 +2254,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 +2398,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 +2441,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 +2460,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 +2474,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 +2484,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 +2531,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 +3711,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 +3922,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 +4646,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 +4688,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..e195993f7aec 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;
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

* [PATCH mptcp-next v26 7/8] mptcp: sync mptcp skb cb layout with tcp one
  2026-11-13  9:58 [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
                   ` (5 preceding siblings ...)
  2026-11-13  9:58 ` [PATCH mptcp-next v26 6/8] mptcp: remove CB offset field Geliang Tang
@ 2026-11-13  9:58 ` Geliang Tang
  2026-11-13 10:30   ` sashiko-bot
  2026-11-13  9:58 ` [PATCH mptcp-next v26 8/8] mptcp: defer read_sock cleanup to mptcp_worker Geliang Tang
  2026-11-13 11:43 ` [PATCH mptcp-next v26 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-13  9:58 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, coalescing
checks, and DATA_FIN validation. __mptcp_try_coalesce() gains a map_seq64
contiguity check to ensure skbs are truly adjacent in the 64-bit DSN space.
The DATA_FIN check in __mptcp_move_skb() now uses map_seq64 to compare
against rcv_data_fin_seq in the full 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 | 96 +++++++++++++++++++++++++-------------------
 net/mptcp/protocol.h |  6 ++-
 3 files changed, 62 insertions(+), 45 deletions(-)

diff --git a/net/mptcp/fastopen.c b/net/mptcp/fastopen.c
index 48298bfe85a4..bd52e9528b29 100644
--- a/net/mptcp/fastopen.c
+++ b/net/mptcp/fastopen.c
@@ -48,7 +48,8 @@ void mptcp_fastopen_subflow_synack_set_params(struct mptcp_subflow_context *subf
 	/* The TFO segment data sits before the IASN; before receiving
 	 * the remote key, IASN is assumed being 0.
 	 */
-	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;
@@ -58,7 +59,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 99dfcd0488a4..d124b702c957 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_SKB_CB(to)->map_seq +
-					   to->len ||
+	if (MPTCP_SKB_CB(from)->map_seq64 != MPTCP_SKB_CB(to)->map_seq64 +
+					     to->len ||
 	    MPTCP_SKB_CB(from)->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_SKB_CB(skb)->map_seq;
-	end_seq = MPTCP_SKB_CB(skb)->map_seq + skb->len;
+	end_seq = MPTCP_SKB_CB(skb)->map_seq64 + skb->len;
 	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,8 +348,8 @@ 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)->map_seq +
-		      msk->ooo_last_skb->len)) {
+	if (!before(seq, MPTCP_SKB_CB(msk->ooo_last_skb)->map_seq +
+		    msk->ooo_last_skb->len)) {
 		MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOQUEUETAIL);
 		parent = &msk->ooo_last_skb->rbnode;
 		p = &parent->rb_right;
@@ -360,19 +361,19 @@ 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_SKB_CB(skb1)->map_seq + skb1->len)) {
-			if (!after64(end_seq,
-				     MPTCP_SKB_CB(skb1)->map_seq + skb1->len)) {
+		if (before(seq, MPTCP_SKB_CB(skb1)->map_seq + skb1->len)) {
+			if (!after((u32)end_seq,
+				   MPTCP_SKB_CB(skb1)->map_seq + skb1->len)) {
 				/* 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    |
@@ -403,8 +404,8 @@ 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)->map_seq + skb1->len))
+		if (before((u32)end_seq,
+			   MPTCP_SKB_CB(skb1)->map_seq + skb1->len))
 			break;
 		rb_erase(&skb1->rbnode, &msk->out_of_order_queue);
 		mptcp_drop(sk, skb1);
@@ -427,10 +428,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;
@@ -455,19 +457,20 @@ 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;
-	u64 dsn = MPTCP_SKB_CB(skb)->map_seq + skb->len - copy_len;
+	u32 copy_len = MPTCP_SKB_CB(skb)->end_seq - MPTCP_SKB_CB(skb)->map_seq;
+	u64 dsn = MPTCP_SKB_CB(skb)->map_seq64 + skb->len - copy_len;
 	struct mptcp_sock *msk = mptcp_sk(sk);
 	struct sk_buff *tail;
 
 	if (READ_ONCE(msk->rcv_data_fin) &&
-	    !before64(MPTCP_SKB_CB(skb)->map_seq,
+	    !before64(MPTCP_SKB_CB(skb)->map_seq64,
 		      READ_ONCE(msk->rcv_data_fin_seq))) {
 		mptcp_drop(sk, skb);
 		return false;
@@ -505,8 +508,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_SKB_CB(skb)->end_seq - msk->ack_seq;
+	if (after64(MPTCP_SKB_CB(skb)->map_seq64 + skb->len, msk->ack_seq)) {
+		copy_len = MPTCP_SKB_CB(skb)->end_seq - (u32)msk->ack_seq;
 		goto insert;
 	}
 
@@ -901,42 +904,41 @@ 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;
 	u64 dsn;
 
 	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);
-		dsn = MPTCP_SKB_CB(skb)->map_seq + skb->len -
+		dsn = MPTCP_SKB_CB(skb)->map_seq64 + skb->len -
 		      (MPTCP_SKB_CB(skb)->end_seq - MPTCP_SKB_CB(skb)->map_seq);
-		if (after64(dsn, msk->ack_seq))
+		if (after((u32)dsn, ack_seq))
 			break;
 
 		p = rb_next(p);
 		rb_erase(&skb->rbnode, &msk->out_of_order_queue);
 
-		if (unlikely(!after64(MPTCP_SKB_CB(skb)->map_seq + skb->len,
-				      msk->ack_seq))) {
+		if (unlikely(!after(MPTCP_SKB_CB(skb)->map_seq + skb->len,
+				    ack_seq))) {
 			mptcp_drop(sk, skb);
 			MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA);
 			continue;
 		}
 
-		end_seq = MPTCP_SKB_CB(skb)->map_seq + skb->len;
+		seq_delta = MPTCP_SKB_CB(skb)->map_seq + skb->len - 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;
@@ -2226,7 +2228,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;
@@ -4646,7 +4648,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;
@@ -4896,11 +4898,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 e195993f7aec..90d67c170e7a 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]))
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH mptcp-next v26 8/8] mptcp: defer read_sock cleanup to mptcp_worker
  2026-11-13  9:58 [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
                   ` (6 preceding siblings ...)
  2026-11-13  9:58 ` [PATCH mptcp-next v26 7/8] mptcp: sync mptcp skb cb layout with tcp one Geliang Tang
@ 2026-11-13  9:58 ` Geliang Tang
  2026-11-13 11:43 ` [PATCH mptcp-next v26 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-13  9:58 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 d124b702c957..91523bdd1b30 100644
--- a/net/mptcp/protocol.c
+++ b/net/mptcp/protocol.c
@@ -3157,6 +3157,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);
+	int 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;
@@ -3232,6 +3247,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);
@@ -3275,6 +3293,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);
@@ -3722,6 +3741,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 */
@@ -4644,7 +4664,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) {
@@ -4661,6 +4681,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)
@@ -4702,11 +4723,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 90d67c170e7a..4fee626b33cb 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;
+	int		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 v26 6/8] mptcp: remove CB offset field
  2026-11-13  9:58 ` [PATCH mptcp-next v26 6/8] mptcp: remove CB offset field Geliang Tang
@ 2026-11-13 10:28   ` sashiko-bot
  0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-11-13 10:28 UTC (permalink / raw)
  To: Geliang Tang, mptcp

Thank you for your contribution! Sashiko AI review found 6 potential
issues to consider:
- [High] mptcp: subflow sk_rmem_alloc leak in __mptcp_move_skb() on
  rcv_data_fin drop
- [High] mptcp: rcv_data_fin guard bypass in __mptcp_move_skb()
  underflows offset
- [High] mptcp: out-of-order queue corruption and stall on non-zero skb
  offset
- [High] mptcp: wrong partial-packet copy_len in __mptcp_move_skb() when
  offset > 0
- [High] mptcp: __mptcp_try_coalesce() clobbers encoded offset on target
  skb
- [Medium] mptcp: commit message describes unimplemented
  mptcp_init_skb() changes
--

commit c03fb1e5a208813143363f01640f8ace04366cfd
Author: Paolo Abeni <pabeni@redhat.com>

mptcp: remove CB offset field

This commit replaces the per-skb MPTCP_SKB_CB(skb)->offset field with an
msk-level copied_seq field to track the read offset in the MPTCP receive
queue.

> In mptcp_init_skb(), map_seq is computed as mptcp_subflow_get_mapped_dsn()
> minus offset. A mapping boundary check using before() for proper 32-bit
> modular arithmetic detects when the skb spans multiple DSS mappings
> (offset > 0 and skb starts before the current mapping). In that case,
> the consumed prefix bytes belong to an old mapping and must be removed
> before setting map_seq. The function now returns bool to indicate success.
>
> When removing the prefix, we handle linear and paged data separately.
> For offset within linear data where offset <= skb_headlen, use skb_pull()
> which is safe and doesn't modify truesize. For offset in paged data where
> offset > skb_headlen, use pskb_pull() which may expand the skb head. To
> avoid truesize corruption in pskb_expand_head(), we first orphan the skb
> to disconnect it from the socket, perform the pull, recompute truesize
> manually, then reattach with skb_set_owner_r(). This ensures proper memory
> accounting.
>
> When mptcp_init_skb() fails, the caller increments seq to skip the
> problematic skb, records the failure via MPTCP_MIB_RCVPRUNED, and jumps
> to cleanup to ensure tp->copied_seq and more_data_avail are properly
> updated. This maintains TCP/MPTCP state consistency, and MPTCP-level
> retransmission will fill any resulting gaps.
>
> When the skb stays within a single mapping and offset > 0, map_seq is
> rewound to cover the already-consumed prefix. A new MPTCP_SKB_CB_F_OFFSET
> flag marks such skbs. __mptcp_try_coalesce() refuses to coalesce when the
> from skb carries this flag, preventing an unvalidated prefix from being
> merged into a validated buffer. mptcp_data_queue_ofo() skips the
> rb_replace_node() replacement when the new skb covers an existing OFO
> entry and carries the flag, so that previously validated data is not
> overwritten by an unvalidated prefix; the new skb is inserted after the
> existing entry instead, and the prefix is naturally skipped when the OFO
> queue is later processed.

[Severity: Medium]
Does the commit message match the actual code changes in this patch?
mptcp_init_skb() still returns void and does not check mapping boundaries
or call skb_pull() / pskb_pull(), __mptcp_move_skbs_from_subflow() has no
failure handling or MPTCP_MIB_RCVPRUNED accounting, and
mptcp_data_queue_ofo() does not check MPTCP_SKB_CB_F_OFFSET before calling
rb_replace_node().

> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
> index 41fc2429d2fed..99dfcd0488a45 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_SKB_CB(to)->map_seq +
> +					   to->len ||
> +	    MPTCP_SKB_CB(from)->flags & MPTCP_SKB_CB_F_OFFSET ||
>  	    ((to->len + from->len) > (limit >> 3)) ||
>  	    !skb_try_coalesce(to, from, fragstolen, delta))
>  		return false;

[Severity: High]
Does coalescing into a target skb that had offset > 0 lose its encoded
offset?

When to has offset > 0, mptcp_init_skb() sets to->map_seq = mapped_dsn -
offset and to->end_seq = to->map_seq + to->len - offset, so __mptcp_move_skb()
and __mptcp_ofo_queue() can recover offset from to->len - (to->end_seq -
to->map_seq).  When from (with offset == 0 and from->end_seq ==
from->map_seq + from->len) is coalesced into to in __mptcp_add_backlog()
or mptcp_data_queue_ofo(), __mptcp_try_coalesce() does:

net/mptcp/protocol.c:__mptcp_try_coalesce() {
    ...
	MPTCP_SKB_CB(to)->end_seq = MPTCP_SKB_CB(from)->end_seq;
	return true;
}

That sets to->end_seq to to->map_seq + to->len, wiping out the offset difference.
When to is later spooled from the backlog or out-of-order queue, dsn is
computed as mapped_dsn - offset instead of mapped_dsn, causing the stale
prefix bytes in to->data[0..offset - 1] to be treated as valid unconsumed
payload.

> @@ -306,7 +307,7 @@ static void mptcp_data_queue_ofo(struct mptcp_sock *msk, struct sk_buff *skb)
>  	struct sk_buff *skb1;
>  
>  	seq = MPTCP_SKB_CB(skb)->map_seq;
> -	end_seq = MPTCP_SKB_CB(skb)->end_seq;
> +	end_seq = MPTCP_SKB_CB(skb)->map_seq + skb->len;
>  	max_seq = atomic64_read(&msk->rcv_wnd_sent);

[ ... ]

> @@ -346,7 +347,8 @@ 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_SKB_CB(msk->ooo_last_skb)->map_seq +
> +		      msk->ooo_last_skb->len)) {
>  		MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_OFOQUEUETAIL);
>  		parent = &msk->ooo_last_skb->rbnode;
>  		p = &parent->rb_right;
> @@ -362,8 +364,9 @@ 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_SKB_CB(skb1)->map_seq + skb1->len)) {
> +			if (!after64(end_seq,
> +				     MPTCP_SKB_CB(skb1)->map_seq + skb1->len)) {
>  				/* All the bits are present. Drop. */
>  				mptcp_drop(sk, skb);
>  				MPTCP_INC_STATS(sock_net(sk), MPTCP_MIB_DUPDATA);
> @@ -400,7 +403,8 @@ 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_SKB_CB(skb1)->map_seq + skb1->len))
>  			break;
>  		rb_erase(&skb1->rbnode, &msk->out_of_order_queue);
>  		mptcp_drop(sk, skb1);
> @@ -425,9 +429,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: High]
Can an out-of-order skb with offset > 0 cause mptcp_data_queue_ofo() to
drop valid segments and stall the receive queue?

When offset > 0, mptcp_init_skb() rewinds MPTCP_SKB_CB(skb)->map_seq to
mapped_dsn - offset while the valid unconsumed payload only starts at
mapped_dsn.  mptcp_data_queue_ofo() uses seq = MPTCP_SKB_CB(skb)->map_seq
(mapped_dsn - offset) to position skb and deduplicate overlapping ranges,
so any valid out-of-order segment skb1 covering [mapped_dsn - offset,
mapped_dsn) is either replaced via rb_replace_node() / merge_right if it
was queued first, or dropped as MPTCP_MIB_DUPDATA if it arrives after skb.

When msk->ack_seq later reaches mapped_dsn - offset, __mptcp_ofo_queue()
checks dsn = mapped_dsn > msk->ack_seq and stops draining the RB-tree
because the segment covering [mapped_dsn - offset, mapped_dsn) was
dropped, stalling the connection.

[ ... ]

> @@ -450,17 +455,24 @@ void __mptcp_sync_rcv_sequence(struct sock *sk)

[ ... ]

>  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;
> +	u64 dsn = MPTCP_SKB_CB(skb)->map_seq + skb->len - copy_len;
>  	struct mptcp_sock *msk = mptcp_sk(sk);
>  	struct sk_buff *tail;
>  
> +	if (READ_ONCE(msk->rcv_data_fin) &&
> +	    !before64(MPTCP_SKB_CB(skb)->map_seq,
> +		      READ_ONCE(msk->rcv_data_fin_seq))) {
> +		mptcp_drop(sk, skb);
> +		return false;
> +	}
> +
>  	mptcp_borrow_fwdmem(sk, skb);

[Severity: High]
Does calling mptcp_drop() before mptcp_borrow_fwdmem() leak
ssk->sk_rmem_alloc on the subflow socket?

Both callers of __mptcp_move_skb() call mptcp_subflow_lend_fwdmem() first,
which clears skb->destructor = NULL while leaving skb->truesize charged to
ssk->sk_rmem_alloc until mptcp_borrow_fwdmem() transfers the forward
memory to sk and subtracts skb->truesize from ssk->sk_rmem_alloc.
Dropping skb before mptcp_borrow_fwdmem() skips that uncharge on ssk (so
closing the subflow triggers the sk_rmem_alloc warning in
inet_sock_destruct()) and calls sk_mem_reclaim(sk) without having credited
sk->sk_forward_alloc.

[Severity: High]
Can data arriving after DATA_FIN has been processed bypass this
rcv_data_fin check and underflow the receive offset?

Once mptcp_check_data_fin() processes the DATA_FIN, it increments
msk->ack_seq to rcv_data_fin_seq + 1 and clears msk->rcv_data_fin to 0:

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);
    ...
}

If a peer then sends a segment at dsn = rcv_data_fin_seq + 1 on an open
subflow, READ_ONCE(msk->rcv_data_fin) is 0, so __mptcp_move_skb() accepts
the skb as in-sequence (dsn == msk->ack_seq) and queues it with map_seq =
rcv_data_fin_seq + 1 while msk->copied_seq is at most rcv_data_fin_seq.
In __mptcp_recvmsg_mskq(), offset = *seq - MPTCP_SKB_CB(skb)->map_seq
underflows and skips the skb without consuming or freeing it.  In
addition, while msk->rcv_data_fin is still set, checking map_seq instead
of map_seq + skb->len misses segments that straddle rcv_data_fin_seq (or
have map_seq < rcv_data_fin_seq <= dsn due to offset > 0) and lets them
advance msk->ack_seq past rcv_data_fin_seq.

[ ... ]

> @@ -495,10 +507,6 @@ 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_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;
>  		goto insert;
>  	}

[Severity: High]
Does the partial-packet check use the wrong end sequence when offset > 0?

In mptcp_init_skb(), MPTCP_SKB_CB(skb)->end_seq is set to map_seq +
copy_len (= dsn + copy_len - offset), which is offset bytes less than the
actual end of the segment (map_seq + skb->len).  When before64(dsn,
msk->ack_seq) and offset > 0, using MPTCP_SKB_CB(skb)->end_seq here
falsely drops the skb as duplicate data if msk->ack_seq falls in
[end_seq, map_seq + skb->len), or under-increments msk->ack_seq and
msk->bytes_received by offset bytes while __mptcp_recvmsg_mskq() and
__mptcp_read_sock() still copy up to skb->len (advancing msk->copied_seq
past msk->ack_seq).

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1794563432.git.tanggeliang@kylinos.cn?part=6

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH mptcp-next v26 7/8] mptcp: sync mptcp skb cb layout with tcp one
  2026-11-13  9:58 ` [PATCH mptcp-next v26 7/8] mptcp: sync mptcp skb cb layout with tcp one Geliang Tang
@ 2026-11-13 10:30   ` sashiko-bot
  0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-11-13 10:30 UTC (permalink / raw)
  To: Geliang Tang, mptcp

Thank you for your contribution! Sashiko AI review found 5 potential
issues to consider:
- [High] mptcp: u32 underflow of partial-packet copy_len in
  __mptcp_move_skb()
- [High] mptcp: __mptcp_try_coalesce() overwrites target skb offset in
  end_seq
- [High] mptcp: mptcp_data_queue_ofo() uses map_seq instead of payload
  start dsn
- [High] mptcp: rmem and fwdmem leak in __mptcp_move_skb() on
  rcv_data_fin drop
- [Medium] mptcp: incomplete DATA_FIN sequence and state check in
  __mptcp_move_skb()
--

commit af240c23f3ffb2394bc65420f6d1a5d0d83763e5
Author: Paolo Abeni <pabeni@redhat.com>

mptcp: sync mptcp skb cb layout with tcp one

This commit aligns struct mptcp_skb_cb with struct tcp_skb_cb by converting
map_seq and end_seq to u32, adding a 64-bit map_seq64 field, and updating
the receive and out-of-order paths to use 32-bit sequence helpers where
applicable.

> diff --git a/net/mptcp/protocol.c b/net/mptcp/protocol.c
> index 99dfcd0488a45..d124b702c9576 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_SKB_CB(to)->map_seq +
> -					   to->len ||
> +	if (MPTCP_SKB_CB(from)->map_seq64 != MPTCP_SKB_CB(to)->map_seq64 +
> +					     to->len ||
>  	    MPTCP_SKB_CB(from)->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;

[Severity: High]
Does assigning MPTCP_SKB_CB(to)->end_seq = MPTCP_SKB_CB(from)->end_seq lose
the subflow offset encoded in to when to has MPTCP_SKB_CB_F_OFFSET set?

mptcp_init_skb() sets end_seq to map_seq + copy_len (which is map_seq +
skb->len - offset), and __mptcp_move_skb() and __mptcp_ofo_queue() recover
copy_len = end_seq - map_seq and dsn = map_seq64 + skb->len - copy_len.
When from (with offset == 0 and end_seq == from->map_seq + from->len) is
coalesced into a target skb that had offset > 0, overwriting to->end_seq
with from->end_seq instead of adding from->len makes to->end_seq -
to->map_seq equal the full new to->len.  When the coalesced skb is later
dequeued from the backlog or out-of-order queue, its recovered dsn is
shifted backwards by offset bytes, causing premature dequeue across gaps and
delivering stale subflow prefix bytes to user space.

[ ... ]

> @@ -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_SKB_CB(skb)->map_seq;
> -	end_seq = MPTCP_SKB_CB(skb)->map_seq + skb->len;
> +	end_seq = MPTCP_SKB_CB(skb)->map_seq64 + skb->len;
>  	max_seq = atomic64_read(&msk->rcv_wnd_sent);

[ ... ]

> @@ -360,19 +361,19 @@ 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_SKB_CB(skb1)->map_seq + skb1->len)) {
> -			if (!after64(end_seq,
> -				     MPTCP_SKB_CB(skb1)->map_seq + skb1->len)) {
> +		if (before(seq, MPTCP_SKB_CB(skb1)->map_seq + skb1->len)) {
> +			if (!after((u32)end_seq,
> +				   MPTCP_SKB_CB(skb1)->map_seq + skb1->len)) {
>  				/* 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)) {

[Severity: High]
Should mptcp_data_queue_ofo() order and deduplicate out-of-order segments
using the start DSN of their mapped payload rather than
MPTCP_SKB_CB(skb)->map_seq?

When an out-of-order skb has offset > 0, map_seq is dsn - offset, so
mptcp_data_queue_ofo() treats the unmapped subflow prefix [dsn - offset,
dsn) as present data in msk->out_of_order_queue.  A valid out-of-order
segment covering [dsn - offset, dsn) is then dropped as MPTCP_MIB_DUPDATA
(or evicted by rb_replace_node() / merge_right), while __mptcp_ofo_queue()
computes the true dsn and waits for msk->ack_seq to reach dsn before
dequeuing the skb, stalling the connection.

[ ... ]

> @@ -455,19 +457,20 @@ 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;
> -	u64 dsn = MPTCP_SKB_CB(skb)->map_seq + skb->len - copy_len;
> +	u32 copy_len = MPTCP_SKB_CB(skb)->end_seq - MPTCP_SKB_CB(skb)->map_seq;
> +	u64 dsn = MPTCP_SKB_CB(skb)->map_seq64 + skb->len - copy_len;
>  	struct mptcp_sock *msk = mptcp_sk(sk);
>  	struct sk_buff *tail;
>  
>  	if (READ_ONCE(msk->rcv_data_fin) &&
> -	    !before64(MPTCP_SKB_CB(skb)->map_seq,
> +	    !before64(MPTCP_SKB_CB(skb)->map_seq64,
>  		      READ_ONCE(msk->rcv_data_fin_seq))) {

[Severity: High]
Does dropping skb in this rcv_data_fin check before calling
mptcp_borrow_fwdmem(sk, skb) leak ssk->sk_rmem_alloc and lent forward
memory?

Callers of __mptcp_move_skb() first call mptcp_subflow_lend_fwdmem(), which
clears skb->destructor while leaving skb->truesize charged to
ssk->sk_rmem_alloc.  Because mptcp_drop(sk, skb) is called here before
mptcp_borrow_fwdmem(sk, skb) transfers the forward memory to sk and
subtracts skb->truesize from ssk->sk_rmem_alloc, ssk->sk_rmem_alloc remains
permanently charged when the subflow socket closes.

[Severity: Medium]
Can segments spanning or arriving after rcv_data_fin_seq bypass this
DATA_FIN check?

Checking MPTCP_SKB_CB(skb)->map_seq64 instead of MPTCP_SKB_CB(skb)->map_seq64
+ skb->len accepts segments that start before rcv_data_fin_seq (including
segments at rcv_data_fin_seq with offset > 0) and extend past it, advancing
msk->ack_seq past rcv_data_fin_seq so mptcp_pending_data_fin() never
matches.  In addition, once mptcp_check_data_fin() processes DATA_FIN, it
clears msk->rcv_data_fin to 0 and increments msk->ack_seq to
rcv_data_fin_seq + 1:

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);
}

A subsequent segment at rcv_data_fin_seq + 1 then sees msk->rcv_data_fin == 0
and dsn == msk->ack_seq and is queued to sk_receive_queue while
msk->copied_seq stays at rcv_data_fin_seq, causing offset in
__mptcp_recvmsg_mskq() to wrap to U32_MAX and skip freeing the skb.

> @@ -505,8 +508,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_SKB_CB(skb)->end_seq - msk->ack_seq;
> +	if (after64(MPTCP_SKB_CB(skb)->map_seq64 + skb->len, msk->ack_seq)) {
> +		copy_len = MPTCP_SKB_CB(skb)->end_seq - (u32)msk->ack_seq;
>  		goto insert;
>  	}

[Severity: High]
Can copy_len undercount by offset or underflow u32 when a partial packet has
a non-zero subflow offset?

mptcp_init_skb() sets MPTCP_SKB_CB(skb)->end_seq to map_seq + copy_len,
which is map_seq + skb->len - offset.  When offset > 0 and msk->ack_seq
falls in the last offset bytes of the skb ([map_seq64 + skb->len - offset,
map_seq64 + skb->len)), the after64() check passes while end_seq <=
(u32)msk->ack_seq, so MPTCP_SKB_CB(skb)->end_seq - (u32)msk->ack_seq wraps
around to ~4 GiB and corrupts msk->bytes_received and msk->ack_seq at
insert.  Should this compute MPTCP_SKB_CB(skb)->map_seq + skb->len -
(u32)msk->ack_seq, matching __mptcp_ofo_queue()?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1794563432.git.tanggeliang@kylinos.cn?part=7

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage
  2026-11-13  9:58 [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
                   ` (7 preceding siblings ...)
  2026-11-13  9:58 ` [PATCH mptcp-next v26 8/8] mptcp: defer read_sock cleanup to mptcp_worker Geliang Tang
@ 2026-11-13 11:43 ` MPTCP CI
  8 siblings, 0 replies; 12+ messages in thread
From: MPTCP CI @ 2026-11-13 11:43 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/43407382621

Initiator: Patchew Applier
Commits: https://github.com/multipath-tcp/mptcp_net-next/commits/a83eef20a122
Patchwork: https://patchwork.kernel.org/project/mptcp/list/?series=1210354


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

end of thread, other threads:[~2026-11-13 11:43 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13  9:58 [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage Geliang Tang
2026-11-13  9:58 ` [PATCH mptcp-net v26 1/8] mptcp: fix TFO SIOCINQ overflow in mptcp_inq_hint Geliang Tang
2026-11-13  9:58 ` [PATCH mptcp-next v26 2/8] selftests: mptcp: connect: check TFO SIOCINQ overflow Geliang Tang
2026-11-13  9:58 ` [PATCH mptcp-net v26 3/8] mptcp: fix fallback dummy mapping anchor Geliang Tang
2026-11-13  9:58 ` [PATCH mptcp-next v26 4/8] mptcp: drop the mptcp_ooo_try_coalesce() helper Geliang Tang
2026-11-13  9:58 ` [PATCH mptcp-next v26 5/8] mptcp: drop the cant_coalesce CB field Geliang Tang
2026-11-13  9:58 ` [PATCH mptcp-next v26 6/8] mptcp: remove CB offset field Geliang Tang
2026-11-13 10:28   ` sashiko-bot
2026-11-13  9:58 ` [PATCH mptcp-next v26 7/8] mptcp: sync mptcp skb cb layout with tcp one Geliang Tang
2026-11-13 10:30   ` sashiko-bot
2026-11-13  9:58 ` [PATCH mptcp-next v26 8/8] mptcp: defer read_sock cleanup to mptcp_worker Geliang Tang
2026-11-13 11:43 ` [PATCH mptcp-next v26 0/8] Reduce the differences between TCP and MPTCP for TLS usage MPTCP CI

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox