diff options
author | Rusty Russell <rusty@rustcorp.com.au> | 2008-07-03 06:48:02 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2008-07-03 06:48:02 -0400 |
commit | f43798c27684ab925adde7d8acc34c78c6e50df8 (patch) | |
tree | 21e952a07534dd382835246d02245d5e776c4d8b /drivers/net/tun.c | |
parent | 5228ddc98fa49b3cedab4024e269d62410a0d806 (diff) |
tun: Allow GSO using virtio_net_hdr
Add a IFF_VNET_HDR flag. This uses the same ABI as virtio_net
(ie. prepending struct virtio_net_hdr to packets) to indicate GSO and
checksum information.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Acked-by: Max Krasnyansky <maxk@qualcomm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/net/tun.c')
-rw-r--r-- | drivers/net/tun.c | 95 |
1 files changed, 92 insertions, 3 deletions
diff --git a/drivers/net/tun.c b/drivers/net/tun.c index a314955e6994..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)); |
@@ -684,7 +772,8 @@ static int tun_chr_ioctl(struct inode *inode, struct file *file, | |||
684 | /* Currently this just means: "what IFF flags are valid?". | 772 | /* Currently this just means: "what IFF flags are valid?". |
685 | * This is needed because we never checked for invalid flags on | 773 | * This is needed because we never checked for invalid flags on |
686 | * TUNSETIFF. */ | 774 | * TUNSETIFF. */ |
687 | return put_user(IFF_TUN | IFF_TAP | IFF_NO_PI | IFF_ONE_QUEUE, | 775 | return put_user(IFF_TUN | IFF_TAP | IFF_NO_PI | IFF_ONE_QUEUE | |
776 | IFF_VNET_HDR, | ||
688 | (unsigned int __user*)argp); | 777 | (unsigned int __user*)argp); |
689 | } | 778 | } |
690 | 779 | ||