Commit 7bb90c37 authored by Toshiaki Makita's avatar Toshiaki Makita Committed by David S. Miller

bridge: Fix problems around fdb entries pointing to the bridge device

Adding fdb entries pointing to the bridge device uses fdb_insert(),
which lacks various checks and does not respect added_by_user flag.

As a result, some inconsistent behavior can happen:
* Adding temporary entries succeeds but results in permanent entries.
* Same goes for "dynamic" and "use".
* Changing mac address of the bridge device causes deletion of
  user-added entries.
* Replacing existing entries looks successful from userspace but actually
  not, regardless of NLM_F_EXCL flag.

Use the same logic as other entries and fix them.

Fixes: 3741873b ("bridge: allow adding of fdb entries pointing to the bridge device")
Signed-off-by: default avatarToshiaki Makita <makita.toshiaki@lab.ntt.co.jp>
Acked-by: default avatarRoopa Prabhu <roopa@cumulusnetworks.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 836384d2
...@@ -267,7 +267,7 @@ void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr) ...@@ -267,7 +267,7 @@ void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr)
/* If old entry was unassociated with any port, then delete it. */ /* If old entry was unassociated with any port, then delete it. */
f = __br_fdb_get(br, br->dev->dev_addr, 0); f = __br_fdb_get(br, br->dev->dev_addr, 0);
if (f && f->is_local && !f->dst) if (f && f->is_local && !f->dst && !f->added_by_user)
fdb_delete_local(br, NULL, f); fdb_delete_local(br, NULL, f);
fdb_insert(br, NULL, newaddr, 0); fdb_insert(br, NULL, newaddr, 0);
...@@ -282,7 +282,7 @@ void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr) ...@@ -282,7 +282,7 @@ void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr)
if (!br_vlan_should_use(v)) if (!br_vlan_should_use(v))
continue; continue;
f = __br_fdb_get(br, br->dev->dev_addr, v->vid); f = __br_fdb_get(br, br->dev->dev_addr, v->vid);
if (f && f->is_local && !f->dst) if (f && f->is_local && !f->dst && !f->added_by_user)
fdb_delete_local(br, NULL, f); fdb_delete_local(br, NULL, f);
fdb_insert(br, NULL, newaddr, v->vid); fdb_insert(br, NULL, newaddr, v->vid);
} }
...@@ -764,20 +764,25 @@ int br_fdb_dump(struct sk_buff *skb, ...@@ -764,20 +764,25 @@ int br_fdb_dump(struct sk_buff *skb,
} }
/* Update (create or replace) forwarding database entry */ /* Update (create or replace) forwarding database entry */
static int fdb_add_entry(struct net_bridge_port *source, const __u8 *addr, static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source,
__u16 state, __u16 flags, __u16 vid) const __u8 *addr, __u16 state, __u16 flags, __u16 vid)
{ {
struct net_bridge *br = source->br;
struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)]; struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)];
struct net_bridge_fdb_entry *fdb; struct net_bridge_fdb_entry *fdb;
bool modified = false; bool modified = false;
/* If the port cannot learn allow only local and static entries */ /* If the port cannot learn allow only local and static entries */
if (!(state & NUD_PERMANENT) && !(state & NUD_NOARP) && if (source && !(state & NUD_PERMANENT) && !(state & NUD_NOARP) &&
!(source->state == BR_STATE_LEARNING || !(source->state == BR_STATE_LEARNING ||
source->state == BR_STATE_FORWARDING)) source->state == BR_STATE_FORWARDING))
return -EPERM; return -EPERM;
if (!source && !(state & NUD_PERMANENT)) {
pr_info("bridge: RTM_NEWNEIGH %s without NUD_PERMANENT\n",
br->dev->name);
return -EINVAL;
}
fdb = fdb_find(head, addr, vid); fdb = fdb_find(head, addr, vid);
if (fdb == NULL) { if (fdb == NULL) {
if (!(flags & NLM_F_CREATE)) if (!(flags & NLM_F_CREATE))
...@@ -832,22 +837,28 @@ static int fdb_add_entry(struct net_bridge_port *source, const __u8 *addr, ...@@ -832,22 +837,28 @@ static int fdb_add_entry(struct net_bridge_port *source, const __u8 *addr,
return 0; return 0;
} }
static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge_port *p, static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge *br,
const unsigned char *addr, u16 nlh_flags, u16 vid) struct net_bridge_port *p, const unsigned char *addr,
u16 nlh_flags, u16 vid)
{ {
int err = 0; int err = 0;
if (ndm->ndm_flags & NTF_USE) { if (ndm->ndm_flags & NTF_USE) {
if (!p) {
pr_info("bridge: RTM_NEWNEIGH %s with NTF_USE is not supported\n",
br->dev->name);
return -EINVAL;
}
local_bh_disable(); local_bh_disable();
rcu_read_lock(); rcu_read_lock();
br_fdb_update(p->br, p, addr, vid, true); br_fdb_update(br, p, addr, vid, true);
rcu_read_unlock(); rcu_read_unlock();
local_bh_enable(); local_bh_enable();
} else { } else {
spin_lock_bh(&p->br->hash_lock); spin_lock_bh(&br->hash_lock);
err = fdb_add_entry(p, addr, ndm->ndm_state, err = fdb_add_entry(br, p, addr, ndm->ndm_state,
nlh_flags, vid); nlh_flags, vid);
spin_unlock_bh(&p->br->hash_lock); spin_unlock_bh(&br->hash_lock);
} }
return err; return err;
...@@ -884,6 +895,7 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], ...@@ -884,6 +895,7 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
dev->name); dev->name);
return -EINVAL; return -EINVAL;
} }
br = p->br;
vg = nbp_vlan_group(p); vg = nbp_vlan_group(p);
} }
...@@ -895,15 +907,9 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], ...@@ -895,15 +907,9 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
} }
/* VID was specified, so use it. */ /* VID was specified, so use it. */
if (dev->priv_flags & IFF_EBRIDGE) err = __br_fdb_add(ndm, br, p, addr, nlh_flags, vid);
err = br_fdb_insert(br, NULL, addr, vid);
else
err = __br_fdb_add(ndm, p, addr, nlh_flags, vid);
} else { } else {
if (dev->priv_flags & IFF_EBRIDGE) err = __br_fdb_add(ndm, br, p, addr, nlh_flags, 0);
err = br_fdb_insert(br, NULL, addr, 0);
else
err = __br_fdb_add(ndm, p, addr, nlh_flags, 0);
if (err || !vg || !vg->num_vlans) if (err || !vg || !vg->num_vlans)
goto out; goto out;
...@@ -914,11 +920,7 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[], ...@@ -914,11 +920,7 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],
list_for_each_entry(v, &vg->vlan_list, vlist) { list_for_each_entry(v, &vg->vlan_list, vlist) {
if (!br_vlan_should_use(v)) if (!br_vlan_should_use(v))
continue; continue;
if (dev->priv_flags & IFF_EBRIDGE) err = __br_fdb_add(ndm, br, p, addr, nlh_flags, v->vid);
err = br_fdb_insert(br, NULL, addr, v->vid);
else
err = __br_fdb_add(ndm, p, addr, nlh_flags,
v->vid);
if (err) if (err)
goto out; goto out;
} }
......
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