npt66: network prefix translation for ipv6
[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
13 typedef struct
14 {
15   u32 pool_index;
16   ip6_address_t internal;
17   ip6_address_t external;
18 } npt66_trace_t;
19
20 static inline u8 *
21 format_npt66_trace (u8 *s, va_list *args)
22 {
23   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
24   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
25   npt66_trace_t *t = va_arg (*args, npt66_trace_t *);
26
27   if (t->pool_index != ~0)
28     s = format (s, "npt66: index %d internal: %U external: %U\n",
29                 t->pool_index, format_ip6_address, &t->internal,
30                 format_ip6_address, &t->external);
31   else
32     s = format (s, "npt66: index %d (binding not found)\n", t->pool_index);
33   return s;
34 }
35
36 /* NPT66 next-nodes */
37 typedef enum
38 {
39   NPT66_NEXT_DROP,
40   NPT66_N_NEXT
41 } npt66_next_t;
42
43 static ip6_address_t
44 ip6_prefix_copy (ip6_address_t dest, ip6_address_t src, int plen)
45 {
46   int bytes_to_copy = plen / 8;
47   int residual_bits = plen % 8;
48
49   // Copy full bytes
50   for (int i = 0; i < bytes_to_copy; i++)
51     {
52       dest.as_u8[i] = src.as_u8[i];
53     }
54
55   // Handle the residual bits, if any
56   if (residual_bits)
57     {
58       uint8_t mask = 0xFF << (8 - residual_bits);
59       dest.as_u8[bytes_to_copy] = (dest.as_u8[bytes_to_copy] & ~mask) |
60                                   (src.as_u8[bytes_to_copy] & mask);
61     }
62   return dest;
63 }
64 static int
65 ip6_prefix_cmp (ip6_address_t a, ip6_address_t b, int plen)
66 {
67   int bytes_to_compare = plen / 8;
68   int residual_bits = plen % 8;
69
70   // Compare full bytes
71   for (int i = 0; i < bytes_to_compare; i++)
72     {
73       if (a.as_u8[i] != b.as_u8[i])
74         {
75           return 0; // prefixes are not identical
76         }
77     }
78
79   // Compare the residual bits, if any
80   if (residual_bits)
81     {
82       uint8_t mask = 0xFF << (8 - residual_bits);
83       if ((a.as_u8[bytes_to_compare] & mask) !=
84           (b.as_u8[bytes_to_compare] & mask))
85         {
86           return 0; // prefixes are not identical
87         }
88     }
89   return 1; // prefixes are identical
90 }
91
92 static int
93 npt66_adjust_checksum (int plen, bool add, ip_csum_t delta,
94                        ip6_address_t *address)
95 {
96   if (plen <= 48)
97     {
98       // TODO: Check for 0xFFFF
99       if (address->as_u16[3] == 0xffff)
100         return -1;
101       address->as_u16[3] = add ? ip_csum_add_even (address->as_u16[3], delta) :
102                                        ip_csum_sub_even (address->as_u16[3], delta);
103     }
104   else
105     {
106       /* For prefixes longer than 48 find a 16-bit word in the interface id */
107       for (int i = 4; i < 8; i++)
108         {
109           if (address->as_u16[i] == 0xffff)
110             continue;
111           address->as_u16[i] = add ?
112                                        ip_csum_add_even (address->as_u16[i], delta) :
113                                        ip_csum_sub_even (address->as_u16[i], delta);
114           break;
115         }
116     }
117   return 0;
118 }
119
120 static int
121 npt66_translate (ip6_header_t *ip, npt66_binding_t *binding, int dir)
122 {
123   int rv = 0;
124   clib_warning ("npt66_translate: before: %U", format_ip6_header, ip, 40);
125   if (dir == VLIB_TX)
126     {
127       if (!ip6_prefix_cmp (ip->src_address, binding->internal,
128                            binding->internal_plen))
129         {
130           clib_warning ("npt66_translate: src address is not internal");
131           goto done;
132         }
133       ip->src_address = ip6_prefix_copy (ip->src_address, binding->external,
134                                          binding->external_plen);
135       /* Checksum neutrality */
136       rv = npt66_adjust_checksum (binding->internal_plen, false,
137                                   binding->delta, &ip->src_address);
138     }
139   else
140     {
141       if (!ip6_prefix_cmp (ip->dst_address, binding->external,
142                            binding->external_plen))
143         {
144           clib_warning ("npt66_translate: dst address is not external");
145           goto done;
146         }
147       ip->dst_address = ip6_prefix_copy (ip->dst_address, binding->internal,
148                                          binding->internal_plen);
149       rv = npt66_adjust_checksum (binding->internal_plen, true, binding->delta,
150                                   &ip->src_address);
151     }
152   clib_warning ("npt66_translate: after: %U", format_ip6_header, ip, 40);
153 done:
154   return rv;
155 }
156
157 /*
158  * Lookup the packet tuple in the flow cache, given the lookup mask.
159  * If a binding is found, rewrite the packet according to instructions,
160  * otherwise follow configured default action (forward, punt or drop)
161  */
162 // TODO: Make use of SVR configurable
163 static_always_inline uword
164 npt66_node_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
165                    vlib_frame_t *frame, int dir)
166 {
167   npt66_main_t *nm = &npt66_main;
168   u32 n_left_from, *from;
169   u16 nexts[VLIB_FRAME_SIZE] = { 0 }, *next = nexts;
170   u32 pool_indicies[VLIB_FRAME_SIZE], *pi = pool_indicies;
171   vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
172   ip6_header_t *ip;
173
174   from = vlib_frame_vector_args (frame);
175   n_left_from = frame->n_vectors;
176   vlib_get_buffers (vm, from, b, n_left_from);
177   npt66_binding_t *binding;
178
179   /* Stage 1: build vector of flow hash (based on lookup mask) */
180   while (n_left_from > 0)
181     {
182       clib_warning ("DIRECTION: %u", dir);
183       u32 sw_if_index = vnet_buffer (b[0])->sw_if_index[dir];
184       u32 iph_offset =
185         dir == VLIB_TX ? vnet_buffer (b[0])->ip.save_rewrite_length : 0;
186       ip = (ip6_header_t *) (vlib_buffer_get_current (b[0]) + iph_offset);
187       binding = npt66_interface_by_sw_if_index (sw_if_index);
188       ASSERT (binding);
189       *pi = binding - nm->bindings;
190
191       /* By default pass packet to next node in the feature chain */
192       vnet_feature_next_u16 (next, b[0]);
193
194       int rv = npt66_translate (ip, binding, dir);
195       if (rv < 0)
196         {
197           clib_warning ("npt66_translate failed");
198           *next = NPT66_NEXT_DROP;
199         }
200
201       /*next: */
202       next += 1;
203       n_left_from -= 1;
204       b += 1;
205       pi += 1;
206     }
207
208   /* Packet trace */
209   if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
210     {
211       u32 i;
212       b = bufs;
213       pi = pool_indicies;
214
215       for (i = 0; i < frame->n_vectors; i++)
216         {
217           if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
218             {
219               npt66_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t));
220               if (*pi != ~0)
221                 {
222                   if (!pool_is_free_index (nm->bindings, *pi))
223                     {
224                       npt66_binding_t *tr =
225                         pool_elt_at_index (nm->bindings, *pi);
226                       t->internal = tr->internal;
227                       t->external = tr->external;
228                     }
229                 }
230               t->pool_index = *pi;
231
232               b += 1;
233               pi += 1;
234             }
235           else
236             break;
237         }
238     }
239   vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors);
240
241   return frame->n_vectors;
242 }
243
244 VLIB_NODE_FN (npt66_input_node)
245 (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
246 {
247   return npt66_node_inline (vm, node, frame, VLIB_RX);
248 }
249 VLIB_NODE_FN (npt66_output_node)
250 (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
251 {
252   return npt66_node_inline (vm, node, frame, VLIB_TX);
253 }
254
255 VLIB_REGISTER_NODE(npt66_input_node) = {
256     .name = "npt66-input",
257     .vector_size = sizeof(u32),
258     .format_trace = format_npt66_trace,
259     .type = VLIB_NODE_TYPE_INTERNAL,
260     // .n_errors = NPT66_N_ERROR,
261     // .error_counters = npt66_error_counters,
262     .n_next_nodes = NPT66_N_NEXT,
263     .next_nodes =
264         {
265             [NPT66_NEXT_DROP] = "error-drop",
266         },
267 };
268
269 VLIB_REGISTER_NODE (npt66_output_node) = {
270   .name = "npt66-output",
271   .vector_size = sizeof (u32),
272   .format_trace = format_npt66_trace,
273   .type = VLIB_NODE_TYPE_INTERNAL,
274   // .n_errors = npt66_N_ERROR,
275   // .error_counters = npt66_error_counters,
276   .sibling_of = "npt66-input",
277 };
278
279 /* Hook up features */
280 VNET_FEATURE_INIT (npt66_input, static) = {
281   .arc_name = "ip6-unicast",
282   .node_name = "npt66-input",
283   .runs_after = VNET_FEATURES ("ip4-sv-reassembly-feature"),
284 };
285 VNET_FEATURE_INIT (npt66_output, static) = {
286   .arc_name = "ip6-output",
287   .node_name = "npt66-output",
288   .runs_after = VNET_FEATURES ("ip4-sv-reassembly-output-feature"),
289 };