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]>