LCOV - code coverage report
Current view: top level - plugins/npt66 - npt66_node.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 128 154 83.1 %
Date: 2023-10-26 01:39:38 Functions: 25 31 80.6 %

          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             : };

Generated by: LCOV version 1.14