Line data Source code
1 : /*
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 : */
28 : typedef int (*ip4_to_ip6_set_fn_t) (vlib_buffer_t * b, ip4_header_t * ip4,
29 : ip6_header_t * ip6, void *ctx);
30 :
31 : /* *INDENT-OFF* */
32 : static 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 : */
50 : always_inline u16
51 89785 : ip4_get_port (ip4_header_t * ip, u8 sender)
52 : {
53 179604 : if (ip->ip_version_and_header_length != 0x45 ||
54 89788 : ip4_get_fragment_offset (ip))
55 0 : return 0;
56 :
57 89816 : if (PREDICT_TRUE ((ip->protocol == IP_PROTOCOL_TCP) ||
58 : (ip->protocol == IP_PROTOCOL_UDP)))
59 : {
60 73415 : udp_header_t *udp = (void *) (ip + 1);
61 73415 : return (sender) ? udp->src_port : udp->dst_port;
62 : }
63 16401 : else if (ip->protocol == IP_PROTOCOL_ICMP)
64 : {
65 16375 : icmp46_header_t *icmp = (void *) (ip + 1);
66 16375 : if (icmp->type == ICMP4_echo_request || icmp->type == ICMP4_echo_reply)
67 : {
68 16328 : return *((u16 *) (icmp + 1));
69 : }
70 47 : else if (clib_net_to_host_u16 (ip->length) >= 64)
71 : {
72 35 : ip = (ip4_header_t *) (icmp + 2);
73 35 : if (PREDICT_TRUE ((ip->protocol == IP_PROTOCOL_TCP) ||
74 : (ip->protocol == IP_PROTOCOL_UDP)))
75 : {
76 31 : udp_header_t *udp = (void *) (ip + 1);
77 31 : return (sender) ? udp->dst_port : udp->src_port;
78 : }
79 4 : else if (ip->protocol == IP_PROTOCOL_ICMP)
80 : {
81 4 : icmp46_header_t *icmp = (void *) (ip + 1);
82 4 : if (icmp->type == ICMP4_echo_request ||
83 0 : icmp->type == ICMP4_echo_reply)
84 : {
85 4 : return *((u16 *) (icmp + 1));
86 : }
87 : }
88 : }
89 : }
90 38 : 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 : */
101 : always_inline int
102 15 : icmp_to_icmp6_header (icmp46_header_t * icmp, ip4_header_t ** inner_ip4)
103 : {
104 15 : *inner_ip4 = NULL;
105 15 : switch (icmp->type)
106 : {
107 8 : case ICMP4_echo_reply:
108 8 : icmp->type = ICMP6_echo_reply;
109 8 : break;
110 3 : case ICMP4_echo_request:
111 3 : icmp->type = ICMP6_echo_request;
112 3 : break;
113 3 : case ICMP4_destination_unreachable:
114 3 : *inner_ip4 = (ip4_header_t *) (((u8 *) icmp) + 8);
115 :
116 3 : switch (icmp->code)
117 : {
118 0 : case ICMP4_destination_unreachable_destination_unreachable_net: //0
119 : case ICMP4_destination_unreachable_destination_unreachable_host: //1
120 0 : icmp->type = ICMP6_destination_unreachable;
121 0 : icmp->code = ICMP6_destination_unreachable_no_route_to_destination;
122 0 : break;
123 0 : case ICMP4_destination_unreachable_protocol_unreachable: //2
124 0 : icmp->type = ICMP6_parameter_problem;
125 0 : icmp->code = ICMP6_parameter_problem_unrecognized_next_header;
126 0 : break;
127 0 : case ICMP4_destination_unreachable_port_unreachable: //3
128 0 : icmp->type = ICMP6_destination_unreachable;
129 0 : icmp->code = ICMP6_destination_unreachable_port_unreachable;
130 0 : break;
131 0 : case ICMP4_destination_unreachable_fragmentation_needed_and_dont_fragment_set: //4
132 0 : icmp->type =
133 : ICMP6_packet_too_big;
134 0 : icmp->code = 0;
135 : {
136 0 : u32 advertised_mtu = clib_net_to_host_u32 (*((u32 *) (icmp + 1)));
137 0 : if (advertised_mtu)
138 0 : advertised_mtu += 20;
139 : else
140 0 : 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 0 : *((u32 *) (icmp + 1)) = clib_host_to_net_u32 (advertised_mtu);
144 : }
145 0 : break;
146 :
147 0 : 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 0 : icmp->type =
154 : ICMP6_destination_unreachable;
155 0 : icmp->code = ICMP6_destination_unreachable_no_route_to_destination;
156 0 : break;
157 3 : 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 3 : icmp->type = ICMP6_destination_unreachable;
162 3 : icmp->code =
163 : ICMP6_destination_unreachable_destination_administratively_prohibited;
164 3 : break;
165 0 : case ICMP4_destination_unreachable_host_precedence_violation: //14
166 : default:
167 0 : return -1;
168 : }
169 3 : break;
170 :
171 1 : case ICMP4_time_exceeded: //11
172 1 : *inner_ip4 = (ip4_header_t *) (((u8 *) icmp) + 8);
173 1 : icmp->type = ICMP6_time_exceeded;
174 1 : break;
175 :
176 0 : case ICMP4_parameter_problem:
177 0 : *inner_ip4 = (ip4_header_t *) (((u8 *) icmp) + 8);
178 :
179 0 : switch (icmp->code)
180 : {
181 0 : case ICMP4_parameter_problem_pointer_indicates_error:
182 : case ICMP4_parameter_problem_bad_length:
183 0 : icmp->type = ICMP6_parameter_problem;
184 0 : icmp->code = ICMP6_parameter_problem_erroneous_header_field;
185 : {
186 0 : u8 ptr =
187 0 : icmp_to_icmp6_updater_pointer_table[*((u8 *) (icmp + 1))];
188 0 : if (ptr == 0xff)
189 0 : return -1;
190 :
191 0 : *((u32 *) (icmp + 1)) = clib_host_to_net_u32 (ptr);
192 : }
193 0 : break;
194 0 : default:
195 : //All other codes cause error
196 0 : return -1;
197 : }
198 0 : break;
199 :
200 0 : default:
201 : //All other types cause error
202 0 : return -1;
203 : break;
204 : }
205 15 : 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 : */
219 : always_inline int
220 15 : icmp_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 15 : ip6_frag_hdr_t *outer_frag= NULL;
230 : u32 inner_frag_id;
231 : u32 inner_frag_offset;
232 : u32 outer_frag_id;
233 : u8 inner_frag_more;
234 15 : u16 *inner_L4_checksum = 0;
235 : int rv;
236 :
237 15 : ip4 = vlib_buffer_get_current (p);
238 15 : ip_len = clib_net_to_host_u16 (ip4->length);
239 15 : ASSERT (ip_len <= p->current_length);
240 :
241 15 : icmp = (icmp46_header_t *) (ip4 + 1);
242 15 : if (icmp_to_icmp6_header (icmp, &inner_ip4))
243 0 : return -1;
244 :
245 15 : if (inner_ip4)
246 : {
247 : //We have 2 headers to translate.
248 : //We need to make some room in the middle of the packet
249 4 : if (PREDICT_FALSE (ip4_is_fragment (inner_ip4)))
250 : {
251 : //Here it starts getting really tricky
252 : //We will add a fragmentation header in the inner packet
253 :
254 0 : if (!ip4_is_first_fragment (inner_ip4))
255 : {
256 : //For now we do not handle unless it is the first fragment
257 : //Ideally we should handle the case as we are in slow path already
258 0 : return -1;
259 : }
260 :
261 0 : vlib_buffer_advance (p,
262 : -2 * (sizeof (*ip6) - sizeof (*ip4)) -
263 : sizeof (*inner_frag));
264 0 : ip6 = vlib_buffer_get_current (p);
265 0 : memmove (u8_ptr_add (ip6, sizeof (*ip6) - sizeof (*ip4)), ip4,
266 : 20 + 8);
267 0 : ip4 =
268 : (ip4_header_t *) u8_ptr_add (ip6, sizeof (*ip6) - sizeof (*ip4));
269 0 : icmp = (icmp46_header_t *) (ip4 + 1);
270 :
271 0 : inner_ip6 =
272 0 : (ip6_header_t *) u8_ptr_add (inner_ip4,
273 : sizeof (*ip4) - sizeof (*ip6) -
274 : sizeof (*inner_frag));
275 0 : inner_frag =
276 : (ip6_frag_hdr_t *) u8_ptr_add (inner_ip6, sizeof (*inner_ip6));
277 0 : ip6->payload_length =
278 0 : u16_net_add (ip4->length,
279 : sizeof (*ip6) - 2 * sizeof (*ip4) +
280 : sizeof (*inner_frag));
281 0 : inner_frag_id = frag_id_4to6 (inner_ip4->fragment_id);
282 0 : inner_frag_offset = ip4_get_fragment_offset (inner_ip4);
283 0 : inner_frag_more =
284 0 : ! !(inner_ip4->flags_and_fragment_offset &
285 0 : clib_net_to_host_u16 (IP4_HEADER_FLAG_MORE_FRAGMENTS));
286 : }
287 : else
288 : {
289 4 : vlib_buffer_advance (p, -2 * (sizeof (*ip6) - sizeof (*ip4)));
290 4 : ip6 = vlib_buffer_get_current (p);
291 4 : memmove (u8_ptr_add (ip6, sizeof (*ip6) - sizeof (*ip4)), ip4,
292 : 20 + 8);
293 4 : ip4 =
294 : (ip4_header_t *) u8_ptr_add (ip6, sizeof (*ip6) - sizeof (*ip4));
295 4 : icmp = (icmp46_header_t *) u8_ptr_add (ip4, sizeof (*ip4));
296 4 : inner_ip6 =
297 4 : (ip6_header_t *) u8_ptr_add (inner_ip4,
298 : sizeof (*ip4) - sizeof (*ip6));
299 4 : ip6->payload_length =
300 4 : u16_net_add (ip4->length, sizeof (*ip6) - 2 * sizeof (*ip4));
301 4 : inner_frag = NULL;
302 : }
303 :
304 4 : if (PREDICT_TRUE (inner_ip4->protocol == IP_PROTOCOL_TCP))
305 : {
306 1 : inner_L4_checksum = &((tcp_header_t *) (inner_ip4 + 1))->checksum;
307 1 : *inner_L4_checksum =
308 1 : ip_csum_fold (ip_csum_sub_even
309 1 : (*inner_L4_checksum,
310 1 : *((u64 *) (&inner_ip4->src_address))));
311 : }
312 3 : else if (PREDICT_TRUE (inner_ip4->protocol == IP_PROTOCOL_UDP))
313 : {
314 2 : inner_L4_checksum = &((udp_header_t *) (inner_ip4 + 1))->checksum;
315 2 : if (*inner_L4_checksum)
316 1 : *inner_L4_checksum =
317 1 : ip_csum_fold (ip_csum_sub_even
318 1 : (*inner_L4_checksum,
319 1 : *((u64 *) (&inner_ip4->src_address))));
320 : }
321 1 : else if (inner_ip4->protocol == IP_PROTOCOL_ICMP)
322 : {
323 : //We have an ICMP inside an ICMP
324 : //It needs to be translated, but not for error ICMP messages
325 1 : icmp46_header_t *inner_icmp = (icmp46_header_t *) (inner_ip4 + 1);
326 : //Only types ICMP4_echo_request and ICMP4_echo_reply are handled by icmp_to_icmp6_header
327 1 : inner_icmp->type = (inner_icmp->type == ICMP4_echo_request) ?
328 : ICMP6_echo_request : ICMP6_echo_reply;
329 1 : inner_L4_checksum = &inner_icmp->checksum;
330 1 : inner_ip4->protocol = IP_PROTOCOL_ICMP6;
331 : }
332 : else
333 : {
334 : /* To shut up Coverity */
335 0 : os_panic ();
336 : }
337 :
338 4 : inner_ip6->ip_version_traffic_class_and_flow_label =
339 4 : clib_host_to_net_u32 ((6 << 28) + (inner_ip4->tos << 20));
340 4 : inner_ip6->payload_length =
341 4 : u16_net_add (inner_ip4->length, -sizeof (*inner_ip4));
342 4 : inner_ip6->hop_limit = inner_ip4->ttl;
343 4 : inner_ip6->protocol = inner_ip4->protocol;
344 :
345 4 : if ((rv = inner_fn (p, inner_ip4, inner_ip6, inner_ctx)) != 0)
346 0 : return rv;
347 :
348 4 : if (PREDICT_FALSE (inner_frag != NULL))
349 : {
350 0 : inner_frag->next_hdr = inner_ip6->protocol;
351 0 : inner_frag->identification = inner_frag_id;
352 0 : inner_frag->rsv = 0;
353 0 : inner_frag->fragment_offset_and_more =
354 0 : ip6_frag_hdr_offset_and_more (inner_frag_offset, inner_frag_more);
355 0 : inner_ip6->protocol = IP_PROTOCOL_IPV6_FRAGMENTATION;
356 0 : inner_ip6->payload_length =
357 0 : clib_host_to_net_u16 (clib_net_to_host_u16
358 0 : (inner_ip6->payload_length) +
359 : sizeof (*inner_frag));
360 : }
361 :
362 4 : csum = *inner_L4_checksum;
363 4 : if (inner_ip6->protocol == IP_PROTOCOL_ICMP6)
364 : {
365 : //Recompute ICMP checksum
366 1 : icmp46_header_t *inner_icmp = (icmp46_header_t *) (inner_ip4 + 1);
367 :
368 1 : inner_icmp->checksum = 0;
369 1 : csum = ip_csum_with_carry (0, inner_ip6->payload_length);
370 : csum =
371 1 : ip_csum_with_carry (csum,
372 1 : clib_host_to_net_u16 (inner_ip6->protocol));
373 1 : csum = ip_csum_with_carry (csum, inner_ip6->src_address.as_u64[0]);
374 1 : csum = ip_csum_with_carry (csum, inner_ip6->src_address.as_u64[1]);
375 1 : csum = ip_csum_with_carry (csum, inner_ip6->dst_address.as_u64[0]);
376 1 : csum = ip_csum_with_carry (csum, inner_ip6->dst_address.as_u64[1]);
377 : csum =
378 1 : ip_incremental_checksum (csum, inner_icmp,
379 1 : clib_net_to_host_u16
380 1 : (inner_ip6->payload_length));
381 1 : inner_icmp->checksum = ~ip_csum_fold (csum);
382 : }
383 : else
384 : {
385 : /* UDP checksum is optional */
386 3 : if (csum)
387 : {
388 : csum =
389 2 : ip_csum_add_even (csum, inner_ip6->src_address.as_u64[0]);
390 : csum =
391 2 : ip_csum_add_even (csum, inner_ip6->src_address.as_u64[1]);
392 : csum =
393 2 : ip_csum_add_even (csum, inner_ip6->dst_address.as_u64[0]);
394 : csum =
395 2 : ip_csum_add_even (csum, inner_ip6->dst_address.as_u64[1]);
396 2 : *inner_L4_checksum = ip_csum_fold (csum);
397 : }
398 : }
399 : }
400 : else
401 : {
402 11 : if (PREDICT_FALSE (ip4->flags_and_fragment_offset &
403 : clib_host_to_net_u16 (IP4_HEADER_FLAG_MORE_FRAGMENTS)))
404 : {
405 1 : vlib_buffer_advance (p, sizeof (*ip4) - sizeof (*ip6) -
406 : sizeof (*outer_frag));
407 1 : ip6 = vlib_buffer_get_current (p);
408 1 : outer_frag = (ip6_frag_hdr_t *) (ip6 + 1);
409 1 : outer_frag_id = frag_id_4to6 (ip4->fragment_id);
410 : }
411 : else
412 : {
413 10 : vlib_buffer_advance (p, sizeof (*ip4) - sizeof (*ip6));
414 10 : ip6 = vlib_buffer_get_current (p);
415 : }
416 11 : ip6->payload_length =
417 11 : clib_host_to_net_u16 (clib_net_to_host_u16 (ip4->length) -
418 : sizeof (*ip4));
419 : }
420 :
421 : //Translate outer IPv6
422 15 : ip6->ip_version_traffic_class_and_flow_label =
423 15 : clib_host_to_net_u32 ((6 << 28) + (ip4->tos << 20));
424 :
425 15 : ip6->hop_limit = ip4->ttl;
426 15 : ip6->protocol = IP_PROTOCOL_ICMP6;
427 :
428 15 : if ((rv = fn (p, ip4, ip6, ctx)) != 0)
429 0 : return rv;
430 :
431 15 : if (PREDICT_FALSE (outer_frag != NULL))
432 : {
433 1 : outer_frag->next_hdr = ip6->protocol;
434 1 : outer_frag->identification = outer_frag_id;
435 1 : outer_frag->rsv = 0;
436 1 : outer_frag->fragment_offset_and_more = ip6_frag_hdr_offset_and_more (0, 1);
437 1 : ip6->protocol = IP_PROTOCOL_IPV6_FRAGMENTATION;
438 1 : ip6->payload_length = u16_net_add (ip6->payload_length,
439 : sizeof (*outer_frag));
440 : }
441 :
442 : //Truncate when ICMPv6 error message exceeds the minimal IPv6 MTU
443 15 : if (p->current_length > 1280 && icmp->type < 128)
444 : {
445 0 : ip6->payload_length = clib_host_to_net_u16 (1280 - sizeof (*ip6));
446 0 : p->current_length = 1280; //Looks too simple to be correct...
447 : }
448 :
449 : //Recompute ICMP checksum
450 15 : icmp->checksum = 0;
451 15 : csum = ip_csum_with_carry (0, ip6->payload_length);
452 15 : csum = ip_csum_with_carry (csum, clib_host_to_net_u16 (ip6->protocol));
453 15 : csum = ip_csum_with_carry (csum, ip6->src_address.as_u64[0]);
454 15 : csum = ip_csum_with_carry (csum, ip6->src_address.as_u64[1]);
455 15 : csum = ip_csum_with_carry (csum, ip6->dst_address.as_u64[0]);
456 15 : csum = ip_csum_with_carry (csum, ip6->dst_address.as_u64[1]);
457 : csum =
458 15 : ip_incremental_checksum (csum, icmp,
459 15 : clib_net_to_host_u16 (ip6->payload_length));
460 15 : icmp->checksum = ~ip_csum_fold (csum);
461 :
462 15 : return 0;
463 : }
464 :
465 : #endif /* __included_ip4_to_ip6_h__ */
|