summaryrefslogtreecommitdiff
path: root/drivers/net/ipvlan
diff options
context:
space:
mode:
authorAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-09-08 01:01:14 -0300
committerAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-09-08 01:01:14 -0300
commite5fd91f1ef340da553f7a79da9540c3db711c937 (patch)
treeb11842027dc6641da63f4bcc524f8678263304a3 /drivers/net/ipvlan
parent2a9b0348e685a63d97486f6749622b61e9e3292f (diff)
Linux-libre 4.2-gnu
Diffstat (limited to 'drivers/net/ipvlan')
-rw-r--r--drivers/net/ipvlan/ipvlan.h14
-rw-r--r--drivers/net/ipvlan/ipvlan_core.c144
-rw-r--r--drivers/net/ipvlan/ipvlan_main.c67
3 files changed, 129 insertions, 96 deletions
diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h
index 54549a622..9542b7bac 100644
--- a/drivers/net/ipvlan/ipvlan.h
+++ b/drivers/net/ipvlan/ipvlan.h
@@ -39,6 +39,8 @@
#define IPVLAN_MAC_FILTER_SIZE (1 << IPVLAN_MAC_FILTER_BITS)
#define IPVLAN_MAC_FILTER_MASK (IPVLAN_MAC_FILTER_SIZE - 1)
+#define IPVLAN_QBACKLOG_LIMIT 1000
+
typedef enum {
IPVL_IPV6 = 0,
IPVL_ICMPV6,
@@ -65,8 +67,6 @@ struct ipvl_dev {
struct ipvl_port *port;
struct net_device *phy_dev;
struct list_head addrs;
- int ipv4cnt;
- int ipv6cnt;
struct ipvl_pcpu_stats __percpu *pcpu_stats;
DECLARE_BITMAP(mac_filters, IPVLAN_MAC_FILTER_SIZE);
netdev_features_t sfeatures;
@@ -93,6 +93,8 @@ struct ipvl_port {
struct hlist_head hlhead[IPVLAN_HASH_SIZE];
struct list_head ipvlans;
struct rcu_head rcu;
+ struct work_struct wq;
+ struct sk_buff_head backlog;
int count;
u16 mode;
};
@@ -102,6 +104,11 @@ static inline struct ipvl_port *ipvlan_port_get_rcu(const struct net_device *d)
return rcu_dereference(d->rx_handler_data);
}
+static inline struct ipvl_port *ipvlan_port_get_rcu_bh(const struct net_device *d)
+{
+ return rcu_dereference_bh(d->rx_handler_data);
+}
+
static inline struct ipvl_port *ipvlan_port_get_rtnl(const struct net_device *d)
{
return rtnl_dereference(d->rx_handler_data);
@@ -112,6 +119,7 @@ void ipvlan_set_port_mode(struct ipvl_port *port, u32 nval);
void ipvlan_init_secret(void);
unsigned int ipvlan_mac_hash(const unsigned char *addr);
rx_handler_result_t ipvlan_handle_frame(struct sk_buff **pskb);
+void ipvlan_process_multicast(struct work_struct *work);
int ipvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev);
void ipvlan_ht_addr_add(struct ipvl_dev *ipvlan, struct ipvl_addr *addr);
struct ipvl_addr *ipvlan_find_addr(const struct ipvl_dev *ipvlan,
@@ -119,5 +127,5 @@ struct ipvl_addr *ipvlan_find_addr(const struct ipvl_dev *ipvlan,
bool ipvlan_addr_busy(struct ipvl_port *port, void *iaddr, bool is_v6);
struct ipvl_addr *ipvlan_ht_addr_lookup(const struct ipvl_port *port,
const void *iaddr, bool is_v6);
-void ipvlan_ht_addr_del(struct ipvl_addr *addr, bool sync);
+void ipvlan_ht_addr_del(struct ipvl_addr *addr);
#endif /* __IPVLAN_H */
diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c
index c30b5c300..207f62e8d 100644
--- a/drivers/net/ipvlan/ipvlan_core.c
+++ b/drivers/net/ipvlan/ipvlan_core.c
@@ -85,11 +85,9 @@ void ipvlan_ht_addr_add(struct ipvl_dev *ipvlan, struct ipvl_addr *addr)
hlist_add_head_rcu(&addr->hlnode, &port->hlhead[hash]);
}
-void ipvlan_ht_addr_del(struct ipvl_addr *addr, bool sync)
+void ipvlan_ht_addr_del(struct ipvl_addr *addr)
{
hlist_del_init_rcu(&addr->hlnode);
- if (sync)
- synchronize_rcu();
}
struct ipvl_addr *ipvlan_find_addr(const struct ipvl_dev *ipvlan,
@@ -189,62 +187,69 @@ unsigned int ipvlan_mac_hash(const unsigned char *addr)
return hash & IPVLAN_MAC_FILTER_MASK;
}
-static void ipvlan_multicast_frame(struct ipvl_port *port, struct sk_buff *skb,
- const struct ipvl_dev *in_dev, bool local)
+void ipvlan_process_multicast(struct work_struct *work)
{
- struct ethhdr *eth = eth_hdr(skb);
+ struct ipvl_port *port = container_of(work, struct ipvl_port, wq);
+ struct ethhdr *ethh;
struct ipvl_dev *ipvlan;
- struct sk_buff *nskb;
+ struct sk_buff *skb, *nskb;
+ struct sk_buff_head list;
unsigned int len;
unsigned int mac_hash;
int ret;
+ u8 pkt_type;
+ bool hlocal, dlocal;
- if (skb->protocol == htons(ETH_P_PAUSE))
- return;
-
- rcu_read_lock();
- list_for_each_entry_rcu(ipvlan, &port->ipvlans, pnode) {
- if (local && (ipvlan == in_dev))
- continue;
+ __skb_queue_head_init(&list);
- mac_hash = ipvlan_mac_hash(eth->h_dest);
- if (!test_bit(mac_hash, ipvlan->mac_filters))
- continue;
+ spin_lock_bh(&port->backlog.lock);
+ skb_queue_splice_tail_init(&port->backlog, &list);
+ spin_unlock_bh(&port->backlog.lock);
- ret = NET_RX_DROP;
- len = skb->len + ETH_HLEN;
- nskb = skb_clone(skb, GFP_ATOMIC);
- if (!nskb)
- goto mcast_acct;
+ while ((skb = __skb_dequeue(&list)) != NULL) {
+ ethh = eth_hdr(skb);
+ hlocal = ether_addr_equal(ethh->h_source, port->dev->dev_addr);
+ mac_hash = ipvlan_mac_hash(ethh->h_dest);
- if (ether_addr_equal(eth->h_dest, ipvlan->phy_dev->broadcast))
- nskb->pkt_type = PACKET_BROADCAST;
+ if (ether_addr_equal(ethh->h_dest, port->dev->broadcast))
+ pkt_type = PACKET_BROADCAST;
else
- nskb->pkt_type = PACKET_MULTICAST;
-
- nskb->dev = ipvlan->dev;
- if (local)
- ret = dev_forward_skb(ipvlan->dev, nskb);
- else
- ret = netif_rx(nskb);
-mcast_acct:
- ipvlan_count_rx(ipvlan, len, ret == NET_RX_SUCCESS, true);
- }
- rcu_read_unlock();
-
- /* Locally generated? ...Forward a copy to the main-device as
- * well. On the RX side we'll ignore it (wont give it to any
- * of the virtual devices.
- */
- if (local) {
- nskb = skb_clone(skb, GFP_ATOMIC);
- if (nskb) {
- if (ether_addr_equal(eth->h_dest, port->dev->broadcast))
- nskb->pkt_type = PACKET_BROADCAST;
+ pkt_type = PACKET_MULTICAST;
+
+ dlocal = false;
+ rcu_read_lock();
+ list_for_each_entry_rcu(ipvlan, &port->ipvlans, pnode) {
+ if (hlocal && (ipvlan->dev == skb->dev)) {
+ dlocal = true;
+ continue;
+ }
+ if (!test_bit(mac_hash, ipvlan->mac_filters))
+ 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
- nskb->pkt_type = PACKET_MULTICAST;
-
- dev_forward_skb(port->dev, nskb);
+ ret = netif_rx(nskb);
+acct:
+ ipvlan_count_rx(ipvlan, len, ret == NET_RX_SUCCESS, true);
+ }
+ rcu_read_unlock();
+
+ if (dlocal) {
+ /* If the packet originated here, send it out. */
+ skb->dev = port->dev;
+ skb->pkt_type = pkt_type;
+ dev_queue_xmit(skb);
+ } else {
+ kfree_skb(skb);
}
}
}
@@ -446,6 +451,26 @@ out:
return ret;
}
+static void ipvlan_multicast_enqueue(struct ipvl_port *port,
+ struct sk_buff *skb)
+{
+ if (skb->protocol == htons(ETH_P_PAUSE)) {
+ kfree_skb(skb);
+ return;
+ }
+
+ spin_lock(&port->backlog.lock);
+ if (skb_queue_len(&port->backlog) < IPVLAN_QBACKLOG_LIMIT) {
+ __skb_queue_tail(&port->backlog, skb);
+ spin_unlock(&port->backlog.lock);
+ schedule_work(&port->wq);
+ } else {
+ spin_unlock(&port->backlog.lock);
+ atomic_long_inc(&skb->dev->rx_dropped);
+ kfree_skb(skb);
+ }
+}
+
static int ipvlan_xmit_mode_l3(struct sk_buff *skb, struct net_device *dev)
{
const struct ipvl_dev *ipvlan = netdev_priv(dev);
@@ -493,11 +518,8 @@ static int ipvlan_xmit_mode_l2(struct sk_buff *skb, struct net_device *dev)
return dev_forward_skb(ipvlan->phy_dev, skb);
} else if (is_multicast_ether_addr(eth->h_dest)) {
- u8 ip_summed = skb->ip_summed;
-
- skb->ip_summed = CHECKSUM_UNNECESSARY;
- ipvlan_multicast_frame(ipvlan->port, skb, ipvlan, true);
- skb->ip_summed = ip_summed;
+ ipvlan_multicast_enqueue(ipvlan->port, skb);
+ return NET_XMIT_SUCCESS;
}
skb->dev = ipvlan->phy_dev;
@@ -507,7 +529,7 @@ static int ipvlan_xmit_mode_l2(struct sk_buff *skb, struct net_device *dev)
int ipvlan_queue_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
- struct ipvl_port *port = ipvlan_port_get_rcu(ipvlan->phy_dev);
+ struct ipvl_port *port = ipvlan_port_get_rcu_bh(ipvlan->phy_dev);
if (!port)
goto out;
@@ -581,8 +603,18 @@ static rx_handler_result_t ipvlan_handle_mode_l2(struct sk_buff **pskb,
int addr_type;
if (is_multicast_ether_addr(eth->h_dest)) {
- if (ipvlan_external_frame(skb, port))
- ipvlan_multicast_frame(port, skb, NULL, false);
+ if (ipvlan_external_frame(skb, port)) {
+ struct sk_buff *nskb = skb_clone(skb, GFP_ATOMIC);
+
+ /* External frames are queued for device local
+ * distribution, but a copy is given to master
+ * straight away to avoid sending duplicates later
+ * when work-queue processes this frame. This is
+ * achieved by returning RX_HANDLER_PASS.
+ */
+ if (nskb)
+ ipvlan_multicast_enqueue(port, nskb);
+ }
} else {
struct ipvl_addr *addr;
diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c
index 77b92a0fe..20b58bdec 100644
--- a/drivers/net/ipvlan/ipvlan_main.c
+++ b/drivers/net/ipvlan/ipvlan_main.c
@@ -54,6 +54,9 @@ static int ipvlan_port_create(struct net_device *dev)
for (idx = 0; idx < IPVLAN_HASH_SIZE; idx++)
INIT_HLIST_HEAD(&port->hlhead[idx]);
+ skb_queue_head_init(&port->backlog);
+ INIT_WORK(&port->wq, ipvlan_process_multicast);
+
err = netdev_rx_handler_register(dev, ipvlan_handle_frame, port);
if (err)
goto err;
@@ -72,6 +75,8 @@ static void ipvlan_port_destroy(struct net_device *dev)
dev->priv_flags &= ~IFF_IPVLAN_MASTER;
netdev_rx_handler_unregister(dev);
+ cancel_work_sync(&port->wq);
+ __skb_queue_purge(&port->backlog);
kfree_rcu(port, rcu);
}
@@ -148,10 +153,9 @@ static int ipvlan_open(struct net_device *dev)
else
dev->flags &= ~IFF_NOARP;
- if (ipvlan->ipv6cnt > 0 || ipvlan->ipv4cnt > 0) {
- list_for_each_entry(addr, &ipvlan->addrs, anode)
- ipvlan_ht_addr_add(ipvlan, addr);
- }
+ list_for_each_entry(addr, &ipvlan->addrs, anode)
+ ipvlan_ht_addr_add(ipvlan, addr);
+
return dev_uc_add(phy_dev, phy_dev->dev_addr);
}
@@ -166,10 +170,9 @@ static int ipvlan_stop(struct net_device *dev)
dev_uc_del(phy_dev, phy_dev->dev_addr);
- if (ipvlan->ipv6cnt > 0 || ipvlan->ipv4cnt > 0) {
- list_for_each_entry(addr, &ipvlan->addrs, anode)
- ipvlan_ht_addr_del(addr, !dev->dismantle);
- }
+ list_for_each_entry(addr, &ipvlan->addrs, anode)
+ ipvlan_ht_addr_del(addr);
+
return 0;
}
@@ -213,17 +216,6 @@ static void ipvlan_change_rx_flags(struct net_device *dev, int change)
dev_set_allmulti(phy_dev, dev->flags & IFF_ALLMULTI? 1 : -1);
}
-static void ipvlan_set_broadcast_mac_filter(struct ipvl_dev *ipvlan, bool set)
-{
- struct net_device *dev = ipvlan->dev;
- unsigned int hashbit = ipvlan_mac_hash(dev->broadcast);
-
- if (set && !test_bit(hashbit, ipvlan->mac_filters))
- __set_bit(hashbit, ipvlan->mac_filters);
- else if (!set && test_bit(hashbit, ipvlan->mac_filters))
- __clear_bit(hashbit, ipvlan->mac_filters);
-}
-
static void ipvlan_set_multicast_mac_filter(struct net_device *dev)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
@@ -238,6 +230,12 @@ static void ipvlan_set_multicast_mac_filter(struct net_device *dev)
netdev_for_each_mc_addr(ha, dev)
__set_bit(ipvlan_mac_hash(ha->addr), mc_filters);
+ /* Turn-on broadcast bit irrespective of address family,
+ * since broadcast is deferred to a work-queue, hence no
+ * impact on fast-path processing.
+ */
+ __set_bit(ipvlan_mac_hash(dev->broadcast), mc_filters);
+
bitmap_copy(ipvlan->mac_filters, mc_filters,
IPVLAN_MAC_FILTER_SIZE);
}
@@ -471,8 +469,6 @@ static int ipvlan_link_new(struct net *src_net, struct net_device *dev,
ipvlan->port = port;
ipvlan->sfeatures = IPVLAN_FEATURES;
INIT_LIST_HEAD(&ipvlan->addrs);
- ipvlan->ipv4cnt = 0;
- ipvlan->ipv6cnt = 0;
/* TODO Probably put random address here to be presented to the
* world but keep using the physical-dev address for the outgoing
@@ -508,12 +504,12 @@ static void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
struct ipvl_dev *ipvlan = netdev_priv(dev);
struct ipvl_addr *addr, *next;
- if (ipvlan->ipv6cnt > 0 || ipvlan->ipv4cnt > 0) {
- list_for_each_entry_safe(addr, next, &ipvlan->addrs, anode) {
- ipvlan_ht_addr_del(addr, !dev->dismantle);
- list_del(&addr->anode);
- }
+ list_for_each_entry_safe(addr, next, &ipvlan->addrs, anode) {
+ ipvlan_ht_addr_del(addr);
+ list_del(&addr->anode);
+ kfree_rcu(addr, rcu);
}
+
list_del_rcu(&ipvlan->pnode);
unregister_netdevice_queue(dev, head);
netdev_upper_dev_unlink(ipvlan->phy_dev, dev);
@@ -627,7 +623,7 @@ static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr)
memcpy(&addr->ip6addr, ip6_addr, sizeof(struct in6_addr));
addr->atype = IPVL_IPV6;
list_add_tail(&addr->anode, &ipvlan->addrs);
- ipvlan->ipv6cnt++;
+
/* If the interface is not up, the address will be added to the hash
* list by ipvlan_open.
*/
@@ -645,10 +641,8 @@ static void ipvlan_del_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr)
if (!addr)
return;
- ipvlan_ht_addr_del(addr, true);
+ ipvlan_ht_addr_del(addr);
list_del(&addr->anode);
- ipvlan->ipv6cnt--;
- WARN_ON(ipvlan->ipv6cnt < 0);
kfree_rcu(addr, rcu);
return;
@@ -661,6 +655,10 @@ static int ipvlan_addr6_event(struct notifier_block *unused,
struct net_device *dev = (struct net_device *)if6->idev->dev;
struct ipvl_dev *ipvlan = netdev_priv(dev);
+ /* FIXME IPv6 autoconf calls us from bh without RTNL */
+ if (in_softirq())
+ return NOTIFY_DONE;
+
if (!netif_is_ipvlan(dev))
return NOTIFY_DONE;
@@ -699,13 +697,12 @@ static int ipvlan_add_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr)
memcpy(&addr->ip4addr, ip4_addr, sizeof(struct in_addr));
addr->atype = IPVL_IPV4;
list_add_tail(&addr->anode, &ipvlan->addrs);
- ipvlan->ipv4cnt++;
+
/* If the interface is not up, the address will be added to the hash
* list by ipvlan_open.
*/
if (netif_running(ipvlan->dev))
ipvlan_ht_addr_add(ipvlan, addr);
- ipvlan_set_broadcast_mac_filter(ipvlan, true);
return 0;
}
@@ -718,12 +715,8 @@ static void ipvlan_del_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr)
if (!addr)
return;
- ipvlan_ht_addr_del(addr, true);
+ ipvlan_ht_addr_del(addr);
list_del(&addr->anode);
- ipvlan->ipv4cnt--;
- WARN_ON(ipvlan->ipv4cnt < 0);
- if (!ipvlan->ipv4cnt)
- ipvlan_set_broadcast_mac_filter(ipvlan, false);
kfree_rcu(addr, rcu);
return;