summaryrefslogtreecommitdiffstats
path: root/drivers/net/tun.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/tun.c')
-rw-r--r--drivers/net/tun.c153
1 files changed, 131 insertions, 22 deletions
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index 7b816a03295..0ce07a339c7 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -62,18 +62,57 @@
#include <linux/if_ether.h>
#include <linux/if_tun.h>
#include <linux/crc32.h>
+#include <linux/nsproxy.h>
#include <net/net_namespace.h>
+#include <net/netns/generic.h>
#include <asm/system.h>
#include <asm/uaccess.h>
+/* Uncomment to enable debugging */
+/* #define TUN_DEBUG 1 */
+
#ifdef TUN_DEBUG
static int debug;
+
+#define DBG if(tun->debug)printk
+#define DBG1 if(debug==2)printk
+#else
+#define DBG( a... )
+#define DBG1( a... )
+#endif
+
+struct tun_struct {
+ struct list_head list;
+ unsigned long flags;
+ int attached;
+ uid_t owner;
+ gid_t group;
+
+ wait_queue_head_t read_wait;
+ struct sk_buff_head readq;
+
+ struct net_device *dev;
+
+ struct fasync_struct *fasync;
+
+ unsigned long if_flags;
+ u8 dev_addr[ETH_ALEN];
+ u32 chr_filter[2];
+ u32 net_filter[2];
+
+#ifdef TUN_DEBUG
+ int debug;
#endif
+};
/* Network device part of the driver */
-static LIST_HEAD(tun_dev_list);
+static unsigned int tun_net_id;
+struct tun_net {
+ struct list_head dev_list;
+};
+
static const struct ethtool_ops tun_ethtool_ops;
/* Net device open. */
@@ -253,8 +292,11 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv,
return -EFAULT;
}
- if ((tun->flags & TUN_TYPE_MASK) == TUN_TAP_DEV)
+ if ((tun->flags & TUN_TYPE_MASK) == TUN_TAP_DEV) {
align = NET_IP_ALIGN;
+ if (unlikely(len < ETH_HLEN))
+ return -EINVAL;
+ }
if (!(skb = alloc_skb(len + align, GFP_KERNEL))) {
tun->dev->stats.rx_dropped++;
@@ -435,14 +477,15 @@ static void tun_setup(struct net_device *dev)
dev->stop = tun_net_close;
dev->ethtool_ops = &tun_ethtool_ops;
dev->destructor = free_netdev;
+ dev->features |= NETIF_F_NETNS_LOCAL;
}
-static struct tun_struct *tun_get_by_name(const char *name)
+static struct tun_struct *tun_get_by_name(struct tun_net *tn, const char *name)
{
struct tun_struct *tun;
ASSERT_RTNL();
- list_for_each_entry(tun, &tun_dev_list, list) {
+ list_for_each_entry(tun, &tn->dev_list, list) {
if (!strncmp(tun->dev->name, name, IFNAMSIZ))
return tun;
}
@@ -450,13 +493,15 @@ static struct tun_struct *tun_get_by_name(const char *name)
return NULL;
}
-static int tun_set_iff(struct file *file, struct ifreq *ifr)
+static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
{
+ struct tun_net *tn;
struct tun_struct *tun;
struct net_device *dev;
int err;
- tun = tun_get_by_name(ifr->ifr_name);
+ tn = net_generic(net, tun_net_id);
+ tun = tun_get_by_name(tn, ifr->ifr_name);
if (tun) {
if (tun->attached)
return -EBUSY;
@@ -469,7 +514,7 @@ static int tun_set_iff(struct file *file, struct ifreq *ifr)
!capable(CAP_NET_ADMIN))
return -EPERM;
}
- else if (__dev_get_by_name(&init_net, ifr->ifr_name))
+ else if (__dev_get_by_name(net, ifr->ifr_name))
return -EINVAL;
else {
char *name;
@@ -500,6 +545,7 @@ static int tun_set_iff(struct file *file, struct ifreq *ifr)
if (!dev)
return -ENOMEM;
+ dev_net_set(dev, net);
tun = netdev_priv(dev);
tun->dev = dev;
tun->flags = flags;
@@ -522,7 +568,7 @@ static int tun_set_iff(struct file *file, struct ifreq *ifr)
if (err < 0)
goto err_free_dev;
- list_add(&tun->list, &tun_dev_list);
+ list_add(&tun->list, &tn->dev_list);
}
DBG(KERN_INFO "%s: tun_set_iff\n", tun->dev->name);
@@ -539,6 +585,7 @@ static int tun_set_iff(struct file *file, struct ifreq *ifr)
file->private_data = tun;
tun->attached = 1;
+ get_net(dev_net(tun->dev));
strcpy(ifr->ifr_name, tun->dev->name);
return 0;
@@ -567,7 +614,7 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,
ifr.ifr_name[IFNAMSIZ-1] = '\0';
rtnl_lock();
- err = tun_set_iff(file, &ifr);
+ err = tun_set_iff(current->nsproxy->net_ns, file, &ifr);
rtnl_unlock();
if (err)
@@ -621,16 +668,23 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,
break;
case TUNSETLINK:
+ {
+ int ret;
+
/* Only allow setting the type when the interface is down */
+ rtnl_lock();
if (tun->dev->flags & IFF_UP) {
DBG(KERN_INFO "%s: Linktype set failed because interface is up\n",
tun->dev->name);
- return -EBUSY;
+ ret = -EBUSY;
} else {
tun->dev->type = (int) arg;
DBG(KERN_INFO "%s: linktype set to %d\n", tun->dev->name, tun->dev->type);
+ ret = 0;
}
- break;
+ rtnl_unlock();
+ return ret;
+ }
#ifdef TUN_DEBUG
case TUNSETDEBUG:
@@ -687,7 +741,12 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,
case SIOCADDMULTI:
/** Add the specified group to the character device's multicast filter
* list. */
+ rtnl_lock();
+ netif_tx_lock_bh(tun->dev);
add_multi(tun->chr_filter, ifr.ifr_hwaddr.sa_data);
+ netif_tx_unlock_bh(tun->dev);
+ rtnl_unlock();
+
DBG(KERN_DEBUG "%s: add multi: %s\n",
tun->dev->name, print_mac(mac, ifr.ifr_hwaddr.sa_data));
return 0;
@@ -695,7 +754,12 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file,
case SIOCDELMULTI:
/** Remove the specified group from the character device's multicast
* filter list. */
+ rtnl_lock();
+ netif_tx_lock_bh(tun->dev);
del_multi(tun->chr_filter, ifr.ifr_hwaddr.sa_data);
+ netif_tx_unlock_bh(tun->dev);
+ rtnl_unlock();
+
DBG(KERN_DEBUG "%s: del multi: %s\n",
tun->dev->name, print_mac(mac, ifr.ifr_hwaddr.sa_data));
return 0;
@@ -754,6 +818,7 @@ static int tun_chr_close(struct inode *inode, struct file *file)
/* Detach from net device */
file->private_data = NULL;
tun->attached = 0;
+ put_net(dev_net(tun->dev));
/* Drop read queue */
skb_queue_purge(&tun->readq);
@@ -873,32 +938,76 @@ static const struct ethtool_ops tun_ethtool_ops = {
.set_rx_csum = tun_set_rx_csum
};
-static int __init tun_init(void)
+static int tun_init_net(struct net *net)
{
- int ret = 0;
+ struct tun_net *tn;
- printk(KERN_INFO "tun: %s, %s\n", DRV_DESCRIPTION, DRV_VERSION);
- printk(KERN_INFO "tun: %s\n", DRV_COPYRIGHT);
+ tn = kmalloc(sizeof(*tn), GFP_KERNEL);
+ if (tn == NULL)
+ return -ENOMEM;
- ret = misc_register(&tun_miscdev);
- if (ret)
- printk(KERN_ERR "tun: Can't register misc device %d\n", TUN_MINOR);
- return ret;
+ INIT_LIST_HEAD(&tn->dev_list);
+
+ if (net_assign_generic(net, tun_net_id, tn)) {
+ kfree(tn);
+ return -ENOMEM;
+ }
+
+ return 0;
}
-static void tun_cleanup(void)
+static void tun_exit_net(struct net *net)
{
+ struct tun_net *tn;
struct tun_struct *tun, *nxt;
- misc_deregister(&tun_miscdev);
+ tn = net_generic(net, tun_net_id);
rtnl_lock();
- list_for_each_entry_safe(tun, nxt, &tun_dev_list, list) {
+ list_for_each_entry_safe(tun, nxt, &tn->dev_list, list) {
DBG(KERN_INFO "%s cleaned up\n", tun->dev->name);
unregister_netdevice(tun->dev);
}
rtnl_unlock();
+ kfree(tn);
+}
+
+static struct pernet_operations tun_net_ops = {
+ .init = tun_init_net,
+ .exit = tun_exit_net,
+};
+
+static int __init tun_init(void)
+{
+ int ret = 0;
+
+ printk(KERN_INFO "tun: %s, %s\n", DRV_DESCRIPTION, DRV_VERSION);
+ printk(KERN_INFO "tun: %s\n", DRV_COPYRIGHT);
+
+ ret = register_pernet_gen_device(&tun_net_id, &tun_net_ops);
+ if (ret) {
+ printk(KERN_ERR "tun: Can't register pernet ops\n");
+ goto err_pernet;
+ }
+
+ ret = misc_register(&tun_miscdev);
+ if (ret) {
+ printk(KERN_ERR "tun: Can't register misc device %d\n", TUN_MINOR);
+ goto err_misc;
+ }
+ return 0;
+
+err_misc:
+ unregister_pernet_gen_device(tun_net_id, &tun_net_ops);
+err_pernet:
+ return ret;
+}
+
+static void tun_cleanup(void)
+{
+ misc_deregister(&tun_miscdev);
+ unregister_pernet_gen_device(tun_net_id, &tun_net_ops);
}
module_init(tun_init);