diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/tun.c | 150 |
1 files changed, 148 insertions, 2 deletions
diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 7ab94c825b57..aa4ee4439f04 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c | |||
@@ -63,6 +63,7 @@ | |||
63 | #include <linux/if_tun.h> | 63 | #include <linux/if_tun.h> |
64 | #include <linux/crc32.h> | 64 | #include <linux/crc32.h> |
65 | #include <linux/nsproxy.h> | 65 | #include <linux/nsproxy.h> |
66 | #include <linux/virtio_net.h> | ||
66 | #include <net/net_namespace.h> | 67 | #include <net/net_namespace.h> |
67 | #include <net/netns/generic.h> | 68 | #include <net/netns/generic.h> |
68 | 69 | ||
@@ -283,6 +284,7 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv, | |||
283 | struct tun_pi pi = { 0, __constant_htons(ETH_P_IP) }; | 284 | struct tun_pi pi = { 0, __constant_htons(ETH_P_IP) }; |
284 | struct sk_buff *skb; | 285 | struct sk_buff *skb; |
285 | size_t len = count, align = 0; | 286 | size_t len = count, align = 0; |
287 | struct virtio_net_hdr gso = { 0 }; | ||
286 | 288 | ||
287 | if (!(tun->flags & TUN_NO_PI)) { | 289 | if (!(tun->flags & TUN_NO_PI)) { |
288 | if ((len -= sizeof(pi)) > count) | 290 | if ((len -= sizeof(pi)) > count) |
@@ -292,6 +294,17 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv, | |||
292 | return -EFAULT; | 294 | return -EFAULT; |
293 | } | 295 | } |
294 | 296 | ||
297 | if (tun->flags & TUN_VNET_HDR) { | ||
298 | if ((len -= sizeof(gso)) > count) | ||
299 | return -EINVAL; | ||
300 | |||
301 | if (memcpy_fromiovec((void *)&gso, iv, sizeof(gso))) | ||
302 | return -EFAULT; | ||
303 | |||
304 | if (gso.hdr_len > len) | ||
305 | return -EINVAL; | ||
306 | } | ||
307 | |||
295 | if ((tun->flags & TUN_TYPE_MASK) == TUN_TAP_DEV) { | 308 | if ((tun->flags & TUN_TYPE_MASK) == TUN_TAP_DEV) { |
296 | align = NET_IP_ALIGN; | 309 | align = NET_IP_ALIGN; |
297 | if (unlikely(len < ETH_HLEN)) | 310 | if (unlikely(len < ETH_HLEN)) |
@@ -311,6 +324,16 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv, | |||
311 | return -EFAULT; | 324 | return -EFAULT; |
312 | } | 325 | } |
313 | 326 | ||
327 | if (gso.flags & VIRTIO_NET_HDR_F_NEEDS_CSUM) { | ||
328 | if (!skb_partial_csum_set(skb, gso.csum_start, | ||
329 | gso.csum_offset)) { | ||
330 | tun->dev->stats.rx_frame_errors++; | ||
331 | kfree_skb(skb); | ||
332 | return -EINVAL; | ||
333 | } | ||
334 | } else if (tun->flags & TUN_NOCHECKSUM) | ||
335 | skb->ip_summed = CHECKSUM_UNNECESSARY; | ||
336 | |||
314 | switch (tun->flags & TUN_TYPE_MASK) { | 337 | switch (tun->flags & TUN_TYPE_MASK) { |
315 | case TUN_TUN_DEV: | 338 | case TUN_TUN_DEV: |
316 | if (tun->flags & TUN_NO_PI) { | 339 | if (tun->flags & TUN_NO_PI) { |
@@ -337,8 +360,35 @@ static __inline__ ssize_t tun_get_user(struct tun_struct *tun, struct iovec *iv, | |||
337 | break; | 360 | break; |
338 | }; | 361 | }; |
339 | 362 | ||
340 | if (tun->flags & TUN_NOCHECKSUM) | 363 | if (gso.gso_type != VIRTIO_NET_HDR_GSO_NONE) { |
341 | skb->ip_summed = CHECKSUM_UNNECESSARY; | 364 | pr_debug("GSO!\n"); |
365 | switch (gso.gso_type & ~VIRTIO_NET_HDR_GSO_ECN) { | ||
366 | case VIRTIO_NET_HDR_GSO_TCPV4: | ||
367 | skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4; | ||
368 | break; | ||
369 | case VIRTIO_NET_HDR_GSO_TCPV6: | ||
370 | skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6; | ||
371 | break; | ||
372 | default: | ||
373 | tun->dev->stats.rx_frame_errors++; | ||
374 | kfree_skb(skb); | ||
375 | return -EINVAL; | ||
376 | } | ||
377 | |||
378 | if (gso.gso_type & VIRTIO_NET_HDR_GSO_ECN) | ||
379 | skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN; | ||
380 | |||
381 | skb_shinfo(skb)->gso_size = gso.gso_size; | ||
382 | if (skb_shinfo(skb)->gso_size == 0) { | ||
383 | tun->dev->stats.rx_frame_errors++; | ||
384 | kfree_skb(skb); | ||
385 | return -EINVAL; | ||
386 | } | ||
387 | |||
388 | /* Header must be checked, and gso_segs computed. */ | ||
389 | skb_shinfo(skb)->gso_type |= SKB_GSO_DODGY; | ||
390 | skb_shinfo(skb)->gso_segs = 0; | ||
391 | } | ||
342 | 392 | ||
343 | netif_rx_ni(skb); | 393 | netif_rx_ni(skb); |
344 | tun->dev->last_rx = jiffies; | 394 | tun->dev->last_rx = jiffies; |
@@ -384,6 +434,39 @@ static __inline__ ssize_t tun_put_user(struct tun_struct *tun, | |||
384 | total += sizeof(pi); | 434 | total += sizeof(pi); |
385 | } | 435 | } |
386 | 436 | ||
437 | if (tun->flags & TUN_VNET_HDR) { | ||
438 | struct virtio_net_hdr gso = { 0 }; /* no info leak */ | ||
439 | if ((len -= sizeof(gso)) < 0) | ||
440 | return -EINVAL; | ||
441 | |||
442 | if (skb_is_gso(skb)) { | ||
443 | struct skb_shared_info *sinfo = skb_shinfo(skb); | ||
444 | |||
445 | /* This is a hint as to how much should be linear. */ | ||
446 | gso.hdr_len = skb_headlen(skb); | ||
447 | gso.gso_size = sinfo->gso_size; | ||
448 | if (sinfo->gso_type & SKB_GSO_TCPV4) | ||
449 | gso.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; | ||
450 | else if (sinfo->gso_type & SKB_GSO_TCPV6) | ||
451 | gso.gso_type = VIRTIO_NET_HDR_GSO_TCPV6; | ||
452 | else | ||
453 | BUG(); | ||
454 | if (sinfo->gso_type & SKB_GSO_TCP_ECN) | ||
455 | gso.gso_type |= VIRTIO_NET_HDR_GSO_ECN; | ||
456 | } else | ||
457 | gso.gso_type = VIRTIO_NET_HDR_GSO_NONE; | ||
458 | |||
459 | if (skb->ip_summed == CHECKSUM_PARTIAL) { | ||
460 | gso.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM; | ||
461 | gso.csum_start = skb->csum_start - skb_headroom(skb); | ||
462 | gso.csum_offset = skb->csum_offset; | ||
463 | } /* else everything is zero */ | ||
464 | |||
465 | if (unlikely(memcpy_toiovec(iv, (void *)&gso, sizeof(gso)))) | ||
466 | return -EFAULT; | ||
467 | total += sizeof(gso); | ||
468 | } | ||
469 | |||
387 | len = min_t(int, skb->len, len); | 470 | len = min_t(int, skb->len, len); |
388 | 471 | ||
389 | skb_copy_datagram_iovec(skb, 0, iv, len); | 472 | skb_copy_datagram_iovec(skb, 0, iv, len); |
@@ -598,6 +681,11 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) | |||
598 | else | 681 | else |
599 | tun->flags &= ~TUN_ONE_QUEUE; | 682 | tun->flags &= ~TUN_ONE_QUEUE; |
600 | 683 | ||
684 | if (ifr->ifr_flags & IFF_VNET_HDR) | ||
685 | tun->flags |= TUN_VNET_HDR; | ||
686 | else | ||
687 | tun->flags &= ~TUN_VNET_HDR; | ||
688 | |||
601 | file->private_data = tun; | 689 | file->private_data = tun; |
602 | tun->attached = 1; | 690 | tun->attached = 1; |
603 | get_net(dev_net(tun->dev)); | 691 | get_net(dev_net(tun->dev)); |
@@ -611,6 +699,46 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr) | |||
611 | return err; | 699 | return err; |
612 | } | 700 | } |
613 | 701 | ||
702 | /* This is like a cut-down ethtool ops, except done via tun fd so no | ||
703 | * privs required. */ | ||
704 | static int set_offload(struct net_device *dev, unsigned long arg) | ||
705 | { | ||
706 | unsigned int old_features, features; | ||
707 | |||
708 | old_features = dev->features; | ||
709 | /* Unset features, set them as we chew on the arg. */ | ||
710 | features = (old_features & ~(NETIF_F_HW_CSUM|NETIF_F_SG|NETIF_F_FRAGLIST | ||
711 | |NETIF_F_TSO_ECN|NETIF_F_TSO|NETIF_F_TSO6)); | ||
712 | |||
713 | if (arg & TUN_F_CSUM) { | ||
714 | features |= NETIF_F_HW_CSUM|NETIF_F_SG|NETIF_F_FRAGLIST; | ||
715 | arg &= ~TUN_F_CSUM; | ||
716 | |||
717 | if (arg & (TUN_F_TSO4|TUN_F_TSO6)) { | ||
718 | if (arg & TUN_F_TSO_ECN) { | ||
719 | features |= NETIF_F_TSO_ECN; | ||
720 | arg &= ~TUN_F_TSO_ECN; | ||
721 | } | ||
722 | if (arg & TUN_F_TSO4) | ||
723 | features |= NETIF_F_TSO; | ||
724 | if (arg & TUN_F_TSO6) | ||
725 | features |= NETIF_F_TSO6; | ||
726 | arg &= ~(TUN_F_TSO4|TUN_F_TSO6); | ||
727 | } | ||
728 | } | ||
729 | |||
730 | /* This gives the user a way to test for new features in future by | ||
731 | * trying to set them. */ | ||
732 | if (arg) | ||
733 | return -EINVAL; | ||
734 | |||
735 | dev->features = features; | ||
736 | if (old_features != dev->features) | ||
737 | netdev_features_change(dev); | ||
738 | |||
739 | return 0; | ||
740 | } | ||
741 | |||
614 | static int tun_chr_ioctl(struct inode *inode, struct file *file, | 742 | static int tun_chr_ioctl(struct inode *inode, struct file *file, |
615 | unsigned int cmd, unsigned long arg) | 743 | unsigned int cmd, unsigned long arg) |
616 | { | 744 | { |
@@ -640,6 +768,15 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file, | |||
640 | return 0; | 768 | return 0; |
641 | } | 769 | } |
642 | 770 | ||
771 | if (cmd == TUNGETFEATURES) { | ||
772 | /* Currently this just means: "what IFF flags are valid?". | ||
773 | * This is needed because we never checked for invalid flags on | ||
774 | * TUNSETIFF. */ | ||
775 | return put_user(IFF_TUN | IFF_TAP | IFF_NO_PI | IFF_ONE_QUEUE | | ||
776 | IFF_VNET_HDR, | ||
777 | (unsigned int __user*)argp); | ||
778 | } | ||
779 | |||
643 | if (!tun) | 780 | if (!tun) |
644 | return -EBADFD; | 781 | return -EBADFD; |
645 | 782 | ||
@@ -707,6 +844,15 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file, | |||
707 | break; | 844 | break; |
708 | #endif | 845 | #endif |
709 | 846 | ||
847 | case TUNSETOFFLOAD: | ||
848 | { | ||
849 | int ret; | ||
850 | rtnl_lock(); | ||
851 | ret = set_offload(tun->dev, arg); | ||
852 | rtnl_unlock(); | ||
853 | return ret; | ||
854 | } | ||
855 | |||
710 | case SIOCGIFFLAGS: | 856 | case SIOCGIFFLAGS: |
711 | ifr.ifr_flags = tun->if_flags; | 857 | ifr.ifr_flags = tun->if_flags; |
712 | if (copy_to_user( argp, &ifr, sizeof ifr)) | 858 | if (copy_to_user( argp, &ifr, sizeof ifr)) |