Commit 2f75c0faa336 for kernel

commit 2f75c0faa3361b28e36cc0512b3299e163e25789
Author: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Date:   Mon Jul 6 18:16:24 2026 +0800

    ipvs: use parsed transport offset in SCTP state lookup

    set_sctp_state() reads the SCTP chunk header again in order to drive the
    IPVS SCTP state table. For IPv6 it computes the offset with
    sizeof(struct ipv6hdr), while the surrounding IPVS code uses iph.len from
    ip_vs_fill_iph_skb(), where ipv6_find_hdr() has already skipped
    extension headers and found the real transport header.

    This makes the state machine read from the wrong offset for IPv6 SCTP
    packets that carry extension headers. For example, an INIT packet with an
    8-byte destination options header can be scheduled correctly by
    sctp_conn_schedule(), but set_sctp_state() reads the first byte of the
    SCTP verification tag as a DATA chunk type. The connection then moves
    from NONE to ESTABLISHED instead of INIT1, gets the longer established
    timeout, and updates the active/inactive destination counters
    incorrectly. This happens even though the SCTP handshake has not
    completed.

    Use the parsed transport offset passed down from ip_vs_set_state() for
    the SCTP chunk-header lookup. For IPv4 and IPv6 packets without
    extension headers this preserves the existing offset.

    Fixes: 2906f66a5682 ("ipvs: SCTP Trasport Loadbalancing Support")
    Cc: stable@vger.kernel.org
    Link: https://lore.kernel.org/netdev/20260705123040.35755-1-zhaoyz24@mails.tsinghua.edu.cn/
    Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
    Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
    Reported-by: Ao Wang <wangao@seu.edu.cn>
    Reported-by: Xuewei Feng <fengxw06@126.com>
    Reported-by: Qi Li <qli01@tsinghua.edu.cn>
    Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
    Assisted-by: Claude Code:GLM-5.2
    Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
    Acked-by: Julian Anastasov <ja@ssi.bg>
    Signed-off-by: Florian Westphal <fw@strlen.de>

diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c
index 394367b7b388..c67317be17df 100644
--- a/net/netfilter/ipvs/ip_vs_proto_sctp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c
@@ -372,20 +372,15 @@ static const char *sctp_state_name(int state)

 static inline void
 set_sctp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp,
-		int direction, const struct sk_buff *skb)
+		int direction, const struct sk_buff *skb,
+		unsigned int iph_len)
 {
 	struct sctp_chunkhdr _sctpch, *sch;
 	unsigned char chunk_type;
 	int event, next_state;
-	int ihl, cofs;
-
-#ifdef CONFIG_IP_VS_IPV6
-	ihl = cp->af == AF_INET ? ip_hdrlen(skb) : sizeof(struct ipv6hdr);
-#else
-	ihl = ip_hdrlen(skb);
-#endif
+	int cofs;

-	cofs = ihl + sizeof(struct sctphdr);
+	cofs = iph_len + sizeof(struct sctphdr);
 	sch = skb_header_pointer(skb, cofs, sizeof(_sctpch), &_sctpch);
 	if (sch == NULL)
 		return;
@@ -472,7 +467,7 @@ sctp_state_transition(struct ip_vs_conn *cp, int direction,
 		unsigned int iph_len)
 {
 	spin_lock_bh(&cp->lock);
-	set_sctp_state(pd, cp, direction, skb);
+	set_sctp_state(pd, cp, direction, skb, iph_len);
 	spin_unlock_bh(&cp->lock);
 }