npt66: icmp6 alg to handle icmp6 error messages
[vpp.git] / src / plugins / npt66 / npt66_node.c
1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright(c) 2023 Cisco Systems, Inc.
3
4 // This file contains the implementation of the NPT66 node.
5 // RFC6296: IPv6-to-IPv6 Network Prefix Translation (NPTv6)
6
7 #include <vnet/ip/ip.h>
8 #include <vnet/ip/ip6.h>
9 #include <vnet/ip/ip6_packet.h>
10
11 #include <npt66/npt66.h>
12 #include <npt66/npt66.api_enum.h>
13
14 typedef struct
15 {
16   u32 pool_index;
17   ip6_address_t internal;
18   ip6_address_t external;
19 } npt66_trace_t;
20
21 static inline u8 *
22 format_npt66_trace (u8 *s, va_list *args)
23 {
24   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
25   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
26   npt66_trace_t *t = va_arg (*args, npt66_trace_t *);
27
28   if (t->pool_index != ~0)
29     s = format (s, "npt66: index %d internal: %U external: %U\n",
30                 t->pool_index, format_ip6_address, &t->internal,
31                 format_ip6_address, &t->external);
32   else
33     s = format (s, "npt66: index %d (binding not found)\n", t->pool_index);
34   return s;
35 }
36
37 /* NPT66 next-nodes */
38 typedef enum
39 {
40   NPT66_NEXT_DROP,
41   NPT66_N_NEXT
42 } npt66_next_t;
43
44 static ip6_address_t
45 ip6_prefix_copy (ip6_address_t dest, ip6_address_t src, int plen)
46 {
47   int bytes_to_copy = plen / 8;
48   int residual_bits = plen % 8;
49
50   // Copy full bytes
51   for (int i = 0; i < bytes_to_copy; i++)
52     {
53       dest.as_u8[i] = src.as_u8[i];
54     }
55
56   // Handle the residual bits, if any
57   if (residual_bits)
58     {
59       uint8_t mask = 0xFF << (8 - residual_bits);
60       dest.as_u8[bytes_to_copy] = (dest.as_u8[bytes_to_copy] & ~mask) |
61                                   (src.as_u8[bytes_to_copy] & mask);
62     }
63   return dest;
64 }
65 static int
66 ip6_prefix_cmp (ip6_address_t a, ip6_address_t b, int plen)
67 {
68   int bytes_to_compare = plen / 8;
69   int residual_bits = plen % 8;
70
71   // Compare full bytes
72   for (int i = 0; i < bytes_to_compare; i++)
73     {
74       if (a.as_u8[i] != b.as_u8[i])
75         {
76           return 0; // prefixes are not identical
77         }
78     }
79
80   // Compare the residual bits, if any
81   if (residual_bits)
82     {
83       uint8_t mask = 0xFF << (8 - residual_bits);
84       if ((a.as_u8[bytes_to_compare] & mask) !=
85           (b.as_u8[bytes_to_compare] & mask))
86         {
87           return 0; // prefixes are not identical
88         }
89     }
90   return 1; // prefixes are identical
91 }
92
93 static int
94 npt66_adjust_checksum (int plen, bool add, ip_csum_t delta,
95                        ip6_address_t *address)
96 {
97   if (plen <= 48)
98     {
99       // TODO: Check for 0xFFFF
100       if (address->as_u16[3] == 0xffff)
101         return -1;
102       address->as_u16[3] = add ? ip_csum_add_even (address->as_u16[3], delta) :
103                                        ip_csum_sub_even (address->as_u16[3], delta);
104     }
105   else
106     {
107       /* For prefixes longer than 48 find a 16-bit word in the interface id */
108       for (int i = 4; i < 8; i++)
109         {
110           if (address->as_u16[i] == 0xffff)
111             continue;
112           address->as_u16[i] = add ?
113                                        ip_csum_add_even (address->as_u16[i], delta) :
114                                        ip_csum_sub_even (address->as_u16[i], delta);
115           break;
116         }
117     }
118   return 0;
119 }
120
121 static int
122 npt66_translate (ip6_header_t *ip, npt66_binding_t *binding, int dir)
123 {
124   int rv = 0;
125   if (dir == VLIB_TX)
126     {
127       if (!ip6_prefix_cmp (ip->src_address, binding->internal,
128                            binding->internal_plen))
129         {
130           clib_warning (
131             "npt66_translate: src address is not internal (%U -> %U)",
132             format_ip6_address, &ip->src_address, format_ip6_address,
133             &ip->dst_address);
134           goto done;
135         }
136       ip->src_address = ip6_prefix_copy (ip->src_address, binding->external,
137                                          binding->external_plen);
138       /* Checksum neutrality */
139       rv = npt66_adjust_checksum (binding->internal_plen, false,
140                                   binding->delta, &ip->src_address);
141     }
142   else
143     {
144       if (!ip6_prefix_cmp (ip->dst_address, binding->external,
145                            binding->external_plen))
146         {
147           clib_warning (
148             "npt66_translate: dst address is not external (%U -> %U)",
149             format_ip6_address, &ip->src_address, format_ip6_address,
150             &ip->dst_address);
151           goto done;
152         }
153       ip->dst_address = ip6_prefix_copy (ip->dst_address, binding->internal,
154                                          binding->internal_plen);
155       rv = npt66_adjust_checksum (binding->internal_plen, true, binding->delta,
156                                   &ip->dst_address);
157     }
158 done:
159   return rv;
160 }
161
162 static int
163 npt66_icmp6_translate (vlib_buffer_t *b, ip6_header_t *outer_ip,
164                        icmp46_header_t *icmp, npt66_binding_t *binding,
165                        int dir)
166 {
167   ip6_header_t *ip = (ip6_header_t *) (icmp + 2);
168   int rv = 0;
169   vlib_main_t *vm = vlib_get_main ();
170
171   if (clib_net_to_host_u16 (outer_ip->payload_length) <
172       sizeof (icmp46_header_t) + 4 + sizeof (ip6_header_t))
173     {
174       clib_warning ("ICMP6 payload too short");
175       return -1;
176     }
177
178   // Validate checksums
179   int bogus_length;
180   u16 sum16;
181   sum16 = ip6_tcp_udp_icmp_compute_checksum (vm, b, outer_ip, &bogus_length);
182   if (sum16 != 0 && sum16 != 0xffff)
183     {
184       clib_warning ("ICMP6 checksum failed");
185       return -1;
186     }
187   if (dir == VLIB_RX)
188     {
189       if (!ip6_prefix_cmp (ip->src_address, binding->external,
190                            binding->external_plen))
191         {
192           clib_warning (
193             "npt66_icmp6_translate: src address is not internal (%U -> %U)",
194             format_ip6_address, &ip->src_address, format_ip6_address,
195             &ip->dst_address);
196           goto done;
197         }
198       ip->src_address = ip6_prefix_copy (ip->src_address, binding->internal,
199                                          binding->internal_plen);
200       /* Checksum neutrality */
201       rv = npt66_adjust_checksum (binding->internal_plen, true, binding->delta,
202                                   &ip->src_address);
203     }
204   else
205     {
206       if (!ip6_prefix_cmp (ip->dst_address, binding->external,
207                            binding->external_plen))
208         {
209           clib_warning (
210             "npt66_icmp6_translate: dst address is not external (%U -> %U)",
211             format_ip6_address, &ip->src_address, format_ip6_address,
212             &ip->dst_address);
213           goto done;
214         }
215       ip->dst_address = ip6_prefix_copy (ip->dst_address, binding->internal,
216                                          binding->internal_plen);
217       rv = npt66_adjust_checksum (binding->internal_plen, false,
218                                   binding->delta, &ip->dst_address);
219     }
220 done:
221
222   return rv;
223 }
224
225 /*
226  * Lookup the packet tuple in the flow cache, given the lookup mask.
227  * If a binding is found, rewrite the packet according to instructions,
228  * otherwise follow configured default action (forward, punt or drop)
229  */
230 // TODO: Make use of SVR configurable
231 static_always_inline uword
232 npt66_node_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
233                    vlib_frame_t *frame, int dir)
234 {
235   npt66_main_t *nm = &npt66_main;
236   u32 n_left_from, *from;
237   u16 nexts[VLIB_FRAME_SIZE] = { 0 }, *next = nexts;
238   u32 pool_indicies[VLIB_FRAME_SIZE], *pi = pool_indicies;
239   vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
240   ip6_header_t *ip;
241
242   from = vlib_frame_vector_args (frame);
243   n_left_from = frame->n_vectors;
244   vlib_get_buffers (vm, from, b, n_left_from);
245   npt66_binding_t *binding;
246
247   /* Stage 1: build vector of flow hash (based on lookup mask) */
248   while (n_left_from > 0)
249     {
250       u32 sw_if_index = vnet_buffer (b[0])->sw_if_index[dir];
251       u32 iph_offset =
252         dir == VLIB_TX ? vnet_buffer (b[0])->ip.save_rewrite_length : 0;
253       ip = (ip6_header_t *) (vlib_buffer_get_current (b[0]) + iph_offset);
254       binding = npt66_interface_by_sw_if_index (sw_if_index);
255       ASSERT (binding);
256       *pi = binding - nm->bindings;
257
258       /* By default pass packet to next node in the feature chain */
259       vnet_feature_next_u16 (next, b[0]);
260       int rv;
261       icmp46_header_t *icmp = (icmp46_header_t *) (ip + 1);
262       if (ip->protocol == IP_PROTOCOL_ICMP6 && icmp->type < 128)
263         {
264           rv = npt66_icmp6_translate (b[0], ip, icmp, binding, dir);
265           if (rv < 0)
266             {
267               clib_warning ("ICMP6 npt66_translate failed");
268               *next = NPT66_NEXT_DROP;
269               goto next;
270             }
271         }
272       rv = npt66_translate (ip, binding, dir);
273
274       if (rv < 0)
275         {
276           vlib_node_increment_counter (vm, node->node_index,
277                                        NPT66_ERROR_TRANSLATION, 1);
278           *next = NPT66_NEXT_DROP;
279           goto next;
280         }
281       else if (dir == VLIB_TX)
282         vlib_node_increment_counter (vm, node->node_index, NPT66_ERROR_TX, 1);
283       else
284         vlib_node_increment_counter (vm, node->node_index, NPT66_ERROR_RX, 1);
285
286     next:
287       next += 1;
288       n_left_from -= 1;
289       b += 1;
290       pi += 1;
291     }
292
293   /* Packet trace */
294   if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
295     {
296       u32 i;
297       b = bufs;
298       pi = pool_indicies;
299
300       for (i = 0; i < frame->n_vectors; i++)
301         {
302           if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
303             {
304               npt66_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t));
305               if (*pi != ~0)
306                 {
307                   if (!pool_is_free_index (nm->bindings, *pi))
308                     {
309                       npt66_binding_t *tr =
310                         pool_elt_at_index (nm->bindings, *pi);
311                       t->internal = tr->internal;
312                       t->external = tr->external;
313                     }
314                 }
315               t->pool_index = *pi;
316
317               b += 1;
318               pi += 1;
319             }
320           else
321             break;
322         }
323     }
324   vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors);
325
326   return frame->n_vectors;
327 }
328
329 VLIB_NODE_FN (npt66_input_node)
330 (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
331 {
332   return npt66_node_inline (vm, node, frame, VLIB_RX);
333 }
334 VLIB_NODE_FN (npt66_output_node)
335 (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
336 {
337   return npt66_node_inline (vm, node, frame, VLIB_TX);
338 }
339
340 VLIB_REGISTER_NODE(npt66_input_node) = {
341     .name = "npt66-input",
342     .vector_size = sizeof(u32),
343     .format_trace = format_npt66_trace,
344     .type = VLIB_NODE_TYPE_INTERNAL,
345     .n_errors = NPT66_N_ERROR,
346     .error_counters = npt66_error_counters,
347     .n_next_nodes = NPT66_N_NEXT,
348     .next_nodes =
349         {
350             [NPT66_NEXT_DROP] = "error-drop",
351         },
352 };
353
354 VLIB_REGISTER_NODE (npt66_output_node) = {
355   .name = "npt66-output",
356   .vector_size = sizeof (u32),
357   .format_trace = format_npt66_trace,
358   .type = VLIB_NODE_TYPE_INTERNAL,
359   .n_errors = NPT66_N_ERROR,
360   .error_counters = npt66_error_counters,
361   .sibling_of = "npt66-input",
362 };
363
364 /* Hook up features */
365 VNET_FEATURE_INIT (npt66_input, static) = {
366   .arc_name = "ip6-unicast",
367   .node_name = "npt66-input",
368 };
369 VNET_FEATURE_INIT (npt66_output, static) = {
370   .arc_name = "ip6-output",
371   .node_name = "npt66-output",
372 };