mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net 0/4] sctp: fix I-FORWARD-TSN handling with user message interleaving
@ 2026-10-24 12:06 Tim Budding
  2026-10-24 12:06 ` [PATCH net 1/4] sctp: purge abandoned I-DATA fragments by stream and MID, not by TSN Tim Budding
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Tim Budding @ 2026-10-24 12:06 UTC (permalink / raw)
  To: netdev, linux-sctp
  Cc: Xin Long, Marcelo Ricardo Leitner, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Shuah Khan, linux-kselftest, linux-kernel, Tim Budding

With user message interleaving (RFC 8260), fragments of messages on
different streams can be interleaved in TSN space. When a PR-SCTP
message is abandoned, the sender reports it in an I-FORWARD-TSN, which
carries a New Cumulative TSN and the stream, U bit and MID of the
skipped messages. The receiver still uses the New Cumulative TSN as if
it delimited messages, which loses or corrupts unrelated messages:

  TSN  stream  MID
    0    0      0    B   64KB reliable message on stream 0
  ...
    5    0      0
    6    1      0    BE  PR-SCTP message on stream 1, lost, abandoned
    7    0      0
  ...
  I-FORWARD-TSN new_cum=6 skip=[sid=1 mid=0]

The receiver frees the stream 0 fragments with TSNs 0-5 together with
the abandoned message. They have already been acked and are not
retransmitted, so the stream 0 message is never delivered and the
stream stays blocked.

Patch 1 frees reassembly fragments per I-FORWARD-TSN skip entry (stream,
U bit and MID) instead of by TSN. Patch 2 aborts an unordered partial
delivery when the message being delivered is the skipped one, instead
of merging it with the next unordered message on the stream. Patch 3
stops treating an I-FORWARD-TSN as an association reset when its New
Cumulative TSN is the highest TSN seen, which aborted partial delivery
on all streams and could abort the association. Patch 2 comes first
because the code that patch 3 removes also aborted that unordered
partial delivery in the leading edge case.

Patch 4 adds a selftest. Its ordered, unordered, leading_edge and uo_pd
tests fail without the corresponding fix and pass with it; its reset
test checks that an SSN/TSN association reset still works.

Tested with the new selftest, sctp_vrf.sh and the lksctp-tools
functional tests, also on a kernel with KASAN, lockdep and kmemleak
enabled. W=1 and sparse show no new warnings.

The first bug showed up as message loss in real use, and I found its
cause by AI-assisted code inspection. An AI assistant found the other
two while I was fixing it. The assistant also helped write the patches,
the selftest and these messages; I have reviewed and tested all of it.

Tim Budding (4):
  sctp: purge abandoned I-DATA fragments by stream and MID, not by TSN
  sctp: abort unordered partial delivery when its message is skipped
  sctp: don't treat an I-FORWARD-TSN as an association reset
  selftests: net: add SCTP I-FORWARD-TSN tests with message interleaving

 net/sctp/stream.c                           |   2 +
 net/sctp/stream_interleave.c                |  34 +-
 tools/testing/selftests/net/.gitignore      |   1 +
 tools/testing/selftests/net/Makefile        |   2 +
 tools/testing/selftests/net/sctp_ifwdtsn.c  | 538 ++++++++++++++++++++
 tools/testing/selftests/net/sctp_ifwdtsn.sh | 141 +++++
 6 files changed, 696 insertions(+), 22 deletions(-)
 create mode 100644 tools/testing/selftests/net/sctp_ifwdtsn.c
 create mode 100755 tools/testing/selftests/net/sctp_ifwdtsn.sh


base-commit: 7076f684d5132e6a6145e1da2fa74771ecd052e5
-- 
2.34.1


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

* [PATCH net 1/4] sctp: purge abandoned I-DATA fragments by stream and MID, not by TSN
  2026-10-24 12:06 [PATCH net 0/4] sctp: fix I-FORWARD-TSN handling with user message interleaving Tim Budding
@ 2026-10-24 12:06 ` Tim Budding
  2026-10-24 12:06 ` [PATCH net 2/4] sctp: abort unordered partial delivery when its message is skipped Tim Budding
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Tim Budding @ 2026-10-24 12:06 UTC (permalink / raw)
  To: netdev, linux-sctp
  Cc: Xin Long, Marcelo Ricardo Leitner, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Shuah Khan, linux-kselftest, linux-kernel, Tim Budding, stable

When an I-FORWARD-TSN is received, sctp_report_iftsn() calls
sctp_intl_reasm_flushtsn(), which frees every fragment in reasm and
reasm_uo with a TSN at or below the New Cumulative TSN. That is only
correct for DATA chunks, where the fragments of a message use
consecutive TSNs. With I-DATA, fragments of messages on different
streams can be interleaved, and RFC 8260 section 2.2.3 says the receiver
"MUST NOT make any assumption about the TSN assignments of the sender".

So when a PR-SCTP message is abandoned while a large message on another
stream is still being received, the fragments of the large message below
the abandoned TSN are freed as well. They have already been acked and
are not retransmitted, so the large message is never delivered, its
remaining fragments stay queued until a later I-FORWARD-TSN covers them
or the association goes away, and on an ordered stream every later
message is blocked behind it. Also, received fragments of the abandoned
message above the New Cumulative TSN are only freed if a later
I-FORWARD-TSN happens to cover them.

The skip entries of the I-FORWARD-TSN already identify the abandoned
messages by stream, U bit and MID. Free the fragments from
sctp_intl_skip() based on those instead. An entry carries the largest
skipped MID for its stream and U bit (RFC 8260 section 2.3.1), and a
sender fragments only one message per stream at a time (section 2.2.2),
so all fragments on that stream with a MID at or below it can be freed.
The reassembly queues are sorted by stream and MID, so the walk stops
once it is past them. Fragments that no skip entry names, such as a late
copy of an abandoned fragment, are no longer freed by an unrelated
I-FORWARD-TSN; they stay until the association is freed. The association
reset paths that also call sctp_report_iftsn() are not affected: they
pass the highest TSN seen, so sctp_intl_abort_pd() still flushes the
whole ulpq for them.

Observed in real use as unexpected message loss when sending messages of
mixed sizes over one association; the cause was found by AI-assisted
code inspection. Reproduced on an unmodified kernel with the ordered and
unordered tests of the sctp_ifwdtsn.sh selftest added later in this
series, which pass with this fix.

Fixes: 47b20a88566f ("sctp: implement report_ftsn for sctp_stream_interleave")
Cc: stable@vger.kernel.org
Signed-off-by: Tim Budding <tim.budding@gmail.com>
---
 net/sctp/stream_interleave.c | 29 +++++++++++------------------
 1 file changed, 11 insertions(+), 18 deletions(-)

diff --git a/net/sctp/stream_interleave.c b/net/sctp/stream_interleave.c
index 840f24045ae2..f8c818a5533c 100644
--- a/net/sctp/stream_interleave.c
+++ b/net/sctp/stream_interleave.c
@@ -1203,28 +1203,21 @@ static void sctp_report_fwdtsn(struct sctp_ulpq *ulpq, __u32 ftsn)
 	sctp_ulpq_abort_pd(ulpq, GFP_ATOMIC);
 }
 
-static void sctp_intl_reasm_flushtsn(struct sctp_ulpq *ulpq, __u32 ftsn)
+static void sctp_intl_reasm_flush_mid(struct sk_buff_head *reasm, __u16 sid,
+				      __u32 mid)
 {
 	struct sk_buff *pos, *tmp;
 
-	skb_queue_walk_safe(&ulpq->reasm, pos, tmp) {
+	skb_queue_walk_safe(reasm, pos, tmp) {
 		struct sctp_ulpevent *event = sctp_skb2event(pos);
-		__u32 tsn = event->tsn;
 
-		if (TSN_lte(tsn, ftsn)) {
-			__skb_unlink(pos, &ulpq->reasm);
-			sctp_ulpevent_free(event);
-		}
-	}
-
-	skb_queue_walk_safe(&ulpq->reasm_uo, pos, tmp) {
-		struct sctp_ulpevent *event = sctp_skb2event(pos);
-		__u32 tsn = event->tsn;
+		if (event->stream < sid)
+			continue;
+		if (event->stream > sid || MID_lt(mid, event->mid))
+			break;
 
-		if (TSN_lte(tsn, ftsn)) {
-			__skb_unlink(pos, &ulpq->reasm_uo);
-			sctp_ulpevent_free(event);
-		}
+		__skb_unlink(pos, reasm);
+		sctp_ulpevent_free(event);
 	}
 }
 
@@ -1232,8 +1225,6 @@ static void sctp_report_iftsn(struct sctp_ulpq *ulpq, __u32 ftsn)
 {
 	/* Move the Cumulattive TSN Ack ahead. */
 	sctp_tsnmap_skip(&ulpq->asoc->peer.tsn_map, ftsn);
-	/* purge the fragmentation queue */
-	sctp_intl_reasm_flushtsn(ulpq, ftsn);
 	/* abort only when it's for all data */
 	if (ftsn == sctp_tsnmap_get_max_tsn_seen(&ulpq->asoc->peer.tsn_map))
 		sctp_intl_abort_pd(ulpq, GFP_ATOMIC);
@@ -1260,6 +1251,7 @@ static void sctp_intl_skip(struct sctp_ulpq *ulpq, __u16 sid, __u32 mid,
 			sctp_intl_stream_abort_pd(ulpq, sid, mid, 0x1,
 						  GFP_ATOMIC);
 		}
+		sctp_intl_reasm_flush_mid(&ulpq->reasm_uo, sid, mid);
 		return;
 	}
 
@@ -1271,6 +1263,7 @@ static void sctp_intl_skip(struct sctp_ulpq *ulpq, __u16 sid, __u32 mid,
 		sctp_intl_stream_abort_pd(ulpq, sid, mid, 0x0, GFP_ATOMIC);
 	}
 
+	sctp_intl_reasm_flush_mid(&ulpq->reasm, sid, mid);
 	sctp_mid_skip(stream, in, sid, mid);
 
 	sctp_intl_reap_ordered(ulpq, sid);
-- 
2.34.1


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

* [PATCH net 2/4] sctp: abort unordered partial delivery when its message is skipped
  2026-10-24 12:06 [PATCH net 0/4] sctp: fix I-FORWARD-TSN handling with user message interleaving Tim Budding
  2026-10-24 12:06 ` [PATCH net 1/4] sctp: purge abandoned I-DATA fragments by stream and MID, not by TSN Tim Budding
