[Bridge] [PATCH 3/7] bridge: Add addresses from static fdbs to bridge address list

Vlad Yasevich
2014-02-26T15:18:21Z

When a static fdb entry is created, add the mac address to the bridge
address list.  This list is used to program the proper port's
address list.

Signed-off-by: Vlad Yasevich <vyasevic at redhat.com>
---
 include/linux/netdevice.h |   6 +++
 net/bridge/br_device.c    |   2 +
 net/bridge/br_fdb.c       | 110 +++++++++++++++++++++++++++++++++++++++++++---
 net/bridge/br_if.c        |  14 ++++--
 net/bridge/br_private.h   |   3 ++
 net/core/dev.c            |   1 +
 net/core/dev_addr_lists.c |  14 +++---
 7 files changed, 134 insertions(+), 16 deletions(-)

diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 440a02e..e29cce1 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -2881,6 +2881,12 @@ int register_netdev(struct net_device *dev);
 void unregister_netdev(struct net_device *dev);

 /* General hardware address lists handling functions */
+int __hw_addr_add(struct netdev_hw_addr_list *list,
+		  const unsigned char *addr, int addr_len,
+		  unsigned char addr_type);
+int __hw_addr_del(struct netdev_hw_addr_list *list,
+		  const unsigned char *addr, int addr_len,
+		  unsigned char addr_type);
 int __hw_addr_sync(struct netdev_hw_addr_list *to_list,
 		   struct netdev_hw_addr_list *from_list, int addr_len);
 void __hw_addr_unsync(struct netdev_hw_addr_list *to_list,
diff --git a/net/bridge/br_device.c b/net/bridge/br_device.c
index 63f0455..1521db6 100644
--- a/net/bridge/br_device.c
+++ b/net/bridge/br_device.c
@@ -100,6 +100,8 @@ static int br_dev_init(struct net_device *dev)
 		u64_stats_init(&br_dev_stats->syncp);
 	}

+	__hw_addr_init(&br->conf_addrs);
+
 	return 0;
 }

diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c
index 9203d5a..ef95e81 100644
--- a/net/bridge/br_fdb.c
+++ b/net/bridge/br_fdb.c
@@ -35,6 +35,9 @@ static int fdb_insert(struct net_bridge *br, struct
net_bridge_port *source,
 static void fdb_notify(struct net_bridge *br,
 		       const struct net_bridge_fdb_entry *, int);

+static int br_addr_add(struct net_bridge *br, const unsigned char *addr);
+static int br_addr_del(struct net_bridge *br, const unsigned char *addr);
+
 static u32 fdb_salt __read_mostly;

 int __init br_fdb_init(void)
@@ -87,6 +90,9 @@ static void fdb_rcu_free(struct rcu_head *head)

 static void fdb_delete(struct net_bridge *br, struct net_bridge_fdb_entry *f)
 {
+	if (f->is_static)
+		br_addr_del(br, f->addr.addr);
+
 	hlist_del_rcu(&f->hlist);
 	fdb_notify(br, f, RTM_DELNEIGH);
 	call_rcu(&f->rcu, fdb_rcu_free);
@@ -466,6 +472,7 @@ static int fdb_insert(struct net_bridge *br, struct
net_bridge_port *source,
 		return -ENOMEM;

 	fdb->is_local = fdb->is_static = 1;
+	br_addr_add(br, addr);
 	fdb_notify(br, fdb, RTM_NEWNEIGH);
 	return 0;
 }
@@ -678,16 +685,29 @@ static int fdb_add_entry(struct net_bridge_port *source,
const __u8 *addr,
 	}

 	if (fdb_to_nud(fdb) != state) {
-		if (state & NUD_PERMANENT)
-			fdb->is_local = fdb->is_static = 1;
-		else if (state & NUD_NOARP) {
+		if (state & NUD_PERMANENT) {
+			fdb->is_local = 1;
+			if (!fdb->is_static) {
+				fdb->is_static = 1;
+				br_addr_add(br, addr);
+			}
+		} else if (state & NUD_NOARP) {
+			fdb->is_local = 0;
+			if (!fdb->is_static) {
+				fdb->is_static = 1;
+				br_addr_add(br, addr);
+			}
+		} else {
 			fdb->is_local = 0;
-			fdb->is_static = 1;
-		} else
-			fdb->is_local = fdb->is_static = 0;
+			if (fdb->is_static) {
+				fdb->is_static = 0;
+				br_addr_del(br, addr);
+			}
+		}

 		modified = true;
 	}
+
 	fdb->added_by_user = 1;

 	fdb->used = jiffies;
@@ -874,3 +894,81 @@ int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[],
 out:
 	return err;
 }
+
+
+/* Sync the current list to the correct flood port.  */
+void br_fdb_addrs_sync(struct net_bridge *br)
+{
+	struct net_bridge_port *p;
+	int err;
+
+	/* This function has to run under RTNL.
+	 * Program the user added addresses into the proper port.  This
+	 * fuction follows the following algorithm:
+	 *   a)  If only 1 port is flooding:
+	 *       - write all the addresses to this one port.
+	 */
+	if (br->n_flood_ports == 1) {
+		p = br->c_flood_port;
+		netif_addr_lock(p->dev);
+		err = __hw_addr_sync(&p->dev->uc, &br->conf_addrs,
+				     p->dev->addr_len);
+		if (!err)
+			__dev_set_rx_mode(p->dev);
+		netif_addr_unlock(p->dev);
+
+	}
+}
+
+void br_fdb_addrs_unsync(struct net_bridge *br)
+{
+	struct net_bridge_port *p = br->c_flood_port;
+
+	if (!p)
+		return;
+
+	netif_addr_lock(p->dev);
+	__hw_addr_unsync(&p->dev->uc, &br->conf_addrs,
p->dev->addr_len);
+	__dev_set_rx_mode(p->dev);
+	netif_addr_unlock(p->dev);
+}
+
+/* When a static FDB entry is added, the mac address from the entry is
+ * added to the bridge private HW address list and all required ports
+ * are then updated with the new information.
+ * Called under RTNL.
+ */
+static int br_addr_add(struct net_bridge *br, const unsigned char *addr)
+{
+	int err;
+
+	ASSERT_RTNL();
+
+	err = __hw_addr_add(&br->conf_addrs, addr, br->dev->addr_len,
+			    NETDEV_HW_ADDR_T_UNICAST);
+
+	if (!err)
+		br_fdb_addrs_sync(br);
+
+	return err;
+}
+
+/* When a static FDB entry is deleted, the HW address from that entry is
+ * also removed from the bridge private HW address list and updates all
+ * the ports with needed information.
+ * Called under RTNL.
+ */
+static int br_addr_del(struct net_bridge *br, const unsigned char *addr)
+{
+	int err;
+
+	ASSERT_RTNL();
+
+	err = __hw_addr_del(&br->conf_addrs, addr, br->dev->addr_len,
+			    NETDEV_HW_ADDR_T_UNICAST);
+	if (!err)
+		br_fdb_addrs_sync(br);
+
+	return err;
+}
+
diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c
index f072b34..e782c2e 100644
--- a/net/bridge/br_if.c
+++ b/net/bridge/br_if.c
@@ -144,7 +144,7 @@ static void del_nbp(struct net_bridge_port *p)

 	br_ifinfo_notify(RTM_DELLINK, p);

-	list_del_rcu(&p->list);
+	dev->priv_flags &= ~IFF_BRIDGE_PORT;

 	if (p->flags & BR_FLOOD)
 		br_del_flood_port(p, br);
@@ -152,7 +152,7 @@ static void del_nbp(struct net_bridge_port *p)
 	nbp_vlan_flush(p);
 	br_fdb_delete_by_port(br, p, 1);

-	dev->priv_flags &= ~IFF_BRIDGE_PORT;
+	list_del_rcu(&p->list);

 	netdev_rx_handler_unregister(dev);

@@ -473,6 +473,8 @@ static void br_add_flood_port(struct net_bridge_port *p,
struct net_bridge *br)
 	br->n_flood_ports++;
 	if (br->n_flood_ports == 1)
 		br->c_flood_port = p;
+
+	br_fdb_addrs_sync(br);
 }

 static void br_del_flood_port(struct net_bridge_port *p, struct net_bridge *br)
