From: Ali Firas <alishmery18@gmail.com>
To: netdev@vger.kernel.org, idosch@nvidia.com
Cc: kuba@kernel.org, pabeni@redhat.com, davem@davemloft.net,
edumazet@google.com, andrew+netdev@lunn.ch, razor@blackwall.org,
roopa@nvidia.com, bestswngs@gmail.com, xmei5@asu.edu,
linux-kernel@vger.kernel.org, Ali Firas <alishmery18@gmail.com>
Subject: [PATCH net v2 2/2] vxlan: vnifilter: roll back VNI insertion when the group update fails
Date: Sat, 12 Sep 2026 00:52:03 +0300 [thread overview]
Message-ID: <20260911215203.3054653-2-alishmery18@gmail.com> (raw)
In-Reply-To: <20260911215203.3054653-1-alishmery18@gmail.com>
vxlan_vni_add() publishes the new VNI before it can fail. The node is
inserted into the rhashtable, added to the VNI list and, when the
device is up, linked into the per-socket hash, and only then is
vxlan_vni_update_group() called. Nothing is undone if that fails, and
since commit aa6ca1c5c338 ("vxlan: vnifilter: send notification on VNI
add") the RTM_NEWTUNNEL notification is sent unconditionally rather
than only when the VNI changed, so userspace is also told a VNI was
created that the request reported as failed.
Undo the publication on that path and notify only on success.
Reproduced in a QEMU guest as an unprivileged uid inside
unshare(CLONE_NEWUSER | CLONE_NEWNET). ip_mc_join_group() returns
-ENOBUFS once the socket already holds sysctl_igmp_max_memberships
groups, so an add whose group is the first one over that limit fails
inside vxlan_vni_update_group(), after the node has been published:
ip link add dum0 type dummy
ip link set dum0 up
ip link add vx0 type vxlan external vnifilter dstport 4789 dev dum0
ip link set vx0 up
for i in $(seq 1 21); do
bridge vni add vni $i group 239.1.1.$i dev vx0
done
The limit defaults to 20 and is tunable per network namespace, so the
first failing VNI is the limit plus one; with the default the 21st add
fails:
RTNETLINK answers: No buffer space available
Before this change "bridge vni show" lists VNIs 1 to 20 and also VNI
21, which the request reported as failed, and a notification is
emitted for it. After it the same 20 VNIs are listed and VNI 21 is
absent, with no notification. 21 adds are issued in total; the first
20 succeed.
The default FDB entry needs more care than the rest of the state. It
is not enough to call vxlan_vni_delete_group(): its guard fires when
either the per-VNI remote IP or the device default remote IP is set,
so on a device with a default remote it issues __vxlan_fdb_delete()
even when vxlan_update_default_fdb_entry() is what failed. The entry
may also not be ours at all. A zero-MAC entry can be installed out of
band, and vxlan_fdb_append() returns early when the destination
already exists, so the add creates nothing:
bridge fdb append 00:00:00:00:00:00 dst 239.1.1.21 src_vni 21 \
vni 21 via dum0 dev vx0
bridge vni add vni 21 group 239.1.1.21 dev vx0
The "via dum0" is required. Without it the entry carries rdst ifindex
0 while the add installs with default_dst.remote_ifindex, so
vxlan_fdb_append() creates a second remote instead of finding this
one, and the rollback then removes only what it added. With it, the
entry matches and an unconditional rollback destroys it, reporting
RTM_DELNEIGH for something the user configured.
Report through vxlan_fdb_update() whether a new remote was actually
created, thread that back to vxlan_vni_add(), and remove the entry
only in that case.
Leaving the entry alone instead would be a much smaller change and it
is inert in isolation: it holds no reference to the VNI node so
nothing dangles, the user can remove it with bridge fdb del, and
vxlan_dellink() flushes it with the rest. It loses on retry. Without
this fix an entry the failed add created outlives the VNI, and
vxlan_fdb_append() merges it with a later successful add of the same
VNI rather than replacing it:
bridge vni add vni 23 group 239.1.1.23 dev vx0 # fails
bridge vni del vni 1 dev vx0 # free a membership
bridge vni add vni 23 group 239.1.1.99 dev vx0 # succeeds
00:00:00:00:00:00 dst 239.1.1.23 vni 23 src_vni 23 via dum0 self permanent
00:00:00:00:00:00 dst 239.1.1.99 vni 23 src_vni 23 via dum0 self permanent
VNI 23 ends up with two default remotes, so BUM traffic is replicated
to a group the user never asked for and the device never joined. That,
and the asymmetry with vxlan_vni_del(), which does remove the entry,
are why creation is tracked rather than the entry simply being left.
No membership has to be given back. vxlan_igmp_leave() is unreachable
from vxlan_vni_add(): it is gated on
vxlan_addr_multicast(&old_remote_ip), and old_remote_ip is a copy of
the remote IP of a node that was just kzalloc'd, so it is zero. This
does not depend on the device's default remote, which the guard does
not read. Verified on a device created with a multicast default,
where /proc/net/igmp shows the same 22 membership rows before and
after the failed add, including the device's own group.
Two things are deliberately not addressed here. vxlan_vni_add()
returns early into vxlan_vni_update() when the VNI already exists, and
that path commits remote_ip and swaps the FDB entry before the join
can fail, leaving the VNI pointing at a group it never joined while
the request reports failure, and with no notification since *changed
stays false. It needs a second vxlan device sharing the socket to
reproduce, because vxlan_group_used() returns false as soon as the
socket is used by only one device, so the leave otherwise frees a
membership and the join succeeds:
ip link add vx1 type vxlan external vnifilter dstport 4789 dev dum0
ip link set vx1 up
bridge vni add vni 100 group 239.1.1.1 dev vx1
bridge vni add vni 1 group 239.9.9.9 dev vx0
The last command fails with -ENOBUFS, yet "bridge vni show" then
reports vni 1 with group 239.9.9.9. A correct fix there has to restore
remote_ip, reinstall the old FDB entry and rejoin the old group, which
is a transaction rather than a guard, so it belongs in its own change.
vxlan_vni_add_del() leaving the earlier VNIs of a partially applied
range installed is likewise a separate question about that function.
This patch depends on the preceding one, "vxlan: report whether
vxlan_fdb_update() created the remote", and does not apply without it.
Fixes: f9c4bb0b245c ("vxlan: vni filtering support on collect metadata device")
Link: https://lore.kernel.org/netdev/20260323095544.3311285-4-bestswngs@gmail.com/
Link: https://lore.kernel.org/netdev/20260324133449.GA460138@shredder/
Suggested-by: Ido Schimmel <idosch@nvidia.com>
Cc: Weiming Shi <bestswngs@gmail.com>
Cc: Xiang Mei <xmei5@asu.edu>
Assisted-by: LLM
Signed-off-by: Ali Firas <alishmery18@gmail.com>
---
v1: https://lore.kernel.org/netdev/20260904021707.2891129-1-alishmery18@gmail.com/
v2:
- do not remove a default FDB entry this add did not create; the guard
in vxlan_vni_delete_group() is an OR, so the v1 rollback could delete
an entry installed out of band. Track creation instead, which is what
patch 1/2 is for.
- drop the vxlan_vni_delete_group() call from the error path entirely:
vxlan_igmp_leave() is unreachable from vxlan_vni_add(), so there was
never a membership to give back.
- correct the changelog: VNIs 1 to 20 remain listed and only 21 is
absent, not "the table is empty", and 21 adds are issued, not 22.
- state what is not addressed: the vxlan_vni_update() path and
vxlan_vni_add_del() partial ranges.
- use the documented Assisted-by: LLM form.
drivers/net/vxlan/vxlan_vnifilter.c | 53 ++++++++++++++++++++++++++---
1 file changed, 48 insertions(+), 5 deletions(-)
diff --git a/drivers/net/vxlan/vxlan_vnifilter.c b/drivers/net/vxlan/vxlan_vnifilter.c
index 7e8abc55ef53..9cc932c6177d 100644
--- a/drivers/net/vxlan/vxlan_vnifilter.c
+++ b/drivers/net/vxlan/vxlan_vnifilter.c
@@ -473,6 +473,7 @@ static const struct nla_policy vni_filter_policy[VXLAN_VNIFILTER_MAX + 1] = {
static int vxlan_update_default_fdb_entry(struct vxlan_dev *vxlan, __be32 vni,
union vxlan_addr *old_remote_ip,
union vxlan_addr *remote_ip,
+ bool *fdb_created,
struct netlink_ext_ack *extack)
{
struct vxlan_rdst *dst = &vxlan->default_dst;
@@ -488,7 +489,8 @@ static int vxlan_update_default_fdb_entry(struct vxlan_dev *vxlan, __be32 vni,
vni,
vni,
dst->remote_ifindex,
- NTF_SELF, 0, true, NULL, extack);
+ NTF_SELF, 0, true, fdb_created,
+ extack);
if (err) {
spin_unlock_bh(&vxlan->hash_lock);
return err;
@@ -512,6 +514,7 @@ static int vxlan_vni_update_group(struct vxlan_dev *vxlan,
struct vxlan_vni_node *vninode,
union vxlan_addr *group,
bool create, bool *changed,
+ bool *fdb_created,
struct netlink_ext_ack *extack)
{
struct vxlan_net *vn = net_generic(vxlan->net, vxlan_net_id);
@@ -545,7 +548,7 @@ static int vxlan_vni_update_group(struct vxlan_dev *vxlan,
return 0;
ret = vxlan_update_default_fdb_entry(vxlan, vninode->vni,
- oldrip, newrip,
+ oldrip, newrip, fdb_created,
extack);
if (ret)
goto out;
@@ -599,7 +602,7 @@ int vxlan_vnilist_update_group(struct vxlan_dev *vxlan,
ret = vxlan_update_default_fdb_entry(vxlan, vent->vni,
old_remote_ip,
new_remote_ip,
- extack);
+ NULL, extack);
if (ret)
return ret;
}
@@ -655,7 +658,7 @@ static int vxlan_vni_update(struct vxlan_dev *vxlan,
return 0;
ret = vxlan_vni_update_group(vxlan, vninode, group, false, changed,
- extack);
+ NULL, extack);
if (ret)
return ret;
@@ -718,6 +721,8 @@ static void vxlan_vni_free(struct vxlan_vni_node *vninode)
kfree(vninode);
}
+static void vxlan_vni_node_rcu_free(struct rcu_head *rcu);
+
static int vxlan_vni_add(struct vxlan_dev *vxlan,
struct vxlan_vni_group *vg,
u32 vni, union vxlan_addr *group,
@@ -725,6 +730,7 @@ static int vxlan_vni_add(struct vxlan_dev *vxlan,
{
struct vxlan_vni_node *vninode;
__be32 v = cpu_to_be32(vni);
+ bool fdb_created = false;
bool changed = false;
int err = 0;
@@ -755,10 +761,47 @@ static int vxlan_vni_add(struct vxlan_dev *vxlan,
vxlan_vs_add_del_vninode(vxlan, vninode, false);
err = vxlan_vni_update_group(vxlan, vninode, group, true, &changed,
- extack);
+ &fdb_created, extack);
+ if (err)
+ goto err_vni_del;
vxlan_vnifilter_notify(vxlan, vninode, RTM_NEWTUNNEL);
+ return 0;
+
+err_vni_del:
+ /* Undo only the default FDB entry this add installed. An entry that
+ * was already there, whether added out of band or by another VNI, is
+ * left alone.
+ *
+ * No membership has to be given back: vxlan_igmp_leave() is gated on
+ * vxlan_addr_multicast(&old_remote_ip), and old_remote_ip is a copy
+ * of the remote IP of a node this add just allocated, so it is zero.
+ *
+ * The address picked below is the one the entry was installed
+ * against. If the request carried a group, vxlan_vni_update_group()
+ * copied it into vninode->remote_ip before the join could fail; if
+ * it did not, that copy was skipped and the entry went to the device
+ * default.
+ */
+ if (fdb_created) {
+ union vxlan_addr *rip = vxlan_addr_any(&vninode->remote_ip) ?
+ &vxlan->default_dst.remote_ip :
+ &vninode->remote_ip;
+
+ spin_lock_bh(&vxlan->hash_lock);
+ __vxlan_fdb_delete(vxlan, all_zeros_mac, *rip,
+ vxlan->cfg.dst_port, vninode->vni,
+ vninode->vni,
+ vxlan->default_dst.remote_ifindex, true);
+ spin_unlock_bh(&vxlan->hash_lock);
+ }
+ rhashtable_remove_fast(&vg->vni_hash, &vninode->vnode,
+ vxlan_vni_rht_params);
+ __vxlan_vni_del_list(vg, vninode);
+ if (vxlan->dev->flags & IFF_UP)
+ vxlan_vs_add_del_vninode(vxlan, vninode, true);
+ call_rcu(&vninode->rcu, vxlan_vni_node_rcu_free);
return err;
}
--
2.53.0
next prev parent reply other threads:[~2026-09-11 21:52 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-11 21:52 [PATCH net v2 1/2] vxlan: report whether vxlan_fdb_update() created the remote Ali Firas
2026-09-11 21:52 ` Ali Firas [this message]
2026-09-16 0:48 ` [PATCH net v2 2/2] vxlan: vnifilter: roll back VNI insertion when the group update fails Jakub Kicinski
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260911215203.3054653-2-alishmery18@gmail.com \
--to=alishmery18@gmail.com \
--cc=andrew+netdev@lunn.ch \
--cc=bestswngs@gmail.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=idosch@nvidia.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=razor@blackwall.org \
--cc=roopa@nvidia.com \
--cc=xmei5@asu.edu \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®