* [PATCH net 00/15] IPVS fixes for net
@ 2026-11-13 1:14 Pablo Neira Ayuso
2026-11-13 1:14 ` [PATCH net 01/15] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp_v6 Pablo Neira Ayuso
` (14 more replies)
0 siblings, 15 replies; 18+ messages in thread
From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
Hi,
The following patchset contains IPVS fixes for net:
1) Avoid out-of-bounds write in ip_vs_nat_icmp_v6() when an ICMPv6
error embeds a non-first ESP fragment, from Axel Mierczuk.
2) Update *offset for non-first fragments in ipv6_find_hdr(), so
callers no longer use the initial offset as if it had been
updated. Also from Axel.
3) Rework connection refcounting so the pending and running timer
hold a reference. This fixes a use-after-free race between
connection deletion and a concurrent timer callback, from
Julian Anastasov.
4) Expire controlled connection chains iteratively instead of
recursively to avoid kernel stack exhaustion, from Zihan Xi.
5) Reject zero and configured FTP control ports as data ports in
the FTP helper, from Zihan Xi.
6) Skip conntrack templates in ip_vs_ftp_out() to fix a UAF in the
expectation list after the CT rule is removed, from Theodor
Arsenij Larionov Trichkine.
7) Add the seqadj extension before linking the FTP expectation, since
krealloc() of ct->ext can otherwise leave the expectation pointing
to freed memory. Also from Theodor.
8) Update conn flags, seqs and related fields in the backup server
under cp->lock, from Julian Anastasov.
9) Read conn flags and seqs atomically in the master when building
sync messages, from Julian Anastasov.
10) Fix a race that can leave a destination in the overload state
forever. Also make the LBLC/LBLCR schedulers honor the overload
state for cached destinations, from Julian Anastasov.
11) Validate cport and IP_VS_CONN_F_NO_CPORT in received sync records.
This rejects templates and connections that incoming lookups
cannot match, from Axel Mierczuk.
12) Skip cport 0 connections in ip_vs_conn_out_get(), using the same
rule as __ip_vs_conn_in_get(). Also from Axel.
13) Do not create connections from packets with client or virtual
port 0, which could create a duplicate connection for every
packet, from Julian Anastasov.
14) Wait for an RCU grace period before reusing a destination from
dest_trash, and before restarting its estimator, from Darong Lu.
15) Pass the netfilter hook's state->sk to the update_pmtu handler,
and pass a NULL sk to ip_local_out() when forwarding through an
IPVS tunnel, from Julian Anastasov.
Please, pull these changes from:
git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf.git nf-26-11-13
Thanks.
----------------------------------------------------------------
The following changes since commit d1bb235aa409a4e5aeba6c748d953d948625b1e9:
netfilter: nat: zero newrange before filling it in (2026-11-05 13:31:11 +0100)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf.git nf-26-11-13
for you to fetch changes up to 937b060406a547e4d931525e600540726ea26532:
ipvs: pass correct sk to update_pmtu and on tunneling (2026-11-13 02:08:35 +0100)
----------------------------------------------------------------
netfilter pull request 26-11-13
----------------------------------------------------------------
Axel Mierczuk (4):
ipvs: avoid out-of-bounds write in ip_vs_nat_icmp_v6
ipv6: update *offset for non-first fragments in ipv6_find_hdr()
ipvs: validate cport in received sync records
ipvs: skip cport 0 connections in ip_vs_conn_out_get()
Darong Lu (1):
ipvs: wait grace period before reusing destinations
Julian Anastasov (6):
ipvs: fix problems during connection deletion
ipvs: update flags and seqs in backup server under lock
ipvs: read flags and seqs in master atomically
ipvs: honor the overload state and do not enter it forever
ipvs: do not create conns from packets with port 0
ipvs: pass correct sk to update_pmtu and on tunneling
Theodor Arsenij Larionov Trichkine (2):
ipvs: ftp: skip conntrack templates in ip_vs_ftp_out
ipvs: ftp: add seqadj extension before linking the expectation
Zihan Xi (2):
ipvs: avoid stack overflow from recursive connection expiration
ipvs: reject FTP control ports as data ports
include/net/ip_vs.h | 52 ++++++++----
net/ipv6/exthdrs_core.c | 6 +-
net/netfilter/ipvs/ip_vs_conn.c | 133 ++++++++++++++++++------------
net/netfilter/ipvs/ip_vs_core.c | 58 +++++++++----
net/netfilter/ipvs/ip_vs_ctl.c | 140 ++++++++++++++++++++++++--------
net/netfilter/ipvs/ip_vs_ftp.c | 39 +++++++--
net/netfilter/ipvs/ip_vs_lblc.c | 8 +-
net/netfilter/ipvs/ip_vs_lblcr.c | 13 +--
net/netfilter/ipvs/ip_vs_proto_ah_esp.c | 2 +-
net/netfilter/ipvs/ip_vs_proto_sctp.c | 12 ++-
net/netfilter/ipvs/ip_vs_proto_tcp.c | 12 ++-
net/netfilter/ipvs/ip_vs_proto_udp.c | 11 ++-
net/netfilter/ipvs/ip_vs_sync.c | 53 ++++++++++--
net/netfilter/ipvs/ip_vs_xmit.c | 114 +++++++++++++++++---------
14 files changed, 462 insertions(+), 191 deletions(-)
^ permalink raw reply [flat|nested] 18+ messages in thread* [PATCH net 01/15] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp_v6 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:16 ` netdev-bot+sinfo 2026-11-13 1:14 ` [PATCH net 02/15] ipv6: update *offset for non-first fragments in ipv6_find_hdr() Pablo Neira Ayuso ` (13 subsequent siblings) 14 siblings, 1 reply; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Axel Mierczuk <axel.mierczuk@1password.com> ipv6_find_hdr() does not set the header offset for non-first fragments, so an ICMPv6 error embedding such an ESP fragment leaves ip_vs_fill_iph_skb_icmp() with len == off. The NAT paths size skb_ensure_writable() from len, and ip_vs_nat_icmp_v6() then writes the embedded addresses after the validated area. Include the embedded IP header in ciph.len at both ICMPv6 call sites when parsing stops at a non-first fragment. ESP lookup does not require a transport header. Non-first TCP, UDP and SCTP fragments bypass IPVS ICMP handling before connection lookup. This fix does not depend on patch 2. Stable kernels without commit 342e24a339b9 ("ipvs: do not mangle ICMP replies for non-first fragments") also need that commit to handle incoming ICMPv6 errors quoting non-first ESP fragments. Fixes: 63dca2c0b0e7 ("ipvs: Fix faulty IPv6 extension header handling in IPVS") Cc: stable@vger.kernel.org Suggested-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Axel Mierczuk <axel.mierczuk@1password.com> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- net/netfilter/ipvs/ip_vs_core.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index 8a3c880fdb26..76213159404f 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -1206,6 +1206,13 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, true, &ciph)) return NF_ACCEPT; /* The packet looks wrong, ignore */ + /* ipv6_find_hdr() does not include the embedded header for + * non-first fragments, add it so that ESP can pass and the + * NAT writable checks cover the rewritten addresses + */ + if (ciph.len == ciph.off) + ciph.len += sizeof(struct ipv6hdr); + pp = ip_vs_proto_get(ciph.protocol); if (!pp) return NF_ACCEPT; @@ -2036,6 +2043,13 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, if (!ip_vs_fill_iph_skb_icmp(AF_INET6, skb, offset, true, &ciph)) return NF_ACCEPT; + /* ipv6_find_hdr() does not include the embedded header for + * non-first fragments, add it so that ESP can pass and the + * NAT writable checks cover the rewritten addresses + */ + if (ciph.len == ciph.off) + ciph.len += sizeof(struct ipv6hdr); + pd = ip_vs_proto_data_get(ipvs, ciph.protocol); if (!pd) return NF_ACCEPT; -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH net 01/15] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp_v6 2026-11-13 1:14 ` [PATCH net 01/15] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp_v6 Pablo Neira Ayuso @ 2026-11-13 1:16 ` netdev-bot+sinfo 2026-11-13 3:17 ` Pablo Neira Ayuso 0 siblings, 1 reply; 18+ messages in thread From: netdev-bot+sinfo @ 2026-11-13 1:16 UTC (permalink / raw) To: Pablo Neira Ayuso Cc: netfilter-devel, davem, netdev, kuba, pabeni, edumazet, horms, fw, ja Hi! This is an automated message. This series looks like a fix, but its commit messages seem to be missing some information: - How the issue was discovered, e.g. hit in production, hit during development, syzbot report, manual code inspection, LLM or static analysis tool scan. - Whether the issue was actually triggered, or is only theoretical (e.g. found by LLM and not proven at runtime). If it was triggered please include the symptoms, like the stack trace or kernel messages. Please do not repost the series just to address the above. Instead, reply to this email with the missing information, so that reviewers can take it into account. If the series needs another revision for other reasons, please include the information in the commit messages then. The evaluation is done by an LLM so it may be wrong, if you think that is the case please reply and explain. ^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net 01/15] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp_v6 2026-11-13 1:16 ` netdev-bot+sinfo @ 2026-11-13 3:17 ` Pablo Neira Ayuso 0 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 3:17 UTC (permalink / raw) To: netdev-bot+sinfo Cc: netfilter-devel, davem, netdev, kuba, pabeni, edumazet, horms, fw, ja On Fri, Nov 13, 2026 at 01:16:59AM +0000, netdev-bot+sinfo@kernel.org wrote: > Hi! > > This is an automated message. This series looks like a fix, but its > commit messages seem to be missing some information: > > - How the issue was discovered, e.g. hit in production, hit during > development, syzbot report, manual code inspection, LLM or static > analysis tool scan. This is a PR, there is a mix of patches coming from different sources but... > - Whether the issue was actually triggered, or is only theoretical > (e.g. found by LLM and not proven at runtime). If it was triggered > please include the symptoms, like the stack trace or kernel messages. ... these are mostly LLM findings. > Please do not repost the series just to address the above. Instead, > reply to this email with the missing information, so that reviewers > can take it into account. If the series needs another revision for > other reasons, please include the information in the commit messages > then. > > The evaluation is done by an LLM so it may be wrong, if you think > that is the case please reply and explain. ^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net 02/15] ipv6: update *offset for non-first fragments in ipv6_find_hdr() 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 01/15] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp_v6 Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 03/15] ipvs: fix problems during connection deletion Pablo Neira Ayuso ` (12 subsequent siblings) 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Axel Mierczuk <axel.mierczuk@1password.com> The non-first fragment early return is the only successful exit of ipv6_find_hdr() that leaves *offset untouched. IPVS used the initial offset as if it had been updated. For non-first fragments with target < 0, set *offset immediately after the Fragment header, at the start of the fragment payload. Callers must still account for the nonzero fragment offset. A NEXTHDR_FRAGMENT search continues to return the Fragment header's offset. Other target < 0 callers check the returned fragment offset or ignore *offset here. Fixes: b777e0ce7437 ("[NETFILTER]: make ipv6_find_hdr() find transport protocol header") Suggested-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Axel Mierczuk <axel.mierczuk@1password.com> Acked-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- net/ipv6/exthdrs_core.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/net/ipv6/exthdrs_core.c b/net/ipv6/exthdrs_core.c index 4a9748338cf4..e27f5b8cc154 100644 --- a/net/ipv6/exthdrs_core.c +++ b/net/ipv6/exthdrs_core.c @@ -179,7 +179,10 @@ EXPORT_SYMBOL_GPL(ipv6_find_tlv); * * Note that non-1st fragment is special case that "the protocol number * of last header" is "next header" field in Fragment header. In this case, - * *offset is meaningless and fragment offset is stored in *fragoff if fragoff + * for target < 0, *offset points immediately after the Fragment header, + * at the start of the fragment payload. Callers must still account for + * the nonzero fragment offset before interpreting the payload. The + * fragment offset is stored in *fragoff if fragoff * isn't NULL. * * if flags is not NULL and it's a fragment, then the frag flag @@ -261,6 +264,7 @@ int ipv6_find_hdr(const struct sk_buff *skb, unsigned int *offset, hp->nexthdr == NEXTHDR_NONE)) { if (fragoff) *fragoff = _frag_off; + *offset = start + sizeof(struct frag_hdr); return hp->nexthdr; } if (!found) -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 03/15] ipvs: fix problems during connection deletion 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 01/15] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp_v6 Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 02/15] ipv6: update *offset for non-first fragments in ipv6_find_hdr() Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 04/15] ipvs: avoid stack overflow from recursive connection expiration Pablo Neira Ayuso ` (11 subsequent siblings) 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Julian Anastasov <ja@ssi.bg> Sashiko reports for problem when deleting connections. If connection timer expires, its callback can not be concurrently running but if connection is deleted the callback can be running on another CPU even after all references are released. Before now we continued with the connection freeing, risking the callback to access the deleted connection after it is freed. As ip_vs_conn_del() runs under RCU lock there is no risk for accessing a connection freed by concurrent timer callback. As Sashiko warns, the risk is present if normal connection expires and tries to delete its cp->control chain with ip_vs_conn_del_put() without holding a RCU read lock. It is evident that as the pending timer and the running timer callback do not hold reference for the connection, there are races which is difficult to avoid. Change the connection refcounting as follows: * the connection hashing does not take reference anymore * new connection handoffs its reference to the timer if started in __ip_vs_conn_put_timer(), to hold it while it is pending. * if user finds connection in hash table it gets reference and can handoff this reference to the pending timer if this timer is started in __ip_vs_conn_put_timer(), otherwise puts its reference if the timer was already pending which is the common case. * the pending timer handoffs its reference to the running timer callback implicitly. By this way, the timer callback in the common case has the last reference. In rare cases, while the timer callback is running and before it changes refcnt from 1 to 0, other users can get reference and to handoff it again to the pending timer they arm. In this case, the expiration is deferred for later time. As result, cp->refcnt accounts all current connection users and the pending and running timer. Two paths can try to delete the connection: the normal expiration path and ip_vs_conn_del() if it successfully steals the connection reference from the pending timer. refcount_dec_if_one() will tell us if we were the last active user with no packets accessing the connection and no pending/running timers. Change ip_vs_conn_unlink() to directly unhash the connection because when we are the last user the connection's forwarding method can not change. Use smp_acquire__after_ctrl_dep() after refcount_dec_if_one() and before reading the connection fields. ip_vs_conn_expire() does not need to call timer_delete() anymore because the pending timer is already accounted in refcnt. Add explicit rcu_read_lock() while deleting the cp->control chain to protect from concurrent timer callback for ct to expire it before us and to cause use-after-free. During such races, try to keep 0 in cp->timeout as it is a request for deleting our cp->control chain immediately. Make sure we do not send timeout 0 (seconds) in the v1 sync message because the backup server will select some large default value which is not desired for small timeouts. Remove the smp_mb__before_atomic() call from __ip_vs_conn_put(), it is from the atomic_dec() era, now refcount_dec() has RELEASE semantics. Link: https://sashiko.dev/#/patchset/cover.1789435989.git.zihanx%40nebusec.ai Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/cover.1790266803.git.zihanx%40nebusec.ai Fixes: f9200a52eedf ("ipvs: avoid expiring many connections from timer") Signed-off-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- include/net/ip_vs.h | 1 - net/netfilter/ipvs/ip_vs_conn.c | 99 ++++++++++++++++++--------------- net/netfilter/ipvs/ip_vs_sync.c | 2 +- 3 files changed, 55 insertions(+), 47 deletions(-) diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index 9a83bff32569..97097d60d1c4 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -1685,7 +1685,6 @@ static inline bool __ip_vs_conn_get(struct ip_vs_conn *cp) /* put back the conn without restarting its timer */ static inline void __ip_vs_conn_put(struct ip_vs_conn *cp) { - smp_mb__before_atomic(); refcount_dec(&cp->refcnt); } void ip_vs_conn_put(struct ip_vs_conn *cp); diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index 94f25fb7d87e..86c5e6c7b5af 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -293,7 +293,6 @@ static inline int ip_vs_conn_hash(struct ip_vs_conn *cp) cp->flags |= IP_VS_CONN_F_HASHED; WRITE_ONCE(cp->hn0.hash_key, hash_key); WRITE_ONCE(cp->hn1.hash_key, hash_key2); - refcount_inc(&cp->refcnt); hlist_bl_add_head_rcu(&cp->hn0.node, head); if (use2) hlist_bl_add_head_rcu(&cp->hn1.node, head2); @@ -319,11 +318,27 @@ static inline bool ip_vs_conn_unlink(struct ip_vs_conn *cp) struct hlist_bl_head *head, *head2; u32 hash_key, hash_key2; struct ip_vs_rht *t; - bool ret = false; bool use2; + /* No more users and no running/pending timer except us? */ + if (!refcount_dec_if_one(&cp->refcnt)) + return false; + + /* The ONE_PACKET connection is special: + * - the flag is present only for normal connections, not for templates + * - its timer is never started + * - it is never hashed, so you can not find it and delete it + * - it is not synced + * - it has only one user + * - it is freed after use immediately + */ if (cp->flags & IP_VS_CONN_F_ONE_PACKET) - return refcount_dec_if_one(&cp->refcnt); + return true; + + /* Add the needed ACQUIRE barrier between the above + * refcount_dec_if_one() call and the read of cp fields + */ + smp_acquire__after_ctrl_dep(); rcu_read_lock(); local_bh_disable(); @@ -337,15 +352,11 @@ static inline bool ip_vs_conn_unlink(struct ip_vs_conn *cp) false /* new_hash2 */, &head, &head2); if (cp->flags & IP_VS_CONN_F_HASHED) { - /* Decrease refcnt and unlink conn only if we are last user */ - if (use2 == ip_vs_conn_use_hash2(cp) && - refcount_dec_if_one(&cp->refcnt)) { - hlist_bl_del_rcu(&cp->hn0.node); - if (use2) - hlist_bl_del_rcu(&cp->hn1.node); - cp->flags &= ~IP_VS_CONN_F_HASHED; - ret = true; - } + /* Unlink conn as we are the last user */ + hlist_bl_del_rcu(&cp->hn0.node); + if (use2) + hlist_bl_del_rcu(&cp->hn1.node); + cp->flags &= ~IP_VS_CONN_F_HASHED; } conn_tab_unlock(head, head2); @@ -353,7 +364,7 @@ static inline bool ip_vs_conn_unlink(struct ip_vs_conn *cp) local_bh_enable(); rcu_read_unlock(); - return ret; + return true; } @@ -621,9 +632,12 @@ static void __ip_vs_conn_put_timer(struct ip_vs_conn *cp) { unsigned long t = (cp->flags & IP_VS_CONN_F_ONE_PACKET) ? 0 : cp->timeout; - mod_timer(&cp->timer, jiffies+t); - __ip_vs_conn_put(cp); + /* Handoff our reference to the timer if it was just armed, + * put the reference if timer was already pending. + */ + if (mod_timer(&cp->timer, jiffies + t)) + __ip_vs_conn_put(cp); } void ip_vs_conn_put(struct ip_vs_conn *cp) @@ -1322,9 +1336,18 @@ static void ip_vs_conn_rcu_free(struct rcu_head *head) kmem_cache_free(ip_vs_conn_cachep, cp); } -/* Try to delete connection while not holding reference */ +/* Try to delete connection while not holding reference. + * It can be called concurrently and always under RCU lock + * to protect from connection deletion from others. + * Used when we need to remove the connection immediately + * and without overloading the timer processing. + * OTOH, we use ip_vs_conn_expire_now() when we prefer to + * leave the current context (packet processing) and to + * remove the connection from timer. + */ static void ip_vs_conn_del(struct ip_vs_conn *cp) { + /* Steal reference from the pending timer */ if (timer_delete(&cp->timer)) { /* Drop cp->control chain too */ if (cp->control) @@ -1333,20 +1356,11 @@ static void ip_vs_conn_del(struct ip_vs_conn *cp) } } -/* Try to delete connection while holding reference */ -static void ip_vs_conn_del_put(struct ip_vs_conn *cp) -{ - if (timer_delete(&cp->timer)) { - /* Drop cp->control chain too */ - if (cp->control) - cp->timeout = 0; - __ip_vs_conn_put(cp); - ip_vs_conn_expire(&cp->timer); - } else { - __ip_vs_conn_put(cp); - } -} - +/* Connection is removed in the following steps: + * - timer expires (with ref) or connection is deleted (with stolen ref) + * - there should be no more users except us (pending/running timer, + * n_control>0 or refcnt>1) + */ static void ip_vs_conn_expire(struct timer_list *t) { struct ip_vs_conn *cp = timer_container_of(cp, t, timer); @@ -1362,23 +1376,18 @@ static void ip_vs_conn_expire(struct timer_list *t) if (likely(ip_vs_conn_unlink(cp))) { struct ip_vs_conn *ct = cp->control; - /* delete the timer if it is activated by other users */ - timer_delete(&cp->timer); - /* does anybody control me? */ if (ct) { - bool has_ref = !cp->timeout && __ip_vs_conn_get(ct); - + rcu_read_lock(); ip_vs_control_del(cp); /* Drop CTL or non-assured TPL if not used anymore */ - if (has_ref && !atomic_read(&ct->n_control) && + if (!cp->timeout && !atomic_read(&ct->n_control) && (!(ct->flags & IP_VS_CONN_F_TEMPLATE) || !(ct->state & IP_VS_CTPL_S_ASSURED))) { IP_VS_DBG(4, "drop controlling connection\n"); - ip_vs_conn_del_put(ct); - } else if (has_ref) { - __ip_vs_conn_put(ct); + ip_vs_conn_del(ct); } + rcu_read_unlock(); } if ((cp->flags & IP_VS_CONN_F_NFCT) && @@ -1413,8 +1422,8 @@ static void ip_vs_conn_expire(struct timer_list *t) refcount_read(&cp->refcnt), atomic_read(&cp->n_control)); - refcount_inc(&cp->refcnt); - cp->timeout = 60*HZ; + if (cp->timeout || atomic_read(&cp->n_control)) + cp->timeout = 60 * HZ; if (ipvs->sync_state & IP_VS_STATE_MASTER) ip_vs_sync_conn(ipvs, cp, sysctl_sync_threshold(ipvs)); @@ -1432,7 +1441,7 @@ static void ip_vs_conn_expire(struct timer_list *t) void ip_vs_conn_expire_now(struct ip_vs_conn *cp) { /* Using mod_timer_pending will ensure the timer is not - * modified after the final timer_delete in ip_vs_conn_expire. + * started without giving it new reference. */ if (timer_pending(&cp->timer) && time_after(cp->timer.expires, jiffies)) @@ -1500,9 +1509,9 @@ ip_vs_conn_new(const struct ip_vs_conn_param *p, int dest_af, spin_lock_init(&cp->lock); /* - * Set the entry is referenced by the current thread before hashing - * it in the table, so that other thread run ip_vs_random_dropentry - * but cannot drop this entry. + * Set the entry as referenced by the current thread before starting + * the timer and giving it our reference to allow other threads to + * run ip_vs_random_dropentry() or any deletion method safely. */ refcount_set(&cp->refcnt, 1); diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c index 157f5c8361ab..1caacf419af9 100644 --- a/net/netfilter/ipvs/ip_vs_sync.c +++ b/net/netfilter/ipvs/ip_vs_sync.c @@ -732,7 +732,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts) s->v4.vport = cp->vport; s->v4.dport = cp->dport; s->v4.fwmark = htonl(cp->fwmark); - s->v4.timeout = htonl(cp->timeout / HZ); + s->v4.timeout = htonl(max(1UL, cp->timeout / HZ)); m->nr_conns++; #ifdef CONFIG_IP_VS_IPV6 -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 04/15] ipvs: avoid stack overflow from recursive connection expiration 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso ` (2 preceding siblings ...) 2026-11-13 1:14 ` [PATCH net 03/15] ipvs: fix problems during connection deletion Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 05/15] ipvs: reject FTP control ports as data ports Pablo Neira Ayuso ` (10 subsequent siblings) 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Zihan Xi <zihanx@nebusec.ai> When a controlled IPVS connection expires, its controller may be expired synchronously if it has no remaining controlled connections. A chain of controlled connections can then recurse through ip_vs_conn_expire() and exhaust the kernel stack during namespace cleanup. Continue expiration with the controller after the current connection has been fully cleaned up instead of calling ip_vs_conn_del() recursively. Keep the expiration walk under RCU, preserve the immediate-drop timeout for a controller with its own controller, and switch to deletion mode before the next iteration. This keeps controlled-connection cleanup synchronous while using one stack frame for the whole chain. The timer callback race during connection deletion is handled by the preceding connection-deletion fix. This patch depends on "ipvs: fix problems during connection deletion" to protect the iterative controller cleanup from the timer callback race. Fixes: f9200a52eedf ("ipvs: avoid expiring many connections from timer") Cc: stable@vger.kernel.org Reported-by: Vega <vega@nebusec.ai> Co-developed-by: Luxing Yin <root@tr0jan.top> Signed-off-by: Luxing Yin <root@tr0jan.top> Signed-off-by: Zihan Xi <zihanx@nebusec.ai> Signed-off-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- net/netfilter/ipvs/ip_vs_conn.c | 40 ++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index 86c5e6c7b5af..27db8d558a03 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -1336,9 +1336,23 @@ static void ip_vs_conn_rcu_free(struct rcu_head *head) kmem_cache_free(ip_vs_conn_cachep, cp); } -/* Try to delete connection while not holding reference. +/* Try to steal the reference from the pending timer. * It can be called concurrently and always under RCU lock * to protect from connection deletion from others. + */ +static bool ip_vs_conn_del_reference(struct ip_vs_conn *cp) +{ + /* Steal reference from the pending timer */ + if (!timer_delete(&cp->timer)) + return false; + + /* Drop cp->control chain too */ + if (cp->control) + cp->timeout = 0; + return true; +} + +/* Try to delete connection while not holding reference. * Used when we need to remove the connection immediately * and without overloading the timer processing. * OTOH, we use ip_vs_conn_expire_now() when we prefer to @@ -1347,13 +1361,8 @@ static void ip_vs_conn_rcu_free(struct rcu_head *head) */ static void ip_vs_conn_del(struct ip_vs_conn *cp) { - /* Steal reference from the pending timer */ - if (timer_delete(&cp->timer)) { - /* Drop cp->control chain too */ - if (cp->control) - cp->timeout = 0; + if (ip_vs_conn_del_reference(cp)) ip_vs_conn_expire(&cp->timer); - } } /* Connection is removed in the following steps: @@ -1366,6 +1375,9 @@ static void ip_vs_conn_expire(struct timer_list *t) struct ip_vs_conn *cp = timer_container_of(cp, t, timer); struct netns_ipvs *ipvs = cp->ipvs; + rcu_read_lock(); + +repeat: /* * do I control anybody? */ @@ -1375,19 +1387,18 @@ static void ip_vs_conn_expire(struct timer_list *t) /* Unlink conn if not referenced anymore */ if (likely(ip_vs_conn_unlink(cp))) { struct ip_vs_conn *ct = cp->control; + bool next = false; /* does anybody control me? */ if (ct) { - rcu_read_lock(); ip_vs_control_del(cp); /* Drop CTL or non-assured TPL if not used anymore */ if (!cp->timeout && !atomic_read(&ct->n_control) && (!(ct->flags & IP_VS_CONN_F_TEMPLATE) || !(ct->state & IP_VS_CTPL_S_ASSURED))) { IP_VS_DBG(4, "drop controlling connection\n"); - ip_vs_conn_del(ct); + next = ip_vs_conn_del_reference(ct); } - rcu_read_unlock(); } if ((cp->flags & IP_VS_CONN_F_NFCT) && @@ -1414,7 +1425,11 @@ static void ip_vs_conn_expire(struct timer_list *t) else call_rcu(&cp->rcu_head, ip_vs_conn_rcu_free); atomic_dec(&ipvs->conn_count); - return; + if (next) { + cp = ct; + goto repeat; + } + goto out; } expire_later: @@ -1429,6 +1444,9 @@ static void ip_vs_conn_expire(struct timer_list *t) ip_vs_sync_conn(ipvs, cp, sysctl_sync_threshold(ipvs)); __ip_vs_conn_put_timer(cp); + +out: + rcu_read_unlock(); } /* Modify timer, so that it expires as soon as possible. -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 05/15] ipvs: reject FTP control ports as data ports 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso ` (3 preceding siblings ...) 2026-11-13 1:14 ` [PATCH net 04/15] ipvs: avoid stack overflow from recursive connection expiration Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 06/15] ipvs: ftp: skip conntrack templates in ip_vs_ftp_out Pablo Neira Ayuso ` (9 subsequent siblings) 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Zihan Xi <zihanx@nebusec.ai> The FTP helper must not treat a configured control port as a data port. Accepting such a port can leave IPVS connections in a state that leads to recursive expiration and kernel stack exhaustion. Reject zero and configured control ports before creating passive data connections. For active mode, reject a zero client port and a derived data port that is itself a configured control port. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega <vega@nebusec.ai> Co-developed-by: Luxing Yin <root@tr0jan.top> Signed-off-by: Luxing Yin <root@tr0jan.top> Signed-off-by: Zihan Xi <zihanx@nebusec.ai> Signed-off-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- net/netfilter/ipvs/ip_vs_ftp.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/net/netfilter/ipvs/ip_vs_ftp.c b/net/netfilter/ipvs/ip_vs_ftp.c index 9e3e005a8263..4822a1a75212 100644 --- a/net/netfilter/ipvs/ip_vs_ftp.c +++ b/net/netfilter/ipvs/ip_vs_ftp.c @@ -62,6 +62,17 @@ static unsigned short ports[IP_VS_APP_MAX_PORTS] = {21, 0}; module_param_array(ports, ushort, &ports_count, 0444); MODULE_PARM_DESC(ports, "Ports to monitor for FTP control commands"); +static bool is_control_port(u16 port) +{ + unsigned int i; + + for (i = 0; i < ports_count; i++) { + if (ports[i] == port) + return true; + } + return false; +} + static char *ip_vs_ftp_data_ptr(struct sk_buff *skb, struct ip_vs_iphdr *ipvsh) { @@ -319,6 +330,10 @@ static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp, return 1; } + /* Do not redirect data to control ports */ + if (!port || is_control_port(ntohs(port))) + return 0; + /* Now update or create a connection entry for it */ { struct ip_vs_conn_param p; @@ -529,6 +544,9 @@ static int ip_vs_ftp_in(struct ip_vs_app *app, struct ip_vs_conn *cp, return 1; } + if (!port || is_control_port(ntohs(cp->vport) - 1)) + return 0; + /* Passive mode off */ cp->app_data = (void *) IP_VS_FTP_ACTIVE; -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 06/15] ipvs: ftp: skip conntrack templates in ip_vs_ftp_out 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso ` (4 preceding siblings ...) 2026-11-13 1:14 ` [PATCH net 05/15] ipvs: reject FTP control ports as data ports Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 07/15] ipvs: ftp: add seqadj extension before linking the expectation Pablo Neira Ayuso ` (8 subsequent siblings) 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Theodor Arsenij Larionov Trichkine <theodorlarionov@gmail.com> A CT target hooked after conntrack attaches its template to a packet that conntrack left without an entry. On an IPVS FTP PASV/EPSV reply, ip_vs_ftp_out() then mangles with the template and creates an expectation with the template as master. Deleting the CT rule frees the template and leaves the expectation, so a later expectation flush reads freed memory: BUG: KASAN: slab-use-after-free in nf_ct_unlink_expect_report Reachable with CAP_NET_ADMIN in a user namespace. Skip templates, as nft_ct and nfnetlink_queue do. Fixes: f4bc17cdd205 ("ipvs: netfilter connection tracking changes") Cc: stable@vger.kernel.org Signed-off-by: Theodor Arsenij Larionov Trichkine <theodorlarionov@gmail.com> Acked-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- net/netfilter/ipvs/ip_vs_ftp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/ipvs/ip_vs_ftp.c b/net/netfilter/ipvs/ip_vs_ftp.c index 4822a1a75212..44de99f4d187 100644 --- a/net/netfilter/ipvs/ip_vs_ftp.c +++ b/net/netfilter/ipvs/ip_vs_ftp.c @@ -383,7 +383,7 @@ static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp, buf_len = strlen(buf); ct = nf_ct_get(skb, &ctinfo); - if (ct) { + if (ct && !nf_ct_is_template(ct)) { bool mangled; /* If mangling fails this function will return 0 -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 07/15] ipvs: ftp: add seqadj extension before linking the expectation 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso ` (5 preceding siblings ...) 2026-11-13 1:14 ` [PATCH net 06/15] ipvs: ftp: skip conntrack templates in ip_vs_ftp_out Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 08/15] ipvs: update flags and seqs in backup server under lock Pablo Neira Ayuso ` (7 subsequent siblings) 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Theodor Arsenij Larionov Trichkine <theodorlarionov@gmail.com> ip_vs_ftp_out() links an expectation into the helper extension of the conntrack, then ip_vs_update_conntrack() adds the seqadj extension. If the conntrack is still unconfirmed, which happens when the real server's reply does not match an existing conntrack and creates a new one, krealloc() can move ct->ext and the expectation list keeps pointing into the freed area. Deleting the conntrack then writes to it: BUG: KASAN: slab-use-after-free in nf_ct_unlink_expect_report+0x490/0x4c0 Write of size 8 at addr ffff888004dd4e48 by task syz.2.17/649 Reachable with CAP_NET_ADMIN in a user namespace. Add the seqadj extension before mangling, so it is in place before the expectation is linked. Fixes: b25adce16064 ("ipvs: correct usage/allocation of seqadj ext in ipvs") Cc: stable@vger.kernel.org Acked-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Theodor Arsenij Larionov Trichkine <theodorlarionov@gmail.com> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- net/netfilter/ipvs/ip_vs_ftp.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_ftp.c b/net/netfilter/ipvs/ip_vs_ftp.c index 44de99f4d187..81c3a2fd27b3 100644 --- a/net/netfilter/ipvs/ip_vs_ftp.c +++ b/net/netfilter/ipvs/ip_vs_ftp.c @@ -29,6 +29,7 @@ #include <linux/netfilter.h> #include <net/netfilter/nf_conntrack.h> #include <net/netfilter/nf_conntrack_expect.h> +#include <net/netfilter/nf_conntrack_seqadj.h> #include <net/netfilter/nf_nat.h> #include <net/netfilter/nf_nat_helper.h> #include <linux/gfp.h> @@ -384,19 +385,25 @@ static int ip_vs_ftp_out(struct ip_vs_app *app, struct ip_vs_conn *cp, ct = nf_ct_get(skb, &ctinfo); if (ct && !nf_ct_is_template(ct)) { - bool mangled; + bool mangled = false; /* If mangling fails this function will return 0 * which will cause the packet to be dropped. * Mangling can only fail under memory pressure, * hopefully it will succeed on the retransmitted * packet. + * + * Add the seqadj extension before the expectation is + * linked into the helper extension: adding it later + * reallocates ct->ext under the expectation list. */ - mangled = nf_nat_mangle_tcp_packet(skb, ct, ctinfo, - ipvsh->len, - start - data, - end - start, - buf, buf_len); + if (nf_ct_is_confirmed(ct) || nfct_seqadj(ct) || + nfct_seqadj_ext_add(ct)) + mangled = nf_nat_mangle_tcp_packet(skb, ct, ctinfo, + ipvsh->len, + start - data, + end - start, + buf, buf_len); if (mangled) { ip_vs_nfct_expect_related(skb, ct, n_cp, ipvsh->protocol, 0, 0); -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 08/15] ipvs: update flags and seqs in backup server under lock 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso ` (6 preceding siblings ...) 2026-11-13 1:14 ` [PATCH net 07/15] ipvs: ftp: add seqadj extension before linking the expectation Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 09/15] ipvs: read flags and seqs in master atomically Pablo Neira Ayuso ` (6 subsequent siblings) 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Julian Anastasov <ja@ssi.bg> Sashiko reports that we modify the conn flags and seq values in the backup server without holding cp->lock. The same is true for other fields such as state, old_state and timeout which are modified by protocols under lock. Extend the locked region in ip_vs_proc_conn() to update the fields atomically. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927220816.268206-1-pablo%40netfilter.org Signed-off-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- net/netfilter/ipvs/ip_vs_sync.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c index 1caacf419af9..ba2fa1f9d08e 100644 --- a/net/netfilter/ipvs/ip_vs_sync.c +++ b/net/netfilter/ipvs/ip_vs_sync.c @@ -850,6 +850,7 @@ static void ip_vs_proc_conn(struct netns_ipvs *ipvs, struct ip_vs_conn_param *pa unsigned long timeout, __u32 fwmark, struct ip_vs_sync_conn_options *opt) { + struct ip_vs_conn *cp_new = NULL; struct ip_vs_dest *dest; struct ip_vs_conn *cp; @@ -891,9 +892,6 @@ static void ip_vs_proc_conn(struct netns_ipvs *ipvs, struct ip_vs_conn_param *pa flags &= IP_VS_CONN_F_BACKUP_UPD_MASK; flags |= cp->flags & ~IP_VS_CONN_F_BACKUP_UPD_MASK; cp->flags = flags; - spin_unlock_bh(&cp->lock); - if (!dest) - ip_vs_try_bind_dest(cp); } else { /* * Find the appropriate destination for the connection. @@ -920,6 +918,8 @@ static void ip_vs_proc_conn(struct netns_ipvs *ipvs, struct ip_vs_conn_param *pa } if (!(flags & IP_VS_CONN_F_TEMPLATE)) kfree(param->pe_data); + cp_new = cp; + spin_lock_bh(&cp->lock); } if (opt) { @@ -951,6 +951,9 @@ static void ip_vs_proc_conn(struct netns_ipvs *ipvs, struct ip_vs_conn_param *pa else cp->timeout = (3*60*HZ); } + spin_unlock_bh(&cp->lock); + if (!dest && !cp_new) + ip_vs_try_bind_dest(cp); ip_vs_conn_put(cp); } -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 09/15] ipvs: read flags and seqs in master atomically 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso ` (7 preceding siblings ...) 2026-11-13 1:14 ` [PATCH net 08/15] ipvs: update flags and seqs in backup server under lock Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 10/15] ipvs: honor the overload state and do not enter it forever Pablo Neira Ayuso ` (5 subsequent siblings) 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Julian Anastasov <ja@ssi.bg> After we fixed the double read of cp->flags in the master with commit 3e3fe49e8678 ("ipvs: fix buffer overflow when sending sync messages") Sashiko reports that we should also read the cp->flags and the seqs atomically. So if the SEQ_MASK is set, re-read the flags and the seqs under cp->lock to provide a snapshot when sending the sync message. Fixes: 3e3fe49e8678 ("ipvs: fix buffer overflow when sending sync messages") Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927220816.268206-1-pablo%40netfilter.org Signed-off-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- net/netfilter/ipvs/ip_vs_sync.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c index ba2fa1f9d08e..f11c59eca553 100644 --- a/net/netfilter/ipvs/ip_vs_sync.c +++ b/net/netfilter/ipvs/ip_vs_sync.c @@ -539,6 +539,7 @@ static int ip_vs_sync_conn_needed(struct netns_ipvs *ipvs, static void ip_vs_sync_conn_v0(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts) { + struct ip_vs_sync_conn_options opts; struct ip_vs_sync_mesg_v0 *m; struct ip_vs_sync_conn_v0 *s; struct ip_vs_sync_buff *buff; @@ -557,6 +558,14 @@ static void ip_vs_sync_conn_v0(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, if (!ip_vs_sync_conn_needed(ipvs, cp, pkts)) return; + if (flags & IP_VS_CONN_F_SEQ_MASK) { + /* Take atomic snapshot of flags and seqs */ + spin_lock_bh(&cp->lock); + flags = cp->flags; + memcpy(&opts, &cp->sync_conn_opt, sizeof(opts)); + spin_unlock_bh(&cp->lock); + } + spin_lock_bh(&ipvs->sync_buff_lock); if (!(ipvs->sync_state & IP_VS_STATE_MASTER)) { spin_unlock_bh(&ipvs->sync_buff_lock); @@ -604,7 +613,7 @@ static void ip_vs_sync_conn_v0(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, if (seq_mask) { struct ip_vs_sync_conn_options *opt = (struct ip_vs_sync_conn_options *)&s[1]; - memcpy(opt, &cp->sync_conn_opt, sizeof(*opt)); + memcpy(opt, &opts, sizeof(*opt)); } m->nr_conns++; @@ -630,6 +639,7 @@ static void ip_vs_sync_conn_v0(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, */ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts) { + struct ip_vs_sync_conn_options opts; struct ip_vs_sync_mesg *m; union ip_vs_sync_conn *s; struct ip_vs_sync_buff *buff; @@ -651,6 +661,13 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts) if (!ip_vs_sync_conn_needed(ipvs, cp, pkts)) goto control; flags = READ_ONCE(cp->flags); + if (flags & IP_VS_CONN_F_SEQ_MASK) { + /* Take atomic snapshot of flags and seqs */ + spin_lock_bh(&cp->lock); + flags = cp->flags; + memcpy(&opts, &cp->sync_conn_opt, sizeof(opts)); + spin_unlock_bh(&cp->lock); + } /* Sanity checks */ pe_name_len = 0; @@ -752,9 +769,9 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts) if (seq_mask) { *(p++) = IPVS_OPT_SEQ_DATA; *(p++) = sizeof(struct ip_vs_sync_conn_options); - hton_seq(&cp->in_seq, (struct ip_vs_seq *)p); + hton_seq(&opts.in_seq, (struct ip_vs_seq *)p); p += sizeof(struct ip_vs_seq); - hton_seq(&cp->out_seq, (struct ip_vs_seq *)p); + hton_seq(&opts.out_seq, (struct ip_vs_seq *)p); p += sizeof(struct ip_vs_seq); } /* Handle pe data */ -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 10/15] ipvs: honor the overload state and do not enter it forever 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso ` (8 preceding siblings ...) 2026-11-13 1:14 ` [PATCH net 09/15] ipvs: read flags and seqs in master atomically Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 11/15] ipvs: validate cport in received sync records Pablo Neira Ayuso ` (4 subsequent siblings) 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Julian Anastasov <ja@ssi.bg> Sashiko warns that we may enter the dest overload state forever if setting the u_threshold to 1 races with the ip_vs_unbind_dest() call for the last connection for dest. The problem is that we prefer ip_vs_unbind_dest() to avoid using locks while reading the threshold. To avoid the race, add a smp_mb() in __ip_vs_dest_update_overload() that is paired with the atomic_fetch_dec() in ip_vs_unbind_dest() which allows to re-read the totalconns and to leave the overload state if we are below l_threshold_val. Also, remove the overload parameter from ip_vs_dest_update_overload() because we just need to know the current state while deciding which threshold ranges to use. This ip_vs_dest_is_overloaded() check can be in __ip_vs_dest_update_overload() instead. To avoid ip_vs_bind_dest() to enter lock just to initialize u_threshold_val to INT_MAX, do it in ip_vs_new_dest(). The LBLC and LBLCR schedulers do not honor the global overload state for cached dests, so add a ip_vs_dest_is_overloaded() check for the situations where we may fallback from overloaded dest (LBLC and LBLCR) or when we hit a cached dest (LBLC). Also, move the ip_vs_dest_set_min() call in LBLCR after the ip_vs_dest_set_erase() call that removes the most-loaded server because due to races we may work with dest that is in process of removing, i.e. ip_vs_dest_set_min() and ip_vs_dest_set_max() may return same dest. Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261019150249.1879516-2-pablo%40netfilter.org Fixes: 3ba20137287f ("ipvs: fix more races around the overload flag") Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- include/net/ip_vs.h | 2 +- net/netfilter/ipvs/ip_vs_conn.c | 4 ++-- net/netfilter/ipvs/ip_vs_ctl.c | 30 ++++++++++++++++++------------ net/netfilter/ipvs/ip_vs_lblc.c | 8 +++++--- net/netfilter/ipvs/ip_vs_lblcr.c | 13 +++++++------ 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index 97097d60d1c4..c1f7a3f144f1 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -1922,7 +1922,7 @@ static inline void ip_vs_dest_put_and_free(struct ip_vs_dest *dest) kfree(dest); } -void ip_vs_dest_update_overload(struct ip_vs_dest *dest, bool overload); +void ip_vs_dest_update_overload(struct ip_vs_dest *dest); /* Check if dest is in overloaded state */ static inline bool ip_vs_dest_is_overloaded(const struct ip_vs_dest *dest) diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index 27db8d558a03..66fcdc00ef37 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -1168,7 +1168,7 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest) atomic_inc(&dest->activeconns); tc = atomic_inc_return(&dest->totalconns); if (tc >= READ_ONCE(dest->u_threshold_val)) - ip_vs_dest_update_overload(dest, true); + ip_vs_dest_update_overload(dest); } else { /* It is a persistent connection/template, so increase the persistent connection counter */ @@ -1261,7 +1261,7 @@ static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp) atomic_dec(&dest->activeconns); tc = atomic_fetch_dec(&dest->totalconns); if (tc <= READ_ONCE(dest->l_threshold_val)) - ip_vs_dest_update_overload(dest, false); + ip_vs_dest_update_overload(dest); } else { /* It is a persistent connection/template, so decrease the persistent connection counter */ diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c index ce9fe20d6106..5868adda6ed6 100644 --- a/net/netfilter/ipvs/ip_vs_ctl.c +++ b/net/netfilter/ipvs/ip_vs_ctl.c @@ -1304,12 +1304,12 @@ void ip_vs_stats_free(struct ip_vs_stats *stats) } } -/* Update overload flag based on number of dest conns and lower/upper +/* Update overload state based on number of dest conns and lower/upper * connection thresholds: - * - conns reach u_threshold and exceed it: set the flag - * - conns go below l_threshold (or 75% of u_threshold): clear the flag + * - conns reach u_threshold and exceed it: set the overload state + * - conns go below l_threshold (or 75% of u_threshold): clear the state */ -static void __ip_vs_dest_update_overload(struct ip_vs_dest *dest, bool overload) +static void __ip_vs_dest_update_overload(struct ip_vs_dest *dest) { int conns; u32 l, u; @@ -1320,10 +1320,18 @@ static void __ip_vs_dest_update_overload(struct ip_vs_dest *dest, bool overload) /* Low threshold defaults to 75% of upper threshold */ l = READ_ONCE(dest->l_threshold) ? : (u - (u >> 2)); conns = atomic_read(&dest->totalconns); - if (conns >= (overload ? l : u)) { + if (conns >= (ip_vs_dest_is_overloaded(dest) ? l : u)) { WRITE_ONCE(dest->u_threshold_val, INT_MAX); WRITE_ONCE(dest->l_threshold_val, l); - return; + /* Commit thresholds before repeating the check, + * it is paired with atomic_fetch_dec() in + * ip_vs_unbind_dest() to avoid entering the + * overload state forever. + */ + smp_mb(); + conns = atomic_read(&dest->totalconns); + if (conns >= l) + return; } } @@ -1331,10 +1339,10 @@ static void __ip_vs_dest_update_overload(struct ip_vs_dest *dest, bool overload) WRITE_ONCE(dest->l_threshold_val, 0); } -void ip_vs_dest_update_overload(struct ip_vs_dest *dest, bool overload) +void ip_vs_dest_update_overload(struct ip_vs_dest *dest) { spin_lock_bh(&dest->dst_lock); - __ip_vs_dest_update_overload(dest, overload); + __ip_vs_dest_update_overload(dest); spin_unlock_bh(&dest->dst_lock); } @@ -1406,13 +1414,10 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest, if (READ_ONCE(dest->u_threshold) != udest->u_threshold || READ_ONCE(dest->l_threshold) != udest->l_threshold) { - bool overload; - spin_lock_bh(&dest->dst_lock); WRITE_ONCE(dest->u_threshold, udest->u_threshold); WRITE_ONCE(dest->l_threshold, udest->l_threshold); - overload = ip_vs_dest_is_overloaded(dest); - __ip_vs_dest_update_overload(dest, overload); + __ip_vs_dest_update_overload(dest); spin_unlock_bh(&dest->dst_lock); } @@ -1477,6 +1482,7 @@ ip_vs_new_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest) if (ret < 0) goto err_stats; + dest->u_threshold_val = INT_MAX; dest->af = udest->af; dest->protocol = svc->protocol; dest->vaddr = svc->addr; diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c index e1111c3b2721..f5ce1f2afb86 100644 --- a/net/netfilter/ipvs/ip_vs_lblc.c +++ b/net/netfilter/ipvs/ip_vs_lblc.c @@ -467,10 +467,11 @@ is_overloaded(struct ip_vs_dest *dest, struct ip_vs_service *svc) struct ip_vs_dest *d; list_for_each_entry_rcu(d, &svc->destinations, n_list) { - if (atomic_read(&d->activeconns)*2 - < atomic_read(&d->weight)) { + if (ip_vs_dest_is_overloaded(d)) + continue; + if (atomic_read(&d->activeconns) * 2 < + atomic_read(&d->weight)) return 1; - } } } return 0; @@ -507,6 +508,7 @@ ip_vs_lblc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, dest = en->dest; if ((dest->cflags & IP_VS_DEST_CF_AVAILABLE) && + !ip_vs_dest_is_overloaded(dest) && atomic_read(&dest->weight) > 0 && !is_overloaded(dest, svc)) goto out; } diff --git a/net/netfilter/ipvs/ip_vs_lblcr.c b/net/netfilter/ipvs/ip_vs_lblcr.c index dfb5bf83750d..4046608922e7 100644 --- a/net/netfilter/ipvs/ip_vs_lblcr.c +++ b/net/netfilter/ipvs/ip_vs_lblcr.c @@ -630,10 +630,11 @@ is_overloaded(struct ip_vs_dest *dest, struct ip_vs_service *svc) struct ip_vs_dest *d; list_for_each_entry_rcu(d, &svc->destinations, n_list) { - if (atomic_read(&d->activeconns)*2 - < atomic_read(&d->weight)) { + if (ip_vs_dest_is_overloaded(d)) + continue; + if (atomic_read(&d->activeconns) * 2 < + atomic_read(&d->weight)) return 1; - } } } return 0; @@ -658,9 +659,6 @@ ip_vs_lblcr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, if (en) { en->lastuse = jiffies; - /* Get the least loaded destination */ - dest = ip_vs_dest_set_min(&en->set); - /* More than one destination + enough time passed by, cleanup */ if (atomic_read(&en->set.size) > 1 && time_after(jiffies, en->set.lastmod + @@ -676,6 +674,9 @@ ip_vs_lblcr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb, spin_unlock_bh(&svc->sched_lock); } + /* Get the least loaded destination */ + dest = ip_vs_dest_set_min(&en->set); + /* If the destination is not overloaded, use it */ if (dest && !is_overloaded(dest, svc)) goto out; -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 11/15] ipvs: validate cport in received sync records 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso ` (9 preceding siblings ...) 2026-11-13 1:14 ` [PATCH net 10/15] ipvs: honor the overload state and do not enter it forever Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 12/15] ipvs: skip cport 0 connections in ip_vs_conn_out_get() Pablo Neira Ayuso ` (3 subsequent siblings) 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Axel Mierczuk <axel.mierczuk@1password.com> Local templates have cport 0 and IP_VS_CONN_F_NO_CPORT clear, so ip_vs_conn_in_get() cannot match them. A template received over the unauthenticated sync protocol with a nonzero cport can reach the protocol state machine. Before template state validation, this could cause out-of-bounds reads, crashes and kernel data leaks. Ordinary records with cport 0 and IP_VS_CONN_F_NO_CPORT clear, or with a nonzero cport and IP_VS_CONN_F_NO_CPORT set, create connections that incoming lookups cannot match. The latter also keeps the cport-zero fallback in ip_vs_conn_in_get() enabled. Reject all three cases. Continue accepting ordinary connections with cport 0 and IP_VS_CONN_F_NO_CPORT. The companion commit f96e91748f63 ("ipvs: filter some flags received in the backup server") rejects template records carrying IP_VS_CONN_F_NO_CPORT. Fixes: 87375ab47cd0 ("[IPVS]: ip_vs_ftp breaks connections using persistence") Cc: stable@vger.kernel.org Suggested-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Axel Mierczuk <axel.mierczuk@1password.com> Acked-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- net/netfilter/ipvs/ip_vs_sync.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c index f11c59eca553..7e562e745ab5 100644 --- a/net/netfilter/ipvs/ip_vs_sync.c +++ b/net/netfilter/ipvs/ip_vs_sync.c @@ -871,6 +871,25 @@ static void ip_vs_proc_conn(struct netns_ipvs *ipvs, struct ip_vs_conn_param *pa struct ip_vs_dest *dest; struct ip_vs_conn *cp; + /* Templates require cport 0. Ordinary connections require + * NO_CPORT exactly when cport is 0. + */ + if (flags & IP_VS_CONN_F_TEMPLATE) { + if (param->cport) { + IP_VS_DBG(2, "BACKUP, template with cport dropped\n"); + kfree(param->pe_data); + return; + } + } else if (!param->cport && !(flags & IP_VS_CONN_F_NO_CPORT)) { + IP_VS_DBG(2, "BACKUP, conn without cport dropped\n"); + kfree(param->pe_data); + return; + } else if (param->cport && (flags & IP_VS_CONN_F_NO_CPORT)) { + IP_VS_DBG(2, "BACKUP, conn with cport and NO_CPORT dropped\n"); + kfree(param->pe_data); + return; + } + if (!(flags & IP_VS_CONN_F_TEMPLATE)) { cp = ip_vs_conn_in_get(param); if (cp && ((cp->dport != dport) || -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 12/15] ipvs: skip cport 0 connections in ip_vs_conn_out_get() 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso ` (10 preceding siblings ...) 2026-11-13 1:14 ` [PATCH net 11/15] ipvs: validate cport in received sync records Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 13/15] ipvs: do not create conns from packets with port 0 Pablo Neira Ayuso ` (2 subsequent siblings) 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Axel Mierczuk <axel.mierczuk@1password.com> ip_vs_conn_out_get() does not filter templates. A template with cport 0 is matched by a packet sent to client port 0 that traverses the out hook, and handle_response() then runs ip_vs_set_state() and the timeout update on a connection that must never see packets. A backup server receives such templates over the unauthenticated sync protocol. An ordinary connection scheduled for a client that uses source port 0 has cport 0 as well and is matched the same way. Apply the rule used by __ip_vs_conn_in_get(): a lookup for client port 0 matches only connections flagged IP_VS_CONN_F_NO_CPORT, which still wait for their cport, and a lookup for a nonzero client port matches only connections without this flag. Test p->vport, which was already compared with cp->cport, instead of reading cp->cport again. ip_vs_ftp_out() relies on the first case: it looks up the data connection with client port 0 to find the IP_VS_CONN_F_NO_CPORT entry created for an earlier PASV or EPSV reply. That lookup keeps working. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Suggested-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Axel Mierczuk <axel.mierczuk@1password.com> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- net/netfilter/ipvs/ip_vs_conn.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index 66fcdc00ef37..83d8a9153a34 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -590,6 +590,8 @@ struct ip_vs_conn *ip_vs_conn_out_get(const struct ip_vs_conn_param *p) ip_vs_addr_equal(p->af, p->vaddr, &cp->caddr) && ip_vs_addr_equal(p->af, p->caddr, saddr) && + (!p->vport ^ + (!(cp->flags & IP_VS_CONN_F_NO_CPORT))) && p->protocol == cp->protocol) { if (__ip_vs_conn_get(cp)) goto out; -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 13/15] ipvs: do not create conns from packets with port 0 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso ` (11 preceding siblings ...) 2026-11-13 1:14 ` [PATCH net 12/15] ipvs: skip cport 0 connections in ip_vs_conn_out_get() Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 14/15] ipvs: wait grace period before reusing destinations Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 15/15] ipvs: pass correct sk to update_pmtu and on tunneling Pablo Neira Ayuso 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Julian Anastasov <ja@ssi.bg> We should disallow normal connections with cport 0 to be created from packets by the scheduler (from clients) or by ip_vs_new_conn_out (from real servers) because later such traffic can not hit the connection due to the NO_CPORT requirement. As result, it can lead to duplicate connection for every packet. Do the same for vport 0. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Link: https://sashiko.dev/#/patchset/20261104201530.147292-1-ja%40ssi.bg Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261103140308.591690-3-pablo%40netfilter.org Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260925141115.16126-1-axel.mierczuk%401password.com Signed-off-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- net/netfilter/ipvs/ip_vs_core.c | 3 ++- net/netfilter/ipvs/ip_vs_proto_sctp.c | 7 +++++++ net/netfilter/ipvs/ip_vs_proto_tcp.c | 7 +++++++ net/netfilter/ipvs/ip_vs_proto_udp.c | 6 ++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index 76213159404f..15617bd19e96 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -1312,6 +1312,7 @@ static inline bool is_new_conn_expected(const struct ip_vs_conn *cp, * In fwmark-VS actual vaddr and vport are unknown to IPVS * 2) Real Server and Virtual Service were NOT configured without port: * This is to allow match of different VS to the same RS ip-addr + * 3) Client port is not 0 */ struct ip_vs_conn *ip_vs_new_conn_out(struct ip_vs_service *svc, struct ip_vs_dest *dest, @@ -1335,7 +1336,7 @@ struct ip_vs_conn *ip_vs_new_conn_out(struct ip_vs_service *svc, /* check pre-requisites are satisfied */ if (svc->fwmark) return NULL; - if (!vport || !dport) + if (!vport || !dport || !cport) return NULL; /* for persistent service first create connection template */ diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c index fb8af6b15a39..cb34bcceb4f6 100644 --- a/net/netfilter/ipvs/ip_vs_proto_sctp.c +++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c @@ -30,6 +30,7 @@ sctp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, sch = skb_header_pointer(skb, iph->len + sizeof(_sctph), sizeof(_schunkh), &_schunkh); if (sch) { + /* Accept packet without scheduling? */ if (sch->type == SCTP_CID_ABORT || !(sysctl_sloppy_sctp(ipvs) || sch->type == SCTP_CID_INIT)) @@ -56,6 +57,12 @@ sctp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, if (svc) { int ignored; + /* Do not create connection for port 0 */ + if (!ports[0] || !ports[1]) { + *verdict = NF_DROP; + return 0; + } + if (ip_vs_todrop(ipvs)) { /* * It seems that we are very loaded. diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c index fec0e8b47b71..25d4576d0e12 100644 --- a/net/netfilter/ipvs/ip_vs_proto_tcp.c +++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c @@ -48,6 +48,7 @@ tcp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, if (likely(!ip_vs_iph_icmp(iph))) { th = skb_header_pointer(skb, iph->len, sizeof(_tcph), &_tcph); if (th) { + /* Accept packet without scheduling? */ if (th->rst || !(sysctl_sloppy_tcp(ipvs) || th->syn)) return 1; ports = &th->source; @@ -74,6 +75,12 @@ tcp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, if (svc) { int ignored; + /* Do not create connection for port 0 */ + if (!ports[0] || !ports[1]) { + *verdict = NF_DROP; + return 0; + } + if (ip_vs_todrop(ipvs)) { /* * It seems that we are very loaded. diff --git a/net/netfilter/ipvs/ip_vs_proto_udp.c b/net/netfilter/ipvs/ip_vs_proto_udp.c index f32785682402..ab3ab9524168 100644 --- a/net/netfilter/ipvs/ip_vs_proto_udp.c +++ b/net/netfilter/ipvs/ip_vs_proto_udp.c @@ -62,6 +62,12 @@ udp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, if (svc) { int ignored; + /* Do not create connection for port 0 */ + if (!ports[0] || !ports[1]) { + *verdict = NF_DROP; + return 0; + } + if (ip_vs_todrop(ipvs)) { /* * It seems that we are very loaded. -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 14/15] ipvs: wait grace period before reusing destinations 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso ` (12 preceding siblings ...) 2026-11-13 1:14 ` [PATCH net 13/15] ipvs: do not create conns from packets with port 0 Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 15/15] ipvs: pass correct sk to update_pmtu and on tunneling Pablo Neira Ayuso 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Darong Lu <zzyy19904204639@163.com> An IPVS destination can be removed from a service and immediately reused from dest_trash before an RCU grace period has elapsed. Reusing its n_list or d_list can make existing readers follow links into another service or real-server hash chain. The grace-period cookie is also saved when a live destination is unhashed for an edit and consumed when it is rehashed. Describe rcu_state as the cookie for an unlink, covering both trash and rehash cases. When a destination is retrieved from dest_trash, ip_vs_add_dest() currently restarts its estimator before waiting for the grace period. If the estimator was on a tick chain, ip_vs_stop_estimator() removed est->list with hlist_del_rcu(), so an estimator kthread can still be traversing that node. ip_vs_start_estimator() rewrites the node on its temporary list on success, or initializes it on failure, before the current wait. A reader can then follow the changed link or stop traversing the chain early. Wait immediately after retrieving the destination, before starting its estimator. The conditional grace-period wait then covers the destination list, real-server hash and estimator list on both estimator success and failure paths. Fixes: bcbde4c0a755 ("ipvs: make the service replacement more robust") Cc: stable@vger.kernel.org Reported-by: Vega <vega@nebusec.ai> Reported-by: Darong Lu <zzyy19904204639@163.com> Co-developed-by: Jiayuan Chen <jiayuan.chen@linux.dev> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev> Signed-off-by: Darong Lu <zzyy19904204639@163.com> Signed-off-by: Ren Wei <weir@nebusec.ai> Signed-off-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- include/net/ip_vs.h | 4 +- net/netfilter/ipvs/ip_vs_ctl.c | 110 ++++++++++++++++++++++++++------- 2 files changed, 91 insertions(+), 23 deletions(-) diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index c1f7a3f144f1..8dc80d5d42dc 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -1021,7 +1021,9 @@ struct ip_vs_dest { struct rcu_head rcu_head; struct list_head t_list; /* in dest_trash */ - unsigned int in_rs_table:1; /* we are in rs_table */ + unsigned long rcu_state; /* GP cookie on unlink (trash or rehash) */ + unsigned int in_rs_table:1, /* we are in rs_table */ + need_gp:1; /* Need GP on rehash */ }; /* The scheduler object */ diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c index 5868adda6ed6..be818bf7d380 100644 --- a/net/netfilter/ipvs/ip_vs_ctl.c +++ b/net/netfilter/ipvs/ip_vs_ctl.c @@ -64,7 +64,8 @@ int ip_vs_get_debug_level(void) /* Protos */ -static void __ip_vs_del_service(struct ip_vs_service *svc, bool cleanup); +static void __ip_vs_del_service(struct ip_vs_service *svc, + struct list_head *dest_trash, bool cleanup); #ifdef CONFIG_IP_VS_IPV6 @@ -918,6 +919,11 @@ static void ip_vs_rs_hash(struct netns_ipvs *ipvs, struct ip_vs_dest *dest) */ hash = ip_vs_rs_hashkey(dest->af, &dest->addr, port); + /* Need RCU grace period when rehashing */ + if (dest->need_gp) { + cond_synchronize_rcu(dest->rcu_state); + dest->need_gp = 0; + } hlist_add_head_rcu(&dest->d_list, &ipvs->rs_table[hash]); dest->in_rs_table = 1; } @@ -1111,6 +1117,8 @@ ip_vs_trash_get_dest(struct ip_vs_service *svc, int dest_af, struct ip_vs_dest *dest; struct netns_ipvs *ipvs = svc->ipvs; + lockdep_assert_held(&ipvs->service_mutex); + /* * Find the destination in trash */ @@ -1144,24 +1152,57 @@ ip_vs_trash_get_dest(struct ip_vs_service *svc, int dest_af, return dest; } -/* Put destination in trash */ -static void ip_vs_trash_put_dest(struct netns_ipvs *ipvs, +/* Add destination in temp list for trash */ +static void ip_vs_trash_add_dest(struct netns_ipvs *ipvs, struct ip_vs_dest *dest, unsigned long istart, - bool cleanup) + struct list_head *dest_trash) { - spin_lock_bh(&ipvs->dest_trash_lock); IP_VS_DBG_BUF(3, "Moving dest %s:%u into trash, dest->refcnt=%d\n", IP_VS_DBG_ADDR(dest->af, &dest->addr), ntohs(dest->port), refcount_read(&dest->refcnt)); + /* dest lives in trash with reference */ + list_add(&dest->t_list, dest_trash); + dest->idle_start = istart; +} + +/* Put destinations in trash */ +static void ip_vs_trash_put_dests(struct netns_ipvs *ipvs, + struct list_head *dest_trash, bool cleanup) +{ + spin_lock_bh(&ipvs->dest_trash_lock); if (list_empty(&ipvs->dest_trash) && !cleanup) mod_timer(&ipvs->dest_trash_timer, jiffies + (IP_VS_DEST_TRASH_PERIOD >> 1)); - /* dest lives in trash with reference */ - list_add(&dest->t_list, &ipvs->dest_trash); - dest->idle_start = istart; + list_splice(dest_trash, &ipvs->dest_trash); spin_unlock_bh(&ipvs->dest_trash_lock); } +/* Put destination back in trash */ +static void ip_vs_trash_put_back(struct netns_ipvs *ipvs, + struct ip_vs_dest *dest) +{ + LIST_HEAD(dest_trash); + + ip_vs_trash_add_dest(ipvs, dest, dest->idle_start, &dest_trash); + ip_vs_trash_put_dests(ipvs, &dest_trash, false); +} + +/* Put destinations in trash and save the RCU state */ +static void ip_vs_trash_save_dests(struct netns_ipvs *ipvs, + struct list_head *dest_trash, bool cleanup) +{ + struct ip_vs_dest *dest; + unsigned long rcu_state; + + if (!list_empty(dest_trash)) { + /* Remember when dests were removed and added to dest_trash */ + rcu_state = get_state_synchronize_rcu(); + list_for_each_entry(dest, dest_trash, t_list) + dest->rcu_state = rcu_state; + ip_vs_trash_put_dests(ipvs, dest_trash, cleanup); + } +} + static void ip_vs_dest_rcu_free(struct rcu_head *head) { struct ip_vs_dest *dest; @@ -1377,8 +1418,16 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest, if ((udest->conn_flags & IP_VS_CONN_F_FWD_MASK) != IP_VS_DFWD_METHOD(dest) || udest->tun_type != dest->tun_type || - udest->tun_port != dest->tun_port) + udest->tun_port != dest->tun_port) { + bool unlinked = dest->in_rs_table; + ip_vs_rs_unhash(dest); + if (unlinked) { + dest->rcu_state = get_state_synchronize_rcu(); + /* Need grace period in this or next dest update */ + dest->need_gp = 1; + } + } /* set the tunnel info */ dest->tun_type = udest->tun_type; @@ -1562,6 +1611,11 @@ ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest) dest = ip_vs_trash_get_dest(svc, udest->af, &daddr, dport); if (dest != NULL) { + /* Readers may still follow the n_list, d_list or estimator list + * links. Wait before reusing any of them. + */ + cond_synchronize_rcu(dest->rcu_state); + IP_VS_DBG_BUF(3, "Get destination %s:%u from trash, " "dest->refcnt=%d, service %u/%s:%u\n", IP_VS_DBG_ADDR(udest->af, &daddr), ntohs(dport), @@ -1572,11 +1626,14 @@ ip_vs_add_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest) ret = ip_vs_start_estimator(svc->ipvs, &dest->stats); /* On error put back dest into the trash */ - if (ret < 0) - ip_vs_trash_put_dest(svc->ipvs, dest, dest->idle_start, - false); - else + if (ret < 0) { + ip_vs_trash_put_back(svc->ipvs, dest); + } else { + /* need_gp matters only on dest edit, reinit it */ + dest->need_gp = 0; + __ip_vs_update_dest(svc, dest, udest, 1); + } } else { /* * Allocate and initialize the dest structure @@ -1640,7 +1697,7 @@ ip_vs_edit_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest) * Delete a destination (must be already unlinked from the service) */ static void __ip_vs_del_dest(struct netns_ipvs *ipvs, struct ip_vs_dest *dest, - bool cleanup) + struct list_head *dest_trash, bool cleanup) { ip_vs_stop_estimator(ipvs, &dest->stats); @@ -1649,7 +1706,7 @@ static void __ip_vs_del_dest(struct netns_ipvs *ipvs, struct ip_vs_dest *dest, */ ip_vs_rs_unhash(dest); - ip_vs_trash_put_dest(ipvs, dest, 0, cleanup); + ip_vs_trash_add_dest(ipvs, dest, 0, dest_trash); /* Queue up delayed work to expire all no destination connections. * No-op when CONFIG_SYSCTL is disabled. @@ -1699,6 +1756,7 @@ ip_vs_del_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest) { struct ip_vs_dest *dest; __be16 dport = udest->port; + LIST_HEAD(dest_trash); /* We use function that requires RCU lock */ rcu_read_lock(); @@ -1718,7 +1776,9 @@ ip_vs_del_dest(struct ip_vs_service *svc, struct ip_vs_dest_user_kern *udest) /* * Delete the destination */ - __ip_vs_del_dest(svc->ipvs, dest, false); + __ip_vs_del_dest(svc->ipvs, dest, &dest_trash, false); + + ip_vs_trash_save_dests(svc->ipvs, &dest_trash, false); return 0; } @@ -2063,7 +2123,8 @@ ip_vs_edit_service(struct ip_vs_service *svc, struct ip_vs_service_user_kern *u) * - The service must be unlinked, unlocked and not referenced! * - We are called under _bh lock */ -static void __ip_vs_del_service(struct ip_vs_service *svc, bool cleanup) +static void __ip_vs_del_service(struct ip_vs_service *svc, + struct list_head *dest_trash, bool cleanup) { struct ip_vs_dest *dest, *nxt; struct ip_vs_scheduler *old_sched; @@ -2097,7 +2158,7 @@ static void __ip_vs_del_service(struct ip_vs_service *svc, bool cleanup) */ list_for_each_entry_safe(dest, nxt, &svc->destinations, n_list) { __ip_vs_unlink_dest(svc, dest, 0); - __ip_vs_del_dest(svc->ipvs, dest, cleanup); + __ip_vs_del_dest(svc->ipvs, dest, dest_trash, cleanup); } /* @@ -2120,7 +2181,8 @@ static void __ip_vs_del_service(struct ip_vs_service *svc, bool cleanup) /* * Unlink a service from list and try to delete it if its refcnt reached 0 */ -static void ip_vs_unlink_service(struct ip_vs_service *svc, bool cleanup) +static void ip_vs_unlink_service(struct ip_vs_service *svc, + struct list_head *dest_trash, bool cleanup) { ip_vs_unregister_conntrack(svc); /* Hold svc to avoid double release from dest_trash */ @@ -2130,7 +2192,7 @@ static void ip_vs_unlink_service(struct ip_vs_service *svc, bool cleanup) */ ip_vs_svc_unhash(svc); - __ip_vs_del_service(svc, cleanup); + __ip_vs_del_service(svc, dest_trash, cleanup); } /* @@ -2140,12 +2202,14 @@ static int ip_vs_del_service(struct ip_vs_service *svc) { struct netns_ipvs *ipvs; struct ip_vs_rht *t, *p; + LIST_HEAD(dest_trash); int ns; if (svc == NULL) return -EEXIST; ipvs = svc->ipvs; - ip_vs_unlink_service(svc, false); + ip_vs_unlink_service(svc, &dest_trash, false); + ip_vs_trash_save_dests(ipvs, &dest_trash, false); /* Drop the table if no more services */ ns = ip_vs_get_num_services(ipvs); @@ -2196,6 +2260,7 @@ static int ip_vs_flush(struct netns_ipvs *ipvs, bool cleanup) struct hlist_bl_node *ne; struct hlist_bl_node *e; struct ip_vs_rht *t, *p; + LIST_HEAD(dest_trash); /* Stop the resizer and drop the tables */ if (!test_and_set_bit(IP_VS_WORK_SVC_NORESIZE, &ipvs->work_flags)) @@ -2205,8 +2270,9 @@ static int ip_vs_flush(struct netns_ipvs *ipvs, bool cleanup) if (ip_vs_get_num_services(ipvs)) { ip_vs_rht_walk_buckets(ipvs->svc_table, head) { hlist_bl_for_each_entry_safe(svc, e, ne, head, s_list) - ip_vs_unlink_service(svc, cleanup); + ip_vs_unlink_service(svc, &dest_trash, cleanup); } + ip_vs_trash_save_dests(ipvs, &dest_trash, cleanup); } /* Unregister the hash table and release it after RCU grace period */ -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 15/15] ipvs: pass correct sk to update_pmtu and on tunneling 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso ` (13 preceding siblings ...) 2026-11-13 1:14 ` [PATCH net 14/15] ipvs: wait grace period before reusing destinations Pablo Neira Ayuso @ 2026-11-13 1:14 ` Pablo Neira Ayuso 14 siblings, 0 replies; 18+ messages in thread From: Pablo Neira Ayuso @ 2026-11-13 1:14 UTC (permalink / raw) To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja From: Julian Anastasov <ja@ssi.bg> This commit fixes two cases of providing sk on tunneling, one to the update_pmtu handler and the second case is to the ip_local_out() call. For local clients, we can see at LOCAL_OUT a netfilter hook's state->sk that is different from the protocol's skb->sk. This can be the case for UDP_TUNNEL where different sk is provided to ip_local_out. If such packet is then forwarded via IPVS tunnel we may need to provide state->sk to the update_pmtu handler, not skb->sk. Fix that by passing the netfilter's hook state via the packet_xmit and conn_schedule handlers to call the update_pmtu handler with state->sk. While here, change maybe_update_pmtu() to be family independent by using dst and do not request neigh confirm via the last update_pmtu arg because we do not generate the event based on remote signal. Sashiko has some warnings for the update_pmtu handlers because we provide only sk, not skb: - we do not provide skb because the handlers assume they are called from tunnel transmitters where skb->dev is present but for IPVS skb->dev is not set yet - without skb, the zero destination address for unconnected sockets can be blindly used for FIB nexthop or route exceptions. So, we now restrict the sk to be for TCP_ESTABLISHED state where the sk is connected to destination address. - as we provide the original sk (from state->sk) for the original IP header, make sure we do not provide sk from one family to the update_pmtu handler for different family. The second problem is that on tunnel forwarding we call ip_local_out() with skb->sk. As we push our own outer IP header in ip_vs_tunnel_xmit*(), the right thing is to provide NULL instead. For consistency, even if we do not walk the netfilter hooks and just pass the packet with NF_ACCEPT, provide ip_vs_null_xmit_v6 handler to properly indicate that we are sending IPv6 packet. Before now we used ip_vs_null_xmit for both families. Link: https://sashiko.dev/#/patchset/20261103152550.60449-1-ja%40ssi.bg Fixes: 79b16aadea32 ("udp_tunnel: Pass UDP socket down through udp_tunnel{, 6}_xmit_skb().") Fixes: 7026b1ddb6b8 ("netfilter: Pass socket pointer down through okfn().") Signed-off-by: Julian Anastasov <ja@ssi.bg> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> --- include/net/ip_vs.h | 45 +++++++--- net/netfilter/ipvs/ip_vs_conn.c | 2 +- net/netfilter/ipvs/ip_vs_core.c | 41 +++++---- net/netfilter/ipvs/ip_vs_proto_ah_esp.c | 2 +- net/netfilter/ipvs/ip_vs_proto_sctp.c | 5 +- net/netfilter/ipvs/ip_vs_proto_tcp.c | 5 +- net/netfilter/ipvs/ip_vs_proto_udp.c | 5 +- net/netfilter/ipvs/ip_vs_xmit.c | 114 ++++++++++++++++-------- 8 files changed, 141 insertions(+), 78 deletions(-) diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index 8dc80d5d42dc..fdb3a9063e60 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -731,7 +731,8 @@ struct ip_vs_protocol { int af, struct sk_buff *skb, struct ip_vs_proto_data *pd, int *verdict, struct ip_vs_conn **cpp, - struct ip_vs_iphdr *iph); + struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); struct ip_vs_conn * (*conn_in_get)(struct netns_ipvs *ipvs, @@ -872,7 +873,8 @@ struct ip_vs_conn { * NF_ACCEPT can be returned when destination is local. */ int (*packet_xmit)(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); struct rcu_head rcu_head; }; @@ -1872,7 +1874,8 @@ ip_vs_schedule(struct ip_vs_service *svc, struct sk_buff *skb, struct ip_vs_proto_data *pd, int *ignored, struct ip_vs_iphdr *iph); int ip_vs_leave(struct ip_vs_service *svc, struct sk_buff *skb, - struct ip_vs_proto_data *pd, struct ip_vs_iphdr *iph); + struct ip_vs_proto_data *pd, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); void ip_vs_scheduler_err(struct ip_vs_service *svc, const char *msg); @@ -1987,32 +1990,46 @@ static inline int ip_vs_est_max_threads(struct netns_ipvs *ipvs) /* Various IPVS packet transmitters (from ip_vs_xmit.c) */ int ip_vs_null_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); int ip_vs_bypass_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); int ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); int ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); int ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, unsigned int toff, - unsigned int hooknum, struct ip_vs_iphdr *ciph); + const struct nf_hook_state *state, + struct ip_vs_iphdr *ciph); void ip_vs_dest_dst_rcu_free(struct rcu_head *head); #ifdef CONFIG_IP_VS_IPV6 +int ip_vs_null_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, + struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); int ip_vs_bypass_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); int ip_vs_nat_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); int ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); int ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph); + struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state); int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, unsigned int toff, - unsigned int hooknum, struct ip_vs_iphdr *ciph); + const struct nf_hook_state *state, + struct ip_vs_iphdr *ciph); #endif #ifdef CONFIG_SYSCTL diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index 83d8a9153a34..7cb8de634f5f 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -1057,7 +1057,7 @@ static inline void ip_vs_bind_xmit(struct ip_vs_conn *cp) break; case IP_VS_CONN_F_LOCALNODE: - cp->packet_xmit = ip_vs_null_xmit; + cp->packet_xmit = ip_vs_null_xmit_v6; break; case IP_VS_CONN_F_BYPASS: diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c index 15617bd19e96..85ea82367cfe 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -760,7 +760,8 @@ static inline int ip_vs_addr_is_unicast(struct net *net, int af, * no destination is available for a new connection. */ int ip_vs_leave(struct ip_vs_service *svc, struct sk_buff *skb, - struct ip_vs_proto_data *pd, struct ip_vs_iphdr *iph) + struct ip_vs_proto_data *pd, struct ip_vs_iphdr *iph, + const struct nf_hook_state *state) { __be16 _ports[2], *pptr, dport; struct netns_ipvs *ipvs = svc->ipvs; @@ -805,7 +806,7 @@ int ip_vs_leave(struct ip_vs_service *svc, struct sk_buff *skb, ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd, iph->len); /* transmit the first SYN packet */ - ret = cp->packet_xmit(skb, cp, pd->pp, iph); + ret = cp->packet_xmit(skb, cp, pd->pp, iph, state); /* do not touch skb anymore */ if ((cp->flags & IP_VS_CONN_F_ONE_PACKET) && cp->control) @@ -1655,7 +1656,8 @@ static unsigned int ip_vs_try_to_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, struct ip_vs_proto_data *pd, int *verdict, struct ip_vs_conn **cpp, - struct ip_vs_iphdr *iph) + struct ip_vs_iphdr *iph, + const struct nf_hook_state *state) { struct ip_vs_protocol *pp = pd->pp; @@ -1665,7 +1667,8 @@ ip_vs_try_to_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, */ /* Schedule and create new connection entry into cpp */ - if (!pp->conn_schedule(ipvs, af, skb, pd, verdict, cpp, iph)) + if (!pp->conn_schedule(ipvs, af, skb, pd, verdict, cpp, iph, + state)) return 0; } @@ -1765,7 +1768,7 @@ static int ipvs_gre_decap(struct netns_ipvs *ipvs, struct sk_buff *skb, */ static int ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, - unsigned int hooknum, struct ip_vs_iphdr *iph) + const struct nf_hook_state *state, struct ip_vs_iphdr *iph) { struct icmphdr _icmph, *ic; struct iphdr _ciph, *cih; /* The ip header contained within the ICMP */ @@ -1784,7 +1787,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* reassemble IP fragments */ if (ip_is_fragment(ip_hdr(skb))) { - if (ip_vs_gather_frags(ipvs, skb, ip_vs_defrag_user(hooknum))) + if (ip_vs_gather_frags(ipvs, skb, + ip_vs_defrag_user(state->hook))) return NF_STOLEN; if (!ip_vs_fill_iph_skb(AF_INET, skb, false, iph)) return NF_ACCEPT; @@ -1901,7 +1905,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, if (tunnel || !sysctl_schedule_icmp(ipvs)) return NF_ACCEPT; - if (!ip_vs_try_to_schedule(ipvs, AF_INET, skb, pd, &v, &cp, &ciph)) + if (!ip_vs_try_to_schedule(ipvs, AF_INET, skb, pd, &v, &cp, + &ciph, state)) return v; new_cp = true; } @@ -1989,7 +1994,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, /* do the statistics and put it back */ ip_vs_in_stats(cp, skb); - verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, hooknum, &ciph); + verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, state, &ciph); out: if (likely(!new_cp)) @@ -2002,7 +2007,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, #ifdef CONFIG_IP_VS_IPV6 static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, - int *related, unsigned int hooknum, + int *related, const struct nf_hook_state *state, struct ip_vs_iphdr *iph) { struct icmp6hdr _icmph, *ic; @@ -2075,7 +2080,8 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, if (!sysctl_schedule_icmp(ipvs)) return NF_ACCEPT; - if (!ip_vs_try_to_schedule(ipvs, AF_INET6, skb, pd, &v, &cp, &ciph)) + if (!ip_vs_try_to_schedule(ipvs, AF_INET6, skb, pd, &v, &cp, + &ciph, state)) return v; new_cp = true; @@ -2096,7 +2102,7 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb, /* do the statistics and put it back */ ip_vs_in_stats(cp, skb); - verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, hooknum, &ciph); + verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, state, &ciph); out: if (likely(!new_cp)) @@ -2165,7 +2171,7 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state if (unlikely(iph.protocol == IPPROTO_ICMPV6)) { int related; int verdict = ip_vs_in_icmp_v6(ipvs, skb, &related, - hooknum, &iph); + state, &iph); if (related) return verdict; @@ -2175,7 +2181,7 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state if (unlikely(iph.protocol == IPPROTO_ICMP)) { int related; int verdict = ip_vs_in_icmp(ipvs, skb, &related, - hooknum, &iph); + state, &iph); if (related) return verdict; @@ -2263,7 +2269,8 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state if (unlikely(!cp)) { int v; - if (!ip_vs_try_to_schedule(ipvs, af, skb, pd, &v, &cp, &iph)) + if (!ip_vs_try_to_schedule(ipvs, af, skb, pd, &v, &cp, &iph, + state)) return v; } @@ -2272,7 +2279,7 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state ip_vs_in_stats(cp, skb); ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd, iph.len); if (cp->packet_xmit) - ret = cp->packet_xmit(skb, cp, pp, &iph); + ret = cp->packet_xmit(skb, cp, pp, &iph, state); /* do not touch skb anymore */ else { IP_VS_DBG_RL("warning: packet_xmit is null"); @@ -2335,11 +2342,11 @@ ip_vs_forward_icmp(void *priv, struct sk_buff *skb, if (iphdr.protocol != IPPROTO_ICMPV6) return NF_ACCEPT; - return ip_vs_in_icmp_v6(ipvs, skb, &r, state->hook, &iphdr); + return ip_vs_in_icmp_v6(ipvs, skb, &r, state, &iphdr); #endif } - return ip_vs_in_icmp(ipvs, skb, &r, state->hook, &iphdr); + return ip_vs_in_icmp(ipvs, skb, &r, state, &iphdr); } static const struct nf_hook_ops ip_vs_ops4[] = { diff --git a/net/netfilter/ipvs/ip_vs_proto_ah_esp.c b/net/netfilter/ipvs/ip_vs_proto_ah_esp.c index 44e14acc187e..c7f90b29be51 100644 --- a/net/netfilter/ipvs/ip_vs_proto_ah_esp.c +++ b/net/netfilter/ipvs/ip_vs_proto_ah_esp.c @@ -102,7 +102,7 @@ static int ah_esp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, struct ip_vs_proto_data *pd, int *verdict, struct ip_vs_conn **cpp, - struct ip_vs_iphdr *iph) + struct ip_vs_iphdr *iph, const struct nf_hook_state *state) { /* * AH/ESP is only related traffic. Pass the packet to IP stack. diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c index cb34bcceb4f6..3d773c881f75 100644 --- a/net/netfilter/ipvs/ip_vs_proto_sctp.c +++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c @@ -17,7 +17,7 @@ static int sctp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, struct ip_vs_proto_data *pd, int *verdict, struct ip_vs_conn **cpp, - struct ip_vs_iphdr *iph) + struct ip_vs_iphdr *iph, const struct nf_hook_state *state) { struct ip_vs_service *svc; struct sctp_chunkhdr _schunkh, *sch; @@ -78,7 +78,8 @@ sctp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, *cpp = ip_vs_schedule(svc, skb, pd, &ignored, iph); if (!*cpp && ignored <= 0) { if (!ignored) - *verdict = ip_vs_leave(svc, skb, pd, iph); + *verdict = ip_vs_leave(svc, skb, pd, iph, + state); else *verdict = NF_DROP; return 0; diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c index 25d4576d0e12..13ac917a0ac8 100644 --- a/net/netfilter/ipvs/ip_vs_proto_tcp.c +++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c @@ -35,7 +35,7 @@ static int tcp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, struct ip_vs_proto_data *pd, int *verdict, struct ip_vs_conn **cpp, - struct ip_vs_iphdr *iph) + struct ip_vs_iphdr *iph, const struct nf_hook_state *state) { struct ip_vs_service *svc; struct tcphdr _tcph, *th; @@ -97,7 +97,8 @@ tcp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, *cpp = ip_vs_schedule(svc, skb, pd, &ignored, iph); if (!*cpp && ignored <= 0) { if (!ignored) - *verdict = ip_vs_leave(svc, skb, pd, iph); + *verdict = ip_vs_leave(svc, skb, pd, iph, + state); else *verdict = NF_DROP; return 0; diff --git a/net/netfilter/ipvs/ip_vs_proto_udp.c b/net/netfilter/ipvs/ip_vs_proto_udp.c index ab3ab9524168..760b019e6a06 100644 --- a/net/netfilter/ipvs/ip_vs_proto_udp.c +++ b/net/netfilter/ipvs/ip_vs_proto_udp.c @@ -31,7 +31,7 @@ static int udp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, struct ip_vs_proto_data *pd, int *verdict, struct ip_vs_conn **cpp, - struct ip_vs_iphdr *iph) + struct ip_vs_iphdr *iph, const struct nf_hook_state *state) { struct ip_vs_service *svc; struct udphdr _udph, *uh; @@ -84,7 +84,8 @@ udp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb, *cpp = ip_vs_schedule(svc, skb, pd, &ignored, iph); if (!*cpp && ignored <= 0) { if (!ignored) - *verdict = ip_vs_leave(svc, skb, pd, iph); + *verdict = ip_vs_leave(svc, skb, pd, iph, + state); else *verdict = NF_DROP; return 0; diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c index ebc6b697ae51..c777493eee74 100644 --- a/net/netfilter/ipvs/ip_vs_xmit.c +++ b/net/netfilter/ipvs/ip_vs_xmit.c @@ -205,13 +205,24 @@ static inline bool crosses_local_route_boundary(int skb_af, struct sk_buff *skb, return false; } -static inline void maybe_update_pmtu(int skb_af, struct sk_buff *skb, int mtu) +static inline void maybe_update_pmtu(int skb_af, struct sk_buff *skb, int mtu, + const struct nf_hook_state *state) { - struct sock *sk = skb->sk; - struct rtable *ort = skb_rtable(skb); - - if (!skb->dev && sk && sk_fullsock(sk)) - ort->dst.ops->update_pmtu(&ort->dst, sk, NULL, mtu, true); + struct dst_entry *dst = skb_dst(skb); + struct sock *sk = state->sk; + + /* The update_pmtu handlers are missing some checks for: + * - skb->dev: present for tunnels, not for IPVS, so we do not + * provide skb, only sk + * - present daddr: so, we require TCP_ESTABLISHED (connected socket) + * + * In some states the senders ignore the PMTU events: + * - sk_fullsock(): so use the TCP_ESTABLISHED check + */ + if (sk && sk->sk_state == TCP_ESTABLISHED && + state->hook == NF_INET_LOCAL_OUT && + sk->sk_family == dst->ops->family) + dst->ops->update_pmtu(dst, sk, NULL, mtu, false); } static inline bool ensure_mtu_is_adequate(struct netns_ipvs *ipvs, int skb_af, @@ -316,7 +327,7 @@ static int __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb, struct ip_vs_dest *dest, __be32 daddr, int rt_mode, __be32 *ret_saddr, - struct ip_vs_iphdr *ipvsh) + struct ip_vs_iphdr *ipvsh, const struct nf_hook_state *state) { struct net *net = ipvs->net; struct ip_vs_dest_dst *dest_dst; @@ -417,7 +428,7 @@ __ip_vs_get_out_rt(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb, IP_VS_DBG_RL("%s(): mtu less than 68\n", __func__); goto err_put; } - maybe_update_pmtu(skb_af, skb, mtu); + maybe_update_pmtu(skb_af, skb, mtu, state); } if (!ensure_mtu_is_adequate(ipvs, skb_af, rt_mode, ipvsh, skb, mtu)) @@ -489,7 +500,8 @@ static int __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb, struct ip_vs_dest *dest, struct in6_addr *daddr, struct in6_addr *ret_saddr, - struct ip_vs_iphdr *ipvsh, int do_xfrm, int rt_mode) + struct ip_vs_iphdr *ipvsh, int do_xfrm, int rt_mode, + const struct nf_hook_state *state) { struct net *net = ipvs->net; struct ip_vs_dest_dst *dest_dst; @@ -596,7 +608,7 @@ __ip_vs_get_out_rt_v6(struct netns_ipvs *ipvs, int skb_af, struct sk_buff *skb, IPV6_MIN_MTU); goto err_put; } - maybe_update_pmtu(skb_af, skb, mtu); + maybe_update_pmtu(skb_af, skb, mtu, state); } if (!ensure_mtu_is_adequate(ipvs, skb_af, rt_mode, ipvsh, skb, mtu)) @@ -720,12 +732,26 @@ static inline int ip_vs_send_or_cont(int pf, struct sk_buff *skb, */ int ip_vs_null_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh) + struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh, + const struct nf_hook_state *state) { /* we do not touch skb and do not need pskb ptr */ return ip_vs_send_or_cont(NFPROTO_IPV4, skb, cp, 1); } +#ifdef CONFIG_IP_VS_IPV6 +/* + * NULL transmitter (do nothing except return NF_ACCEPT) + */ +int +ip_vs_null_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, + struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh, + const struct nf_hook_state *state) +{ + /* we do not touch skb and do not need pskb ptr */ + return ip_vs_send_or_cont(NFPROTO_IPV6, skb, cp, 1); +} +#endif /* * Bypass transmitter @@ -734,10 +760,11 @@ ip_vs_null_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, */ int ip_vs_bypass_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh) + struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh, + const struct nf_hook_state *state) { if (__ip_vs_get_out_rt(cp->ipvs, cp->af, skb, NULL, ip_hdr(skb)->daddr, - IP_VS_RT_MODE_NON_LOCAL, NULL, ipvsh) < 0) + IP_VS_RT_MODE_NON_LOCAL, NULL, ipvsh, state) < 0) goto tx_error; ip_send_check(ip_hdr(skb)); @@ -757,13 +784,15 @@ ip_vs_bypass_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, #ifdef CONFIG_IP_VS_IPV6 int ip_vs_bypass_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh) + struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh, + const struct nf_hook_state *state) { struct ipv6hdr *iph = ipv6_hdr(skb); if (__ip_vs_get_out_rt_v6(cp->ipvs, cp->af, skb, NULL, &iph->daddr, NULL, - ipvsh, 0, IP_VS_RT_MODE_NON_LOCAL) < 0) + ipvsh, 0, IP_VS_RT_MODE_NON_LOCAL, + state) < 0) goto tx_error; /* Another hack: avoid icmp_send in ip_fragment */ @@ -785,7 +814,8 @@ ip_vs_bypass_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, */ int ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh) + struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh, + const struct nf_hook_state *state) { struct rtable *rt; /* Route to the other host */ int local, rc, was_input; @@ -805,7 +835,7 @@ ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, local = __ip_vs_get_out_rt(cp->ipvs, cp->af, skb, cp->dest, cp->daddr.ip, IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | - IP_VS_RT_MODE_RDR, NULL, ipvsh); + IP_VS_RT_MODE_RDR, NULL, ipvsh, state); if (local < 0) goto tx_error; rt = skb_rtable(skb); @@ -869,7 +899,8 @@ ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, #ifdef CONFIG_IP_VS_IPV6 int ip_vs_nat_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh) + struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh, + const struct nf_hook_state *state) { struct rt6_info *rt; /* Route to the other host */ int local, rc; @@ -889,7 +920,7 @@ ip_vs_nat_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, NULL, ipvsh, 0, IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | - IP_VS_RT_MODE_RDR); + IP_VS_RT_MODE_RDR, state); if (local < 0) goto tx_error; rt = dst_rt6_info(skb_dst(skb)); @@ -1145,7 +1176,8 @@ ipvs_gre_encap(struct net *net, struct sk_buff *skb, */ int ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh) + struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh, + const struct nf_hook_state *state) { struct netns_ipvs *ipvs = cp->ipvs; struct net *net = ipvs->net; @@ -1167,7 +1199,7 @@ ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | IP_VS_RT_MODE_CONNECT | - IP_VS_RT_MODE_TUNNEL, &saddr, ipvsh); + IP_VS_RT_MODE_TUNNEL, &saddr, ipvsh, state); if (local < 0) goto tx_error; if (local) @@ -1276,7 +1308,7 @@ ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, ret = ip_vs_tunnel_xmit_prepare(skb, cp); if (ret == NF_ACCEPT) - ip_local_out(net, skb->sk, skb); + ip_local_out(net, NULL, skb); else if (ret == NF_DROP) kfree_skb(skb); @@ -1290,7 +1322,8 @@ ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, #ifdef CONFIG_IP_VS_IPV6 int ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh) + struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh, + const struct nf_hook_state *state) { struct netns_ipvs *ipvs = cp->ipvs; struct net *net = ipvs->net; @@ -1312,7 +1345,7 @@ ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, &saddr, ipvsh, 1, IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | - IP_VS_RT_MODE_TUNNEL); + IP_VS_RT_MODE_TUNNEL, state); if (local < 0) goto tx_error; if (local) @@ -1418,7 +1451,7 @@ ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, ret = ip_vs_tunnel_xmit_prepare(skb, cp); if (ret == NF_ACCEPT) - ip6_local_out(net, skb->sk, skb); + ip6_local_out(net, NULL, skb); else if (ret == NF_DROP) kfree_skb(skb); @@ -1437,14 +1470,15 @@ ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, */ int ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh) + struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh, + const struct nf_hook_state *state) { int local; local = __ip_vs_get_out_rt(cp->ipvs, cp->af, skb, cp->dest, cp->daddr.ip, IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | - IP_VS_RT_MODE_KNOWN_NH, NULL, ipvsh); + IP_VS_RT_MODE_KNOWN_NH, NULL, ipvsh, state); if (local < 0) goto tx_error; if (local) @@ -1467,7 +1501,8 @@ ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, #ifdef CONFIG_IP_VS_IPV6 int ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh) + struct ip_vs_protocol *pp, struct ip_vs_iphdr *ipvsh, + const struct nf_hook_state *state) { int local; @@ -1476,7 +1511,7 @@ ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, NULL, ipvsh, 0, IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | - IP_VS_RT_MODE_KNOWN_NH); + IP_VS_RT_MODE_KNOWN_NH, state); if (local < 0) goto tx_error; if (local) @@ -1503,7 +1538,7 @@ ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, struct ip_vs_protocol *pp, unsigned int toff, - unsigned int hooknum, struct ip_vs_iphdr *ciph) + const struct nf_hook_state *state, struct ip_vs_iphdr *ciph) { struct rtable *rt; /* Route to the other host */ int rc; @@ -1517,7 +1552,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, translate address/port back */ if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) { if (cp->packet_xmit) - rc = cp->packet_xmit(skb, cp, pp, ciph); + rc = cp->packet_xmit(skb, cp, pp, ciph, state); else rc = NF_ACCEPT; /* do not touch skb anymore */ @@ -1531,11 +1566,11 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, was_input = rt_is_input_route(skb_rtable(skb)); /* LOCALNODE from FORWARD hook is not supported */ - rt_mode = (hooknum != NF_INET_FORWARD) ? + rt_mode = (state->hook != NF_INET_FORWARD) ? IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | IP_VS_RT_MODE_RDR : IP_VS_RT_MODE_NON_LOCAL; - local = __ip_vs_get_out_rt(cp->ipvs, cp->af, skb, cp->dest, cp->daddr.ip, rt_mode, - NULL, ciph); + local = __ip_vs_get_out_rt(cp->ipvs, cp->af, skb, cp->dest, + cp->daddr.ip, rt_mode, NULL, ciph, state); if (local < 0) goto tx_error; rt = skb_rtable(skb); @@ -1597,8 +1632,8 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp, #ifdef CONFIG_IP_VS_IPV6 int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, - struct ip_vs_protocol *pp, unsigned int toff, - unsigned int hooknum, struct ip_vs_iphdr *ciph) + struct ip_vs_protocol *pp, unsigned int toff, + const struct nf_hook_state *state, struct ip_vs_iphdr *ciph) { bool has_ports = false; struct rt6_info *rt; /* Route to the other host */ @@ -1612,7 +1647,7 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, translate address/port back */ if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) { if (cp->packet_xmit) - rc = cp->packet_xmit(skb, cp, pp, ciph); + rc = cp->packet_xmit(skb, cp, pp, ciph, state); else rc = NF_ACCEPT; /* do not touch skb anymore */ @@ -1625,11 +1660,12 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp, */ /* LOCALNODE from FORWARD hook is not supported */ - rt_mode = (hooknum != NF_INET_FORWARD) ? + rt_mode = (state->hook != NF_INET_FORWARD) ? IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL | IP_VS_RT_MODE_RDR : IP_VS_RT_MODE_NON_LOCAL; local = __ip_vs_get_out_rt_v6(cp->ipvs, cp->af, skb, cp->dest, - &cp->daddr.in6, NULL, ciph, 0, rt_mode); + &cp->daddr.in6, NULL, ciph, 0, rt_mode, + state); if (local < 0) goto tx_error; rt = dst_rt6_info(skb_dst(skb)); -- 2.47.3 ^ permalink raw reply related [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-11-13 3:17 UTC | newest] Thread overview: 18+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-11-13 1:14 [PATCH net 00/15] IPVS fixes for net Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 01/15] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp_v6 Pablo Neira Ayuso 2026-11-13 1:16 ` netdev-bot+sinfo 2026-11-13 3:17 ` Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 02/15] ipv6: update *offset for non-first fragments in ipv6_find_hdr() Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 03/15] ipvs: fix problems during connection deletion Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 04/15] ipvs: avoid stack overflow from recursive connection expiration Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 05/15] ipvs: reject FTP control ports as data ports Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 06/15] ipvs: ftp: skip conntrack templates in ip_vs_ftp_out Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 07/15] ipvs: ftp: add seqadj extension before linking the expectation Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 08/15] ipvs: update flags and seqs in backup server under lock Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 09/15] ipvs: read flags and seqs in master atomically Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 10/15] ipvs: honor the overload state and do not enter it forever Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 11/15] ipvs: validate cport in received sync records Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 12/15] ipvs: skip cport 0 connections in ip_vs_conn_out_get() Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 13/15] ipvs: do not create conns from packets with port 0 Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 14/15] ipvs: wait grace period before reusing destinations Pablo Neira Ayuso 2026-11-13 1:14 ` [PATCH net 15/15] ipvs: pass correct sk to update_pmtu and on tunneling Pablo Neira Ayuso
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox