summaryrefslogtreecommitdiff
path: root/drivers/net/ipvlan
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2017-02-22 10:15:09 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2017-02-22 10:15:09 -0800
commit3051bf36c25d5153051704291782f8d44e744d36 (patch)
tree72dfc8a1d12675c6f2981d13102df954b678f11b /drivers/net/ipvlan
parent1e74a2eb1f5cc7f2f2b5aa9c9eeecbcf352220a3 (diff)
parent005c3490e9db23738d91e02788606c0fe4734723 (diff)
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next
Pull networking updates from David Miller: "Highlights: 1) Support TX_RING in AF_PACKET TPACKET_V3 mode, from Sowmini Varadhan. 2) Simplify classifier state on sk_buff in order to shrink it a bit. From Willem de Bruijn. 3) Introduce SIPHASH and it's usage for secure sequence numbers and syncookies. From Jason A. Donenfeld. 4) Reduce CPU usage for ICMP replies we are going to limit or suppress, from Jesper Dangaard Brouer. 5) Introduce Shared Memory Communications socket layer, from Ursula Braun. 6) Add RACK loss detection and allow it to actually trigger fast recovery instead of just assisting after other algorithms have triggered it. From Yuchung Cheng. 7) Add xmit_more and BQL support to mvneta driver, from Simon Guinot. 8) skb_cow_data avoidance in esp4 and esp6, from Steffen Klassert. 9) Export MPLS packet stats via netlink, from Robert Shearman. 10) Significantly improve inet port bind conflict handling, especially when an application is restarted and changes it's setting of reuseport. From Josef Bacik. 11) Implement TX batching in vhost_net, from Jason Wang. 12) Extend the dummy device so that VF (virtual function) features, such as configuration, can be more easily tested. From Phil Sutter. 13) Avoid two atomic ops per page on x86 in bnx2x driver, from Eric Dumazet. 14) Add new bpf MAP, implementing a longest prefix match trie. From Daniel Mack. 15) Packet sample offloading support in mlxsw driver, from Yotam Gigi. 16) Add new aquantia driver, from David VomLehn. 17) Add bpf tracepoints, from Daniel Borkmann. 18) Add support for port mirroring to b53 and bcm_sf2 drivers, from Florian Fainelli. 19) Remove custom busy polling in many drivers, it is done in the core networking since 4.5 times. From Eric Dumazet. 20) Support XDP adjust_head in virtio_net, from John Fastabend. 21) Fix several major holes in neighbour entry confirmation, from Julian Anastasov. 22) Add XDP support to bnxt_en driver, from Michael Chan. 23) VXLAN offloads for enic driver, from Govindarajulu Varadarajan. 24) Add IPVTAP driver (IP-VLAN based tap driver) from Sainath Grandhi. 25) Support GRO in IPSEC protocols, from Steffen Klassert" * git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next: (1764 commits) Revert "ath10k: Search SMBIOS for OEM board file extension" net: socket: fix recvmmsg not returning error from sock_error bnxt_en: use eth_hw_addr_random() bpf: fix unlocking of jited image when module ronx not set arch: add ARCH_HAS_SET_MEMORY config net: napi_watchdog() can use napi_schedule_irqoff() tcp: Revert "tcp: tcp_probe: use spin_lock_bh()" net/hsr: use eth_hw_addr_random() net: mvpp2: enable building on 64-bit platforms net: mvpp2: switch to build_skb() in the RX path net: mvpp2: simplify MVPP2_PRS_RI_* definitions net: mvpp2: fix indentation of MVPP2_EXT_GLOBAL_CTRL_DEFAULT net: mvpp2: remove unused register definitions net: mvpp2: simplify mvpp2_bm_bufs_add() net: mvpp2: drop useless fields in mvpp2_bm_pool and related code net: mvpp2: remove unused 'tx_skb' field of 'struct mvpp2_tx_queue' net: mvpp2: release reference to txq_cpu[] entry after unmapping net: mvpp2: handle too large value in mvpp2_rx_time_coal_set() net: mvpp2: handle too large value handling in mvpp2_rx_pkts_coal_set() net: mvpp2: remove useless arguments in mvpp2_rx_{pkts, time}_coal_set ...
Diffstat (limited to 'drivers/net/ipvlan')
-rw-r--r--drivers/net/ipvlan/Makefile1
-rw-r--r--drivers/net/ipvlan/ipvlan.h9
-rw-r--r--drivers/net/ipvlan/ipvlan_core.c6
-rw-r--r--drivers/net/ipvlan/ipvlan_main.c135
-rw-r--r--drivers/net/ipvlan/ipvtap.c241
5 files changed, 331 insertions, 61 deletions
diff --git a/drivers/net/ipvlan/Makefile b/drivers/net/ipvlan/Makefile
index df79910192d6..8a2c64dc9641 100644
--- a/drivers/net/ipvlan/Makefile
+++ b/drivers/net/ipvlan/Makefile
@@ -3,5 +3,6 @@
#
obj-$(CONFIG_IPVLAN) += ipvlan.o
+obj-$(CONFIG_IPVTAP) += ipvtap.o
ipvlan-objs := ipvlan_core.o ipvlan_main.o
diff --git a/drivers/net/ipvlan/ipvlan.h b/drivers/net/ipvlan/ipvlan.h
index dbfbb33ac66c..800a46c8d26c 100644
--- a/drivers/net/ipvlan/ipvlan.h
+++ b/drivers/net/ipvlan/ipvlan.h
@@ -94,9 +94,11 @@ struct ipvl_port {
struct hlist_head hlhead[IPVLAN_HASH_SIZE];
struct list_head ipvlans;
u16 mode;
+ u16 dev_id_start;
struct work_struct wq;
struct sk_buff_head backlog;
int count;
+ struct ida ida;
};
struct ipvl_skb_cb {
@@ -133,4 +135,11 @@ struct sk_buff *ipvlan_l3_rcv(struct net_device *dev, struct sk_buff *skb,
u16 proto);
unsigned int ipvlan_nf_input(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state);
+void ipvlan_count_rx(const struct ipvl_dev *ipvlan,
+ unsigned int len, bool success, bool mcast);
+int ipvlan_link_new(struct net *src_net, struct net_device *dev,
+ struct nlattr *tb[], struct nlattr *data[]);
+void ipvlan_link_delete(struct net_device *dev, struct list_head *head);
+void ipvlan_link_setup(struct net_device *dev);
+int ipvlan_link_register(struct rtnl_link_ops *ops);
#endif /* __IPVLAN_H */
diff --git a/drivers/net/ipvlan/ipvlan_core.c b/drivers/net/ipvlan/ipvlan_core.c
index 83ce74acf82d..1f3295e274d0 100644
--- a/drivers/net/ipvlan/ipvlan_core.c
+++ b/drivers/net/ipvlan/ipvlan_core.c
@@ -16,12 +16,9 @@ void ipvlan_init_secret(void)
net_get_random_once(&ipvlan_jhash_secret, sizeof(ipvlan_jhash_secret));
}
-static void ipvlan_count_rx(const struct ipvl_dev *ipvlan,
+void ipvlan_count_rx(const struct ipvl_dev *ipvlan,
unsigned int len, bool success, bool mcast)
{
- if (!ipvlan)
- return;
-
if (likely(success)) {
struct ipvl_pcpu_stats *pcptr;
@@ -36,6 +33,7 @@ static void ipvlan_count_rx(const struct ipvl_dev *ipvlan,
this_cpu_inc(ipvlan->pcpu_stats->rx_errs);
}
}
+EXPORT_SYMBOL_GPL(ipvlan_count_rx);
static u8 ipvlan_get_v6_hash(const void *iaddr)
{
diff --git a/drivers/net/ipvlan/ipvlan_main.c b/drivers/net/ipvlan/ipvlan_main.c
index 8b0f99300cbc..aa8575ccbce3 100644
--- a/drivers/net/ipvlan/ipvlan_main.c
+++ b/drivers/net/ipvlan/ipvlan_main.c
@@ -102,8 +102,8 @@ static int ipvlan_port_create(struct net_device *dev)
return -EINVAL;
}
- if (netif_is_macvlan_port(dev)) {
- netdev_err(dev, "Master is a macvlan port.\n");
+ if (netdev_is_rx_handler_busy(dev)) {
+ netdev_err(dev, "Device is already in use.\n");
return -EBUSY;
}
@@ -119,6 +119,8 @@ static int ipvlan_port_create(struct net_device *dev)
skb_queue_head_init(&port->backlog);
INIT_WORK(&port->wq, ipvlan_process_multicast);
+ ida_init(&port->ida);
+ port->dev_id_start = 1;
err = netdev_rx_handler_register(dev, ipvlan_handle_frame, port);
if (err)
@@ -150,6 +152,7 @@ static void ipvlan_port_destroy(struct net_device *dev)
dev_put(skb->dev);
kfree_skb(skb);
}
+ ida_destroy(&port->ida);
kfree(port);
}
@@ -301,8 +304,8 @@ static void ipvlan_set_multicast_mac_filter(struct net_device *dev)
dev_mc_sync(ipvlan->phy_dev, dev);
}
-static struct rtnl_link_stats64 *ipvlan_get_stats64(struct net_device *dev,
- struct rtnl_link_stats64 *s)
+static void ipvlan_get_stats64(struct net_device *dev,
+ struct rtnl_link_stats64 *s)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
@@ -339,7 +342,6 @@ static struct rtnl_link_stats64 *ipvlan_get_stats64(struct net_device *dev,
s->rx_dropped = rx_errs;
s->tx_dropped = tx_drps;
}
- return s;
}
static int ipvlan_vlan_rx_add_vid(struct net_device *dev, __be16 proto, u16 vid)
@@ -494,8 +496,8 @@ err:
return ret;
}
-static int ipvlan_link_new(struct net *src_net, struct net_device *dev,
- struct nlattr *tb[], struct nlattr *data[])
+int ipvlan_link_new(struct net *src_net, struct net_device *dev,
+ struct nlattr *tb[], struct nlattr *data[])
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
struct ipvl_port *port;
@@ -533,6 +535,29 @@ static int ipvlan_link_new(struct net *src_net, struct net_device *dev,
ipvlan_adjust_mtu(ipvlan, phy_dev);
INIT_LIST_HEAD(&ipvlan->addrs);
+ /* If the port-id base is at the MAX value, then wrap it around and
+ * begin from 0x1 again. This may be due to a busy system where lots
+ * of slaves are getting created and deleted.
+ */
+ if (port->dev_id_start == 0xFFFE)
+ port->dev_id_start = 0x1;
+
+ /* Since L2 address is shared among all IPvlan slaves including
+ * master, use unique 16 bit dev-ids to diffentiate among them.
+ * Assign IDs between 0x1 and 0xFFFE (used by the master) to each
+ * slave link [see addrconf_ifid_eui48()].
+ */
+ err = ida_simple_get(&port->ida, port->dev_id_start, 0xFFFE,
+ GFP_KERNEL);
+ if (err < 0)
+ err = ida_simple_get(&port->ida, 0x1, port->dev_id_start,
+ GFP_KERNEL);
+ if (err < 0)
+ goto destroy_ipvlan_port;
+ dev->dev_id = err;
+ /* Increment id-base to the next slot for the future assignment */
+ port->dev_id_start = err + 1;
+
/* TODO Probably put random address here to be presented to the
* world but keep using the physical-dev address for the outgoing
* packets.
@@ -543,7 +568,7 @@ static int ipvlan_link_new(struct net *src_net, struct net_device *dev,
err = register_netdevice(dev);
if (err < 0)
- goto destroy_ipvlan_port;
+ goto remove_ida;
err = netdev_upper_dev_link(phy_dev, dev);
if (err) {
@@ -562,13 +587,16 @@ unlink_netdev:
netdev_upper_dev_unlink(phy_dev, dev);
unregister_netdev:
unregister_netdevice(dev);
+remove_ida:
+ ida_simple_remove(&port->ida, dev->dev_id);
destroy_ipvlan_port:
if (create)
ipvlan_port_destroy(phy_dev);
return err;
}
+EXPORT_SYMBOL_GPL(ipvlan_link_new);
-static void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
+void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
{
struct ipvl_dev *ipvlan = netdev_priv(dev);
struct ipvl_addr *addr, *next;
@@ -579,12 +607,14 @@ static void ipvlan_link_delete(struct net_device *dev, struct list_head *head)
kfree_rcu(addr, rcu);
}
+ ida_simple_remove(&ipvlan->port->ida, dev->dev_id);
list_del_rcu(&ipvlan->pnode);
unregister_netdevice_queue(dev, head);
netdev_upper_dev_unlink(ipvlan->phy_dev, dev);
}
+EXPORT_SYMBOL_GPL(ipvlan_link_delete);
-static void ipvlan_link_setup(struct net_device *dev)
+void ipvlan_link_setup(struct net_device *dev)
{
ether_setup(dev);
@@ -595,6 +625,7 @@ static void ipvlan_link_setup(struct net_device *dev)
dev->header_ops = &ipvlan_header_ops;
dev->ethtool_ops = &ipvlan_ethtool_ops;
}
+EXPORT_SYMBOL_GPL(ipvlan_link_setup);
static const struct nla_policy ipvlan_nl_policy[IFLA_IPVLAN_MAX + 1] =
{
@@ -605,22 +636,22 @@ static struct rtnl_link_ops ipvlan_link_ops = {
.kind = "ipvlan",
.priv_size = sizeof(struct ipvl_dev),
- .get_size = ipvlan_nl_getsize,
- .policy = ipvlan_nl_policy,
- .validate = ipvlan_nl_validate,
- .fill_info = ipvlan_nl_fillinfo,
- .changelink = ipvlan_nl_changelink,
- .maxtype = IFLA_IPVLAN_MAX,
-
.setup = ipvlan_link_setup,
.newlink = ipvlan_link_new,
.dellink = ipvlan_link_delete,
};
-static int ipvlan_link_register(struct rtnl_link_ops *ops)
+int ipvlan_link_register(struct rtnl_link_ops *ops)
{
+ ops->get_size = ipvlan_nl_getsize;
+ ops->policy = ipvlan_nl_policy;
+ ops->validate = ipvlan_nl_validate;
+ ops->fill_info = ipvlan_nl_fillinfo;
+ ops->changelink = ipvlan_nl_changelink;
+ ops->maxtype = IFLA_IPVLAN_MAX;
return rtnl_link_register(ops);
}
+EXPORT_SYMBOL_GPL(ipvlan_link_register);
static int ipvlan_device_event(struct notifier_block *unused,
unsigned long event, void *ptr)
@@ -674,23 +705,22 @@ static int ipvlan_device_event(struct notifier_block *unused,
return NOTIFY_DONE;
}
-static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr)
+static int ipvlan_add_addr(struct ipvl_dev *ipvlan, void *iaddr, bool is_v6)
{
struct ipvl_addr *addr;
- if (ipvlan_addr_busy(ipvlan->port, ip6_addr, true)) {
- netif_err(ipvlan, ifup, ipvlan->dev,
- "Failed to add IPv6=%pI6c addr for %s intf\n",
- ip6_addr, ipvlan->dev->name);
- return -EINVAL;
- }
addr = kzalloc(sizeof(struct ipvl_addr), GFP_ATOMIC);
if (!addr)
return -ENOMEM;
addr->master = ipvlan;
- memcpy(&addr->ip6addr, ip6_addr, sizeof(struct in6_addr));
- addr->atype = IPVL_IPV6;
+ if (is_v6) {
+ memcpy(&addr->ip6addr, iaddr, sizeof(struct in6_addr));
+ addr->atype = IPVL_IPV6;
+ } else {
+ memcpy(&addr->ip4addr, iaddr, sizeof(struct in_addr));
+ addr->atype = IPVL_IPV4;
+ }
list_add_tail(&addr->anode, &ipvlan->addrs);
/* If the interface is not up, the address will be added to the hash
@@ -702,11 +732,11 @@ static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr)
return 0;
}
-static void ipvlan_del_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr)
+static void ipvlan_del_addr(struct ipvl_dev *ipvlan, void *iaddr, bool is_v6)
{
struct ipvl_addr *addr;
- addr = ipvlan_find_addr(ipvlan, ip6_addr, true);
+ addr = ipvlan_find_addr(ipvlan, iaddr, is_v6);
if (!addr)
return;
@@ -717,6 +747,23 @@ static void ipvlan_del_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr)
return;
}
+static int ipvlan_add_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr)
+{
+ if (ipvlan_addr_busy(ipvlan->port, ip6_addr, true)) {
+ netif_err(ipvlan, ifup, ipvlan->dev,
+ "Failed to add IPv6=%pI6c addr for %s intf\n",
+ ip6_addr, ipvlan->dev->name);
+ return -EINVAL;
+ }
+
+ return ipvlan_add_addr(ipvlan, ip6_addr, true);
+}
+
+static void ipvlan_del_addr6(struct ipvl_dev *ipvlan, struct in6_addr *ip6_addr)
+{
+ return ipvlan_del_addr(ipvlan, ip6_addr, true);
+}
+
static int ipvlan_addr6_event(struct notifier_block *unused,
unsigned long event, void *ptr)
{
@@ -750,45 +797,19 @@ static int ipvlan_addr6_event(struct notifier_block *unused,
static int ipvlan_add_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr)
{
- struct ipvl_addr *addr;
-
if (ipvlan_addr_busy(ipvlan->port, ip4_addr, false)) {
netif_err(ipvlan, ifup, ipvlan->dev,
"Failed to add IPv4=%pI4 on %s intf.\n",
ip4_addr, ipvlan->dev->name);
return -EINVAL;
}
- addr = kzalloc(sizeof(struct ipvl_addr), GFP_KERNEL);
- if (!addr)
- return -ENOMEM;
-
- addr->master = ipvlan;
- memcpy(&addr->ip4addr, ip4_addr, sizeof(struct in_addr));
- addr->atype = IPVL_IPV4;
- list_add_tail(&addr->anode, &ipvlan->addrs);
- /* 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);
-
- return 0;
+ return ipvlan_add_addr(ipvlan, ip4_addr, false);
}
static void ipvlan_del_addr4(struct ipvl_dev *ipvlan, struct in_addr *ip4_addr)
{
- struct ipvl_addr *addr;
-
- addr = ipvlan_find_addr(ipvlan, ip4_addr, false);
- if (!addr)
- return;
-
- ipvlan_ht_addr_del(addr);
- list_del(&addr->anode);
- kfree_rcu(addr, rcu);
-
- return;
+ return ipvlan_del_addr(ipvlan, ip4_addr, false);
}
static int ipvlan_addr4_event(struct notifier_block *unused,
diff --git a/drivers/net/ipvlan/ipvtap.c b/drivers/net/ipvlan/ipvtap.c
new file mode 100644
index 000000000000..2b713b63b62c
--- /dev/null
+++ b/drivers/net/ipvlan/ipvtap.c
@@ -0,0 +1,241 @@
+#include <linux/etherdevice.h>
+#include "ipvlan.h"
+#include <linux/if_vlan.h>
+#include <linux/if_tap.h>
+#include <linux/interrupt.h>
+#include <linux/nsproxy.h>
+#include <linux/compat.h>
+#include <linux/if_tun.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/cache.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/cdev.h>
+#include <linux/idr.h>
+#include <linux/fs.h>
+#include <linux/uio.h>
+
+#include <net/net_namespace.h>
+#include <net/rtnetlink.h>
+#include <net/sock.h>
+#include <linux/virtio_net.h>
+
+#define TUN_OFFLOADS (NETIF_F_HW_CSUM | NETIF_F_TSO_ECN | NETIF_F_TSO | \
+ NETIF_F_TSO6 | NETIF_F_UFO)
+
+static dev_t ipvtap_major;
+static struct cdev ipvtap_cdev;
+
+static const void *ipvtap_net_namespace(struct device *d)
+{
+ struct net_device *dev = to_net_dev(d->parent);
+ return dev_net(dev);
+}
+
+static struct class ipvtap_class = {
+ .name = "ipvtap",
+ .owner = THIS_MODULE,
+ .ns_type = &net_ns_type_operations,
+ .namespace = ipvtap_net_namespace,
+};
+
+struct ipvtap_dev {
+ struct ipvl_dev vlan;
+ struct tap_dev tap;
+};
+
+static void ipvtap_count_tx_dropped(struct tap_dev *tap)
+{
+ struct ipvtap_dev *vlantap = container_of(tap, struct ipvtap_dev, tap);
+ struct ipvl_dev *vlan = &vlantap->vlan;
+
+ this_cpu_inc(vlan->pcpu_stats->tx_drps);
+}
+
+static void ipvtap_count_rx_dropped(struct tap_dev *tap)
+{
+ struct ipvtap_dev *vlantap = container_of(tap, struct ipvtap_dev, tap);
+ struct ipvl_dev *vlan = &vlantap->vlan;
+
+ ipvlan_count_rx(vlan, 0, 0, 0);
+}
+
+static void ipvtap_update_features(struct tap_dev *tap,
+ netdev_features_t features)
+{
+ struct ipvtap_dev *vlantap = container_of(tap, struct ipvtap_dev, tap);
+ struct ipvl_dev *vlan = &vlantap->vlan;
+
+ vlan->sfeatures = features;
+ netdev_update_features(vlan->dev);
+}
+
+static int ipvtap_newlink(struct net *src_net,
+ struct net_device *dev,
+ struct nlattr *tb[],
+ struct nlattr *data[])
+{
+ struct ipvtap_dev *vlantap = netdev_priv(dev);
+ int err;
+
+ INIT_LIST_HEAD(&vlantap->tap.queue_list);
+
+ /* Since macvlan supports all offloads by default, make
+ * tap support all offloads also.
+ */
+ vlantap->tap.tap_features = TUN_OFFLOADS;
+ vlantap->tap.count_tx_dropped = ipvtap_count_tx_dropped;
+ vlantap->tap.update_features = ipvtap_update_features;
+ vlantap->tap.count_rx_dropped = ipvtap_count_rx_dropped;
+
+ err = netdev_rx_handler_register(dev, tap_handle_frame, &vlantap->tap);
+ if (err)
+ return err;
+
+ /* Don't put anything that may fail after macvlan_common_newlink
+ * because we can't undo what it does.
+ */
+ err = ipvlan_link_new(src_net, dev, tb, data);
+ if (err) {
+ netdev_rx_handler_unregister(dev);
+ return err;
+ }
+
+ vlantap->tap.dev = vlantap->vlan.dev;
+
+ return err;
+}
+
+static void ipvtap_dellink(struct net_device *dev,
+ struct list_head *head)
+{
+ struct ipvtap_dev *vlan = netdev_priv(dev);
+
+ netdev_rx_handler_unregister(dev);
+ tap_del_queues(&vlan->tap);
+ ipvlan_link_delete(dev, head);
+}
+
+static void ipvtap_setup(struct net_device *dev)
+{
+ ipvlan_link_setup(dev);
+ dev->tx_queue_len = TUN_READQ_SIZE;
+ dev->priv_flags &= ~IFF_NO_QUEUE;
+}
+
+static struct rtnl_link_ops ipvtap_link_ops __read_mostly = {
+ .kind = "ipvtap",
+ .setup = ipvtap_setup,
+ .newlink = ipvtap_newlink,
+ .dellink = ipvtap_dellink,
+ .priv_size = sizeof(struct ipvtap_dev),
+};
+
+static int ipvtap_device_event(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+ struct ipvtap_dev *vlantap;
+ struct device *classdev;
+ dev_t devt;
+ int err;
+ char tap_name[IFNAMSIZ];
+
+ if (dev->rtnl_link_ops != &ipvtap_link_ops)
+ return NOTIFY_DONE;
+
+ snprintf(tap_name, IFNAMSIZ, "tap%d", dev->ifindex);
+ vlantap = netdev_priv(dev);
+
+ switch (event) {
+ case NETDEV_REGISTER:
+ /* Create the device node here after the network device has
+ * been registered but before register_netdevice has
+ * finished running.
+ */
+ err = tap_get_minor(ipvtap_major, &vlantap->tap);
+ if (err)
+ return notifier_from_errno(err);
+
+ devt = MKDEV(MAJOR(ipvtap_major), vlantap->tap.minor);
+ classdev = device_create(&ipvtap_class, &dev->dev, devt,
+ dev, tap_name);
+ if (IS_ERR(classdev)) {
+ tap_free_minor(ipvtap_major, &vlantap->tap);
+ return notifier_from_errno(PTR_ERR(classdev));
+ }
+ err = sysfs_create_link(&dev->dev.kobj, &classdev->kobj,
+ tap_name);
+ if (err)
+ return notifier_from_errno(err);
+ break;
+ case NETDEV_UNREGISTER:
+ /* vlan->minor == 0 if NETDEV_REGISTER above failed */
+ if (vlantap->tap.minor == 0)
+ break;
+ sysfs_remove_link(&dev->dev.kobj, tap_name);
+ devt = MKDEV(MAJOR(ipvtap_major), vlantap->tap.minor);
+ device_destroy(&ipvtap_class, devt);
+ tap_free_minor(ipvtap_major, &vlantap->tap);
+ break;
+ case NETDEV_CHANGE_TX_QUEUE_LEN:
+ if (tap_queue_resize(&vlantap->tap))
+ return NOTIFY_BAD;
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block ipvtap_notifier_block __read_mostly = {
+ .notifier_call = ipvtap_device_event,
+};
+
+static int ipvtap_init(void)
+{
+ int err;
+
+ err = tap_create_cdev(&ipvtap_cdev, &ipvtap_major, "ipvtap");
+
+ if (err)
+ goto out1;
+
+ err = class_register(&ipvtap_class);
+ if (err)
+ goto out2;
+
+ err = register_netdevice_notifier(&ipvtap_notifier_block);
+ if (err)
+ goto out3;
+
+ err = ipvlan_link_register(&ipvtap_link_ops);
+ if (err)
+ goto out4;
+
+ return 0;
+
+out4:
+ unregister_netdevice_notifier(&ipvtap_notifier_block);
+out3:
+ class_unregister(&ipvtap_class);
+out2:
+ tap_destroy_cdev(ipvtap_major, &ipvtap_cdev);
+out1:
+ return err;
+}
+module_init(ipvtap_init);
+
+static void ipvtap_exit(void)
+{
+ rtnl_link_unregister(&ipvtap_link_ops);
+ unregister_netdevice_notifier(&ipvtap_notifier_block);
+ class_unregister(&ipvtap_class);
+ tap_destroy_cdev(ipvtap_major, &ipvtap_cdev);
+}
+module_exit(ipvtap_exit);
+MODULE_ALIAS_RTNL_LINK("ipvtap");
+MODULE_AUTHOR("Sainath Grandhi <sainath.grandhi@intel.com>");
+MODULE_LICENSE("GPL");