]> Pileus Git - ~andy/linux/blob - net/ipv6/udp_offload.c
3bb3a891a42416b23ddb278d3fd7c051d25cfcf7
[~andy/linux] / net / ipv6 / udp_offload.c
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>
16 #include <net/ip6_checksum.h>
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         /* UDP Tunnel offload on ipv6 is not yet supported. */
25         if (skb->encapsulation)
26                 return -EINVAL;
27
28         if (!pskb_may_pull(skb, sizeof(*uh)))
29                 return -EINVAL;
30
31         ipv6h = ipv6_hdr(skb);
32         uh = udp_hdr(skb);
33
34         uh->check = ~csum_ipv6_magic(&ipv6h->saddr, &ipv6h->daddr, skb->len,
35                                      IPPROTO_UDP, 0);
36         skb->csum_start = skb_transport_header(skb) - skb->head;
37         skb->csum_offset = offsetof(struct udphdr, check);
38         skb->ip_summed = CHECKSUM_PARTIAL;
39         return 0;
40 }
41
42 static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb,
43         netdev_features_t features)
44 {
45         struct sk_buff *segs = ERR_PTR(-EINVAL);
46         unsigned int mss;
47         unsigned int unfrag_ip6hlen, unfrag_len;
48         struct frag_hdr *fptr;
49         u8 *mac_start, *prevhdr;
50         u8 nexthdr;
51         u8 frag_hdr_sz = sizeof(struct frag_hdr);
52         int offset;
53         __wsum csum;
54
55         mss = skb_shinfo(skb)->gso_size;
56         if (unlikely(skb->len <= mss))
57                 goto out;
58
59         if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
60                 /* Packet is from an untrusted source, reset gso_segs. */
61                 int type = skb_shinfo(skb)->gso_type;
62
63                 if (unlikely(type & ~(SKB_GSO_UDP |
64                                       SKB_GSO_DODGY |
65                                       SKB_GSO_UDP_TUNNEL |
66                                       SKB_GSO_GRE) ||
67                              !(type & (SKB_GSO_UDP))))
68                         goto out;
69
70                 skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
71
72                 segs = NULL;
73                 goto out;
74         }
75
76         /* Do software UFO. Complete and fill in the UDP checksum as HW cannot
77          * do checksum of UDP packets sent as multiple IP fragments.
78          */
79         offset = skb_checksum_start_offset(skb);
80         csum = skb_checksum(skb, offset, skb->len - offset, 0);
81         offset += skb->csum_offset;
82         *(__sum16 *)(skb->data + offset) = csum_fold(csum);
83         skb->ip_summed = CHECKSUM_NONE;
84
85         /* Check if there is enough headroom to insert fragment header. */
86         if ((skb_mac_header(skb) < skb->head + frag_hdr_sz) &&
87             pskb_expand_head(skb, frag_hdr_sz, 0, GFP_ATOMIC))
88                 goto out;
89
90         /* Find the unfragmentable header and shift it left by frag_hdr_sz
91          * bytes to insert fragment header.
92          */
93         unfrag_ip6hlen = ip6_find_1stfragopt(skb, &prevhdr);
94         nexthdr = *prevhdr;
95         *prevhdr = NEXTHDR_FRAGMENT;
96         unfrag_len = skb_network_header(skb) - skb_mac_header(skb) +
97                      unfrag_ip6hlen;
98         mac_start = skb_mac_header(skb);
99         memmove(mac_start-frag_hdr_sz, mac_start, unfrag_len);
100
101         skb->mac_header -= frag_hdr_sz;
102         skb->network_header -= frag_hdr_sz;
103
104         fptr = (struct frag_hdr *)(skb_network_header(skb) + unfrag_ip6hlen);
105         fptr->nexthdr = nexthdr;
106         fptr->reserved = 0;
107         ipv6_select_ident(fptr, (struct rt6_info *)skb_dst(skb));
108
109         /* Fragment the skb. ipv6 header and the remaining fields of the
110          * fragment header are updated in ipv6_gso_segment()
111          */
112         segs = skb_segment(skb, features);
113
114 out:
115         return segs;
116 }
117 static const struct net_offload udpv6_offload = {
118         .callbacks = {
119                 .gso_send_check =       udp6_ufo_send_check,
120                 .gso_segment    =       udp6_ufo_fragment,
121         },
122 };
123
124 int __init udp_offload_init(void)
125 {
126         return inet6_add_offload(&udpv6_offload, IPPROTO_UDP);
127 }