blob: 57c2b6ff78b9a617a2205c6ca071efb9a8ee01ba [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 */
Klement Sekeraf126e742019-10-10 09:46:06 +000028typedef int (*ip4_to_ip6_set_fn_t) (vlib_buffer_t * b, ip4_header_t * ip4,
29 ip6_header_t * ip6, void *ctx);
Matus Fabiana774b532017-05-02 03:15:22 -070030
Matus Fabiana774b532017-05-02 03:15:22 -070031static u8 icmp_to_icmp6_updater_pointer_table[] =
32 { 0, 1, 4, 4, ~0,
33 ~0, ~0, ~0, 7, 6,
34 ~0, ~0, 8, 8, 8,
35 8, 24, 24, 24, 24
36 };
Matus Fabiana774b532017-05-02 03:15:22 -070037
38#define frag_id_4to6(id) (id)
39
40/**
41 * @brief Get TCP/UDP port number or ICMP id from IPv4 packet.
42 *
43 * @param ip4 IPv4 header.
44 * @param sender 1 get sender port, 0 get receiver port.
45 *
46 * @returns Port number on success, 0 otherwise.
47 */
48always_inline u16
49ip4_get_port (ip4_header_t * ip, u8 sender)
50{
51 if (ip->ip_version_and_header_length != 0x45 ||
52 ip4_get_fragment_offset (ip))
53 return 0;
54
55 if (PREDICT_TRUE ((ip->protocol == IP_PROTOCOL_TCP) ||
56 (ip->protocol == IP_PROTOCOL_UDP)))
57 {
58 udp_header_t *udp = (void *) (ip + 1);
59 return (sender) ? udp->src_port : udp->dst_port;
60 }
61 else if (ip->protocol == IP_PROTOCOL_ICMP)
62 {
63 icmp46_header_t *icmp = (void *) (ip + 1);
64 if (icmp->type == ICMP4_echo_request || icmp->type == ICMP4_echo_reply)
65 {
66 return *((u16 *) (icmp + 1));
67 }
68 else if (clib_net_to_host_u16 (ip->length) >= 64)
69 {
70 ip = (ip4_header_t *) (icmp + 2);
71 if (PREDICT_TRUE ((ip->protocol == IP_PROTOCOL_TCP) ||
72 (ip->protocol == IP_PROTOCOL_UDP)))
73 {
74 udp_header_t *udp = (void *) (ip + 1);
75 return (sender) ? udp->dst_port : udp->src_port;
76 }
77 else if (ip->protocol == IP_PROTOCOL_ICMP)
78 {
79 icmp46_header_t *icmp = (void *) (ip + 1);
80 if (icmp->type == ICMP4_echo_request ||
81 icmp->type == ICMP4_echo_reply)
82 {
83 return *((u16 *) (icmp + 1));
84 }
85 }
86 }
87 }
88 return 0;
89}
90
91/**
92 * @brief Convert type and code value from ICMP4 to ICMP6.
93 *
94 * @param icmp ICMP header.
95 * @param inner_ip4 Inner IPv4 header if present, 0 otherwise.
96 *
97 * @returns 0 on success, non-zero value otherwise.
98 */
99always_inline int
100icmp_to_icmp6_header (icmp46_header_t * icmp, ip4_header_t ** inner_ip4)
101{
102 *inner_ip4 = NULL;
103 switch (icmp->type)
104 {
105 case ICMP4_echo_reply:
106 icmp->type = ICMP6_echo_reply;
107 break;
108 case ICMP4_echo_request:
109 icmp->type = ICMP6_echo_request;
110 break;
111 case ICMP4_destination_unreachable:
112 *inner_ip4 = (ip4_header_t *) (((u8 *) icmp) + 8);
113
114 switch (icmp->code)
115 {
116 case ICMP4_destination_unreachable_destination_unreachable_net: //0
117 case ICMP4_destination_unreachable_destination_unreachable_host: //1
118 icmp->type = ICMP6_destination_unreachable;
119 icmp->code = ICMP6_destination_unreachable_no_route_to_destination;
120 break;
121 case ICMP4_destination_unreachable_protocol_unreachable: //2
122 icmp->type = ICMP6_parameter_problem;
123 icmp->code = ICMP6_parameter_problem_unrecognized_next_header;
124 break;
125 case ICMP4_destination_unreachable_port_unreachable: //3
126 icmp->type = ICMP6_destination_unreachable;
127 icmp->code = ICMP6_destination_unreachable_port_unreachable;
128 break;
129 case ICMP4_destination_unreachable_fragmentation_needed_and_dont_fragment_set: //4
130 icmp->type =
131 ICMP6_packet_too_big;
132 icmp->code = 0;
133 {
134 u32 advertised_mtu = clib_net_to_host_u32 (*((u32 *) (icmp + 1)));
135 if (advertised_mtu)
136 advertised_mtu += 20;
137 else
138 advertised_mtu = 1000; //FIXME ! (RFC 1191 - plateau value)
139
140 //FIXME: = minimum(advertised MTU+20, MTU_of_IPv6_nexthop, (MTU_of_IPv4_nexthop)+20)
141 *((u32 *) (icmp + 1)) = clib_host_to_net_u32 (advertised_mtu);
142 }
143 break;
144
145 case ICMP4_destination_unreachable_source_route_failed: //5
146 case ICMP4_destination_unreachable_destination_network_unknown: //6
147 case ICMP4_destination_unreachable_destination_host_unknown: //7
148 case ICMP4_destination_unreachable_source_host_isolated: //8
149 case ICMP4_destination_unreachable_network_unreachable_for_type_of_service: //11
150 case ICMP4_destination_unreachable_host_unreachable_for_type_of_service: //12
151 icmp->type =
152 ICMP6_destination_unreachable;
153 icmp->code = ICMP6_destination_unreachable_no_route_to_destination;
154 break;
155 case ICMP4_destination_unreachable_network_administratively_prohibited: //9
156 case ICMP4_destination_unreachable_host_administratively_prohibited: //10
157 case ICMP4_destination_unreachable_communication_administratively_prohibited: //13
158 case ICMP4_destination_unreachable_precedence_cutoff_in_effect: //15
159 icmp->type = ICMP6_destination_unreachable;
160 icmp->code =
161 ICMP6_destination_unreachable_destination_administratively_prohibited;
162 break;
163 case ICMP4_destination_unreachable_host_precedence_violation: //14
164 default:
165 return -1;
166 }
167 break;
168
169 case ICMP4_time_exceeded: //11
170 *inner_ip4 = (ip4_header_t *) (((u8 *) icmp) + 8);
171 icmp->type = ICMP6_time_exceeded;
172 break;
173
174 case ICMP4_parameter_problem:
175 *inner_ip4 = (ip4_header_t *) (((u8 *) icmp) + 8);
176
177 switch (icmp->code)
178 {
179 case ICMP4_parameter_problem_pointer_indicates_error:
180 case ICMP4_parameter_problem_bad_length:
181 icmp->type = ICMP6_parameter_problem;
182 icmp->code = ICMP6_parameter_problem_erroneous_header_field;
183 {
184 u8 ptr =
185 icmp_to_icmp6_updater_pointer_table[*((u8 *) (icmp + 1))];
186 if (ptr == 0xff)
187 return -1;
188
189 *((u32 *) (icmp + 1)) = clib_host_to_net_u32 (ptr);
190 }
191 break;
192 default:
193 //All other codes cause error
194 return -1;
195 }
196 break;
197
198 default:
199 //All other types cause error
200 return -1;
201 break;
202 }
203 return 0;
204}
205
206/**
207 * @brief Translate ICMP4 packet to ICMP6.
208 *
209 * @param p Buffer to translate.
210 * @param fn The function to translate outer header.
211 * @param ctx A context passed in the outer header translate function.
212 * @param inner_fn The function to translate inner header.
213 * @param inner_ctx A context passed in the inner header translate function.
214 *
215 * @returns 0 on success, non-zero value otherwise.
216 */
217always_inline int
218icmp_to_icmp6 (vlib_buffer_t * p, ip4_to_ip6_set_fn_t fn, void *ctx,
219 ip4_to_ip6_set_fn_t inner_fn, void *inner_ctx)
220{
221 ip4_header_t *ip4, *inner_ip4;
222 ip6_header_t *ip6, *inner_ip6;
223 u32 ip_len;
224 icmp46_header_t *icmp;
225 ip_csum_t csum;
226 ip6_frag_hdr_t *inner_frag;
Alexander Chernavin8af24b12020-01-31 09:19:49 -0500227 ip6_frag_hdr_t *outer_frag= NULL;
Matus Fabiana774b532017-05-02 03:15:22 -0700228 u32 inner_frag_id;
229 u32 inner_frag_offset;
Alexander Chernavin8af24b12020-01-31 09:19:49 -0500230 u32 outer_frag_id;
Matus Fabiana774b532017-05-02 03:15:22 -0700231 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);
Dave Barachb7b92992018-10-17 10:38:51 -0400263 memmove (u8_ptr_add (ip6, sizeof (*ip6) - sizeof (*ip4)), ip4,
Klement Sekeraf126e742019-10-10 09:46:06 +0000264 20 + 8);
Matus Fabiana774b532017-05-02 03:15:22 -0700265 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);
Dave Barachb7b92992018-10-17 10:38:51 -0400289 memmove (u8_ptr_add (ip6, sizeof (*ip6) - sizeof (*ip4)), ip4,
Klement Sekeraf126e742019-10-10 09:46:06 +0000290 20 + 8);
Matus Fabiana774b532017-05-02 03:15:22 -0700291 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);
Matus Fabiana774b532017-05-02 03:15:22 -0700324 //Only types ICMP4_echo_request and ICMP4_echo_reply are handled by icmp_to_icmp6_header
Matus Fabiana774b532017-05-02 03:15:22 -0700325 inner_icmp->type = (inner_icmp->type == ICMP4_echo_request) ?
326 ICMP6_echo_request : ICMP6_echo_reply;
Matus Fabiana774b532017-05-02 03:15:22 -0700327 inner_L4_checksum = &inner_icmp->checksum;
328 inner_ip4->protocol = IP_PROTOCOL_ICMP6;
329 }
330 else
331 {
332 /* To shut up Coverity */
333 os_panic ();
334 }
335
Matus Fabiana774b532017-05-02 03:15:22 -0700336 inner_ip6->ip_version_traffic_class_and_flow_label =
337 clib_host_to_net_u32 ((6 << 28) + (inner_ip4->tos << 20));
338 inner_ip6->payload_length =
339 u16_net_add (inner_ip4->length, -sizeof (*inner_ip4));
340 inner_ip6->hop_limit = inner_ip4->ttl;
341 inner_ip6->protocol = inner_ip4->protocol;
342
Klement Sekeraf126e742019-10-10 09:46:06 +0000343 if ((rv = inner_fn (p, inner_ip4, inner_ip6, inner_ctx)) != 0)
Matus Fabiana774b532017-05-02 03:15:22 -0700344 return rv;
345
346 if (PREDICT_FALSE (inner_frag != NULL))
347 {
348 inner_frag->next_hdr = inner_ip6->protocol;
349 inner_frag->identification = inner_frag_id;
350 inner_frag->rsv = 0;
351 inner_frag->fragment_offset_and_more =
352 ip6_frag_hdr_offset_and_more (inner_frag_offset, inner_frag_more);
353 inner_ip6->protocol = IP_PROTOCOL_IPV6_FRAGMENTATION;
354 inner_ip6->payload_length =
355 clib_host_to_net_u16 (clib_net_to_host_u16
356 (inner_ip6->payload_length) +
357 sizeof (*inner_frag));
358 }
359
Matus Fabian029f3d22017-06-15 02:28:50 -0700360 csum = *inner_L4_checksum;
361 if (inner_ip6->protocol == IP_PROTOCOL_ICMP6)
Matus Fabian89223f42017-06-12 02:29:39 -0700362 {
Matus Fabian029f3d22017-06-15 02:28:50 -0700363 //Recompute ICMP checksum
364 icmp46_header_t *inner_icmp = (icmp46_header_t *) (inner_ip4 + 1);
365
366 inner_icmp->checksum = 0;
367 csum = ip_csum_with_carry (0, inner_ip6->payload_length);
368 csum =
369 ip_csum_with_carry (csum,
370 clib_host_to_net_u16 (inner_ip6->protocol));
371 csum = ip_csum_with_carry (csum, inner_ip6->src_address.as_u64[0]);
372 csum = ip_csum_with_carry (csum, inner_ip6->src_address.as_u64[1]);
373 csum = ip_csum_with_carry (csum, inner_ip6->dst_address.as_u64[0]);
374 csum = ip_csum_with_carry (csum, inner_ip6->dst_address.as_u64[1]);
375 csum =
376 ip_incremental_checksum (csum, inner_icmp,
377 clib_net_to_host_u16
378 (inner_ip6->payload_length));
379 inner_icmp->checksum = ~ip_csum_fold (csum);
380 }
381 else
382 {
383 /* UDP checksum is optional */
384 if (csum)
385 {
386 csum =
387 ip_csum_add_even (csum, inner_ip6->src_address.as_u64[0]);
388 csum =
389 ip_csum_add_even (csum, inner_ip6->src_address.as_u64[1]);
390 csum =
391 ip_csum_add_even (csum, inner_ip6->dst_address.as_u64[0]);
392 csum =
393 ip_csum_add_even (csum, inner_ip6->dst_address.as_u64[1]);
394 *inner_L4_checksum = ip_csum_fold (csum);
395 }
Matus Fabian89223f42017-06-12 02:29:39 -0700396 }
Matus Fabiana774b532017-05-02 03:15:22 -0700397 }
398 else
399 {
Alexander Chernavin8af24b12020-01-31 09:19:49 -0500400 if (PREDICT_FALSE (ip4->flags_and_fragment_offset &
401 clib_host_to_net_u16 (IP4_HEADER_FLAG_MORE_FRAGMENTS)))
402 {
403 vlib_buffer_advance (p, sizeof (*ip4) - sizeof (*ip6) -
404 sizeof (*outer_frag));
405 ip6 = vlib_buffer_get_current (p);
406 outer_frag = (ip6_frag_hdr_t *) (ip6 + 1);
407 outer_frag_id = frag_id_4to6 (ip4->fragment_id);
408 }
409 else
410 {
411 vlib_buffer_advance (p, sizeof (*ip4) - sizeof (*ip6));
412 ip6 = vlib_buffer_get_current (p);
413 }
Matus Fabiana774b532017-05-02 03:15:22 -0700414 ip6->payload_length =
415 clib_host_to_net_u16 (clib_net_to_host_u16 (ip4->length) -
416 sizeof (*ip4));
417 }
418
419 //Translate outer IPv6
420 ip6->ip_version_traffic_class_and_flow_label =
421 clib_host_to_net_u32 ((6 << 28) + (ip4->tos << 20));
422
423 ip6->hop_limit = ip4->ttl;
424 ip6->protocol = IP_PROTOCOL_ICMP6;
425
Klement Sekeraf126e742019-10-10 09:46:06 +0000426 if ((rv = fn (p, ip4, ip6, ctx)) != 0)
Matus Fabiana774b532017-05-02 03:15:22 -0700427 return rv;
428
Alexander Chernavin8af24b12020-01-31 09:19:49 -0500429 if (PREDICT_FALSE (outer_frag != NULL))
430 {
431 outer_frag->next_hdr = ip6->protocol;
432 outer_frag->identification = outer_frag_id;
433 outer_frag->rsv = 0;
434 outer_frag->fragment_offset_and_more = ip6_frag_hdr_offset_and_more (0, 1);
435 ip6->protocol = IP_PROTOCOL_IPV6_FRAGMENTATION;
436 ip6->payload_length = u16_net_add (ip6->payload_length,
437 sizeof (*outer_frag));
438 }
439
Alexander Chernavin180210f2020-01-15 06:45:47 -0500440 //Truncate when ICMPv6 error message exceeds the minimal IPv6 MTU
441 if (p->current_length > 1280 && icmp->type < 128)
Matus Fabiana774b532017-05-02 03:15:22 -0700442 {
443 ip6->payload_length = clib_host_to_net_u16 (1280 - sizeof (*ip6));
444 p->current_length = 1280; //Looks too simple to be correct...
445 }
446
447 //Recompute ICMP checksum
448 icmp->checksum = 0;
449 csum = ip_csum_with_carry (0, ip6->payload_length);
450 csum = ip_csum_with_carry (csum, clib_host_to_net_u16 (ip6->protocol));
451 csum = ip_csum_with_carry (csum, ip6->src_address.as_u64[0]);
452 csum = ip_csum_with_carry (csum, ip6->src_address.as_u64[1]);
453 csum = ip_csum_with_carry (csum, ip6->dst_address.as_u64[0]);
454 csum = ip_csum_with_carry (csum, ip6->dst_address.as_u64[1]);
455 csum =
456 ip_incremental_checksum (csum, icmp,
457 clib_net_to_host_u16 (ip6->payload_length));
458 icmp->checksum = ~ip_csum_fold (csum);
459
460 return 0;
461}
462
Matus Fabiana774b532017-05-02 03:15:22 -0700463#endif /* __included_ip4_to_ip6_h__ */