* [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory
@ 2026-10-14 12:38 Ali Firas
2026-10-14 12:38 ` [PATCH net-next v4 1/7] vxlan: vnifilter: validate the VXLAN_VNIFILTER_ENTRY nest Ali Firas
` (7 more replies)
0 siblings, 8 replies; 10+ messages in thread
From: Ali Firas @ 2026-10-14 12:38 UTC (permalink / raw)
To: netdev, idosch
Cc: kuba, pabeni, davem, edumazet, andrew+netdev, horms, razor, roopa,
shuah, linux-kselftest, linux-kernel, Ali Firas
The VNI filter interface accepts a START/END range with no bound on
either endpoint and no bound on how many VNIs one message may ask for,
and the memory it allocates per VNI is not charged to the caller's
cgroup.
Patch 1 links the VXLAN_VNIFILTER_ENTRY nest to its policy with
NLA_POLICY_NESTED(), as Jakub asked in v2:
https://lore.kernel.org/netdev/20260921150904.65a704eb@kernel.org/
Without it the entry attributes were validated only as each entry was
dispatched, so a multi-entry message whose later entry was invalid had
the earlier entries applied and notified before the message was
rejected. With the nest linked, an entry the policy rejects refuses the
message as a unit; faults the policy does not cover are still handled
one entry at a time, as before.
Patch 2 range-validates both endpoints to the 24-bit VNI space.
Patch 3 makes the dump re-enterable, and does so before patch 4 caps the
input, so no commit in the series reports a range its own input path
would reject. vxlan_vnifilter_dump_dev() coalesces a contiguous run of
VNIs, and packs as many entries into a netlink message as the skb holds,
with no bound on either. It introduces VXLAN_VNI_FILTER_MSG_MAX and
bounds both: it ends a coalesced run at the limit, and carries a
per-message VNI budget that ends the message once its entries would sum
past the limit, the same way the loop already ends it when the skb is
full. The same resume path (cb->args[1], counting nodes dumped) drives
both; the dump does not drop or repeat a VNI.
Patch 4 caps the number of VNIs one request may add or delete, summed
over its entries, at VXLAN_VNI_FILTER_MSG_MAX. Both add and delete walk
the span one VNI at a time under rtnl_lock, so both are bounded; that
walk is the cost, not the memory.
Patch 5 charges the per-VNI node and its per-CPU stats block to the
cgroup of the task that created the VNI, the way the device's own struct
and its Tx/Rx queues already are (commit c948f51c1654 ("memcg: enable
accounting for net_device and Tx/Rx queues")).
Patch 6 charges the default FDB entry a VNI with a group or remote
installs -- the vxlan_fdb, its vxlan_rdst and the per-CPU dst cache. It
threads a gfp argument through vxlan_fdb_update() down to
vxlan_fdb_alloc()/vxlan_fdb_append(), on both the create and the
remote-change paths, and a second gfp through
vxlan_update_default_fdb_entry() so each caller picks it. A per-request
add or change (RTM_NEWTUNNEL) passes GFP_NOWAIT | __GFP_ACCOUNT, which
is non-blocking like GFP_ATOMIC but hard-bounded by the memcg: at its
limit the request fails with -ENOMEM instead of charging past
memory.max, and because the new entry is allocated before the old is
freed, a failed add leaves the existing entry intact. The device-wide
default-remote change (RTM_NEWLINK, via vxlan_vnilist_update_group())
keeps GFP_ATOMIC | __GFP_ACCOUNT; that pre-existing walk is not bounded
here ("Not addressed" below). Every non-vnifilter caller --
vxlan_fdb_add(), vxlan_snoop(), vxlan_dev_create(), vxlan_changelink()
and the switchdev vxlan_fdb_external_learn_add() -- keeps plain
GFP_ATOMIC, so the data path and unrelated callers are untouched.
Patch 7 adds the selftests.
This is the reasoning of commit e2c8d550a973 ("netfilter: ebtables:
account ebt_table_info to kmemcg") and of the net_device accounting
above: memory whose lifetime is tied to the net namespace rather than to
the allocating task cannot be relieved by the OOM killer, so a
container's netadmin must not be able to grow it outside the memcg that
bounds the container.
What this series does and does not account, measured on production
builds (no lockdep/KASAN) in a memcg at 2 and 8 vCPU:
- every persistent per-VNI allocation the vnifilter path creates is
attributed to the cgroup of the task whose request allocates it, and
bounded by it on the per-request path. Patch 5's node (128 B) and
per-CPU stats (64 B/CPU), and patch 6's FDB entry -- vxlan_fdb
(128 B), vxlan_rdst (96 B) and a per-CPU dst cache -- on the
per-request path (RTM_NEWTUNNEL) are hard-bounded: the node and stats
use GFP_KERNEL_ACCOUNT and the FDB entry uses
GFP_NOWAIT | __GFP_ACCOUNT, so a cgroup at its limit fails the
create, the first remote, or a remote change with -ENOMEM, and
memory.current does not exceed memory.max (measured at 2 and 8 vCPU
in an 8 MiB cgroup). Failure is clean: the new entry is allocated
before the old is freed, so a failed add leaves the existing entry
intact and returns the error -- no established state is destroyed and
no false success is returned. The at-rest footprint is one node, its
stats and one FDB entry per VNI; this memory has device and net
namespace lifetime, not task lifetime (killing the cgroup's tasks did
not release it, only deleting the device did), so it must be charged
rather than left to the OOM killer;
- the rhashtable bucket tables backing the VNI and FDB hashes are not
charged. That is ~32 bytes per VNI across both tables, generic
infrastructure shared by every rhashtable user, O(entries) with
automatic shrinking (an add/delete cycle returns it, measured), and
the series does not address it.
The cap is symmetric, applied to add and delete alike, which is what Ido
asked for when he agreed to a 4k limit. Patch 3 is what makes that safe:
no dumped message carries more VNIs than one request may ask for, so a
device holding more than the limit can still be torn down by replaying
what "bridge vni show" reports. Device teardown itself
(vxlan_vnigroup_uninit()) is not a netlink message and is not subject to
the cap.
uAPI changes, all of them:
- a VNI at or above 2^24, previously accepted and stored (and
truncated on the wire), is now rejected with -ERANGE;
- a single request asking to add or delete more than 4096 VNIs, summed
over its entries, is now rejected with -EINVAL -- "bridge vni add
dev X vni 1-10000" used to be accepted;
- a request rejected by the entry policy, aimed at a missing or
non-vnifilter device, now returns that policy error rather than
-ENODEV or -EOPNOTSUPP, because the nest is validated before the
device is resolved (patch 1);
- "bridge vni show" now splits a contiguous run longer than the limit
into several entries, and across several messages, instead of one;
the set of VNIs it reports is unchanged;
- in a cgroup at its memory limit, adding a VNI, or adding or changing
a VNI's remote (RTM_NEWTUNNEL), now returns -ENOMEM instead of
charging past the limit; the existing entry, if any, is left intact.
This is a policy and hardening change, not a fix for a crash or
corruption; it targets net-next and should not be backported.
One pre-existing semantic is worth review: an entry that carries only
END and no START is treated as the range [0, END], so a lone END near
the top of the space is a whole-space request that the cap now rejects;
whether END-without-START should mean that is left as an open question.
Not addressed here:
- the device-wide default-remote change (RTM_NEWLINK) reaches the same
per-VNI FDB entries outside the request cap and force-charges them
rather than hard-bounding, so this series narrows the exposure to
the per-request route rather than closing it. Patch 6 does charge
those entries to the requesting task's cgroup, where before the
series they grew unaccounted host memory; bounding the walk is left
to a follow-up.
- vxlan_vni_add_del() still leaves the earlier VNIs of a range
installed if an allocation fails partway through it; with the cap
that is now bounded to fewer than 4096 VNIs. A fix needs a
transaction and is left to a follow-up.
- the rhashtable bucket memory described above.
Changes since v3:
- rebased onto current net-next.
- the dump patch (now patch 3) is ordered before the input cap (patch
4) and introduces VXLAN_VNI_FILTER_MSG_MAX, so every commit is
self-consistent; it also carries a per-message VNI budget, not only a
per-entry clamp, so a dump of a run longer than the limit splits into
messages the input path accepts (demonstrated with raw netlink).
- new patch 6 accounts the default FDB entry, scoped to the vnifilter
call site via a gfp argument so the data path stays on GFP_ATOMIC;
the gfp is threaded on both the create and the remote-change paths.
The per-request path (RTM_NEWTUNNEL) uses GFP_NOWAIT | __GFP_ACCOUNT
so the charge is hard-bounded (fails with -ENOMEM at the cgroup
limit); the pre-existing device-default walk (RTM_NEWLINK) keeps
GFP_ATOMIC | __GFP_ACCOUNT.
- patch 5 reworded to cover the node and stats only, with its
net_device-accounting reference narrowed to net_device and its Tx/Rx
queues.
- patch 1 reworded to scope the "rejected as a unit" claim to entries
the policy rejects.
Changes since v2:
- add patch 1 linking the nest to its policy (Jakub);
- the request cap applies to both add and delete (Ido).
Ali Firas (7):
vxlan: vnifilter: validate the VXLAN_VNIFILTER_ENTRY nest
vxlan: vnifilter: reject VNIs outside the 24-bit space
vxlan: vnifilter: clamp the dumped VNI range to the request limit
vxlan: vnifilter: bound the number of VNIs one request may touch
vxlan: vnifilter: account per-VNI memory to memcg
vxlan: vnifilter: account the default FDB entry to memcg
selftests: net: test the vxlan vnifilter request limit and dump replay
drivers/net/vxlan/vxlan_core.c | 47 +++---
drivers/net/vxlan/vxlan_private.h | 5 +-
drivers/net/vxlan/vxlan_vnifilter.c | 132 +++++++++++++++---
.../selftests/net/test_vxlan_vnifiltering.sh | 119 +++++++++++++++-
4 files changed, 270 insertions(+), 46 deletions(-)
base-commit: 05b5019b01fb9443b09ae4ed34af1135a80a84c8
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH net-next v4 1/7] vxlan: vnifilter: validate the VXLAN_VNIFILTER_ENTRY nest 2026-10-14 12:38 [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory Ali Firas @ 2026-10-14 12:38 ` Ali Firas 2026-10-14 12:38 ` [PATCH net-next v4 2/7] vxlan: vnifilter: reject VNIs outside the 24-bit space Ali Firas ` (6 subsequent siblings) 7 siblings, 0 replies; 10+ messages in thread From: Ali Firas @ 2026-10-14 12:38 UTC (permalink / raw) To: netdev, idosch Cc: kuba, pabeni, davem, edumazet, andrew+netdev, horms, razor, roopa, shuah, linux-kselftest, linux-kernel, Ali Firas vxlan_vnifilter_process() parses the message with vni_filter_policy, whose VXLAN_VNIFILTER_ENTRY is a bare NLA_NESTED with no nested policy attached. The entry attributes are therefore validated only later, one entry at a time, by the nla_parse_nested() inside vxlan_process_vni_filter(). Entries are parsed as they are dispatched: in a message whose first entry is valid and whose second is not, the first entry is applied and its RTM_NEWTUNNEL notification sent before the second is rejected. Link the nest to vni_filter_entry_policy with NLA_POLICY_NESTED() so every entry is checked against the entry policy up front, before any entry is acted on. A message whose entry the policy rejects -- a malformed attribute, or, with the next patch, an out-of-range endpoint -- is now refused as a unit and installs nothing. Faults the policy does not cover, such as a device or handler error raised on a later entry, are unchanged: entries are still dispatched and applied one at a time. This reorders two faults. The nest is validated before the device is looked up and before the vnifilter flag is checked, so a request rejected by the entry policy, aimed at a missing or non-vnifilter device, now returns that policy error where it previously returned -ENODEV or -EOPNOTSUPP. The request was invalid either way; only the errno an operator sees changes. The nla_parse_nested() in vxlan_process_vni_filter() stays: it is what fills the per-entry attribute table the handler reads. It can no longer fail on a message that has reached it. Suggested-by: Jakub Kicinski <kuba@kernel.org> Link: https://lore.kernel.org/netdev/20260921150904.65a704eb@kernel.org/ Assisted-by: LLM Signed-off-by: Ali Firas <alishmery18@gmail.com> --- v4: scope the "rejected as a unit" claim to policy-rejected entries; rebased onto net-next. drivers/net/vxlan/vxlan_vnifilter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c index 41095096b698..124ba47758e9 100644 --- a/drivers/net/vxlan/vxlan_vnifilter.c +++ b/drivers/net/vxlan/vxlan_vnifilter.c @@ -499,7 +499,7 @@ static const struct nla_policy vni_filter_entry_policy[VXLAN_VNIFILTER_ENTRY_MAX }; static const struct nla_policy vni_filter_policy[VXLAN_VNIFILTER_MAX + 1] = { - [VXLAN_VNIFILTER_ENTRY] = { .type = NLA_NESTED }, + [VXLAN_VNIFILTER_ENTRY] = NLA_POLICY_NESTED(vni_filter_entry_policy), }; bool vxlan_vnifilter_has_multicast(const struct vxlan_dev *vxlan) base-commit: 05b5019b01fb9443b09ae4ed34af1135a80a84c8 -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v4 2/7] vxlan: vnifilter: reject VNIs outside the 24-bit space 2026-10-14 12:38 [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory Ali Firas 2026-10-14 12:38 ` [PATCH net-next v4 1/7] vxlan: vnifilter: validate the VXLAN_VNIFILTER_ENTRY nest Ali Firas @ 2026-10-14 12:38 ` Ali Firas 2026-10-14 12:38 ` [PATCH net-next v4 3/7] vxlan: vnifilter: clamp the dumped VNI range to the request limit Ali Firas ` (5 subsequent siblings) 7 siblings, 0 replies; 10+ messages in thread From: Ali Firas @ 2026-10-14 12:38 UTC (permalink / raw) To: netdev, idosch Cc: kuba, pabeni, davem, edumazet, andrew+netdev, horms, razor, roopa, shuah, linux-kselftest, linux-kernel, Ali Firas VXLAN_VNIFILTER_ENTRY_START and VXLAN_VNIFILTER_ENTRY_END are bare NLA_U32, so neither is bounded before vxlan_process_vni_filter() hands them to vxlan_vni_add_del(): int v, err = 0; ... for (v = start_vni; v <= end_vni; v++) v is int and end_vni is __u32, so the comparison is done unsigned. A request carrying only START=0xffffffff has vni_start == vni_end == 0xffffffff and looks like a single VNI; v is then -1, the comparison promotes it to 0xffffffff and passes, v++ makes v 0, and the loop walks the space upwards from there, creating a VNI node and a per-CPU stats block per iteration under rtnl_lock. Any range ending at 0xffffffff behaves the same way. Separately, a VNI at or above VXLAN_N_VID is accepted and stored even though the VXLAN header carries only 24 bits: vxlan_vni_field() shifts without masking, so such an entry keeps its own rhashtable slot while being truncated on the wire. Range-validate both attributes against the 24-bit space, as vxlan_mdb.c already does for its own VNI attributes. With the nest now linked to this policy, an out-of-range endpoint is rejected during netlink policy validation, before vxlan_process_vni_filter() runs and before anything is allocated. How many VNIs a single in-range request may span is a separate question, bounded by the next patch; that limit is not a policy check and does run in the handler. Make the loop counter u32 as well. With the range bounded it is no longer what keeps the loop finite, but it drops the undefined signed overflow past INT_MAX and matches the u32 vni that vxlan_vni_add() and vxlan_vni_del() already take. Assisted-by: LLM Signed-off-by: Ali Firas <alishmery18@gmail.com> --- v4: no functional change; rebased onto net-next. drivers/net/vxlan/vxlan_vnifilter.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c index 124ba47758e9..9e15f4de75c9 100644 --- a/drivers/net/vxlan/vxlan_vnifilter.c +++ b/drivers/net/vxlan/vxlan_vnifilter.c @@ -491,9 +491,15 @@ static int vxlan_vnifilter_dump(struct sk_buff *skb, struct netlink_callback *cb return err; } +static const struct netlink_range_validation vni_filter_vni_range = { + .max = VXLAN_N_VID - 1, +}; + static const struct nla_policy vni_filter_entry_policy[VXLAN_VNIFILTER_ENTRY_MAX + 1] = { - [VXLAN_VNIFILTER_ENTRY_START] = { .type = NLA_U32 }, - [VXLAN_VNIFILTER_ENTRY_END] = { .type = NLA_U32 }, + [VXLAN_VNIFILTER_ENTRY_START] = NLA_POLICY_FULL_RANGE(NLA_U32, + &vni_filter_vni_range), + [VXLAN_VNIFILTER_ENTRY_END] = NLA_POLICY_FULL_RANGE(NLA_U32, + &vni_filter_vni_range), [VXLAN_VNIFILTER_ENTRY_GROUP] = NLA_POLICY_EXACT_LEN(sizeof_field(struct iphdr, daddr)), [VXLAN_VNIFILTER_ENTRY_GROUP6] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), }; @@ -914,7 +920,8 @@ static int vxlan_vni_add_del(struct vxlan_dev *vxlan, __u32 start_vni, int cmd, struct netlink_ext_ack *extack) { struct vxlan_vni_group *vg; - int v, err = 0; + int err = 0; + u32 v; vg = rtnl_dereference(vxlan->vnigrp); -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v4 3/7] vxlan: vnifilter: clamp the dumped VNI range to the request limit 2026-10-14 12:38 [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory Ali Firas 2026-10-14 12:38 ` [PATCH net-next v4 1/7] vxlan: vnifilter: validate the VXLAN_VNIFILTER_ENTRY nest Ali Firas 2026-10-14 12:38 ` [PATCH net-next v4 2/7] vxlan: vnifilter: reject VNIs outside the 24-bit space Ali Firas @ 2026-10-14 12:38 ` Ali Firas 2026-10-14 12:38 ` [PATCH net-next v4 4/7] vxlan: vnifilter: bound the number of VNIs one request may touch Ali Firas ` (4 subsequent siblings) 7 siblings, 0 replies; 10+ messages in thread From: Ali Firas @ 2026-10-14 12:38 UTC (permalink / raw) To: netdev, idosch Cc: kuba, pabeni, davem, edumazet, andrew+netdev, horms, razor, roopa, shuah, linux-kselftest, linux-kernel, Ali Firas vxlan_vnifilter_dump_dev() coalesces a contiguous run of VNIs sharing a remote into one VXLAN_VNIFILTER_ENTRY, and packs as many entries into a netlink message as the skb holds, with no bound on either. A later patch bounds a single request to VXLAN_VNI_FILTER_MSG_MAX VNIs summed over its entries; without a matching bound on the dump, a device holding more than that -- populated by several requests -- would dump a run, or a message, that the input path then refuses on replay. Introduce VXLAN_VNI_FILTER_MSG_MAX here and clamp the dump to it first, so the series never passes through a commit where the kernel reports a range its own input path rejects. Bound the two ways the dump can exceed the limit: - end a coalesced run once it reaches the limit, in the merge condition, even when the next VNI is contiguous and shares the remote; and - carry a per-message VNI budget, ending the message once its entries would sum past the limit, the same way the loop already ends it when the skb is full. cb->args[1] continues to count the VNI nodes already dumped and is advanced only when a completed entry is written, so the budget break and the skb-full break share one resume path; a run stays a gapless block, so the dump never drops a VNI and never emits one twice. Assisted-by: LLM Signed-off-by: Ali Firas <alishmery18@gmail.com> --- v4: moved before the input cap; now introduces VXLAN_VNI_FILTER_MSG_MAX and adds a per-message VNI budget (v3 clamped per entry only), so the dump never emits a message the input path rejects. drivers/net/vxlan/vxlan_vnifilter.c | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c index 9e15f4de75c9..5beec29eb84a 100644 --- a/drivers/net/vxlan/vxlan_vnifilter.c +++ b/drivers/net/vxlan/vxlan_vnifilter.c @@ -17,6 +17,13 @@ #include "vxlan_private.h" +/* The dump coalesces a contiguous run of VNIs into entries, and packs entries + * into netlink messages, of at most this many VNIs each, so every message the + * dump emits stays within what the input path will accept on replay. A later + * patch bounds a single RTM_NEWTUNNEL/RTM_DELTUNNEL request to the same number. + */ +#define VXLAN_VNI_FILTER_MSG_MAX 4096 + static inline int vxlan_vni_cmp(struct rhashtable_compare_arg *arg, const void *ptr) { @@ -342,6 +349,7 @@ static int vxlan_vnifilter_dump_dev(const struct net_device *dev, struct nlmsghdr *nlh; int idx = 0, s_idx; bool dump_stats; + u32 vnis = 0; int err = 0; if (cb->args[2] != dev->ifindex) { @@ -388,15 +396,29 @@ static int vxlan_vnifilter_dump_dev(const struct net_device *dev, continue; } if (!dump_stats && vnirange(vend, v) == 1 && + vnirange(vbegin, v) < VXLAN_VNI_FILTER_MSG_MAX && vxlan_addr_equal(&v->remote_ip, &vend->remote_ip)) { goto update_end; } else { + u32 sz = vnirange(vbegin, vend) + 1; + + /* End this message on its VNI budget the same way we + * end it when the skb is full, so a dumped message + * never sums to more VNIs than the input path accepts + * in one request. Resume re-emits this run as the + * first entry of the next message. + */ + if (vnis + sz > VXLAN_VNI_FILTER_MSG_MAX) { + err = -EMSGSIZE; + break; + } if (!vxlan_fill_vni_filter_entry(skb, vbegin, vend, dump_stats)) { err = -EMSGSIZE; break; } - idx += vnirange(vbegin, vend) + 1; + vnis += sz; + idx += sz; vbegin = v; } update_end: @@ -404,7 +426,10 @@ static int vxlan_vnifilter_dump_dev(const struct net_device *dev, } if (!err && vbegin) { - if (!vxlan_fill_vni_filter_entry(skb, vbegin, vend, dump_stats)) + if (vnis + vnirange(vbegin, vend) + 1 > VXLAN_VNI_FILTER_MSG_MAX) + err = -EMSGSIZE; + else if (!vxlan_fill_vni_filter_entry(skb, vbegin, vend, + dump_stats)) err = -EMSGSIZE; } -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v4 4/7] vxlan: vnifilter: bound the number of VNIs one request may touch 2026-10-14 12:38 [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory Ali Firas ` (2 preceding siblings ...) 2026-10-14 12:38 ` [PATCH net-next v4 3/7] vxlan: vnifilter: clamp the dumped VNI range to the request limit Ali Firas @ 2026-10-14 12:38 ` Ali Firas 2026-10-14 12:38 ` [PATCH net-next v4 5/7] vxlan: vnifilter: account per-VNI memory to memcg Ali Firas ` (3 subsequent siblings) 7 siblings, 0 replies; 10+ messages in thread From: Ali Firas @ 2026-10-14 12:38 UTC (permalink / raw) To: netdev, idosch Cc: kuba, pabeni, davem, edumazet, andrew+netdev, horms, razor, roopa, shuah, linux-kselftest, linux-kernel, Ali Firas A single RTM_NEWTUNNEL or RTM_DELTUNNEL message can ask for the whole 24-bit space in one range. vxlan_vni_add_del() then walks it one VNI at a time under rtnl_lock, holding the lock for the length of the walk. That walk is the cost being bounded, not the memory: an add allocates a VNI node and a per-CPU stats block per VNI, a delete creates neither, but both walk the same span and hold rtnl the same way, so both are bounded. The span of one VXLAN_VNIFILTER_ENTRY is not the quantity to bound. An entry carrying just START and END is 20 bytes on the wire and a message may carry many of them, vxlan_process_vni_filter() being called once per entry, so bounding each entry alone would still let one message ask for thousands of times the limit. Sum the spans of every entry and reject the message as a whole in vxlan_vnifilter_check_msg(), before the dispatch loop: entries are applied and notified one at a time, so a limit enforced during dispatch would return an error only after every preceding entry had been acted on. The range extraction is factored into vxlan_vni_filter_entry_range() so the count and the range later acted on cannot drift. The limit, VXLAN_VNI_FILTER_MSG_MAX (4096), is the one the preceding patch introduced for the dump; it follows the usable VLAN ID space, since vnifilter is mainly used on bridged devices where the VNI is derived from the VLAN. It bounds one request, not how many VNIs a device may hold: the whole space can still be installed in more than one request, and a device holding it is still torn down in one step by vxlan_vnigroup_uninit(), which is device teardown, not a netlink message, and is not subject to this cap. This is a policy narrowing of what a single message may ask for, not a fix for a crash or corruption, and is not a backport candidate. Suggested-by: Ido Schimmel <idosch@nvidia.com> Assisted-by: LLM Signed-off-by: Ali Firas <alishmery18@gmail.com> --- v4: use the constant the dump patch introduces; rebased. drivers/net/vxlan/vxlan_vnifilter.c | 80 ++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 8 deletions(-) diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c index 5beec29eb84a..2999dd536625 100644 --- a/drivers/net/vxlan/vxlan_vnifilter.c +++ b/drivers/net/vxlan/vxlan_vnifilter.c @@ -971,13 +971,79 @@ static int vxlan_vni_add_del(struct vxlan_dev *vxlan, __u32 start_vni, return err; } +/* Derive the VNI range one VXLAN_VNIFILTER_ENTRY selects. Shared so that the + * count taken by vxlan_vnifilter_check_msg() cannot drift from the range + * vxlan_process_vni_filter() then acts on. + */ +static void vxlan_vni_filter_entry_range(struct nlattr **vattrs, u32 *vni_start, + u32 *vni_end) +{ + *vni_start = 0; + *vni_end = 0; + + if (vattrs[VXLAN_VNIFILTER_ENTRY_START]) { + *vni_start = nla_get_u32(vattrs[VXLAN_VNIFILTER_ENTRY_START]); + *vni_end = *vni_start; + } + + if (vattrs[VXLAN_VNIFILTER_ENTRY_END]) + *vni_end = nla_get_u32(vattrs[VXLAN_VNIFILTER_ENTRY_END]); +} + +/* Reject a request touching more than VXLAN_VNI_FILTER_MSG_MAX VNIs before any + * of its entries is acted on. Both add and delete walk the span one VNI at a + * time under rtnl_lock, so both are bounded. Entries are applied one at a time + * and each one notifies as it goes, so a limit checked inside the dispatch loop + * would leave the entries ahead of the offending one already applied. + */ +static int vxlan_vnifilter_check_msg(const struct nlmsghdr *nlh, + struct netlink_ext_ack *extack) +{ + struct nlattr *vattrs[VXLAN_VNIFILTER_ENTRY_MAX + 1]; + struct nlattr *attr; + u32 vnis = 0; + int err, rem; + + nlmsg_for_each_attr_type(attr, VXLAN_VNIFILTER_ENTRY, nlh, + sizeof(struct tunnel_msg), rem) { + u32 vni_start, vni_end; + + err = nla_parse_nested(vattrs, VXLAN_VNIFILTER_ENTRY_MAX, attr, + vni_filter_entry_policy, extack); + if (err) + return err; + + vxlan_vni_filter_entry_range(vattrs, &vni_start, &vni_end); + + /* A start above the end selects no VNI and costs nothing; + * leave it behaving as it does today. + */ + if (vni_end < vni_start) + continue; + + /* vni_filter_entry_policy has already bounded both endpoints + * below VXLAN_N_VID, so one entry adds at most VXLAN_N_VID and + * vnis cannot wrap before the test below rejects it. + */ + vnis += vni_end - vni_start + 1; + if (vnis > VXLAN_VNI_FILTER_MSG_MAX) { + NL_SET_ERR_MSG_ATTR_FMT(extack, attr, + "Request asks for more than %u VNIs", + VXLAN_VNI_FILTER_MSG_MAX); + return -EINVAL; + } + } + + return 0; +} + static int vxlan_process_vni_filter(struct vxlan_dev *vxlan, struct nlattr *nlvnifilter, int cmd, struct netlink_ext_ack *extack) { struct nlattr *vattrs[VXLAN_VNIFILTER_ENTRY_MAX + 1]; const struct vxlan_config *cfg; - u32 vni_start = 0, vni_end = 0; + u32 vni_start, vni_end; union vxlan_addr group; int err; @@ -988,13 +1054,7 @@ static int vxlan_process_vni_filter(struct vxlan_dev *vxlan, if (err) return err; - if (vattrs[VXLAN_VNIFILTER_ENTRY_START]) { - vni_start = nla_get_u32(vattrs[VXLAN_VNIFILTER_ENTRY_START]); - vni_end = vni_start; - } - - if (vattrs[VXLAN_VNIFILTER_ENTRY_END]) - vni_end = nla_get_u32(vattrs[VXLAN_VNIFILTER_ENTRY_END]); + vxlan_vni_filter_entry_range(vattrs, &vni_start, &vni_end); if (!vni_start && !vni_end) { NL_SET_ERR_MSG_ATTR(extack, nlvnifilter, @@ -1104,6 +1164,10 @@ static int vxlan_vnifilter_process(struct sk_buff *skb, struct nlmsghdr *nlh, if (!(cfg->flags & VXLAN_F_VNIFILTER)) return -EOPNOTSUPP; + err = vxlan_vnifilter_check_msg(nlh, extack); + if (err) + return err; + nlmsg_for_each_attr_type(attr, VXLAN_VNIFILTER_ENTRY, nlh, sizeof(*tmsg), rem) { err = vxlan_process_vni_filter(vxlan, attr, nlh->nlmsg_type, -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v4 5/7] vxlan: vnifilter: account per-VNI memory to memcg 2026-10-14 12:38 [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory Ali Firas ` (3 preceding siblings ...) 2026-10-14 12:38 ` [PATCH net-next v4 4/7] vxlan: vnifilter: bound the number of VNIs one request may touch Ali Firas @ 2026-10-14 12:38 ` Ali Firas 2026-10-14 12:38 ` [PATCH net-next v4 6/7] vxlan: vnifilter: account the default FDB entry " Ali Firas ` (2 subsequent siblings) 7 siblings, 0 replies; 10+ messages in thread From: Ali Firas @ 2026-10-14 12:38 UTC (permalink / raw) To: netdev, idosch Cc: kuba, pabeni, davem, edumazet, andrew+netdev, horms, razor, roopa, shuah, linux-kselftest, linux-kernel, Ali Firas vxlan_vni_alloc() allocates a struct vxlan_vni_node and a per-CPU stats block for every VNI, both with plain GFP_KERNEL, so neither is charged to the cgroup of the process that created the VNI. A netdevice's own struct and its Tx/Rx queues are already GFP_KERNEL_ACCOUNT in alloc_netdev_mqs() (commit c948f51c1654 ("memcg: enable accounting for net_device and Tx/Rx queues")), so these per-VNI allocations are an unaccounted part of a device that a cgroup can grow one VNI at a time. Charge both to the caller's cgroup with GFP_KERNEL_ACCOUNT in vxlan_vni_alloc(). __GFP_ACCOUNT only charges an allocation made from a task in a non-root memcg, so the sole caller, which already handles a NULL return, is unaffected outside a memcg. The charge lands in the caller's memcg: adding VNIs from inside one moves each node into the accounted slab and raises the cgroup's per-CPU memory. This covers the node and its stats only; a VNI carrying a group or remote also installs a default FDB entry, whose allocations are not charged here and are accounted by the next patch. Assisted-by: LLM Signed-off-by: Ali Firas <alishmery18@gmail.com> --- v4: reworded to cover the node and stats only; the c948f51c1654 reference narrowed to net_device and its Tx/Rx queues; rebased. drivers/net/vxlan/vxlan_vnifilter.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c index 2999dd536625..4a631fcdf23a 100644 --- a/drivers/net/vxlan/vxlan_vnifilter.c +++ b/drivers/net/vxlan/vxlan_vnifilter.c @@ -825,10 +825,11 @@ static struct vxlan_vni_node *vxlan_vni_alloc(struct vxlan_dev *vxlan, { struct vxlan_vni_node *vninode; - vninode = kzalloc_obj(*vninode); + vninode = kzalloc_obj(*vninode, GFP_KERNEL_ACCOUNT); if (!vninode) return NULL; - vninode->stats = netdev_alloc_pcpu_stats(struct vxlan_vni_stats_pcpu); + vninode->stats = __netdev_alloc_pcpu_stats(struct vxlan_vni_stats_pcpu, + GFP_KERNEL_ACCOUNT); if (!vninode->stats) { kfree(vninode); return NULL; -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v4 6/7] vxlan: vnifilter: account the default FDB entry to memcg 2026-10-14 12:38 [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory Ali Firas ` (4 preceding siblings ...) 2026-10-14 12:38 ` [PATCH net-next v4 5/7] vxlan: vnifilter: account per-VNI memory to memcg Ali Firas @ 2026-10-14 12:38 ` Ali Firas 2026-10-14 12:38 ` [PATCH net-next v4 7/7] selftests: net: test the vxlan vnifilter request limit and dump replay Ali Firas 2026-10-14 12:45 ` [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory netdev-bot+sinfo 7 siblings, 0 replies; 10+ messages in thread From: Ali Firas @ 2026-10-14 12:38 UTC (permalink / raw) To: netdev, idosch Cc: kuba, pabeni, davem, edumazet, andrew+netdev, horms, razor, roopa, shuah, linux-kselftest, linux-kernel, Ali Firas A VNI added through the vnifilter interface with a group or remote installs a default FDB entry for that VNI: vxlan_vni_update_group() calls vxlan_update_default_fdb_entry(), which calls vxlan_fdb_update() and vxlan_fdb_create(). The entry allocates a struct vxlan_fdb and, per remote, a struct vxlan_rdst plus its per-CPU dst cache, all with GFP_ATOMIC and none charged to a memory cgroup. The previous patch accounts the per-VNI node and its stats; these FDB objects are the rest of what one vnifilter request allocates per VNI. Charge them to the caller, as commit c948f51c1654 ("memcg: enable accounting for net_device and Tx/Rx queues") and commit e2c8d550a973 ("netfilter: ebtables: account ebt_table_info to kmemcg") do: the lifetime of this memory is the net namespace, not the allocating task, so the OOM killer cannot reclaim it and it must be charged to the cgroup of the task whose request allocates it. Thread a gfp argument through vxlan_fdb_update() to vxlan_fdb_alloc()/vxlan_fdb_append() on both paths it can take -- the create path (vxlan_fdb_update_create() -> vxlan_fdb_create()) and the path that changes an existing entry's remote (vxlan_fdb_update_existing()) -- and a second gfp through vxlan_update_default_fdb_entry(), so each caller chooses it. Every non-vnifilter caller of vxlan_fdb_update() -- vxlan_fdb_add(), vxlan_snoop(), vxlan_dev_create(), vxlan_changelink() and the switchdev vxlan_fdb_external_learn_add() -- keeps plain GFP_ATOMIC, so the data path and unrelated callers are unchanged. dst_cache_init() already takes a gfp argument, so no change outside the driver is needed. A per-request add or change of a VNI's remote (RTM_NEWTUNNEL, via vxlan_vni_update_group()) passes GFP_NOWAIT | __GFP_ACCOUNT. These allocations run under the hash spinlock and cannot sleep, but a configuration path does not need the atomic reserves GFP_ATOMIC grants, and GFP_NOWAIT makes the memcg charge hard-bounded rather than force-charged: a cgroup at its limit fails the add or change there with -ENOMEM instead of pushing memory.current past memory.max. The failure is clean -- the new entry is allocated before the old is freed, so an add that fails leaves the existing entry intact and returns the error; no established FDB state is destroyed and no false success is returned. Creating a VNI is gated the same way by the previous patch's node and stats (GFP_KERNEL_ACCOUNT, hard-bounded). Measured on production builds at 2 and 8 vCPU in an 8 MiB cgroup: over-limit adds and changes return -ENOMEM and memory.current does not exceed memory.max. The device-wide default-remote change (RTM_NEWLINK, via vxlan_vnilist_update_group()) keeps GFP_ATOMIC | __GFP_ACCOUNT. That walk touches every VNI inheriting the device default in one message, outside the per-request cap, and its existing error unwind re-installs entries; hard-bounding it would turn a cgroup limit into a mid-walk failure that the unwind cannot cleanly reverse. Bounding and unwinding that pre-existing walk is left to a separate change; here it stays force-charged, as before this series, and is charged to the task issuing the RTM_NEWLINK. This adds no new failure path on the data or learning paths: their allocations already existed and keep GFP_ATOMIC; only the vnifilter configuration path gains an -ENOMEM return, which its callers already propagate. Assisted-by: LLM Signed-off-by: Ali Firas <alishmery18@gmail.com> --- v4: new patch. drivers/net/vxlan/vxlan_core.c | 47 ++++++++++++++++------------- drivers/net/vxlan/vxlan_private.h | 7 +++-- drivers/net/vxlan/vxlan_vnifilter.c | 14 ++++++--- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 278d41f58d89..7991b544af5a 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -577,7 +577,7 @@ static int vxlan_fdb_replace(struct vxlan_fdb *f, /* Add/update destinations for multicast */ static int vxlan_fdb_append(struct vxlan_fdb *f, union vxlan_addr *ip, __be16 port, __be32 vni, - __u32 ifindex, struct vxlan_rdst **rdp) + __u32 ifindex, struct vxlan_rdst **rdp, gfp_t gfp) { struct vxlan_rdst *rd; @@ -585,14 +585,14 @@ static int vxlan_fdb_append(struct vxlan_fdb *f, if (rd) return 0; - rd = kmalloc_obj(*rd, GFP_ATOMIC); + rd = kmalloc_obj(*rd, gfp); if (rd == NULL) return -ENOMEM; /* The driver can work correctly without a dst cache, so do not treat * dst cache initialization errors as fatal. */ - dst_cache_init(&rd->dst_cache, GFP_ATOMIC | __GFP_NOWARN); + dst_cache_init(&rd->dst_cache, gfp | __GFP_NOWARN); rd->remote_ip = *ip; rd->remote_port = port; @@ -779,11 +779,11 @@ static int vxlan_gpe_gro_complete(struct sock *sk, struct sk_buff *skb, int nhof static struct vxlan_fdb *vxlan_fdb_alloc(struct vxlan_dev *vxlan, const u8 *mac, __u16 state, __be32 src_vni, - __u16 ndm_flags) + __u16 ndm_flags, gfp_t gfp) { struct vxlan_fdb *f; - f = kmalloc_obj(*f, GFP_ATOMIC); + f = kmalloc_obj(*f, gfp); if (!f) return NULL; memset(&f->key, 0, sizeof(f->key)); @@ -869,7 +869,7 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, __u16 state, __be16 port, __be32 src_vni, __be32 vni, __u32 ifindex, __u16 ndm_flags, u32 nhid, struct vxlan_fdb **fdb, - struct netlink_ext_ack *extack) + struct netlink_ext_ack *extack, gfp_t gfp) { const struct vxlan_config *cfg = rcu_dereference_rtnl(vxlan->cfg); struct vxlan_rdst *rd = NULL; @@ -881,14 +881,14 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, return -ENOSPC; netdev_dbg(vxlan->dev, "add %pM -> %pIS\n", mac, ip); - f = vxlan_fdb_alloc(vxlan, mac, state, src_vni, ndm_flags); + f = vxlan_fdb_alloc(vxlan, mac, state, src_vni, ndm_flags, gfp); if (!f) return -ENOMEM; if (nhid) rc = vxlan_fdb_nh_update(vxlan, f, nhid, extack); else - rc = vxlan_fdb_append(f, ip, port, vni, ifindex, &rd); + rc = vxlan_fdb_append(f, ip, port, vni, ifindex, &rd, gfp); if (rc < 0) goto errout; @@ -984,7 +984,7 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, __u32 ifindex, __u16 ndm_flags, struct vxlan_fdb *f, u32 nhid, bool swdev_notify, - struct netlink_ext_ack *extack) + struct netlink_ext_ack *extack, gfp_t gfp) { __u16 fdb_flags = (ndm_flags & ~NTF_USE); struct vxlan_rdst *rd = NULL; @@ -1047,7 +1047,8 @@ static int vxlan_fdb_update_existing(struct vxlan_dev *vxlan, if ((flags & NLM_F_APPEND) && (is_multicast_ether_addr(f->key.eth_addr) || is_zero_ether_addr(f->key.eth_addr))) { - rc = vxlan_fdb_append(f, ip, port, vni, ifindex, &rd); + rc = vxlan_fdb_append(f, ip, port, vni, ifindex, &rd, + gfp); if (rc < 0) return rc; @@ -1088,7 +1089,7 @@ static int vxlan_fdb_update_create(struct vxlan_dev *vxlan, __be16 port, __be32 src_vni, __be32 vni, __u32 ifindex, __u16 ndm_flags, u32 nhid, bool swdev_notify, - struct netlink_ext_ack *extack) + struct netlink_ext_ack *extack, gfp_t gfp) { __u16 fdb_flags = (ndm_flags & ~NTF_USE); struct vxlan_fdb *f; @@ -1101,7 +1102,7 @@ static int vxlan_fdb_update_create(struct vxlan_dev *vxlan, netdev_dbg(vxlan->dev, "add %pM -> %pIS\n", mac, ip); rc = vxlan_fdb_create(vxlan, mac, ip, state, port, src_vni, - vni, ifindex, fdb_flags, nhid, &f, extack); + vni, ifindex, fdb_flags, nhid, &f, extack, gfp); if (rc < 0) return rc; @@ -1124,7 +1125,7 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, __be16 port, __be32 src_vni, __be32 vni, __u32 ifindex, __u16 ndm_flags, u32 nhid, bool swdev_notify, - struct netlink_ext_ack *extack) + struct netlink_ext_ack *extack, gfp_t gfp) { struct vxlan_fdb *f; @@ -1138,7 +1139,8 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, return vxlan_fdb_update_existing(vxlan, ip, state, flags, port, vni, ifindex, ndm_flags, f, - nhid, swdev_notify, extack); + nhid, swdev_notify, extack, + gfp); } else { if (!(flags & NLM_F_CREATE)) return -ENOENT; @@ -1146,7 +1148,7 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, return vxlan_fdb_update_create(vxlan, mac, ip, state, flags, port, src_vni, vni, ifindex, ndm_flags, nhid, swdev_notify, - extack); + extack, gfp); } } @@ -1287,7 +1289,7 @@ static int vxlan_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], err = vxlan_fdb_update(vxlan, addr, &ip, ndm->ndm_state, flags, port, src_vni, vni, ifindex, ndm->ndm_flags | NTF_VXLAN_ADDED_BY_USER, - nhid, true, extack); + nhid, true, extack, GFP_ATOMIC); spin_unlock_bh(&vxlan->hash_lock); if (!err) @@ -1507,7 +1509,8 @@ static enum skb_drop_reason vxlan_snoop(struct net_device *dev, cfg->dst_port, vni, cfg->vni, - ifindex, NTF_SELF, 0, true, NULL); + ifindex, NTF_SELF, 0, true, NULL, + GFP_ATOMIC); spin_unlock(&vxlan->hash_lock); } @@ -4154,7 +4157,7 @@ static int vxlan_dev_create(struct net *net, struct net_device *dev, cfg->vni, cfg->vni, cfg->remote_ifindex, - NTF_SELF, 0, true, extack); + NTF_SELF, 0, true, extack, GFP_ATOMIC); spin_unlock_bh(&vxlan->hash_lock); if (err) goto unlink; @@ -4617,7 +4620,8 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[], cfg->dst_port, conf.vni, conf.vni, conf.remote_ifindex, - NTF_SELF, 0, true, extack); + NTF_SELF, 0, true, extack, + GFP_ATOMIC); if (err) { spin_unlock_bh(&vxlan->hash_lock); if (lowerdev_changed) @@ -4652,7 +4656,8 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[], &cfg->remote_ip, conf.remote_ifindex, cfg->remote_ifindex, - NULL); + NULL, + GFP_ATOMIC | __GFP_ACCOUNT); if (lowerdev_changed) netdev_adjacent_change_abort(vxlan->lowerdev, lowerdev, dev); @@ -4972,7 +4977,7 @@ vxlan_fdb_external_learn_add(struct net_device *dev, fdb_info->remote_vni, fdb_info->remote_ifindex, NTF_USE | NTF_SELF | NTF_EXT_LEARNED, - 0, false, extack); + 0, false, extack, GFP_ATOMIC); spin_unlock_bh(&vxlan->hash_lock); return err; diff --git a/drivers/net/vxlan/vxlan_private.h b/drivers/net/vxlan/vxlan_private.h index f0ee85f83732..b5a80ed3eff2 100644 --- a/drivers/net/vxlan/vxlan_private.h +++ b/drivers/net/vxlan/vxlan_private.h @@ -192,7 +192,7 @@ int vxlan_fdb_create(struct vxlan_dev *vxlan, __u16 state, __be16 port, __be32 src_vni, __be32 vni, __u32 ifindex, __u16 ndm_flags, u32 nhid, struct vxlan_fdb **fdb, - struct netlink_ext_ack *extack); + struct netlink_ext_ack *extack, gfp_t gfp); int __vxlan_fdb_delete(struct vxlan_dev *vxlan, const unsigned char *addr, union vxlan_addr ip, __be16 port, __be32 src_vni, __be32 vni, @@ -202,7 +202,8 @@ int vxlan_fdb_update(struct vxlan_dev *vxlan, __u16 state, __u16 flags, __be16 port, __be32 src_vni, __be32 vni, __u32 ifindex, __u16 ndm_flags, u32 nhid, - bool swdev_notify, struct netlink_ext_ack *extack); + bool swdev_notify, struct netlink_ext_ack *extack, + gfp_t gfp); void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, const struct vxlan_config *cfg, __be32 default_vni, struct vxlan_rdst *rdst, bool did_rsc); @@ -229,7 +230,7 @@ int vxlan_update_default_fdb_entry(struct vxlan_dev *vxlan, __be32 vni, const union vxlan_addr *old_remote_ip, const union vxlan_addr *remote_ip, u32 old_ifindex, u32 new_ifindex, - struct netlink_ext_ack *extack); + struct netlink_ext_ack *extack, gfp_t gfp); int vxlan_vnilist_update_group(struct vxlan_dev *vxlan, const union vxlan_addr *old_remote_ip, const union vxlan_addr *new_remote_ip, diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c index 4a631fcdf23a..4b43674cceff 100644 --- a/drivers/net/vxlan/vxlan_vnifilter.c +++ b/drivers/net/vxlan/vxlan_vnifilter.c @@ -553,7 +553,7 @@ int vxlan_update_default_fdb_entry(struct vxlan_dev *vxlan, __be32 vni, const union vxlan_addr *old_remote_ip, const union vxlan_addr *remote_ip, u32 old_ifindex, u32 new_ifindex, - struct netlink_ext_ack *extack) + struct netlink_ext_ack *extack, gfp_t gfp) { const struct vxlan_config *cfg = rtnl_dereference(vxlan->cfg); int err = 0; @@ -575,7 +575,8 @@ int vxlan_update_default_fdb_entry(struct vxlan_dev *vxlan, __be32 vni, vni, vni, new_ifindex, - NTF_SELF, 0, true, extack); + NTF_SELF, 0, true, extack, + gfp); if (err && extack) { spin_unlock_bh(&vxlan->hash_lock); return err; @@ -636,7 +637,8 @@ static int vxlan_vni_update_group(struct vxlan_dev *vxlan, oldrip, newrip, cfg->remote_ifindex, cfg->remote_ifindex, - extack); + extack, + GFP_NOWAIT | __GFP_ACCOUNT); if (ret) goto out; @@ -707,7 +709,8 @@ int vxlan_vnilist_update_group(struct vxlan_dev *vxlan, ret = vxlan_update_default_fdb_entry(vxlan, vent->vni, oldrip, newrip, old_ifindex, new_ifindex, - extack); + extack, + GFP_ATOMIC | __GFP_ACCOUNT); if (ret) goto err_unwind; } @@ -729,7 +732,8 @@ int vxlan_vnilist_update_group(struct vxlan_dev *vxlan, vxlan_update_default_fdb_entry(vxlan, vent->vni, newrip, oldrip, new_ifindex, old_ifindex, - NULL); + NULL, + GFP_ATOMIC | __GFP_ACCOUNT); } return ret; } -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH net-next v4 7/7] selftests: net: test the vxlan vnifilter request limit and dump replay 2026-10-14 12:38 [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory Ali Firas ` (5 preceding siblings ...) 2026-10-14 12:38 ` [PATCH net-next v4 6/7] vxlan: vnifilter: account the default FDB entry " Ali Firas @ 2026-10-14 12:38 ` Ali Firas 2026-10-14 12:45 ` [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory netdev-bot+sinfo 7 siblings, 0 replies; 10+ messages in thread From: Ali Firas @ 2026-10-14 12:38 UTC (permalink / raw) To: netdev, idosch Cc: kuba, pabeni, davem, edumazet, andrew+netdev, horms, razor, roopa, shuah, linux-kselftest, linux-kernel, Ali Firas Extend the vnifilter tests for the request-size limit, the 24-bit VNI range, and the dump/replay round trip the limit requires. The API test gains: the largest accepted add and one VNI past it; a rejected oversized add and a rejected multi-entry add whose entries are each under the limit but sum over it, each checked to install nothing (the range is asserted absent first, since vxlan_vni_add() folds an existing VNI into the update path and would return 0 either way). The cap is symmetric, so it also checks a maximum-size delete accepted and a max+1 delete rejected, with every VNI in the max+1 range installed first so the refusal can only come from the cap; a within-cap delete of a never-installed range, which must fail on the missing VNI; an inverted range accepted as a no-op; and the 24-bit bound exercised through END, not only START. bridge(8) places one VXLAN_VNIFILTER_ENTRY per comma-separated item into a single message, so the multi-entry case is reachable without a hand-built netlink message. vxlan_vnifilter_dump_replay() builds a contiguous run twice the limit from two requests, then replays every range the dump reports into a second device and requires all to be accepted and the two dumps to match. Without the dump clamp the run dumps as one over-limit entry that replay rejects; with it the run dumps as limit-sized entries that replay. vxlan_vnifilter_api() had no teardown of its own device and namespace; add veth-host and the test netns to cleanup(), which runs on EXIT, so a failing case does not leave them or its entries behind. Suggested-by: Ido Schimmel <idosch@nvidia.com> Assisted-by: LLM Signed-off-by: Ali Firas <alishmery18@gmail.com> --- v4: assert the dump is non-empty before replay, so the replay case cannot pass vacuously; rebased. .../selftests/net/test_vxlan_vnifiltering.sh | 119 +++++++++++++++++- 1 file changed, 118 insertions(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/test_vxlan_vnifiltering.sh b/tools/testing/selftests/net/test_vxlan_vnifiltering.sh index 6fb5af013dac..bb35e89d2d03 100755 --- a/tools/testing/selftests/net/test_vxlan_vnifiltering.sh +++ b/tools/testing/selftests/net/test_vxlan_vnifiltering.sh @@ -84,6 +84,7 @@ ret=0 # all tests in this script. Can be overridden with -t option TESTS=" vxlan_vnifilter_api + vxlan_vnifilter_dump_replay vxlan_vnifilter_datapath vxlan_vnifilter_datapath_pervni vxlan_vnifilter_datapath_mgroup @@ -139,8 +140,9 @@ check_vm_connectivity() { cleanup() { ip link del veth-hv-1 2>/dev/null || true ip link del vethhv-11 vethhv-12 vethhv-21 vethhv-22 2>/dev/null || true + ip link del veth-host 2>/dev/null || true - cleanup_ns $hv_1 $hv_2 $vm_11 $vm_21 $vm_12 $vm_22 $vm_31 $vm_32 + cleanup_ns $hv_1 $hv_2 $vm_11 $vm_21 $vm_12 $vm_22 $vm_31 $vm_32 $testns } trap cleanup EXIT @@ -347,6 +349,121 @@ vxlan_vnifilter_api() # change vxlan vnifilter flag run_cmd "ip -netns $testns link set dev vxlan-ext1 type vxlan external novnifilter" log_test $? 2 "Cannot unset vnifilter flag on a device" + + # A single request may add at most 4096 VNIs. bridge(8) puts one + # VXLAN_VNIFILTER_ENTRY per comma-separated item into a single message, + # so both the single-entry and the multi-entry paths are reachable here. + + # The largest accepted add, and one VNI more rejected. + run_cmd "bridge -netns $testns vni add dev vxlan-ext1 vni 10000-14095" + log_test $? 0 "Add a request of the maximum VNI count" + + # The rejected oversized add must install nothing. Assert the range is + # absent first: vxlan_vni_add() folds an already-present VNI into the + # update path and returns 0, so a later probe cannot tell "installed + # nothing" from "installed part" unless it started absent. + run_cmd "bridge -netns $testns vni show dev vxlan-ext1 | grep -qw 20000" + log_test $? 1 "VNI 20000 absent before the oversized add" + run_cmd "bridge -netns $testns vni add dev vxlan-ext1 vni 20000-24096" + log_test $? 255 "Cannot add a request over the maximum VNI count" + run_cmd "bridge -netns $testns vni show dev vxlan-ext1 | grep -qw 20000" + log_test $? 1 "The rejected oversized add installed nothing" + + # Two entries each under the limit but summing over it: the per-message + # total is what is bounded, not the span of one entry. This is the shape + # a per-entry check would have let through. + run_cmd "bridge -netns $testns vni show dev vxlan-ext1 | grep -qw 30000" + log_test $? 1 "VNI 30000 absent before the oversized multi-entry add" + run_cmd "bridge -netns $testns vni add dev vxlan-ext1 vni 30000-32047,32048-34097" + log_test $? 255 "Cannot add a multi-entry request summing over the maximum" + run_cmd "bridge -netns $testns vni show dev vxlan-ext1 | grep -qw 30000" + log_test $? 1 "The rejected multi-entry add installed nothing" + + # The cap is symmetric: a delete may touch at most the maximum too. + # Install a maximum-size range and the VNI past it, so the max+1 delete + # below has every VNI present and can only be refused by the cap, not by + # a missing VNI. + run_cmd "bridge -netns $testns vni add dev vxlan-ext1 vni 40000-44095" + log_test $? 0 "Populate a maximum-size range to delete" + run_cmd "bridge -netns $testns vni add dev vxlan-ext1 vni 44096" + log_test $? 0 "Add the VNI past the maximum range" + run_cmd "bridge -netns $testns vni del dev vxlan-ext1 vni 40000-44096" + log_test $? 255 "Cannot delete a request over the maximum VNI count" + run_cmd "bridge -netns $testns vni del dev vxlan-ext1 vni 40000-44095" + log_test $? 0 "Delete a request of the maximum VNI count" + run_cmd "bridge -netns $testns vni del dev vxlan-ext1 vni 44096" + log_test $? 0 "Delete the VNI past the maximum range" + + # A within-cap delete of a never-installed range reaches the handler and + # fails there on the missing VNI, not on the cap. + run_cmd "bridge -netns $testns vni del dev vxlan-ext1 vni 50000-50010" + log_test $? 255 "Cannot delete a range that was never installed" + + # A start above the end selects nothing and is accepted as a no-op. + # Use a VNI no earlier case installs, so the absence check is meaningful. + run_cmd "bridge -netns $testns vni show dev vxlan-ext1 | grep -qw 55000" + log_test $? 1 "VNI 55000 absent before the inverted range" + run_cmd "bridge -netns $testns vni add dev vxlan-ext1 vni 55000-50000" + log_test $? 0 "An inverted range is accepted as a no-op" + run_cmd "bridge -netns $testns vni show dev vxlan-ext1 | grep -qw 55000" + log_test $? 1 "The inverted range installed nothing" + + # The VXLAN header carries 24 bits. The bound is on both endpoints, so a + # range whose END alone leaves the space is rejected too. + run_cmd "bridge -netns $testns vni add dev vxlan-ext1 vni 16777215" + log_test $? 0 "Add the highest VNI the header can carry" + run_cmd "bridge -netns $testns vni del dev vxlan-ext1 vni 16777215" + log_test $? 0 "Delete the highest VNI the header can carry" + run_cmd "bridge -netns $testns vni add dev vxlan-ext1 vni 16777215-16777216" + log_test $? 255 "Cannot add a range whose END leaves the 24-bit space" + run_cmd "bridge -netns $testns vni add dev vxlan-ext1 vni 100-4294967295" + log_test $? 255 "Cannot add a range whose END wraps past the 24-bit space" +} + +# A device may hold a contiguous run longer than one request's limit, built +# from several requests. The dump coalesces it, so the dump must break the run +# into entries each no larger than the limit, or the configuration it reports +# cannot be replayed. Install such a run, then feed every range the dump +# reports back into a second device and require all to be accepted. +vxlan_vnifilter_dump_replay() +{ + local opts="external vnifilter local 172.16.0.1 dev veth-testns" + local range rc=0 src_ranges dst_ranges + + cleanup_vnifilter_api &>/dev/null + setup_vnifilter_api + + # The destination is on a different dstport so re-adding the same VNIs + # does not collide with the source under vxlan_vni_in_use(). + run_cmd "ip -netns $testns link add vxlan-src type vxlan $opts dstport 4789" + log_test $? 0 "dump/replay: create source device" + run_cmd "ip -netns $testns link add vxlan-dst type vxlan $opts dstport 4790" + log_test $? 0 "dump/replay: create destination device" + + # 8192 contiguous VNIs sharing the default remote: one run, twice the + # limit, installed in two accepted requests. + run_cmd "bridge -netns $testns vni add dev vxlan-src vni 10000-14095" + run_cmd "bridge -netns $testns vni add dev vxlan-src vni 14096-18191" + log_test $? 0 "dump/replay: populate a run larger than the limit" + + # Assert the dump is non-empty before replaying it, so an empty or + # failed dump cannot let the replay check below pass without testing + # anything. + src_ranges=$(bridge -netns $testns vni show dev vxlan-src | \ + grep -oE '[0-9]+-[0-9]+|[0-9]{2,}' | sort) + test -n "$src_ranges" + log_test $? 0 "dump/replay: source dump is non-empty" + + for range in $src_ranges; do + bridge -netns $testns vni add dev vxlan-dst vni "$range" \ + 2>/dev/null || rc=$? + done + log_test $rc 0 "dump/replay: every dumped entry is accepted on replay" + + dst_ranges=$(bridge -netns $testns vni show dev vxlan-dst | \ + grep -oE '[0-9]+-[0-9]+|[0-9]{2,}' | sort) + [ "$src_ranges" = "$dst_ranges" ] + log_test $? 0 "dump/replay: source and destination dump the same ranges" } # Sanity test vnifilter datapath -- 2.53.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory 2026-10-14 12:38 [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory Ali Firas ` (6 preceding siblings ...) 2026-10-14 12:38 ` [PATCH net-next v4 7/7] selftests: net: test the vxlan vnifilter request limit and dump replay Ali Firas @ 2026-10-14 12:45 ` netdev-bot+sinfo 7 siblings, 0 replies; 10+ messages in thread From: netdev-bot+sinfo @ 2026-10-14 12:45 UTC (permalink / raw) To: Ali Firas Cc: netdev, idosch, kuba, pabeni, davem, edumazet, andrew+netdev, horms, razor, roopa, shuah, linux-kselftest, linux-kernel 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 code inspection). If it was triggered please include the symptoms, like the stack trace or error 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] 10+ messages in thread
[parent not found: <179198195143.320699.130100546123857623120@kernel.org>]
* Re: [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory [not found] <179198195143.320699.130100546123857623120@kernel.org> @ 2026-10-14 12:59 ` Ali Firas 0 siblings, 0 replies; 10+ messages in thread From: Ali Firas @ 2026-10-14 12:59 UTC (permalink / raw) To: netdev, idosch Cc: kuba, pabeni, davem, edumazet, andrew+netdev, horms, razor, roopa, shuah, linux-kselftest, linux-kernel On 10/14/26 15:45, netdev-bot+sinfo@kernel.org wrote: > - How the issue was discovered Manual source review of the vnifilter netlink path, after noticing that vxlan_mdb.c range-validates its VNI attributes while vxlan_vnifilter.c does not. Prepared with LLM assistance, as the Assisted-by: tags say. > - Whether the issue was actually triggered, or is only theoretical Triggered and measured in QEMU, from a user namespace holding CAP_NET_ADMIN only inside it. There is no stack trace to quote: the symptoms are rtnl hold time and uncharged memory growth, not a crash. The changelogs carry the numbers. The cover letter states this is a policy and hardening change rather than a fix, targeting net-next with no Fixes tag, at Jakub's request: https://lore.kernel.org/netdev/20260921150904.65a704eb@kernel.org/ ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-10-14 12:59 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14 12:38 [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory Ali Firas
2026-10-14 12:38 ` [PATCH net-next v4 1/7] vxlan: vnifilter: validate the VXLAN_VNIFILTER_ENTRY nest Ali Firas
2026-10-14 12:38 ` [PATCH net-next v4 2/7] vxlan: vnifilter: reject VNIs outside the 24-bit space Ali Firas
2026-10-14 12:38 ` [PATCH net-next v4 3/7] vxlan: vnifilter: clamp the dumped VNI range to the request limit Ali Firas
2026-10-14 12:38 ` [PATCH net-next v4 4/7] vxlan: vnifilter: bound the number of VNIs one request may touch Ali Firas
2026-10-14 12:38 ` [PATCH net-next v4 5/7] vxlan: vnifilter: account per-VNI memory to memcg Ali Firas
2026-10-14 12:38 ` [PATCH net-next v4 6/7] vxlan: vnifilter: account the default FDB entry " Ali Firas
2026-10-14 12:38 ` [PATCH net-next v4 7/7] selftests: net: test the vxlan vnifilter request limit and dump replay Ali Firas
2026-10-14 12:45 ` [PATCH net-next v4 0/7] vxlan: vnifilter: bound a single request and account per-VNI memory netdev-bot+sinfo
[not found] <179198195143.320699.130100546123857623120@kernel.org>
2026-10-14 12:59 ` Ali Firas
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).