blob: dad35230b12c09e2547a8bedde637744758e6317 [file] [log] [blame]
Matus Fabiana774b532017-05-02 03:15:22 -07001/*
2 * Copyright (c) 2017 Cisco and/or its affiliates.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at:
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15/**
16 * @file
17 * @brief IPv4 to IPv6 translation
18 */
19#ifndef __included_ip4_to_ip6_h__
20#define __included_ip4_to_ip6_h__
21
22#include <vnet/ip/ip.h>
23
24
25/**
26 * IPv4 to IPv6 set call back function type
27 */
28typedef int (*ip4_to_ip6_set_fn_t) (ip4_header_t * ip4, ip6_header_t * ip6,
29 void *ctx);
30
31/* *INDENT-OFF* */
32static u8 icmp_to_icmp6_updater_pointer_table[] =
33 { 0, 1, 4, 4, ~0,
34 ~0, ~0, ~0, 7, 6,
35 ~0, ~0, 8, 8, 8,
36 8, 24, 24, 24, 24
37 };
38/* *INDENT-ON* */
39
40#define frag_id_4to6(id) (id)
41
42/**
43 * @brief Get TCP/UDP port number or ICMP id from IPv4 packet.
44 *
45 * @param ip4 IPv4 header.
46 * @param sender 1 get sender port, 0 get receiver port.
47 *
48 * @returns Port number on success, 0 otherwise.
49 */
50always_inline u16
51ip4_get_port (ip4_header_t * ip, u8 sender)
52{
53 if (ip->ip_version_and_header_length != 0x45 ||
54 ip4_get_fragment_offset (ip))
55 return 0;
56
57 if (PREDICT_TRUE ((ip->protocol == IP_PROTOCOL_TCP) ||
58 (ip->protocol == IP_PROTOCOL_UDP)))
59 {
60 udp_header_t *udp = (void *) (ip + 1);
61 return (sender) ? udp->src_port : udp->dst_port;
62 }
63 else if (ip->protocol == IP_PROTOCOL_ICMP)
64 {
65 icmp46_header_t *icmp = (void *) (ip + 1);
66 if (icmp->type == ICMP4_echo_request || icmp->type == ICMP4_echo_reply)
67 {
68 return *((u16 *) (icmp + 1));
69 }
70 else if (clib_net_to_host_u16 (ip->length) >= 64)
71 {
72 ip = (ip4_header_t *) (icmp + 2);
73 if (PREDICT_TRUE ((ip->protocol == IP_PROTOCOL_TCP) ||
74 (ip->protocol == IP_PROTOCOL_UDP)))
75 {
76 udp_header_t *udp = (void *) (ip + 1);
77 return (sender) ? udp->dst_port : udp->src_port;
78 }
79 else if (ip->protocol == IP_PROTOCOL_ICMP)
80 {
81 icmp46_header_t *icmp = (void *) (ip + 1);
82 if (icmp->type == ICMP4_echo_request ||
83 icmp->type == ICMP4_echo_reply)
84 {
85 return *((u16 *) (icmp + 1));
86 }
87 }
88 }
89 }
90 return 0;
91}
92
93/**
94 * @brief Convert type and code value from ICMP4 to ICMP6.
95 *
96 * @param icmp ICMP header.
97 * @param inner_ip4 Inner IPv4 header if present, 0 otherwise.
98 *
99 * @returns 0 on success, non-zero value otherwise.
100 */
101always_inline int
102icmp_to_icmp6_header (icmp46_header_t * icmp, ip4_header_t ** inner_ip4)
103{
104 *inner_ip4 = NULL;
105 switch (icmp->type)
106 {
107 case ICMP4_echo_reply:
108 icmp->type = ICMP6_echo_reply;
109 break;
110 case ICMP4_echo_request:
111 icmp->type = ICMP6_echo_request;
112 break;
113 case ICMP4_destination_unreachable:
114 *inner_ip4 = (ip4_header_t *) (((u8 *) icmp) + 8);
115
116 switch (icmp->code)
117 {
118 case ICMP4_destination_unreachable_destination_unreachable_net: //0
119 case ICMP4_destination_unreachable_destination_unreachable_host: //1
120 icmp->type = ICMP6_destination_unreachable;
121 icmp->code = ICMP6_destination_unreachable_no_route_to_destination;
122 break;
123 case ICMP4_destination_unreachable_protocol_unreachable: //2
124 icmp->type = ICMP6_parameter_problem;
125 icmp->code = ICMP6_parameter_problem_unrecognized_next_header;
126 break;
127 case ICMP4_destination_unreachable_port_unreachable: //3
128 icmp->type = ICMP6_destination_unreachable;
129 icmp->code = ICMP6_destination_unreachable_port_unreachable;
130 break;
131 case ICMP4_destination_unreachable_fragmentation_needed_and_dont_fragment_set: //4
132 icmp->type =
133 ICMP6_packet_too_big;
134 icmp->code = 0;
135 {
136 u32 advertised_mtu = clib_net_to_host_u32 (*((u32 *) (icmp + 1)));
137 if (advertised_mtu)
138 advertised_mtu += 20;
139 else
140 advertised_mtu = 1000; //FIXME ! (RFC 1191 - plateau value)
141
142 //FIXME: = minimum(advertised MTU+20, MTU_of_IPv6_nexthop, (MTU_of_IPv4_nexthop)+20)
143 *((u32 *) (icmp + 1)) = clib_host_to_net_u32 (advertised_mtu);
144 }
145 break;
146
147 case ICMP4_destination_unreachable_source_route_failed: //5
148 case ICMP4_destination_unreachable_destination_network_unknown: //6
149 case ICMP4_destination_unreachable_destination_host_unknown: //7
150 case ICMP4_destination_unreachable_source_host_isolated: //8
151 case ICMP4_destination_unreachable_network_unreachable_for_type_of_service: //11
152 case ICMP4_destination_unreachable_host_unreachable_for_type_of_service: //12
153 icmp->type =
154 ICMP6_destination_unreachable;
155 icmp->code = ICMP6_destination_unreachable_no_route_to_destination;
156 break;
157 case ICMP4_destination_unreachable_network_administratively_prohibited: //9
158 case ICMP4_destination_unreachable_host_administratively_prohibited: //10
159 case ICMP4_destination_unreachable_communication_administratively_prohibited: //13
160 case ICMP4_destination_unreachable_precedence_cutoff_in_effect: //15
161 icmp->type = ICMP6_destination_unreachable;
162 icmp->code =
163 ICMP6_destination_unreachable_destination_administratively_prohibited;
164 break;
165 case ICMP4_destination_unreachable_host_precedence_violation: //14
166 default:
167 return -1;
168 }
169 break;
170
171 case ICMP4_time_exceeded: //11
172 *inner_ip4 = (ip4_header_t *) (((u8 *) icmp) + 8);
173 icmp->type = ICMP6_time_exceeded;
174 break;
175
176 case ICMP4_parameter_problem:
177 *inner_ip4 = (ip4_header_t *) (((u8 *) icmp) + 8);
178
179 switch (icmp->code)
180 {
181 case ICMP4_parameter_problem_pointer_indicates_error:
182 case ICMP4_parameter_problem_bad_length:
183 icmp->type = ICMP6_parameter_problem;
184 icmp->code = ICMP6_parameter_problem_erroneous_header_field;
185 {
186 u8 ptr =
187 icmp_to_icmp6_updater_pointer_table[*((u8 *) (icmp + 1))];
188 if (ptr == 0xff)
189 return -1;
190
191 *((u32 *) (icmp + 1)) = clib_host_to_net_u32 (ptr);
192 }
193 break;
194 default:
195 //All other codes cause error
196 return -1;
197 }
198 break;
199
200 default:
201 //All other types cause error
202 return -1;
203 break;
204 }
205 return 0;
206}
207
208/**
209 * @brief Translate ICMP4 packet to ICMP6.
210 *
211 * @param p Buffer to translate.
212 * @param fn The function to translate outer header.
213 * @param ctx A context passed in the outer header translate function.
214 * @param inner_fn The function to translate inner header.
215 * @param inner_ctx A context passed in the inner header translate function.
216 *
217 * @returns 0 on success, non-zero value otherwise.
218 */
219always_inline int
220icmp_to_icmp6 (vlib_buffer_t * p, ip4_to_ip6_set_fn_t fn, void *ctx,
221 ip4_to_ip6_set_fn_t inner_fn, void *inner_ctx)
222{
223 ip4_header_t *ip4, *inner_ip4;
224 ip6_header_t *ip6, *inner_ip6;
225 u32 ip_len;
226 icmp46_header_t *icmp;
227 ip_csum_t csum;
228 ip6_frag_hdr_t *inner_frag;
229 u32 inner_frag_id;
230 u32 inner_frag_offset;
231 u8 inner_frag_more;
232 u16 *inner_L4_checksum = 0;
233 int rv;
234
235 ip4 = vlib_buffer_get_current (p);
236 ip_len = clib_net_to_host_u16 (ip4->length);
237 ASSERT (ip_len <= p->current_length);
238
239 icmp = (icmp46_header_t *) (ip4 + 1);
240 if (icmp_to_icmp6_header (icmp, &inner_ip4))
241 return -1;
242
243 if (inner_ip4)
244 {
245 //We have 2 headers to translate.
246 //We need to make some room in the middle of the packet
247 if (PREDICT_FALSE (ip4_is_fragment (inner_ip4)))
248 {
249 //Here it starts getting really tricky
250 //We will add a fragmentation header in the inner packet
251
252 if (!ip4_is_first_fragment (inner_ip4))
253 {
254 //For now we do not handle unless it is the first fragment
255 //Ideally we should handle the case as we are in slow path already
256 return -1;
257 }
258
259 vlib_buffer_advance (p,
260 -2 * (sizeof (*ip6) - sizeof (*ip4)) -
261 sizeof (*inner_frag));
262 ip6 = vlib_buffer_get_current (p);
263 clib_memcpy (u8_ptr_add (ip6, sizeof (*ip6) - sizeof (*ip4)), ip4,
264 20 + 8);
265 ip4 =
266 (ip4_header_t *) u8_ptr_add (ip6, sizeof (*ip6) - sizeof (*ip4));
267 icmp = (icmp46_header_t *) (ip4 + 1);
268
269 inner_ip6 =
270 (ip6_header_t *) u8_ptr_add (inner_ip4,
271 sizeof (*ip4) - sizeof (*ip6) -
272 sizeof (*inner_frag));
273 inner_frag =
274 (ip6_frag_hdr_t *) u8_ptr_add (inner_ip6, sizeof (*inner_ip6));
275 ip6->payload_length =
276 u16_net_add (ip4->length,
277 sizeof (*ip6) - 2 * sizeof (*ip4) +
278 sizeof (*inner_frag));
279 inner_frag_id = frag_id_4to6 (inner_ip4->fragment_id);
280 inner_frag_offset = ip4_get_fragment_offset (inner_ip4);
281 inner_frag_more =
282 ! !(inner_ip4->flags_and_fragment_offset &
283 clib_net_to_host_u16 (IP4_HEADER_FLAG_MORE_FRAGMENTS));
284 }
285 else
286 {
287 vlib_buffer_advance (p, -2 * (sizeof (*ip6) - sizeof (*ip4)));
288 ip6 = vlib_buffer_get_current (p);
289 clib_memcpy (u8_ptr_add (ip6, sizeof (*ip6) - sizeof (*ip4)), ip4,
290 20 + 8);
291 ip4 =
292 (ip4_header_t *) u8_ptr_add (ip6, sizeof (*ip6) - sizeof (*ip4));
293 icmp = (icmp46_header_t *) u8_ptr_add (ip4, sizeof (*ip4));
294 inner_ip6 =
295 (ip6_header_t *) u8_ptr_add (inner_ip4,
296 sizeof (*ip4) - sizeof (*ip6));
297 ip6->payload_length =
298 u16_net_add (ip4->length, sizeof (*ip6) - 2 * sizeof (*ip4));
299 inner_frag = NULL;
300 }
301
302 if (PREDICT_TRUE (inner_ip4->protocol == IP_PROTOCOL_TCP))
303 {
304 inner_L4_checksum = &((tcp_header_t *) (inner_ip4 + 1))->checksum;
305 *inner_L4_checksum =
306 ip_csum_fold (ip_csum_sub_even
307 (*inner_L4_checksum,
308 *((u64 *) (&inner_ip4->src_address))));
309 }
310 else if (PREDICT_TRUE (inner_ip4->protocol == IP_PROTOCOL_UDP))
311 {
312 inner_L4_checksum = &((udp_header_t *) (inner_ip4 + 1))->checksum;
Matus Fabian732036d2017-06-08 05:24:28 -0700313 if (*inner_L4_checksum)
314 *inner_L4_checksum =
315 ip_csum_fold (ip_csum_sub_even
316 (*inner_L4_checksum,
317 *((u64 *) (&inner_ip4->src_address))));
Matus Fabiana774b532017-05-02 03:15:22 -0700318 }
319 else if (inner_ip4->protocol == IP_PROTOCOL_ICMP)
320 {
321 //We have an ICMP inside an ICMP
322 //It needs to be translated, but not for error ICMP messages
323 icmp46_header_t *inner_icmp = (icmp46_header_t *) (inner_ip4 + 1);
324 csum = inner_icmp->checksum;
325 //Only types ICMP4_echo_request and ICMP4_echo_reply are handled by icmp_to_icmp6_header
326 csum = ip_csum_sub_even (csum, *((u16 *) inner_icmp));
327 inner_icmp->type = (inner_icmp->type == ICMP4_echo_request) ?
328 ICMP6_echo_request : ICMP6_echo_reply;
329 csum = ip_csum_add_even (csum, *((u16 *) inner_icmp));
330 csum =
331 ip_csum_add_even (csum, clib_host_to_net_u16 (IP_PROTOCOL_ICMP6));
332 csum =
333 ip_csum_add_even (csum, inner_ip4->length - sizeof (*inner_ip4));
334 inner_icmp->checksum = ip_csum_fold (csum);
335 inner_L4_checksum = &inner_icmp->checksum;
336 inner_ip4->protocol = IP_PROTOCOL_ICMP6;
337 }
338 else
339 {
340 /* To shut up Coverity */
341 os_panic ();
342 }
343
344 csum = *inner_L4_checksum; //Initial checksum of the inner L4 header
345
346 inner_ip6->ip_version_traffic_class_and_flow_label =
347 clib_host_to_net_u32 ((6 << 28) + (inner_ip4->tos << 20));
348 inner_ip6->payload_length =
349 u16_net_add (inner_ip4->length, -sizeof (*inner_ip4));
350 inner_ip6->hop_limit = inner_ip4->ttl;
351 inner_ip6->protocol = inner_ip4->protocol;
352
353 if ((rv = inner_fn (inner_ip4, inner_ip6, inner_ctx)) != 0)
354 return rv;
355
356 if (PREDICT_FALSE (inner_frag != NULL))
357 {
358 inner_frag->next_hdr = inner_ip6->protocol;
359 inner_frag->identification = inner_frag_id;
360 inner_frag->rsv = 0;
361 inner_frag->fragment_offset_and_more =
362 ip6_frag_hdr_offset_and_more (inner_frag_offset, inner_frag_more);
363 inner_ip6->protocol = IP_PROTOCOL_IPV6_FRAGMENTATION;
364 inner_ip6->payload_length =
365 clib_host_to_net_u16 (clib_net_to_host_u16
366 (inner_ip6->payload_length) +
367 sizeof (*inner_frag));
368 }
369
Matus Fabian89223f42017-06-12 02:29:39 -0700370 /* UDP checksum is optional */
371 if (csum)
372 {
373 csum = ip_csum_add_even (csum, inner_ip6->src_address.as_u64[0]);
374 csum = ip_csum_add_even (csum, inner_ip6->src_address.as_u64[1]);
375 csum = ip_csum_add_even (csum, inner_ip6->dst_address.as_u64[0]);
376 csum = ip_csum_add_even (csum, inner_ip6->dst_address.as_u64[1]);
377 *inner_L4_checksum = ip_csum_fold (csum);
378 }
Matus Fabiana774b532017-05-02 03:15:22 -0700379 }
380 else
381 {
382 vlib_buffer_advance (p, sizeof (*ip4) - sizeof (*ip6));
383 ip6 = vlib_buffer_get_current (p);
384 ip6->payload_length =
385 clib_host_to_net_u16 (clib_net_to_host_u16 (ip4->length) -
386 sizeof (*ip4));
387 }
388
389 //Translate outer IPv6
390 ip6->ip_version_traffic_class_and_flow_label =
391 clib_host_to_net_u32 ((6 << 28) + (ip4->tos << 20));
392
393 ip6->hop_limit = ip4->ttl;
394 ip6->protocol = IP_PROTOCOL_ICMP6;
395
396 if ((rv = fn (ip4, ip6, ctx)) != 0)
397 return rv;
398
399 //Truncate when the packet exceeds the minimal IPv6 MTU
400 if (p->current_length > 1280)
401 {
402 ip6->payload_length = clib_host_to_net_u16 (1280 - sizeof (*ip6));
403 p->current_length = 1280; //Looks too simple to be correct...
404 }
405
406 //Recompute ICMP checksum
407 icmp->checksum = 0;
408 csum = ip_csum_with_carry (0, ip6->payload_length);
409 csum = ip_csum_with_carry (csum, clib_host_to_net_u16 (ip6->protocol));
410 csum = ip_csum_with_carry (csum, ip6->src_address.as_u64[0]);
411 csum = ip_csum_with_carry (csum, ip6->src_address.as_u64[1]);
412 csum = ip_csum_with_carry (csum, ip6->dst_address.as_u64[0]);
413 csum = ip_csum_with_carry (csum, ip6->dst_address.as_u64[1]);
414 csum =
415 ip_incremental_checksum (csum, icmp,
416 clib_net_to_host_u16 (ip6->payload_length));
417 icmp->checksum = ~ip_csum_fold (csum);
418
419 return 0;
420}
421
422/**
423 * @brief Translate IPv4 fragmented packet to IPv6.
424 *
425 * @param p Buffer to translate.
426 * @param fn The function to translate header.
427 * @param ctx A context passed in the header translate function.
428 *
429 * @returns 0 on success, non-zero value otherwise.
430 */
431always_inline int
432ip4_to_ip6_fragmented (vlib_buffer_t * p, ip4_to_ip6_set_fn_t fn, void *ctx)
433{
434 ip4_header_t *ip4;
435 ip6_header_t *ip6;
436 ip6_frag_hdr_t *frag;
437 int rv;
438
439 ip4 = vlib_buffer_get_current (p);
440 frag = (ip6_frag_hdr_t *) u8_ptr_add (ip4, sizeof (*ip4) - sizeof (*frag));
441 ip6 =
442 (ip6_header_t *) u8_ptr_add (ip4,
443 sizeof (*ip4) - sizeof (*frag) -
444 sizeof (*ip6));
445 vlib_buffer_advance (p, sizeof (*ip4) - sizeof (*ip6) - sizeof (*frag));
446
447 //We know that the protocol was one of ICMP, TCP or UDP
448 //because the first fragment was found and cached
449 frag->next_hdr =
450 (ip4->protocol == IP_PROTOCOL_ICMP) ? IP_PROTOCOL_ICMP6 : ip4->protocol;
451 frag->identification = frag_id_4to6 (ip4->fragment_id);
452 frag->rsv = 0;
453 frag->fragment_offset_and_more =
454 ip6_frag_hdr_offset_and_more (ip4_get_fragment_offset (ip4),
455 clib_net_to_host_u16
456 (ip4->flags_and_fragment_offset) &
457 IP4_HEADER_FLAG_MORE_FRAGMENTS);
458
459 ip6->ip_version_traffic_class_and_flow_label =
460 clib_host_to_net_u32 ((6 << 28) + (ip4->tos << 20));
461 ip6->payload_length =
462 clib_host_to_net_u16 (clib_net_to_host_u16 (ip4->length) -
463 sizeof (*ip4) + sizeof (*frag));
464 ip6->hop_limit = ip4->ttl;
465 ip6->protocol = IP_PROTOCOL_IPV6_FRAGMENTATION;
466
467 if ((rv = fn (ip4, ip6, ctx)) != 0)
468 return rv;
469
470 return 0;
471}
472
473/**
474 * @brief Translate IPv4 UDP/TCP packet to IPv6.
475 *
476 * @param p Buffer to translate.
477 * @param fn The function to translate header.
478 * @param ctx A context passed in the header translate function.
479 *
480 * @returns 0 on success, non-zero value otherwise.
481 */
482always_inline int
483ip4_to_ip6_tcp_udp (vlib_buffer_t * p, ip4_to_ip6_set_fn_t fn, void *ctx)
484{
485 ip4_header_t *ip4;
486 ip6_header_t *ip6;
487 ip_csum_t csum;
488 u16 *checksum;
489 ip6_frag_hdr_t *frag;
490 u32 frag_id;
491 int rv;
492
493 ip4 = vlib_buffer_get_current (p);
494
495 if (ip4->protocol == IP_PROTOCOL_UDP)
496 {
497 udp_header_t *udp = ip4_next_header (ip4);
498 checksum = &udp->checksum;
499
500 //UDP checksum is optional over IPv4 but mandatory for IPv6
501 //We do not check udp->length sanity but use our safe computed value instead
502 if (PREDICT_FALSE (!checksum))
503 {
504 u16 udp_len = clib_host_to_net_u16 (ip4->length) - sizeof (*ip4);
505 csum = ip_incremental_checksum (0, udp, udp_len);
506 csum = ip_csum_with_carry (csum, clib_host_to_net_u16 (udp_len));
507 csum =
508 ip_csum_with_carry (csum, clib_host_to_net_u16 (IP_PROTOCOL_UDP));
509 csum = ip_csum_with_carry (csum, *((u64 *) (&ip4->src_address)));
510 *checksum = ~ip_csum_fold (csum);
511 }
512 }
513 else
514 {
515 tcp_header_t *tcp = ip4_next_header (ip4);
516 checksum = &tcp->checksum;
517 }
518
519 csum = ip_csum_sub_even (*checksum, ip4->src_address.as_u32);
520 csum = ip_csum_sub_even (csum, ip4->dst_address.as_u32);
521
522 // Deal with fragmented packets
523 if (PREDICT_FALSE (ip4->flags_and_fragment_offset &
524 clib_host_to_net_u16 (IP4_HEADER_FLAG_MORE_FRAGMENTS)))
525 {
526 ip6 =
527 (ip6_header_t *) u8_ptr_add (ip4,
528 sizeof (*ip4) - sizeof (*ip6) -
529 sizeof (*frag));
530 frag =
531 (ip6_frag_hdr_t *) u8_ptr_add (ip4, sizeof (*ip4) - sizeof (*frag));
532 frag_id = frag_id_4to6 (ip4->fragment_id);
533 vlib_buffer_advance (p, sizeof (*ip4) - sizeof (*ip6) - sizeof (*frag));
534 }
535 else
536 {
537 ip6 = (ip6_header_t *) (((u8 *) ip4) + sizeof (*ip4) - sizeof (*ip6));
538 vlib_buffer_advance (p, sizeof (*ip4) - sizeof (*ip6));
539 frag = NULL;
540 }
541
542 ip6->ip_version_traffic_class_and_flow_label =
543 clib_host_to_net_u32 ((6 << 28) + (ip4->tos << 20));
544 ip6->payload_length = u16_net_add (ip4->length, -sizeof (*ip4));
545 ip6->hop_limit = ip4->ttl;
546 ip6->protocol = ip4->protocol;
547
548 if (PREDICT_FALSE (frag != NULL))
549 {
550 frag->next_hdr = ip6->protocol;
551 frag->identification = frag_id;
552 frag->rsv = 0;
553 frag->fragment_offset_and_more = ip6_frag_hdr_offset_and_more (0, 1);
554 ip6->protocol = IP_PROTOCOL_IPV6_FRAGMENTATION;
555 ip6->payload_length = u16_net_add (ip6->payload_length, sizeof (*frag));
556 }
557
558 if ((rv = fn (ip4, ip6, ctx)) != 0)
559 return rv;
560
561 csum = ip_csum_add_even (csum, ip6->src_address.as_u64[0]);
562 csum = ip_csum_add_even (csum, ip6->src_address.as_u64[1]);
563 csum = ip_csum_add_even (csum, ip6->dst_address.as_u64[0]);
564 csum = ip_csum_add_even (csum, ip6->dst_address.as_u64[1]);
565 *checksum = ip_csum_fold (csum);
566
567 return 0;
568}
569
570#endif /* __included_ip4_to_ip6_h__ */
571
572/*
573 * fd.io coding-style-patch-verification: ON
574 *
575 * Local Variables:
576 * eval: (c-set-style "gnu")
577 * End:
578 */