Blame view

net/ipv6/ip6_offload.c 6.39 KB
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  /*
   *	IPV6 GSO/GRO offload support
   *	Linux INET6 implementation
   *
   *	This program is free software; you can redistribute it and/or
   *      modify it under the terms of the GNU General Public License
   *      as published by the Free Software Foundation; either version
   *      2 of the License, or (at your option) any later version.
   */
  
  #include <linux/kernel.h>
  #include <linux/socket.h>
  #include <linux/netdevice.h>
  #include <linux/skbuff.h>
c6b641a4c   Vlad Yasevich   ipv6: Pull IPv6 G...
15
  #include <linux/printk.h>
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
  
  #include <net/protocol.h>
  #include <net/ipv6.h>
  
  #include "ip6_offload.h"
  
  static int ipv6_gso_pull_exthdrs(struct sk_buff *skb, int proto)
  {
  	const struct net_offload *ops = NULL;
  
  	for (;;) {
  		struct ipv6_opt_hdr *opth;
  		int len;
  
  		if (proto != NEXTHDR_HOP) {
  			ops = rcu_dereference(inet6_offloads[proto]);
  
  			if (unlikely(!ops))
  				break;
  
  			if (!(ops->flags & INET6_PROTO_GSO_EXTHDR))
  				break;
  		}
  
  		if (unlikely(!pskb_may_pull(skb, 8)))
  			break;
  
  		opth = (void *)skb->data;
  		len = ipv6_optlen(opth);
  
  		if (unlikely(!pskb_may_pull(skb, len)))
  			break;
  
  		proto = opth->nexthdr;
  		__skb_pull(skb, len);
  	}
  
  	return proto;
  }
  
  static int ipv6_gso_send_check(struct sk_buff *skb)
  {
  	const struct ipv6hdr *ipv6h;
  	const struct net_offload *ops;
  	int err = -EINVAL;
  
  	if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h))))
  		goto out;
  
  	ipv6h = ipv6_hdr(skb);
  	__skb_pull(skb, sizeof(*ipv6h));
  	err = -EPROTONOSUPPORT;
  
  	rcu_read_lock();
  	ops = rcu_dereference(inet6_offloads[
  		ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr)]);
f191a1d17   Vlad Yasevich   net: Remove code ...
72
  	if (likely(ops && ops->callbacks.gso_send_check)) {
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
73
  		skb_reset_transport_header(skb);
f191a1d17   Vlad Yasevich   net: Remove code ...
74
  		err = ops->callbacks.gso_send_check(skb);
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
  	}
  	rcu_read_unlock();
  
  out:
  	return err;
  }
  
  static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb,
  	netdev_features_t features)
  {
  	struct sk_buff *segs = ERR_PTR(-EINVAL);
  	struct ipv6hdr *ipv6h;
  	const struct net_offload *ops;
  	int proto;
  	struct frag_hdr *fptr;
  	unsigned int unfrag_ip6hlen;
  	u8 *prevhdr;
  	int offset = 0;
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
93
94
95
96
  	if (unlikely(skb_shinfo(skb)->gso_type &
  		     ~(SKB_GSO_UDP |
  		       SKB_GSO_DODGY |
  		       SKB_GSO_TCP_ECN |
68c331631   Pravin B Shelar   v4 GRE: Add TCP s...
97
  		       SKB_GSO_GRE |
731362674   Pravin B Shelar   tunneling: Add ge...
98
  		       SKB_GSO_UDP_TUNNEL |
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
99
100
101
102
103
104
105
106
107
108
109
110
111
112
  		       SKB_GSO_TCPV6 |
  		       0)))
  		goto out;
  
  	if (unlikely(!pskb_may_pull(skb, sizeof(*ipv6h))))
  		goto out;
  
  	ipv6h = ipv6_hdr(skb);
  	__skb_pull(skb, sizeof(*ipv6h));
  	segs = ERR_PTR(-EPROTONOSUPPORT);
  
  	proto = ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr);
  	rcu_read_lock();
  	ops = rcu_dereference(inet6_offloads[proto]);
