Considering this as an RFC given that net-next is closed... Jonathan Toppins <jtoppins@xxxxxxxxxx> wrote: >Attempt to replicate the OvS SLB Bonding[1] feature by preventing >duplicate frame delivery on a bond whos members are connected to >physically different switches. > >Combining this feature with vlan+srcmac hash policy allows a user to >create an access network without the need to use expensive switches that >support features like Cisco's VCP. Could you describe this use case / implementation in a bit more detail? I.e., how exactly that configuration works. I don't think this patch is replicating everything in the OVS SLB documentation. >[1] https://docs.openvswitch.org/en/latest/topics/bonding/#slb-bonding > >Co-developed-by: Long Xin <lxin@xxxxxxxxxx> >Signed-off-by: Long Xin <lxin@xxxxxxxxxx> >Signed-off-by: Jonathan Toppins <jtoppins@xxxxxxxxxx> >--- > Documentation/networking/bonding.rst | 19 +++ > drivers/net/bonding/Makefile | 2 +- > drivers/net/bonding/bond_mac_filter.c | 222 ++++++++++++++++++++++++++ > drivers/net/bonding/bond_mac_filter.h | 40 +++++ > drivers/net/bonding/bond_main.c | 26 +++ > drivers/net/bonding/bond_netlink.c | 13 ++ > drivers/net/bonding/bond_options.c | 58 ++++++- > drivers/net/bonding/bonding_priv.h | 1 + > include/net/bond_options.h | 1 + > include/net/bonding.h | 3 + > include/uapi/linux/if_link.h | 1 + > 11 files changed, 384 insertions(+), 2 deletions(-) > create mode 100644 drivers/net/bonding/bond_mac_filter.c > create mode 100644 drivers/net/bonding/bond_mac_filter.h > >diff --git a/Documentation/networking/bonding.rst b/Documentation/networking/bonding.rst >index 525e6842dd33..a5a1669d3efe 100644 >--- a/Documentation/networking/bonding.rst >+++ b/Documentation/networking/bonding.rst >@@ -550,6 +550,25 @@ lacp_rate > > The default is slow. > >+mac_filter >+ >+ Tells the bonding device to drop frames received who's source MAC >+ address matches entries in a filter table. The filter table is >+ populated when the bond transmits frames. This is similar in >+ concept to the MAC learning table implemented in the bridge code. >+ >+ This filtering is only enabled for the balance-xor bonding mode. >+ >+ off or 0 >+ Turns the feature off >+ >+ number >+ A number greater than zero turns the feature on and sets >+ the maximum number of MAC addresses to store in the hash >+ table. >+ >+ The default is off. >+ > max_bonds > > Specifies the number of bonding devices to create for this >diff --git a/drivers/net/bonding/Makefile b/drivers/net/bonding/Makefile >index 30e8ae3da2da..5dbc360a8522 100644 >--- a/drivers/net/bonding/Makefile >+++ b/drivers/net/bonding/Makefile >@@ -5,7 +5,7 @@ > > obj-$(CONFIG_BONDING) += bonding.o > >-bonding-objs := bond_main.o bond_3ad.o bond_alb.o bond_sysfs.o bond_sysfs_slave.o bond_debugfs.o bond_netlink.o bond_options.o >+bonding-objs := bond_main.o bond_3ad.o bond_alb.o bond_sysfs.o bond_sysfs_slave.o bond_debugfs.o bond_netlink.o bond_options.o bond_mac_filter.o > > proc-$(CONFIG_PROC_FS) += bond_procfs.o > bonding-objs += $(proc-y) >diff --git a/drivers/net/bonding/bond_mac_filter.c b/drivers/net/bonding/bond_mac_filter.c >new file mode 100644 >index 000000000000..a16a1a000f05 >--- /dev/null >+++ b/drivers/net/bonding/bond_mac_filter.c >@@ -0,0 +1,222 @@ >+// SPDX-License-Identifier: GPL-2.0-only >+/* >+ * Filter received frames based on MAC addresses "behind" the bond. >+ */ >+ >+#include "bonding_priv.h" >+ >+/* -------------- Cache Management -------------- */ I don't think this header adds anything, given that there's not really a lot in the section. >+static struct kmem_cache *bond_mac_cache __read_mostly; >+ >+int __init bond_mac_cache_init(void) >+{ >+ bond_mac_cache = kmem_cache_create("bond_mac_cache", >+ sizeof(struct bond_mac_cache_entry), >+ 0, SLAB_HWCACHE_ALIGN, NULL); >+ if (!bond_mac_cache) >+ return -ENOMEM; >+ return 0; >+} >+ >+void bond_mac_cache_destroy(void) >+{ >+ kmem_cache_destroy(bond_mac_cache); >+} There are a lot of the above sort of wrapper functions that are only ever called once. Some of these, e.g., mac_delete, below, I agree with, as the call site is nested fairly deep and the function is non-trivial; or, mac_delete_rcu, which is used as a callback. The above two, though, I don't see a justification for, along with hold_time and maybe a couple others, below. In my opinion, over-abstracting these trivial things with one call site makes the code harder to follow. >+ >+/* -------------- Hash Table Management -------------- */ >+ >+static const struct rhashtable_params bond_rht_params = { >+ .head_offset = offsetof(struct bond_mac_cache_entry, rhnode), >+ .key_offset = offsetof(struct bond_mac_cache_entry, key), >+ .key_len = sizeof(struct mac_addr), >+ .automatic_shrinking = true, >+}; >+ >+static inline unsigned long hold_time(const struct bonding *bond) >+{ >+ return msecs_to_jiffies(5000); >+} This shouldn't be a magic number, and if it's an important timeout, should it be configurable? >+ >+static bool has_expired(const struct bonding *bond, >+ struct bond_mac_cache_entry *mac) >+{ >+ return time_before_eq(mac->used + hold_time(bond), jiffies); >+} >+ >+static void mac_delete_rcu(struct callback_head *head) >+{ >+ kmem_cache_free(bond_mac_cache, >+ container_of(head, struct bond_mac_cache_entry, rcu)); >+} >+ >+static int mac_delete(struct bonding *bond, >+ struct bond_mac_cache_entry *entry) >+{ >+ int ret; >+ >+ ret = rhashtable_remove_fast(bond->mac_filter_tbl, >+ &entry->rhnode, >+ bond->mac_filter_tbl->p); >+ set_bit(BOND_MAC_DEAD, &entry->flags); >+ call_rcu(&entry->rcu, mac_delete_rcu); >+ return ret; >+} >+ >+void bond_mac_hash_release_entries(struct work_struct *work) >+{ >+ struct bonding *bond = container_of(work, struct bonding, >+ mac_work.work); >+ struct rhashtable_iter iter; >+ struct bond_mac_cache_entry *entry; >+ unsigned long flags; >+ >+ rhashtable_walk_enter(bond->mac_filter_tbl, &iter); >+ rhashtable_walk_start(&iter); >+ while ((entry = rhashtable_walk_next(&iter)) != NULL) { >+ if (IS_ERR(entry)) >+ continue; >+ >+ spin_lock_irqsave(&entry->lock, flags); >+ if (has_expired(bond, entry)) >+ mac_delete(bond, entry); >+ spin_unlock_irqrestore(&entry->lock, flags); >+ } >+ rhashtable_walk_stop(&iter); >+ rhashtable_walk_exit(&iter); >+ queue_delayed_work(bond->wq, &bond->mac_work, >+ msecs_to_jiffies(5 * 60 * 1000)); >+} >+ >+int bond_mac_hash_init(struct bonding *bond) >+{ >+ int rc; As a point of style, (almost) everywhere else in bonding uses "ret" for a return value. The exceptions are largely my doing, but, still, it'd be nice to be mostly consistent in nomenclature. >+ >+ netdev_dbg(bond->dev, "mac_filter: alloc memory for hash table\n"); >+ bond->mac_filter_tbl = kzalloc(sizeof(*bond->mac_filter_tbl), >+ GFP_KERNEL); >+ if (!bond->mac_filter_tbl) >+ return -ENOMEM; >+ >+ rc = rhashtable_init(bond->mac_filter_tbl, &bond_rht_params); >+ if (rc) >+ kfree(bond->mac_filter_tbl); >+ >+ return rc; >+} >+ >+static void bond_mac_free_entry(void *entry, void *ctx) >+{ >+ kmem_cache_free((struct kmem_cache *)ctx, entry); >+} >+ >+void bond_mac_hash_destroy(struct bonding *bond) >+{ >+ if (bond->mac_filter_tbl) { >+ rhashtable_free_and_destroy(bond->mac_filter_tbl, >+ bond_mac_free_entry, >+ bond_mac_cache); >+ kfree(bond->mac_filter_tbl); >+ bond->mac_filter_tbl = NULL; >+ } >+} >+ >+static int mac_create(struct bonding *bond, const u8 *addr) >+{ >+ struct bond_mac_cache_entry *entry; >+ int ret; >+ >+ entry = kmem_cache_alloc(bond_mac_cache, GFP_ATOMIC); >+ if (!entry) >+ return -ENOMEM; >+ spin_lock_init(&entry->lock); >+ memcpy(&entry->key, addr, sizeof(entry->key)); >+ entry->used = jiffies; >+ ret = rhashtable_lookup_insert_fast(bond->mac_filter_tbl, >+ &entry->rhnode, >+ bond->mac_filter_tbl->p); >+ if (ret) { >+ kmem_cache_free(bond_mac_cache, entry); >+ entry = NULL; >+ if (ret == -EEXIST) >+ return 0; >+ pr_err_once("Failed to insert mac entry %d\n", ret); >+ } >+ return ret; >+} >+ >+static struct bond_mac_cache_entry *mac_find(struct bonding *bond, >+ const u8 *addr) >+{ >+ struct mac_addr key; >+ >+ memcpy(&key, addr, sizeof(key)); >+ return rhashtable_lookup(bond->mac_filter_tbl, &key, >+ bond->mac_filter_tbl->p); >+} >+ >+inline void mac_update(struct bond_mac_cache_entry *entry) >+{ >+ entry->used = jiffies; >+} >+ >+int bond_mac_insert(struct bonding *bond, const u8 *addr) >+{ >+ struct bond_mac_cache_entry *entry; >+ int rc = 0; >+ >+ if (!is_valid_ether_addr(addr)) >+ return -EINVAL; >+ >+ rcu_read_lock(); >+ entry = mac_find(bond, addr); >+ if (entry) { >+ unsigned long flags; >+ >+ spin_lock_irqsave(&entry->lock, flags); >+ if (!test_bit(BOND_MAC_DEAD, &entry->flags)) { >+ mac_update(entry); >+ spin_unlock_irqrestore(&entry->lock, flags); >+ goto out; >+ } >+ spin_unlock_irqrestore(&entry->lock, flags); This seems really expensive, as it will add a spin_lock_irqsave round trip for almost every packet transmitted when mac_filter is enabled (as this will be called by bond_xmit_3ad_xor_slave_get). >+ } >+ >+ rc = mac_create(bond, addr); >+ >+out: >+ rcu_read_unlock(); >+ return rc; >+} >+ >+int bond_xor_recv(const struct sk_buff *skb, struct bonding *bond, >+ struct slave *slave) >+{ >+ const struct ethhdr *mac_hdr; >+ struct bond_mac_cache_entry *entry; >+ int rc = RX_HANDLER_PASS; >+ >+ mac_hdr = (struct ethhdr *)skb_mac_header(skb); >+ rcu_read_lock(); >+ if (is_multicast_ether_addr(mac_hdr->h_dest) && >+ slave != rcu_dereference(bond->curr_active_slave)) { >+ rc = RX_HANDLER_CONSUMED; >+ goto out; >+ } >+ >+ entry = mac_find(bond, mac_hdr->h_source); >+ if (entry) { >+ unsigned long flags; >+ bool expired; >+ >+ spin_lock_irqsave(&entry->lock, flags); >+ expired = has_expired(bond, entry); >+ spin_unlock_irqrestore(&entry->lock, flags); >+ if (!expired) >+ rc = RX_HANDLER_CONSUMED; >+ } As above, really expensive, except for incoming packets here (since this is called as the recv_probe). >+ >+out: >+ rcu_read_unlock(); >+ return rc; >+} >diff --git a/drivers/net/bonding/bond_mac_filter.h b/drivers/net/bonding/bond_mac_filter.h >new file mode 100644 >index 000000000000..0cfcc5653e7e >--- /dev/null >+++ b/drivers/net/bonding/bond_mac_filter.h >@@ -0,0 +1,40 @@ >+/* SPDX-License-Identifier: GPL-2.0-only >+ * >+ * Filter received frames based on MAC addresses "behind" the bond. >+ */ >+ >+#ifndef _BOND_MAC_FILTER_H >+#define _BOND_MAC_FILTER_H >+#include <net/bonding.h> >+#include <linux/spinlock.h> >+#include <linux/rhashtable.h> >+ >+enum { >+ BOND_MAC_DEAD, >+ BOND_MAC_LOCKED, >+ BOND_MAC_STATIC, >+}; >+ >+struct bond_mac_cache_entry { >+ struct rhash_head rhnode; >+ struct mac_addr key; >+ >+ spinlock_t lock; /* protects used member */ >+ unsigned long flags; >+ unsigned long used; >+ struct rcu_head rcu; >+}; >+ >+int __init bond_mac_cache_init(void); >+void bond_mac_cache_destroy(void); >+ >+void bond_mac_hash_release_entries(struct work_struct *work); >+int bond_mac_hash_init(struct bonding *bond); >+void bond_mac_hash_destroy(struct bonding *bond); >+ >+int bond_mac_insert(struct bonding *bond, const u8 *addr); >+int bond_xor_recv(const struct sk_buff *skb, >+ struct bonding *bond, >+ struct slave *slave); >+ >+#endif >diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c >index 15eddca7b4b6..f5a8a50e9116 100644 >--- a/drivers/net/bonding/bond_main.c >+++ b/drivers/net/bonding/bond_main.c >@@ -1549,6 +1549,10 @@ static rx_handler_result_t bond_handle_frame(struct sk_buff **pskb) > return RX_HANDLER_EXACT; > } > >+ /* this function should not rely on the recv_probe to set ret >+ * correctly >+ */ >+ ret = RX_HANDLER_ANOTHER; This change is overriding the return from a recv_probe added by this patch (bond_xor_recv can return RX_HANDLER_PASS). Why? Also, I don't agree with the comment; the recv_probe return value by design affects the return value from bond_handle_frame. > skb->dev = bond->dev; > > if (BOND_MODE(bond) == BOND_MODE_ALB && >@@ -4117,6 +4121,7 @@ void bond_work_init_all(struct bonding *bond) > INIT_DELAYED_WORK(&bond->arp_work, bond_arp_monitor); > INIT_DELAYED_WORK(&bond->ad_work, bond_3ad_state_machine_handler); > INIT_DELAYED_WORK(&bond->slave_arr_work, bond_slave_arr_handler); >+ INIT_DELAYED_WORK(&bond->mac_work, bond_mac_hash_release_entries); > } > > static void bond_work_cancel_all(struct bonding *bond) >@@ -4127,6 +4132,7 @@ static void bond_work_cancel_all(struct bonding *bond) > cancel_delayed_work_sync(&bond->ad_work); > cancel_delayed_work_sync(&bond->mcast_work); > cancel_delayed_work_sync(&bond->slave_arr_work); >+ cancel_delayed_work_sync(&bond->mac_work); > } > > static int bond_open(struct net_device *bond_dev) >@@ -4174,6 +4180,11 @@ static int bond_open(struct net_device *bond_dev) > bond_3ad_initiate_agg_selection(bond, 1); > } > >+ if (bond->params.mac_filter) { >+ bond->recv_probe = bond_xor_recv; >+ queue_delayed_work(bond->wq, &bond->mac_work, 0); >+ } >+ > if (bond_mode_can_use_xmit_hash(bond)) > bond_update_slave_arr(bond, NULL); > >@@ -5043,6 +5054,13 @@ static struct slave *bond_xmit_3ad_xor_slave_get(struct bonding *bond, > if (unlikely(!count)) > return NULL; > >+ if (bond->params.mac_filter) { >+ const struct ethhdr *mac_hdr; >+ >+ mac_hdr = (struct ethhdr *)skb_mac_header(skb); >+ if (bond_mac_insert(bond, mac_hdr->h_source)) >+ return NULL; >+ } > slave = slaves->arr[hash % count]; > return slave; > } >@@ -5660,6 +5678,8 @@ static void bond_destructor(struct net_device *bond_dev) > > if (bond->rr_tx_counter) > free_percpu(bond->rr_tx_counter); >+ >+ bond_mac_hash_destroy(bond); > } > > void bond_setup(struct net_device *bond_dev) >@@ -6115,6 +6135,7 @@ static int bond_check_params(struct bond_params *params) > params->downdelay = downdelay; > params->peer_notif_delay = 0; > params->use_carrier = use_carrier; >+ params->mac_filter = 0; > params->lacp_active = 1; > params->lacp_fast = lacp_fast; > params->primary[0] = 0; >@@ -6317,6 +6338,10 @@ static int __init bonding_init(void) > goto err; > } > >+ res = bond_mac_cache_init(); >+ if (res) >+ goto err; >+ > skb_flow_dissector_init(&flow_keys_bonding, > flow_keys_bonding_keys, > ARRAY_SIZE(flow_keys_bonding_keys)); >@@ -6346,6 +6371,7 @@ static void __exit bonding_exit(void) > /* Make sure we don't have an imbalance on our netpoll blocking */ > WARN_ON(atomic_read(&netpoll_block_tx)); > #endif >+ bond_mac_cache_destroy(); > } > > module_init(bonding_init); >diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c >index f427fa1737c7..249d79b6e21a 100644 >--- a/drivers/net/bonding/bond_netlink.c >+++ b/drivers/net/bonding/bond_netlink.c >@@ -113,6 +113,7 @@ static const struct nla_policy bond_policy[IFLA_BOND_MAX + 1] = { > [IFLA_BOND_PEER_NOTIF_DELAY] = { .type = NLA_U32 }, > [IFLA_BOND_MISSED_MAX] = { .type = NLA_U8 }, > [IFLA_BOND_NS_IP6_TARGET] = { .type = NLA_NESTED }, >+ [IFLA_BOND_MAC_FILTER] = { .type = NLA_U8 }, > }; > > static const struct nla_policy bond_slave_policy[IFLA_BOND_SLAVE_MAX + 1] = { >@@ -498,6 +499,14 @@ static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[], > if (err) > return err; > } >+ if (data[IFLA_BOND_MAC_FILTER]) { >+ u8 mac_filter = nla_get_u8(data[IFLA_BOND_MAC_FILTER]); >+ >+ bond_opt_initval(&newval, mac_filter); >+ err = __bond_opt_set(bond, BOND_OPT_MAC_FILTER, &newval); >+ if (err) >+ return err; >+ } > > return 0; > } >@@ -565,6 +574,7 @@ static size_t bond_get_size(const struct net_device *bond_dev) > /* IFLA_BOND_NS_IP6_TARGET */ > nla_total_size(sizeof(struct nlattr)) + > nla_total_size(sizeof(struct in6_addr)) * BOND_MAX_NS_TARGETS + >+ nla_total_size(sizeof(u8)) + /* IFLA_BOND_MAC_FILTER */ > 0; > } > >@@ -723,6 +733,9 @@ static int bond_fill_info(struct sk_buff *skb, > if (nla_put_u8(skb, IFLA_BOND_MISSED_MAX, > bond->params.missed_max)) > goto nla_put_failure; >+ if (nla_put_u8(skb, IFLA_BOND_MAC_FILTER, >+ bond->params.mac_filter)) >+ goto nla_put_failure; > > if (BOND_MODE(bond) == BOND_MODE_8023AD) { > struct ad_info info; >diff --git a/drivers/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c >index 64f7db2627ce..0f6036ff7b86 100644 >--- a/drivers/net/bonding/bond_options.c >+++ b/drivers/net/bonding/bond_options.c >@@ -15,6 +15,7 @@ > #include <linux/sched/signal.h> > > #include <net/bonding.h> >+#include "bonding_priv.h" > > static int bond_option_active_slave_set(struct bonding *bond, > const struct bond_opt_value *newval); >@@ -84,6 +85,8 @@ static int bond_option_ad_user_port_key_set(struct bonding *bond, > const struct bond_opt_value *newval); > static int bond_option_missed_max_set(struct bonding *bond, > const struct bond_opt_value *newval); >+static int bond_option_mac_filter_set(struct bonding *bond, >+ const struct bond_opt_value *newval); > > > static const struct bond_opt_value bond_mode_tbl[] = { >@@ -226,6 +229,12 @@ static const struct bond_opt_value bond_missed_max_tbl[] = { > { NULL, -1, 0}, > }; > >+static const struct bond_opt_value bond_mac_filter_tbl[] = { >+ { "off", 0, BOND_VALFLAG_MIN | BOND_VALFLAG_DEFAULT}, >+ { "maxval", 18, BOND_VALFLAG_MAX}, What's the magic number 18? >+ { NULL, -1, 0} >+}; >+ > static const struct bond_option bond_opts[BOND_OPT_LAST] = { > [BOND_OPT_MODE] = { > .id = BOND_OPT_MODE, >@@ -482,7 +491,16 @@ static const struct bond_option bond_opts[BOND_OPT_LAST] = { > .desc = "Delay between each peer notification on failover event, in milliseconds", > .values = bond_intmax_tbl, > .set = bond_option_peer_notif_delay_set >- } >+ }, >+ [BOND_OPT_MAC_FILTER] = { >+ .id = BOND_OPT_MAC_FILTER, >+ .name = "mac_filter", >+ .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_XOR)), >+ .desc = "filter received frames based on MAC addresses that have transmitted from the bond, number of MAC addresses to track", >+ .flags = BOND_OPTFLAG_NOSLAVES | BOND_OPTFLAG_IFDOWN, >+ .values = bond_mac_filter_tbl, >+ .set = bond_option_mac_filter_set >+ }, > }; > > /* Searches for an option by name */ >@@ -1035,6 +1053,44 @@ static int bond_option_use_carrier_set(struct bonding *bond, > return 0; > } > >+static int bond_option_mac_filter_set(struct bonding *bond, >+ const struct bond_opt_value *newval) >+{ >+ int rc = 0; >+ u8 prev = bond->params.mac_filter; >+ >+ if (newval->value && bond->params.arp_interval) { >+ netdev_err(bond->dev, "ARP monitoring cannot be used with MAC Filtering.\n"); >+ rc = -EPERM; >+ goto err; >+ } What happens if a user (a) switches to ARP monitor with arp_validate in balance-xor mode after mac_filter is enabled, or, (b) changes the mode to something other than balance-xor with mac_filter enabled (both by changing the configuration in real time)? -J >+ >+ netdev_dbg(bond->dev, "Setting mac_filter to %llu\n", >+ newval->value); >+ bond->params.mac_filter = newval->value; >+ >+ if (prev == 0 && bond->params.mac_filter > 0) { >+ rc = bond_mac_hash_init(bond); >+ if (rc) >+ goto err; >+ } else if (prev > 0 && bond->params.mac_filter == 0) >+ bond_mac_hash_destroy(bond); >+ >+ if (bond->mac_filter_tbl) { >+ bond->mac_filter_tbl->p.max_size = >+ 1 << bond->params.mac_filter; >+ netdev_dbg(bond->dev, "mac_filter hash table size: %d\n", >+ bond->mac_filter_tbl->p.max_size); >+ } >+ >+out: >+ return rc; >+ >+err: >+ bond->params.mac_filter = 0; >+ goto out; >+} >+ > /* There are two tricky bits here. First, if ARP monitoring is activated, then > * we must disable MII monitoring. Second, if the ARP timer isn't running, > * we must start it. >diff --git a/drivers/net/bonding/bonding_priv.h b/drivers/net/bonding/bonding_priv.h >index 48cdf3a49a7d..0299f8bcb5fd 100644 >--- a/drivers/net/bonding/bonding_priv.h >+++ b/drivers/net/bonding/bonding_priv.h >@@ -15,6 +15,7 @@ > #ifndef _BONDING_PRIV_H > #define _BONDING_PRIV_H > #include <generated/utsrelease.h> >+#include "bond_mac_filter.h" > > #define DRV_NAME "bonding" > #define DRV_DESCRIPTION "Ethernet Channel Bonding Driver" >diff --git a/include/net/bond_options.h b/include/net/bond_options.h >index 61b49063791c..42e3e676b9c2 100644 >--- a/include/net/bond_options.h >+++ b/include/net/bond_options.h >@@ -67,6 +67,7 @@ enum { > BOND_OPT_LACP_ACTIVE, > BOND_OPT_MISSED_MAX, > BOND_OPT_NS_TARGETS, >+ BOND_OPT_MAC_FILTER, > BOND_OPT_LAST > }; > >diff --git a/include/net/bonding.h b/include/net/bonding.h >index b14f4c0b4e9e..5bc3e7b5a2af 100644 >--- a/include/net/bonding.h >+++ b/include/net/bonding.h >@@ -125,6 +125,7 @@ struct bond_params { > int miimon; > u8 num_peer_notif; > u8 missed_max; >+ u8 mac_filter; > int arp_interval; > int arp_validate; > int arp_all_targets; >@@ -248,6 +249,7 @@ struct bonding { > struct delayed_work alb_work; > struct delayed_work ad_work; > struct delayed_work mcast_work; >+ struct delayed_work mac_work; > struct delayed_work slave_arr_work; > #ifdef CONFIG_DEBUG_FS > /* debugging support via debugfs */ >@@ -260,6 +262,7 @@ struct bonding { > spinlock_t ipsec_lock; > #endif /* CONFIG_XFRM_OFFLOAD */ > struct bpf_prog *xdp_prog; >+ struct rhashtable *mac_filter_tbl; > }; > > #define bond_slave_get_rcu(dev) \ >diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h >index cc284c048e69..9291df89581f 100644 >--- a/include/uapi/linux/if_link.h >+++ b/include/uapi/linux/if_link.h >@@ -929,6 +929,7 @@ enum { > IFLA_BOND_AD_LACP_ACTIVE, > IFLA_BOND_MISSED_MAX, > IFLA_BOND_NS_IP6_TARGET, >+ IFLA_BOND_MAC_FILTER, > __IFLA_BOND_MAX, > }; > >-- >2.27.0 > --- -Jay Vosburgh, jay.vosburgh@xxxxxxxxxxxxx