Skip to content

Commit

Permalink
Merge branch 'bridge-fdbs-bitops'
Browse files Browse the repository at this point in the history
Nikolay Aleksandrov says:

====================
net: bridge: convert fdbs to use bitops

We'd like to have a well-defined behaviour when changing fdb flags. The
problem is that we've added new fields which are changed from all
contexts without any locking. We are aware of the bit test/change races
and these are fine (we can remove them later), but it is considered
undefined behaviour to change bitfields from multiple threads and also
on some architectures that can result in unexpected results,
specifically when all fields between the changed ones are also
bitfields. The conversion to bitops shows the intent clearly and
makes them use functions with well-defined behaviour in such cases.
There is no overhead for the fast-path, the bit changing functions are
used only in special cases when learning and in the slow path.
In addition this conversion allows us to simplify fdb flag handling and
avoid bugs for future bits (e.g. a forgetting to clear the new bit when
allocating a new fdb). All bridge selftests passed, also tried all of the
converted bits manually in a VM.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
David S. Miller committed Oct 30, 2019
2 parents 8466a57 + 3fb01a3 commit 9014fc3
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 79 deletions.
133 changes: 65 additions & 68 deletions net/bridge/br_fdb.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,9 @@ static inline unsigned long hold_time(const struct net_bridge *br)
static inline int has_expired(const struct net_bridge *br,
const struct net_bridge_fdb_entry *fdb)
{
return !fdb->is_static && !fdb->added_by_external_learn &&
time_before_eq(fdb->updated + hold_time(br), jiffies);
return !test_bit(BR_FDB_STATIC, &fdb->flags) &&
!test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags) &&
time_before_eq(fdb->updated + hold_time(br), jiffies);
}

static void fdb_rcu_free(struct rcu_head *head)
Expand Down Expand Up @@ -197,7 +198,7 @@ static void fdb_delete(struct net_bridge *br, struct net_bridge_fdb_entry *f,
{
trace_fdb_delete(br, f);

if (f->is_static)
if (test_bit(BR_FDB_STATIC, &f->flags))
fdb_del_hw_addr(br, f->key.addr.addr);

hlist_del_init_rcu(&f->fdb_node);
Expand All @@ -224,7 +225,7 @@ static void fdb_delete_local(struct net_bridge *br,
if (op != p && ether_addr_equal(op->dev->dev_addr, addr) &&
(!vid || br_vlan_find(vg, vid))) {
f->dst = op;
f->added_by_user = 0;
clear_bit(BR_FDB_ADDED_BY_USER, &f->flags);
return;
}
}
Expand All @@ -235,7 +236,7 @@ static void fdb_delete_local(struct net_bridge *br,
if (p && ether_addr_equal(br->dev->dev_addr, addr) &&
(!vid || (v && br_vlan_should_use(v)))) {
f->dst = NULL;
f->added_by_user = 0;
clear_bit(BR_FDB_ADDED_BY_USER, &f->flags);
return;
}

Expand All @@ -250,7 +251,8 @@ void br_fdb_find_delete_local(struct net_bridge *br,

spin_lock_bh(&br->hash_lock);
f = br_fdb_find(br, addr, vid);
if (f && f->is_local && !f->added_by_user && f->dst == p)
if (f && test_bit(BR_FDB_LOCAL, &f->flags) &&
!test_bit(BR_FDB_ADDED_BY_USER, &f->flags) && f->dst == p)
fdb_delete_local(br, p, f);
spin_unlock_bh(&br->hash_lock);
}
Expand All @@ -265,7 +267,8 @@ void br_fdb_changeaddr(struct net_bridge_port *p, const unsigned char *newaddr)
spin_lock_bh(&br->hash_lock);
vg = nbp_vlan_group(p);
hlist_for_each_entry(f, &br->fdb_list, fdb_node) {
if (f->dst == p && f->is_local && !f->added_by_user) {
if (f->dst == p && test_bit(BR_FDB_LOCAL, &f->flags) &&
!test_bit(BR_FDB_ADDED_BY_USER, &f->flags)) {
/* delete old one */
fdb_delete_local(br, p, f);

Expand Down Expand Up @@ -306,7 +309,8 @@ void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr)

/* If old entry was unassociated with any port, then delete it. */
f = br_fdb_find(br, br->dev->dev_addr, 0);
if (f && f->is_local && !f->dst && !f->added_by_user)
if (f && test_bit(BR_FDB_LOCAL, &f->flags) &&
!f->dst && !test_bit(BR_FDB_ADDED_BY_USER, &f->flags))
fdb_delete_local(br, NULL, f);

fdb_insert(br, NULL, newaddr, 0);
Expand All @@ -321,7 +325,8 @@ void br_fdb_change_mac_address(struct net_bridge *br, const u8 *newaddr)
if (!br_vlan_should_use(v))
continue;
f = br_fdb_find(br, br->dev->dev_addr, v->vid);
if (f && f->is_local && !f->dst && !f->added_by_user)
if (f && test_bit(BR_FDB_LOCAL, &f->flags) &&
!f->dst && !test_bit(BR_FDB_ADDED_BY_USER, &f->flags))
fdb_delete_local(br, NULL, f);
fdb_insert(br, NULL, newaddr, v->vid);
}
Expand All @@ -346,7 +351,8 @@ void br_fdb_cleanup(struct work_struct *work)
hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) {
unsigned long this_timer;

if (f->is_static || f->added_by_external_learn)
if (test_bit(BR_FDB_STATIC, &f->flags) ||
test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &f->flags))
continue;
this_timer = f->updated + delay;
if (time_after(this_timer, now)) {
Expand All @@ -373,7 +379,7 @@ void br_fdb_flush(struct net_bridge *br)

spin_lock_bh(&br->hash_lock);
hlist_for_each_entry_safe(f, tmp, &br->fdb_list, fdb_node) {
if (!f->is_static)
if (!test_bit(BR_FDB_STATIC, &f->flags))
fdb_delete(br, f, true);
}
spin_unlock_bh(&br->hash_lock);
Expand All @@ -397,10 +403,11 @@ void br_fdb_delete_by_port(struct net_bridge *br,
continue;

if (!do_all)
if (f->is_static || (vid && f->key.vlan_id != vid))
if (test_bit(BR_FDB_STATIC, &f->flags) ||
(vid && f->key.vlan_id != vid))
continue;

if (f->is_local)
if (test_bit(BR_FDB_LOCAL, &f->flags))
fdb_delete_local(br, p, f);
else
fdb_delete(br, f, true);
Expand Down Expand Up @@ -469,8 +476,8 @@ int br_fdb_fillbuf(struct net_bridge *br, void *buf,
fe->port_no = f->dst->port_no;
fe->port_hi = f->dst->port_no >> 8;

fe->is_local = f->is_local;
if (!f->is_static)
fe->is_local = test_bit(BR_FDB_LOCAL, &f->flags);
if (!test_bit(BR_FDB_STATIC, &f->flags))
fe->ageing_timer_value = jiffies_delta_to_clock_t(jiffies - f->updated);
++fe;
++num;
Expand All @@ -484,8 +491,7 @@ static struct net_bridge_fdb_entry *fdb_create(struct net_bridge *br,
struct net_bridge_port *source,
const unsigned char *addr,
__u16 vid,
unsigned char is_local,
unsigned char is_static)
unsigned long flags)
{
struct net_bridge_fdb_entry *fdb;

Expand All @@ -494,12 +500,7 @@ static struct net_bridge_fdb_entry *fdb_create(struct net_bridge *br,
memcpy(fdb->key.addr.addr, addr, ETH_ALEN);
fdb->dst = source;
fdb->key.vlan_id = vid;
fdb->is_local = is_local;
fdb->is_static = is_static;
fdb->added_by_user = 0;
fdb->added_by_external_learn = 0;
fdb->offloaded = 0;
fdb->is_sticky = 0;
fdb->flags = flags;
fdb->updated = fdb->used = jiffies;
if (rhashtable_lookup_insert_fast(&br->fdb_hash_tbl,
&fdb->rhnode,
Expand All @@ -526,14 +527,15 @@ static int fdb_insert(struct net_bridge *br, struct net_bridge_port *source,
/* it is okay to have multiple ports with same
* address, just use the first one.
*/
if (fdb->is_local)
if (test_bit(BR_FDB_LOCAL, &fdb->flags))
return 0;
br_warn(br, "adding interface %s with same address as a received packet (addr:%pM, vlan:%u)\n",
source ? source->dev->name : br->dev->name, addr, vid);
fdb_delete(br, fdb, true);
}

fdb = fdb_create(br, source, addr, vid, 1, 1);
fdb = fdb_create(br, source, addr, vid,
BIT(BR_FDB_LOCAL) | BIT(BR_FDB_STATIC));
if (!fdb)
return -ENOMEM;

Expand Down Expand Up @@ -572,36 +574,37 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
fdb = fdb_find_rcu(&br->fdb_hash_tbl, addr, vid);
if (likely(fdb)) {
/* attempt to update an entry for a local interface */
if (unlikely(fdb->is_local)) {
if (unlikely(test_bit(BR_FDB_LOCAL, &fdb->flags))) {
if (net_ratelimit())
br_warn(br, "received packet on %s with own address as source address (addr:%pM, vlan:%u)\n",
source->dev->name, addr, vid);
} else {
unsigned long now = jiffies;

/* fastpath: update of existing entry */
if (unlikely(source != fdb->dst && !fdb->is_sticky)) {
if (unlikely(source != fdb->dst &&
!test_bit(BR_FDB_STICKY, &fdb->flags))) {
fdb->dst = source;
fdb_modified = true;
/* Take over HW learned entry */
if (unlikely(fdb->added_by_external_learn))
fdb->added_by_external_learn = 0;
test_and_clear_bit(BR_FDB_ADDED_BY_EXT_LEARN,
&fdb->flags);
}
if (now != fdb->updated)
fdb->updated = now;
if (unlikely(added_by_user))
fdb->added_by_user = 1;
set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags);
if (unlikely(fdb_modified)) {
trace_br_fdb_update(br, source, addr, vid, added_by_user);
fdb_notify(br, fdb, RTM_NEWNEIGH, true);
}
}
} else {
spin_lock(&br->hash_lock);
fdb = fdb_create(br, source, addr, vid, 0, 0);
fdb = fdb_create(br, source, addr, vid, 0);
if (fdb) {
if (unlikely(added_by_user))
fdb->added_by_user = 1;
set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags);
trace_br_fdb_update(br, source, addr, vid,
added_by_user);
fdb_notify(br, fdb, RTM_NEWNEIGH, true);
Expand All @@ -616,9 +619,9 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
static int fdb_to_nud(const struct net_bridge *br,
const struct net_bridge_fdb_entry *fdb)
{
if (fdb->is_local)
if (test_bit(BR_FDB_LOCAL, &fdb->flags))
return NUD_PERMANENT;
else if (fdb->is_static)
else if (test_bit(BR_FDB_STATIC, &fdb->flags))
return NUD_NOARP;
else if (has_expired(br, fdb))
return NUD_STALE;
Expand Down Expand Up @@ -648,11 +651,11 @@ static int fdb_fill_info(struct sk_buff *skb, const struct net_bridge *br,
ndm->ndm_ifindex = fdb->dst ? fdb->dst->dev->ifindex : br->dev->ifindex;
ndm->ndm_state = fdb_to_nud(br, fdb);

if (fdb->offloaded)
if (test_bit(BR_FDB_OFFLOADED, &fdb->flags))
ndm->ndm_flags |= NTF_OFFLOADED;
if (fdb->added_by_external_learn)
if (test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags))
ndm->ndm_flags |= NTF_EXT_LEARNED;
if (fdb->is_sticky)
if (test_bit(BR_FDB_STICKY, &fdb->flags))
ndm->ndm_flags |= NTF_STICKY;

if (nla_put(skb, NDA_LLADDR, ETH_ALEN, &fdb->key.addr))
Expand Down Expand Up @@ -799,7 +802,7 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source,
const u8 *addr, u16 state, u16 flags, u16 vid,
u8 ndm_flags)
{
u8 is_sticky = !!(ndm_flags & NTF_STICKY);
bool is_sticky = !!(ndm_flags & NTF_STICKY);
struct net_bridge_fdb_entry *fdb;
bool modified = false;

Expand All @@ -823,7 +826,7 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source,
if (!(flags & NLM_F_CREATE))
return -ENOENT;

fdb = fdb_create(br, source, addr, vid, 0, 0);
fdb = fdb_create(br, source, addr, vid, 0);
if (!fdb)
return -ENOMEM;

Expand All @@ -840,34 +843,28 @@ static int fdb_add_entry(struct net_bridge *br, struct net_bridge_port *source,

if (fdb_to_nud(br, fdb) != state) {
if (state & NUD_PERMANENT) {
fdb->is_local = 1;
if (!fdb->is_static) {
fdb->is_static = 1;
set_bit(BR_FDB_LOCAL, &fdb->flags);
if (!test_and_set_bit(BR_FDB_STATIC, &fdb->flags))
fdb_add_hw_addr(br, addr);
}
} else if (state & NUD_NOARP) {
fdb->is_local = 0;
if (!fdb->is_static) {
fdb->is_static = 1;
clear_bit(BR_FDB_LOCAL, &fdb->flags);
if (!test_and_set_bit(BR_FDB_STATIC, &fdb->flags))
fdb_add_hw_addr(br, addr);
}
} else {
fdb->is_local = 0;
if (fdb->is_static) {
fdb->is_static = 0;
clear_bit(BR_FDB_LOCAL, &fdb->flags);
if (test_and_clear_bit(BR_FDB_STATIC, &fdb->flags))
fdb_del_hw_addr(br, addr);
}
}

modified = true;
}

if (is_sticky != fdb->is_sticky) {
fdb->is_sticky = is_sticky;
if (is_sticky != test_bit(BR_FDB_STICKY, &fdb->flags)) {
change_bit(BR_FDB_STICKY, &fdb->flags);
modified = true;
}

fdb->added_by_user = 1;
set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags);

fdb->used = jiffies;
if (modified) {
Expand Down Expand Up @@ -1064,7 +1061,7 @@ int br_fdb_sync_static(struct net_bridge *br, struct net_bridge_port *p)
rcu_read_lock();
hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) {
/* We only care for static entries */
if (!f->is_static)
if (!test_bit(BR_FDB_STATIC, &f->flags))
continue;
err = dev_uc_add(p->dev, f->key.addr.addr);
if (err)
Expand All @@ -1078,7 +1075,7 @@ int br_fdb_sync_static(struct net_bridge *br, struct net_bridge_port *p)
rollback:
hlist_for_each_entry_rcu(tmp, &br->fdb_list, fdb_node) {
/* We only care for static entries */
if (!tmp->is_static)
if (!test_bit(BR_FDB_STATIC, &tmp->flags))
continue;
if (tmp == f)
break;
Expand All @@ -1097,7 +1094,7 @@ void br_fdb_unsync_static(struct net_bridge *br, struct net_bridge_port *p)
rcu_read_lock();
hlist_for_each_entry_rcu(f, &br->fdb_list, fdb_node) {
/* We only care for static entries */
if (!f->is_static)
if (!test_bit(BR_FDB_STATIC, &f->flags))
continue;

dev_uc_del(p->dev, f->key.addr.addr);
Expand All @@ -1119,14 +1116,14 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p,

fdb = br_fdb_find(br, addr, vid);
if (!fdb) {
fdb = fdb_create(br, p, addr, vid, 0, 0);
fdb = fdb_create(br, p, addr, vid, 0);
if (!fdb) {
err = -ENOMEM;
goto err_unlock;
}
if (swdev_notify)
fdb->added_by_user = 1;
fdb->added_by_external_learn = 1;
set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags);
set_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags);
fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify);
} else {
fdb->updated = jiffies;
Expand All @@ -1136,17 +1133,17 @@ int br_fdb_external_learn_add(struct net_bridge *br, struct net_bridge_port *p,
modified = true;
}

if (fdb->added_by_external_learn) {
if (test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags)) {
/* Refresh entry */
fdb->used = jiffies;
} else if (!fdb->added_by_user) {
} else if (!test_bit(BR_FDB_ADDED_BY_USER, &fdb->flags)) {
/* Take over SW learned entry */
fdb->added_by_external_learn = 1;
set_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags);
modified = true;
}

if (swdev_notify)
fdb->added_by_user = 1;
set_bit(BR_FDB_ADDED_BY_USER, &fdb->flags);

if (modified)
fdb_notify(br, fdb, RTM_NEWNEIGH, swdev_notify);
Expand All @@ -1168,7 +1165,7 @@ int br_fdb_external_learn_del(struct net_bridge *br, struct net_bridge_port *p,
spin_lock_bh(&br->hash_lock);

fdb = br_fdb_find(br, addr, vid);
if (fdb && fdb->added_by_external_learn)
if (fdb && test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags))
fdb_delete(br, fdb, swdev_notify);
else
err = -ENOENT;
Expand All @@ -1186,8 +1183,8 @@ void br_fdb_offloaded_set(struct net_bridge *br, struct net_bridge_port *p,
spin_lock_bh(&br->hash_lock);

fdb = br_fdb_find(br, addr, vid);
if (fdb)
fdb->offloaded = offloaded;
if (fdb && offloaded != test_bit(BR_FDB_OFFLOADED, &fdb->flags))
change_bit(BR_FDB_OFFLOADED, &fdb->flags);

spin_unlock_bh(&br->hash_lock);
}
Expand All @@ -1206,7 +1203,7 @@ void br_fdb_clear_offload(const struct net_device *dev, u16 vid)
spin_lock_bh(&p->br->hash_lock);
hlist_for_each_entry(f, &p->br->fdb_list, fdb_node) {
if (f->dst == p && f->key.vlan_id == vid)
f->offloaded = 0;
clear_bit(BR_FDB_OFFLOADED, &f->flags);
}
spin_unlock_bh(&p->br->hash_lock);
}
Expand Down
Loading

0 comments on commit 9014fc3

Please sign in to comment.