2 * IPV6 GSO/GRO offload support
3 * Linux INET6 implementation
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.
11 #include <linux/kernel.h>
12 #include <linux/socket.h>
13 #include <linux/netdevice.h>
14 #include <linux/skbuff.h>
15 #include <linux/printk.h>
17 #include <net/protocol.h>
20 #include "ip6_offload.h"
22 static int ipv6_gso_pull_exthdrs(struct sk_buff
*skb
, int proto
)
24 const struct net_offload
*ops
= NULL
;
27 struct ipv6_opt_hdr
*opth
;
30 if (proto
!= NEXTHDR_HOP
) {
31 ops
= rcu_dereference(inet6_offloads
[proto
]);
36 if (!(ops
->flags
& INET6_PROTO_GSO_EXTHDR
))
40 if (unlikely(!pskb_may_pull(skb
, 8)))
43 opth
= (void *)skb
->data
;
44 len
= ipv6_optlen(opth
);
46 if (unlikely(!pskb_may_pull(skb
, len
)))
49 proto
= opth
->nexthdr
;
56 static int ipv6_gso_send_check(struct sk_buff
*skb
)
58 const struct ipv6hdr
*ipv6h
;
59 const struct net_offload
*ops
;
62 if (unlikely(!pskb_may_pull(skb
, sizeof(*ipv6h
))))
65 ipv6h
= ipv6_hdr(skb
);
66 __skb_pull(skb
, sizeof(*ipv6h
));
67 err
= -EPROTONOSUPPORT
;
70 ops
= rcu_dereference(inet6_offloads
[
71 ipv6_gso_pull_exthdrs(skb
, ipv6h
->nexthdr
)]);
73 if (likely(ops
&& ops
->callbacks
.gso_send_check
)) {
74 skb_reset_transport_header(skb
);
75 err
= ops
->callbacks
.gso_send_check(skb
);
83 static struct sk_buff
*ipv6_gso_segment(struct sk_buff
*skb
,
84 netdev_features_t features
)
86 struct sk_buff
*segs
= ERR_PTR(-EINVAL
);
87 struct ipv6hdr
*ipv6h
;
88 const struct net_offload
*ops
;
90 struct frag_hdr
*fptr
;
91 unsigned int unfrag_ip6hlen
;
96 if (unlikely(skb_shinfo(skb
)->gso_type
&
107 if (unlikely(!pskb_may_pull(skb
, sizeof(*ipv6h
))))
110 tunnel
= skb
->encapsulation
;
111 ipv6h
= ipv6_hdr(skb
);
112 __skb_pull(skb
, sizeof(*ipv6h
));
113 segs
= ERR_PTR(-EPROTONOSUPPORT
);
115 proto
= ipv6_gso_pull_exthdrs(skb
, ipv6h
->nexthdr
);
117 ops
= rcu_dereference(inet6_offloads
[proto
]);
118 if (likely(ops
&& ops
->callbacks
.gso_segment
)) {
119 skb_reset_transport_header(skb
);
120 segs
= ops
->callbacks
.gso_segment(skb
, features
);
127 for (skb
= segs
; skb
; skb
= skb
->next
) {
128 ipv6h
= ipv6_hdr(skb
);
129 ipv6h
->payload_len
= htons(skb
->len
- skb
->mac_len
-
131 if (!tunnel
&& proto
== IPPROTO_UDP
) {
132 unfrag_ip6hlen
= ip6_find_1stfragopt(skb
, &prevhdr
);
133 fptr
= (struct frag_hdr
*)(skb_network_header(skb
) +
135 fptr
->frag_off
= htons(offset
);
136 if (skb
->next
!= NULL
)
137 fptr
->frag_off
|= htons(IP6_MF
);
138 offset
+= (ntohs(ipv6h
->payload_len
) -
139 sizeof(struct frag_hdr
));
147 static struct sk_buff
**ipv6_gro_receive(struct sk_buff
**head
,
150 const struct net_offload
*ops
;
151 struct sk_buff
**pp
= NULL
;
161 off
= skb_gro_offset(skb
);
162 hlen
= off
+ sizeof(*iph
);
163 iph
= skb_gro_header_fast(skb
, off
);
164 if (skb_gro_header_hard(skb
, hlen
)) {
165 iph
= skb_gro_header_slow(skb
, hlen
, off
);
170 skb_gro_pull(skb
, sizeof(*iph
));
171 skb_set_transport_header(skb
, skb_gro_offset(skb
));
173 flush
+= ntohs(iph
->payload_len
) != skb_gro_len(skb
);
176 proto
= iph
->nexthdr
;
177 ops
= rcu_dereference(inet6_offloads
[proto
]);
178 if (!ops
|| !ops
->callbacks
.gro_receive
) {
179 __pskb_pull(skb
, skb_gro_offset(skb
));
180 proto
= ipv6_gso_pull_exthdrs(skb
, proto
);
181 skb_gro_pull(skb
, -skb_transport_offset(skb
));
182 skb_reset_transport_header(skb
);
183 __skb_push(skb
, skb_gro_offset(skb
));
185 ops
= rcu_dereference(inet6_offloads
[proto
]);
186 if (!ops
|| !ops
->callbacks
.gro_receive
)
192 NAPI_GRO_CB(skb
)->proto
= proto
;
195 nlen
= skb_network_header_len(skb
);
197 for (p
= *head
; p
; p
= p
->next
) {
198 const struct ipv6hdr
*iph2
;
199 __be32 first_word
; /* <Version:4><Traffic_Class:8><Flow_Label:20> */
201 if (!NAPI_GRO_CB(p
)->same_flow
)
205 first_word
= *(__be32
*)iph
^ *(__be32
*)iph2
;
207 /* All fields must match except length and Traffic Class. */
208 if (nlen
!= skb_network_header_len(p
) ||
209 (first_word
& htonl(0xF00FFFFF)) ||
210 memcmp(&iph
->nexthdr
, &iph2
->nexthdr
,
211 nlen
- offsetof(struct ipv6hdr
, nexthdr
))) {
212 NAPI_GRO_CB(p
)->same_flow
= 0;
215 /* flush if Traffic Class fields are different */
216 NAPI_GRO_CB(p
)->flush
|= !!(first_word
& htonl(0x0FF00000));
217 NAPI_GRO_CB(p
)->flush
|= flush
;
220 NAPI_GRO_CB(skb
)->flush
|= flush
;
223 skb_postpull_rcsum(skb
, iph
, skb_network_header_len(skb
));
225 pp
= ops
->callbacks
.gro_receive(head
, skb
);
233 NAPI_GRO_CB(skb
)->flush
|= flush
;
238 static int ipv6_gro_complete(struct sk_buff
*skb
)
240 const struct net_offload
*ops
;
241 struct ipv6hdr
*iph
= ipv6_hdr(skb
);
244 iph
->payload_len
= htons(skb
->len
- skb_network_offset(skb
) -
248 ops
= rcu_dereference(inet6_offloads
[NAPI_GRO_CB(skb
)->proto
]);
249 if (WARN_ON(!ops
|| !ops
->callbacks
.gro_complete
))
252 err
= ops
->callbacks
.gro_complete(skb
);
260 static struct packet_offload ipv6_packet_offload __read_mostly
= {
261 .type
= cpu_to_be16(ETH_P_IPV6
),
263 .gso_send_check
= ipv6_gso_send_check
,
264 .gso_segment
= ipv6_gso_segment
,
265 .gro_receive
= ipv6_gro_receive
,
266 .gro_complete
= ipv6_gro_complete
,
270 static int __init
ipv6_offload_init(void)
273 if (tcpv6_offload_init() < 0)
274 pr_crit("%s: Cannot add TCP protocol offload\n", __func__
);
275 if (udp_offload_init() < 0)
276 pr_crit("%s: Cannot add UDP protocol offload\n", __func__
);
277 if (ipv6_exthdrs_offload_init() < 0)
278 pr_crit("%s: Cannot add EXTHDRS protocol offload\n", __func__
);
280 dev_add_offload(&ipv6_packet_offload
);
284 fs_initcall(ipv6_offload_init
);