Line data Source code
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 18 : format_npt66_trace (u8 *s, va_list *args)
23 : {
24 18 : CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
25 18 : CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
26 18 : npt66_trace_t *t = va_arg (*args, npt66_trace_t *);
27 :
28 18 : if (t->pool_index != ~0)
29 18 : 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 0 : s = format (s, "npt66: index %d (binding not found)\n", t->pool_index);
34 18 : 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 20 : ip6_prefix_copy (ip6_address_t dest, ip6_address_t src, int plen)
46 : {
47 20 : int bytes_to_copy = plen / 8;
48 20 : int residual_bits = plen % 8;
49 :
50 : // Copy full bytes
51 135 : for (int i = 0; i < bytes_to_copy; i++)
52 : {
53 115 : dest.as_u8[i] = src.as_u8[i];
54 : }
55 :
56 : // Handle the residual bits, if any
57 20 : if (residual_bits)
58 : {
59 3 : uint8_t mask = 0xFF << (8 - residual_bits);
60 3 : dest.as_u8[bytes_to_copy] = (dest.as_u8[bytes_to_copy] & ~mask) |
61 3 : (src.as_u8[bytes_to_copy] & mask);
62 : }
63 20 : return dest;
64 : }
65 : static int
66 21 : ip6_prefix_cmp (ip6_address_t a, ip6_address_t b, int plen)
67 : {
68 21 : int bytes_to_compare = plen / 8;
69 21 : int residual_bits = plen % 8;
70 :
71 : // Compare full bytes
72 136 : for (int i = 0; i < bytes_to_compare; i++)
73 : {
74 116 : if (a.as_u8[i] != b.as_u8[i])
75 : {
76 1 : return 0; // prefixes are not identical
77 : }
78 : }
79 :
80 : // Compare the residual bits, if any
81 20 : if (residual_bits)
82 : {
83 2 : uint8_t mask = 0xFF << (8 - residual_bits);
84 2 : if ((a.as_u8[bytes_to_compare] & mask) !=
85 2 : (b.as_u8[bytes_to_compare] & mask))
86 : {
87 0 : return 0; // prefixes are not identical
88 : }
89 : }
90 20 : return 1; // prefixes are identical
91 : }
92 :
93 : static int
94 20 : npt66_adjust_checksum (int plen, bool add, ip_csum_t delta,
95 : ip6_address_t *address)
96 : {
97 20 : if (plen <= 48)
98 : {
99 : // TODO: Check for 0xFFFF
100 15 : if (address->as_u16[3] == 0xffff)
101 0 : return -1;
102 21 : address->as_u16[3] = add ? ip_csum_add_even (address->as_u16[3], delta) :
103 6 : 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 5 : for (int i = 4; i < 8; i++)
109 : {
110 5 : if (address->as_u16[i] == 0xffff)
111 0 : continue;
112 10 : address->as_u16[i] = add ?
113 3 : ip_csum_add_even (address->as_u16[i], delta) :
114 2 : ip_csum_sub_even (address->as_u16[i], delta);
115 5 : break;
116 : }
117 : }
118 20 : return 0;
119 : }
120 :
121 : static int
122 17 : npt66_translate (ip6_header_t *ip, npt66_binding_t *binding, int dir)
123 : {
124 17 : int rv = 0;
125 17 : if (dir == VLIB_TX)
126 : {
127 9 : if (!ip6_prefix_cmp (ip->src_address, binding->internal,
128 9 : binding->internal_plen))
129 : {
130 1 : 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 1 : goto done;
135 : }
136 8 : ip->src_address = ip6_prefix_copy (ip->src_address, binding->external,
137 8 : binding->external_plen);
138 : /* Checksum neutrality */
139 8 : rv = npt66_adjust_checksum (binding->internal_plen, false,
140 : binding->delta, &ip->src_address);
141 : }
142 : else
143 : {
144 8 : if (!ip6_prefix_cmp (ip->dst_address, binding->external,
145 8 : binding->external_plen))
146 : {
147 0 : 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 0 : goto done;
152 : }
153 8 : ip->dst_address = ip6_prefix_copy (ip->dst_address, binding->internal,
154 8 : binding->internal_plen);
155 8 : rv = npt66_adjust_checksum (binding->internal_plen, true, binding->delta,
156 : &ip->dst_address);
157 : }
158 17 : done:
159 17 : return rv;
160 : }
161 :
162 : static int
163 4 : 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 4 : ip6_header_t *ip = (ip6_header_t *) (icmp + 2);
168 4 : int rv = 0;
169 4 : vlib_main_t *vm = vlib_get_main ();
170 :
171 4 : if (clib_net_to_host_u16 (outer_ip->payload_length) <
172 : sizeof (icmp46_header_t) + 4 + sizeof (ip6_header_t))
173 : {
174 0 : clib_warning ("ICMP6 payload too short");
175 0 : return -1;
176 : }
177 :
178 : // Validate checksums
179 : int bogus_length;
180 : u16 sum16;
181 4 : sum16 = ip6_tcp_udp_icmp_compute_checksum (vm, b, outer_ip, &bogus_length);
182 4 : if (sum16 != 0 && sum16 != 0xffff)
183 : {
184 0 : clib_warning ("ICMP6 checksum failed");
185 0 : return -1;
186 : }
187 4 : if (dir == VLIB_RX)
188 : {
189 4 : if (!ip6_prefix_cmp (ip->src_address, binding->external,
190 4 : binding->external_plen))
191 : {
192 0 : 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 0 : goto done;
197 : }
198 4 : ip->src_address = ip6_prefix_copy (ip->src_address, binding->internal,
199 4 : binding->internal_plen);
200 : /* Checksum neutrality */
201 4 : rv = npt66_adjust_checksum (binding->internal_plen, true, binding->delta,
202 : &ip->src_address);
203 : }
204 : else
205 : {
206 0 : if (!ip6_prefix_cmp (ip->dst_address, binding->external,
207 0 : binding->external_plen))
208 : {
209 0 : 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 0 : goto done;
214 : }
215 0 : ip->dst_address = ip6_prefix_copy (ip->dst_address, binding->internal,
216 0 : binding->internal_plen);
217 0 : rv = npt66_adjust_checksum (binding->internal_plen, false,
218 : binding->delta, &ip->dst_address);
219 : }
220 4 : done:
221 :
222 4 : 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 17 : npt66_node_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
233 : vlib_frame_t *frame, int dir)
234 : {
235 17 : npt66_main_t *nm = &npt66_main;
236 : u32 n_left_from, *from;
237 17 : u16 nexts[VLIB_FRAME_SIZE] = { 0 }, *next = nexts;
238 17 : u32 pool_indicies[VLIB_FRAME_SIZE], *pi = pool_indicies;
239 17 : vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
240 : ip6_header_t *ip;
241 :
242 17 : from = vlib_frame_vector_args (frame);
243 17 : n_left_from = frame->n_vectors;
244 17 : 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 34 : while (n_left_from > 0)
249 : {
250 17 : u32 sw_if_index = vnet_buffer (b[0])->sw_if_index[dir];
251 17 : u32 iph_offset =
252 17 : dir == VLIB_TX ? vnet_buffer (b[0])->ip.save_rewrite_length : 0;
253 17 : ip = (ip6_header_t *) (vlib_buffer_get_current (b[0]) + iph_offset);
254 17 : binding = npt66_interface_by_sw_if_index (sw_if_index);
255 17 : ASSERT (binding);
256 17 : *pi = binding - nm->bindings;
257 :
258 : /* By default pass packet to next node in the feature chain */
259 17 : vnet_feature_next_u16 (next, b[0]);
260 : int rv;
261 17 : icmp46_header_t *icmp = (icmp46_header_t *) (ip + 1);
262 17 : if (ip->protocol == IP_PROTOCOL_ICMP6 && icmp->type < 128)
263 : {
264 4 : rv = npt66_icmp6_translate (b[0], ip, icmp, binding, dir);
265 4 : if (rv < 0)
266 : {
267 0 : clib_warning ("ICMP6 npt66_translate failed");
268 0 : *next = NPT66_NEXT_DROP;
269 0 : goto next;
270 : }
271 : }
272 17 : rv = npt66_translate (ip, binding, dir);
273 :
274 17 : if (rv < 0)
275 : {
276 0 : vlib_node_increment_counter (vm, node->node_index,
277 : NPT66_ERROR_TRANSLATION, 1);
278 0 : *next = NPT66_NEXT_DROP;
279 0 : goto next;
280 : }
281 17 : else if (dir == VLIB_TX)
282 9 : vlib_node_increment_counter (vm, node->node_index, NPT66_ERROR_TX, 1);
283 : else
284 8 : vlib_node_increment_counter (vm, node->node_index, NPT66_ERROR_RX, 1);
285 :
286 17 : next:
287 17 : next += 1;
288 17 : n_left_from -= 1;
289 17 : b += 1;
290 17 : pi += 1;
291 : }
292 :
293 : /* Packet trace */
294 17 : if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
295 : {
296 : u32 i;
297 16 : b = bufs;
298 16 : pi = pool_indicies;
299 :
300 32 : for (i = 0; i < frame->n_vectors; i++)
301 : {
302 16 : if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
303 : {
304 16 : npt66_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t));
305 16 : if (*pi != ~0)
306 : {
307 16 : if (!pool_is_free_index (nm->bindings, *pi))
308 : {
309 16 : npt66_binding_t *tr =
310 16 : pool_elt_at_index (nm->bindings, *pi);
311 16 : t->internal = tr->internal;
312 16 : t->external = tr->external;
313 : }
314 : }
315 16 : t->pool_index = *pi;
316 :
317 16 : b += 1;
318 16 : pi += 1;
319 : }
320 : else
321 0 : break;
322 : }
323 : }
324 17 : vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors);
325 :
326 17 : return frame->n_vectors;
327 : }
328 :
329 12 : VLIB_NODE_FN (npt66_input_node)
330 : (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
331 : {
332 8 : return npt66_node_inline (vm, node, frame, VLIB_RX);
333 : }
334 13 : VLIB_NODE_FN (npt66_output_node)
335 : (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
336 : {
337 9 : return npt66_node_inline (vm, node, frame, VLIB_TX);
338 : }
339 :
340 101 : 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 101 : 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 51 : VNET_FEATURE_INIT (npt66_input, static) = {
366 : .arc_name = "ip6-unicast",
367 : .node_name = "npt66-input",
368 : };
369 51 : VNET_FEATURE_INIT (npt66_output, static) = {
370 : .arc_name = "ip6-output",
371 : .node_name = "npt66-output",
372 : };
|