@@ -485,13 +487,17 @@ static void br_del_flood_port(struct net_bridge_port *p,
struct net_bridge *br)
 	 * set it if it is not set.
 	 */
 	br->n_flood_ports--;
-	if (p == br->c_flood_port)
+	if (p == br->c_flood_port) {
+		br_fdb_addrs_unsync(br);
 		br->c_flood_port = NULL;
+	}

 	if (br->n_flood_ports == 1) {
 		list_for_each_entry(port, &p->br->port_list, list) {
-			if (port->flags & BR_FLOOD) {
+			if (br_port_exists(port->dev) &&
+			    (port->flags & BR_FLOOD)) {
 				br->c_flood_port = port;
+				br_fdb_addrs_sync(br);
 				break;
 			}
 		}
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
index 26a3987..40a6927 100644
--- a/net/bridge/br_private.h
+++ b/net/bridge/br_private.h
@@ -296,6 +296,7 @@ struct net_bridge
 	u8				vlan_enabled;
 	struct net_port_vlans __rcu	*vlan_info;
 #endif
+	struct netdev_hw_addr_list	conf_addrs;
 };

 struct br_input_skb_cb {
@@ -397,6 +398,8 @@ int br_fdb_add(struct ndmsg *nlh, struct nlattr *tb[],
struct net_device *dev,
 	       const unsigned char *addr, u16 nlh_flags);
 int br_fdb_dump(struct sk_buff *skb, struct netlink_callback *cb,
 		struct net_device *dev, int idx);
+void br_fdb_addrs_sync(struct net_bridge *br);
+void br_fdb_addrs_unsync(struct net_bridge *br);

 /* br_forward.c */
 void br_deliver(const struct net_bridge_port *to, struct sk_buff *skb);
diff --git a/net/core/dev.c b/net/core/dev.c
index 4ad1b78..eca4d476 100644
--- a/net/core/dev.c
+++ b/net/core/dev.c
@@ -5212,6 +5212,7 @@ void __dev_set_rx_mode(struct net_device *dev)
 	if (ops->ndo_set_rx_mode)
 		ops->ndo_set_rx_mode(dev);
 }
+EXPORT_SYMBOL(__dev_set_rx_mode);

 void dev_set_rx_mode(struct net_device *dev)
 {
diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c
index 329d579..3de44a3 100644
--- a/net/core/dev_addr_lists.c
+++ b/net/core/dev_addr_lists.c
@@ -81,13 +81,14 @@ static int __hw_addr_add_ex(struct netdev_hw_addr_list
*list,
 				   sync);
 }

-static int __hw_addr_add(struct netdev_hw_addr_list *list,
-			 const unsigned char *addr, int addr_len,
-			 unsigned char addr_type)
+int __hw_addr_add(struct netdev_hw_addr_list *list,
+		  const unsigned char *addr, int addr_len,
+		  unsigned char addr_type)
 {
 	return __hw_addr_add_ex(list, addr, addr_len, addr_type, false, false,
 				0);
 }
+EXPORT_SYMBOL(__hw_addr_add);

 static int __hw_addr_del_entry(struct netdev_hw_addr_list *list,
 			       struct netdev_hw_addr *ha, bool global,
@@ -127,12 +128,13 @@ static int __hw_addr_del_ex(struct netdev_hw_addr_list
*list,
 	return -ENOENT;
 }

-static int __hw_addr_del(struct netdev_hw_addr_list *list,
-			 const unsigned char *addr, int addr_len,
-			 unsigned char addr_type)
+int __hw_addr_del(struct netdev_hw_addr_list *list,
+		  const unsigned char *addr, int addr_len,
+		  unsigned char addr_type)
 {
 	return __hw_addr_del_ex(list, addr, addr_len, addr_type, false, false);
 }
+EXPORT_SYMBOL(__hw_addr_del);

 static int __hw_addr_sync_one(struct netdev_hw_addr_list *to_list,
 			       struct netdev_hw_addr *ha,
--
1.8.5.3