f191a1d17   Vlad Yasevich   net: Remove code ...
113
  	if (likely(ops && ops->callbacks.gso_segment)) {
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
114
  		skb_reset_transport_header(skb);
f191a1d17   Vlad Yasevich   net: Remove code ...
115
  		segs = ops->callbacks.gso_segment(skb, features);
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
  	}
  	rcu_read_unlock();
  
  	if (IS_ERR(segs))
  		goto out;
  
  	for (skb = segs; skb; skb = skb->next) {
  		ipv6h = ipv6_hdr(skb);
  		ipv6h->payload_len = htons(skb->len - skb->mac_len -
  					   sizeof(*ipv6h));
  		if (proto == IPPROTO_UDP) {
  			unfrag_ip6hlen = ip6_find_1stfragopt(skb, &prevhdr);
  			fptr = (struct frag_hdr *)(skb_network_header(skb) +
  				unfrag_ip6hlen);
  			fptr->frag_off = htons(offset);
  			if (skb->next != NULL)
  				fptr->frag_off |= htons(IP6_MF);
  			offset += (ntohs(ipv6h->payload_len) -
  				   sizeof(struct frag_hdr));
  		}
  	}
  
  out:
  	return segs;
  }
  
  static struct sk_buff **ipv6_gro_receive(struct sk_buff **head,
  					 struct sk_buff *skb)
  {
  	const struct net_offload *ops;
  	struct sk_buff **pp = NULL;
  	struct sk_buff *p;
  	struct ipv6hdr *iph;
  	unsigned int nlen;
  	unsigned int hlen;
  	unsigned int off;
  	int flush = 1;
  	int proto;
  	__wsum csum;
  
  	off = skb_gro_offset(skb);
  	hlen = off + sizeof(*iph);
  	iph = skb_gro_header_fast(skb, off);
  	if (skb_gro_header_hard(skb, hlen)) {
  		iph = skb_gro_header_slow(skb, hlen, off);
  		if (unlikely(!iph))
  			goto out;
  	}
  
  	skb_gro_pull(skb, sizeof(*iph));
  	skb_set_transport_header(skb, skb_gro_offset(skb));
  
  	flush += ntohs(iph->payload_len) != skb_gro_len(skb);
  
  	rcu_read_lock();
  	proto = iph->nexthdr;
  	ops = rcu_dereference(inet6_offloads[proto]);
f191a1d17   Vlad Yasevich   net: Remove code ...
173
  	if (!ops || !ops->callbacks.gro_receive) {
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
174
175
176
177
178
179
180
  		__pskb_pull(skb, skb_gro_offset(skb));
  		proto = ipv6_gso_pull_exthdrs(skb, proto);
  		skb_gro_pull(skb, -skb_transport_offset(skb));
  		skb_reset_transport_header(skb);
  		__skb_push(skb, skb_gro_offset(skb));
  
  		ops = rcu_dereference(inet6_offloads[proto]);
f191a1d17   Vlad Yasevich   net: Remove code ...
181
  		if (!ops || !ops->callbacks.gro_receive)
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
  			goto out_unlock;
  
  		iph = ipv6_hdr(skb);
  	}
  
  	NAPI_GRO_CB(skb)->proto = proto;
  
  	flush--;
  	nlen = skb_network_header_len(skb);
  
  	for (p = *head; p; p = p->next) {
  		const struct ipv6hdr *iph2;
  		__be32 first_word; /* <Version:4><Traffic_Class:8><Flow_Label:20> */
  
  		if (!NAPI_GRO_CB(p)->same_flow)
  			continue;
  
  		iph2 = ipv6_hdr(p);
  		first_word = *(__be32 *)iph ^ *(__be32 *)iph2 ;
  
  		/* All fields must match except length and Traffic Class. */
  		if (nlen != skb_network_header_len(p) ||
  		    (first_word & htonl(0xF00FFFFF)) ||
  		    memcmp(&iph->nexthdr, &iph2->nexthdr,
  			   nlen - offsetof(struct ipv6hdr, nexthdr))) {
  			NAPI_GRO_CB(p)->same_flow = 0;
  			continue;
  		}
  		/* flush if Traffic Class fields are different */
  		NAPI_GRO_CB(p)->flush |= !!(first_word & htonl(0x0FF00000));
  		NAPI_GRO_CB(p)->flush |= flush;
  	}
  
  	NAPI_GRO_CB(skb)->flush |= flush;
  
  	csum = skb->csum;
  	skb_postpull_rcsum(skb, iph, skb_network_header_len(skb));
f191a1d17   Vlad Yasevich   net: Remove code ...
219
  	pp = ops->callbacks.gro_receive(head, skb);
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
  
  	skb->csum = csum;
  
  out_unlock:
  	rcu_read_unlock();
  
  out:
  	NAPI_GRO_CB(skb)->flush |= flush;
  
  	return pp;
  }
  
  static int ipv6_gro_complete(struct sk_buff *skb)
  {
  	const struct net_offload *ops;
  	struct ipv6hdr *iph = ipv6_hdr(skb);
  	int err = -ENOSYS;
  
  	iph->payload_len = htons(skb->len - skb_network_offset(skb) -
  				 sizeof(*iph));
  
  	rcu_read_lock();
  	ops = rcu_dereference(inet6_offloads[NAPI_GRO_CB(skb)->proto]);
f191a1d17   Vlad Yasevich   net: Remove code ...
243
  	if (WARN_ON(!ops || !ops->callbacks.gro_complete))
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
244
  		goto out_unlock;
f191a1d17   Vlad Yasevich   net: Remove code ...
245
  	err = ops->callbacks.gro_complete(skb);
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
246
247
248
249
250
251
252
253
254
  
  out_unlock:
  	rcu_read_unlock();
  
  	return err;
  }
  
  static struct packet_offload ipv6_packet_offload __read_mostly = {
  	.type = cpu_to_be16(ETH_P_IPV6),
f191a1d17   Vlad Yasevich   net: Remove code ...
255
256
257
258
259
260
  	.callbacks = {
  		.gso_send_check = ipv6_gso_send_check,
  		.gso_segment = ipv6_gso_segment,
  		.gro_receive = ipv6_gro_receive,
  		.gro_complete = ipv6_gro_complete,
  	},
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
261
  };
c6b641a4c   Vlad Yasevich   ipv6: Pull IPv6 G...
262
  static int __init ipv6_offload_init(void)
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
263
  {
c6b641a4c   Vlad Yasevich   ipv6: Pull IPv6 G...
264
265
266
267
268
269
270
271
272
273
  
  	if (tcpv6_offload_init() < 0)
  		pr_crit("%s: Cannot add TCP protocol offload
  ", __func__);
  	if (udp_offload_init() < 0)
  		pr_crit("%s: Cannot add UDP protocol offload
  ", __func__);
  	if (ipv6_exthdrs_offload_init() < 0)
  		pr_crit("%s: Cannot add EXTHDRS protocol offload
  ", __func__);
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
274
  	dev_add_offload(&ipv6_packet_offload);
c6b641a4c   Vlad Yasevich   ipv6: Pull IPv6 G...
275
  	return 0;
d1da932ed   Vlad Yasevich   ipv6: Separate ip...
276
  }
c6b641a4c   Vlad Yasevich   ipv6: Pull IPv6 G...
277
  fs_initcall(ipv6_offload_init);