diff options
author | Stephen Hemminger <shemminger@osdl.org> | 2006-02-09 20:08:52 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2006-02-09 20:08:52 -0500 |
commit | b3f1be4b5412e34647764457bec901e06b03e624 (patch) | |
tree | b3f1529ff0882ebb5aed190d8a61b52f703503ce /net | |
parent | 6fcf9412de64056238a6295f21db7aa9c37a532e (diff) |
[BRIDGE]: fix for RCU and deadlock on device removal
Change Bridge receive path to correctly handle RCU removal of device
from bridge. Also fixes deadlock between carrier_check and del_nbp.
This replaces the previous deleted flag fix.
Signed-off-by: Stephen Hemminger <shemminger@osdl.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r-- | net/bridge/br_if.c | 21 | ||||
-rw-r--r-- | net/bridge/br_input.c | 19 | ||||
-rw-r--r-- | net/bridge/br_private.h | 1 | ||||
-rw-r--r-- | net/bridge/br_stp_bpdu.c | 30 |
4 files changed, 41 insertions, 30 deletions
diff --git a/net/bridge/br_if.c b/net/bridge/br_if.c index da687c8dc6f..70b7ef91723 100644 --- a/net/bridge/br_if.c +++ b/net/bridge/br_if.c | |||
@@ -79,9 +79,14 @@ static int port_cost(struct net_device *dev) | |||
79 | */ | 79 | */ |
80 | static void port_carrier_check(void *arg) | 80 | static void port_carrier_check(void *arg) |
81 | { | 81 | { |
82 | struct net_bridge_port *p = arg; | 82 | struct net_device *dev = arg; |
83 | struct net_bridge_port *p; | ||
83 | 84 | ||
84 | rtnl_lock(); | 85 | rtnl_lock(); |
86 | p = dev->br_port; | ||
87 | if (!p) | ||
88 | goto done; | ||
89 | |||
85 | if (netif_carrier_ok(p->dev)) { | 90 | if (netif_carrier_ok(p->dev)) { |
86 | u32 cost = port_cost(p->dev); | 91 | u32 cost = port_cost(p->dev); |
87 | 92 | ||
@@ -97,6 +102,7 @@ static void port_carrier_check(void *arg) | |||
97 | br_stp_disable_port(p); | 102 | br_stp_disable_port(p); |
98 | spin_unlock_bh(&p->br->lock); | 103 | spin_unlock_bh(&p->br->lock); |
99 | } | 104 | } |
105 | done: | ||
100 | rtnl_unlock(); | 106 | rtnl_unlock(); |
101 | } | 107 | } |
102 | 108 | ||
@@ -104,7 +110,6 @@ static void destroy_nbp(struct net_bridge_port *p) | |||
104 | { | 110 | { |
105 | struct net_device *dev = p->dev; | 111 | struct net_device *dev = p->dev; |
106 | 112 | ||
107 | dev->br_port = NULL; | ||
108 | p->br = NULL; | 113 | p->br = NULL; |
109 | p->dev = NULL; | 114 | p->dev = NULL; |
110 | dev_put(dev); | 115 | dev_put(dev); |
@@ -133,24 +138,20 @@ static void del_nbp(struct net_bridge_port *p) | |||
133 | struct net_bridge *br = p->br; | 138 | struct net_bridge *br = p->br; |
134 | struct net_device *dev = p->dev; | 139 | struct net_device *dev = p->dev; |
135 | 140 | ||
136 | /* Race between RTNL notify and RCU callback */ | ||
137 | if (p->deleted) | ||
138 | return; | ||
139 | |||
140 | dev_set_promiscuity(dev, -1); | 141 | dev_set_promiscuity(dev, -1); |
141 | 142 | ||
142 | cancel_delayed_work(&p->carrier_check); | 143 | cancel_delayed_work(&p->carrier_check); |
143 | flush_scheduled_work(); | ||
144 | 144 | ||
145 | spin_lock_bh(&br->lock); | 145 | spin_lock_bh(&br->lock); |
146 | br_stp_disable_port(p); | 146 | br_stp_disable_port(p); |
147 | p->deleted = 1; | ||
148 | spin_unlock_bh(&br->lock); | 147 | spin_unlock_bh(&br->lock); |
149 | 148 | ||
150 | br_fdb_delete_by_port(br, p); | 149 | br_fdb_delete_by_port(br, p); |
151 | 150 | ||
152 | list_del_rcu(&p->list); | 151 | list_del_rcu(&p->list); |
153 | 152 | ||
153 | rcu_assign_pointer(dev->br_port, NULL); | ||
154 | |||
154 | call_rcu(&p->rcu, destroy_nbp_rcu); | 155 | call_rcu(&p->rcu, destroy_nbp_rcu); |
155 | } | 156 | } |
156 | 157 | ||
@@ -254,11 +255,10 @@ static struct net_bridge_port *new_nbp(struct net_bridge *br, | |||
254 | p->dev = dev; | 255 | p->dev = dev; |
255 | p->path_cost = port_cost(dev); | 256 | p->path_cost = port_cost(dev); |
256 | p->priority = 0x8000 >> BR_PORT_BITS; | 257 | p->priority = 0x8000 >> BR_PORT_BITS; |
257 | dev->br_port = p; | ||
258 | p->port_no = index; | 258 | p->port_no = index; |
259 | br_init_port(p); | 259 | br_init_port(p); |
260 | p->state = BR_STATE_DISABLED; | 260 | p->state = BR_STATE_DISABLED; |
261 | INIT_WORK(&p->carrier_check, port_carrier_check, p); | 261 | INIT_WORK(&p->carrier_check, port_carrier_check, dev); |
262 | kobject_init(&p->kobj); | 262 | kobject_init(&p->kobj); |
263 | 263 | ||
264 | return p; | 264 | return p; |
@@ -397,6 +397,7 @@ int br_add_if(struct net_bridge *br, struct net_device *dev) | |||
397 | else if ((err = br_sysfs_addif(p))) | 397 | else if ((err = br_sysfs_addif(p))) |
398 | del_nbp(p); | 398 | del_nbp(p); |
399 | else { | 399 | else { |
400 | rcu_assign_pointer(dev->br_port, p); | ||
400 | dev_set_promiscuity(dev, 1); | 401 | dev_set_promiscuity(dev, 1); |
401 | 402 | ||
402 | list_add_rcu(&p->list, &br->port_list); | 403 | list_add_rcu(&p->list, &br->port_list); |
diff --git a/net/bridge/br_input.c b/net/bridge/br_input.c index e3a73cead6b..4eef8375531 100644 --- a/net/bridge/br_input.c +++ b/net/bridge/br_input.c | |||
@@ -45,18 +45,20 @@ static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb) | |||
45 | int br_handle_frame_finish(struct sk_buff *skb) | 45 | int br_handle_frame_finish(struct sk_buff *skb) |
46 | { | 46 | { |
47 | const unsigned char *dest = eth_hdr(skb)->h_dest; | 47 | const unsigned char *dest = eth_hdr(skb)->h_dest; |
48 | struct net_bridge_port *p = skb->dev->br_port; | 48 | struct net_bridge_port *p = rcu_dereference(skb->dev->br_port); |
49 | struct net_bridge *br = p->br; | 49 | struct net_bridge *br; |
50 | struct net_bridge_fdb_entry *dst; | 50 | struct net_bridge_fdb_entry *dst; |
51 | int passedup = 0; | 51 | int passedup = 0; |
52 | 52 | ||
53 | if (!p || p->state == BR_STATE_DISABLED) | ||
54 | goto drop; | ||
55 | |||
53 | /* insert into forwarding database after filtering to avoid spoofing */ | 56 | /* insert into forwarding database after filtering to avoid spoofing */ |
54 | br_fdb_update(p->br, p, eth_hdr(skb)->h_source); | 57 | br = p->br; |
58 | br_fdb_update(br, p, eth_hdr(skb)->h_source); | ||
55 | 59 | ||
56 | if (p->state == BR_STATE_LEARNING) { | 60 | if (p->state == BR_STATE_LEARNING) |
57 | kfree_skb(skb); | 61 | goto drop; |
58 | goto out; | ||
59 | } | ||
60 | 62 | ||
61 | if (br->dev->flags & IFF_PROMISC) { | 63 | if (br->dev->flags & IFF_PROMISC) { |
62 | struct sk_buff *skb2; | 64 | struct sk_buff *skb2; |
@@ -93,6 +95,9 @@ int br_handle_frame_finish(struct sk_buff *skb) | |||
93 | 95 | ||
94 | out: | 96 | out: |
95 | return 0; | 97 | return 0; |
98 | drop: | ||
99 | kfree_skb(skb); | ||
100 | goto out; | ||
96 | } | 101 | } |
97 | 102 | ||
98 | /* | 103 | /* |
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index e330b17b6d8..c5bd631ffcd 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h | |||
@@ -68,7 +68,6 @@ struct net_bridge_port | |||
68 | /* STP */ | 68 | /* STP */ |
69 | u8 priority; | 69 | u8 priority; |
70 | u8 state; | 70 | u8 state; |
71 | u8 deleted; | ||
72 | u16 port_no; | 71 | u16 port_no; |
73 | unsigned char topology_change_ack; | 72 | unsigned char topology_change_ack; |
74 | unsigned char config_pending; | 73 | unsigned char config_pending; |
diff --git a/net/bridge/br_stp_bpdu.c b/net/bridge/br_stp_bpdu.c index d071f1c9ad0..296f6a487c5 100644 --- a/net/bridge/br_stp_bpdu.c +++ b/net/bridge/br_stp_bpdu.c | |||
@@ -133,29 +133,35 @@ void br_send_tcn_bpdu(struct net_bridge_port *p) | |||
133 | 133 | ||
134 | static const unsigned char header[6] = {0x42, 0x42, 0x03, 0x00, 0x00, 0x00}; | 134 | static const unsigned char header[6] = {0x42, 0x42, 0x03, 0x00, 0x00, 0x00}; |
135 | 135 | ||
136 | /* NO locks */ | 136 | /* NO locks, but rcu_read_lock (preempt_disabled) */ |
137 | int br_stp_handle_bpdu(struct sk_buff *skb) | 137 | int br_stp_handle_bpdu(struct sk_buff *skb) |
138 | { | 138 | { |
139 | struct net_bridge_port *p = skb->dev->br_port; | 139 | struct net_bridge_port *p = rcu_dereference(skb->dev->br_port); |
140 | struct net_bridge *br = p->br; | 140 | struct net_bridge *br; |
141 | unsigned char *buf; | 141 | unsigned char *buf; |
142 | 142 | ||
143 | if (!p) | ||
144 | goto err; | ||
145 | |||
146 | br = p->br; | ||
147 | spin_lock(&br->lock); | ||
148 | |||
149 | if (p->state == BR_STATE_DISABLED || !(br->dev->flags & IFF_UP)) | ||
150 | goto out; | ||
151 | |||
143 | /* insert into forwarding database after filtering to avoid spoofing */ | 152 | /* insert into forwarding database after filtering to avoid spoofing */ |
144 | br_fdb_update(p->br, p, eth_hdr(skb)->h_source); | 153 | br_fdb_update(br, p, eth_hdr(skb)->h_source); |
154 | |||
155 | if (!br->stp_enabled) | ||
156 | goto out; | ||
145 | 157 | ||
146 | /* need at least the 802 and STP headers */ | 158 | /* need at least the 802 and STP headers */ |
147 | if (!pskb_may_pull(skb, sizeof(header)+1) || | 159 | if (!pskb_may_pull(skb, sizeof(header)+1) || |
148 | memcmp(skb->data, header, sizeof(header))) | 160 | memcmp(skb->data, header, sizeof(header))) |
149 | goto err; | 161 | goto out; |
150 | 162 | ||
151 | buf = skb_pull(skb, sizeof(header)); | 163 | buf = skb_pull(skb, sizeof(header)); |
152 | 164 | ||
153 | spin_lock_bh(&br->lock); | ||
154 | if (p->state == BR_STATE_DISABLED | ||
155 | || !(br->dev->flags & IFF_UP) | ||
156 | || !br->stp_enabled) | ||
157 | goto out; | ||
158 | |||
159 | if (buf[0] == BPDU_TYPE_CONFIG) { | 165 | if (buf[0] == BPDU_TYPE_CONFIG) { |
160 | struct br_config_bpdu bpdu; | 166 | struct br_config_bpdu bpdu; |
161 | 167 | ||
@@ -201,7 +207,7 @@ int br_stp_handle_bpdu(struct sk_buff *skb) | |||
201 | br_received_tcn_bpdu(p); | 207 | br_received_tcn_bpdu(p); |
202 | } | 208 | } |
203 | out: | 209 | out: |
204 | spin_unlock_bh(&br->lock); | 210 | spin_unlock(&br->lock); |
205 | err: | 211 | err: |
206 | kfree_skb(skb); | 212 | kfree_skb(skb); |
207 | return 0; | 213 | return 0; |