summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2023-10-13 10:00:32 +0100
committerDavid S. Miller <davem@davemloft.net>2023-10-13 10:00:32 +0100
commit7497b0af21573bbfa78eb40d7cefe663c4c2477b (patch)
treea3d7abd589f0cdba77afd026fa25b453c6c99cd4
parent0e6bb5b7f4c8e6665e76bdafce37ad4a8daf83c5 (diff)
parentf826f2a2ee1ed648c59b792f93bb4466bfe367a1 (diff)
Merge branch 'vxlan-fdb-flushing'
Amit Cohen says: ==================== Extend VXLAN driver to support FDB flushing The merge commit 92716869375b ("Merge branch 'br-flush-filtering'") added support for FDB flushing in bridge driver. Extend VXLAN driver to support FDB flushing also. Add support for filtering by fields which are relevant for VXLAN FDBs: * Source VNI * Nexthop ID * 'router' flag * Destination VNI * Destination Port * Destination IP Without this set, flush for VXLAN device fails: $ bridge fdb flush dev vx10 RTNETLINK answers: Operation not supported With this set, such flush works with the relevant arguments, for example: $ bridge fdb flush dev vx10 vni 5000 dst 193.2.2.1 < flush all vx10 entries with VNI 5000 and destination IP 193.2.2.1> Some preparations are required, handle them before adding flushing support in VXLAN driver. See more details in commit messages. Patch set overview: Patch #1 prepares flush policy to be used by VXLAN driver Patches #2-#3 are preparations in VXLAN driver Patch #4 adds an initial support for flushing in VXLAN driver Patches #5-#9 add support for filtering by several attributes Patch #10 adds a test for FDB flush with VXLAN Patch #11 extends the test to check FDB flush with bridge ==================== Acked-by: Nikolay Aleksandrov <razor@blackwall.org> Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--drivers/net/vxlan/vxlan_core.c207
-rw-r--r--include/linux/netdevice.h8
-rw-r--r--net/bridge/br_fdb.c29
-rw-r--r--net/bridge/br_private.h3
-rw-r--r--net/core/rtnetlink.c27
-rw-r--r--tools/testing/selftests/net/Makefile1
-rwxr-xr-xtools/testing/selftests/net/fdb_flush.sh812
7 files changed, 1049 insertions, 38 deletions
diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c
index 5b5597073b00..ece377b1b6bd 100644
--- a/drivers/net/vxlan/vxlan_core.c
+++ b/drivers/net/vxlan/vxlan_core.c
@@ -3022,9 +3022,101 @@ static int vxlan_open(struct net_device *dev)
return ret;
}
+struct vxlan_fdb_flush_desc {
+ bool ignore_default_entry;
+ unsigned long state;
+ unsigned long state_mask;
+ unsigned long flags;
+ unsigned long flags_mask;
+ __be32 src_vni;
+ u32 nhid;
+ __be32 vni;
+ __be16 port;
+ union vxlan_addr dst_ip;
+};
+
+static bool vxlan_fdb_is_default_entry(const struct vxlan_fdb *f,
+ const struct vxlan_dev *vxlan)
+{
+ return is_zero_ether_addr(f->eth_addr) && f->vni == vxlan->cfg.vni;
+}
+
+static bool vxlan_fdb_nhid_matches(const struct vxlan_fdb *f, u32 nhid)
+{
+ struct nexthop *nh = rtnl_dereference(f->nh);
+
+ return nh && nh->id == nhid;
+}
+
+static bool vxlan_fdb_flush_matches(const struct vxlan_fdb *f,
+ const struct vxlan_dev *vxlan,
+ const struct vxlan_fdb_flush_desc *desc)
+{
+ if (desc->state_mask && (f->state & desc->state_mask) != desc->state)
+ return false;
+
+ if (desc->flags_mask && (f->flags & desc->flags_mask) != desc->flags)
+ return false;
+
+ if (desc->ignore_default_entry && vxlan_fdb_is_default_entry(f, vxlan))
+ return false;
+
+ if (desc->src_vni && f->vni != desc->src_vni)
+ return false;
+
+ if (desc->nhid && !vxlan_fdb_nhid_matches(f, desc->nhid))
+ return false;
+
+ return true;
+}
+
+static bool
+vxlan_fdb_flush_should_match_remotes(const struct vxlan_fdb_flush_desc *desc)
+{
+ return desc->vni || desc->port || desc->dst_ip.sa.sa_family;
+}
+
+static bool
+vxlan_fdb_flush_remote_matches(const struct vxlan_fdb_flush_desc *desc,
+ const struct vxlan_rdst *rd)
+{
+ if (desc->vni && rd->remote_vni != desc->vni)
+ return false;
+
+ if (desc->port && rd->remote_port != desc->port)
+ return false;
+
+ if (desc->dst_ip.sa.sa_family &&
+ !vxlan_addr_equal(&rd->remote_ip, &desc->dst_ip))
+ return false;
+
+ return true;
+}
+
+static void
+vxlan_fdb_flush_match_remotes(struct vxlan_fdb *f, struct vxlan_dev *vxlan,
+ const struct vxlan_fdb_flush_desc *desc,
+ bool *p_destroy_fdb)
+{
+ bool remotes_flushed = false;
+ struct vxlan_rdst *rd, *tmp;
+
+ list_for_each_entry_safe(rd, tmp, &f->remotes, list) {
+ if (!vxlan_fdb_flush_remote_matches(desc, rd))
+ continue;
+
+ vxlan_fdb_dst_destroy(vxlan, f, rd, true);
+ remotes_flushed = true;
+ }
+
+ *p_destroy_fdb = remotes_flushed && list_empty(&f->remotes);
+}
+
/* Purge the forwarding table */
-static void vxlan_flush(struct vxlan_dev *vxlan, bool do_all)
+static void vxlan_flush(struct vxlan_dev *vxlan,
+ const struct vxlan_fdb_flush_desc *desc)
{
+ bool match_remotes = vxlan_fdb_flush_should_match_remotes(desc);
unsigned int h;
for (h = 0; h < FDB_HASH_SIZE; ++h) {
@@ -3034,28 +3126,122 @@ static void vxlan_flush(struct vxlan_dev *vxlan, bool do_all)
hlist_for_each_safe(p, n, &vxlan->fdb_head[h]) {
struct vxlan_fdb *f
= container_of(p, struct vxlan_fdb, hlist);
- if (!do_all && (f->state & (NUD_PERMANENT | NUD_NOARP)))
- continue;
- /* the all_zeros_mac entry is deleted at vxlan_uninit */
- if (is_zero_ether_addr(f->eth_addr) &&
- f->vni == vxlan->cfg.vni)
+
+ if (!vxlan_fdb_flush_matches(f, vxlan, desc))
continue;
+
+ if (match_remotes) {
+ bool destroy_fdb = false;
+
+ vxlan_fdb_flush_match_remotes(f, vxlan, desc,
+ &destroy_fdb);
+
+ if (!destroy_fdb)
+ continue;
+ }
+
vxlan_fdb_destroy(vxlan, f, true, true);
}
spin_unlock_bh(&vxlan->hash_lock[h]);
}
}
+static const struct nla_policy vxlan_del_bulk_policy[NDA_MAX + 1] = {
+ [NDA_SRC_VNI] = { .type = NLA_U32 },
+ [NDA_NH_ID] = { .type = NLA_U32 },
+ [NDA_VNI] = { .type = NLA_U32 },
+ [NDA_PORT] = { .type = NLA_U16 },
+ [NDA_DST] = NLA_POLICY_RANGE(NLA_BINARY, sizeof(struct in_addr),
+ sizeof(struct in6_addr)),
+ [NDA_NDM_STATE_MASK] = { .type = NLA_U16 },
+ [NDA_NDM_FLAGS_MASK] = { .type = NLA_U8 },
+};
+
+#define VXLAN_FDB_FLUSH_IGNORED_NDM_FLAGS (NTF_MASTER | NTF_SELF)
+#define VXLAN_FDB_FLUSH_ALLOWED_NDM_STATES (NUD_PERMANENT | NUD_NOARP)
+#define VXLAN_FDB_FLUSH_ALLOWED_NDM_FLAGS (NTF_EXT_LEARNED | NTF_OFFLOADED | \
+ NTF_ROUTER)
+
+static int vxlan_fdb_delete_bulk(struct nlmsghdr *nlh, struct net_device *dev,
+ struct netlink_ext_ack *extack)
+{
+ struct vxlan_dev *vxlan = netdev_priv(dev);
+ struct vxlan_fdb_flush_desc desc = {};
+ struct ndmsg *ndm = nlmsg_data(nlh);
+ struct nlattr *tb[NDA_MAX + 1];
+ u8 ndm_flags;
+ int err;
+
+ ndm_flags = ndm->ndm_flags & ~VXLAN_FDB_FLUSH_IGNORED_NDM_FLAGS;
+
+ err = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, vxlan_del_bulk_policy,
+ extack);
+ if (err)
+ return err;
+
+ if (ndm_flags & ~VXLAN_FDB_FLUSH_ALLOWED_NDM_FLAGS) {
+ NL_SET_ERR_MSG(extack, "Unsupported fdb flush ndm flag bits set");
+ return -EINVAL;
+ }
+ if (ndm->ndm_state & ~VXLAN_FDB_FLUSH_ALLOWED_NDM_STATES) {
+ NL_SET_ERR_MSG(extack, "Unsupported fdb flush ndm state bits set");
+ return -EINVAL;
+ }
+
+ desc.state = ndm->ndm_state;
+ desc.flags = ndm_flags;
+
+ if (tb[NDA_NDM_STATE_MASK])
+ desc.state_mask = nla_get_u16(tb[NDA_NDM_STATE_MASK]);
+
+ if (tb[NDA_NDM_FLAGS_MASK])
+ desc.flags_mask = nla_get_u8(tb[NDA_NDM_FLAGS_MASK]);
+
+ if (tb[NDA_SRC_VNI])
+ desc.src_vni = cpu_to_be32(nla_get_u32(tb[NDA_SRC_VNI]));
+
+ if (tb[NDA_NH_ID])
+ desc.nhid = nla_get_u32(tb[NDA_NH_ID]);
+
+ if (tb[NDA_VNI])
+ desc.vni = cpu_to_be32(nla_get_u32(tb[NDA_VNI]));
+
+ if (tb[NDA_PORT])
+ desc.port = nla_get_be16(tb[NDA_PORT]);
+
+ if (tb[NDA_DST]) {
+ union vxlan_addr ip;
+
+ err = vxlan_nla_get_addr(&ip, tb[NDA_DST]);
+ if (err) {
+ NL_SET_ERR_MSG_ATTR(extack, tb[NDA_DST],
+ "Unsupported address family");
+ return err;
+ }
+ desc.dst_ip = ip;
+ }
+
+ vxlan_flush(vxlan, &desc);
+
+ return 0;
+}
+
/* Cleanup timer and forwarding table on shutdown */
static int vxlan_stop(struct net_device *dev)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
+ struct vxlan_fdb_flush_desc desc = {
+ /* Default entry is deleted at vxlan_uninit. */
+ .ignore_default_entry = true,
+ .state = 0,
+ .state_mask = NUD_PERMANENT | NUD_NOARP,
+ };
vxlan_multicast_leave(vxlan);
del_timer_sync(&vxlan->age_timer);
- vxlan_flush(vxlan, false);
+ vxlan_flush(vxlan, &desc);
vxlan_sock_release(vxlan);
return 0;
@@ -3142,6 +3328,7 @@ static const struct net_device_ops vxlan_netdev_ether_ops = {
.ndo_set_mac_address = eth_mac_addr,
.ndo_fdb_add = vxlan_fdb_add,
.ndo_fdb_del = vxlan_fdb_delete,
+ .ndo_fdb_del_bulk = vxlan_fdb_delete_bulk,
.ndo_fdb_dump = vxlan_fdb_dump,
.ndo_fdb_get = vxlan_fdb_get,
.ndo_mdb_add = vxlan_mdb_add,
@@ -4294,8 +4481,12 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[],
static void vxlan_dellink(struct net_device *dev, struct list_head *head)
{
struct vxlan_dev *vxlan = netdev_priv(dev);
+ struct vxlan_fdb_flush_desc desc = {
+ /* Default entry is deleted at vxlan_uninit. */
+ .ignore_default_entry = true,
+ };
- vxlan_flush(vxlan, true);
+ vxlan_flush(vxlan, &desc);
list_del(&vxlan->next);
unregister_netdevice_queue(dev, head);
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index ae553f886796..1c7681263d30 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -1287,9 +1287,7 @@ struct netdev_net_notifier {
* struct net_device *dev,
* const unsigned char *addr, u16 vid)
* Deletes the FDB entry from dev coresponding to addr.
- * int (*ndo_fdb_del_bulk)(struct ndmsg *ndm, struct nlattr *tb[],
- * struct net_device *dev,
- * u16 vid,
+ * int (*ndo_fdb_del_bulk)(struct nlmsghdr *nlh, struct net_device *dev,
* struct netlink_ext_ack *extack);
* int (*ndo_fdb_dump)(struct sk_buff *skb, struct netlink_callback *cb,
* struct net_device *dev, struct net_device *filter_dev,
@@ -1564,10 +1562,8 @@ struct net_device_ops {
struct net_device *dev,
const unsigned char *addr,
u16 vid, struct netlink_ext_ack *extack);
- int (*ndo_fdb_del_bulk)(struct ndmsg *ndm,
- struct nlattr *tb[],
+ int (*ndo_fdb_del_bulk)(struct nlmsghdr *nlh,
struct net_device *dev,
- u16 vid,
struct netlink_ext_ack *extack);
int (*ndo_fdb_dump)(struct sk_buff *skb,
struct netlink_callback *cb,
diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
index e69a872bfc1d..a98ad763b368 100644
--- a/net/bridge/br_fdb.c
+++ b/net/bridge/br_fdb.c
@@ -661,14 +661,30 @@ static int __fdb_flush_validate_ifindex(const struct net_bridge *br,
return 0;
}
-int br_fdb_delete_bulk(struct ndmsg *ndm, struct nlattr *tb[],
- struct net_device *dev, u16 vid,
+static const struct nla_policy br_fdb_del_bulk_policy[NDA_MAX + 1] = {
+ [NDA_VLAN] = NLA_POLICY_RANGE(NLA_U16, 1, VLAN_N_VID - 2),
+ [NDA_IFINDEX] = NLA_POLICY_MIN(NLA_S32, 1),
+ [NDA_NDM_STATE_MASK] = { .type = NLA_U16 },
+ [NDA_NDM_FLAGS_MASK] = { .type = NLA_U8 },
+};
+
+int br_fdb_delete_bulk(struct nlmsghdr *nlh, struct net_device *dev,
struct netlink_ext_ack *extack)
{
- u8 ndm_flags = ndm->ndm_flags & ~FDB_FLUSH_IGNORED_NDM_FLAGS;
- struct net_bridge_fdb_flush_desc desc = { .vlan_id = vid };
+ struct net_bridge_fdb_flush_desc desc = {};
+ struct ndmsg *ndm = nlmsg_data(nlh);
struct net_bridge_port *p = NULL;
+ struct nlattr *tb[NDA_MAX + 1];
struct net_bridge *br;
+ u8 ndm_flags;
+ int err;
+
+ ndm_flags = ndm->ndm_flags & ~FDB_FLUSH_IGNORED_NDM_FLAGS;
+
+ err = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX,
+ br_fdb_del_bulk_policy, extack);
+ if (err)
+ return err;
if (netif_is_bridge_master(dev)) {
br = netdev_priv(dev);
@@ -681,6 +697,9 @@ int br_fdb_delete_bulk(struct ndmsg *ndm, struct nlattr *tb[],
br = p->br;
}
+ if (tb[NDA_VLAN])
+ desc.vlan_id = nla_get_u16(tb[NDA_VLAN]);
+
if (ndm_flags & ~FDB_FLUSH_ALLOWED_NDM_FLAGS) {
NL_SET_ERR_MSG(extack, "Unsupported fdb flush ndm flag bits set");
return -EINVAL;
@@ -703,7 +722,7 @@ int br_fdb_delete_bulk(struct ndmsg *ndm, struct nlattr *tb[],
desc.flags_mask |= __ndm_flags_to_fdb_flags(ndm_flags_mask);
}
if (tb[NDA_IFINDEX]) {
- int err, ifidx = nla_get_s32(tb[NDA_IFINDEX]);
+ int ifidx = nla_get_s32(tb[NDA_IFINDEX]);
err = __fdb_flush_validate_ifindex(br, ifidx, extack);
if (err)
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index a1f4acfa6994..cbbe35278459 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -847,8 +847,7 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[],
struct net_device *dev, const unsigned char *addr, u16 vid,
struct netlink_ext_ack *extack);
-int br_fdb_delete_bulk(struct ndmsg *ndm, struct nlattr *tb[],
- struct net_device *dev, u16 vid,
+int br_fdb_delete_bulk(struct nlmsghdr *nlh, struct net_device *dev,
struct netlink_ext_ack *extack);
int br_fdb_add(struct ndmsg *nlh, struct nlattr *tb[], struct net_device *dev,
const unsigned char *addr, u16 vid, u16 nlh_flags,
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 7452a6d190c5..eef7f7788996 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -4367,13 +4367,6 @@ int ndo_dflt_fdb_del(struct ndmsg *ndm,
}
EXPORT_SYMBOL(ndo_dflt_fdb_del);
-static const struct nla_policy fdb_del_bulk_policy[NDA_MAX + 1] = {
- [NDA_VLAN] = { .type = NLA_U16 },
- [NDA_IFINDEX] = NLA_POLICY_MIN(NLA_S32, 1),
- [NDA_NDM_STATE_MASK] = { .type = NLA_U16 },
- [NDA_NDM_FLAGS_MASK] = { .type = NLA_U8 },
-};
-
static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack)
{
@@ -4394,8 +4387,10 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
err = nlmsg_parse_deprecated(nlh, sizeof(*ndm), tb, NDA_MAX,
NULL, extack);
} else {
- err = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX,
- fdb_del_bulk_policy, extack);
+ /* For bulk delete, the drivers will parse the message with
+ * policy.
+ */
+ err = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, NULL, extack);
}
if (err < 0)
return err;
@@ -4418,6 +4413,10 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
return -EINVAL;
}
addr = nla_data(tb[NDA_LLADDR]);
+
+ err = fdb_vid_parse(tb[NDA_VLAN], &vid, extack);
+ if (err)
+ return err;
}
if (dev->type != ARPHRD_ETHER) {
@@ -4425,10 +4424,6 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
return -EINVAL;
}
- err = fdb_vid_parse(tb[NDA_VLAN], &vid, extack);
- if (err)
- return err;
-
err = -EOPNOTSUPP;
/* Support fdb on master device the net/bridge default case */
@@ -4442,8 +4437,7 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
err = ops->ndo_fdb_del(ndm, tb, dev, addr, vid, extack);
} else {
if (ops->ndo_fdb_del_bulk)
- err = ops->ndo_fdb_del_bulk(ndm, tb, dev, vid,
- extack);
+ err = ops->ndo_fdb_del_bulk(nlh, dev, extack);
}
if (err)
@@ -4464,8 +4458,7 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
/* in case err was cleared by NTF_MASTER call */
err = -EOPNOTSUPP;
if (ops->ndo_fdb_del_bulk)
- err = ops->ndo_fdb_del_bulk(ndm, tb, dev, vid,
- extack);
+ err = ops->ndo_fdb_del_bulk(nlh, dev, extack);
}
if (!err) {
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 8b017070960d..61939a695f95 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -89,6 +89,7 @@ TEST_PROGS += test_vxlan_mdb.sh
TEST_PROGS += test_bridge_neigh_suppress.sh
TEST_PROGS += test_vxlan_nolocalbypass.sh
TEST_PROGS += test_bridge_backup_port.sh
+TEST_PROGS += fdb_flush.sh
TEST_FILES := settings
diff --git a/tools/testing/selftests/net/fdb_flush.sh b/tools/testing/selftests/net/fdb_flush.sh
new file mode 100755
index 000000000000..90e7a29e0476
--- /dev/null
+++ b/tools/testing/selftests/net/fdb_flush.sh
@@ -0,0 +1,812 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+#
+# This test is for checking functionality of flushing FDB entries.
+# Check that flush works as expected with all the supported arguments and verify
+# some combinations of arguments.
+
+FLUSH_BY_STATE_TESTS="
+ vxlan_test_flush_by_permanent
+ vxlan_test_flush_by_nopermanent
+ vxlan_test_flush_by_static
+ vxlan_test_flush_by_nostatic
+ vxlan_test_flush_by_dynamic
+ vxlan_test_flush_by_nodynamic
+"
+
+FLUSH_BY_FLAG_TESTS="
+ vxlan_test_flush_by_extern_learn
+ vxlan_test_flush_by_noextern_learn
+ vxlan_test_flush_by_router
+ vxlan_test_flush_by_norouter
+"
+
+TESTS="
+ vxlan_test_flush_by_dev
+ vxlan_test_flush_by_vni
+ vxlan_test_flush_by_src_vni
+ vxlan_test_flush_by_port
+ vxlan_test_flush_by_dst_ip
+ vxlan_test_flush_by_nhid
+ $FLUSH_BY_STATE_TESTS
+ $FLUSH_BY_FLAG_TESTS
+ vxlan_test_flush_by_several_args
+ vxlan_test_flush_by_remote_attributes
+ bridge_test_flush_by_dev
+ bridge_test_flush_by_vlan
+ bridge_vxlan_test_flush
+"
+
+: ${VERBOSE:=0}
+: ${PAUSE_ON_FAIL:=no}
+: ${PAUSE:=no}
+: ${VXPORT:=4789}
+
+run_cmd()
+{
+ local cmd="$1"
+ local out
+ local rc
+ local stderr="2>/dev/null"
+
+ if [ "$VERBOSE" = "1" ]; then
+ printf "COMMAND: $cmd\n"
+ stderr=
+ fi
+
+ out=$(eval $cmd $stderr)
+ rc=$?
+ if [ "$VERBOSE" = "1" -a -n "$out" ]; then
+ echo " $out"
+ fi
+
+ return $rc
+}
+
+log_test()
+{
+ local rc=$1
+ local expected=$2
+ local msg="$3"
+ local nsuccess
+ local nfail
+ local ret
+
+ if [ ${rc} -eq ${expected} ]; then
+ printf "TEST: %-60s [ OK ]\n" "${msg}"
+ nsuccess=$((nsuccess+1))
+ else
+ ret=1
+ nfail=$((nfail+1))
+ printf "TEST: %-60s [FAIL]\n" "${msg}"
+ if [ "$VERBOSE" = "1" ]; then
+ echo " rc=$rc, expected $expected"
+ fi
+
+ if [ "${PAUSE_ON_FAIL}" = "yes" ]; then
+ echo
+ echo "hit enter to continue, 'q' to quit"
+ read a
+ [ "$a" = "q" ] && exit 1
+ fi
+ fi
+
+ if [ "${PAUSE}" = "yes" ]; then
+ echo
+ echo "hit enter to continue, 'q' to quit"
+ read a
+ [ "$a" = "q" ] && exit 1
+ fi
+
+ [ "$VERBOSE" = "1" ] && echo
+}
+
+MAC_POOL_1="
+ de:ad:be:ef:13:10
+ de:ad:be:ef:13:11
+ de:ad:be:ef:13:12
+ de:ad:be:ef:13:13
+ de:ad:be:ef:13:14
+"
+mac_pool_1_len=$(echo "$MAC_POOL_1" | grep -c .)
+
+MAC_POOL_2="
+ ca:fe:be:ef:13:10
+ ca:fe:be:ef:13:11
+ ca:fe:be:ef:13:12
+ ca:fe:be:ef:13:13
+ ca:fe:be:ef:13:14
+"
+mac_pool_2_len=$(echo "$MAC_POOL_2" | grep -c .)
+
+fdb_add_mac_pool_1()
+{
+ local dev=$1; shift
+ local args="$@"
+
+ for mac in $MAC_POOL_1
+ do
+ $BRIDGE fdb add $mac dev $dev $args
+ done
+}
+
+fdb_add_mac_pool_2()
+{
+ local dev=$1; shift
+ local args="$@"
+
+ for mac in $MAC_POOL_2
+ do
+ $BRIDGE fdb add $mac dev $dev $args
+ done
+}
+
+fdb_check_n_entries_by_dev_filter()
+{
+ local dev=$1; shift
+ local exp_entries=$1; shift
+ local filter="$@"
+
+ local entries=$($BRIDGE fdb show dev $dev | grep "$filter" | wc -l)
+
+ [[ $entries -eq $exp_entries ]]
+ rc=$?
+
+ log_test $rc 0 "$dev: Expected $exp_entries FDB entries, got $entries"
+ return $rc
+}
+
+vxlan_test_flush_by_dev()
+{
+ local vni=3000
+ local dst_ip=192.0.2.1
+
+ fdb_add_mac_pool_1 vx10 vni $vni dst $dst_ip
+ fdb_add_mac_pool_2 vx20 vni $vni dst $dst_ip
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len
+ fdb_check_n_entries_by_dev_filter vx20 $mac_pool_2_len
+
+ run_cmd "$BRIDGE fdb flush dev vx10"
+ log_test $? 0 "Flush FDB by dev vx10"
+
+ fdb_check_n_entries_by_dev_filter vx10 0
+ log_test $? 0 "Flush FDB by dev vx10 - test vx10 entries"
+
+ fdb_check_n_entries_by_dev_filter vx20 $mac_pool_2_len
+ log_test $? 0 "Flush FDB by dev vx10 - test vx20 entries"
+}
+
+vxlan_test_flush_by_vni()
+{
+ local vni_1=3000
+ local vni_2=4000
+ local dst_ip=192.0.2.1
+
+ fdb_add_mac_pool_1 vx10 vni $vni_1 dst $dst_ip
+ fdb_add_mac_pool_2 vx10 vni $vni_2 dst $dst_ip
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len vni $vni_1
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_2_len vni $vni_2
+
+ run_cmd "$BRIDGE fdb flush dev vx10 vni $vni_2"
+ log_test $? 0 "Flush FDB by dev vx10 and vni $vni_2"
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len vni $vni_1
+ log_test $? 0 "Test entries with vni $vni_1"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 vni $vni_2
+ log_test $? 0 "Test entries with vni $vni_2"
+}
+
+vxlan_test_flush_by_src_vni()
+{
+ # Set some entries with {vni=x,src_vni=y} and some with the opposite -
+ # {vni=y,src_vni=x}, to verify that when we flush by src_vni=x, entries
+ # with vni=x are not flused.
+ local vni_1=3000
+ local vni_2=4000
+ local src_vni_1=4000
+ local src_vni_2=3000
+ local dst_ip=192.0.2.1
+
+ # Reconfigure vx10 with 'external' to get 'src_vni' details in
+ # 'bridge fdb' output
+ $IP link del dev vx10
+ $IP link add name vx10 type vxlan dstport "$VXPORT" external
+
+ fdb_add_mac_pool_1 vx10 vni $vni_1 src_vni $src_vni_1 dst $dst_ip
+ fdb_add_mac_pool_2 vx10 vni $vni_2 src_vni $src_vni_2 dst $dst_ip
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len \
+ src_vni $src_vni_1
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_2_len \
+ src_vni $src_vni_2
+
+ run_cmd "$BRIDGE fdb flush dev vx10 src_vni $src_vni_2"
+ log_test $? 0 "Flush FDB by dev vx10 and src_vni $src_vni_2"
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len \
+ src_vni $src_vni_1
+ log_test $? 0 "Test entries with src_vni $src_vni_1"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 src_vni $src_vni_2
+ log_test $? 0 "Test entries with src_vni $src_vni_2"
+}
+
+vxlan_test_flush_by_port()
+{
+ local port_1=1234
+ local port_2=4321
+ local dst_ip=192.0.2.1
+
+ fdb_add_mac_pool_1 vx10 port $port_1 dst $dst_ip
+ fdb_add_mac_pool_2 vx10 port $port_2 dst $dst_ip
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len port $port_1
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_2_len port $port_2
+
+ run_cmd "$BRIDGE fdb flush dev vx10 port $port_2"
+ log_test $? 0 "Flush FDB by dev vx10 and port $port_2"
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len port $port_1
+ log_test $? 0 "Test entries with port $port_1"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 port $port_2
+ log_test $? 0 "Test entries with port $port_2"
+}
+
+vxlan_test_flush_by_dst_ip()
+{
+ local dst_ip_1=192.0.2.1
+ local dst_ip_2=192.0.2.2
+
+ fdb_add_mac_pool_1 vx10 dst $dst_ip_1
+ fdb_add_mac_pool_2 vx10 dst $dst_ip_2
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len dst $dst_ip_1
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_2_len dst $dst_ip_2
+
+ run_cmd "$BRIDGE fdb flush dev vx10 dst $dst_ip_2"
+ log_test $? 0 "Flush FDB by dev vx10 and dst $dst_ip_2"
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len dst $dst_ip_1
+ log_test $? 0 "Test entries with dst $dst_ip_1"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 dst $dst_ip_2
+ log_test $? 0 "Test entries with dst $dst_ip_2"
+}
+
+nexthops_add()
+{
+ local nhid_1=$1; shift
+ local nhid_2=$1; shift
+
+ $IP nexthop add id 10 via 192.0.2.1 fdb
+ $IP nexthop add id $nhid_1 group 10 fdb
+
+ $IP nexthop add id 20 via 192.0.2.2 fdb
+ $IP nexthop add id $nhid_2 group 20 fdb
+}
+
+vxlan_test_flush_by_nhid()
+{
+ local nhid_1=100
+ local nhid_2=200
+
+ nexthops_add $nhid_1 $nhid_2
+
+ fdb_add_mac_pool_1 vx10 nhid $nhid_1
+ fdb_add_mac_pool_2 vx10 nhid $nhid_2
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len nhid $nhid_1
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_2_len nhid $nhid_2
+
+ run_cmd "$BRIDGE fdb flush dev vx10 nhid $nhid_2"
+ log_test $? 0 "Flush FDB by dev vx10 and nhid $nhid_2"
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len nhid $nhid_1
+ log_test $? 0 "Test entries with nhid $nhid_1"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 nhid $nhid_2
+ log_test $? 0 "Test entries with nhid $nhid_2"
+
+ # Flush also entries with $nhid_1, and then verify that flushing by
+ # 'nhid' does not return an error when there are no entries with
+ # nexthops.
+ run_cmd "$BRIDGE fdb flush dev vx10 nhid $nhid_1"
+ log_test $? 0 "Flush FDB by dev vx10 and nhid $nhid_1"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 nhid
+ log_test $? 0 "Test entries with 'nhid' keyword"
+
+ run_cmd "$BRIDGE fdb flush dev vx10 nhid $nhid_1"
+ log_test $? 0 "Flush FDB by nhid when there are no entries with nexthop"
+}
+
+vxlan_test_flush_by_state()
+{
+ local flush_by_state=$1; shift
+ local state_1=$1; shift
+ local exp_state_1=$1; shift
+ local state_2=$1; shift
+ local exp_state_2=$1; shift
+
+ local dst_ip_1=192.0.2.1
+ local dst_ip_2=192.0.2.2
+
+ fdb_add_mac_pool_1 vx10 dst $dst_ip_1 $state_1
+ fdb_add_mac_pool_2 vx10 dst $dst_ip_2 $state_2
+
+ # Check the entries by dst_ip as not all states appear in 'bridge fdb'
+ # output.
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len dst $dst_ip_1
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_2_len dst $dst_ip_2
+
+ run_cmd "$BRIDGE fdb flush dev vx10 $flush_by_state"
+ log_test $? 0 "Flush FDB by dev vx10 and state $flush_by_state"
+
+ fdb_check_n_entries_by_dev_filter vx10 $exp_state_1 dst $dst_ip_1
+ log_test $? 0 "Test entries with state $state_1"
+
+ fdb_check_n_entries_by_dev_filter vx10 $exp_state_2 dst $dst_ip_2
+ log_test $? 0 "Test entries with state $state_2"
+}
+
+vxlan_test_flush_by_permanent()
+{
+ # Entries that are added without state get 'permanent' state by
+ # default, add some entries with flag 'extern_learn' instead of state,
+ # so they will be added with 'permanent' and should be flushed also.
+ local flush_by_state="permanent"
+ local state_1="permanent"
+ local exp_state_1=0
+ local state_2="extern_learn"
+ local exp_state_2=0
+
+ vxlan_test_flush_by_state $flush_by_state $state_1 $exp_state_1 \
+ $state_2 $exp_state_2
+}
+
+vxlan_test_flush_by_nopermanent()
+{
+ local flush_by_state="nopermanent"
+ local state_1="permanent"
+ local exp_state_1=$mac_pool_1_len
+ local state_2="static"
+ local exp_state_2=0
+
+ vxlan_test_flush_by_state $flush_by_state $state_1 $exp_state_1 \
+ $state_2 $exp_state_2
+}
+
+vxlan_test_flush_by_static()
+{
+ local flush_by_state="static"
+ local state_1="static"
+ local exp_state_1=0
+ local state_2="dynamic"
+ local exp_state_2=$mac_pool_2_len
+
+ vxlan_test_flush_by_state $flush_by_state $state_1 $exp_state_1 \
+ $state_2 $exp_state_2
+}
+
+vxlan_test_flush_by_nostatic()
+{
+ local flush_by_state="nostatic"
+ local state_1="permanent"
+ local exp_state_1=$mac_pool_1_len
+ local state_2="dynamic"
+ local exp_state_2=0
+
+ vxlan_test_flush_by_state $flush_by_state $state_1 $exp_state_1 \
+ $state_2 $exp_state_2
+}
+
+vxlan_test_flush_by_dynamic()
+{
+ local flush_by_state="dynamic"
+ local state_1="dynamic"
+ local exp_state_1=0
+ local state_2="static"
+ local exp_state_2=$mac_pool_2_len
+
+ vxlan_test_flush_by_state $flush_by_state $state_1 $exp_state_1 \
+ $state_2 $exp_state_2
+}
+
+vxlan_test_flush_by_nodynamic()
+{
+ local flush_by_state="nodynamic"
+ local state_1="permanent"
+ local exp_state_1=0
+ local state_2="dynamic"
+ local exp_state_2=$mac_pool_2_len
+
+ vxlan_test_flush_by_state $flush_by_state $state_1 $exp_state_1 \
+ $state_2 $exp_state_2
+}
+
+vxlan_test_flush_by_flag()
+{
+ local flush_by_flag=$1; shift
+ local flag_1=$1; shift
+ local exp_flag_1=$1; shift
+ local flag_2=$1; shift
+ local exp_flag_2=$1; shift
+
+ local dst_ip_1=192.0.2.1
+ local dst_ip_2=192.0.2.2
+
+ fdb_add_mac_pool_1 vx10 dst $dst_ip_1 $flag_1
+ fdb_add_mac_pool_2 vx10 dst $dst_ip_2 $flag_2
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len $flag_1
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_2_len $flag_2
+
+ run_cmd "$BRIDGE fdb flush dev vx10 $flush_by_flag"
+ log_test $? 0 "Flush FDB by dev vx10 and flag $flush_by_flag"
+
+ fdb_check_n_entries_by_dev_filter vx10 $exp_flag_1 dst $dst_ip_1
+ log_test $? 0 "Test entries with flag $flag_1"
+
+ fdb_check_n_entries_by_dev_filter vx10 $exp_flag_2 dst $dst_ip_2
+ log_test $? 0 "Test entries with flag $flag_2"
+}
+
+vxlan_test_flush_by_extern_learn()
+{
+ local flush_by_flag="extern_learn"
+ local flag_1="extern_learn"
+ local exp_flag_1=0
+ local flag_2="router"
+ local exp_flag_2=$mac_pool_2_len
+
+ vxlan_test_flush_by_flag $flush_by_flag $flag_1 $exp_flag_1 \
+ $flag_2 $exp_flag_2
+}
+
+vxlan_test_flush_by_noextern_learn()
+{
+ local flush_by_flag="noextern_learn"
+ local flag_1="extern_learn"
+ local exp_flag_1=$mac_pool_1_len
+ local flag_2="router"
+ local exp_flag_2=0
+
+ vxlan_test_flush_by_flag $flush_by_flag $flag_1 $exp_flag_1 \
+ $flag_2 $exp_flag_2
+}
+
+vxlan_test_flush_by_router()
+{
+ local flush_by_flag="router"
+ local flag_1="router"
+ local exp_flag_1=0
+ local flag_2="extern_learn"
+ local exp_flag_2=$mac_pool_2_len
+
+ vxlan_test_flush_by_flag $flush_by_flag $flag_1 $exp_flag_1 \
+ $flag_2 $exp_flag_2
+}
+
+vxlan_test_flush_by_norouter()
+{
+
+ local flush_by_flag="norouter"
+ local flag_1="router"
+ local exp_flag_1=$mac_pool_1_len
+ local flag_2="extern_learn"
+ local exp_flag_2=0
+
+ vxlan_test_flush_by_flag $flush_by_flag $flag_1 $exp_flag_1 \
+ $flag_2 $exp_flag_2
+}
+
+vxlan_test_flush_by_several_args()
+{
+ local dst_ip_1=192.0.2.1
+ local dst_ip_2=192.0.2.2
+ local state_1=permanent
+ local state_2=static
+ local vni=3000
+ local port=1234
+ local nhid=100
+ local flag=router
+ local flush_args
+
+ ################### Flush by 2 args - nhid and flag ####################
+ $IP nexthop add id 10 via 192.0.2.1 fdb
+ $IP nexthop add id $nhid group 10 fdb
+
+ fdb_add_mac_pool_1 vx10 nhid $nhid $flag $state_1
+ fdb_add_mac_pool_2 vx10 nhid $nhid $flag $state_2
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len $state_1
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_2_len $state_2
+
+ run_cmd "$BRIDGE fdb flush dev vx10 nhid $nhid $flag"
+ log_test $? 0 "Flush FDB by dev vx10 nhid $nhid $flag"
+
+ # All entries should be flushed as 'state' is not an argument for flush
+ # filtering.
+ fdb_check_n_entries_by_dev_filter vx10 0 $state_1
+ log_test $? 0 "Test entries with state $state_1"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 $state_2
+ log_test $? 0 "Test entries with state $state_2"
+
+ ################ Flush by 3 args - VNI, port and dst_ip ################
+ fdb_add_mac_pool_1 vx10 vni $vni port $port dst $dst_ip_1
+ fdb_add_mac_pool_2 vx10 vni $vni port $port dst $dst_ip_2
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len dst $dst_ip_1
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_2_len dst $dst_ip_2
+
+ flush_args="vni $vni port $port dst $dst_ip_2"
+ run_cmd "$BRIDGE fdb flush dev vx10 $flush_args"
+ log_test $? 0 "Flush FDB by dev vx10 $flush_args"
+
+ # Only entries with $dst_ip_2 should be flushed, even the rest arguments
+ # match the filter, the flush should be AND of all the arguments.
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len dst $dst_ip_1
+ log_test $? 0 "Test entries with dst $dst_ip_1"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 dst $dst_ip_2
+ log_test $? 0 "Test entries with dst $dst_ip_2"
+}
+
+multicast_fdb_entries_add()
+{
+ mac=00:00:00:00:00:00
+ vnis=(2000 3000)
+
+ for vni in "${vnis[@]}"; do
+ $BRIDGE fdb append $mac dev vx10 dst 192.0.2.1 vni $vni \
+ src_vni 5000
+ $BRIDGE fdb append $mac dev vx10 dst 192.0.2.1 vni $vni \
+ port 1111
+ $BRIDGE fdb append $mac dev vx10 dst 192.0.2.2 vni $vni \
+ port 2222
+ done
+}
+
+vxlan_test_flush_by_remote_attributes()
+{
+ local flush_args
+
+ # Reconfigure vx10 with 'external' to get 'src_vni' details in
+ # 'bridge fdb' output
+ $IP link del dev vx10
+ $IP link add name vx10 type vxlan dstport "$VXPORT" external
+
+ # For multicat FDB entries, the VXLAN driver stores a linked list of
+ # remotes for a given key. Verify that only the expected remotes are
+ # flushed.
+ multicast_fdb_entries_add
+
+ ## Flush by 3 remote's attributes - destination IP, port and VNI ##
+ flush_args="dst 192.0.2.1 port 1111 vni 2000"
+ fdb_check_n_entries_by_dev_filter vx10 1 $flush_args
+
+ t0_n_entries=$($BRIDGE fdb show dev vx10 | wc -l)
+ run_cmd "$BRIDGE fdb flush dev vx10 $flush_args"
+ log_test $? 0 "Flush FDB by dev vx10 $flush_args"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 $flush_args
+
+ exp_n_entries=$((t0_n_entries - 1))
+ t1_n_entries=$($BRIDGE fdb show dev vx10 | wc -l)
+ [[ $t1_n_entries -eq $exp_n_entries ]]
+ log_test $? 0 "Check how many entries were flushed"
+
+ ## Flush by 2 remote's attributes - destination IP and port ##
+ flush_args="dst 192.0.2.2 port 2222"
+
+ fdb_check_n_entries_by_dev_filter vx10 2 $flush_args
+
+ t0_n_entries=$($BRIDGE fdb show dev vx10 | wc -l)
+ run_cmd "$BRIDGE fdb flush dev vx10 $flush_args"
+ log_test $? 0 "Flush FDB by dev vx10 $flush_args"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 $flush_args
+
+ exp_n_entries=$((t0_n_entries - 2))
+ t1_n_entries=$($BRIDGE fdb show dev vx10 | wc -l)
+ [[ $t1_n_entries -eq $exp_n_entries ]]
+ log_test $? 0 "Check how many entries were flushed"
+
+ ## Flush by source VNI, which is not remote's attribute and VNI ##
+ flush_args="vni 3000 src_vni 5000"
+
+ fdb_check_n_entries_by_dev_filter vx10 1 $flush_args
+
+ t0_n_entries=$($BRIDGE fdb show dev vx10 | wc -l)
+ run_cmd "$BRIDGE fdb flush dev vx10 $flush_args"
+ log_test $? 0 "Flush FDB by dev vx10 $flush_args"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 $flush_args
+
+ exp_n_entries=$((t0_n_entries -1))
+ t1_n_entries=$($BRIDGE fdb show dev vx10 | wc -l)
+ [[ $t1_n_entries -eq $exp_n_entries ]]
+ log_test $? 0 "Check how many entries were flushed"
+
+ # Flush by 1 remote's attribute - destination IP ##
+ flush_args="dst 192.0.2.1"
+
+ fdb_check_n_entries_by_dev_filter vx10 2 $flush_args
+
+ t0_n_entries=$($BRIDGE fdb show dev vx10 | wc -l)
+ run_cmd "$BRIDGE fdb flush dev vx10 $flush_args"
+ log_test $? 0 "Flush FDB by dev vx10 $flush_args"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 $flush_args
+
+ exp_n_entries=$((t0_n_entries -2))
+ t1_n_entries=$($BRIDGE fdb show dev vx10 | wc -l)
+ [[ $t1_n_entries -eq $exp_n_entries ]]
+ log_test $? 0 "Check how many entries were flushed"
+}
+
+bridge_test_flush_by_dev()
+{
+ local dst_ip=192.0.2.1
+ local br0_n_ent_t0=$($BRIDGE fdb show dev br0 | wc -l)
+ local br1_n_ent_t0=$($BRIDGE fdb show dev br1 | wc -l)
+
+ fdb_add_mac_pool_1 br0 dst $dst_ip
+ fdb_add_mac_pool_2 br1 dst $dst_ip
+
+ # Each 'fdb add' command adds one extra entry in the bridge with the
+ # default vlan.
+ local exp_br0_n_ent=$(($br0_n_ent_t0 + 2 * $mac_pool_1_len))
+ local exp_br1_n_ent=$(($br1_n_ent_t0 + 2 * $mac_pool_2_len))
+
+ fdb_check_n_entries_by_dev_filter br0 $exp_br0_n_ent
+ fdb_check_n_entries_by_dev_filter br1 $exp_br1_n_ent
+
+ run_cmd "$BRIDGE fdb flush dev br0"
+ log_test $? 0 "Flush FDB by dev br0"
+
+ # The default entry should not be flushed
+ fdb_check_n_entries_by_dev_filter br0 1
+ log_test $? 0 "Flush FDB by dev br0 - test br0 entries"
+
+ fdb_check_n_entries_by_dev_filter br1 $exp_br1_n_ent
+ log_test $? 0 "Flush FDB by dev br0 - test br1 entries"
+}
+
+bridge_test_flush_by_vlan()
+{
+ local vlan_1=10
+ local vlan_2=20
+ local vlan_1_ent_t0
+ local vlan_2_ent_t0
+
+ $BRIDGE vlan add vid $vlan_1 dev br0 self
+ $BRIDGE vlan add vid $vlan_2 dev br0 self
+
+ vlan_1_ent_t0=$($BRIDGE fdb show dev br0 | grep "vlan $vlan_1" | wc -l)
+ vlan_2_ent_t0=$($BRIDGE fdb show dev br0 | grep "vlan $vlan_2" | wc -l)
+
+ fdb_add_mac_pool_1 br0 vlan $vlan_1
+ fdb_add_mac_pool_2 br0 vlan $vlan_2
+
+ local exp_vlan_1_ent=$(($vlan_1_ent_t0 + $mac_pool_1_len))
+ local exp_vlan_2_ent=$(($vlan_2_ent_t0 + $mac_pool_2_len))
+
+ fdb_check_n_entries_by_dev_filter br0 $exp_vlan_1_ent vlan $vlan_1
+ fdb_check_n_entries_by_dev_filter br0 $exp_vlan_2_ent vlan $vlan_2
+
+ run_cmd "$BRIDGE fdb flush dev br0 vlan $vlan_1"
+ log_test $? 0 "Flush FDB by dev br0 and vlan $vlan_1"
+
+ fdb_check_n_entries_by_dev_filter br0 0 vlan $vlan_1
+ log_test $? 0 "Test entries with vlan $vlan_1"
+
+ fdb_check_n_entries_by_dev_filter br0 $exp_vlan_2_ent vlan $vlan_2
+ log_test $? 0 "Test entries with vlan $vlan_2"
+}
+
+bridge_vxlan_test_flush()
+{
+ local vlan_1=10
+ local dst_ip=192.0.2.1
+
+ $IP link set dev vx10 master br0
+ $BRIDGE vlan add vid $vlan_1 dev br0 self
+ $BRIDGE vlan add vid $vlan_1 dev vx10
+
+ fdb_add_mac_pool_1 vx10 vni 3000 dst $dst_ip self master
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len vlan $vlan_1
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len vni 3000
+
+ # Such command should fail in VXLAN driver as vlan is not supported,
+ # but the command should flush the entries in the bridge
+ run_cmd "$BRIDGE fdb flush dev vx10 vlan $vlan_1 master self"
+ log_test $? 255 \
+ "Flush FDB by dev vx10, vlan $vlan_1, master and self"
+
+ fdb_check_n_entries_by_dev_filter vx10 0 vlan $vlan_1
+ log_test $? 0 "Test entries with vlan $vlan_1"
+
+ fdb_check_n_entries_by_dev_filter vx10 $mac_pool_1_len dst $dst_ip
+ log_test $? 0 "Test entries with dst $dst_ip"
+}
+
+setup()
+{
+ IP="ip -netns ns1"
+ BRIDGE="bridge -netns ns1"
+
+ ip netns add ns1
+
+ $IP link add name vx10 type vxlan id 1000 dstport "$VXPORT"
+ $IP link add name vx20 type vxlan id 2000 dstport "$VXPORT"
+
+ $IP link add br0 type bridge vlan_filtering 1
+ $IP link add br1 type bridge vlan_filtering 1
+}
+
+cleanup()
+{
+ $IP link del dev br1
+ $IP link del dev br0
+
+ $IP link del dev vx20
+ $IP link del dev vx10
+
+ ip netns del ns1
+}
+
+################################################################################
+# main
+
+while getopts :t:pPhvw: o
+do
+ case $o in
+ t) TESTS=$OPTARG;;
+ p) PAUSE_ON_FAIL=yes;;
+ P) PAUSE=yes;;
+ v) VERBOSE=$(($VERBOSE + 1));;
+ w) PING_TIMEOUT=$OPTARG;;
+ h) usage; exit 0;;
+ *) usage; exit 1;;
+ esac
+done
+
+# make sure we don't pause twice
+[ "${PAUSE}" = "yes" ] && PAUSE_ON_FAIL=no
+
+if [ "$(id -u)" -ne 0 ];then
+ echo "SKIP: Need root privileges"
+ exit $ksft_skip;
+fi
+
+if [ ! -x "$(command -v ip)" ]; then
+ echo "SKIP: Could not run test without ip tool"
+ exit $ksft_skip
+fi
+
+# Check a flag that is added to flush command as part of VXLAN flush support
+bridge fdb help 2>&1 | grep -q "\[no\]router"
+if [ $? -ne 0 ]; then
+ echo "SKIP: iproute2 too old, missing flush command for VXLAN"
+ exit $ksft_skip
+fi
+
+ip link add dev vx10 type vxlan id 1000 2> /dev/null
+out=$(bridge fdb flush dev vx10 2>&1 | grep -q "Operation not supported")
+if [ $? -eq 0 ]; then
+ echo "SKIP: kernel lacks vxlan flush support"
+ exit $ksft_skip
+fi
+ip link del dev vx10
+
+for t in $TESTS
+do
+ setup; $t; cleanup;
+done