* [PATCH iproute2-next v4] rdma: display resource limits in curr/max format
From: Tao Cui @ 2026-07-08 13:40 UTC (permalink / raw)
To: dsahern, leonro; +Cc: linux-rdma, netdev, cui.tao, cuitao
From: Tao Cui <cuitao@kylinos.cn>
Parse the new RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX netlink attribute
to show resource limits alongside current counts in curr/max format:
Before: 0: mlx5_0: qp 123 cq 45 mr 200 pd 10
After: 0: mlx5_0: qp 123/131072 cq 45/65536 mr 200/1000000 pd 10/32768
JSON output provides both current and max fields per resource type
(e.g. "qp": 123, "qp-max": 131072). Backward compatible: no output
change when kernel lacks the new attribute.
Signed-off-by: Tao Cui <cuitao@kylinos.cn>
Link: https://lore.kernel.org/all/20260615003646.168704-1-cui.tao@linux.dev/
---
Changes in v4:
- Add Link to the kernel patch that introduces the new uapi attribute.
---
rdma/include/uapi/rdma/rdma_netlink.h | 5 +++++
rdma/res.c | 21 ++++++++++++++++++++-
rdma/utils.c | 1 +
3 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/rdma/include/uapi/rdma/rdma_netlink.h b/rdma/include/uapi/rdma/rdma_netlink.h
index 4356ec4a..e5b8b065 100644
--- a/rdma/include/uapi/rdma/rdma_netlink.h
+++ b/rdma/include/uapi/rdma/rdma_netlink.h
@@ -604,6 +604,11 @@ enum rdma_nldev_attr {
RDMA_NLDEV_ATTR_FRMR_POOL_PINNED_HANDLES, /* u32 */
RDMA_NLDEV_ATTR_FRMR_POOL_KEY_KERNEL_VENDOR_KEY, /* u64 */
+ /*
+ * Resource summary entry maximum value.
+ */
+ RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX, /* u64 */
+
/*
* Always the end
*/
diff --git a/rdma/res.c b/rdma/res.c
index 062f0007..046935e2 100644
--- a/rdma/res.c
+++ b/rdma/res.c
@@ -55,7 +55,26 @@ static int res_print_summary(struct nlattr **tb)
name = mnl_attr_get_str(nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_NAME]);
curr = mnl_attr_get_u64(nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_CURR]);
- res_print_u64(name, curr, nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_CURR]);
+ if (nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX]) {
+ uint64_t max;
+ char max_name[64];
+
+ max = mnl_attr_get_u64(
+ nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX]);
+ snprintf(max_name, sizeof(max_name), "%s-max", name);
+ print_u64(PRINT_JSON, name, NULL, curr);
+ print_u64(PRINT_JSON, max_name, NULL, max);
+ if (!is_json_context()) {
+ char buf[64];
+
+ snprintf(buf, sizeof(buf), "%s %" PRIu64 "/%" PRIu64 " ",
+ name, curr, max);
+ pr_out("%s", buf);
+ }
+ } else {
+ res_print_u64(name, curr,
+ nla_line[RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_CURR]);
+ }
}
return 0;
}
diff --git a/rdma/utils.c b/rdma/utils.c
index 87003b2c..90ea1c55 100644
--- a/rdma/utils.c
+++ b/rdma/utils.c
@@ -480,6 +480,7 @@ static const enum mnl_attr_data_type nldev_policy[RDMA_NLDEV_ATTR_MAX] = {
[RDMA_NLDEV_ATTR_EVENT_TYPE] = MNL_TYPE_U8,
[RDMA_NLDEV_SYS_ATTR_MONITOR_MODE] = MNL_TYPE_U8,
[RDMA_NLDEV_ATTR_STAT_OPCOUNTER_ENABLED] = MNL_TYPE_U8,
+ [RDMA_NLDEV_ATTR_RES_SUMMARY_ENTRY_MAX] = MNL_TYPE_U64,
};
static int rd_attr_check(const struct nlattr *attr, int *typep)
--
2.43.0
^ permalink raw reply related
* [PATCH bpf v2 2/2] selftests/bpf: Test FIONREAD on a sockmap socket without a verdict program
From: Mattia Meleleo via B4 Relay @ 2026-07-08 13:40 UTC (permalink / raw)
To: bpf
Cc: netdev, John Fastabend, Jakub Sitnicki, Jiayuan Chen,
Kumar Kartikeya Dwivedi, Emil Tsalapatis, Mattia Meleleo
In-Reply-To: <20260708-fionread-no-verdict-v2-0-29dd293621c7@coralogix.com>
From: Mattia Meleleo <mattia.meleleo@coralogix.com>
Add a test validating that FIONREAD on a TCP socket in a sockmap
without a verdict program reports data pending in sk_receive_queue.
Reviewed-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Mattia Meleleo <mattia.meleleo@coralogix.com>
---
.../selftests/bpf/prog_tests/sockmap_basic.c | 39 ++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
index cb3229711..f0f368201 100644
--- a/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
+++ b/tools/testing/selftests/bpf/prog_tests/sockmap_basic.c
@@ -1373,6 +1373,43 @@ static void test_sockmap_multi_channels(int sotype)
test_sockmap_pass_prog__destroy(skel);
}
+/* A socket in a sockmap without a verdict program keeps its ingress data
+ * in sk_receive_queue: FIONREAD must account for it.
+ */
+static void test_sockmap_no_verdict_fionread(void)
+{
+ int err, map, zero = 0, sent, avail;
+ int c0 = -1, c1 = -1, p0 = -1, p1 = -1;
+ struct test_sockmap_pass_prog *skel;
+ char buf[256] = "0123456789";
+
+ skel = test_sockmap_pass_prog__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+ map = bpf_map__fd(skel->maps.sock_map_rx);
+
+ err = create_socket_pairs(AF_INET, SOCK_STREAM, &c0, &c1, &p0, &p1);
+ if (!ASSERT_OK(err, "create_socket_pairs()"))
+ goto out;
+
+ err = bpf_map_update_elem(map, &zero, &c1, BPF_NOEXIST);
+ if (!ASSERT_OK(err, "bpf_map_update_elem(c1)"))
+ goto out_close;
+
+ sent = xsend(p1, &buf, sizeof(buf), 0);
+ ASSERT_EQ(sent, sizeof(buf), "xsend(p1)");
+ avail = wait_for_fionread(c1, sizeof(buf), IO_TIMEOUT_SEC);
+ ASSERT_EQ(avail, sizeof(buf), "ioctl(FIONREAD)");
+
+out_close:
+ close(c0);
+ close(p0);
+ close(c1);
+ close(p1);
+out:
+ test_sockmap_pass_prog__destroy(skel);
+}
+
void test_sockmap_basic(void)
{
if (test__start_subtest("sockmap create_update_free"))
@@ -1415,6 +1452,8 @@ void test_sockmap_basic(void)
test_sockmap_skb_verdict_shutdown();
if (test__start_subtest("sockmap skb_verdict fionread"))
test_sockmap_skb_verdict_fionread(true);
+ if (test__start_subtest("sockmap no_verdict fionread"))
+ test_sockmap_no_verdict_fionread();
if (test__start_subtest("sockmap skb_verdict fionread on drop"))
test_sockmap_skb_verdict_fionread(false);
if (test__start_subtest("sockmap skb_verdict change tail"))
--
Git-155)
^ permalink raw reply related
* [PATCH bpf v2 0/2] bpf, sockmap: Fix FIONREAD for sockets without a verdict program
From: Mattia Meleleo via B4 Relay @ 2026-07-08 13:40 UTC (permalink / raw)
To: bpf
Cc: netdev, John Fastabend, Jakub Sitnicki, Jiayuan Chen,
Kumar Kartikeya Dwivedi, Emil Tsalapatis, Mattia Meleleo
Sockets added to a sockmap/sockhash with no stream/skb verdict program
attached answer FIONREAD with 0 even when unread data is pending in
sk_receive_queue. Fix tcp_bpf_ioctl() to account for the receive queue
in that case, and add a selftest.
Changes in v2:
- Split the fix and the selftest into separate patches
- Use READ_ONCE() to read the verdict program pointers
- Link to v1: https://patch.msgid.link/20260707-fionread-no-verdict-v1-1-ce94a72357ec@coralogix.com
Signed-off-by: Mattia Meleleo <mattia.meleleo@coralogix.com>
---
Mattia Meleleo (2):
bpf, sockmap: Account for receive queue in FIONREAD without a verdict program
selftests/bpf: Test FIONREAD on a sockmap socket without a verdict program
net/ipv4/tcp_bpf.c | 17 +++++++++-
.../selftests/bpf/prog_tests/sockmap_basic.c | 39 ++++++++++++++++++++++
2 files changed, 55 insertions(+), 1 deletion(-)
---
base-commit: d2c9a99135da931377240942d44f3dea104cedb8
change-id: 20260707-fionread-no-verdict-a4f8697ac9f9
Best regards,
--
Mattia Meleleo <mattia.meleleo@coralogix.com>
^ permalink raw reply
* [PATCH bpf v2 1/2] bpf, sockmap: Account for receive queue in FIONREAD without a verdict program
From: Mattia Meleleo via B4 Relay @ 2026-07-08 13:40 UTC (permalink / raw)
To: bpf
Cc: netdev, John Fastabend, Jakub Sitnicki, Jiayuan Chen,
Kumar Kartikeya Dwivedi, Emil Tsalapatis, Mattia Meleleo
In-Reply-To: <20260708-fionread-no-verdict-v2-0-29dd293621c7@coralogix.com>
From: Mattia Meleleo <mattia.meleleo@coralogix.com>
tcp_bpf_ioctl() answers SIOCINQ from psock->msg_tot_len, which only
counts bytes in ingress_msg. Without a stream/skb verdict program
nothing is diverted there: data stays in sk_receive_queue, so FIONREAD
returns 0 even though read() returns data.
Add tcp_inq() to the reported value when the psock has no verdict
program. The two queues are disjoint, so bytes redirected into
ingress_msg from other sockets stay correctly accounted through
msg_tot_len.
Fixes: 929e30f93125 ("bpf, sockmap: Fix FIONREAD for sockmap")
Reviewed-by: Jiayuan Chen <jiayuan.chen@linux.dev>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Signed-off-by: Mattia Meleleo <mattia.meleleo@coralogix.com>
---
net/ipv4/tcp_bpf.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
index cc0bd73f3..8e905b50d 100644
--- a/net/ipv4/tcp_bpf.c
+++ b/net/ipv4/tcp_bpf.c
@@ -334,6 +334,7 @@ static int tcp_bpf_recvmsg_parser(struct sock *sk,
static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
{
+ struct sk_psock *psock;
bool slow;
if (cmd != SIOCINQ)
@@ -344,7 +345,21 @@ static int tcp_bpf_ioctl(struct sock *sk, int cmd, int *karg)
return -EINVAL;
slow = lock_sock_fast(sk);
- *karg = sk_psock_msg_inq(sk);
+ psock = sk_psock_get(sk);
+ if (unlikely(!psock)) {
+ unlock_sock_fast(sk, slow);
+ return tcp_ioctl(sk, cmd, karg);
+ }
+ *karg = sk_psock_get_msg_len_nolock(psock);
+ /* Without a verdict program, ingress data is never diverted to
+ * ingress_msg: it stays in sk_receive_queue and is read through
+ * the fallback to tcp_recvmsg(), so account for it like
+ * tcp_ioctl() does.
+ */
+ if (!READ_ONCE(psock->progs.stream_verdict) &&
+ !READ_ONCE(psock->progs.skb_verdict))
+ *karg += tcp_inq(sk);
+ sk_psock_put(sk, psock);
unlock_sock_fast(sk, slow);
return 0;
--
Git-155)
^ permalink raw reply related
* Re: [PATCH 02/15] dt-bindings: clock: mediatek: regroup MT8188 dt-bindings into MT8186
From: Louis-Alexis Eyraud @ 2026-07-08 13:44 UTC (permalink / raw)
To: Rob Herring
Cc: Michael Turquette, Stephen Boyd, Brian Masney,
Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Chun-Jie Chen, Philipp Zabel,
Edward-JW Yang, Richard Cochran, kernel, linux-clk, devicetree,
linux-kernel, linux-arm-kernel, linux-mediatek, netdev
In-Reply-To: <20260701193311.GA1402559-robh@kernel.org>
Hello Rob,
On Wed, 2026-07-01 at 14:33 -0500, Rob Herring wrote:
> On Wed, Jul 01, 2026 at 03:11:07PM +0200, Louis-Alexis Eyraud wrote:
> > Regroup the MT8188 clock and system clock dt-bindings into MT8186
> > ones
> > to ease maintainability and have common files for several currently
> > supported SoC or new future ones, that have the same kind of clock
> > controller design.
> >
> > Note:
> > The `#clock-cells` property is a required property for all
> > compatibles
> > declared in MT8188 clock and system clock dt-bindings but not in
> > MT8186
> > ones.
> > To avoid ABI breakage, conditional blocks to check this requirement
> > for MT8188 compatibles are added, rather than enforcing it for
> > MT8186
> > compatibles.
>
> If the existing DTs are just wrong, then I would just make #clock-
> cells
> required. But please update the .dts files so the warnings don't
> grow.
>
I've tested to make the #clock-cells required for the MT8186, MT8192
and MT8195 system and functional clock controllers.
I did not see new warnings, so no extra dts patches would be needed.
I'll add new patches (one per SoC) in the next revision of the series
for this, as it simplifies the grouping patches (no more if/then to
require #clock-cells for the MT8188/MT8189 clock controllers) and the
note in commit message could be removed.
> The grouping I would do here is:
>
> - clock controller only
> - reset controller only
> - both clock and reset controller
>
> That should avoid any if/then schemas.
>
By this grouping, I understand you suggest having separate dt-bindings
files, that could look like:
- mediatek,mt8186-clock.yaml: clock controllers
- <name to be found>: reset controllers
- <name to be found>: clock controllers with reset controller
- mediatek,mt8186-sys-clock.yaml: system clock controllers.
- <name to be found>: system clock controllers with reset controller
Is that what you meant?
There is no pure reset controllers for those SoC so no dedicated file
would needed at the moment.
The system clock controllers all have reset-controllers, even they may
currently be not all implemented, so no separate files for system clock
controllers would needed as well.
Also, from what I see the current dt-bindings, the system clocks
controllers for the MT8186/MT8188/MT8192/MT8195 SoC have the #reset-
cells property but it is not required for them (examples:
mediatek,mt8188-infracfg-ao or mediatek,mt8195-infracfg_ao).
With the patches to make the #clock-cells property required, I already
removed the biggest if/else block in mediatek,mt8186-clock.yaml, so
only the one regarding #reset-cells property remains.
So, should I create separate files, following the grouping suggestion,
for the v2 of this patch?
Regards,
Louis-Alexis
> Rob
^ permalink raw reply
* Re: [PATCH net] net: stmmac: intel: don't reconfigure SerDes on unchanged mode
From: Andrew Lunn @ 2026-07-08 13:55 UTC (permalink / raw)
To: Markus Breitenberger
Cc: andrew+netdev, bre, davem, edumazet, kuba, netdev, pabeni, stable,
yong.liang.choong
In-Reply-To: <20260707220814.109028-1-bre@breiti.cc>
On Wed, Jul 08, 2026 at 12:08:14AM +0200, Markus Breitenberger wrote:
> Hi Andrew,
>
> Thanks for looking at this, and you're right - the runtime case is the
> more dangerous one. If a genuine interface change (SGMII <-> 2500BASE-X)
> happened at runtime while the disk was live, reprogramming the shared
> ModPHY LCPLL would disturb the SATA PHY under an active filesystem, and
> a failed boot would be preferable to that.
>
> Two points of clarification:
>
> - A plain switch change does not reprogram the ModPHY on my fixed-PHY
> setup. mac_finish() only runs a real reconfiguration when the
> MAC-side interface mode changes (e.g. a multi-rate SFP moving between
> SGMII and 2500BASE-X).
A fibre SFP is unlikely to use SGMII. It will swap between 2500BaseX
and 1000BaseX, if the SFP module is ejected and a different one
plugged in.
> On a fixed copper PHY the interface mode does
> not change, so changing the link partner / switch does not trigger
> the reconfiguration.
That depends on the PHY. Some change there host side interface to
match the line side. So they use 25000BaseX for 2.5G, but SGMII for
10/100/1G. Other use 'rate-adaptation'. They run the host side at the
fastest speed, 25000BaseX, and then insert pause frames to slow down
the MAC when the line side is running at 10/100/1G.
> Given that, I'd like to keep this patch scoped to the boot regression
> and leave the pre-existing shared-ModPHY-with-live-SATA question to the
> maintainers, who have the hardware knowledge to decide whether a
> stronger guard is warranted.
a42f6b3f1cc1 is from Intel, so i assume they thought about what
happens to the SATA controller, and are happy to take the risk of
destroying filesystems. So, yes, lets leave it as is for the moment.
Andrew
^ permalink raw reply
* Re: [PATCH net v2] ppp: defer channel free to an RCU grace period to fix pppol2tp RX UAF
From: Sebastian Andrzej Siewior @ 2026-07-08 13:56 UTC (permalink / raw)
To: Petr Pavlu
Cc: Qingfang Deng, Breno Leitao, Norbert Szetei, Andrew Lunn,
David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
Taegu Ha, Kees Cook, linux-ppp, linux-kernel, Guillaume Nault,
netdev, Luis Chamberlain, Daniel Gomez, Sami Tolvanen,
Aaron Tomlin, linux-modules, Paul E. McKenney
In-Reply-To: <bef05178-4856-4b62-9c3b-62bf636c239d@suse.com>
On 2026-07-08 15:04:32 [+0200], Petr Pavlu wrote:
> As RCU usage in modules is now more common, I see an argument for the
> module loader to invoke rcu_barrier() during module unload to make RCU
> usage easier. In general, module unloading is a rare operation, so even
> if it becomes somewhat slower, I don't expect it to be a significant
> issue.
Okay.
> One problem is that I'm not sure where the new rcu_barrier() call should
> be placed. The prototype adds it before calling the module's exit
> function. Would this actually fit all modules? From a quick look, I can
> see that various modules call it at different points during their exit.
I don't know why you would use call_rcu() in your module_exit()
(pointing to the same module). But you could have call_rcu() invoking
kmem_cache_free() and destroying that cache (kmem_cache_destroy()) in
your exit path. From that perspective it would make sense to flush all
calls before invoking module_exit().
> --
> Thanks,
> Petr
Sebastian
^ permalink raw reply
* Re: [PATCH net v2] ppp: defer channel free to an RCU grace period to fix pppol2tp RX UAF
From: Paul E. McKenney @ 2026-07-08 14:01 UTC (permalink / raw)
To: Sebastian Andrzej Siewior
Cc: Petr Pavlu, Qingfang Deng, Breno Leitao, Norbert Szetei,
Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Taegu Ha, Kees Cook, linux-ppp, linux-kernel,
Guillaume Nault, netdev, Luis Chamberlain, Daniel Gomez,
Sami Tolvanen, Aaron Tomlin, linux-modules
In-Reply-To: <20260708091147.O1d41Vi0@linutronix.de>
On Wed, Jul 08, 2026 at 11:11:47AM +0200, Sebastian Andrzej Siewior wrote:
> On 2026-07-07 09:39:01 [-0700], Paul E. McKenney wrote:
> > Alternatives include:
> >
> > o Provide a patch like that above, but only execute the
> > rcu_barrier() in some debug mode. If your code works when
> > that debug is enabled but does not otherwise, you add the
> > rcu_barrier().
> >
> > o If debug is enabled, make rcu_do_batch() check the function
> > before invoking it. If the function is not mapped, issue a
> > diagnostic, and don't try to invoke the function. (But is
> > there a sufficiently cheap way to check for the function not
> > being mapped?)
>
> In both cases you would see a backtrace and the name of the last
> unloaded module. And since we don't see a lot of these reports, people
> either don't run into this because it does not exist or RCU is quick
> enough.
Good point, the splat from calling the no-longer-mapped function should
call out the offending module. So maybe our debug code is good enough
already.
Thanx, Paul
^ permalink raw reply
* [PATCH net 00/17] netfilter: updates for net
From: Florian Westphal @ 2026-07-08 14:02 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
Hi,
The following patchset contains Netfilter fixes for *net*.
Most of these are LLM fixes for old issues flagged by sashiko/LLMs.
Many of these trigger drive-by-findings in sashiko. In particular:
- many load/store tearing and missing memory barriers, races
etc. in ipset, esp. with GC and resizing.
Keeping the proposed patches spinning for yet-another-iteration
keeps legit fixes back, so I prefer to add these now and follow
up with other reports later.
- flowtable work queue still has possible races with teardown,
but same rationale as with ipset: drive-by findings, not
problems coming with the flowtable IPIP changeset in this PR.
- ever since unreadable frag skb support was added in 6.12, we can no
longer do: BUG_ON(skb_copy_bits( ...): it will fire with such skbs.
Mina Almasry is looking at similar patterns elsewhere in the stack.
1) Guard skb->mac_header adjustment after IPv6 defragmentation in
nf_conntrack_reasm. From Xiang Mei.
2) NUL-terminate ebtables table names before calling find_table_lock() to
prevent stack-out-of-bounds reads. Also from Xiang Mei.
3) Zero the ebtables chainstack array, else error unwind may free bogus
pointer when CPU mask is sparse. All three issues date from 2.6 days.
4) Ensure ebtables module names are c-strings, same bug pattern as 2).
Bug added in 4.6.
5) Fix catchall element handling for inverted lookups in nft_lookup. Fold the
catchall lookup into ext before computing the match status. Was like
this ever since catchall elements got introduced in 5.13.
From Tamaki Yanagawa.
6-9) ipset updates from Jozsef Kadlecsik:
- mark rcu protected areas correctly
- address gc and resize clash in the comment extension
- add/del backlog cleanup in the error path
- allocate right size for the generic hash structure
10-12): IPIP flowtable updates from Pablo Neira Ayuso:
- Use the current direction's route when pushing IPIP headers
Fix incorrect headroom and fragmentation offset calculations.
- Avoid hardware offload for IPIP tunnels due to lack of driver support.
- Support IPIP tunnels with direct xmit in netfilter flowtable.
dst_cache and dst_cookie are moved outside the union to share route
state across flows. This is a followup to work done in 6.19 cycle.
13) Don't BUG() on skb_copy_bits error. Handle unreadable fragments by
either returning an error or restricting the copy operations to linear area,
This became an issue when unreable frag support was merged in 6.12.
14-16): IPVS updates from Yizhou Zhao:
- Pass parsed transport offset to IPVS state handlers.
update callback signatures.
- use correct transport header offset on state lookp in TCP.
As-is it was possible for ipv6 extension header data to be
treated as L4 header.
- same for SCTP. This was also broken since 2.6 days.
17) Ensure inner IP headers in ICMP errors are in the skb headroom after
stripping outer headers. Add more checks for the length of inner headers.
This was broken since 3.7 days.
From Julian Anastasov.
Please, pull these changes from:
The following changes since commit 6d27e29a90bc6a717b97c6ddcd866db7bd8e4adc:
Merge branch 'ipv4-ipv6-fix-uaf-and-memory-leak-in-igmp-mld' (2026-07-08 14:41:04 +0200)
are available in the Git repository at:
https://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf.git tags/nf-26-07-08
for you to fetch changes up to 3f7a535ff0fa627a0132803e4c2f903ceffcbc1c:
ipvs: ensure inner headers in ICMP errors are in headroom (2026-07-08 15:33:44 +0200)
----------------------------------------------------------------
netfilter pull request nf-26-07-08
----------------------------------------------------------------
Florian Westphal (3):
netfilter: ebtables: zero chainstack array
netfilter: ebtables: module names must be null-terminated
netfilter: handle unreadable frags
Jozsef Kadlecsik (4):
netfilter: ipset: mark the rcu locked areas properly
netfilter: ipset: exclude gc when resize is in progress
netfilter: ipset: cleanup the add/del backlog when resize failed
netfilter: ipset: allocate the proper memory for the generic hash structure
Julian Anastasov (1):
ipvs: ensure inner headers in ICMP errors are in headroom
Pablo Neira Ayuso (3):
netfilter: flowtable: use dst in this direction when pushing IPIP header
netfilter: flowtable: IPIP tunnel hardware offload is not yet support
netfilter: flowtable: support IPIP tunnel with direct xmit
Tamaki Yanagawa (1):
netfilter: nft_lookup: fix catchall element handling with inverted lookups
Xiang Mei (2):
netfilter: nf_conntrack_reasm: guard mac_header adjustment after IPv6 defrag
netfilter: ebtables: terminate table name before find_table_lock()
Yizhou Zhao (3):
ipvs: pass parsed transport offset to state handlers
ipvs: use parsed transport offset in TCP state lookup
ipvs: use parsed transport offset in SCTP state lookup
include/net/ip_vs.h | 3 +-
include/net/netfilter/nf_flow_table.h | 7 +-
net/bridge/netfilter/ebtables.c | 12 +++-
net/ipv6/netfilter/nf_conntrack_reasm.c | 5 +-
net/netfilter/ipset/ip_set_hash_gen.h | 85 ++++++++++++++++---------
net/netfilter/ipvs/ip_vs_core.c | 31 +++++----
net/netfilter/ipvs/ip_vs_proto_sctp.c | 18 ++----
net/netfilter/ipvs/ip_vs_proto_tcp.c | 11 +---
net/netfilter/ipvs/ip_vs_proto_udp.c | 3 +-
net/netfilter/nf_flow_table_core.c | 19 +++---
net/netfilter/nf_flow_table_ip.c | 21 +++---
net/netfilter/nf_flow_table_offload.c | 22 ++++++-
net/netfilter/nfnetlink_log.c | 26 +++++---
net/netfilter/nfnetlink_queue.c | 16 +++--
net/netfilter/nft_lookup.c | 10 +--
net/netfilter/xt_u32.c | 16 +++--
16 files changed, 196 insertions(+), 109 deletions(-)
--
2.54.0
^ permalink raw reply
* [PATCH net 01/17] netfilter: nf_conntrack_reasm: guard mac_header adjustment after IPv6 defrag
From: Florian Westphal @ 2026-07-08 14:02 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Xiang Mei <xmei5@asu.edu>
nf_ct_frag6_reasm() slides the packet head forward to drop the IPv6
fragment header and then unconditionally advances skb->mac_header:
skb->mac_header += sizeof(struct frag_hdr);
On the NF_INET_LOCAL_OUT defrag path the skb has no link-layer header
yet, so skb->mac_header is still the "not set" sentinel (u16)~0U. Adding
sizeof(struct frag_hdr) wraps it to a small value (0xffff + 8 == 7),
after which skb_mac_header_was_set() wrongly reports a MAC header is
present and skb_mac_header() points into the headroom.
The reassembler has done this unconditional add since it was introduced;
it was harmless while mac_header was a bare pointer, but wrong once
mac_header became a u16 offset whose unset state is the ~0U sentinel
tested by skb_mac_header_was_set(). The sibling net/ipv6/reassembly.c
does the same relocation and does guard the adjustment; mirror the
guard here.
Fixes: 9fb9cbb1082d ("[NETFILTER]: Add nf_conntrack subsystem.")
Cc: stable@vger.kernel.org
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/ipv6/netfilter/nf_conntrack_reasm.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/ipv6/netfilter/nf_conntrack_reasm.c b/net/ipv6/netfilter/nf_conntrack_reasm.c
index 64ab23ff559b..3637b20d3fa4 100644
--- a/net/ipv6/netfilter/nf_conntrack_reasm.c
+++ b/net/ipv6/netfilter/nf_conntrack_reasm.c
@@ -348,7 +348,8 @@ static int nf_ct_frag6_reasm(struct frag_queue *fq, struct sk_buff *skb,
skb_network_header(skb)[fq->nhoffset] = skb_transport_header(skb)[0];
memmove(skb->head + sizeof(struct frag_hdr), skb->head,
(skb->data - skb->head) - sizeof(struct frag_hdr));
- skb->mac_header += sizeof(struct frag_hdr);
+ if (skb_mac_header_was_set(skb))
+ skb->mac_header += sizeof(struct frag_hdr);
skb->network_header += sizeof(struct frag_hdr);
skb_reset_transport_header(skb);
--
2.54.0
^ permalink raw reply related
* [PATCH net 02/17] netfilter: ebtables: terminate table name before find_table_lock()
From: Florian Westphal @ 2026-07-08 14:02 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Xiang Mei <xmei5@asu.edu>
update_counters() and compat_update_counters() forward a user-supplied
32-byte table name to find_table_lock() without NUL-terminating it. On a
lookup miss, find_inlist_lock() calls try_then_request_module(..., "%s%s",
"ebtable_", name), and vsnprintf() reads past the name field and the
stack object until it hits a zero byte.
BUG: KASAN: stack-out-of-bounds in string (lib/vsprintf.c:648 lib/vsprintf.c:730)
Read of size 1 at addr ffff8880119dfb20 by task exploit/147
Call Trace:
...
string (lib/vsprintf.c:648 lib/vsprintf.c:730)
vsnprintf (lib/vsprintf.c:2945)
__request_module (kernel/module/kmod.c:150)
do_update_counters.isra.0 (net/bridge/netfilter/ebtables.c:371 net/bridge/netfilter/ebtables.c:380)
update_counters (net/bridge/netfilter/ebtables.c:1440)
do_ebt_set_ctl (net/bridge/netfilter/ebtables.c:2573)
nf_setsockopt (net/netfilter/nf_sockopt.c:101)
ip_setsockopt (net/ipv4/ip_sockglue.c:1424)
raw_setsockopt (net/ipv4/raw.c:847)
__sys_setsockopt (net/socket.c:2393)
...
compat_do_replace() shares the same unterminated name via
compat_copy_ebt_replace_from_user(); terminate it there too so all
find_table_lock() callers behave alike. The other callers already
terminate the name after the copy.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Fixes: 81e675c227ec ("netfilter: ebtables: add CONFIG_COMPAT support")
Cc: stable@vger.kernel.org
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/bridge/netfilter/ebtables.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c
index f20c039e44c8..5b74ff827493 100644
--- a/net/bridge/netfilter/ebtables.c
+++ b/net/bridge/netfilter/ebtables.c
@@ -1434,6 +1434,8 @@ static int update_counters(struct net *net, sockptr_t arg, unsigned int len)
if (copy_from_sockptr(&hlp, arg, sizeof(hlp)))
return -EFAULT;
+ hlp.name[sizeof(hlp.name) - 1] = '\0';
+
if (len != sizeof(hlp) + hlp.num_counters * sizeof(struct ebt_counter))
return -EINVAL;
@@ -2273,6 +2275,8 @@ static int compat_copy_ebt_replace_from_user(struct ebt_replace *repl,
memcpy(repl, &tmp, offsetof(struct ebt_replace, hook_entry));
+ repl->name[sizeof(repl->name) - 1] = '\0';
+
/* starting with hook_entry, 32 vs. 64 bit structures are different */
for (i = 0; i < NF_BR_NUMHOOKS; i++)
repl->hook_entry[i] = compat_ptr(tmp.hook_entry[i]);
@@ -2395,6 +2399,8 @@ static int compat_update_counters(struct net *net, sockptr_t arg,
if (copy_from_sockptr(&hlp, arg, sizeof(hlp)))
return -EFAULT;
+ hlp.name[sizeof(hlp.name) - 1] = '\0';
+
/* try real handler in case userland supplied needed padding */
if (len != sizeof(hlp) + hlp.num_counters * sizeof(struct ebt_counter))
return update_counters(net, arg, len);
--
2.54.0
^ permalink raw reply related
* [PATCH net 03/17] netfilter: ebtables: zero chainstack array
From: Florian Westphal @ 2026-07-08 14:02 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
sashiko reports:
looking at ebtables table
translation, could a sparse cpu_possible_mask lead to an uninitialized pointer
free?
If cpu_possible_mask is sparse (for example, CPU 0 and CPU 2 are possible,
but CPU 1 is not), the allocation loop skips CPU 1. If vmalloc_node() fails at
CPU 2, the cleanup loop will blindly decrement and call vfree() on
newinfo->chainstack[1].
Not a real-world bug, such allocation isn't expected to fail
in the first place.
Cc: stable@vger.kernel.org
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/bridge/netfilter/ebtables.c | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c
index 5b74ff827493..48187598cdd0 100644
--- a/net/bridge/netfilter/ebtables.c
+++ b/net/bridge/netfilter/ebtables.c
@@ -921,8 +921,7 @@ static int translate_table(struct net *net, const char *name,
* if an error occurs
*/
newinfo->chainstack =
- vmalloc_array(nr_cpu_ids,
- sizeof(*(newinfo->chainstack)));
+ vcalloc(nr_cpu_ids, sizeof(*(newinfo->chainstack)));
if (!newinfo->chainstack)
return -ENOMEM;
for_each_possible_cpu(i) {
--
2.54.0
^ permalink raw reply related
* [PATCH net 04/17] netfilter: ebtables: module names must be null-terminated
From: Florian Westphal @ 2026-07-08 14:02 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
We need to explicitly check the length, else we may pass non-null
terminated string to request_module().
Cc: stable@vger.kernel.org
Fixes: bcf493428840 ("netfilter: ebtables: Fix extension lookup with identical name")
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/bridge/netfilter/ebtables.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c
index 48187598cdd0..96c9a8f57c87 100644
--- a/net/bridge/netfilter/ebtables.c
+++ b/net/bridge/netfilter/ebtables.c
@@ -403,6 +403,9 @@ ebt_check_match(struct ebt_entry_match *m, struct xt_mtchk_param *par,
left - sizeof(struct ebt_entry_match) < m->match_size)
return -EINVAL;
+ if (strnlen(m->u.name, XT_EXTENSION_MAXNAMELEN) == XT_EXTENSION_MAXNAMELEN)
+ return -EINVAL;
+
match = xt_find_match(NFPROTO_BRIDGE, m->u.name, m->u.revision);
if (IS_ERR(match) || match->family != NFPROTO_BRIDGE) {
if (!IS_ERR(match))
--
2.54.0
^ permalink raw reply related
* [PATCH net 05/17] netfilter: nft_lookup: fix catchall element handling with inverted lookups
From: Florian Westphal @ 2026-07-08 14:02 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Tamaki Yanagawa <ty@000ty.net>
nft_lookup_eval() decides whether a lookup matched (`found`) from the
direct set lookup and priv->invert before falling back to the
catchall element used by interval sets (e.g. nft_set_rbtree) for the
open-ended default range. Since `found` is never recomputed after
`ext` is replaced by the catchall lookup, inverted lookups
(NFT_LOOKUP_F_INV, "!= @set") can wrongly match or wrongly skip the
catchall element, producing the wrong verdict. Fold the catchall
lookup into `ext` before computing `found`, matching the order
already used by nft_objref_map_eval().
Fixes: aaa31047a6d2 ("netfilter: nftables: add catch-all set element support")
Signed-off-by: Tamaki Yanagawa <ty@000ty.net>
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/nft_lookup.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/net/netfilter/nft_lookup.c b/net/netfilter/nft_lookup.c
index ba512e94b402..19887439847d 100644
--- a/net/netfilter/nft_lookup.c
+++ b/net/netfilter/nft_lookup.c
@@ -103,13 +103,13 @@ void nft_lookup_eval(const struct nft_expr *expr,
bool found;
ext = nft_set_do_lookup(net, set, ®s->data[priv->sreg]);
+ if (!ext)
+ ext = nft_set_catchall_lookup(net, set);
+
found = !!ext ^ priv->invert;
if (!found) {
- ext = nft_set_catchall_lookup(net, set);
- if (!ext) {
- regs->verdict.code = NFT_BREAK;
- return;
- }
+ regs->verdict.code = NFT_BREAK;
+ return;
}
if (ext) {
--
2.54.0
^ permalink raw reply related
* [PATCH net 06/17] netfilter: ipset: mark the rcu locked areas properly
From: Florian Westphal @ 2026-07-08 14:02 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Jozsef Kadlecsik <kadlec@netfilter.org>
When we bump the uref counter, there's no need to keep
the rcu lock because the referred hash table can't
disappear. Also, from the same reason in mtype_gc we
need the rcu lock and not a spinlock.
Signed-off-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipset/ip_set_hash_gen.h | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index dedf59b661dd..c9a071766243 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -569,9 +569,10 @@ mtype_gc(struct work_struct *work)
set = gc->set;
h = set->data;
- spin_lock_bh(&set->lock);
- t = ipset_dereference_set(h->table, set);
+ rcu_read_lock_bh();
+ t = rcu_dereference_bh(h->table);
atomic_inc(&t->uref);
+ rcu_read_unlock_bh();
numof_locks = ahash_numof_locks(t->htable_bits);
r = gc->region++;
if (r >= numof_locks) {
@@ -580,7 +581,6 @@ mtype_gc(struct work_struct *work)
next_run = (IPSET_GC_PERIOD(set->timeout) * HZ) / numof_locks;
if (next_run < HZ/10)
next_run = HZ/10;
- spin_unlock_bh(&set->lock);
mtype_gc_do(set, h, t, r);
@@ -860,15 +860,13 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
key = HKEY(value, h->initval, t->htable_bits);
r = ahash_region(key);
atomic_inc(&t->uref);
+ rcu_read_unlock_bh();
elements = t->hregion[r].elements;
maxelem = t->maxelem;
if (elements >= maxelem) {
u32 e;
- if (SET_WITH_TIMEOUT(set)) {
- rcu_read_unlock_bh();
+ if (SET_WITH_TIMEOUT(set))
mtype_gc_do(set, h, t, r);
- rcu_read_lock_bh();
- }
maxelem = h->maxelem;
elements = 0;
for (e = 0; e < ahash_numof_locks(t->htable_bits); e++)
@@ -876,7 +874,6 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
if (elements >= maxelem && SET_WITH_FORCEADD(set))
forceadd = true;
}
- rcu_read_unlock_bh();
spin_lock_bh(&t->hregion[r].lock);
n = rcu_dereference_bh(hbucket(t, key));
--
2.54.0
^ permalink raw reply related
* [PATCH net 07/17] netfilter: ipset: exclude gc when resize is in progress
From: Florian Westphal @ 2026-07-08 14:02 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Jozsef Kadlecsik <kadlec@netfilter.org>
Zhengchuan Liang and Eulgyu Kim reported that because resize
does not copy the comment extension into the resized set but
uses it's pointer, ongoing gc can free the extension in the
original set which then results stale pointer in the resized
one. The proposed patch was to recreate the extensions for
every element in the resized set. It is both expensive and
wastes memory, so better exclude gc when resizing in progress
detected: resizing will destroy the original set anyway,
so doing gc on it is unnecessary.
Introduce a new spinlock to exclude parallel gc and resize.
Because we just set and check a bool value, there's no need
for the parameter to be atomic_t and rename it for better
readability.
Reported-by: Yuan Tan <yuantan098@gmail.com>
Reported-by: Yifan Wu <yifanwucs@gmail.com>
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
Reported-by: Xin Liu <bird@lzu.edu.cn>
Reported by: Zhengchuan Liang <zcliangcn@gmail.com>
Reported by: Eulgyu Kim <eulgyukim@snu.ac.kr>
Signed-off-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipset/ip_set_hash_gen.h | 31 +++++++++++++++++----------
1 file changed, 20 insertions(+), 11 deletions(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index c9a071766243..8104dbac02fa 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -75,12 +75,13 @@ struct hbucket {
struct htable_gc {
struct delayed_work dwork;
struct ip_set *set; /* Set the gc belongs to */
+ spinlock_t lock; /* Lock to exclude gc and resize */
u32 region; /* Last gc run position */
};
/* The hash table: the table size stored here in order to make resizing easy */
struct htable {
- atomic_t ref; /* References for resizing */
+ bool resizing; /* Mark ongoing resize */
atomic_t uref; /* References for dumping and gc */
u8 htable_bits; /* size of hash table == 2^htable_bits */
u32 maxelem; /* Maxelem per region */
@@ -582,9 +583,12 @@ mtype_gc(struct work_struct *work)
if (next_run < HZ/10)
next_run = HZ/10;
- mtype_gc_do(set, h, t, r);
+ spin_lock_bh(&gc->lock);
+ if (!t->resizing)
+ mtype_gc_do(set, h, t, r);
+ spin_unlock_bh(&gc->lock);
- if (atomic_dec_and_test(&t->uref) && atomic_read(&t->ref)) {
+ if (atomic_dec_and_test(&t->uref) && t->resizing) {
pr_debug("Table destroy after resize by expire: %p\n", t);
mtype_ahash_destroy(set, t, false);
}
@@ -672,11 +676,13 @@ mtype_resize(struct ip_set *set, bool retried)
spin_lock_init(&t->hregion[i].lock);
/* There can't be another parallel resizing,
- * but dumping, gc, kernel side add/del are possible
+ * but dumping and kernel side add/del are possible
*/
orig = ipset_dereference_bh_nfnl(h->table);
- atomic_set(&orig->ref, 1);
atomic_inc(&orig->uref);
+ spin_lock_bh(&h->gc.lock);
+ orig->resizing = true;
+ spin_unlock_bh(&h->gc.lock);
pr_debug("attempt to resize set %s from %u to %u, t %p\n",
set->name, orig->htable_bits, htable_bits, orig);
for (r = 0; r < ahash_numof_locks(orig->htable_bits); r++) {
@@ -792,7 +798,9 @@ mtype_resize(struct ip_set *set, bool retried)
cleanup:
rcu_read_unlock_bh();
- atomic_set(&orig->ref, 0);
+ spin_lock_bh(&h->gc.lock);
+ orig->resizing = false;
+ spin_unlock_bh(&h->gc.lock);
atomic_dec(&orig->uref);
mtype_ahash_destroy(set, t, false);
if (ret == -EAGAIN)
@@ -1000,7 +1008,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
ret = 0;
resize:
spin_unlock_bh(&t->hregion[r].lock);
- if (atomic_read(&t->ref) && ext->target) {
+ if (t->resizing && ext && ext->target) {
/* Resize is in process and kernel side add, save values */
struct mtype_resize_ad *x;
@@ -1027,7 +1035,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
unlock:
spin_unlock_bh(&t->hregion[r].lock);
out:
- if (atomic_dec_and_test(&t->uref) && atomic_read(&t->ref)) {
+ if (atomic_dec_and_test(&t->uref) && t->resizing) {
pr_debug("Table destroy after resize by add: %p\n", t);
mtype_ahash_destroy(set, t, false);
}
@@ -1090,7 +1098,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
#endif
ip_set_ext_destroy(set, data);
- if (atomic_read(&t->ref) && ext->target) {
+ if (t->resizing && ext && ext->target) {
/* Resize is in process and kernel side del,
* save values
*/
@@ -1141,7 +1149,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
list_add(&x->list, &h->ad);
spin_unlock_bh(&set->lock);
}
- if (atomic_dec_and_test(&t->uref) && atomic_read(&t->ref)) {
+ if (atomic_dec_and_test(&t->uref) && t->resizing) {
pr_debug("Table destroy after resize by del: %p\n", t);
mtype_ahash_destroy(set, t, false);
}
@@ -1350,7 +1358,7 @@ mtype_uref(struct ip_set *set, struct netlink_callback *cb, bool start)
rcu_read_unlock_bh();
} else if (cb->args[IPSET_CB_PRIVATE]) {
t = (struct htable *)cb->args[IPSET_CB_PRIVATE];
- if (atomic_dec_and_test(&t->uref) && atomic_read(&t->ref)) {
+ if (atomic_dec_and_test(&t->uref) && t->resizing) {
pr_debug("Table destroy after resize "
" by dump: %p\n", t);
mtype_ahash_destroy(set, t, false);
@@ -1590,6 +1598,7 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
return -ENOMEM;
}
h->gc.set = set;
+ spin_lock_init(&h->gc.lock);
for (i = 0; i < ahash_numof_locks(hbits); i++)
spin_lock_init(&t->hregion[i].lock);
h->maxelem = maxelem;
--
2.54.0
^ permalink raw reply related
* [PATCH net 08/17] netfilter: ipset: cleanup the add/del backlog when resize failed
From: Florian Westphal @ 2026-07-08 14:03 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Jozsef Kadlecsik <kadlec@netfilter.org>
Sashiko pointed out that the add/del backlog was not cleaned up
when resize failed. Fix it in the corresponding error path. Also,
make sure that the add/del backlog is htable-specific so when
resize creates a new htable, old/new backlog can't be mixed up.
Signed-off-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipset/ip_set_hash_gen.h | 28 +++++++++++++++++++--------
1 file changed, 20 insertions(+), 8 deletions(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 8104dbac02fa..c0132d0f4cc0 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -85,6 +85,7 @@ struct htable {
atomic_t uref; /* References for dumping and gc */
u8 htable_bits; /* size of hash table == 2^htable_bits */
u32 maxelem; /* Maxelem per region */
+ struct list_head ad; /* Resize add|del backlist */
struct ip_set_region *hregion; /* Region locks and ext sizes */
struct hbucket __rcu *bucket[]; /* hashtable buckets */
};
@@ -302,7 +303,6 @@ struct htype {
u8 netmask; /* netmask value for subnets to store */
union nf_inet_addr bitmask; /* stores bitmask */
#endif
- struct list_head ad; /* Resize add|del backlist */
struct mtype_elem next; /* temporary storage for uadd */
#ifdef IP_SET_HASH_WITH_NETS
struct net_prefixes nets[NLEN]; /* book-keeping of prefixes */
@@ -452,13 +452,14 @@ static void
mtype_destroy(struct ip_set *set)
{
struct htype *h = set->data;
+ struct htable *t = (__force struct htable *)h->table;
struct list_head *l, *lt;
- mtype_ahash_destroy(set, (__force struct htable *)h->table, true);
- list_for_each_safe(l, lt, &h->ad) {
+ list_for_each_safe(l, lt, &t->ad) {
list_del(l);
kfree(l);
}
+ mtype_ahash_destroy(set, t, true);
kfree(h);
set->data = NULL;
@@ -672,6 +673,7 @@ mtype_resize(struct ip_set *set, bool retried)
}
t->htable_bits = htable_bits;
t->maxelem = h->maxelem / ahash_numof_locks(htable_bits);
+ INIT_LIST_HEAD(&t->ad);
for (i = 0; i < ahash_numof_locks(htable_bits); i++)
spin_lock_init(&t->hregion[i].lock);
@@ -774,7 +776,7 @@ mtype_resize(struct ip_set *set, bool retried)
* Kernel-side add cannot trigger a resize and userspace actions
* are serialized by the mutex.
*/
- list_for_each_safe(l, lt, &h->ad) {
+ list_for_each_safe(l, lt, &orig->ad) {
x = list_entry(l, struct mtype_resize_ad, list);
if (x->ad == IPSET_ADD) {
mtype_add(set, &x->d, &x->ext, &x->mext, x->flags);
@@ -801,10 +803,21 @@ mtype_resize(struct ip_set *set, bool retried)
spin_lock_bh(&h->gc.lock);
orig->resizing = false;
spin_unlock_bh(&h->gc.lock);
+ /* Make sure parallel readers see that orig->resizing is false
+ * before we decrement uref */
+ synchronize_rcu();
atomic_dec(&orig->uref);
mtype_ahash_destroy(set, t, false);
if (ret == -EAGAIN)
goto retry;
+
+ /* Cleanup the backlog of ADD/DEL elements */
+ spin_lock_bh(&set->lock);
+ list_for_each_safe(l, lt, &orig->ad) {
+ list_del(l);
+ kfree(l);
+ }
+ spin_unlock_bh(&set->lock);
goto out;
hbwarn:
@@ -1022,7 +1035,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
memcpy(&x->mext, mext, sizeof(struct ip_set_ext));
x->flags = flags;
spin_lock_bh(&set->lock);
- list_add_tail(&x->list, &h->ad);
+ list_add_tail(&x->list, &t->ad);
spin_unlock_bh(&set->lock);
}
goto out;
@@ -1146,7 +1159,7 @@ mtype_del(struct ip_set *set, void *value, const struct ip_set_ext *ext,
spin_unlock_bh(&t->hregion[r].lock);
if (x) {
spin_lock_bh(&set->lock);
- list_add(&x->list, &h->ad);
+ list_add(&x->list, &t->ad);
spin_unlock_bh(&set->lock);
}
if (atomic_dec_and_test(&t->uref) && t->resizing) {
@@ -1625,9 +1638,8 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
}
t->htable_bits = hbits;
t->maxelem = h->maxelem / ahash_numof_locks(hbits);
+ INIT_LIST_HEAD(&t->ad);
RCU_INIT_POINTER(h->table, t);
-
- INIT_LIST_HEAD(&h->ad);
set->data = h;
#ifndef IP_SET_PROTO_UNDEF
if (set->family == NFPROTO_IPV4) {
--
2.54.0
^ permalink raw reply related
* [PATCH net 09/17] netfilter: ipset: allocate the proper memory for the generic hash structure
From: Florian Westphal @ 2026-07-08 14:03 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Jozsef Kadlecsik <kadlec@netfilter.org>
Because a single create function is emitted for every hash type,
from the IPv4 and IPv6 generic hash structure definitions the last
one, i.e. the IPv6 was in effect for IPv4 too. Use the proper size
when allocating the structure. Comment properly that because create()
refers to elements of the generic hash structure, all referred ones
must come before the IPv4/IPv6 dependent 'next' member.
Signed-off-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipset/ip_set_hash_gen.h | 13 +++++++++++--
1 file changed, 11 insertions(+), 2 deletions(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index c0132d0f4cc0..8231317b0f1f 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -303,10 +303,13 @@ struct htype {
u8 netmask; /* netmask value for subnets to store */
union nf_inet_addr bitmask; /* stores bitmask */
#endif
- struct mtype_elem next; /* temporary storage for uadd */
#ifdef IP_SET_HASH_WITH_NETS
struct net_prefixes nets[NLEN]; /* book-keeping of prefixes */
#endif
+ /* Because 'next' is IPv4/IPv6 dependent, no elements of this
+ * structure and referred in create() may come after 'next'.
+ */
+ struct mtype_elem next; /* temporary storage for uadd */
};
/* ADD|DEL entries saved during resize */
@@ -1584,7 +1587,13 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
if (tb[IPSET_ATTR_MAXELEM])
maxelem = ip_set_get_h32(tb[IPSET_ATTR_MAXELEM]);
- hsize = sizeof(*h);
+#ifdef IP_SET_PROTO_UNDEF
+ hsize = sizeof(struct htype);
+#else
+ hsize = set->family == NFPROTO_IPV6 ?
+ sizeof(struct IPSET_TOKEN(HTYPE, 6)) :
+ sizeof(struct IPSET_TOKEN(HTYPE, 4));
+#endif
h = kzalloc(hsize, GFP_KERNEL);
if (!h)
return -ENOMEM;
--
2.54.0
^ permalink raw reply related
* [PATCH net 10/17] netfilter: flowtable: use dst in this direction when pushing IPIP header
From: Florian Westphal @ 2026-07-08 14:03 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Pablo Neira Ayuso <pablo@netfilter.org>
When pushing the IPIP header, the route of the other direction is used
to calculate the headroom, use the route in this direction. Accessing
the other tuple to set the IP source and destination is fine because
this tuple does not provide such information to avoid storing redundant
information. However, this tuple already provides the dst for this
direction, this went unnoticed because this bug affects headroom and
iph->frag_off only at this stage.
Fixes: d30301ba4b07 ("netfilter: flowtable: Add IPIP tx sw acceleration")
Fixes: 93cf357fa797 ("netfilter: flowtable: Add IP6IP6 tx sw acceleration")
Cc: stable@vger.kernel.org
Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/nf_flow_table_ip.c | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
index 29e93ac1e2e4..089f2bc19972 100644
--- a/net/netfilter/nf_flow_table_ip.c
+++ b/net/netfilter/nf_flow_table_ip.c
@@ -590,10 +590,10 @@ static int nf_flow_pppoe_push(struct sk_buff *skb, u16 id,
static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb,
struct flow_offload_tuple *tuple,
- __be32 *ip_daddr)
+ struct dst_entry *dst, __be32 *ip_daddr)
{
struct iphdr *iph = (struct iphdr *)skb_network_header(skb);
- struct rtable *rt = dst_rtable(tuple->dst_cache);
+ struct rtable *rt = dst_rtable(dst);
u8 tos = iph->tos, ttl = iph->ttl;
__be16 frag_off = iph->frag_off;
u32 headroom = sizeof(*iph);
@@ -636,21 +636,22 @@ static int nf_flow_tunnel_ipip_push(struct net *net, struct sk_buff *skb,
static int nf_flow_tunnel_v4_push(struct net *net, struct sk_buff *skb,
struct flow_offload_tuple *tuple,
- __be32 *ip_daddr)
+ struct dst_entry *dst, __be32 *ip_daddr)
{
if (tuple->tun_num)
- return nf_flow_tunnel_ipip_push(net, skb, tuple, ip_daddr);
+ return nf_flow_tunnel_ipip_push(net, skb, tuple, dst, ip_daddr);
return 0;
}
static int nf_flow_tunnel_ip6ip6_push(struct net *net, struct sk_buff *skb,
struct flow_offload_tuple *tuple,
+ struct dst_entry *dst,
struct in6_addr **ip6_daddr)
{
struct ipv6hdr *ip6h = (struct ipv6hdr *)skb_network_header(skb);
- struct rtable *rt = dst_rtable(tuple->dst_cache);
__u8 dsfield = ipv6_get_dsfield(ip6h);
+ struct rtable *rt = dst_rtable(dst);
struct flowi6 fl6 = {
.daddr = tuple->tun.src_v6,
.saddr = tuple->tun.dst_v6,
@@ -696,10 +697,11 @@ static int nf_flow_tunnel_ip6ip6_push(struct net *net, struct sk_buff *skb,
static int nf_flow_tunnel_v6_push(struct net *net, struct sk_buff *skb,
struct flow_offload_tuple *tuple,
+ struct dst_entry *dst,
struct in6_addr **ip6_daddr)
{
if (tuple->tun_num)
- return nf_flow_tunnel_ip6ip6_push(net, skb, tuple, ip6_daddr);
+ return nf_flow_tunnel_ip6ip6_push(net, skb, tuple, dst, ip6_daddr);
return 0;
}
@@ -842,7 +844,8 @@ nf_flow_offload_ip_hook(void *priv, struct sk_buff *skb,
other_tuple = &flow->tuplehash[!dir].tuple;
ip_daddr = other_tuple->src_v4.s_addr;
- if (nf_flow_tunnel_v4_push(state->net, skb, other_tuple, &ip_daddr) < 0)
+ if (nf_flow_tunnel_v4_push(state->net, skb, other_tuple,
+ tuplehash->tuple.dst_cache, &ip_daddr) < 0)
return NF_DROP;
switch (tuplehash->tuple.xmit_type) {
@@ -1158,6 +1161,7 @@ nf_flow_offload_ipv6_hook(void *priv, struct sk_buff *skb,
ip6_daddr = &other_tuple->src_v6;
if (nf_flow_tunnel_v6_push(state->net, skb, other_tuple,
+ tuplehash->tuple.dst_cache,
&ip6_daddr) < 0)
return NF_DROP;
--
2.54.0
^ permalink raw reply related
* [PATCH net 11/17] netfilter: flowtable: IPIP tunnel hardware offload is not yet support
From: Florian Westphal @ 2026-07-08 14:03 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Pablo Neira Ayuso <pablo@netfilter.org>
No driver supports for IPIP tunnels yet, give up early on setting up the
hardware offload for this scenario.
This patch adds a stub that can be enhanced to add more configuration
that are currently not supported. As of now, the offload work is
enqueued to the worker, then ignored if the hardware offload
configuration is not supported.
Check the NF_FLOW_HW flag to know if this entry was already tried once
to be offloaded so this is not retried on refresh when unsupported. Move
NF_FLOW_HW flag check to nf_flow_offload_add(). If this NF_FLOW_HW flag
is unset the _del and _stats variants are never called.
This can be updated later on to skip hardware offload work to be queued
in case hardware offload does not support it.
Fixes: d98103575dcd ("netfilter: flowtable: Add IP6IP6 rx sw acceleration")
Fixes: ab427db17885 ("netfilter: flowtable: Add IPIP rx sw acceleration")
Cc: stable@vger.kernel.org
Reported-by: Yuan Tan <yuantan098@gmail.com>
Reported-by: Xin Liu <bird@lzu.edu.cn>
Reported-by: Zhengyang Chen <chzhengyang2023@lzu.edu.cn>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
include/net/netfilter/nf_flow_table.h | 2 ++
net/netfilter/nf_flow_table_core.c | 7 +++----
net/netfilter/nf_flow_table_offload.c | 22 ++++++++++++++++++++--
3 files changed, 25 insertions(+), 6 deletions(-)
diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
index 7b23b245a5a8..dc5c9b48e65a 100644
--- a/include/net/netfilter/nf_flow_table.h
+++ b/include/net/netfilter/nf_flow_table.h
@@ -357,6 +357,8 @@ static inline int nf_flow_register_bpf(void)
void nf_flow_offload_add(struct nf_flowtable *flowtable,
struct flow_offload *flow);
+void nf_flow_offload_refresh(struct nf_flowtable *flowtable,
+ struct flow_offload *flow);
void nf_flow_offload_del(struct nf_flowtable *flowtable,
struct flow_offload *flow);
void nf_flow_offload_stats(struct nf_flowtable *flowtable,
diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c
index 99c5b9d671a0..d06ce0848b68 100644
--- a/net/netfilter/nf_flow_table_core.c
+++ b/net/netfilter/nf_flow_table_core.c
@@ -345,10 +345,8 @@ int flow_offload_add(struct nf_flowtable *flow_table, struct flow_offload *flow)
nf_ct_refresh(flow->ct, NF_CT_DAY);
- if (nf_flowtable_hw_offload(flow_table)) {
- __set_bit(NF_FLOW_HW, &flow->flags);
+ if (nf_flowtable_hw_offload(flow_table))
nf_flow_offload_add(flow_table, flow);
- }
return 0;
}
@@ -369,7 +367,8 @@ void flow_offload_refresh(struct nf_flowtable *flow_table,
test_bit(NF_FLOW_CLOSING, &flow->flags))
return;
- nf_flow_offload_add(flow_table, flow);
+ if (test_bit(NF_FLOW_HW, &flow->flags))
+ nf_flow_offload_refresh(flow_table, flow);
}
EXPORT_SYMBOL_GPL(flow_offload_refresh);
diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
index 002ec15d988b..801a3dd9ceea 100644
--- a/net/netfilter/nf_flow_table_offload.c
+++ b/net/netfilter/nf_flow_table_offload.c
@@ -1101,9 +1101,17 @@ nf_flow_offload_work_alloc(struct nf_flowtable *flowtable,
return offload;
}
+static bool nf_flow_offload_unsupported(struct flow_offload *flow)
+{
+ if (flow->tuplehash[FLOW_OFFLOAD_DIR_ORIGINAL].tuple.tun_num ||
+ flow->tuplehash[FLOW_OFFLOAD_DIR_REPLY].tuple.tun_num)
+ return true;
-void nf_flow_offload_add(struct nf_flowtable *flowtable,
- struct flow_offload *flow)
+ return false;
+}
+
+void nf_flow_offload_refresh(struct nf_flowtable *flowtable,
+ struct flow_offload *flow)
{
struct flow_offload_work *offload;
@@ -1114,6 +1122,16 @@ void nf_flow_offload_add(struct nf_flowtable *flowtable,
flow_offload_queue_work(offload);
}
+void nf_flow_offload_add(struct nf_flowtable *flowtable,
+ struct flow_offload *flow)
+{
+ if (nf_flow_offload_unsupported(flow))
+ return;
+
+ set_bit(NF_FLOW_HW, &flow->flags);
+ nf_flow_offload_refresh(flowtable, flow);
+}
+
void nf_flow_offload_del(struct nf_flowtable *flowtable,
struct flow_offload *flow)
{
--
2.54.0
^ permalink raw reply related
* [PATCH net 12/17] netfilter: flowtable: support IPIP tunnel with direct xmit
From: Florian Westphal @ 2026-07-08 14:03 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Pablo Neira Ayuso <pablo@netfilter.org>
The combination of IPIP tunnel with direct xmit, eg. bridge device,
breaks because no dst_entry is provided to check the skb headroom and to
set the iph->frag_off field. This leads to invalid dst usage and can
trigger a crash in the tunnel transmit path.
Fix this by moving dst_cache and dst_cookie out of the runtime union so
that they can be shared by neighbour, xfrm, and direct tunnel flows.
For FLOW_OFFLOAD_XMIT_DIRECT tuples carrying tunnel metadata, preserve
route state in these shared fields and release it through the common
dst release path.
Since dst_entry is now available to the three supported xmit modes and
dst_release() already deals with NULL dst, remove the xmit type check
in nft_flow_dst_release(). Moreover, skip the check if the dst entry
is NULL in nf_flow_dst_check() which is now the case for the direct
xmit case.
Based on patch from Rein Wei <n05ec@lzu.edu.cn>.
Fixes: d30301ba4b07 ("netfilter: flowtable: Add IPIP tx sw acceleration")
Cc: stable@vger.kernel.org
Reported-by: Yuan Tan <yuantan098@gmail.com>
Reported-by: Xin Liu <bird@lzu.edu.cn>
Reported-by: Zhengyang Chen <chzhengyang2023@lzu.edu.cn>
Reported-by: Ren Wei <n05ec@lzu.edu.cn>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
include/net/netfilter/nf_flow_table.h | 5 +++--
net/netfilter/nf_flow_table_core.c | 12 ++++++++----
net/netfilter/nf_flow_table_ip.c | 3 +--
3 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
index dc5c9b48e65a..ce414118962f 100644
--- a/include/net/netfilter/nf_flow_table.h
+++ b/include/net/netfilter/nf_flow_table.h
@@ -155,11 +155,12 @@ struct flow_offload_tuple {
tun_num:2,
in_vlan_ingress:2;
u16 mtu;
+ u32 dst_cookie;
+ struct dst_entry *dst_cache;
+
union {
struct {
- struct dst_entry *dst_cache;
u32 ifidx;
- u32 dst_cookie;
};
struct {
u32 ifidx;
diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c
index d06ce0848b68..2a829b5e8240 100644
--- a/net/netfilter/nf_flow_table_core.c
+++ b/net/netfilter/nf_flow_table_core.c
@@ -127,12 +127,18 @@ static int flow_offload_fill_route(struct flow_offload *flow,
switch (route->tuple[dir].xmit_type) {
case FLOW_OFFLOAD_XMIT_DIRECT:
+ if (flow_tuple->tun_num) {
+ flow_tuple->dst_cache = dst;
+ flow_tuple->dst_cookie =
+ flow_offload_dst_cookie(flow_tuple);
+ }
memcpy(flow_tuple->out.h_dest, route->tuple[dir].out.h_dest,
ETH_ALEN);
memcpy(flow_tuple->out.h_source, route->tuple[dir].out.h_source,
ETH_ALEN);
flow_tuple->out.ifidx = route->tuple[dir].out.ifindex;
- dst_release(dst);
+ if (!flow_tuple->tun_num)
+ dst_release(dst);
break;
case FLOW_OFFLOAD_XMIT_XFRM:
case FLOW_OFFLOAD_XMIT_NEIGH:
@@ -152,9 +158,7 @@ static int flow_offload_fill_route(struct flow_offload *flow,
static void nft_flow_dst_release(struct flow_offload *flow,
enum flow_offload_tuple_dir dir)
{
- if (flow->tuplehash[dir].tuple.xmit_type == FLOW_OFFLOAD_XMIT_NEIGH ||
- flow->tuplehash[dir].tuple.xmit_type == FLOW_OFFLOAD_XMIT_XFRM)
- dst_release(flow->tuplehash[dir].tuple.dst_cache);
+ dst_release(flow->tuplehash[dir].tuple.dst_cache);
}
void flow_offload_route_init(struct flow_offload *flow,
diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
index 089f2bc19972..0b78decce8a9 100644
--- a/net/netfilter/nf_flow_table_ip.c
+++ b/net/netfilter/nf_flow_table_ip.c
@@ -299,8 +299,7 @@ static bool nf_flow_exceeds_mtu(const struct sk_buff *skb, unsigned int mtu)
static inline bool nf_flow_dst_check(struct flow_offload_tuple *tuple)
{
- if (tuple->xmit_type != FLOW_OFFLOAD_XMIT_NEIGH &&
- tuple->xmit_type != FLOW_OFFLOAD_XMIT_XFRM)
+ if (!tuple->dst_cache)
return true;
return dst_check(tuple->dst_cache, tuple->dst_cookie);
--
2.54.0
^ permalink raw reply related
* [PATCH net 13/17] netfilter: handle unreadable frags
From: Florian Westphal @ 2026-07-08 14:03 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
sashiko reports:
When an skb with unreadable fragments (such as from devmem TCP, where
skb_frags_readable(skb) returns false) is processed by the u32 module,
skb_copy_bits() will safely return a negative error code [..]
xt_u32: bail out with hotdrop in this case.
gather_frags: return -1, just as if we had no fragment header.
nfnetlink_queue: restrict to the linear part.
nfnetlink_log: restrict to the linear part.
v2:
- skb_zerocopy helpers don't copy readable flag, i.e. nfnetlink_queue
is broken too
xt_u32 shouldn't return true if hotdrop was set.
Fixes: 65249feb6b3d ("net: add support for skbs with unreadable frags")
Cc: stable@vger.kernel.org
Acked-by: Mina Almasry <almasrymina@google.com>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/ipv6/netfilter/nf_conntrack_reasm.c | 2 +-
net/netfilter/nfnetlink_log.c | 26 ++++++++++++++++---------
net/netfilter/nfnetlink_queue.c | 16 +++++++++++----
net/netfilter/xt_u32.c | 16 ++++++++++-----
4 files changed, 41 insertions(+), 19 deletions(-)
diff --git a/net/ipv6/netfilter/nf_conntrack_reasm.c b/net/ipv6/netfilter/nf_conntrack_reasm.c
index 3637b20d3fa4..599c49bf0a0a 100644
--- a/net/ipv6/netfilter/nf_conntrack_reasm.c
+++ b/net/ipv6/netfilter/nf_conntrack_reasm.c
@@ -419,7 +419,7 @@ find_prev_fhdr(struct sk_buff *skb, u8 *prevhdrp, int *prevhoff, int *fhoff)
return -1;
}
if (skb_copy_bits(skb, start, &hdr, sizeof(hdr)))
- BUG();
+ return -1;
if (nexthdr == NEXTHDR_AUTH)
hdrlen = ipv6_authlen(&hdr);
else
diff --git a/net/netfilter/nfnetlink_log.c b/net/netfilter/nfnetlink_log.c
index fa3657599861..5fee61b3813c 100644
--- a/net/netfilter/nfnetlink_log.c
+++ b/net/netfilter/nfnetlink_log.c
@@ -676,7 +676,7 @@ __build_packet_message(struct nfnl_log_net *log,
goto nla_put_failure;
if (skb_copy_bits(skb, 0, nla_data(nla), data_len))
- BUG();
+ goto nla_put_failure;
}
nlh->nlmsg_len = inst->skb->tail - old_tail;
@@ -698,6 +698,21 @@ static const struct nf_loginfo default_loginfo = {
},
};
+static unsigned int nfulnl_get_copy_len(const struct nf_loginfo *li,
+ const struct sk_buff *skb,
+ unsigned int copy_len)
+{
+ unsigned int len = skb->len;
+
+ if ((li->u.ulog.flags & NF_LOG_F_COPY_LEN) &&
+ li->u.ulog.copy_len < copy_len)
+ copy_len = li->u.ulog.copy_len;
+ if (!skb_frags_readable(skb))
+ len = skb_headlen(skb);
+
+ return min(len, copy_len);
+}
+
/* log handler for internal netfilter logging api */
static void
nfulnl_log_packet(struct net *net,
@@ -790,14 +805,7 @@ nfulnl_log_packet(struct net *net,
break;
case NFULNL_COPY_PACKET:
- data_len = inst->copy_range;
- if ((li->u.ulog.flags & NF_LOG_F_COPY_LEN) &&
- (li->u.ulog.copy_len < data_len))
- data_len = li->u.ulog.copy_len;
-
- if (data_len > skb->len)
- data_len = skb->len;
-
+ data_len = nfulnl_get_copy_len(li, skb, inst->copy_range);
size += nla_total_size(data_len);
break;
diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c
index 35d4c6c628ff..b8aaf39cb4d8 100644
--- a/net/netfilter/nfnetlink_queue.c
+++ b/net/netfilter/nfnetlink_queue.c
@@ -690,6 +690,17 @@ static int nfqnl_put_master_ifindex(struct sk_buff *nlskb, int attr,
}
#endif
+static unsigned int nfqnl_get_data_len(const struct sk_buff *entskb,
+ unsigned int copy_range)
+{
+ unsigned int data_len = entskb->len;
+
+ if (!skb_frags_readable(entskb))
+ data_len = skb_headlen(entskb);
+
+ return min(data_len, copy_range);
+}
+
static struct sk_buff *
nfqnl_build_packet_message(struct net *net, struct nfqnl_instance *queue,
struct nf_queue_entry *entry,
@@ -755,10 +766,7 @@ nfqnl_build_packet_message(struct net *net, struct nfqnl_instance *queue,
nf_queue_checksum_help(entskb))
return NULL;
- data_len = READ_ONCE(queue->copy_range);
- if (data_len > entskb->len)
- data_len = entskb->len;
-
+ data_len = nfqnl_get_data_len(entskb, READ_ONCE(queue->copy_range));
hlen = skb_zerocopy_headlen(entskb);
hlen = min_t(unsigned int, hlen, data_len);
size += sizeof(struct nlattr) + hlen;
diff --git a/net/netfilter/xt_u32.c b/net/netfilter/xt_u32.c
index ec1a21e3b6e2..dabbaa742874 100644
--- a/net/netfilter/xt_u32.c
+++ b/net/netfilter/xt_u32.c
@@ -14,8 +14,8 @@
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter/xt_u32.h>
-static bool u32_match_it(const struct xt_u32 *data,
- const struct sk_buff *skb)
+static int u32_match_it(const struct xt_u32 *data,
+ const struct sk_buff *skb)
{
const struct xt_u32_test *ct;
unsigned int testind;
@@ -40,7 +40,8 @@ static bool u32_match_it(const struct xt_u32 *data,
return false;
if (skb_copy_bits(skb, pos, &n, sizeof(n)) < 0)
- BUG();
+ return -1;
+
val = ntohl(n);
nnums = ct->nnums;
@@ -68,7 +69,7 @@ static bool u32_match_it(const struct xt_u32 *data,
if (skb_copy_bits(skb, at + pos, &n,
sizeof(n)) < 0)
- BUG();
+ return -1;
val = ntohl(n);
break;
}
@@ -90,9 +91,14 @@ static bool u32_match_it(const struct xt_u32 *data,
static bool u32_mt(const struct sk_buff *skb, struct xt_action_param *par)
{
const struct xt_u32 *data = par->matchinfo;
- bool ret;
+ int ret;
ret = u32_match_it(data, skb);
+ if (ret < 0) {
+ par->hotdrop = true;
+ return false;
+ }
+
return ret ^ data->invert;
}
--
2.54.0
^ permalink raw reply related
* [PATCH net 14/17] ipvs: pass parsed transport offset to state handlers
From: Florian Westphal @ 2026-07-08 14:03 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
IPVS callers already parse the packet into struct ip_vs_iphdr before
updating connection state. For IPv6 this records the real
transport-header offset after extension headers in iph.len.
Pass this parsed transport offset through ip_vs_set_state() and the
protocol state_transition() callback so protocol handlers can use the
same packet context as scheduling and NAT handling. This patch only
changes the common callback plumbing and adapts the protocol callback
signatures; TCP and SCTP start using the value in follow-up patches.
Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Acked-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
include/net/ip_vs.h | 3 ++-
net/netfilter/ipvs/ip_vs_core.c | 10 +++++-----
net/netfilter/ipvs/ip_vs_proto_sctp.c | 3 ++-
net/netfilter/ipvs/ip_vs_proto_tcp.c | 3 ++-
net/netfilter/ipvs/ip_vs_proto_udp.c | 3 ++-
5 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index 49297fec448a..417ff51f62fc 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -752,7 +752,8 @@ struct ip_vs_protocol {
void (*state_transition)(struct ip_vs_conn *cp, int direction,
const struct sk_buff *skb,
- struct ip_vs_proto_data *pd);
+ struct ip_vs_proto_data *pd,
+ unsigned int iph_len);
int (*register_app)(struct netns_ipvs *ipvs, struct ip_vs_app *inc);
diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
index 906f2c361676..f79c09869636 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c
@@ -398,10 +398,10 @@ ip_vs_conn_stats(struct ip_vs_conn *cp, struct ip_vs_service *svc)
static inline void
ip_vs_set_state(struct ip_vs_conn *cp, int direction,
const struct sk_buff *skb,
- struct ip_vs_proto_data *pd)
+ struct ip_vs_proto_data *pd, unsigned int iph_len)
{
if (likely(pd->pp->state_transition))
- pd->pp->state_transition(cp, direction, skb, pd);
+ pd->pp->state_transition(cp, direction, skb, pd, iph_len);
}
static inline int
@@ -803,7 +803,7 @@ int ip_vs_leave(struct ip_vs_service *svc, struct sk_buff *skb,
ip_vs_in_stats(cp, skb);
/* set state */
- ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd);
+ 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);
@@ -1484,7 +1484,7 @@ handle_response(int af, struct sk_buff *skb, struct ip_vs_proto_data *pd,
after_nat:
ip_vs_out_stats(cp, skb);
- ip_vs_set_state(cp, IP_VS_DIR_OUTPUT, skb, pd);
+ ip_vs_set_state(cp, IP_VS_DIR_OUTPUT, skb, pd, iph->len);
skb->ipvs_property = 1;
if (!(cp->flags & IP_VS_CONN_F_NFCT))
ip_vs_notrack(skb);
@@ -2233,7 +2233,7 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state
IP_VS_DBG_PKT(11, af, pp, skb, iph.off, "Incoming packet");
ip_vs_in_stats(cp, skb);
- ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd);
+ 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);
/* do not touch skb anymore */
diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c
index 63c78a1f3918..394367b7b388 100644
--- a/net/netfilter/ipvs/ip_vs_proto_sctp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c
@@ -468,7 +468,8 @@ set_sctp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp,
static void
sctp_state_transition(struct ip_vs_conn *cp, int direction,
- const struct sk_buff *skb, struct ip_vs_proto_data *pd)
+ const struct sk_buff *skb, struct ip_vs_proto_data *pd,
+ unsigned int iph_len)
{
spin_lock_bh(&cp->lock);
set_sctp_state(pd, cp, direction, skb);
diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c
index 8cc0a8ce6241..2d3f6aeafe52 100644
--- a/net/netfilter/ipvs/ip_vs_proto_tcp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c
@@ -579,7 +579,8 @@ set_tcp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp,
static void
tcp_state_transition(struct ip_vs_conn *cp, int direction,
const struct sk_buff *skb,
- struct ip_vs_proto_data *pd)
+ struct ip_vs_proto_data *pd,
+ unsigned int iph_len)
{
struct tcphdr _tcph, *th;
diff --git a/net/netfilter/ipvs/ip_vs_proto_udp.c b/net/netfilter/ipvs/ip_vs_proto_udp.c
index f9de632e38cd..58f9e255927e 100644
--- a/net/netfilter/ipvs/ip_vs_proto_udp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_udp.c
@@ -444,7 +444,8 @@ static const char * udp_state_name(int state)
static void
udp_state_transition(struct ip_vs_conn *cp, int direction,
const struct sk_buff *skb,
- struct ip_vs_proto_data *pd)
+ struct ip_vs_proto_data *pd,
+ unsigned int iph_len)
{
if (unlikely(!pd)) {
pr_err("UDP no ns data\n");
--
2.54.0
^ permalink raw reply related
* [PATCH net 15/17] ipvs: use parsed transport offset in TCP state lookup
From: Florian Westphal @ 2026-07-08 14:03 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
TCP state handling reparses the skb to find the TCP header. For IPv6 it
uses sizeof(struct ipv6hdr), while the surrounding IPVS code already
parsed the packet with ip_vs_fill_iph_skb() and has the real
transport-header offset in iph.len.
This makes TCP state handling look at the wrong bytes when an IPv6
packet carries extension headers. Use the parsed transport offset passed
down from ip_vs_set_state() when reading the TCP header.
For IPv4 and for IPv6 packets without extension headers, the passed
offset matches the previous value.
Fixes: 0bbdd42b7efa6 ("IPVS: Extend protocol DNAT/SNAT and state handlers")
Link: https://lore.kernel.org/netdev/20260705125659.37744-1-zhaoyz24@mails.tsinghua.edu.cn/
Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
Reported-by: Ao Wang <wangao@seu.edu.cn>
Reported-by: Xuewei Feng <fengxw06@126.com>
Reported-by: Qi Li <qli01@tsinghua.edu.cn>
Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
Assisted-by: Claude Code:GLM-5.2
Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Acked-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipvs/ip_vs_proto_tcp.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c
index 2d3f6aeafe52..f86b763efcc4 100644
--- a/net/netfilter/ipvs/ip_vs_proto_tcp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c
@@ -584,13 +584,7 @@ tcp_state_transition(struct ip_vs_conn *cp, int direction,
{
struct tcphdr _tcph, *th;
-#ifdef CONFIG_IP_VS_IPV6
- int ihl = cp->af == AF_INET ? ip_hdrlen(skb) : sizeof(struct ipv6hdr);
-#else
- int ihl = ip_hdrlen(skb);
-#endif
-
- th = skb_header_pointer(skb, ihl, sizeof(_tcph), &_tcph);
+ th = skb_header_pointer(skb, iph_len, sizeof(_tcph), &_tcph);
if (th == NULL)
return;
--
2.54.0
^ permalink raw reply related
* [PATCH net 16/17] ipvs: use parsed transport offset in SCTP state lookup
From: Florian Westphal @ 2026-07-08 14:03 UTC (permalink / raw)
To: netdev
Cc: Paolo Abeni, David S. Miller, Eric Dumazet, Jakub Kicinski,
netfilter-devel, pablo
In-Reply-To: <20260708140309.19633-1-fw@strlen.de>
From: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
set_sctp_state() reads the SCTP chunk header again in order to drive the
IPVS SCTP state table. For IPv6 it computes the offset with
sizeof(struct ipv6hdr), while the surrounding IPVS code uses iph.len from
ip_vs_fill_iph_skb(), where ipv6_find_hdr() has already skipped
extension headers and found the real transport header.
This makes the state machine read from the wrong offset for IPv6 SCTP
packets that carry extension headers. For example, an INIT packet with an
8-byte destination options header can be scheduled correctly by
sctp_conn_schedule(), but set_sctp_state() reads the first byte of the
SCTP verification tag as a DATA chunk type. The connection then moves
from NONE to ESTABLISHED instead of INIT1, gets the longer established
timeout, and updates the active/inactive destination counters
incorrectly. This happens even though the SCTP handshake has not
completed.
Use the parsed transport offset passed down from ip_vs_set_state() for
the SCTP chunk-header lookup. For IPv4 and IPv6 packets without
extension headers this preserves the existing offset.
Fixes: 2906f66a5682 ("ipvs: SCTP Trasport Loadbalancing Support")
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/netdev/20260705123040.35755-1-zhaoyz24@mails.tsinghua.edu.cn/
Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
Reported-by: Ao Wang <wangao@seu.edu.cn>
Reported-by: Xuewei Feng <fengxw06@126.com>
Reported-by: Qi Li <qli01@tsinghua.edu.cn>
Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
Assisted-by: Claude Code:GLM-5.2
Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Acked-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Florian Westphal <fw@strlen.de>
---
net/netfilter/ipvs/ip_vs_proto_sctp.c | 15 +++++----------
1 file changed, 5 insertions(+), 10 deletions(-)
diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c
index 394367b7b388..c67317be17df 100644
--- a/net/netfilter/ipvs/ip_vs_proto_sctp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c
@@ -372,20 +372,15 @@ static const char *sctp_state_name(int state)
static inline void
set_sctp_state(struct ip_vs_proto_data *pd, struct ip_vs_conn *cp,
- int direction, const struct sk_buff *skb)
+ int direction, const struct sk_buff *skb,
+ unsigned int iph_len)
{
struct sctp_chunkhdr _sctpch, *sch;
unsigned char chunk_type;
int event, next_state;
- int ihl, cofs;
-
-#ifdef CONFIG_IP_VS_IPV6
- ihl = cp->af == AF_INET ? ip_hdrlen(skb) : sizeof(struct ipv6hdr);
-#else
- ihl = ip_hdrlen(skb);
-#endif
+ int cofs;
- cofs = ihl + sizeof(struct sctphdr);
+ cofs = iph_len + sizeof(struct sctphdr);
sch = skb_header_pointer(skb, cofs, sizeof(_sctpch), &_sctpch);
if (sch == NULL)
return;
@@ -472,7 +467,7 @@ sctp_state_transition(struct ip_vs_conn *cp, int direction,
unsigned int iph_len)
{
spin_lock_bh(&cp->lock);
- set_sctp_state(pd, cp, direction, skb);
+ set_sctp_state(pd, cp, direction, skb, iph_len);
spin_unlock_bh(&cp->lock);
}
--
2.54.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox