Hello,

On Sun, 5 Jul 2026, Yizhou Zhao wrote:

> 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 ip_vs_fill_iph_skb() in set_sctp_state() and base the chunk-header
> offset on iph.len, matching sctp_conn_schedule() and the SCTP NAT handlers.
> For IPv4 and IPv6 packets without extension headers this preserves the
> existing offset.
> 
> Fixes: 2906f66a5682 ("ipvs: SCTP Trasport Loadbalancing Support")
> Cc: [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 | 12 +++++-------
>  1 file changed, 5 insertions(+), 7 deletions(-)
> 
> diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c 
> b/net/netfilter/ipvs/ip_vs_proto_sctp.c
> index 63c78a1f3918..6e0fc23be305 100644
> --- a/net/netfilter/ipvs/ip_vs_proto_sctp.c
> +++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c
> @@ -375,17 +375,15 @@ set_sctp_state(struct ip_vs_proto_data *pd, struct 
> ip_vs_conn *cp,
>               int direction, const struct sk_buff *skb)
>  {
>       struct sctp_chunkhdr _sctpch, *sch;
>       unsigned char chunk_type;
> +     struct ip_vs_iphdr iph;
>       int event, next_state;
> -     int ihl, cofs;
> +     int cofs;
>  
> -#ifdef CONFIG_IP_VS_IPV6
> -     ihl = cp->af == AF_INET ? ip_hdrlen(skb) : sizeof(struct ipv6hdr);
> -#else
> -     ihl = ip_hdrlen(skb);
> -#endif
> +     if (!ip_vs_fill_iph_skb(cp->af, skb, false, &iph))
> +             return;

        May be it is better starting from ip_vs_set_state()
to provide new arg 'int iph_len/offset' (set to iph.len), down to
state_transition(), sctp_state_transition() and set_sctp_state().
Same for all protos. It should cost less stack and ipv6_find_hdr()
calls and what matters most, correct iph context in case we
have IP+ICMP+TCP (with just two ports or even with TCP flags)
and are scheduling ICMP, i.e. not IP+TCP as usually.

        But what I see is that ip_vs_in_icmp*() are missing
the ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd) call just
after ip_vs_in_stats() and before ip_vs_icmp_xmit() where
we should provide ciph.len. That is why we don't reach the
set_tcp_state() calls to set correct cp->state and timeout
when scheduling related ICMP. So, this should be fixed too.

> -     cofs = ihl + sizeof(struct sctphdr);
> +     cofs = iph.len + sizeof(struct sctphdr);
>       sch = skb_header_pointer(skb, cofs, sizeof(_sctpch), &_sctpch);
>       if (sch == NULL)
>               return;
> -- 
> 2.47.3

Regards

--
Julian Anastasov <[email protected]>


Reply via email to