Commit 7497b0af authored by David S. Miller's avatar David S. Miller

Merge branch 'vxlan-fdb-flushing'

Amit Cohen says:

====================
Extend VXLAN driver to support FDB flushing

The merge commit 92716869 ("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: default avatarNikolay Aleksandrov <razor@blackwall.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 0e6bb5b7 f826f2a2
...@@ -3022,9 +3022,101 @@ static int vxlan_open(struct net_device *dev) ...@@ -3022,9 +3022,101 @@ static int vxlan_open(struct net_device *dev)
return ret; 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 */ /* 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; unsigned int h;
for (h = 0; h < FDB_HASH_SIZE; ++h) { for (h = 0; h < FDB_HASH_SIZE; ++h) {
...@@ -3034,28 +3126,122 @@ static void vxlan_flush(struct vxlan_dev *vxlan, bool do_all) ...@@ -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]) { hlist_for_each_safe(p, n, &vxlan->fdb_head[h]) {
struct vxlan_fdb *f struct vxlan_fdb *f
= container_of(p, struct vxlan_fdb, hlist); = container_of(p, struct vxlan_fdb, hlist);
if (!do_all && (f->state & (NUD_PERMANENT | NUD_NOARP)))
continue; if (!vxlan_fdb_flush_matches(f, vxlan, desc))
/* the all_zeros_mac entry is deleted at vxlan_uninit */
if (is_zero_ether_addr(f->eth_addr) &&
f->vni == vxlan->cfg.vni)
continue; 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); vxlan_fdb_destroy(vxlan, f, true, true);
} }
spin_unlock_bh(&vxlan->hash_lock[h]); 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 */ /* Cleanup timer and forwarding table on shutdown */
static int vxlan_stop(struct net_device *dev) static int vxlan_stop(struct net_device *dev)
{ {
struct vxlan_dev *vxlan = netdev_priv(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); vxlan_multicast_leave(vxlan);
del_timer_sync(&vxlan->age_timer); del_timer_sync(&vxlan->age_timer);
vxlan_flush(vxlan, false); vxlan_flush(vxlan, &desc);
vxlan_sock_release(vxlan); vxlan_sock_release(vxlan);
return 0; return 0;
...@@ -3142,6 +3328,7 @@ static const struct net_device_ops vxlan_netdev_ether_ops = { ...@@ -3142,6 +3328,7 @@ static const struct net_device_ops vxlan_netdev_ether_ops = {
.ndo_set_mac_address = eth_mac_addr, .ndo_set_mac_address = eth_mac_addr,
.ndo_fdb_add = vxlan_fdb_add, .ndo_fdb_add = vxlan_fdb_add,
.ndo_fdb_del = vxlan_fdb_delete, .ndo_fdb_del = vxlan_fdb_delete,
.ndo_fdb_del_bulk = vxlan_fdb_delete_bulk,
.ndo_fdb_dump = vxlan_fdb_dump, .ndo_fdb_dump = vxlan_fdb_dump,
.ndo_fdb_get = vxlan_fdb_get, .ndo_fdb_get = vxlan_fdb_get,
.ndo_mdb_add = vxlan_mdb_add, .ndo_mdb_add = vxlan_mdb_add,
...@@ -4294,8 +4481,12 @@ static int vxlan_changelink(struct net_device *dev, struct nlattr *tb[], ...@@ -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) static void vxlan_dellink(struct net_device *dev, struct list_head *head)
{ {
struct vxlan_dev *vxlan = netdev_priv(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,
};
vxlan_flush(vxlan, true); vxlan_flush(vxlan, &desc);
list_del(&vxlan->next); list_del(&vxlan->next);
unregister_netdevice_queue(dev, head); unregister_netdevice_queue(dev, head);
......
...@@ -1287,9 +1287,7 @@ struct netdev_net_notifier { ...@@ -1287,9 +1287,7 @@ struct netdev_net_notifier {
* struct net_device *dev, * struct net_device *dev,
* const unsigned char *addr, u16 vid) * const unsigned char *addr, u16 vid)
* Deletes the FDB entry from dev coresponding to addr. * Deletes the FDB entry from dev coresponding to addr.
* int (*ndo_fdb_del_bulk)(struct ndmsg *ndm, struct nlattr *tb[], * int (*ndo_fdb_del_bulk)(struct nlmsghdr *nlh, struct net_device *dev,
* struct net_device *dev,
* u16 vid,
* struct netlink_ext_ack *extack); * struct netlink_ext_ack *extack);
* int (*ndo_fdb_dump)(struct sk_buff *skb, struct netlink_callback *cb, * int (*ndo_fdb_dump)(struct sk_buff *skb, struct netlink_callback *cb,
* struct net_device *dev, struct net_device *filter_dev, * struct net_device *dev, struct net_device *filter_dev,
...@@ -1564,10 +1562,8 @@ struct net_device_ops { ...@@ -1564,10 +1562,8 @@ struct net_device_ops {
struct net_device *dev, struct net_device *dev,
const unsigned char *addr, const unsigned char *addr,
u16 vid, struct netlink_ext_ack *extack); u16 vid, struct netlink_ext_ack *extack);
int (*ndo_fdb_del_bulk)(struct ndmsg *ndm, int (*ndo_fdb_del_bulk)(struct nlmsghdr *nlh,
struct nlattr *tb[],
struct net_device *dev, struct net_device *dev,
u16 vid,
struct netlink_ext_ack *extack); struct netlink_ext_ack *extack);
int (*ndo_fdb_dump)(struct sk_buff *skb, int (*ndo_fdb_dump)(struct sk_buff *skb,
struct netlink_callback *cb, struct netlink_callback *cb,
......
...@@ -661,14 +661,30 @@ static int __fdb_flush_validate_ifindex(const struct net_bridge *br, ...@@ -661,14 +661,30 @@ static int __fdb_flush_validate_ifindex(const struct net_bridge *br,
return 0; return 0;
} }
int br_fdb_delete_bulk(struct ndmsg *ndm, struct nlattr *tb[], static const struct nla_policy br_fdb_del_bulk_policy[NDA_MAX + 1] = {
struct net_device *dev, u16 vid, [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) struct netlink_ext_ack *extack)
{ {
u8 ndm_flags = ndm->ndm_flags & ~FDB_FLUSH_IGNORED_NDM_FLAGS; struct net_bridge_fdb_flush_desc desc = {};
struct net_bridge_fdb_flush_desc desc = { .vlan_id = vid }; struct ndmsg *ndm = nlmsg_data(nlh);
struct net_bridge_port *p = NULL; struct net_bridge_port *p = NULL;
struct nlattr *tb[NDA_MAX + 1];
struct net_bridge *br; 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)) { if (netif_is_bridge_master(dev)) {
br = netdev_priv(dev); br = netdev_priv(dev);
...@@ -681,6 +697,9 @@ int br_fdb_delete_bulk(struct ndmsg *ndm, struct nlattr *tb[], ...@@ -681,6 +697,9 @@ int br_fdb_delete_bulk(struct ndmsg *ndm, struct nlattr *tb[],
br = p->br; br = p->br;
} }
if (tb[NDA_VLAN])
desc.vlan_id = nla_get_u16(tb[NDA_VLAN]);
if (ndm_flags & ~FDB_FLUSH_ALLOWED_NDM_FLAGS) { if (ndm_flags & ~FDB_FLUSH_ALLOWED_NDM_FLAGS) {
NL_SET_ERR_MSG(extack, "Unsupported fdb flush ndm flag bits set"); NL_SET_ERR_MSG(extack, "Unsupported fdb flush ndm flag bits set");
return -EINVAL; return -EINVAL;
...@@ -703,7 +722,7 @@ int br_fdb_delete_bulk(struct ndmsg *ndm, struct nlattr *tb[], ...@@ -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); desc.flags_mask |= __ndm_flags_to_fdb_flags(ndm_flags_mask);
} }
if (tb[NDA_IFINDEX]) { 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); err = __fdb_flush_validate_ifindex(br, ifidx, extack);
if (err) if (err)
......
...@@ -847,8 +847,7 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, ...@@ -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[], int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[],
struct net_device *dev, const unsigned char *addr, u16 vid, struct net_device *dev, const unsigned char *addr, u16 vid,
struct netlink_ext_ack *extack); struct netlink_ext_ack *extack);
int br_fdb_delete_bulk(struct ndmsg *ndm, struct nlattr *tb[], int br_fdb_delete_bulk(struct nlmsghdr *nlh, struct net_device *dev,
struct net_device *dev, u16 vid,
struct netlink_ext_ack *extack); struct netlink_ext_ack *extack);
int br_fdb_add(struct ndmsg *nlh, struct nlattr *tb[], struct net_device *dev, int br_fdb_add(struct ndmsg *nlh, struct nlattr *tb[], struct net_device *dev,
const unsigned char *addr, u16 vid, u16 nlh_flags, const unsigned char *addr, u16 vid, u16 nlh_flags,
......
...@@ -4367,13 +4367,6 @@ int ndo_dflt_fdb_del(struct ndmsg *ndm, ...@@ -4367,13 +4367,6 @@ int ndo_dflt_fdb_del(struct ndmsg *ndm,
} }
EXPORT_SYMBOL(ndo_dflt_fdb_del); 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, static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack) struct netlink_ext_ack *extack)
{ {
...@@ -4394,8 +4387,10 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -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, err = nlmsg_parse_deprecated(nlh, sizeof(*ndm), tb, NDA_MAX,
NULL, extack); NULL, extack);
} else { } else {
err = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, /* For bulk delete, the drivers will parse the message with
fdb_del_bulk_policy, extack); * policy.
*/
err = nlmsg_parse(nlh, sizeof(*ndm), tb, NDA_MAX, NULL, extack);
} }
if (err < 0) if (err < 0)
return err; return err;
...@@ -4418,6 +4413,10 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -4418,6 +4413,10 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
return -EINVAL; return -EINVAL;
} }
addr = nla_data(tb[NDA_LLADDR]); addr = nla_data(tb[NDA_LLADDR]);
err = fdb_vid_parse(tb[NDA_VLAN], &vid, extack);
if (err)
return err;
} }
if (dev->type != ARPHRD_ETHER) { if (dev->type != ARPHRD_ETHER) {
...@@ -4425,10 +4424,6 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -4425,10 +4424,6 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh,
return -EINVAL; return -EINVAL;
} }
err = fdb_vid_parse(tb[NDA_VLAN], &vid, extack);
if (err)
return err;
err = -EOPNOTSUPP; err = -EOPNOTSUPP;
/* Support fdb on master device the net/bridge default case */ /* 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, ...@@ -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); err = ops->ndo_fdb_del(ndm, tb, dev, addr, vid, extack);
} else { } else {
if (ops->ndo_fdb_del_bulk) if (ops->ndo_fdb_del_bulk)
err = ops->ndo_fdb_del_bulk(ndm, tb, dev, vid, err = ops->ndo_fdb_del_bulk(nlh, dev, extack);
extack);
} }
if (err) if (err)
...@@ -4464,8 +4458,7 @@ static int rtnl_fdb_del(struct sk_buff *skb, struct nlmsghdr *nlh, ...@@ -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 */ /* in case err was cleared by NTF_MASTER call */
err = -EOPNOTSUPP; err = -EOPNOTSUPP;
if (ops->ndo_fdb_del_bulk) if (ops->ndo_fdb_del_bulk)
err = ops->ndo_fdb_del_bulk(ndm, tb, dev, vid, err = ops->ndo_fdb_del_bulk(nlh, dev, extack);
extack);
} }
if (!err) { if (!err) {
......
...@@ -89,6 +89,7 @@ TEST_PROGS += test_vxlan_mdb.sh ...@@ -89,6 +89,7 @@ TEST_PROGS += test_vxlan_mdb.sh
TEST_PROGS += test_bridge_neigh_suppress.sh TEST_PROGS += test_bridge_neigh_suppress.sh
TEST_PROGS += test_vxlan_nolocalbypass.sh TEST_PROGS += test_vxlan_nolocalbypass.sh
TEST_PROGS += test_bridge_backup_port.sh TEST_PROGS += test_bridge_backup_port.sh
TEST_PROGS += fdb_flush.sh
TEST_FILES := settings TEST_FILES := settings
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment