From cb0592315ab281269d00ed44cd86f73f43b21adf Mon Sep 17 00:00:00 2001 From: Jonas Gorski Date: Fri, 9 Aug 2024 11:56:57 +0200 Subject: [PATCH] nl_bridge: flush our fdb entries on vlan removal The kernel refuses to remove fdb entries for VLANs that are not configured, but at the same time won't remove permanent entries on VLAN deletion, including extern_learn entries. Fortunately recent kernels gained the ability for bulk deletion, so add a new helper to send a flush of all extern_learn entries from a deleted vlan from a certain port. Using this, we can make sure no entries are left behind in the removed vlan. Any removed entry will trigger a DEL_NEIGH message from the kernel, but since we already removed the neigh from the cache and removed the flow, we do not need to handle it again here, so add a check before attempting to handle the deleted neigh. This was tested via: on switch: $ ip link add swbridge type bridge vlan_filtering 1 vlan_default_pvid 0 $ ip link set dev port5 master swbridge $ ip link set dev port5 up $ bridge vlan add dev port5 vid 2 pvid untagged (connect something to port5, let it generate packets) $ bridge fdb show dev port5 | grep extern_learn 0c:c4:7a:9c:29:fc vlan 2 extern_learn master swbridge $ bridge vlan del dev port5 vid 2 Before: $ bridge fdb show dev port5 | grep extern_learn 0c:c4:7a:9c:29:fc vlan 2 extern_learn master swbridge (entry still present) After: $ bridge fdb show dev port5 | grep extern_learn (entry gone) Signed-off-by: Jonas Gorski --- meson.build | 1 + src/netlink/nl_bridge.cc | 29 +++++++++++--- src/netlink/nl_fdb_flush.h | 79 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 src/netlink/nl_fdb_flush.h diff --git a/meson.build b/meson.build index 354c6af7..9c9b84a4 100644 --- a/meson.build +++ b/meson.build @@ -29,6 +29,7 @@ sources = files(''' src/netlink/nl_hashing.h src/netlink/nl_interface.cc src/netlink/nl_interface.h + src/netlink/nl_fdb_flush.h src/netlink/nl_l3.cc src/netlink/nl_l3.h src/netlink/nl_l3_interfaces.h diff --git a/src/netlink/nl_bridge.cc b/src/netlink/nl_bridge.cc index 0b0f5ee3..48510334 100644 --- a/src/netlink/nl_bridge.cc +++ b/src/netlink/nl_bridge.cc @@ -25,6 +25,7 @@ #include "cnetlink.h" #include "netlink-utils.h" #include "nl_bridge.h" +#include "nl_fdb_flush.h" #include "nl_output.h" #include "nl_vlan.h" #include "nl_vxlan.h" @@ -485,6 +486,15 @@ void nl_bridge::update_vlans(rtnl_link *old_link, rtnl_link *new_link) { // the PVID is already being handled outside of the loop vlan->remove_bridge_vlan(_link, vid, false, !egress_untagged); + + // remove all fdb entries by us + nl_fdb_flush ff; + + auto ret = ff.flush_fdb(rtnl_link_get_ifindex(_link), vid, + NTF_MASTER | NTF_EXT_LEARNED); + if (ret < 0) + LOG(WARNING) << __FUNCTION__ << ": failed to flush vid=" << vid + << " on port " << _link; } } } @@ -781,13 +791,20 @@ void nl_bridge::remove_neigh_from_fdb(rtnl_neigh *neigh) { return; } - // lookup l2_cache as well - std::unique_ptr n_lookup( - NEIGH_CAST(nl_cache_search(l2_cache.get(), OBJ_CAST(neigh))), - rtnl_neigh_put); + if ((rtnl_neigh_get_flags(neigh) & NTF_EXT_LEARNED) == NTF_EXT_LEARNED) { + // lookup l2_cache as well + std::unique_ptr n_lookup( + NEIGH_CAST(nl_cache_search(l2_cache.get(), OBJ_CAST(neigh))), + rtnl_neigh_put); - if (n_lookup) { - nl_cache_remove(OBJ_CAST(n_lookup.get())); + if (n_lookup) { + nl_cache_remove(OBJ_CAST(n_lookup.get())); + } else { + // if we flushed the entry, we already removed it from cache and flows, so + // no need to do anything here + VLOG(2) << __FUNCTION__ << ": neigh not found in cache" << neigh; + return; + } } const uint32_t port = nl->get_port_id(rtnl_neigh_get_ifindex(neigh)); diff --git a/src/netlink/nl_fdb_flush.h b/src/netlink/nl_fdb_flush.h new file mode 100644 index 00000000..8a7f10a6 --- /dev/null +++ b/src/netlink/nl_fdb_flush.h @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "nl_output.h" + +namespace basebox { + +class nl_fdb_flush final { + struct nl_sock *sock; + +public: + nl_fdb_flush() { + sock = nl_socket_alloc(); + int err; + if ((err = nl_connect(sock, NETLINK_ROUTE)) < 0) + LOG(FATAL) << __FUNCTION__ << ": Unable to connect netlink socket: %s" + << nl_geterror(err); + } + + ~nl_fdb_flush() { nl_socket_free(sock); } + + /** + * flush fdb + */ + int flush_fdb(int ifindex, uint16_t vlan, uint8_t flags) { + struct nl_msg *m; + struct ndmsg ndm; + int err; + + memset(&ndm, 0, sizeof(ndm)); + ndm.ndm_family = PF_BRIDGE; + ndm.ndm_ifindex = ifindex; + ndm.ndm_flags = flags; + + m = nlmsg_alloc_simple(RTM_DELNEIGH, NLM_F_REQUEST | NLM_F_BULK); + if (!m) + LOG(FATAL) << __FUNCTION__ << ": out of memory"; + + if (nlmsg_append(m, &ndm, sizeof(ndm), NLMSG_ALIGNTO) < 0) + LOG(FATAL) << __FUNCTION__ << ": out of memory"; + + if (vlan > 0) { + if (nla_put_u16(m, NDA_VLAN, vlan) < 0) + LOG(FATAL) << __FUNCTION__ << ": out of memory"; + } + + if (flags > 0) { + if (nla_put_u8(m, NDA_NDM_FLAGS_MASK, flags) < 0) + LOG(FATAL) << __FUNCTION__ << ": out of memory"; + } + + err = nl_send_auto_complete(sock, m); + nlmsg_free(m); + if (err < 0) + LOG(FATAL) << __FUNCTION__ << ": " << nl_geterror(err); + + if ((err = nl_recvmsgs_default(sock)) < 0) { + LOG(FATAL) << __FUNCTION__ << ": " << nl_geterror(err); + } + + return 0; + } +}; + +} // namespace basebox