diff options
Diffstat (limited to 'drivers/net/tun.c')
| -rw-r--r-- | drivers/net/tun.c | 105 |
1 files changed, 101 insertions, 4 deletions
diff --git a/drivers/net/tun.c b/drivers/net/tun.c index e6bbc639c2d0..6daea0c91862 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c | |||
| @@ -358,6 +358,66 @@ static unsigned int tun_chr_poll(struct file *file, poll_table * wait) | |||
| 358 | return mask; | 358 | return mask; |
| 359 | } | 359 | } |
| 360 | 360 | ||
| 361 | /* prepad is the amount to reserve at front. len is length after that. | ||
| 362 | * linear is a hint as to how much to copy (usually headers). */ | ||
| 363 | static struct sk_buff *tun_alloc_skb(size_t prepad, size_t len, size_t linear, | ||
| 364 | gfp_t gfp) | ||
| 365 | { | ||
| 366 | struct sk_buff *skb; | ||
| 367 | unsigned int i; | ||
| 368 | |||
| 369 | skb = alloc_skb(prepad + len, gfp|__GFP_NOWARN); | ||
| 370 | if (skb) { | ||
| 371 | skb_reserve(skb, prepad); | ||
| 372 | skb_put(skb, len); | ||
| 373 | return skb; | ||
| 374 | } | ||
| 375 | |||
| 376 | /* Under a page? Don't bother with paged skb. */ | ||
| 377 | if (prepad + len < PAGE_SIZE) | ||
| 378 | return NULL; | ||
| 379 | |||
| 380 | /* Start with a normal skb, and add pages. */ | ||
| 381 | skb = alloc_skb(prepad + linear, gfp); | ||
| 382 | if (!skb) | ||
| 383 | return NULL; | ||
| 384 | |||
| 385 | skb_reserve(skb, prepad); | ||
| 386 | skb_put(skb, linear); | ||
| 387 | |||
| 388 | len -= linear; | ||
| 389 | |||
| 390 | for (i = 0; i < MAX_SKB_FRAGS; i++) { | ||
| 391 | skb_frag_t *f = &skb_shinfo(skb)->frags[i]; | ||
| 392 | |||
| 393 | f->page = alloc_page(gfp|__GFP_ZERO); | ||
| 394 | if (!f->page) | ||
| 395 | break; | ||
| 396 | |||
| 397 | f->page_offset = 0; | ||
| 398 | f->size = PAGE_SIZE; | ||
| 399 | |||
| 400 | skb->data_len += PAGE_SIZE; | ||
| 401 | skb->len += PAGE_SIZE; | ||
| 402 | skb->truesize += PAGE_SIZE; | ||
| 403 | skb_shinfo(skb)->nr_frags++; | ||
| 404 | |||
| 405 | if (len < PAGE_SIZE) { | ||
| 406 | len = 0; | ||
| 407 | break; | ||
| 408 | } | ||
| 409 | len -= PAGE_SIZE; | ||
| 410 | } | ||
| 411 | |||
| 412 | /* Too large, or alloc fail? */ | ||
| 413 | if (unlikely(len)) { | ||
| 414 | kfree_skb(skb); | ||
| 415 | skb = NULL; | ||
| 416 | } | ||
| 417 | |||
| 418 | return skb; | ||
| 419 | } | ||
| 420 | |||
| 361 | /* Get packet from user space buffer */ | 421 | /* Get packet from user space buffer */ |
| 362 | static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv, size_t count) | 422 | static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv, size_t count) |
| 363 | { | 423 | { |
| @@ -391,14 +451,12 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv, | |||
| 391 | return -EINVAL; | 451 | return -EINVAL; |
| 392 | } | 452 | } |
| 393 | 453 | ||
| 394 | if (!(skb = alloc_skb(len + align, GFP_KERNEL))) { | 454 | if (!(skb = tun_alloc_skb(align, len, gso.hdr_len, GFP_KERNEL))) { |
| 395 | tun->dev->stats.rx_dropped++; | 455 | tun->dev->stats.rx_dropped++; |
| 396 | return -ENOMEM; | 456 | return -ENOMEM; |
| 397 | } | 457 | } |
| 398 | 458 | ||
| 399 | if (align) | 459 | if (skb_copy_datagram_from_iovec(skb, 0, iv, len)) { |
| 400 | skb_reserve(skb, align); | ||
| 401 | if (memcpy_fromiovec(skb_put(skb, len), iv, len)) { | ||
| 402 | tun->dev->stats.rx_dropped++; | 460 | tun->dev->stats.rx_dropped++; |
| 403 | kfree_skb(skb); | 461 | kfree_skb(skb); |
| 404 | return -EFAULT; | 462 | return -EFAULT; |
| @@ -748,6 +806,36 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) | |||
| 748 | return err; | 806 | return err; |
| 749 | } | 807 | } |
| 750 | 808 | ||
| 809 | static int tun_get_iff(struct net *net, struct file *file, struct ifreq *ifr) | ||
| 810 | { | ||
| 811 | struct tun_struct *tun = file->private_data; | ||
| 812 | |||
| 813 | if (!tun) | ||
| 814 | return -EBADFD; | ||
| 815 | |||
| 816 | DBG(KERN_INFO "%s: tun_get_iff\n", tun->dev->name); | ||
| 817 | |||
| 818 | strcpy(ifr->ifr_name, tun->dev->name); | ||
| 819 | |||
| 820 | ifr->ifr_flags = 0; | ||
| 821 | |||
| 822 | if (ifr->ifr_flags & TUN_TUN_DEV) | ||
| 823 | ifr->ifr_flags |= IFF_TUN; | ||
| 824 | else | ||
| 825 | ifr->ifr_flags |= IFF_TAP; | ||
| 826 | |||
| 827 | if (tun->flags & TUN_NO_PI) | ||
| 828 | ifr->ifr_flags |= IFF_NO_PI; | ||
| 829 | |||
| 830 | if (tun->flags & TUN_ONE_QUEUE) | ||
| 831 | ifr->ifr_flags |= IFF_ONE_QUEUE; | ||
| 832 | |||
| 833 | if (tun->flags & TUN_VNET_HDR) | ||
| 834 | ifr->ifr_flags |= IFF_VNET_HDR; | ||
| 835 | |||
| 836 | return 0; | ||
| 837 | } | ||
| 838 | |||
| 751 | /* This is like a cut-down ethtool ops, except done via tun fd so no | 839 | /* This is like a cut-down ethtool ops, except done via tun fd so no |
| 752 | * privs required. */ | 840 | * privs required. */ |
| 753 | static int set_offload(struct net_device *dev, unsigned long arg) | 841 | static int set_offload(struct net_device *dev, unsigned long arg) |
| @@ -833,6 +921,15 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file, | |||
| 833 | DBG(KERN_INFO "%s: tun_chr_ioctl cmd %d\n", tun->dev->name, cmd); | 921 | DBG(KERN_INFO "%s: tun_chr_ioctl cmd %d\n", tun->dev->name, cmd); |
| 834 | 922 | ||
| 835 | switch (cmd) { | 923 | switch (cmd) { |
| 924 | case TUNGETIFF: | ||
| 925 | ret = tun_get_iff(current->nsproxy->net_ns, file, &ifr); | ||
| 926 | if (ret) | ||
| 927 | return ret; | ||
| 928 | |||
| 929 | if (copy_to_user(argp, &ifr, sizeof(ifr))) | ||
| 930 | return -EFAULT; | ||
| 931 | break; | ||
| 932 | |||
| 836 | case TUNSETNOCSUM: | 933 | case TUNSETNOCSUM: |
| 837 | /* Disable/Enable checksum */ | 934 | /* Disable/Enable checksum */ |
| 838 | if (arg) | 935 | if (arg) |