@ 2026-10-24 12:06 ` Tim Budding
  2026-10-24 12:06 ` [PATCH net 3/4] sctp: don't treat an I-FORWARD-TSN as an association reset Tim Budding
  2026-10-24 12:06 ` [PATCH net 4/4] selftests: net: add SCTP I-FORWARD-TSN tests with message interleaving Tim Budding
  3 siblings, 0 replies; 5+ messages in thread
From: Tim Budding @ 2026-10-24 12:06 UTC (permalink / raw)
  To: netdev, linux-sctp
  Cc: Xin Long, Marcelo Ricardo Leitner, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Shuah Khan, linux-kselftest, linux-kernel, Tim Budding, stable

When an I-FORWARD-TSN skips unordered messages on a stream,
sctp_intl_skip() aborts the stream's unordered partial delivery only if
the MID being delivered is lower than the skipped MID. If the skipped
message is the one being partially delivered, partial delivery is left
in progress: the application never gets SCTP_PARTIAL_DELIVERY_ABORTED,
and the next unordered message on that stream is delivered with MSG_EOR
as if it were the rest of the abandoned message, so the application
sees the two messages merged into one. Until a later unordered message
on that stream is abandoned, no other unordered partial delivery can
start there either.

Abort the partial delivery when the MID being delivered is at or below
the skipped MID, as the ordered case already does.

Found by an AI assistant while working on the previous patch. Reproduced
on an unmodified kernel with the uo_pd test of the sctp_ifwdtsn.sh
selftest added later in this series, which passes with this fix.

Fixes: de60fe910543 ("sctp: implement handle_ftsn for sctp_stream_interleave")
Cc: stable@vger.kernel.org
Signed-off-by: Tim Budding <tim.budding@gmail.com>
---
 net/sctp/stream_interleave.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/net/sctp/stream_interleave.c b/net/sctp/stream_interleave.c
index f8c818a5533c..77fe0db5ff77 100644
--- a/net/sctp/stream_interleave.c
+++ b/net/sctp/stream_interleave.c
@@ -1246,7 +1246,7 @@ static void sctp_intl_skip(struct sctp_ulpq *ulpq, __u16 sid, __u32 mid,
 	struct sctp_stream *stream  = &ulpq->asoc->stream;
 
 	if (flags & SCTP_FTSN_U_BIT) {
-		if (sin->pd_mode_uo && MID_lt(sin->mid_uo, mid)) {
+		if (sin->pd_mode_uo && !MID_lt(mid, sin->mid_uo)) {
 			sin->pd_mode_uo = 0;
 			sctp_intl_stream_abort_pd(ulpq, sid, mid, 0x1,
 						  GFP_ATOMIC);
-- 
2.34.1


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

* [PATCH net 3/4] sctp: don't treat an I-FORWARD-TSN as an association reset
  2026-10-24 12:06 [PATCH net 0/4] sctp: fix I-FORWARD-TSN handling with user message interleaving Tim Budding
  2026-10-24 12:06 ` [PATCH net 1/4] sctp: purge abandoned I-DATA fragments by stream and MID, not by TSN Tim Budding
  2026-10-24 12:06 ` [PATCH net 2/4] sctp: abort unordered partial delivery when its message is skipped Tim Budding
@ 2026-10-24 12:06 ` Tim Budding
  2026-10-24 12:06 ` [PATCH net 4/4] selftests: net: add SCTP I-FORWARD-TSN tests with message interleaving Tim Budding
  3 siblings, 0 replies; 5+ messages in thread
From: Tim Budding @ 2026-10-24 12:06 UTC (permalink / raw)
  To: netdev, linux-sctp
  Cc: Xin Long, Marcelo Ricardo Leitner, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Shuah Khan, linux-kselftest, linux-kernel, Tim Budding, stable

sctp_report_iftsn() handles both a received I-FORWARD-TSN and the SSN/TSN
reset request and response of RFC 6525, where all queued data has to be
discarded. It tells them apart by checking whether the New Cumulative
TSN equals the highest TSN seen, and if so calls sctp_intl_abort_pd(),
which aborts partial delivery on every stream, skips the MIDs being
partially delivered and flushes the whole ulpq.

However, sctp_tsnmap_skip() runs first and raises the highest TSN seen
to the New Cumulative TSN, so the check is also true for any
I-FORWARD-TSN that arrives before data with a higher TSN, e.g. when the
abandoned message was the last one sent, or after a burst of loss. All
partially received messages on all streams are then dropped. A stream
that was in partial delivery also has its MID skipped, so the next
fragment of that message fails sctp_validate_idata() as stale, and the
association is aborted with a protocol violation.

Drop the check from sctp_report_iftsn() and have the reset paths call
abort_pd() explicitly. For DATA chunks this changes nothing, as
sctp_report_fwdtsn() already aborts partial delivery and
sctp_ulpq_abort_pd() returns early when none is in progress. For I-DATA
the reset paths still discard everything as before.

This relies on the previous patch, which makes sctp_intl_skip() abort an
unordered partial delivery of the skipped message; the check removed
here used to do that at the leading edge.

Found by an AI assistant while working on the first patch in this
series. Reproduced on an unmodified kernel with the leading_edge test of
the sctp_ifwdtsn.sh selftest added later in this series, which passes
with this fix. Its reset test checks that an SSN/TSN association reset
still works with interleaving.

Fixes: 47b20a88566f ("sctp: implement report_ftsn for sctp_stream_interleave")
Cc: stable@vger.kernel.org
Signed-off-by: Tim Budding <tim.budding@gmail.com>
---
 net/sctp/stream.c            | 2 ++
 net/sctp/stream_interleave.c | 3 ---
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/net/sctp/stream.c b/net/sctp/stream.c
index e4354d58f20b..d5e71a2027f5 100644
--- a/net/sctp/stream.c
+++ b/net/sctp/stream.c
@@ -749,6 +749,7 @@ struct sctp_chunk *sctp_process_strreset_tsnreq(
 	 */
 	max_tsn_seen = sctp_tsnmap_get_max_tsn_seen(&asoc->peer.tsn_map);
 	asoc->stream.si->report_ftsn(&asoc->ulpq, max_tsn_seen);
+	asoc->stream.si->abort_pd(&asoc->ulpq, GFP_ATOMIC);
 
 	/* G1: Compute an appropriate value for the Receiver's Next TSN -- the
 	 *     TSN that the peer should use to send the next DATA chunk.  The
@@ -1028,6 +1029,7 @@ struct sctp_chunk *sctp_process_strreset_resp(
 			LIST_HEAD(temp);
 
 			asoc->stream.si->report_ftsn(&asoc->ulpq, mtsn);
+			asoc->stream.si->abort_pd(&asoc->ulpq, GFP_ATOMIC);
 
 			sctp_tsnmap_init(&asoc->peer.tsn_map,
 					 SCTP_TSN_MAP_INITIAL,
diff --git a/net/sctp/stream_interleave.c b/net/sctp/stream_interleave.c
index 77fe0db5ff77..f49b534ff78f 100644
--- a/net/sctp/stream_interleave.c
+++ b/net/sctp/stream_interleave.c
@@ -1225,9 +1225,6 @@ static void sctp_report_iftsn(struct sctp_ulpq *ulpq, __u32 ftsn)
 {
 	/* Move the Cumulattive TSN Ack ahead. */
 	sctp_tsnmap_skip(&ulpq->asoc->peer.tsn_map, ftsn);
-	/* abort only when it's for all data */
-	if (ftsn == sctp_tsnmap_get_max_tsn_seen(&ulpq->asoc->peer.tsn_map))
-		sctp_intl_abort_pd(ulpq, GFP_ATOMIC);
 }
 
 static void sctp_handle_fwdtsn(struct sctp_ulpq *ulpq, struct sctp_chunk *chunk)
-- 
2.34.1


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

* [PATCH net 4/4] selftests: net: add SCTP I-FORWARD-TSN tests with message interleaving
  2026-10-24 12:06 [PATCH net 0/4] sctp: fix I-FORWARD-TSN handling with user message interleaving Tim Budding
                   ` (2 preceding siblings ...)
  2026-10-24 12:06 ` [PATCH net 3/4] sctp: don't treat an I-FORWARD-TSN as an association reset Tim Budding
@ 2026-10-24 12:06 ` Tim Budding
  3 siblings, 0 replies; 5+ messages in thread
