]>
Commit | Line | Data |
---|---|---|
75020a70 DF |
1 | /* |
2 | * QEMU network structures definitions and helper functions | |
3 | * | |
4 | * Copyright (c) 2012 Ravello Systems LTD (http://ravellosystems.com) | |
5 | * | |
6 | * Developed by Daynix Computing LTD (http://www.daynix.com) | |
7 | * | |
8 | * Authors: | |
9 | * Dmitry Fleytman <[email protected]> | |
10 | * Tamir Shomer <[email protected]> | |
11 | * Yan Vugenfirer <[email protected]> | |
12 | * | |
13 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
14 | * See the COPYING file in the top-level directory. | |
15 | * | |
16 | */ | |
17 | ||
2744d920 | 18 | #include "qemu/osdep.h" |
75020a70 DF |
19 | #include "net/eth.h" |
20 | #include "net/checksum.h" | |
21 | #include "qemu-common.h" | |
22 | #include "net/tap.h" | |
23 | ||
eb700029 DF |
24 | void eth_setup_vlan_headers_ex(struct eth_header *ehdr, uint16_t vlan_tag, |
25 | uint16_t vlan_ethtype, bool *is_new) | |
75020a70 DF |
26 | { |
27 | struct vlan_header *vhdr = PKT_GET_VLAN_HDR(ehdr); | |
28 | ||
29 | switch (be16_to_cpu(ehdr->h_proto)) { | |
30 | case ETH_P_VLAN: | |
31 | case ETH_P_DVLAN: | |
32 | /* vlan hdr exists */ | |
33 | *is_new = false; | |
34 | break; | |
35 | ||
36 | default: | |
37 | /* No VLAN header, put a new one */ | |
38 | vhdr->h_proto = ehdr->h_proto; | |
eb700029 | 39 | ehdr->h_proto = cpu_to_be16(vlan_ethtype); |
75020a70 DF |
40 | *is_new = true; |
41 | break; | |
42 | } | |
43 | vhdr->h_tci = cpu_to_be16(vlan_tag); | |
44 | } | |
45 | ||
46 | uint8_t | |
47 | eth_get_gso_type(uint16_t l3_proto, uint8_t *l3_hdr, uint8_t l4proto) | |
48 | { | |
49 | uint8_t ecn_state = 0; | |
50 | ||
51 | if (l3_proto == ETH_P_IP) { | |
52 | struct ip_header *iphdr = (struct ip_header *) l3_hdr; | |
53 | ||
54 | if (IP_HEADER_VERSION(iphdr) == IP_HEADER_VERSION_4) { | |
55 | if (IPTOS_ECN(iphdr->ip_tos) == IPTOS_ECN_CE) { | |
56 | ecn_state = VIRTIO_NET_HDR_GSO_ECN; | |
57 | } | |
58 | if (l4proto == IP_PROTO_TCP) { | |
59 | return VIRTIO_NET_HDR_GSO_TCPV4 | ecn_state; | |
60 | } else if (l4proto == IP_PROTO_UDP) { | |
61 | return VIRTIO_NET_HDR_GSO_UDP | ecn_state; | |
62 | } | |
63 | } | |
64 | } else if (l3_proto == ETH_P_IPV6) { | |
65 | struct ip6_header *ip6hdr = (struct ip6_header *) l3_hdr; | |
66 | ||
67 | if (IP6_ECN(ip6hdr->ip6_ecn_acc) == IP6_ECN_CE) { | |
68 | ecn_state = VIRTIO_NET_HDR_GSO_ECN; | |
69 | } | |
70 | ||
71 | if (l4proto == IP_PROTO_TCP) { | |
72 | return VIRTIO_NET_HDR_GSO_TCPV6 | ecn_state; | |
73 | } | |
74 | } | |
75 | ||
76 | /* Unsupported offload */ | |
dfc6f865 | 77 | g_assert_not_reached(); |
75020a70 DF |
78 | |
79 | return VIRTIO_NET_HDR_GSO_NONE | ecn_state; | |
80 | } | |
81 | ||
eb700029 DF |
82 | uint16_t |
83 | eth_get_l3_proto(const struct iovec *l2hdr_iov, int iovcnt, size_t l2hdr_len) | |
84 | { | |
85 | uint16_t proto; | |
86 | size_t copied; | |
87 | size_t size = iov_size(l2hdr_iov, iovcnt); | |
88 | size_t proto_offset = l2hdr_len - sizeof(proto); | |
89 | ||
90 | if (size < proto_offset) { | |
91 | return ETH_P_UNKNOWN; | |
92 | } | |
93 | ||
94 | copied = iov_to_buf(l2hdr_iov, iovcnt, proto_offset, | |
95 | &proto, sizeof(proto)); | |
96 | ||
97 | return (copied == sizeof(proto)) ? be16_to_cpu(proto) : ETH_P_UNKNOWN; | |
98 | } | |
99 | ||
100 | static bool | |
101 | _eth_copy_chunk(size_t input_size, | |
102 | const struct iovec *iov, int iovcnt, | |
103 | size_t offset, size_t length, | |
104 | void *buffer) | |
105 | { | |
106 | size_t copied; | |
107 | ||
108 | if (input_size < offset) { | |
109 | return false; | |
110 | } | |
111 | ||
112 | copied = iov_to_buf(iov, iovcnt, offset, buffer, length); | |
113 | ||
114 | if (copied < length) { | |
115 | return false; | |
116 | } | |
117 | ||
118 | return true; | |
119 | } | |
120 | ||
121 | static bool | |
122 | _eth_tcp_has_data(bool is_ip4, | |
123 | const struct ip_header *ip4_hdr, | |
124 | const struct ip6_header *ip6_hdr, | |
125 | size_t full_ip6hdr_len, | |
126 | const struct tcp_header *tcp) | |
127 | { | |
128 | uint32_t l4len; | |
129 | ||
130 | if (is_ip4) { | |
131 | l4len = be16_to_cpu(ip4_hdr->ip_len) - IP_HDR_GET_LEN(ip4_hdr); | |
132 | } else { | |
133 | size_t opts_len = full_ip6hdr_len - sizeof(struct ip6_header); | |
134 | l4len = be16_to_cpu(ip6_hdr->ip6_ctlun.ip6_un1.ip6_un1_plen) - opts_len; | |
135 | } | |
136 | ||
137 | return l4len > TCP_HEADER_DATA_OFFSET(tcp); | |
138 | } | |
139 | ||
140 | void eth_get_protocols(const struct iovec *iov, int iovcnt, | |
75020a70 | 141 | bool *isip4, bool *isip6, |
eb700029 DF |
142 | bool *isudp, bool *istcp, |
143 | size_t *l3hdr_off, | |
144 | size_t *l4hdr_off, | |
145 | size_t *l5hdr_off, | |
146 | eth_ip6_hdr_info *ip6hdr_info, | |
147 | eth_ip4_hdr_info *ip4hdr_info, | |
148 | eth_l4_hdr_info *l4hdr_info) | |
75020a70 DF |
149 | { |
150 | int proto; | |
eb700029 DF |
151 | bool fragment = false; |
152 | size_t l2hdr_len = eth_get_l2_hdr_length_iov(iov, iovcnt); | |
153 | size_t input_size = iov_size(iov, iovcnt); | |
154 | size_t copied; | |
155 | ||
75020a70 DF |
156 | *isip4 = *isip6 = *isudp = *istcp = false; |
157 | ||
eb700029 DF |
158 | proto = eth_get_l3_proto(iov, iovcnt, l2hdr_len); |
159 | ||
160 | *l3hdr_off = l2hdr_len; | |
161 | ||
75020a70 | 162 | if (proto == ETH_P_IP) { |
eb700029 | 163 | struct ip_header *iphdr = &ip4hdr_info->ip4_hdr; |
75020a70 | 164 | |
eb700029 DF |
165 | if (input_size < l2hdr_len) { |
166 | return; | |
167 | } | |
168 | ||
169 | copied = iov_to_buf(iov, iovcnt, l2hdr_len, iphdr, sizeof(*iphdr)); | |
75020a70 | 170 | |
eb700029 | 171 | *isip4 = true; |
75020a70 | 172 | |
eb700029 DF |
173 | if (copied < sizeof(*iphdr)) { |
174 | return; | |
175 | } | |
75020a70 DF |
176 | |
177 | if (IP_HEADER_VERSION(iphdr) == IP_HEADER_VERSION_4) { | |
178 | if (iphdr->ip_p == IP_PROTO_TCP) { | |
179 | *istcp = true; | |
180 | } else if (iphdr->ip_p == IP_PROTO_UDP) { | |
181 | *isudp = true; | |
182 | } | |
183 | } | |
75020a70 | 184 | |
eb700029 DF |
185 | ip4hdr_info->fragment = IP4_IS_FRAGMENT(iphdr); |
186 | *l4hdr_off = l2hdr_len + IP_HDR_GET_LEN(iphdr); | |
187 | ||
188 | fragment = ip4hdr_info->fragment; | |
189 | } else if (proto == ETH_P_IPV6) { | |
75020a70 DF |
190 | |
191 | *isip6 = true; | |
eb700029 DF |
192 | if (eth_parse_ipv6_hdr(iov, iovcnt, l2hdr_len, |
193 | ip6hdr_info)) { | |
194 | if (ip6hdr_info->l4proto == IP_PROTO_TCP) { | |
75020a70 | 195 | *istcp = true; |
eb700029 | 196 | } else if (ip6hdr_info->l4proto == IP_PROTO_UDP) { |
75020a70 DF |
197 | *isudp = true; |
198 | } | |
eb700029 DF |
199 | } else { |
200 | return; | |
201 | } | |
202 | ||
203 | *l4hdr_off = l2hdr_len + ip6hdr_info->full_hdr_len; | |
204 | fragment = ip6hdr_info->fragment; | |
205 | } | |
206 | ||
207 | if (!fragment) { | |
208 | if (*istcp) { | |
209 | *istcp = _eth_copy_chunk(input_size, | |
210 | iov, iovcnt, | |
211 | *l4hdr_off, sizeof(l4hdr_info->hdr.tcp), | |
212 | &l4hdr_info->hdr.tcp); | |
213 | ||
2c5e564f | 214 | if (*istcp) { |
eb700029 DF |
215 | *l5hdr_off = *l4hdr_off + |
216 | TCP_HEADER_DATA_OFFSET(&l4hdr_info->hdr.tcp); | |
217 | ||
218 | l4hdr_info->has_tcp_data = | |
219 | _eth_tcp_has_data(proto == ETH_P_IP, | |
220 | &ip4hdr_info->ip4_hdr, | |
221 | &ip6hdr_info->ip6_hdr, | |
222 | *l4hdr_off - *l3hdr_off, | |
223 | &l4hdr_info->hdr.tcp); | |
224 | } | |
225 | } else if (*isudp) { | |
226 | *isudp = _eth_copy_chunk(input_size, | |
227 | iov, iovcnt, | |
228 | *l4hdr_off, sizeof(l4hdr_info->hdr.udp), | |
229 | &l4hdr_info->hdr.udp); | |
230 | *l5hdr_off = *l4hdr_off + sizeof(l4hdr_info->hdr.udp); | |
231 | } | |
232 | } | |
233 | } | |
234 | ||
235 | bool | |
236 | eth_strip_vlan(const struct iovec *iov, int iovcnt, size_t iovoff, | |
237 | uint8_t *new_ehdr_buf, | |
238 | uint16_t *payload_offset, uint16_t *tci) | |
239 | { | |
240 | struct vlan_header vlan_hdr; | |
241 | struct eth_header *new_ehdr = (struct eth_header *) new_ehdr_buf; | |
242 | ||
243 | size_t copied = iov_to_buf(iov, iovcnt, iovoff, | |
244 | new_ehdr, sizeof(*new_ehdr)); | |
245 | ||
246 | if (copied < sizeof(*new_ehdr)) { | |
247 | return false; | |
248 | } | |
249 | ||
250 | switch (be16_to_cpu(new_ehdr->h_proto)) { | |
251 | case ETH_P_VLAN: | |
252 | case ETH_P_DVLAN: | |
253 | copied = iov_to_buf(iov, iovcnt, iovoff + sizeof(*new_ehdr), | |
254 | &vlan_hdr, sizeof(vlan_hdr)); | |
255 | ||
256 | if (copied < sizeof(vlan_hdr)) { | |
257 | return false; | |
258 | } | |
259 | ||
260 | new_ehdr->h_proto = vlan_hdr.h_proto; | |
261 | ||
262 | *tci = be16_to_cpu(vlan_hdr.h_tci); | |
263 | *payload_offset = iovoff + sizeof(*new_ehdr) + sizeof(vlan_hdr); | |
264 | ||
265 | if (be16_to_cpu(new_ehdr->h_proto) == ETH_P_VLAN) { | |
266 | ||
267 | copied = iov_to_buf(iov, iovcnt, *payload_offset, | |
268 | PKT_GET_VLAN_HDR(new_ehdr), sizeof(vlan_hdr)); | |
269 | ||
270 | if (copied < sizeof(vlan_hdr)) { | |
271 | return false; | |
272 | } | |
273 | ||
274 | *payload_offset += sizeof(vlan_hdr); | |
275 | } | |
276 | return true; | |
277 | default: | |
278 | return false; | |
279 | } | |
280 | } | |
281 | ||
282 | bool | |
283 | eth_strip_vlan_ex(const struct iovec *iov, int iovcnt, size_t iovoff, | |
284 | uint16_t vet, uint8_t *new_ehdr_buf, | |
285 | uint16_t *payload_offset, uint16_t *tci) | |
286 | { | |
287 | struct vlan_header vlan_hdr; | |
288 | struct eth_header *new_ehdr = (struct eth_header *) new_ehdr_buf; | |
289 | ||
290 | size_t copied = iov_to_buf(iov, iovcnt, iovoff, | |
291 | new_ehdr, sizeof(*new_ehdr)); | |
292 | ||
293 | if (copied < sizeof(*new_ehdr)) { | |
294 | return false; | |
295 | } | |
296 | ||
297 | if (be16_to_cpu(new_ehdr->h_proto) == vet) { | |
298 | copied = iov_to_buf(iov, iovcnt, iovoff + sizeof(*new_ehdr), | |
299 | &vlan_hdr, sizeof(vlan_hdr)); | |
300 | ||
301 | if (copied < sizeof(vlan_hdr)) { | |
302 | return false; | |
75020a70 | 303 | } |
eb700029 DF |
304 | |
305 | new_ehdr->h_proto = vlan_hdr.h_proto; | |
306 | ||
307 | *tci = be16_to_cpu(vlan_hdr.h_tci); | |
308 | *payload_offset = iovoff + sizeof(*new_ehdr) + sizeof(vlan_hdr); | |
309 | return true; | |
75020a70 | 310 | } |
eb700029 DF |
311 | |
312 | return false; | |
75020a70 DF |
313 | } |
314 | ||
315 | void | |
316 | eth_setup_ip4_fragmentation(const void *l2hdr, size_t l2hdr_len, | |
317 | void *l3hdr, size_t l3hdr_len, | |
318 | size_t l3payload_len, | |
319 | size_t frag_offset, bool more_frags) | |
320 | { | |
eb700029 DF |
321 | const struct iovec l2vec = { |
322 | .iov_base = (void *) l2hdr, | |
323 | .iov_len = l2hdr_len | |
324 | }; | |
325 | ||
326 | if (eth_get_l3_proto(&l2vec, 1, l2hdr_len) == ETH_P_IP) { | |
75020a70 DF |
327 | uint16_t orig_flags; |
328 | struct ip_header *iphdr = (struct ip_header *) l3hdr; | |
329 | uint16_t frag_off_units = frag_offset / IP_FRAG_UNIT_SIZE; | |
330 | uint16_t new_ip_off; | |
331 | ||
332 | assert(frag_offset % IP_FRAG_UNIT_SIZE == 0); | |
333 | assert((frag_off_units & ~IP_OFFMASK) == 0); | |
334 | ||
335 | orig_flags = be16_to_cpu(iphdr->ip_off) & ~(IP_OFFMASK|IP_MF); | |
336 | new_ip_off = frag_off_units | orig_flags | (more_frags ? IP_MF : 0); | |
337 | iphdr->ip_off = cpu_to_be16(new_ip_off); | |
338 | iphdr->ip_len = cpu_to_be16(l3payload_len + l3hdr_len); | |
339 | } | |
340 | } | |
341 | ||
342 | void | |
343 | eth_fix_ip4_checksum(void *l3hdr, size_t l3hdr_len) | |
344 | { | |
345 | struct ip_header *iphdr = (struct ip_header *) l3hdr; | |
346 | iphdr->ip_sum = 0; | |
347 | iphdr->ip_sum = cpu_to_be16(net_raw_checksum(l3hdr, l3hdr_len)); | |
348 | } | |
349 | ||
350 | uint32_t | |
eb700029 DF |
351 | eth_calc_ip4_pseudo_hdr_csum(struct ip_header *iphdr, |
352 | uint16_t csl, | |
353 | uint32_t *cso) | |
75020a70 DF |
354 | { |
355 | struct ip_pseudo_header ipph; | |
356 | ipph.ip_src = iphdr->ip_src; | |
357 | ipph.ip_dst = iphdr->ip_dst; | |
358 | ipph.ip_payload = cpu_to_be16(csl); | |
359 | ipph.ip_proto = iphdr->ip_p; | |
360 | ipph.zeros = 0; | |
eb700029 DF |
361 | *cso = sizeof(ipph); |
362 | return net_checksum_add(*cso, (uint8_t *) &ipph); | |
363 | } | |
364 | ||
365 | uint32_t | |
366 | eth_calc_ip6_pseudo_hdr_csum(struct ip6_header *iphdr, | |
367 | uint16_t csl, | |
368 | uint8_t l4_proto, | |
369 | uint32_t *cso) | |
370 | { | |
371 | struct ip6_pseudo_header ipph; | |
372 | ipph.ip6_src = iphdr->ip6_src; | |
373 | ipph.ip6_dst = iphdr->ip6_dst; | |
374 | ipph.len = cpu_to_be16(csl); | |
375 | ipph.zero[0] = 0; | |
376 | ipph.zero[1] = 0; | |
377 | ipph.zero[2] = 0; | |
378 | ipph.next_hdr = l4_proto; | |
379 | *cso = sizeof(ipph); | |
380 | return net_checksum_add(*cso, (uint8_t *)&ipph); | |
75020a70 DF |
381 | } |
382 | ||
383 | static bool | |
384 | eth_is_ip6_extension_header_type(uint8_t hdr_type) | |
385 | { | |
386 | switch (hdr_type) { | |
387 | case IP6_HOP_BY_HOP: | |
388 | case IP6_ROUTING: | |
389 | case IP6_FRAGMENT: | |
390 | case IP6_ESP: | |
391 | case IP6_AUTHENTICATION: | |
392 | case IP6_DESTINATON: | |
393 | case IP6_MOBILITY: | |
394 | return true; | |
395 | default: | |
396 | return false; | |
397 | } | |
398 | } | |
399 | ||
eb700029 DF |
400 | static bool |
401 | _eth_get_rss_ex_dst_addr(const struct iovec *pkt, int pkt_frags, | |
402 | size_t rthdr_offset, | |
403 | struct ip6_ext_hdr *ext_hdr, | |
404 | struct in6_address *dst_addr) | |
405 | { | |
406 | struct ip6_ext_hdr_routing *rthdr = (struct ip6_ext_hdr_routing *) ext_hdr; | |
407 | ||
408 | if ((rthdr->rtype == 2) && | |
409 | (rthdr->len == sizeof(struct in6_address) / 8) && | |
410 | (rthdr->segleft == 1)) { | |
411 | ||
412 | size_t input_size = iov_size(pkt, pkt_frags); | |
413 | size_t bytes_read; | |
414 | ||
415 | if (input_size < rthdr_offset + sizeof(*ext_hdr)) { | |
416 | return false; | |
417 | } | |
418 | ||
419 | bytes_read = iov_to_buf(pkt, pkt_frags, | |
420 | rthdr_offset + sizeof(*ext_hdr), | |
4555ca68 | 421 | dst_addr, sizeof(*dst_addr)); |
eb700029 DF |
422 | |
423 | return bytes_read == sizeof(dst_addr); | |
424 | } | |
425 | ||
426 | return false; | |
427 | } | |
428 | ||
429 | static bool | |
430 | _eth_get_rss_ex_src_addr(const struct iovec *pkt, int pkt_frags, | |
431 | size_t dsthdr_offset, | |
432 | struct ip6_ext_hdr *ext_hdr, | |
433 | struct in6_address *src_addr) | |
434 | { | |
435 | size_t bytes_left = (ext_hdr->ip6r_len + 1) * 8 - sizeof(*ext_hdr); | |
436 | struct ip6_option_hdr opthdr; | |
437 | size_t opt_offset = dsthdr_offset + sizeof(*ext_hdr); | |
438 | ||
439 | while (bytes_left > sizeof(opthdr)) { | |
440 | size_t input_size = iov_size(pkt, pkt_frags); | |
441 | size_t bytes_read, optlen; | |
442 | ||
443 | if (input_size < opt_offset) { | |
444 | return false; | |
445 | } | |
446 | ||
447 | bytes_read = iov_to_buf(pkt, pkt_frags, opt_offset, | |
448 | &opthdr, sizeof(opthdr)); | |
449 | ||
450 | if (bytes_read != sizeof(opthdr)) { | |
451 | return false; | |
452 | } | |
453 | ||
454 | optlen = (opthdr.type == IP6_OPT_PAD1) ? 1 | |
455 | : (opthdr.len + sizeof(opthdr)); | |
456 | ||
457 | if (optlen > bytes_left) { | |
458 | return false; | |
459 | } | |
460 | ||
461 | if (opthdr.type == IP6_OPT_HOME) { | |
462 | size_t input_size = iov_size(pkt, pkt_frags); | |
463 | ||
464 | if (input_size < opt_offset + sizeof(opthdr)) { | |
465 | return false; | |
466 | } | |
467 | ||
468 | bytes_read = iov_to_buf(pkt, pkt_frags, | |
469 | opt_offset + sizeof(opthdr), | |
4555ca68 | 470 | src_addr, sizeof(*src_addr)); |
eb700029 DF |
471 | |
472 | return bytes_read == sizeof(src_addr); | |
473 | } | |
474 | ||
475 | opt_offset += optlen; | |
476 | bytes_left -= optlen; | |
477 | } | |
478 | ||
479 | return false; | |
480 | } | |
481 | ||
482 | bool eth_parse_ipv6_hdr(const struct iovec *pkt, int pkt_frags, | |
483 | size_t ip6hdr_off, eth_ip6_hdr_info *info) | |
75020a70 | 484 | { |
75020a70 DF |
485 | struct ip6_ext_hdr ext_hdr; |
486 | size_t bytes_read; | |
eb700029 DF |
487 | uint8_t curr_ext_hdr_type; |
488 | size_t input_size = iov_size(pkt, pkt_frags); | |
489 | ||
490 | info->rss_ex_dst_valid = false; | |
491 | info->rss_ex_src_valid = false; | |
492 | info->fragment = false; | |
493 | ||
494 | if (input_size < ip6hdr_off) { | |
495 | return false; | |
496 | } | |
75020a70 DF |
497 | |
498 | bytes_read = iov_to_buf(pkt, pkt_frags, ip6hdr_off, | |
eb700029 DF |
499 | &info->ip6_hdr, sizeof(info->ip6_hdr)); |
500 | if (bytes_read < sizeof(info->ip6_hdr)) { | |
75020a70 DF |
501 | return false; |
502 | } | |
503 | ||
eb700029 DF |
504 | info->full_hdr_len = sizeof(struct ip6_header); |
505 | ||
506 | curr_ext_hdr_type = info->ip6_hdr.ip6_nxt; | |
75020a70 | 507 | |
eb700029 DF |
508 | if (!eth_is_ip6_extension_header_type(curr_ext_hdr_type)) { |
509 | info->l4proto = info->ip6_hdr.ip6_nxt; | |
510 | info->has_ext_hdrs = false; | |
75020a70 DF |
511 | return true; |
512 | } | |
513 | ||
eb700029 DF |
514 | info->has_ext_hdrs = true; |
515 | ||
75020a70 | 516 | do { |
eb700029 DF |
517 | if (input_size < ip6hdr_off + info->full_hdr_len) { |
518 | return false; | |
519 | } | |
520 | ||
521 | bytes_read = iov_to_buf(pkt, pkt_frags, ip6hdr_off + info->full_hdr_len, | |
75020a70 | 522 | &ext_hdr, sizeof(ext_hdr)); |
75020a70 | 523 | |
eb700029 DF |
524 | if (bytes_read < sizeof(ext_hdr)) { |
525 | return false; | |
526 | } | |
527 | ||
528 | if (curr_ext_hdr_type == IP6_ROUTING) { | |
529 | info->rss_ex_dst_valid = | |
530 | _eth_get_rss_ex_dst_addr(pkt, pkt_frags, | |
531 | ip6hdr_off + info->full_hdr_len, | |
532 | &ext_hdr, &info->rss_ex_dst); | |
533 | } else if (curr_ext_hdr_type == IP6_DESTINATON) { | |
534 | info->rss_ex_src_valid = | |
535 | _eth_get_rss_ex_src_addr(pkt, pkt_frags, | |
536 | ip6hdr_off + info->full_hdr_len, | |
537 | &ext_hdr, &info->rss_ex_src); | |
538 | } else if (curr_ext_hdr_type == IP6_FRAGMENT) { | |
539 | info->fragment = true; | |
540 | } | |
541 | ||
542 | info->full_hdr_len += (ext_hdr.ip6r_len + 1) * IP6_EXT_GRANULARITY; | |
543 | curr_ext_hdr_type = ext_hdr.ip6r_nxt; | |
544 | } while (eth_is_ip6_extension_header_type(curr_ext_hdr_type)); | |
545 | ||
546 | info->l4proto = ext_hdr.ip6r_nxt; | |
75020a70 DF |
547 | return true; |
548 | } |