tcp: fix recv with flags MSG_WAITALL | MSG_PEEK
[firefly-linux-kernel-4.4.55.git] / net / bridge / br_fdb.c
index 659fb96672e41e2e6525323697ca23a41d271fbb..9e9875da0a4f979c5389f616b432d218b19fd776 100644 (file)
@@ -24,6 +24,7 @@
 #include <linux/atomic.h>
 #include <asm/unaligned.h>
 #include <linux/if_vlan.h>
+#include <net/switchdev.h>
 #include "br_private.h"
 
 static struct kmem_cache *br_fdb_cache __read_mostly;
@@ -130,11 +131,27 @@ static void fdb_del_hw_addr(struct net_bridge *br, const unsigned char *addr)
        }
 }
 
+static void fdb_del_external_learn(struct net_bridge_fdb_entry *f)
+{
+       struct switchdev_obj obj = {
+               .id = SWITCHDEV_OBJ_PORT_FDB,
+               .u.fdb = {
+                       .addr = f->addr.addr,
+                       .vid = f->vlan_id,
+               },
+       };
+
+       switchdev_port_obj_del(f->dst->dev, &obj);
+}
+
 static void fdb_delete(struct net_bridge *br, struct net_bridge_fdb_entry *f)
 {
        if (f->is_static)
                fdb_del_hw_addr(br, f->addr.addr);
 
+       if (f->added_by_external_learn)
+               fdb_del_external_learn(f);
+
        hlist_del_rcu(&f->hlist);
        fdb_notify(br, f, RTM_DELNEIGH);
        call_rcu(&f->rcu, fdb_rcu_free);
@@ -313,9 +330,11 @@ void br_fdb_flush(struct net_bridge *br)
 
 /* Flush all entries referring to a specific port.
  * if do_all is set also flush static entries
+ * if vid is set delete all entries that match the vlan_id
  */
 void br_fdb_delete_by_port(struct net_bridge *br,
                           const struct net_bridge_port *p,
+                          u16 vid,
                           int do_all)
 {
        int i;
@@ -330,8 +349,9 @@ void br_fdb_delete_by_port(struct net_bridge *br,
                        if (f->dst != p)
                                continue;
 
-                       if (f->is_static && !do_all)
-                               continue;
+                       if (!do_all)
+                               if (f->is_static || (vid && f->vlan_id != vid))
+                                       continue;
 
                        if (f->is_local)
                                fdb_delete_local(br, p, f);
@@ -736,6 +756,12 @@ static int fdb_add_entry(struct net_bridge_port *source, const __u8 *addr,
        struct net_bridge_fdb_entry *fdb;
        bool modified = false;
 
+       /* If the port cannot learn allow only local and static entries */
+       if (!(state & NUD_PERMANENT) && !(state & NUD_NOARP) &&
+           !(source->state == BR_STATE_LEARNING ||
+             source->state == BR_STATE_FORWARDING))
+               return -EPERM;
+
        fdb = fdb_find(head, addr, vid);
        if (fdb == NULL) {
                if (!(flags & NLM_F_CREATE))
@@ -867,13 +893,15 @@ out:
        return err;
 }
 
-static int fdb_delete_by_addr(struct net_bridge *br, const u8 *addr, u16 vlan)
+static int fdb_delete_by_addr_and_port(struct net_bridge_port *p,
+                                      const u8 *addr, u16 vlan)
 {
+       struct net_bridge *br = p->br;
        struct hlist_head *head = &br->hash[br_mac_hash(addr, vlan)];
        struct net_bridge_fdb_entry *fdb;
 
        fdb = fdb_find(head, addr, vlan);
-       if (!fdb)
+       if (!fdb || fdb->dst != p)
                return -ENOENT;
 
        fdb_delete(br, fdb);
@@ -886,7 +914,7 @@ static int __br_fdb_delete(struct net_bridge_port *p,
        int err;
 
        spin_lock_bh(&p->br->hash_lock);
-       err = fdb_delete_by_addr(p->br, addr, vid);
+       err = fdb_delete_by_addr_and_port(p, addr, vid);
        spin_unlock_bh(&p->br->hash_lock);
 
        return err;