From: Tim Budding @ 2026-10-24 12:06 UTC (permalink / raw)
  To: netdev, linux-sctp
  Cc: Xin Long, Marcelo Ricardo Leitner, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Shuah Khan, linux-kselftest, linux-kernel, Tim Budding

Add tests for the receiver side of I-FORWARD-TSN with user message
interleaving (RFC 8260) and partial reliability. The first four tests
interleave a 64KB reliable message on stream 0 with a PR-SCTP message on
stream 1, which an nft rule drops so that the sender abandons it. The
tests are:

- ordered, unordered: the large message, ordered and unordered, must be
  delivered intact, and the abandoned message's received fragments must
  be freed.
- leading_edge: with the large message in partial delivery, drop all
  data after the abandoned message until the I-FORWARD-TSN arrives; this
  must not be treated as an association reset.
- uo_pd: abandon an unordered message during its partial delivery; the
  partial delivery must be aborted and the next unordered message on the
  stream must be delivered on its own.
- reset: an SSN/TSN association reset with interleaving completes and
  leaves a working association.

The PR-SCTP message is abandoned after one retransmission (RTX policy),
which keeps the tests stable on slow or loaded machines.

Signed-off-by: Tim Budding <tim.budding@gmail.com>
---
 tools/testing/selftests/net/.gitignore      |   1 +
 tools/testing/selftests/net/Makefile        |   2 +
 tools/testing/selftests/net/sctp_ifwdtsn.c  | 538 ++++++++++++++++++++
 tools/testing/selftests/net/sctp_ifwdtsn.sh | 141 +++++
 4 files changed, 682 insertions(+)
 create mode 100644 tools/testing/selftests/net/sctp_ifwdtsn.c
 create mode 100755 tools/testing/selftests/net/sctp_ifwdtsn.sh

diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
index 21412510ac26..ca451bfeda75 100644
--- a/tools/testing/selftests/net/.gitignore
+++ b/tools/testing/selftests/net/.gitignore
@@ -34,6 +34,7 @@ reuseport_bpf_numa
 reuseport_dualstack
 rxtimestamp
 sctp_hello
+sctp_ifwdtsn
 sk_bind_sendto_listen
 sk_connect_zero_addr
 sk_so_peek_off
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index df215e1a6262..58ca9fc23979 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -90,6 +90,7 @@ TEST_PROGS := \
 	run_afpackettests \
 	run_netsocktests \
 	rxtimestamp.sh \
+	sctp_ifwdtsn.sh \
 	sctp_vrf.sh \
 	skf_net_off.sh \
 	srv6_encap_lookup_l3vpn_test.sh \
@@ -164,6 +165,7 @@ TEST_GEN_FILES := \
 	reuseport_addr_any \
 	rxtimestamp \
 	sctp_hello \
+	sctp_ifwdtsn \
 	skf_net_off \
 	so_netns_cookie \
 	so_rcv_listener \
diff --git a/tools/testing/selftests/net/sctp_ifwdtsn.c b/tools/testing/selftests/net/sctp_ifwdtsn.c
new file mode 100644
index 000000000000..15346aca10ef
--- /dev/null
+++ b/tools/testing/selftests/net/sctp_ifwdtsn.c
@@ -0,0 +1,538 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Helper for sctp_ifwdtsn.sh: I-FORWARD-TSN handling with SCTP user message
+ * interleaving (RFC 8260) and partial reliability (RFC 3758).
+ *
+ * Runs both endpoints of one association: the server socket is created in
+ * the current network namespace, the client socket in the namespace given
+ * on the command line. The caller sets up the link and any nft rule that
+ * drops chunks. Messages sent by the client:
+ *
+ *   big:    64KB on stream 0, reliable, fragmented into ~66 I-DATA chunks
+ *   small:  on stream 1, PR-SCTP "RTX" policy allowing one retransmission;
+ *           the nft rule drops (part of) it, so it gets abandoned
+ *   marker: short ordered message on stream 0
+ *
+ * Usage: sctp_ifwdtsn <client netns> <test>
+ * Exit status: 0 pass, 1 fail, 4 skip.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <linux/sctp.h>
+
+#include "kselftest.h"
+
+#ifndef SOL_SCTP
+#define SOL_SCTP	132
+#endif
+
+#define SRV_ADDR	"10.0.0.1"
+#define SRV_PORT	5000
+#define BIG_LEN		(64 * 1024)
+#define BUF_LEN		(BIG_LEN + 1024)
+#define MAXSEG		1000
+#define TIMEOUT_MS	5000
+#define MARKER		"marker"
+#define UO_MARKER	"uo-marker"
+
+struct test {
+	const char *name;
+	bool big_uo;		/* send big unordered */
+	int small_len;		/* 0: don't send the small message */
+	bool small_uo;		/* send small unordered */
+	int pd_point;		/* server SCTP_PARTIAL_DELIVERY_POINT */
+	bool expect_pd_abort;	/* PD on stream 1 must be aborted */
+	bool uo_marker;		/* then send an unordered marker on 1 */
+	bool reset;		/* SSN/TSN association reset after big */
+};
+
+static const struct test tests[] = {
+	{ .name = "ordered", .small_len = 3000 },
+	{ .name = "unordered", .big_uo = true, .small_len = 3000 },
+	{ .name = "leading_edge", .small_len = 1000, .pd_point = 2000 },
+	{ .name = "uo_pd", .small_len = 20000, .small_uo = true,
+	  .pd_point = 2000, .expect_pd_abort = true, .uo_marker = true },
+	{ .name = "reset", .reset = true },
+};
+
+static const struct test *t;
+
+static void die(const char *what)
+{
+	fprintf(stderr, "%s: %s\n", what, strerror(errno));
+	exit(KSFT_FAIL);
+}
+
+static void setopt(int fd, int opt, const void *val, socklen_t len,
+		   const char *name)
+{
+	if (setsockopt(fd, SOL_SCTP, opt, val, len) < 0) {
+		fprintf(stderr, "setsockopt(%s): %s\n", name, strerror(errno));
+		/* Interleaving disabled, or option not supported. */
+		exit(errno == EPERM || errno == ENOPROTOOPT ?
+		     KSFT_SKIP : KSFT_FAIL);
+	}
+}
+
+static void set_assoc_opt(int fd, int opt, unsigned int val, const char *name)
+{
+	struct sctp_assoc_value av = {
+		.assoc_id = SCTP_FUTURE_ASSOC,
+		.assoc_value = val,
+	};
+
+	setopt(fd, opt, &av, sizeof(av), name);
+}
+
+static void subscribe(int fd, int type)
+{
+	struct sctp_event ev = {
+		.se_assoc_id = SCTP_FUTURE_ASSOC,
+		.se_type = type,
+		.se_on = 1,
+	};
+
+	setopt(fd, SCTP_EVENT, &ev, sizeof(ev), "SCTP_EVENT");
+}
+
+static int sctp_socket(void)
+{
+	struct sctp_initmsg im = {
+		.sinit_num_ostreams = 2,
+		.sinit_max_instreams = 2,
+	};
+	struct sctp_paddrparams pp = {
+		.spp_assoc_id = SCTP_FUTURE_ASSOC,
+		.spp_flags = SPP_HB_DISABLE,
+	};
+	struct sctp_rtoinfo rto = {
+		.srto_assoc_id = SCTP_FUTURE_ASSOC,
+		.srto_initial = 300,
+		.srto_min = 300,
+		.srto_max = 1000,
+	};
+	int fd, val;
+
+	fd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
+	if (fd < 0) {
+		perror("socket");
+		exit(errno == EPROTONOSUPPORT ? KSFT_SKIP : KSFT_FAIL);
+	}
+	val = 1;
+	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
+
+	setopt(fd, SCTP_INITMSG, &im, sizeof(im), "SCTP_INITMSG");
+	setopt(fd, SCTP_RTOINFO, &rto, sizeof(rto), "SCTP_RTOINFO");
+	/* No HEARTBEATs: the nft rules only look at the first chunk of a
+	 * packet, and a HEARTBEAT bundled in front of DATA would bypass them.
+	 */
+	setopt(fd, SCTP_PEER_ADDR_PARAMS, &pp, sizeof(pp),
+	       "SCTP_PEER_ADDR_PARAMS");
+	set_assoc_opt(fd, SCTP_PR_SUPPORTED, 1, "SCTP_PR_SUPPORTED");
+	val = 2;
+	setopt(fd, SCTP_FRAGMENT_INTERLEAVE, &val, sizeof(val),
+	       "SCTP_FRAGMENT_INTERLEAVE");
+	set_assoc_opt(fd, SCTP_INTERLEAVING_SUPPORTED, 1,
+		      "SCTP_INTERLEAVING_SUPPORTED");
+	if (t->reset) {
+		set_assoc_opt(fd, SCTP_RECONFIG_SUPPORTED, 1,
+			      "SCTP_RECONFIG_SUPPORTED");
+		set_assoc_opt(fd, SCTP_ENABLE_STREAM_RESET,
+			      SCTP_ENABLE_RESET_ASSOC_REQ,
+			      "SCTP_ENABLE_STREAM_RESET");
+		subscribe(fd, SCTP_ASSOC_RESET_EVENT);
+	}
+	val = 1;
+	setopt(fd, SCTP_RECVRCVINFO, &val, sizeof(val), "SCTP_RECVRCVINFO");
+
+	return fd;
+}
+
+static void send_msg(int fd, int sid, bool uo, const void *buf, size_t len,
+		     bool pr)
+{
+	char cbuf[CMSG_SPACE(sizeof(struct sctp_sndinfo)) +
+		  CMSG_SPACE(sizeof(struct sctp_prinfo))] = {};
+	struct iovec iov = { .iov_base = (void *)buf, .iov_len = len };
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+		.msg_control = cbuf,
+		.msg_controllen = CMSG_SPACE(sizeof(struct sctp_sndinfo)),
+	};
+	struct sctp_sndinfo *si;
+	struct sctp_prinfo *pi;
+	struct cmsghdr *cm;
+
+	cm = CMSG_FIRSTHDR(&msg);
+	cm->cmsg_level = IPPROTO_SCTP;
+	cm->cmsg_type = SCTP_SNDINFO;
+	cm->cmsg_len = CMSG_LEN(sizeof(*si));
+	si = (struct sctp_sndinfo *)CMSG_DATA(cm);
+	si->snd_sid = sid;
+	si->snd_flags = uo ? SCTP_UNORDERED : 0;
+
+	if (pr) {
+		/* Abandon once the chunk would need a second retransmission:
+		 * only a chunk that is lost again after being retransmitted
+		 * qualifies, so abandonment does not depend on timing.
+		 */
+		msg.msg_controllen += CMSG_SPACE(sizeof(*pi));
+		cm = CMSG_NXTHDR(&msg, cm);
+		cm->cmsg_level = IPPROTO_SCTP;
+		cm->cmsg_type = SCTP_PRINFO;
+		cm->cmsg_len = CMSG_LEN(sizeof(*pi));
+		pi = (struct sctp_prinfo *)CMSG_DATA(cm);
+		pi->pr_policy = SCTP_PR_SCTP_RTX;
+		pi->pr_value = 1;
+	}
+
+	if (sendmsg(fd, &msg, 0) != (ssize_t)len)
+		die("sendmsg");
+}
+
+static unsigned char pattern(size_t i)
+{
+	return (i * 7 + (i >> 8)) & 0xff;
+}
+
+static long long now_ms(void)
+{
+	struct timespec ts;
+
+	clock_gettime(CLOCK_MONOTONIC, &ts);
+	return ts.tv_sec * 1000LL + ts.tv_nsec / 1000000;
+}
+
+/* Wait until the client has abandoned a sent message. */
+static bool wait_abandoned(int fd)
+{
+	long long end = now_ms() + TIMEOUT_MS;
+	struct sctp_prstatus ps;
+	socklen_t len;
+
+	while (now_ms() < end) {
+		memset(&ps, 0, sizeof(ps));
+		ps.sprstat_policy = SCTP_PR_SCTP_RTX;
+		len = sizeof(ps);
+		if (getsockopt(fd, SOL_SCTP, SCTP_PR_ASSOC_STATUS, &ps,
+			       &len) < 0)
+			die("SCTP_PR_ASSOC_STATUS");
+		if (ps.sprstat_abandoned_sent)
+			return true;
+		usleep(10000);
+	}
+	return false;
+}
+
+/* Wait for a notification of @type on @fd, discarding anything else. */
+static bool wait_event(int fd, int type)
+{
+	long long end = now_ms() + TIMEOUT_MS;
+	char buf[256];
+
+	while (now_ms() < end) {
+		struct pollfd pfd = { .fd = fd, .events = POLLIN };
+		struct iovec iov = { buf, sizeof(buf) };
+		struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
+		union sctp_notification *sn = (void *)buf;
+
+		if (poll(&pfd, 1, end - now_ms()) <= 0)
+			break;
+		if (recvmsg(fd, &msg, 0) <= 0)
+			break;
+		if ((msg.msg_flags & MSG_NOTIFICATION) &&
+		    sn->sn_header.sn_type == type)
+			return true;
+	}
+	return false;
+}
+
+/* Bytes held by the server association (/proc/net/sctp/assocs RX_QUEUE). */
+static long assoc_rx_queue(void)
+{
+	/* 7 columns, TX_QUEUE, RX_QUEUE, UID, INODE, LPORT */
+	const char *fmt = "%*s %*s %*s %*s %*s %*s %*s %*u %lu %*s %*s %d";
+	unsigned long rxq;
+	char line[512];
+	long ret = -1;
+	FILE *f;
+	int lport;
+
+	f = fopen("/proc/net/sctp/assocs", "r");
+	if (!f)
+		return -1;
+	while (fgets(line, sizeof(line), f)) {
+		if (sscanf(line, fmt, &rxq, &lport) == 2 && lport == SRV_PORT)
+			ret = rxq;
+	}
+	fclose(f);
+	return ret;
+}
+
+struct server_state {
+	unsigned char *buf[2];	/* message being received, per stream */
+	size_t len[2];
+	bool big, marker, uo_marker, reset_event;
+	int pd_aborts[2];
+	int errors;
+};
+
+static void check_msg(struct server_state *s, int sid)
+{
+	unsigned char *m = s->buf[sid];
+	size_t len = s->len[sid];
+	size_t i;
+
+	s->len[sid] = 0;
+	if (sid == 0 && len == sizeof(MARKER) && !memcmp(m, MARKER, len)) {
+		s->marker = true;
+		return;
+	}
+	if (sid == 1 && len == sizeof(UO_MARKER) &&
+	    !memcmp(m, UO_MARKER, len)) {
+		s->uo_marker = true;
+		return;
+	}
+	if (sid == 0 && len == BIG_LEN) {
+		for (i = 0; i < BIG_LEN && m[i] == pattern(i); i++)
+			;
+		if (i == BIG_LEN) {
+			s->big = true;
+			return;
+		}
+		printf("big message corrupted at offset %zu\n", i);
+	} else if (sid == 1 && len > sizeof(UO_MARKER) &&
+		   !memcmp(m + len - sizeof(UO_MARKER), UO_MARKER,
+			   sizeof(UO_MARKER))) {
+		printf("unordered marker appended to the abandoned message\n");
+	} else if (sid == 1 && t->small_len) {
+		printf("abandoned message delivered (%zu bytes)\n", len);
+	} else {
+		printf("unexpected %zu byte message on stream %d\n", len, sid);
+	}
+	s->errors++;
+}
+
+/* Receive until @done(s) is true or the timeout expires. */
+static bool server_recv(int fd, struct server_state *s,
+			bool (*done)(const struct server_state *s))
+{
+	long long end = now_ms() + TIMEOUT_MS;
+	static char cbuf[CMSG_SPACE(sizeof(struct sctp_rcvinfo))];
+	static unsigned char buf[BUF_LEN];
+
+	while (!done(s)) {
+		struct pollfd pfd = { .fd = fd, .events = POLLIN };
+		struct iovec iov = { buf, sizeof(buf) };
+		struct msghdr msg = {
+			.msg_iov = &iov,
+			.msg_iovlen = 1,
+			.msg_control = cbuf,
+			.msg_controllen = sizeof(cbuf),
+		};
+		union sctp_notification *sn = (void *)buf;
+		struct sctp_rcvinfo *ri = NULL;
+		struct cmsghdr *cm;
+		long long left;
+		ssize_t n;
+		int sid;
+
+		left = end - now_ms();
+		if (left <= 0 || poll(&pfd, 1, left) <= 0) {
+			printf("timeout waiting for data\n");
+			return false;
+		}
+		n = recvmsg(fd, &msg, 0);
+		if (n <= 0) {
+			printf("recvmsg: %s\n", n ? strerror(errno) : "EOF");
+			return false;
+		}
+
+		if (msg.msg_flags & MSG_NOTIFICATION) {
+			int type = sn->sn_header.sn_type;
+
+			if (type == SCTP_PARTIAL_DELIVERY_EVENT) {
+				sid = sn->sn_pdapi_event.pdapi_stream;
+				if (sid > 1)
+					sid = 1;
+				s->pd_aborts[sid]++;
+				s->len[sid] = 0;
+			} else if (type == SCTP_ASSOC_RESET_EVENT) {
+				s->reset_event = true;
+			}
+			continue;
+		}
+
+		for (cm = CMSG_FIRSTHDR(&msg); cm; cm = CMSG_NXTHDR(&msg, cm))
+			if (cm->cmsg_level == IPPROTO_SCTP &&
+			    cm->cmsg_type == SCTP_RCVINFO)
+				ri = (struct sctp_rcvinfo *)CMSG_DATA(cm);
+		sid = ri ? ri->rcv_sid : 0;
+		if (sid > 1 || s->len[sid] + n > BUF_LEN) {
+			printf("unexpected data on stream %d\n", sid);
+			return false;
+		}
+		memcpy(s->buf[sid] + s->len[sid], buf, n);
+		s->len[sid] += n;
+		if (msg.msg_flags & MSG_EOR)
+			check_msg(s, sid);
+	}
+	return true;
+}
+
+static bool got_big(const struct server_state *s)
+{
+	return s->big || s->errors;
+}
+
+static bool got_all(const struct server_state *s)
+{
+	return (s->big && s->marker && (!t->uo_marker || s->uo_marker)) ||
+	       s->errors;
+}
+
+static int client_socket(const char *netns)
+{
+	int self, other, fd;
+	char path[256];
+
+	snprintf(path, sizeof(path), "/run/netns/%s", netns);
+	self = open("/proc/self/ns/net", O_RDONLY);
+	other = open(path, O_RDONLY);
+	if (self < 0 || other < 0)
+		die("open netns");
+	if (setns(other, CLONE_NEWNET))
+		die("setns");
+	fd = sctp_socket();
+	if (setns(self, CLONE_NEWNET))
+		die("setns");
+	close(other);
+	close(self);
+	return fd;
+}
+
+int main(int argc, char **argv)
+{
+	struct sockaddr_in sa = {
+		.sin_family = AF_INET,
+		.sin_port = htons(SRV_PORT),
+	};
+	struct server_state s = {};
+	unsigned char *big, *small;
+	int lfd, sfd, cfd, i;
+	sctp_assoc_t id = 0;
+	bool ok;
+	long rxq;
+
+	if (argc != 3) {
+		fprintf(stderr, "usage: %s <client netns> <test>\n", argv[0]);
+		return KSFT_FAIL;
+	}
+	for (i = 0; i < (int)ARRAY_SIZE(tests); i++)
+		if (!strcmp(argv[2], tests[i].name))
+			t = &tests[i];
+	if (!t) {
+		fprintf(stderr, "unknown test %s\n", argv[2]);
+		return KSFT_FAIL;
+	}
+	inet_pton(AF_INET, SRV_ADDR, &sa.sin_addr);
+
+	lfd = sctp_socket();
+	if (t->pd_point) {
+		setopt(lfd, SCTP_PARTIAL_DELIVERY_POINT, &t->pd_point,
+		       sizeof(t->pd_point), "SCTP_PARTIAL_DELIVERY_POINT");
+		subscribe(lfd, SCTP_PARTIAL_DELIVERY_EVENT);
+	}
+	if (bind(lfd, (struct sockaddr *)&sa, sizeof(sa)) || listen(lfd, 1))
+		die("bind/listen");
+
+	cfd = client_socket(argv[1]);
+	set_assoc_opt(cfd, SCTP_MAXSEG, MAXSEG, "SCTP_MAXSEG");
+	/* Round robin, so that the small message is sent between fragments
+	 * of the big one.
+	 */
+	set_assoc_opt(cfd, SCTP_STREAM_SCHEDULER, SCTP_SS_RR,
+		      "SCTP_STREAM_SCHEDULER");
+	if (connect(cfd, (struct sockaddr *)&sa, sizeof(sa)))
+		die("connect");
+	sfd = accept(lfd, NULL, NULL);
+	if (sfd < 0)
+		die("accept");
+
+	big = malloc(BIG_LEN);
+	small = malloc(t->small_len + 1);
+	s.buf[0] = malloc(BUF_LEN);
+	s.buf[1] = malloc(BUF_LEN);
+	if (!big || !small || !s.buf[0] || !s.buf[1])
+		die("malloc");
+	for (i = 0; i < BIG_LEN; i++)
+		big[i] = pattern(i);
+	memset(small, 's', t->small_len);
+
+	send_msg(cfd, 0, t->big_uo, big, BIG_LEN, false);
+	if (t->small_len)
+		send_msg(cfd, 1, t->small_uo, small, t->small_len, true);
+
+	if (t->reset) {
+		ok = server_recv(sfd, &s, got_big);
+		if (ok && setsockopt(sfd, SOL_SCTP, SCTP_RESET_ASSOC, &id,
+				     sizeof(id)))
+			die("SCTP_RESET_ASSOC");
+		/* Only send the marker once the reset is done. */
+		if (ok && !wait_event(cfd, SCTP_ASSOC_RESET_EVENT)) {
+			printf("client: no association reset event\n");
+			return KSFT_FAIL;
+		}
+	}
+	send_msg(cfd, 0, false, MARKER, sizeof(MARKER), false);
+
+	if (t->small_len && !wait_abandoned(cfd)) {
+		printf("client: small message was not abandoned\n");
+		return KSFT_FAIL;
+	}
+	if (t->uo_marker)
+		send_msg(cfd, 1, true, UO_MARKER, sizeof(UO_MARKER), false);
+
+	ok = server_recv(sfd, &s, got_all);
+	rxq = assoc_rx_queue();
+
+	if (!s.big)
+		printf("big message not delivered intact\n");
+	if (!s.marker)
+		printf("marker not delivered\n");
+	if (t->uo_marker && !s.uo_marker)
+		printf("unordered marker not delivered as its own message\n");
+	if (s.pd_aborts[0] || (s.pd_aborts[1] && !t->expect_pd_abort)) {
+		printf("unexpected partial delivery abort\n");
+		ok = false;
+	}
+	if (t->expect_pd_abort && !s.pd_aborts[1]) {
+		printf("partial delivery on stream 1 not aborted\n");
+		ok = false;
+	}
+	if (t->reset && !s.reset_event) {
+		printf("server: no association reset event\n");
+		ok = false;
+	}
+	if (rxq < 0) {
+		printf("association is gone\n");
+		ok = false;
+	} else if (rxq > 0) {
+		printf("%ld bytes left queued on the association\n", rxq);
+		ok = false;
+	}
+
+	return ok && !s.errors ? KSFT_PASS : KSFT_FAIL;
+}
diff --git a/tools/testing/selftests/net/sctp_ifwdtsn.sh b/tools/testing/selftests/net/sctp_ifwdtsn.sh
new file mode 100755
index 000000000000..2bf8d28cee52
--- /dev/null
+++ b/tools/testing/selftests/net/sctp_ifwdtsn.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# SCTP I-FORWARD-TSN handling with user message interleaving (RFC 8260).
+#
+# TOPO: CLI_NS (veth, 10.0.0.2) <---> (veth, 10.0.0.1) SRV_NS
+#
+# A large reliable message on stream 0 is sent interleaved with a PR-SCTP
+# message on stream 1 that an nft rule in SRV_NS drops, so the sender
+# abandons it and sends an I-FORWARD-TSN. The netem delay keeps the sender
+# window-limited, so the two messages' fragments interleave. Most of the
+# checks are done by sctp_ifwdtsn, see there.
+
+source lib.sh
+
+ALL_TESTS="
+	test_ordered
+	test_unordered
+	test_leading_edge
+	test_uo_pd
+	test_reset
+"
+
+# nft matches on the first chunk of a packet: chunk type at byte 12 of the
+# SCTP header, flags at byte 13, I-DATA stream id at byte 20 and FSN at
+# byte 28. SCTP_MAXSEG limits fragments to 1000 bytes and GSO is limited
+# to the MTU, so no two full-size fragments share a packet.
+NFT_SCTP="meta l4proto sctp"
+NFT_CHUNK_IDATA="@th,96,8 64"
+NFT_CHUNK_IFWDTSN="@th,96,8 194"
+NFT_FLAG_B="@th,104,8 & 0x02 == 0x02"
+NFT_SID1="@th,160,16 1"
+NFT_FSN5="@th,224,32 5"
+
+setup()
+{
+	setup_ns CLI_NS SRV_NS || return
+	defer cleanup_all_ns
+
+	ip -n "$SRV_NS" link add veth0 type veth peer name veth0 \
+		netns "$CLI_NS" || return
+	ip -n "$SRV_NS" addr add 10.0.0.1/24 dev veth0 || return
+	ip -n "$CLI_NS" addr add 10.0.0.2/24 dev veth0 || return
+	for ns in "$SRV_NS" "$CLI_NS"; do
+		ip -n "$ns" link set veth0 gso_max_size 1500 up || return
+		tc -n "$ns" qdisc add dev veth0 root netem delay 50ms || return
+		ip netns exec "$ns" sysctl -wq net.sctp.intl_enable=1 \
+			net.sctp.prsctp_enable=1 net.sctp.reconf_enable=1 ||
+			return
+	done
+}
+
+# $1: nft rules for the server's input hook (may be empty)
+nft_rules()
+{
+	ip netns exec "$SRV_NS" nft -f - <<-EOF
+		table ip sctp_test {
+			set blk {
+				type ipv4_addr
+				flags dynamic
+			}
+			chain input {
+				type filter hook input priority 0;
+				$1
+			}
+		}
+	EOF
+}
+
+# $1: test name for sctp_ifwdtsn, $2: description, $3: nft rules (optional)
+run_helper()
+{
+	if ! setup || { [ -n "$3" ] && ! nft_rules "$3"; }; then
+		log_test_skip "$2"
+		return
+	fi
+	ip netns exec "$SRV_NS" ./sctp_ifwdtsn "$CLI_NS" "$1"
+	RET=$?
+	[ "$RET" -eq "$ksft_skip" ] || [ "$RET" -eq 0 ] || RET=$ksft_fail
+	log_test "$2"
+}
+
+# Fragments of the abandoned message must not take fragments of a live
+# message below the New Cumulative TSN with them, and the abandoned
+# message's own received fragments must be freed.
+test_ordered()
+{
+	run_helper ordered \
+		"abandoned message does not purge ordered message" \
+		"$NFT_SCTP $NFT_CHUNK_IDATA $NFT_SID1 $NFT_FLAG_B drop"
+}
+
+test_unordered()
+{
+	run_helper unordered \
+		"abandoned message does not purge unordered message" \
+		"$NFT_SCTP $NFT_CHUNK_IDATA $NFT_SID1 $NFT_FLAG_B drop"
+}
+
+# Drop the abandoned message and every packet after it until one starting
+# with the I-FORWARD-TSN arrives, so that its New Cumulative TSN is the
+# highest TSN received. This must not abort the partial delivery of the
+# message on stream 0.
+test_leading_edge()
+{
+	run_helper leading_edge \
+		"I-FORWARD-TSN at the highest TSN is not a reset" "
+		$NFT_SCTP $NFT_CHUNK_IFWDTSN delete @blk { ip saddr }
+		$NFT_SCTP $NFT_CHUNK_IDATA $NFT_SID1 add @blk { ip saddr } drop
+		$NFT_SCTP ip saddr @blk drop"
+}
+
+# Abandon an unordered message during its partial delivery: the partial
+# delivery must be aborted, and the next unordered message on the stream
+# must not be appended to it.
+test_uo_pd()
+{
+	run_helper uo_pd "abandoned unordered partial delivery is aborted" \
+		"$NFT_SCTP $NFT_CHUNK_IDATA $NFT_SID1 $NFT_FSN5 drop"
+}
+
+# An SSN/TSN association reset with interleaving completes and leaves a
+# working association.
+test_reset()
+{
+	run_helper reset "association reset with interleaving"
+}
+
+require_command nft
+require_command tc
+modprobe -q sctp
+if [ ! -d /proc/net/sctp ]; then
+	echo "SKIP: SCTP not available"
+	exit "$ksft_skip"
+fi
+
+trap defer_scopes_cleanup EXIT
+
+tests_run
+
+exit "$EXIT_STATUS"
-- 
2.34.1


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

end of thread, other threads:[~2026-10-24 12:07 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-24 12:06 [PATCH net 0/4] sctp: fix I-FORWARD-TSN handling with user message interleaving Tim Budding
2026-10-24 12:06 ` [PATCH net 1/4] sctp: purge abandoned I-DATA fragments by stream and MID, not by TSN Tim Budding
2026-10-24 12:06 ` [PATCH net 2/4] sctp: abort unordered partial delivery when its message is skipped Tim Budding
2026-10-24 12:06 ` [PATCH net 3/4] sctp: don't treat an I-FORWARD-TSN as an association reset Tim Budding
2026-10-24 12:06 ` [PATCH net 4/4] selftests: net: add SCTP I-FORWARD-TSN tests with message interleaving Tim Budding

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

all inboxes | Powered by JetHome®