LCOV - code coverage report
Current view: top level - plugins/nat/pnat - pnat.c (source / functions) Hit Total Coverage
Test: coverage-filtered.info Lines: 152 196 77.6 %
Date: 2023-07-05 22:20:52 Functions: 13 14 92.9 %

          Line data    Source code
       1             : /*
       2             :  * Copyright (c) 2021 Cisco and/or its affiliates.
       3             :  * Licensed under the Apache License, Version 2.0 (the "License");
       4             :  * you may not use this file except in compliance with the License.
       5             :  * You may obtain a copy of the License at:
       6             :  *
       7             :  *     http://www.apache.org/licenses/LICENSE-2.0
       8             :  *
       9             :  * Unless required by applicable law or agreed to in writing, software
      10             :  * distributed under the License is distributed on an "AS IS" BASIS,
      11             :  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             :  * See the License for the specific language governing permissions and
      13             :  * limitations under the License.
      14             :  */
      15             : 
      16             : #include "pnat.h"
      17             : #include <arpa/inet.h>
      18             : #include <stdbool.h>
      19             : #include <vlib/vlib.h>
      20             : #include <vnet/feature/feature.h>
      21             : #include <vnet/fib/fib_table.h>
      22             : #include <vnet/ip/format.h>
      23             : #include <vnet/ip/ip4.h>
      24             : #include <vnet/ip/ip4_packet.h>
      25             : #include <vnet/ip/reass/ip4_sv_reass.h>
      26             : #include <vppinfra/clib_error.h>
      27             : 
      28             : /*
      29             :  * This is the main control plane part of the PNAT (Policy 1:1 NAT) feature.
      30             :  */
      31             : 
      32             : pnat_main_t pnat_main;
      33             : 
      34             : /*
      35             :  * Do a lookup in the interface vector (interface_by_sw_if_index)
      36             :  * and return pool entry.
      37             :  */
      38          48 : pnat_interface_t *pnat_interface_by_sw_if_index(u32 sw_if_index) {
      39          48 :     pnat_main_t *pm = &pnat_main;
      40             : 
      41          94 :     if (!pm->interface_by_sw_if_index ||
      42          46 :         sw_if_index > (vec_len(pm->interface_by_sw_if_index) - 1))
      43           4 :         return 0;
      44          44 :     u32 index = pm->interface_by_sw_if_index[sw_if_index];
      45          44 :     if (index == ~0)
      46          18 :         return 0;
      47          26 :     if (pool_is_free_index(pm->interfaces, index))
      48           0 :         return 0;
      49          26 :     return pool_elt_at_index(pm->interfaces, index);
      50             : }
      51             : 
      52          33 : static pnat_mask_fast_t pnat_mask2fast(pnat_mask_t lookup_mask) {
      53          33 :     pnat_mask_fast_t m = {0};
      54             : 
      55          33 :     if (lookup_mask & PNAT_SA)
      56          12 :         m.as_u64[0] = 0xffffffff00000000;
      57          33 :     if (lookup_mask & PNAT_DA)
      58          21 :         m.as_u64[0] |= 0x00000000ffffffff;
      59          33 :     m.as_u64[1] = 0x00ffffff00000000;
      60          33 :     if (lookup_mask & PNAT_PROTO)
      61          12 :         m.as_u64[1] |= 0xff00000000000000;
      62          33 :     if (lookup_mask & PNAT_SPORT)
      63           0 :         m.as_u64[1] |= 0x00000000ffff0000;
      64          33 :     if (lookup_mask & PNAT_DPORT)
      65          18 :         m.as_u64[1] |= 0x000000000000ffff;
      66          33 :     return m;
      67             : }
      68             : 
      69             : /*
      70             :  * Create new PNAT interface object and register the pnat feature in the
      71             :  * corresponding feature chain.
      72             :  * Also enable shallow virtual reassembly, to ensure that we have
      73             :  * L4 ports available for all packets we receive.
      74             :  */
      75          11 : static clib_error_t *pnat_enable_interface(u32 sw_if_index,
      76             :                                            pnat_attachment_point_t attachment,
      77             :                                            pnat_mask_t mask) {
      78          11 :     pnat_main_t *pm = &pnat_main;
      79          11 :     pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
      80             : 
      81          11 :     if (!interface) {
      82          11 :         pool_get_zero(pm->interfaces, interface);
      83          11 :         interface->sw_if_index = sw_if_index;
      84          14 :         vec_validate_init_empty(pm->interface_by_sw_if_index, sw_if_index, ~0);
      85          11 :         pm->interface_by_sw_if_index[sw_if_index] = interface - pm->interfaces;
      86             :     }
      87             : 
      88             :     char *nodename;
      89             :     char *arcname;
      90          11 :     bool input = false;
      91          11 :     switch (attachment) {
      92           7 :     case PNAT_IP4_INPUT:
      93           7 :         nodename = "pnat-input";
      94           7 :         arcname = "ip4-unicast";
      95           7 :         input = true;
      96           7 :         break;
      97             : 
      98           4 :     case PNAT_IP4_OUTPUT:
      99           4 :         nodename = "pnat-output";
     100           4 :         arcname = "ip4-output";
     101           4 :         break;
     102           0 :     default:
     103           0 :         return clib_error_return(0, "Unknown attachment point %u %u",
     104             :                                  sw_if_index, attachment);
     105             :     }
     106             : 
     107          11 :     if (!interface->enabled[attachment]) {
     108          11 :         if (vnet_feature_enable_disable(arcname, nodename, sw_if_index, 1, 0,
     109             :                                         0) != 0)
     110           0 :             return clib_error_return(0, "PNAT feature enable failed on %u",
     111             :                                      sw_if_index);
     112             : 
     113          11 :         if (input) {
     114             :             /* TODO: Make shallow virtual reassembly configurable */
     115           7 :             if (ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 1) != 0)
     116           0 :                 return clib_error_return(0, "PNAT SVR enable failed on %u",
     117             :                                          sw_if_index);
     118             : 
     119             :         } else {
     120           4 :             if (ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index,
     121             :                                                                1) != 0)
     122           0 :                 return clib_error_return(0, "PNAT SVR enable failed on %u",
     123             :                                          sw_if_index);
     124             :         }
     125             : 
     126          11 :         interface->lookup_mask[attachment] = mask;
     127          11 :         interface->lookup_mask_fast[attachment] = pnat_mask2fast(mask);
     128          11 :         interface->enabled[attachment] = true;
     129             : 
     130             :     } else {
     131           0 :         pnat_mask_t current_mask = interface->lookup_mask[attachment];
     132           0 :         if (current_mask != mask) {
     133           0 :             return clib_error_return(0,
     134             :                                      "PNAT lookup mask must be consistent per "
     135             :                                      "interface/direction %u",
     136             :                                      sw_if_index);
     137             :         }
     138             :     }
     139             : 
     140          11 :     interface->refcount++;
     141             : 
     142          11 :     return 0;
     143             : }
     144             : 
     145             : /*
     146             :  * Delete interface object when no rules reference the interface.
     147             :  */
     148          11 : static int pnat_disable_interface(u32 sw_if_index,
     149             :                                   pnat_attachment_point_t attachment) {
     150          11 :     pnat_main_t *pm = &pnat_main;
     151          11 :     pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
     152             : 
     153          11 :     if (!interface)
     154           0 :         return 0;
     155          11 :     if (interface->refcount == 0)
     156           0 :         return 0;
     157             : 
     158          11 :     if (interface->enabled[attachment] && attachment == PNAT_IP4_INPUT) {
     159           7 :         if (ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 0) != 0)
     160           0 :             return -1;
     161           7 :         if (vnet_feature_enable_disable("ip4-unicast", "pnat-input",
     162             :                                         sw_if_index, 0, 0, 0) != 0)
     163           0 :             return -1;
     164             :     }
     165          11 :     if (interface->enabled[attachment] && attachment == PNAT_IP4_OUTPUT) {
     166           4 :         if (ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index, 0) != 0)
     167           0 :             return -1;
     168           4 :         if (vnet_feature_enable_disable("ip4-output", "pnat-output",
     169             :                                         sw_if_index, 0, 0, 0) != 0)
     170           0 :             return -1;
     171             :     }
     172             : 
     173          11 :     interface->lookup_mask[attachment] = 0;
     174          11 :     interface->enabled[attachment] = false;
     175             : 
     176          11 :     interface->refcount--;
     177          11 :     if (interface->refcount == 0) {
     178          11 :         pm->interface_by_sw_if_index[sw_if_index] = ~0;
     179          11 :         pool_put(pm->interfaces, interface);
     180             :     }
     181          11 :     return 0;
     182             : }
     183             : 
     184             : /*
     185             :  * From a 5-tuple (with mask) calculate the key used in the flow cache lookup.
     186             :  */
     187          22 : static inline void pnat_calc_key_from_5tuple(u32 sw_if_index,
     188             :                                              pnat_attachment_point_t attachment,
     189             :                                              pnat_match_tuple_t *match,
     190             :                                              clib_bihash_kv_16_8_t *kv) {
     191          22 :     pnat_mask_fast_t mask = pnat_mask2fast(match->mask);
     192             :     ip4_address_t src, dst;
     193          22 :     clib_memcpy(&src, &match->src, 4);
     194          22 :     clib_memcpy(&dst, &match->dst, 4);
     195          22 :     pnat_calc_key(sw_if_index, attachment, src, dst, match->proto,
     196          22 :                   htons(match->sport), htons(match->dport), mask, kv);
     197          22 : }
     198             : 
     199             : /*
     200             :  * Map between the 5-tuple mask and the instruction set of the rewrite node.
     201             :  */
     202          11 : pnat_instructions_t pnat_instructions_from_mask(pnat_mask_t m) {
     203          11 :     pnat_instructions_t i = 0;
     204             : 
     205          11 :     if (m & PNAT_SA)
     206           5 :         i |= PNAT_INSTR_SOURCE_ADDRESS;
     207          11 :     if (m & PNAT_DA)
     208           5 :         i |= PNAT_INSTR_DESTINATION_ADDRESS;
     209          11 :     if (m & PNAT_SPORT)
     210           0 :         i |= PNAT_INSTR_SOURCE_PORT;
     211          11 :     if (m & PNAT_DPORT)
     212           2 :         i |= PNAT_INSTR_DESTINATION_PORT;
     213          11 :     if (m & PNAT_COPY_BYTE)
     214           0 :         i |= PNAT_INSTR_COPY_BYTE;
     215          11 :     if (m & PNAT_CLEAR_BYTE)
     216           0 :         i |= PNAT_INSTR_CLEAR_BYTE;
     217          11 :     return i;
     218             : }
     219             : 
     220             : /*
     221             :  * "Init" the PNAT datastructures. Called upon first creation of a PNAT rule.
     222             :  * TODO: Make number of buckets configurable.
     223             :  */
     224          11 : static void pnat_enable(void) {
     225          11 :     pnat_main_t *pm = &pnat_main;
     226          11 :     if (pm->enabled)
     227          10 :         return;
     228             : 
     229             :     /* Create new flow cache table */
     230           1 :     clib_bihash_init_16_8(&pm->flowhash, "PNAT flow hash",
     231             :                           PNAT_FLOW_HASH_BUCKETS, 0);
     232             : 
     233           1 :     pm->enabled = true;
     234             : }
     235          11 : static void pnat_disable(void) {
     236          11 :     pnat_main_t *pm = &pnat_main;
     237             : 
     238          11 :     if (!pm->enabled)
     239           0 :         return;
     240          11 :     if (pool_elts(pm->translations))
     241          11 :         return;
     242             : 
     243             :     /* Delete flow cache table */
     244           0 :     clib_bihash_free_16_8(&pm->flowhash);
     245             : 
     246           0 :     pm->enabled = false;
     247             : }
     248             : 
     249             : /*
     250             :  * Ensure that a new rule lookup mask matches what's installed on interface
     251             :  */
     252          11 : static int pnat_interface_check_mask(u32 sw_if_index,
     253             :                                      pnat_attachment_point_t attachment,
     254             :                                      pnat_mask_t mask) {
     255          11 :     pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index);
     256          11 :     if (!interface)
     257          11 :         return 0;
     258           0 :     if (!interface->enabled[attachment])
     259           0 :         return 0;
     260           0 :     if (interface->lookup_mask[attachment] != mask)
     261           0 :         return -1;
     262             : 
     263           0 :     return 0;
     264             : }
     265             : 
     266             : /*
     267             :  * Add a binding to the binding table.
     268             :  * Returns 0 on success.
     269             :  *  -1: Invalid mask
     270             :  *  -2: Only matches ports for UDP or TCP
     271             :  *
     272             :  */
     273          11 : int pnat_binding_add(pnat_match_tuple_t *match, pnat_rewrite_tuple_t *rewrite,
     274             :                      u32 *index) {
     275          11 :     pnat_main_t *pm = &pnat_main;
     276             : 
     277          11 :     *index = -1;
     278             : 
     279             :     /* If we aren't matching or rewriting, why are we here? */
     280          11 :     if (match->mask == 0 || rewrite->mask == 0)
     281           0 :         return -1;
     282             : 
     283             :     /* Check if protocol is set if ports are set */
     284          11 :     if ((match->dport || match->sport) &&
     285           6 :         (match->proto != IP_API_PROTO_UDP && match->proto != IP_API_PROTO_TCP))
     286           0 :         return -2;
     287             : 
     288             :     /* Create pool entry */
     289             :     pnat_translation_t *t;
     290          11 :     pool_get_zero(pm->translations, t);
     291          11 :     memcpy(&t->post_da, &rewrite->dst, 4);
     292          11 :     memcpy(&t->post_sa, &rewrite->src, 4);
     293          11 :     t->post_sp = rewrite->sport;
     294          11 :     t->post_dp = rewrite->dport;
     295          11 :     t->from_offset = rewrite->from_offset;
     296          11 :     t->to_offset = rewrite->to_offset;
     297          11 :     t->clear_offset = rewrite->clear_offset;
     298          11 :     t->instructions = pnat_instructions_from_mask(rewrite->mask);
     299             : 
     300             :     /* These are only used for show commands and trace */
     301          11 :     t->match = *match;
     302          11 :     t->rewrite = *rewrite;
     303             : 
     304          11 :     *index = t - pm->translations;
     305             : 
     306          11 :     return 0;
     307             : }
     308             : 
     309             : /*
     310             :  * Looks a match flow in the flow cache, returns the index  in the binding table
     311             :  */
     312           0 : u32 pnat_flow_lookup(u32 sw_if_index, pnat_attachment_point_t attachment,
     313             :                      pnat_match_tuple_t *match) {
     314           0 :     pnat_main_t *pm = &pnat_main;
     315             :     clib_bihash_kv_16_8_t kv, value;
     316           0 :     pnat_calc_key_from_5tuple(sw_if_index, attachment, match, &kv);
     317           0 :     if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) {
     318           0 :         return value.value;
     319             :     }
     320           0 :     return ~0;
     321             : }
     322             : 
     323             : /*
     324             :  * Attach a binding to an interface / direction.
     325             :  * Returns 0 on success.
     326             :  * -1: Binding does not exist
     327             :  * -2: Interface mask does not match
     328             :  * -3: Existing match entry in flow table
     329             :  * -4: Adding flow table entry failed
     330             :  */
     331          11 : int pnat_binding_attach(u32 sw_if_index, pnat_attachment_point_t attachment,
     332             :                         u32 binding_index) {
     333          11 :     pnat_main_t *pm = &pnat_main;
     334             : 
     335          22 :     if (!pm->translations ||
     336          11 :         pool_is_free_index(pm->translations, binding_index))
     337           0 :         return -1;
     338             : 
     339          11 :     pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index);
     340             : 
     341          11 :     if (pnat_interface_check_mask(sw_if_index, attachment, t->match.mask) != 0)
     342           0 :         return -2;
     343             : 
     344          11 :     pnat_enable();
     345             : 
     346             :     /* Verify non-duplicate */
     347             :     clib_bihash_kv_16_8_t kv, value;
     348          11 :     pnat_calc_key_from_5tuple(sw_if_index, attachment, &t->match, &kv);
     349          11 :     if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) {
     350           0 :         return -3;
     351             :     }
     352             : 
     353             :     /* Create flow cache */
     354          11 :     kv.value = binding_index;
     355          11 :     if (clib_bihash_add_del_16_8(&pm->flowhash, &kv, 1)) {
     356           0 :         pool_put(pm->translations, t);
     357           0 :         return -4;
     358             :     }
     359             : 
     360             :     /* Register interface */
     361          11 :     pnat_enable_interface(sw_if_index, attachment, t->match.mask);
     362             : 
     363          11 :     return 0;
     364             : }
     365             : 
     366          11 : int pnat_binding_detach(u32 sw_if_index, pnat_attachment_point_t attachment,
     367             :                         u32 binding_index) {
     368          11 :     pnat_main_t *pm = &pnat_main;
     369             : 
     370          22 :     if (!pm->translations ||
     371          11 :         pool_is_free_index(pm->translations, binding_index))
     372           0 :         return -1;
     373             : 
     374          11 :     pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index);
     375             : 
     376             :     /* Verify non-duplicate */
     377             :     clib_bihash_kv_16_8_t kv;
     378          11 :     pnat_calc_key_from_5tuple(sw_if_index, attachment, &t->match, &kv);
     379          11 :     if (clib_bihash_add_del_16_8(&pm->flowhash, &kv, 0)) {
     380           0 :         return -2;
     381             :     }
     382             : 
     383             :     /* Deregister interface */
     384          11 :     pnat_disable_interface(sw_if_index, attachment);
     385             : 
     386          11 :     pnat_disable();
     387             : 
     388          11 :     return 0;
     389             : }
     390             : 
     391             : /*
     392             :  * Delete a translation using the index returned from pnat_add_translation.
     393             :  */
     394          11 : int pnat_binding_del(u32 index) {
     395          11 :     pnat_main_t *pm = &pnat_main;
     396             : 
     397          11 :     if (pool_is_free_index(pm->translations, index)) {
     398           0 :         clib_warning("Binding delete: translation does not exist: %d", index);
     399           0 :         return -1;
     400             :     }
     401             : 
     402          11 :     pnat_translation_t *t = pool_elt_at_index(pm->translations, index);
     403          11 :     pool_put(pm->translations, t);
     404             : 
     405          11 :     return 0;
     406             : }

Generated by: LCOV version 1.14