1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright(c) 2023 Cisco Systems, Inc.
4 // This file contains the implementation of the NPT66 node.
5 // RFC6296: IPv6-to-IPv6 Network Prefix Translation (NPTv6)
7 #include <vnet/ip/ip.h>
8 #include <vnet/ip/ip6.h>
9 #include <vnet/ip/ip6_packet.h>
11 #include <npt66/npt66.h>
12 #include <npt66/npt66.api_enum.h>
17 ip6_address_t internal;
18 ip6_address_t external;
22 format_npt66_trace (u8 *s, va_list *args)
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 *);
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);
33 s = format (s, "npt66: index %d (binding not found)\n", t->pool_index);
37 /* NPT66 next-nodes */
45 ip6_prefix_copy (ip6_address_t dest, ip6_address_t src, int plen)
47 int bytes_to_copy = plen / 8;
48 int residual_bits = plen % 8;
51 for (int i = 0; i < bytes_to_copy; i++)
53 dest.as_u8[i] = src.as_u8[i];
56 // Handle the residual bits, if any
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);
66 ip6_prefix_cmp (ip6_address_t a, ip6_address_t b, int plen)
68 int bytes_to_compare = plen / 8;
69 int residual_bits = plen % 8;
72 for (int i = 0; i < bytes_to_compare; i++)
74 if (a.as_u8[i] != b.as_u8[i])
76 return 0; // prefixes are not identical
80 // Compare the residual bits, if any
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))
87 return 0; // prefixes are not identical
90 return 1; // prefixes are identical
94 npt66_adjust_checksum (int plen, bool add, ip_csum_t delta,
95 ip6_address_t *address)
99 // TODO: Check for 0xFFFF
100 if (address->as_u16[3] == 0xffff)
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);
107 /* For prefixes longer than 48 find a 16-bit word in the interface id */
108 for (int i = 4; i < 8; i++)
110 if (address->as_u16[i] == 0xffff)
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);
122 npt66_translate (ip6_header_t *ip, npt66_binding_t *binding, int dir)
127 if (!ip6_prefix_cmp (ip->src_address, binding->internal,
128 binding->internal_plen))
131 "npt66_translate: src address is not internal (%U -> %U)",
132 format_ip6_address, &ip->src_address, format_ip6_address,
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);
144 if (!ip6_prefix_cmp (ip->dst_address, binding->external,
145 binding->external_plen))
148 "npt66_translate: dst address is not external (%U -> %U)",
149 format_ip6_address, &ip->src_address, format_ip6_address,
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,
163 * Lookup the packet tuple in the flow cache, given the lookup mask.
164 * If a binding is found, rewrite the packet according to instructions,
165 * otherwise follow configured default action (forward, punt or drop)
167 // TODO: Make use of SVR configurable
168 static_always_inline uword
169 npt66_node_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
170 vlib_frame_t *frame, int dir)
172 npt66_main_t *nm = &npt66_main;
173 u32 n_left_from, *from;
174 u16 nexts[VLIB_FRAME_SIZE] = { 0 }, *next = nexts;
175 u32 pool_indicies[VLIB_FRAME_SIZE], *pi = pool_indicies;
176 vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
179 from = vlib_frame_vector_args (frame);
180 n_left_from = frame->n_vectors;
181 vlib_get_buffers (vm, from, b, n_left_from);
182 npt66_binding_t *binding;
184 /* Stage 1: build vector of flow hash (based on lookup mask) */
185 while (n_left_from > 0)
187 u32 sw_if_index = vnet_buffer (b[0])->sw_if_index[dir];
189 dir == VLIB_TX ? vnet_buffer (b[0])->ip.save_rewrite_length : 0;
190 ip = (ip6_header_t *) (vlib_buffer_get_current (b[0]) + iph_offset);
191 binding = npt66_interface_by_sw_if_index (sw_if_index);
193 *pi = binding - nm->bindings;
195 /* By default pass packet to next node in the feature chain */
196 vnet_feature_next_u16 (next, b[0]);
198 int rv = npt66_translate (ip, binding, dir);
201 vlib_node_increment_counter (vm, node->node_index,
202 NPT66_ERROR_TRANSLATION, 1);
203 *next = NPT66_NEXT_DROP;
206 else if (dir == VLIB_TX)
207 vlib_node_increment_counter (vm, node->node_index, NPT66_ERROR_TX, 1);
209 vlib_node_increment_counter (vm, node->node_index, NPT66_ERROR_RX, 1);
219 if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE)))
225 for (i = 0; i < frame->n_vectors; i++)
227 if (b[0]->flags & VLIB_BUFFER_IS_TRACED)
229 npt66_trace_t *t = vlib_add_trace (vm, node, b[0], sizeof (*t));
232 if (!pool_is_free_index (nm->bindings, *pi))
234 npt66_binding_t *tr =
235 pool_elt_at_index (nm->bindings, *pi);
236 t->internal = tr->internal;
237 t->external = tr->external;
249 vlib_buffer_enqueue_to_next (vm, node, from, nexts, frame->n_vectors);
251 return frame->n_vectors;
254 VLIB_NODE_FN (npt66_input_node)
255 (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
257 return npt66_node_inline (vm, node, frame, VLIB_RX);
259 VLIB_NODE_FN (npt66_output_node)
260 (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
262 return npt66_node_inline (vm, node, frame, VLIB_TX);
265 VLIB_REGISTER_NODE(npt66_input_node) = {
266 .name = "npt66-input",
267 .vector_size = sizeof(u32),
268 .format_trace = format_npt66_trace,
269 .type = VLIB_NODE_TYPE_INTERNAL,
270 .n_errors = NPT66_N_ERROR,
271 .error_counters = npt66_error_counters,
272 .n_next_nodes = NPT66_N_NEXT,
275 [NPT66_NEXT_DROP] = "error-drop",
279 VLIB_REGISTER_NODE (npt66_output_node) = {
280 .name = "npt66-output",
281 .vector_size = sizeof (u32),
282 .format_trace = format_npt66_trace,
283 .type = VLIB_NODE_TYPE_INTERNAL,
284 .n_errors = NPT66_N_ERROR,
285 .error_counters = npt66_error_counters,
286 .sibling_of = "npt66-input",
289 /* Hook up features */
290 VNET_FEATURE_INIT (npt66_input, static) = {
291 .arc_name = "ip6-unicast",
292 .node_name = "npt66-input",
294 VNET_FEATURE_INIT (npt66_output, static) = {
295 .arc_name = "ip6-output",
296 .node_name = "npt66-output",