| Vlad Yasevich | 5edbb07 | 2012-11-15 08:49:18 +0000 | [diff] [blame] | 1 | /* | 
|  | 2 | *	IPV6 GSO/GRO offload support | 
|  | 3 | *	Linux INET6 implementation | 
|  | 4 | * | 
|  | 5 | *	This program is free software; you can redistribute it and/or | 
|  | 6 | *      modify it under the terms of the GNU General Public License | 
|  | 7 | *      as published by the Free Software Foundation; either version | 
|  | 8 | *      2 of the License, or (at your option) any later version. | 
|  | 9 | * | 
|  | 10 | *      UDPv6 GSO support | 
|  | 11 | */ | 
|  | 12 | #include <linux/skbuff.h> | 
|  | 13 | #include <net/protocol.h> | 
|  | 14 | #include <net/ipv6.h> | 
|  | 15 | #include <net/udp.h> | 
| Vlad Yasevich | d4d0d35 | 2012-11-15 16:35:37 +0000 | [diff] [blame] | 16 | #include <net/ip6_checksum.h> | 
| Vlad Yasevich | 5edbb07 | 2012-11-15 08:49:18 +0000 | [diff] [blame] | 17 | #include "ip6_offload.h" | 
|  | 18 |  | 
|  | 19 | static int udp6_ufo_send_check(struct sk_buff *skb) | 
|  | 20 | { | 
|  | 21 | const struct ipv6hdr *ipv6h; | 
|  | 22 | struct udphdr *uh; | 
|  | 23 |  | 
|  | 24 | if (!pskb_may_pull(skb, sizeof(*uh))) | 
|  | 25 | return -EINVAL; | 
|  | 26 |  | 
|  | 27 | ipv6h = ipv6_hdr(skb); | 
|  | 28 | uh = udp_hdr(skb); | 
|  | 29 |  | 
|  | 30 | uh->check = ~csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, skb->len, | 
|  | 31 | IPPROTO_UDP, 0); | 
|  | 32 | skb->csum_start = skb_transport_header(skb) - skb->head; | 
|  | 33 | skb->csum_offset = offsetof(struct udphdr, check); | 
|  | 34 | skb->ip_summed = CHECKSUM_PARTIAL; | 
|  | 35 | return 0; | 
|  | 36 | } | 
|  | 37 |  | 
|  | 38 | static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb, | 
|  | 39 | netdev_features_t features) | 
|  | 40 | { | 
|  | 41 | struct sk_buff *segs = ERR_PTR(-EINVAL); | 
|  | 42 | unsigned int mss; | 
|  | 43 | unsigned int unfrag_ip6hlen, unfrag_len; | 
|  | 44 | struct frag_hdr *fptr; | 
|  | 45 | u8 *mac_start, *prevhdr; | 
|  | 46 | u8 nexthdr; | 
|  | 47 | u8 frag_hdr_sz = sizeof(struct frag_hdr); | 
|  | 48 | int offset; | 
|  | 49 | __wsum csum; | 
|  | 50 |  | 
|  | 51 | mss = skb_shinfo(skb)->gso_size; | 
|  | 52 | if (unlikely(skb->len <= mss)) | 
|  | 53 | goto out; | 
|  | 54 |  | 
|  | 55 | if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) { | 
|  | 56 | /* Packet is from an untrusted source, reset gso_segs. */ | 
|  | 57 | int type = skb_shinfo(skb)->gso_type; | 
|  | 58 |  | 
|  | 59 | if (unlikely(type & ~(SKB_GSO_UDP | SKB_GSO_DODGY) || | 
|  | 60 | !(type & (SKB_GSO_UDP)))) | 
|  | 61 | goto out; | 
|  | 62 |  | 
|  | 63 | skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss); | 
|  | 64 |  | 
|  | 65 | segs = NULL; | 
|  | 66 | goto out; | 
|  | 67 | } | 
|  | 68 |  | 
|  | 69 | /* Do software UFO. Complete and fill in the UDP checksum as HW cannot | 
|  | 70 | * do checksum of UDP packets sent as multiple IP fragments. | 
|  | 71 | */ | 
|  | 72 | offset = skb_checksum_start_offset(skb); | 
|  | 73 | csum = skb_checksum(skb, offset, skb->len - offset, 0); | 
|  | 74 | offset += skb->csum_offset; | 
|  | 75 | *(__sum16 *)(skb->data + offset) = csum_fold(csum); | 
|  | 76 | skb->ip_summed = CHECKSUM_NONE; | 
|  | 77 |  | 
|  | 78 | /* Check if there is enough headroom to insert fragment header. */ | 
|  | 79 | if ((skb_mac_header(skb) < skb->head + frag_hdr_sz) && | 
|  | 80 | pskb_expand_head(skb, frag_hdr_sz, 0, GFP_ATOMIC)) | 
|  | 81 | goto out; | 
|  | 82 |  | 
|  | 83 | /* Find the unfragmentable header and shift it left by frag_hdr_sz | 
|  | 84 | * bytes to insert fragment header. | 
|  | 85 | */ | 
|  | 86 | unfrag_ip6hlen = ip6_find_1stfragopt(skb, &prevhdr); | 
|  | 87 | nexthdr = *prevhdr; | 
|  | 88 | *prevhdr = NEXTHDR_FRAGMENT; | 
|  | 89 | unfrag_len = skb_network_header(skb) - skb_mac_header(skb) + | 
|  | 90 | unfrag_ip6hlen; | 
|  | 91 | mac_start = skb_mac_header(skb); | 
|  | 92 | memmove(mac_start-frag_hdr_sz, mac_start, unfrag_len); | 
|  | 93 |  | 
|  | 94 | skb->mac_header -= frag_hdr_sz; | 
|  | 95 | skb->network_header -= frag_hdr_sz; | 
|  | 96 |  | 
|  | 97 | fptr = (struct frag_hdr *)(skb_network_header(skb) + unfrag_ip6hlen); | 
|  | 98 | fptr->nexthdr = nexthdr; | 
|  | 99 | fptr->reserved = 0; | 
|  | 100 | ipv6_select_ident(fptr, (struct rt6_info *)skb_dst(skb)); | 
|  | 101 |  | 
|  | 102 | /* Fragment the skb. ipv6 header and the remaining fields of the | 
|  | 103 | * fragment header are updated in ipv6_gso_segment() | 
|  | 104 | */ | 
|  | 105 | segs = skb_segment(skb, features); | 
|  | 106 |  | 
|  | 107 | out: | 
|  | 108 | return segs; | 
|  | 109 | } | 
|  | 110 | static const struct net_offload udpv6_offload = { | 
| Vlad Yasevich | f191a1d | 2012-11-15 08:49:23 +0000 | [diff] [blame] | 111 | .callbacks = { | 
|  | 112 | .gso_send_check =	udp6_ufo_send_check, | 
|  | 113 | .gso_segment	=	udp6_ufo_fragment, | 
|  | 114 | }, | 
| Vlad Yasevich | 5edbb07 | 2012-11-15 08:49:18 +0000 | [diff] [blame] | 115 | }; | 
|  | 116 |  | 
|  | 117 | int __init udp_offload_init(void) | 
|  | 118 | { | 
|  | 119 | return inet6_add_offload(&udpv6_offload, IPPROTO_UDP); | 
|  | 120 | } |