Plan B:
1. Use p->br (the back pointer) as the RCU sentinel
2. Keep dev->br_port set until after the RCU transition
3. Order operations so we don't have to worry about p->br being NULL
in the case of STP timers.
Untested. Probably excessive use of rcu() macros. Since it is only
needed in cases where no locks held (ie. !(RTNL | br_lock))
--- br-fix.orig/net/bridge/br_fdb.c
+++ br-fix/net/bridge/br_fdb.c
@@ -67,9 +67,9 @@ static __inline__ void fdb_delete(struct
br_fdb_put(f);
}
-void br_fdb_changeaddr(struct net_bridge_port *p, const unsigned char *newaddr)
+void br_fdb_changeaddr(struct net_bridge *br, struct net_bridge_port *p,
+ const unsigned char *newaddr)
{
- struct net_bridge *br = p->br;
int i;
spin_lock_bh(&br->hash_lock);
--- br-fix.orig/net/bridge/br_if.c
+++ br-fix/net/bridge/br_if.c
@@ -79,24 +79,31 @@ static int port_cost(struct net_device *
*/
static void port_carrier_check(void *arg)
{
- struct net_bridge_port *p = arg;
+ struct net_device *dev = arg;
+ struct net_bridge_port *p;
+ struct net_bridge *br;
rtnl_lock();
- if (netif_carrier_ok(p->dev)) {
- u32 cost = port_cost(p->dev);
+ if ((p = dev->br_port) == NULL ||
+ (br = rcu_dereference(p->br)) == NULL)
+ goto done;
- spin_lock_bh(&p->br->lock);
+ if (netif_carrier_ok(dev)) {
+ u32 cost = port_cost(dev);
+
+ spin_lock_bh(&br->lock);
if (p->state == BR_STATE_DISABLED) {
p->path_cost = cost;
br_stp_enable_port(p);
}
- spin_unlock_bh(&p->br->lock);
+ spin_unlock_bh(&br->lock);
} else {
- spin_lock_bh(&p->br->lock);
+ spin_lock_bh(&br->lock);
if (p->state != BR_STATE_DISABLED)
br_stp_disable_port(p);
- spin_unlock_bh(&p->br->lock);
+ spin_unlock_bh(&br->lock);
}
+done:
rtnl_unlock();
}
@@ -104,7 +111,7 @@ static void destroy_nbp(struct net_bridg
{
struct net_device *dev = p->dev;
- p->br = NULL;
+ dev->br_port = NULL;
p->dev = NULL;
dev_put(dev);
@@ -119,29 +126,25 @@ static void destroy_nbp_rcu(struct rcu_h
}
/* called with RTNL */
-static void del_nbp(struct net_bridge_port *p)
+static void del_nbp(struct net_bridge *br, struct net_bridge_port *p)
{
- struct net_bridge *br = p->br;
- struct net_device *dev = p->dev;
-
- dev->br_port = NULL;
- dev_set_promiscuity(dev, -1);
+ dev_set_promiscuity(p->dev, -1);
cancel_delayed_work(&p->carrier_check);
- flush_scheduled_work();
spin_lock_bh(&br->lock);
br_stp_disable_port(p);
+ list_del_rcu(&p->list);
spin_unlock_bh(&br->lock);
br_fdb_delete_by_port(br, p);
- list_del_rcu(&p->list);
-
del_timer_sync(&p->message_age_timer);
del_timer_sync(&p->forward_delay_timer);
del_timer_sync(&p->hold_timer);
+ rcu_assign_pointer(p->br, NULL);
+
call_rcu(&p->rcu, destroy_nbp_rcu);
}
@@ -152,7 +155,7 @@ static void del_br(struct net_bridge *br
list_for_each_entry_safe(p, n, &br->port_list, list) {
br_sysfs_removeif(p);
- del_nbp(p);
+ del_nbp(br, p);
}
del_timer_sync(&br->gc_timer);
@@ -249,7 +252,7 @@ static struct net_bridge_port *new_nbp(s
p->port_no = index;
br_init_port(p);
p->state = BR_STATE_DISABLED;
- INIT_WORK(&p->carrier_check, port_carrier_check, p);
+ INIT_WORK(&p->carrier_check, port_carrier_check, dev);
kobject_init(&p->kobj);
return p;
@@ -386,7 +389,7 @@ int br_add_if(struct net_bridge *br, str
destroy_nbp(p);
else if ((err = br_sysfs_addif(p)))
- del_nbp(p);
+ del_nbp(br, p);
else {
dev_set_promiscuity(dev, 1);
@@ -415,7 +418,7 @@ int br_del_if(struct net_bridge *br, str
return -EINVAL;
br_sysfs_removeif(p);
- del_nbp(p);
+ del_nbp(br, p);
spin_lock_bh(&br->lock);
br_stp_recalculate_bridge_id(br);
--- br-fix.orig/net/bridge/br_input.c
+++ br-fix/net/bridge/br_input.c
@@ -46,17 +46,19 @@ int br_handle_frame_finish(struct sk_buf
{
const unsigned char *dest = eth_hdr(skb)->h_dest;
struct net_bridge_port *p = skb->dev->br_port;
- struct net_bridge *br = p->br;
+ struct net_bridge *br;
struct net_bridge_fdb_entry *dst;
int passedup = 0;
+ br = rcu_dereference(p->br);
+ if (!br)
+ goto drop;
+
/* insert into forwarding database after filtering to avoid spoofing */
- br_fdb_update(p->br, p, eth_hdr(skb)->h_source);
+ br_fdb_update(br, p, eth_hdr(skb)->h_source);
- if (p->state == BR_STATE_LEARNING) {
- kfree_skb(skb);
- goto out;
- }
+ if (p->state == BR_STATE_LEARNING)
+ goto drop;
if (br->dev->flags & IFF_PROMISC) {
struct sk_buff *skb2;
@@ -72,26 +74,23 @@ int br_handle_frame_finish(struct sk_buf
br_flood_forward(br, skb, !passedup);
if (!passedup)
br_pass_frame_up(br, skb);
- goto out;
}
-
- dst = __br_fdb_get(br, dest);
- if (dst != NULL && dst->is_local) {
- if (!passedup)
+ else if ((dst = __br_fdb_get(br, dest)) != NULL) {
+ if (dst->is_local) {
+ if (passedup)
+ goto drop;
br_pass_frame_up(br, skb);
+ }
else
- kfree_skb(skb);
- goto out;
+ br_forward(dst->dst, skb);
}
+ else
+ br_flood_forward(br, skb, 0);
- if (dst != NULL) {
- br_forward(dst->dst, skb);
- goto out;
- }
-
- br_flood_forward(br, skb, 0);
+ return 0;
-out:
+drop:
+ kfree_skb(skb);
return 0;
}
@@ -105,14 +104,15 @@ int br_handle_frame(struct net_bridge_po
{
struct sk_buff *skb = *pskb;
const unsigned char *dest = eth_hdr(skb)->h_dest;
+ struct net_bridge *br = rcu_dereference(p->br);
- if (p->state == BR_STATE_DISABLED)
+ if (p->state == BR_STATE_DISABLED || !br)
goto err;
if (!is_valid_ether_addr(eth_hdr(skb)->h_source))
goto err;
- if (p->br->stp_enabled &&
+ if (br->stp_enabled &&
!memcmp(dest, bridge_ula, 5) &&
!(dest[5] & 0xF0)) {
if (!dest[5]) {
@@ -131,7 +131,7 @@ int br_handle_frame(struct net_bridge_po
dest = eth_hdr(skb)->h_dest;
}
- if (!compare_ether_addr(p->br->dev->dev_addr, dest))
+ if (!compare_ether_addr(br->dev->dev_addr, dest))
skb->pkt_type = PACKET_HOST;
NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
--- br-fix.orig/net/bridge/br_notify.c
+++ br-fix/net/bridge/br_notify.c
@@ -39,7 +39,12 @@ static int br_device_event(struct notifi
if (p == NULL)
return NOTIFY_DONE;
- br = p->br;
+ rcu_read_lock();
+ br = rcu_dereference(p->br);
+
+ /* lost race with br_del_if and RCU */
+ if (br == NULL)
+ goto done;
spin_lock_bh(&br->lock);
switch (event) {
@@ -48,7 +53,7 @@ static int br_device_event(struct notifi
break;
case NETDEV_CHANGEADDR:
- br_fdb_changeaddr(p, dev->dev_addr);
+ br_fdb_changeaddr(br, p, dev->dev_addr);
br_stp_recalculate_bridge_id(br);
break;
@@ -84,5 +89,6 @@ static int br_device_event(struct notifi
spin_unlock_bh(&br->lock);
done:
+ rcu_read_unlock();
return NOTIFY_DONE;
}
--- br-fix.orig/net/bridge/br_private.h
+++ br-fix/net/bridge/br_private.h
@@ -138,7 +138,7 @@ extern int br_dev_xmit(struct sk_buff *s
/* br_fdb.c */
extern void br_fdb_init(void);
extern void br_fdb_fini(void);
-extern void br_fdb_changeaddr(struct net_bridge_port *p,
+extern void br_fdb_changeaddr(struct net_bridge *br, struct net_bridge_port *p,
const unsigned char *newaddr);
extern void br_fdb_cleanup(unsigned long arg);
extern void br_fdb_delete_by_port(struct net_bridge *br,
@@ -199,7 +199,8 @@ extern void br_log_state(const struct ne
extern struct net_bridge_port *br_get_port(struct net_bridge *br,
u16 port_no);
extern void br_init_port(struct net_bridge_port *p);
-extern void br_become_designated_port(struct net_bridge_port *p);
+extern void br_become_designated_port(struct net_bridge *br,
+ struct net_bridge_port *p);
/* br_stp_if.c */
extern void br_stp_enable_bridge(struct net_bridge *br);
--- br-fix.orig/net/bridge/br_private_stp.h
+++ br-fix/net/bridge/br_private_stp.h
@@ -45,8 +45,11 @@ extern void br_become_root_bridge(struct
extern void br_config_bpdu_generation(struct net_bridge *);
extern void br_configuration_update(struct net_bridge *);
extern void br_port_state_selection(struct net_bridge *);
-extern void br_received_config_bpdu(struct net_bridge_port *p, struct
br_config_bpdu *bpdu);
-extern void br_received_tcn_bpdu(struct net_bridge_port *p);
+extern void br_received_config_bpdu(struct net_bridge *br,
+ struct net_bridge_port *p,
+ const struct br_config_bpdu *bpdu);
+extern void br_received_tcn_bpdu(struct net_bridge *br,
+ struct net_bridge_port *p);
extern void br_transmit_config(struct net_bridge_port *p);
extern void br_transmit_tcn(struct net_bridge *br);
extern void br_topology_change_detection(struct net_bridge *br);
--- br-fix.orig/net/bridge/br_stp.c
+++ br-fix/net/bridge/br_stp.c
@@ -53,14 +53,13 @@ struct net_bridge_port *br_get_port(stru
}
/* called under bridge lock */
-static int br_should_become_root_port(const struct net_bridge_port *p,
+static int br_should_become_root_port(struct net_bridge *br,
+ const struct net_bridge_port *p,
u16 root_port)
{
- struct net_bridge *br;
struct net_bridge_port *rp;
int t;
- br = p->br;
if (p->state == BR_STATE_DISABLED ||
br_is_designated_port(p))
return 0;
@@ -110,7 +109,7 @@ static void br_root_selection(struct net
u16 root_port = 0;
list_for_each_entry(p, &br->port_list, list) {
- if (br_should_become_root_port(p, root_port))
+ if (br_should_become_root_port(br, p, root_port))
root_port = p->port_no;
}
@@ -148,7 +147,6 @@ void br_transmit_config(struct net_bridg
struct br_config_bpdu bpdu;
struct net_bridge *br;
-
if (timer_pending(&p->hold_timer)) {
p->config_pending = 1;
return;
@@ -184,7 +182,8 @@ void br_transmit_config(struct net_bridg
}
/* called under bridge lock */
-static inline void br_record_config_information(struct net_bridge_port *p,
+static inline void br_record_config_information(struct net_bridge *br,
+ struct net_bridge_port *p,
const struct br_config_bpdu
*bpdu)
{
p->designated_root = bpdu->root;
@@ -193,7 +192,7 @@ static inline void br_record_config_info
p->designated_port = bpdu->port_id;
mod_timer(&p->message_age_timer, jiffies
- + (p->br->max_age - bpdu->message_age));
+ + (br->max_age - bpdu->message_age));
}
/* called under bridge lock */
@@ -213,12 +212,11 @@ void br_transmit_tcn(struct net_bridge *
}
/* called under bridge lock */
-static int br_should_become_designated_port(const struct net_bridge_port *p)
+static int br_should_become_designated_port(const struct net_bridge *br,
+ const struct net_bridge_port *p)
{
- struct net_bridge *br;
int t;
- br = p->br;
if (br_is_designated_port(p))
return 1;
@@ -249,14 +247,16 @@ static void br_designated_port_selection
list_for_each_entry(p, &br->port_list, list) {
if (p->state != BR_STATE_DISABLED &&
- br_should_become_designated_port(p))
- br_become_designated_port(p);
+ br_should_become_designated_port(br, p))
+ br_become_designated_port(br,p);
}
}
/* called under bridge lock */
-static int br_supersedes_port_info(struct net_bridge_port *p, struct
br_config_bpdu *bpdu)
+static int br_supersedes_port_info(const struct net_bridge *br,
+ const struct net_bridge_port *p,
+ const struct br_config_bpdu *bpdu)
{
int t;
@@ -277,7 +277,7 @@ static int br_supersedes_port_info(struc
else if (t > 0)
return 0;
- if (memcmp(&bpdu->bridge_id, &p->br->bridge_id, 8))
+ if (memcmp(&bpdu->bridge_id, &br->bridge_id, 8))
return 1;
if (bpdu->port_id <= p->designated_port)
@@ -339,11 +339,8 @@ void br_configuration_update(struct net_
}
/* called under bridge lock */
-void br_become_designated_port(struct net_bridge_port *p)
+void br_become_designated_port(struct net_bridge *br, struct net_bridge_port
*p)
{
- struct net_bridge *br;
-
- br = p->br;
p->designated_root = br->designated_root;
p->designated_cost = br->root_path_cost;
p->designated_bridge = br->bridge_id;
@@ -367,16 +364,16 @@ static void br_make_blocking(struct net_
}
/* called under bridge lock */
-static void br_make_forwarding(struct net_bridge_port *p)
+static void br_make_forwarding(struct net_bridge *br, struct net_bridge_port
*p)
{
if (p->state == BR_STATE_BLOCKING) {
- if (p->br->stp_enabled) {
+ if (br->stp_enabled)
p->state = BR_STATE_LISTENING;
- } else {
+ else
p->state = BR_STATE_LEARNING;
- }
br_log_state(p);
- mod_timer(&p->forward_delay_timer, jiffies +
p->br->forward_delay); }
+ mod_timer(&p->forward_delay_timer, jiffies +
p->br->forward_delay);
+ }
}
/* called under bridge lock */
@@ -389,10 +386,10 @@ void br_port_state_selection(struct net_
if (p->port_no == br->root_port) {
p->config_pending = 0;
p->topology_change_ack = 0;
- br_make_forwarding(p);
+ br_make_forwarding(br, p);
} else if (br_is_designated_port(p)) {
del_timer(&p->message_age_timer);
- br_make_forwarding(p);
+ br_make_forwarding(br, p);
} else {
p->config_pending = 0;
p->topology_change_ack = 0;
@@ -411,16 +408,13 @@ static inline void br_topology_change_ac
}
/* called under bridge lock */
-void br_received_config_bpdu(struct net_bridge_port *p, struct br_config_bpdu
*bpdu)
+void br_received_config_bpdu(struct net_bridge *br, struct net_bridge_port *p,
+ const struct br_config_bpdu *bpdu)
{
- struct net_bridge *br;
- int was_root;
-
- br = p->br;
- was_root = br_is_root_bridge(br);
+ int was_root = br_is_root_bridge(br);
- if (br_supersedes_port_info(p, bpdu)) {
- br_record_config_information(p, bpdu);
+ if (br_supersedes_port_info(br, p, bpdu)) {
+ br_record_config_information(br, p, bpdu);
br_configuration_update(br);
br_port_state_selection(br);
@@ -447,13 +441,13 @@ void br_received_config_bpdu(struct net_
}
/* called under bridge lock */
-void br_received_tcn_bpdu(struct net_bridge_port *p)
+void br_received_tcn_bpdu(struct net_bridge *br, struct net_bridge_port *p)
{
if (br_is_designated_port(p)) {
pr_info("%s: received tcn bpdu on port %i(%s)\n",
- p->br->dev->name, p->port_no, p->dev->name);
+ br->dev->name, p->port_no, p->dev->name);
- br_topology_change_detection(p->br);
+ br_topology_change_detection(br);
br_topology_change_acknowledge(p);
}
}
--- br-fix.orig/net/bridge/br_stp_bpdu.c
+++ br-fix/net/bridge/br_stp_bpdu.c
@@ -24,11 +24,12 @@
static void br_send_bpdu(struct net_bridge_port *p, unsigned char *data, int
length)
{
+ struct net_bridge *br = rcu_dereference(p->br);
struct net_device *dev;
struct sk_buff *skb;
int size;
- if (!p->br->stp_enabled)
+ if (!br || !br->stp_enabled)
return;
size = length + 2*ETH_ALEN + 2;
@@ -137,11 +138,15 @@ static const unsigned char header[6] = {
int br_stp_handle_bpdu(struct sk_buff *skb)
{
struct net_bridge_port *p = skb->dev->br_port;
- struct net_bridge *br = p->br;
+ struct net_bridge *br;
unsigned char *buf;
+ br = rcu_dereference(p->br);
+ if (!br)
+ goto err;
+
/* insert into forwarding database after filtering to avoid spoofing */
- br_fdb_update(p->br, p, eth_hdr(skb)->h_source);
+ br_fdb_update(br, p, eth_hdr(skb)->h_source);
/* need at least the 802 and STP headers */
if (!pskb_may_pull(skb, sizeof(header)+1) ||
@@ -194,11 +199,11 @@ int br_stp_handle_bpdu(struct sk_buff *s
bpdu.hello_time = br_get_ticks(buf+28);
bpdu.forward_delay = br_get_ticks(buf+30);
- br_received_config_bpdu(p, &bpdu);
+ br_received_config_bpdu(br, p, &bpdu);
}
else if (buf[0] == BPDU_TYPE_TCN) {
- br_received_tcn_bpdu(p);
+ br_received_tcn_bpdu(br, p);
}
out:
spin_unlock_bh(&br->lock);
--- br-fix.orig/net/bridge/br_stp_if.c
+++ br-fix/net/bridge/br_stp_if.c
@@ -35,7 +35,7 @@ static inline port_id br_make_port_id(__
void br_init_port(struct net_bridge_port *p)
{
p->port_id = br_make_port_id(p->priority, p->port_no);
- br_become_designated_port(p);
+ br_become_designated_port(p->br, p);
p->state = BR_STATE_BLOCKING;
p->topology_change_ack = 0;
p->config_pending = 0;
@@ -102,7 +102,7 @@ void br_stp_disable_port(struct net_brid
br->dev->name, p->port_no, p->dev->name, "disabled");
wasroot = br_is_root_bridge(br);
- br_become_designated_port(p);
+ br_become_designated_port(br, p);
p->state = BR_STATE_DISABLED;
p->topology_change_ack = 0;
p->config_pending = 0;
@@ -194,6 +194,7 @@ void br_stp_set_bridge_priority(struct n
/* called under bridge lock */
void br_stp_set_port_priority(struct net_bridge_port *p, u8 newprio)
{
+ struct net_bridge *br = p->br;
port_id new_port_id = br_make_port_id(newprio, p->port_no);
if (br_is_designated_port(p))
@@ -201,10 +202,10 @@ void br_stp_set_port_priority(struct net
p->port_id = new_port_id;
p->priority = newprio;
- if (!memcmp(&p->br->bridge_id, &p->designated_bridge, 8) &&
+ if (!memcmp(&br->bridge_id, &p->designated_bridge, 8) &&
p->port_id < p->designated_port) {
- br_become_designated_port(p);
- br_port_state_selection(p->br);
+ br_become_designated_port(br, p);
+ br_port_state_selection(br);
}
}
--- br-fix.orig/net/bridge/br_stp_timer.c
+++ br-fix/net/bridge/br_stp_timer.c
@@ -76,7 +76,7 @@ static void br_message_age_timer_expired
goto unlock;
was_root = br_is_root_bridge(br);
- br_become_designated_port(p);
+ br_become_designated_port(br, p);
br_configuration_update(br);
br_port_state_selection(br);
if (br_is_root_bridge(br) && !was_root)
--- br-fix.orig/net/bridge/br_sysfs_if.c
+++ br-fix/net/bridge/br_sysfs_if.c
@@ -173,6 +173,7 @@ static ssize_t brport_store(struct kobje
{
struct brport_attribute * brport_attr = to_brport_attr(attr);
struct net_bridge_port * p = to_brport(kobj);
+ struct net_bridge *br;
ssize_t ret = -EINVAL;
char *endp;
unsigned long val;
@@ -183,10 +184,11 @@ static ssize_t brport_store(struct kobje
val = simple_strtoul(buf, &endp, 0);
if (endp != buf) {
rtnl_lock();
- if (p->dev && p->br && brport_attr->store) {
- spin_lock_bh(&p->br->lock);
+ br = rcu_dereference(p->br);
+ if (p->dev && br && brport_attr->store) {
+ spin_lock_bh(&br->lock);
ret = brport_attr->store(p, val);
- spin_unlock_bh(&p->br->lock);
+ spin_unlock_bh(&br->lock);
if (ret == 0)
ret = count;
}
-
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at http://vger.kernel.org/majordomo-info.html