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: [email protected]
Link: 
https://lore.kernel.org/netdev/[email protected]/
Reported-by: Yizhou Zhao <[email protected]>
Reported-by: Yuxiang Yang <[email protected]>
Reported-by: Ao Wang <[email protected]>
Reported-by: Xuewei Feng <[email protected]>
Reported-by: Qi Li <[email protected]>
Reported-by: Ke Xu <[email protected]>
Assisted-by: Claude Code:GLM-5.2
Signed-off-by: Yizhou Zhao <[email protected]>
---
 net/netfilter/ipvs/ip_vs_proto_sctp.c | 15 +++++----------
 1 file changed, 5 insertions(+), 10 deletions(-)

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


Reply via email to