diff options
Diffstat (limited to 'net/bridge/br_fdb.c')
| -rw-r--r-- | net/bridge/br_fdb.c | 286 | 
1 files changed, 227 insertions, 59 deletions
diff --git a/net/bridge/br_fdb.c b/net/bridge/br_fdb.c index ffd5874f259..b524c36c127 100644 --- a/net/bridge/br_fdb.c +++ b/net/bridge/br_fdb.c @@ -27,6 +27,9 @@  #include "br_private.h"  static struct kmem_cache *br_fdb_cache __read_mostly; +static struct net_bridge_fdb_entry *fdb_find(struct hlist_head *head, +					     const unsigned char *addr, +					     __u16 vid);  static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,  		      const unsigned char *addr, u16 vid);  static void fdb_notify(struct net_bridge *br, @@ -82,18 +85,114 @@ 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, *tmp; + +	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(tmp, &br->port_list, list) { +		if (tmp == p) +			break; +		if (!br_promisc_port(tmp)) +			dev_uc_del(tmp->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);  } +/* Delete a local entry if no other port had the same address. */ +static void fdb_delete_local(struct net_bridge *br, +			     const struct net_bridge_port *p, +			     struct net_bridge_fdb_entry *f) +{ +	const unsigned char *addr = f->addr.addr; +	u16 vid = f->vlan_id; +	struct net_bridge_port *op; + +	/* Maybe another port has same hw addr? */ +	list_for_each_entry(op, &br->port_list, list) { +		if (op != p && ether_addr_equal(op->dev->dev_addr, addr) && +		    (!vid || nbp_vlan_find(op, vid))) { +			f->dst = op; +			f->added_by_user = 0; +			return; +		} +	} + +	/* Maybe bridge device has same hw addr? */ +	if (p && ether_addr_equal(br->dev->dev_addr, addr) && +	    (!vid || br_vlan_find(br, vid))) { +		f->dst = NULL; +		f->added_by_user = 0; +		return; +	} + +	fdb_delete(br, f); +} + +void br_fdb_find_delete_local(struct net_bridge *br, +			      const struct net_bridge_port *p, +			      const unsigned char *addr, u16 vid) +{ +	struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)]; +	struct net_bridge_fdb_entry *f; + +	spin_lock_bh(&br->hash_lock); +	f = fdb_find(head, addr, vid); +	if (f && f->is_local && !f->added_by_user && f->dst == p) +		fdb_delete_local(br, p, f); +	spin_unlock_bh(&br->hash_lock); +} +  void br_fdb_changeaddr(struct net_bridge_port *p, const unsigned char *newaddr)  {  	struct net_bridge *br = p->br; -	bool no_vlan = (nbp_get_vlan_info(p) == NULL) ? true : false; +	struct net_port_vlans *pv = nbp_get_vlan_info(p); +	bool no_vlan = !pv;  	int i; +	u16 vid;  	spin_lock_bh(&br->hash_lock); @@ -104,38 +203,34 @@ void br_fdb_changeaddr(struct net_bridge_port *p, const unsigned char *newaddr)  			struct net_bridge_fdb_entry *f;  			f = hlist_entry(h, struct net_bridge_fdb_entry, hlist); -			if (f->dst == p && f->is_local) { -				/* maybe another port has same hw addr? */ -				struct net_bridge_port *op; -				u16 vid = f->vlan_id; -				list_for_each_entry(op, &br->port_list, list) { -					if (op != p && -					    ether_addr_equal(op->dev->dev_addr, -							     f->addr.addr) && -					    nbp_vlan_find(op, vid)) { -						f->dst = op; -						goto insert; -					} -				} - +			if (f->dst == p && f->is_local && !f->added_by_user) {  				/* delete old one */ -				fdb_delete(br, f); -insert: -				/* insert new address,  may fail if invalid -				 * address or dup. -				 */ -				fdb_insert(br, p, newaddr, vid); +				fdb_delete_local(br, p, f);  				/* if this port has no vlan information  				 * configured, we can safely be done at  				 * this point.  				 */  				if (no_vlan) -					goto done; +					goto insert;  			}  		}  	} +insert: +	/* insert new address,  may fail if invalid address or dup. */ +	fdb_insert(br, p, newaddr, 0); + +	if (no_vlan) +		goto done; + +	/* Now add entries for every VLAN configured on the port. +	 * This function runs under RTNL so the bitmap will not change +	 * from under us. +	 */ +	for_each_set_bit(vid, pv->vlan_bitmap, VLAN_N_VID) +		fdb_insert(br, p, newaddr, vid); +  done:  	spin_unlock_bh(&br->hash_lock);  } @@ -146,10 +241,12 @@ void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr)  	struct net_port_vlans *pv;  	u16 vid = 0; +	spin_lock_bh(&br->hash_lock); +  	/* If old entry was unassociated with any port, then delete it. */  	f = __br_fdb_get(br, br->dev->dev_addr, 0);  	if (f && f->is_local && !f->dst) -		fdb_delete(br, f); +		fdb_delete_local(br, NULL, f);  	fdb_insert(br, NULL, newaddr, 0); @@ -159,14 +256,16 @@ void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr)  	 */  	pv = br_get_vlan_info(br);  	if (!pv) -		return; +		goto out;  	for_each_set_bit_from(vid, pv->vlan_bitmap, VLAN_N_VID) {  		f = __br_fdb_get(br, br->dev->dev_addr, vid);  		if (f && f->is_local && !f->dst) -			fdb_delete(br, f); +			fdb_delete_local(br, NULL, f);  		fdb_insert(br, NULL, newaddr, vid);  	} +out: +	spin_unlock_bh(&br->hash_lock);  }  void br_fdb_cleanup(unsigned long _data) @@ -235,25 +334,11 @@ void br_fdb_delete_by_port(struct net_bridge *br,  			if (f->is_static && !do_all)  				continue; -			/* -			 * if multiple ports all have the same device address -			 * then when one port is deleted, assign -			 * the local entry to other port -			 */ -			if (f->is_local) { -				struct net_bridge_port *op; -				list_for_each_entry(op, &br->port_list, list) { -					if (op != p && -					    ether_addr_equal(op->dev->dev_addr, -							     f->addr.addr)) { -						f->dst = op; -						goto skip_delete; -					} -				} -			} -			fdb_delete(br, f); -		skip_delete: ; +			if (f->is_local) +				fdb_delete_local(br, p, f); +			else +				fdb_delete(br, f);  		}  	}  	spin_unlock_bh(&br->hash_lock); @@ -397,6 +482,7 @@ static struct net_bridge_fdb_entry *fdb_create(struct hlist_head *head,  		fdb->vlan_id = vid;  		fdb->is_local = 0;  		fdb->is_static = 0; +		fdb->added_by_user = 0;  		fdb->updated = fdb->used = jiffies;  		hlist_add_head_rcu(&fdb->hlist, head);  	} @@ -430,6 +516,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;  } @@ -447,10 +534,11 @@ int br_fdb_insert(struct net_bridge *br, struct net_bridge_port *source,  }  void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source, -		   const unsigned char *addr, u16 vid) +		   const unsigned char *addr, u16 vid, bool added_by_user)  {  	struct hlist_head *head = &br->hash[br_mac_hash(addr, vid)];  	struct net_bridge_fdb_entry *fdb; +	bool fdb_modified = false;  	/* some users want to always flood. */  	if (hold_time(br) == 0) @@ -471,15 +559,25 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,  					source->dev->name);  		} else {  			/* fastpath: update of existing entry */ -			fdb->dst = source; +			if (unlikely(source != fdb->dst)) { +				fdb->dst = source; +				fdb_modified = true; +			}  			fdb->updated = jiffies; +			if (unlikely(added_by_user)) +				fdb->added_by_user = 1; +			if (unlikely(fdb_modified)) +				fdb_notify(br, fdb, RTM_NEWNEIGH);  		}  	} else {  		spin_lock(&br->hash_lock);  		if (likely(!fdb_find(head, addr, vid))) {  			fdb = fdb_create(head, source, addr, vid); -			if (fdb) +			if (fdb) { +				if (unlikely(added_by_user)) +					fdb->added_by_user = 1;  				fdb_notify(br, fdb, RTM_NEWNEIGH); +			}  		}  		/* else  we lose race and someone else inserts  		 * it first, don't bother updating @@ -524,6 +622,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); @@ -545,6 +645,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));  } @@ -570,8 +671,7 @@ static void fdb_notify(struct net_bridge *br,  	rtnl_notify(skb, net, 0, RTNLGRP_NEIGH, NULL, GFP_ATOMIC);  	return;  errout: -	if (err < 0) -		rtnl_set_sk_err(net, RTNLGRP_NEIGH, err); +	rtnl_set_sk_err(net, RTNLGRP_NEIGH, err);  }  /* Dump information about entries, in response to GETNEIGH */ @@ -638,16 +738,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; +				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;  	} +	fdb->added_by_user = 1;  	fdb->used = jiffies;  	if (modified) { @@ -665,7 +778,7 @@ static int __br_fdb_add(struct ndmsg *ndm, struct net_bridge_port *p,  	if (ndm->ndm_flags & NTF_USE) {  		rcu_read_lock(); -		br_fdb_update(p->br, p, addr, vid); +		br_fdb_update(p->br, p, addr, vid, true);  		rcu_read_unlock();  	} else {  		spin_lock_bh(&p->br->hash_lock); @@ -700,7 +813,7 @@ int br_fdb_add(struct ndmsg *ndm, struct nlattr *tb[],  		vid = nla_get_u16(tb[NDA_VLAN]); -		if (vid >= VLAN_N_VID) { +		if (!vid || vid >= VLAN_VID_MASK) {  			pr_info("bridge: RTM_NEWNEIGH with invalid vlan id %d\n",  				vid);  			return -EINVAL; @@ -750,8 +863,7 @@ out:  	return err;  } -int fdb_delete_by_addr(struct net_bridge *br, const u8 *addr, -		       u16 vlan) +static int fdb_delete_by_addr(struct net_bridge *br, const u8 *addr, u16 vlan)  {  	struct hlist_head *head = &br->hash[br_mac_hash(addr, vlan)];  	struct net_bridge_fdb_entry *fdb; @@ -794,7 +906,7 @@ int br_fdb_delete(struct ndmsg *ndm, struct nlattr *tb[],  		vid = nla_get_u16(tb[NDA_VLAN]); -		if (vid >= VLAN_N_VID) { +		if (!vid || vid >= VLAN_VID_MASK) {  			pr_info("bridge: RTM_NEWNEIGH with invalid vlan id %d\n",  				vid);  			return -EINVAL; @@ -834,3 +946,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); +		} +	} +}  | 
