* [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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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; 17+ 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] 17+ 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
0 siblings, 0 replies; 17+ 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] 17+ messages in thread
end of thread, other threads:[~2026-11-13 1:17 UTC | newest]
Thread overview: 17+ 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 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