diff options
author | Eric Dumazet <edumazet@google.com> | 2016-12-21 21:00:24 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2016-12-23 17:53:47 -0500 |
commit | b1227d019fa98c43381ad8827baf7efbe2923ed1 (patch) | |
tree | 576fad624349c35feded5a554103bf1c4cf5a68e /drivers/net | |
parent | 50b17cfb1917b207612327d354e9043dbcbde431 (diff) |
ipvlan: fix various issues in ipvlan_process_multicast()
1) netif_rx() / dev_forward_skb() should not be called from process
context.
2) ipvlan_count_rx() should be called with preemption disabled.
3) We should check if ipvlan->dev is up before feeding packets
to netif_rx()
4) We need to prevent device from disappearing if some packets
are in the multicast backlog.
5) One kfree_skb() should be a consume_skb() eventually
Fixes: ba35f8588f47 ("ipvlan: Defer multicast / broadcast processing to
a work-queue")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Cc: Mahesh Bandewar <maheshb@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/ipvlan/ipvlan_core.c | 38 | ||||
-rw-r--r-- | drivers/net/ipvlan/ipvlan_main.c | 7 |
2 files changed, 31 insertions, 14 deletions
diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c index b4e990743e1d..ea6bc1e12cdf 100644 --- a/drivers/net/ipvlan/ipvlan_core.c +++ b/drivers/net/ipvlan/ipvlan_core.c | |||
@@ -207,6 +207,9 @@ void ipvlan_process_multicast(struct work_struct *work) | |||
207 | spin_unlock_bh(&port->backlog.lock); | 207 | spin_unlock_bh(&port->backlog.lock); |
208 | 208 | ||
209 | while ((skb = __skb_dequeue(&list)) != NULL) { | 209 | while ((skb = __skb_dequeue(&list)) != NULL) { |
210 | struct net_device *dev = skb->dev; | ||
211 | bool consumed = false; | ||
212 | |||
210 | ethh = eth_hdr(skb); | 213 | ethh = eth_hdr(skb); |
211 | hlocal = ether_addr_equal(ethh->h_source, port->dev->dev_addr); | 214 | hlocal = ether_addr_equal(ethh->h_source, port->dev->dev_addr); |
212 | mac_hash = ipvlan_mac_hash(ethh->h_dest); | 215 | mac_hash = ipvlan_mac_hash(ethh->h_dest); |
@@ -219,27 +222,29 @@ void ipvlan_process_multicast(struct work_struct *work) | |||
219 | dlocal = false; | 222 | dlocal = false; |
220 | rcu_read_lock(); | 223 | rcu_read_lock(); |
221 | list_for_each_entry_rcu(ipvlan, &port->ipvlans, pnode) { | 224 | list_for_each_entry_rcu(ipvlan, &port->ipvlans, pnode) { |
222 | if (hlocal && (ipvlan->dev == skb->dev)) { | 225 | if (hlocal && (ipvlan->dev == dev)) { |
223 | dlocal = true; | 226 | dlocal = true; |
224 | continue; | 227 | continue; |
225 | } | 228 | } |
226 | if (!test_bit(mac_hash, ipvlan->mac_filters)) | 229 | if (!test_bit(mac_hash, ipvlan->mac_filters)) |
227 | continue; | 230 | continue; |
228 | 231 | if (!(ipvlan->dev->flags & IFF_UP)) | |
232 | continue; | ||
229 | ret = NET_RX_DROP; | 233 | ret = NET_RX_DROP; |
230 | len = skb->len + ETH_HLEN; | 234 | len = skb->len + ETH_HLEN; |
231 | nskb = skb_clone(skb, GFP_ATOMIC); | 235 | nskb = skb_clone(skb, GFP_ATOMIC); |
232 | if (!nskb) | 236 | local_bh_disable(); |
233 | goto acct; | 237 | if (nskb) { |
234 | 238 | consumed = true; | |
235 | nskb->pkt_type = pkt_type; | 239 | nskb->pkt_type = pkt_type; |
236 | nskb->dev = ipvlan->dev; | 240 | nskb->dev = ipvlan->dev; |
237 | if (hlocal) | 241 | if (hlocal) |
238 | ret = dev_forward_skb(ipvlan->dev, nskb); | 242 | ret = dev_forward_skb(ipvlan->dev, nskb); |
239 | else | 243 | else |
240 | ret = netif_rx(nskb); | 244 | ret = netif_rx(nskb); |
241 | acct: | 245 | } |
242 | ipvlan_count_rx(ipvlan, len, ret == NET_RX_SUCCESS, true); | 246 | ipvlan_count_rx(ipvlan, len, ret == NET_RX_SUCCESS, true); |
247 | local_bh_enable(); | ||
243 | } | 248 | } |
244 | rcu_read_unlock(); | 249 | rcu_read_unlock(); |
245 | 250 | ||
@@ -249,8 +254,13 @@ acct: | |||
249 | skb->pkt_type = pkt_type; | 254 | skb->pkt_type = pkt_type; |
250 | dev_queue_xmit(skb); | 255 | dev_queue_xmit(skb); |
251 | } else { | 256 | } else { |
252 | kfree_skb(skb); | 257 | if (consumed) |
258 | consume_skb(skb); | ||
259 | else | ||
260 | kfree_skb(skb); | ||
253 | } | 261 | } |
262 | if (dev) | ||
263 | dev_put(dev); | ||
254 | } | 264 | } |
255 | } | 265 | } |
256 | 266 | ||
@@ -479,6 +489,8 @@ static void ipvlan_multicast_enqueue(struct ipvl_port *port, | |||
479 | 489 | ||
480 | spin_lock(&port->backlog.lock); | 490 | spin_lock(&port->backlog.lock); |
481 | if (skb_queue_len(&port->backlog) < IPVLAN_QBACKLOG_LIMIT) { | 491 | if (skb_queue_len(&port->backlog) < IPVLAN_QBACKLOG_LIMIT) { |
492 | if (skb->dev) | ||
493 | dev_hold(skb->dev); | ||
482 | __skb_queue_tail(&port->backlog, skb); | 494 | __skb_queue_tail(&port->backlog, skb); |
483 | spin_unlock(&port->backlog.lock); | 495 | spin_unlock(&port->backlog.lock); |
484 | schedule_work(&port->wq); | 496 | schedule_work(&port->wq); |
diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c index 693ec5b66222..8b0f99300cbc 100644 --- a/drivers/net/ipvlan/ipvlan_main.c +++ b/drivers/net/ipvlan/ipvlan_main.c | |||
@@ -135,6 +135,7 @@ err: | |||
135 | static void ipvlan_port_destroy(struct net_device *dev) | 135 | static void ipvlan_port_destroy(struct net_device *dev) |
136 | { | 136 | { |
137 | struct ipvl_port *port = ipvlan_port_get_rtnl(dev); | 137 | struct ipvl_port *port = ipvlan_port_get_rtnl(dev); |
138 | struct sk_buff *skb; | ||
138 | 139 | ||
139 | dev->priv_flags &= ~IFF_IPVLAN_MASTER; | 140 | dev->priv_flags &= ~IFF_IPVLAN_MASTER; |
140 | if (port->mode == IPVLAN_MODE_L3S) { | 141 | if (port->mode == IPVLAN_MODE_L3S) { |
@@ -144,7 +145,11 @@ static void ipvlan_port_destroy(struct net_device *dev) | |||
144 | } | 145 | } |
145 | netdev_rx_handler_unregister(dev); | 146 | netdev_rx_handler_unregister(dev); |
146 | cancel_work_sync(&port->wq); | 147 | cancel_work_sync(&port->wq); |
147 | __skb_queue_purge(&port->backlog); | 148 | while ((skb = __skb_dequeue(&port->backlog)) != NULL) { |
149 | if (skb->dev) | ||
150 | dev_put(skb->dev); | ||
151 | kfree_skb(skb); | ||
152 | } | ||
148 | kfree(port); | 153 | kfree(port); |
149 | } | 154 | } |
150 | 155 | ||