Skip to content

Commit

Permalink
ipvlan: fix various issues in ipvlan_process_multicast()
Browse files Browse the repository at this point in the history
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: ba35f85 ("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>
  • Loading branch information
Eric Dumazet authored and David S. Miller committed Dec 23, 2016
1 parent 50b17cf commit b1227d0
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 14 deletions.
38 changes: 25 additions & 13 deletions drivers/net/ipvlan/ipvlan_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ void ipvlan_process_multicast(struct work_struct *work)
spin_unlock_bh(&port->backlog.lock);

while ((skb = __skb_dequeue(&list)) != NULL) {
struct net_device *dev = skb->dev;
bool consumed = false;

ethh = eth_hdr(skb);
hlocal = ether_addr_equal(ethh->h_source, port->dev->dev_addr);
mac_hash = ipvlan_mac_hash(ethh->h_dest);
Expand All @@ -219,27 +222,29 @@ void ipvlan_process_multicast(struct work_struct *work)
dlocal = false;
rcu_read_lock();
list_for_each_entry_rcu(ipvlan, &port->ipvlans, pnode) {
if (hlocal && (ipvlan->dev == skb->dev)) {
if (hlocal && (ipvlan->dev == dev)) {
dlocal = true;
continue;
}
if (!test_bit(mac_hash, ipvlan->mac_filters))
continue;

if (!(ipvlan->dev->flags & IFF_UP))
continue;
ret = NET_RX_DROP;
len = skb->len + ETH_HLEN;
nskb = skb_clone(skb, GFP_ATOMIC);
if (!nskb)
goto acct;

nskb->pkt_type = pkt_type;
nskb->dev = ipvlan->dev;
if (hlocal)
ret = dev_forward_skb(ipvlan->dev, nskb);
else
ret = netif_rx(nskb);
acct:
local_bh_disable();
if (nskb) {
consumed = true;
nskb->pkt_type = pkt_type;
nskb->dev = ipvlan->dev;
if (hlocal)
ret = dev_forward_skb(ipvlan->dev, nskb);
else
ret = netif_rx(nskb);
}
ipvlan_count_rx(ipvlan, len, ret == NET_RX_SUCCESS, true);
local_bh_enable();
}
rcu_read_unlock();

Expand All @@ -249,8 +254,13 @@ void ipvlan_process_multicast(struct work_struct *work)
skb->pkt_type = pkt_type;
dev_queue_xmit(skb);
} else {
kfree_skb(skb);
if (consumed)
consume_skb(skb);
else
kfree_skb(skb);
}
if (dev)
dev_put(dev);
}
}

Expand Down Expand Up @@ -479,6 +489,8 @@ static void ipvlan_multicast_enqueue(struct ipvl_port *port,

spin_lock(&port->backlog.lock);
if (skb_queue_len(&port->backlog) < IPVLAN_QBACKLOG_LIMIT) {
if (skb->dev)
dev_hold(skb->dev);
__skb_queue_tail(&port->backlog, skb);
spin_unlock(&port->backlog.lock);
schedule_work(&port->wq);
Expand Down
7 changes: 6 additions & 1 deletion drivers/net/ipvlan/ipvlan_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ static int ipvlan_port_create(struct net_device *dev)
static void ipvlan_port_destroy(struct net_device *dev)
{
struct ipvl_port *port = ipvlan_port_get_rtnl(dev);
struct sk_buff *skb;

dev->priv_flags &= ~IFF_IPVLAN_MASTER;
if (port->mode == IPVLAN_MODE_L3S) {
Expand All @@ -144,7 +145,11 @@ static void ipvlan_port_destroy(struct net_device *dev)
}
netdev_rx_handler_unregister(dev);
cancel_work_sync(&port->wq);
__skb_queue_purge(&port->backlog);
while ((skb = __skb_dequeue(&port->backlog)) != NULL) {
if (skb->dev)
dev_put(skb->dev);
kfree_skb(skb);
}
kfree(port);
}

Expand Down

0 comments on commit b1227d0

Please sign in to comment.