bridge: fdb dumping takes a filter device
[cascardo/linux.git] / net / bridge / br_fdb.c
index 474d36f..6edecd1 100644 (file)
@@ -85,8 +85,56 @@ static void fdb_rcu_free(struct rcu_head *head)
        kmem_cache_free(br_fdb_cache, ent);
 }
 
+/* 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 void fdb_add_hw(struct net_bridge *br, const unsigned char *addr)
+{
+       int err;
+       struct net_bridge_port *p;
+
+       ASSERT_RTNL();
+
+       list_for_each_entry(p, &br->port_list, list) {
+               if (!br_promisc_port(p)) {
+                       err = dev_uc_add(p->dev, addr);
+                       if (err)
+                               goto undo;
+               }
+       }
+
+       return;
+undo:
+       list_for_each_entry_continue_reverse(p, &br->port_list, list) {
+               if (!br_promisc_port(p))
+                       dev_uc_del(p->dev, addr);
+       }
+}
+
+/* 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 void fdb_del_hw(struct net_bridge *br, const unsigned char *addr)
+{
+       struct net_bridge_port *p;
+
+       ASSERT_RTNL();
+
+       list_for_each_entry(p, &br->port_list, list) {
+               if (!br_promisc_port(p))
+                       dev_uc_del(p->dev, addr);
+       }
+}
+
 static void fdb_delete(struct net_bridge *br, struct net_bridge_fdb_entry *f)
 {
+       if (f->is_static)
+               fdb_del_hw(br, f->addr.addr);
+
        hlist_del_rcu(&f->hlist);
        fdb_notify(br, f, RTM_DELNEIGH);
        call_rcu(&f->rcu, fdb_rcu_free);
@@ -466,6 +514,7 @@ static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
                return -ENOMEM;
 
        fdb->is_local = fdb->is_static = 1;
+       fdb_add_hw(br, addr);
        fdb_notify(br, fdb, RTM_NEWNEIGH);
        return 0;
 }
@@ -571,6 +620,8 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br,
 
        if (nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->addr))
                goto nla_put_failure;
+       if (nla_put_u32(skb, NDA_MASTER, br->dev->ifindex))
+               goto nla_put_failure;
        ci.ndm_used      = jiffies_to_clock_t(now - fdb->used);
        ci.ndm_confirmed = 0;
        ci.ndm_updated   = jiffies_to_clock_t(now - fdb->updated);
@@ -592,6 +643,7 @@ static inline size_t fdb_nlmsg_size(void)
 {
        return NLMSG_ALIGN(sizeof(struct ndmsg))
                + nla_total_size(ETH_ALEN) /* NDA_LLADDR */
+               + nla_total_size(sizeof(u32)) /* NDA_MASTER */
                + nla_total_size(sizeof(u16)) /* NDA_VLAN */
                + nla_total_size(sizeof(struct nda_cacheinfo));
 }
@@ -624,6 +676,7 @@ errout:
 int br_fdb_dump(struct sk_buff *skb,
                struct netlink_callback *cb,
                struct net_device *dev,
+               struct net_device *filter_dev,
                int idx)
 {
        struct net_bridge *br = netdev_priv(dev);
@@ -639,6 +692,10 @@ int br_fdb_dump(struct sk_buff *skb,
                        if (idx < cb->args[0])
                                goto skip;
 
+                       if (filter_dev && (!f->dst || !f->dst->dev ||
+                                          f->dst->dev != filter_dev))
+                               goto skip;
+
                        if (fdb_fill_info(skb, br, f,
                                          NETLINK_CB(cb->skb).portid,
                                          cb->nlh->nlmsg_seq,
@@ -684,13 +741,25 @@ 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;
+                               fdb_add_hw(br, addr);
+                       }
+               } else if (state & NUD_NOARP) {
+                       fdb->is_local = 0;
+                       if (!fdb->is_static) {
+                               fdb->is_static = 1;
+                               fdb_add_hw(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;
+                               fdb_del_hw(br, addr);
+                       }
+               }
 
                modified = true;
        }
@@ -880,3 +949,59 @@ int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[],
 out:
        return err;
 }
+
+int br_fdb_sync_static(struct net_bridge *br, struct net_bridge_port *p)
+{
+       struct net_bridge_fdb_entry *fdb, *tmp;
+       int i;
+       int err;
+
+       ASSERT_RTNL();
+
+       for (i = 0; i < BR_HASH_SIZE; i++) {
+               hlist_for_each_entry(fdb, &br->hash[i], hlist) {
+                       /* We only care for static entries */
+                       if (!fdb->is_static)
+                               continue;
+
+                       err = dev_uc_add(p->dev, fdb->addr.addr);
+                       if (err)
+                               goto rollback;
+               }
+       }
+       return 0;
+
+rollback:
+       for (i = 0; i < BR_HASH_SIZE; i++) {
+               hlist_for_each_entry(tmp, &br->hash[i], hlist) {
+                       /* If we reached the fdb that failed, we can stop */
+                       if (tmp == fdb)
+                               break;
+
+                       /* We only care for static entries */
+                       if (!tmp->is_static)
+                               continue;
+
+                       dev_uc_del(p->dev, tmp->addr.addr);
+               }
+       }
+       return err;
+}
+
+void br_fdb_unsync_static(struct net_bridge *br, struct net_bridge_port *p)
+{
+       struct net_bridge_fdb_entry *fdb;
+       int i;
+
+       ASSERT_RTNL();
+
+       for (i = 0; i < BR_HASH_SIZE; i++) {
+               hlist_for_each_entry_rcu(fdb, &br->hash[i], hlist) {
+                       /* We only care for static entries */
+                       if (!fdb->is_static)
+                               continue;
+
+                       dev_uc_del(p->dev, fdb->addr.addr);
+               }
+       }
+}