diff options
author | Ole Troan <ot@cisco.com> | 2021-01-12 21:49:38 +0100 |
---|---|---|
committer | Neale Ranns <neale@graphiant.com> | 2021-02-05 13:27:48 +0000 |
commit | 18327be5d458f9f73c12d76e677ee5a068ec6b10 (patch) | |
tree | bac6dbc08280e5bd6d5749ea56c862e6cdc38434 /src/plugins/nat/pnat/pnat.c | |
parent | 490b92738f3cc1c8d534abd6dee8dba942cb652d (diff) |
nat: 1:1 policy NAT
A NAT sub-plugin doing statically configured match/rewrite on IP4 input or output.
It's stateless (no connection tracking).
Currently it supports rewriting of SA, DA and TCP/UDP ports.
It should be simple to add new rewrites if required.
API:
pnat_binding_add, pnat_binding_del, pnat_bindings_get, pnat_interfaces_get
CLI:
set pnat translation interface <name> match <5-tuple> rewrite <5-tuple> {in|out} [del]
show pnat translations
show pnat interfaces
Trying a new C based unit testing scheme. Where the graph node is tested
in isolation. See pnat/pnat_test.c.
Also added new cmake targets to generate coverage directly.
E.g.:
make test_pnat-ccov-report
File '/vpp/sdnat/src/plugins/nat/pnat/pnat.c':
Name Regions Miss Cover Lines Miss Cover
------------------------------------------------------------------------------------
pnat_interface_by_sw_if_index 39 8 79.49% 13 0 100.00%
pnat_instructions_from_mask 9 0 100.00% 13 0 100.00%
pnat_binding_add 64 8 87.50% 31 2 93.55%
pnat_flow_lookup 4 4 0.00% 10 10 0.00%
pnat_binding_attach 104 75 27.88% 33 6 81.82%
pnat_binding_detach 30 5 83.33% 23 2 91.30%
pnat_binding_del 97 33 65.98% 17 3 82.35%
pnat.c:pnat_calc_key_from_5tuple 9 1 88.89% 14 1 92.86%
pnat.c:pnat_interface_check_mask 10 2 80.00% 11 2 81.82%
pnat.c:pnat_enable 5 0 100.00% 11 0 100.00%
pnat.c:pnat_enable_interface 107 26 75.70% 60 15 75.00%
pnat.c:pnat_disable_interface 91 30 67.03% 32 7 78.12%
pnat.c:pnat_disable 7 2 71.43% 13 7 46.15%
------------------------------------------------------------------------------------
TOTAL 576 194 66.32% 281 55 80.43%
File '/vpp/sdnat/src/plugins/nat/pnat/pnat_node.h':
Name Regions Miss Cover Lines Miss Cover
------------------------------------------------------------------------------------
pnat_test.c:pnat_node_inline 67 11 83.58% 115 1 99.13%
pnat_test.c:pnat_calc_key 9 2 77.78% 14 2 85.71%
pnat_test.c:pnat_rewrite_ip4 55 11 80.00% 60 12 80.00%
pnat_test.c:format_pnat_trace 1 1 0.00% 12 12 0.00%
pnat_node.c:pnat_node_inline 63 63 0.00% 115 115 0.00%
pnat_node.c:pnat_calc_key 9 9 0.00% 14 14 0.00%
pnat_node.c:pnat_rewrite_ip4 55 55 0.00% 60 60 0.00%
pnat_node.c:format_pnat_trace 5 5 0.00% 12 12 0.00%
------------------------------------------------------------------------------------
TOTAL 264 157 40.53% 402 228 43.28%
Type: feature
Change-Id: I9c897f833603054a8303e7369ebff6512517c9e0
Signed-off-by: Ole Troan <ot@cisco.com>
Diffstat (limited to 'src/plugins/nat/pnat/pnat.c')
-rw-r--r-- | src/plugins/nat/pnat/pnat.c | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/src/plugins/nat/pnat/pnat.c b/src/plugins/nat/pnat/pnat.c new file mode 100644 index 00000000000..335d2750ce7 --- /dev/null +++ b/src/plugins/nat/pnat/pnat.c @@ -0,0 +1,372 @@ +/* + * Copyright (c) 2021 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pnat.h" +#include <arpa/inet.h> +#include <stdbool.h> +#include <vlib/vlib.h> +#include <vnet/feature/feature.h> +#include <vnet/fib/fib_table.h> +#include <vnet/ip/format.h> +#include <vnet/ip/ip4.h> +#include <vnet/ip/ip4_packet.h> +#include <vnet/ip/reass/ip4_sv_reass.h> +#include <vppinfra/clib_error.h> + +/* + * This is the main control plane part of the PNAT (Policy 1:1 NAT) feature. + */ + +pnat_main_t pnat_main; + +/* + * Do a lookup in the interface vector (interface_by_sw_if_index) + * and return pool entry. + */ +pnat_interface_t *pnat_interface_by_sw_if_index(u32 sw_if_index) { + pnat_main_t *pm = &pnat_main; + + if (!pm->interface_by_sw_if_index || + sw_if_index > (vec_len(pm->interface_by_sw_if_index) - 1)) + return 0; + u32 index = pm->interface_by_sw_if_index[sw_if_index]; + if (index == ~0) + return 0; + if (pool_is_free_index(pm->interfaces, index)) + return 0; + return pool_elt_at_index(pm->interfaces, index); +} + +static pnat_mask_fast_t pnat_mask2fast(pnat_mask_t lookup_mask) { + pnat_mask_fast_t m = {0}; + + if (lookup_mask & PNAT_SA) + m.as_u64[0] = 0xffffffff00000000; + if (lookup_mask & PNAT_DA) + m.as_u64[0] |= 0x00000000ffffffff; + m.as_u64[1] = 0xffffffff00000000; + if (lookup_mask & PNAT_SPORT) + m.as_u64[1] |= 0x00000000ffff0000; + if (lookup_mask & PNAT_DPORT) + m.as_u64[1] |= 0x000000000000ffff; + return m; +} + +/* + * Create new PNAT interface object and register the pnat feature in the + * corresponding feature chain. + * Also enable shallow virtual reassembly, to ensure that we have + * L4 ports available for all packets we receive. + */ +static clib_error_t *pnat_enable_interface(u32 sw_if_index, + pnat_attachment_point_t attachment, + pnat_mask_t mask) { + pnat_main_t *pm = &pnat_main; + pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index); + + if (!interface) { + pool_get_zero(pm->interfaces, interface); + interface->sw_if_index = sw_if_index; + vec_validate_init_empty(pm->interface_by_sw_if_index, sw_if_index, ~0); + pm->interface_by_sw_if_index[sw_if_index] = interface - pm->interfaces; + } + + char *nodename; + char *arcname; + bool input = false; + switch (attachment) { + case PNAT_IP4_INPUT: + nodename = "pnat-input"; + arcname = "ip4-unicast"; + input = true; + break; + + case PNAT_IP4_OUTPUT: + nodename = "pnat-output"; + arcname = "ip4-output"; + break; + default: + return clib_error_return(0, "Unknown attachment point %u %u", + sw_if_index, attachment); + } + + if (!interface->enabled[attachment]) { + if (vnet_feature_enable_disable(arcname, nodename, sw_if_index, 1, 0, + 0) != 0) + return clib_error_return(0, "PNAT feature enable failed on %u", + sw_if_index); + + if (input) { + /* TODO: Make shallow virtual reassembly configurable */ + ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 1); + } else { + ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index, 1); + } + + interface->lookup_mask[attachment] = mask; + interface->lookup_mask_fast[attachment] = pnat_mask2fast(mask); + interface->enabled[attachment] = true; + + } else { + pnat_mask_t current_mask = interface->lookup_mask[attachment]; + if (current_mask != mask) { + return clib_error_return(0, + "PNAT lookup mask must be consistent per " + "interface/direction %u", + sw_if_index); + } + } + + interface->refcount++; + + return 0; +} + +/* + * Delete interface object when no rules reference the interface. + */ +static int pnat_disable_interface(u32 sw_if_index, + pnat_attachment_point_t attachment) { + pnat_main_t *pm = &pnat_main; + pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index); + + if (!interface) + return 0; + if (interface->refcount == 0) + return 0; + + if (interface->enabled[attachment] && attachment == PNAT_IP4_INPUT) { + ip4_sv_reass_enable_disable_with_refcnt(sw_if_index, 0); + if (vnet_feature_enable_disable("ip4-unicast", "pnat-input", + sw_if_index, 0, 0, 0) != 0) + return -1; + } + if (interface->enabled[attachment] && attachment == PNAT_IP4_OUTPUT) { + ip4_sv_reass_output_enable_disable_with_refcnt(sw_if_index, 0); + if (vnet_feature_enable_disable("ip4-output", "pnat-output", + sw_if_index, 0, 0, 0) != 0) + return -1; + } + + interface->lookup_mask[attachment] = 0; + interface->enabled[attachment] = false; + + interface->refcount--; + if (interface->refcount == 0) { + pm->interface_by_sw_if_index[sw_if_index] = ~0; + pool_put(pm->interfaces, interface); + } + return 0; +} + +/* + * From a 5-tuple (with mask) calculate the key used in the flow cache lookup. + */ +static inline void pnat_calc_key_from_5tuple(u32 sw_if_index, + pnat_attachment_point_t attachment, + pnat_5tuple_t *match, + clib_bihash_kv_16_8_t *kv) { + pnat_mask_fast_t mask = pnat_mask2fast(match->mask); + ip4_address_t src, dst; + clib_memcpy(&src, &match->src, 4); + clib_memcpy(&dst, &match->dst, 4); + pnat_calc_key(sw_if_index, attachment, src, dst, match->proto, + htons(match->sport), htons(match->dport), mask, kv); +} + +/* + * Map between the 5-tuple mask and the instruction set of the rewrite node. + */ +pnat_instructions_t pnat_instructions_from_mask(pnat_mask_t m) { + pnat_instructions_t i = 0; + + if (m & PNAT_SA) + i |= PNAT_INSTR_SOURCE_ADDRESS; + if (m & PNAT_DA) + i |= PNAT_INSTR_DESTINATION_ADDRESS; + if (m & PNAT_SPORT) + i |= PNAT_INSTR_SOURCE_PORT; + if (m & PNAT_DPORT) + i |= PNAT_INSTR_DESTINATION_PORT; + return i; +} + +/* + * "Init" the PNAT datastructures. Called upon first creation of a PNAT rule. + * TODO: Make number of buckets configurable. + */ +static void pnat_enable(void) { + pnat_main_t *pm = &pnat_main; + if (pm->enabled) + return; + + /* Create new flow cache table */ + clib_bihash_init_16_8(&pm->flowhash, "PNAT flow hash", + PNAT_FLOW_HASH_BUCKETS, 0); + + pm->enabled = true; +} +static void pnat_disable(void) { + pnat_main_t *pm = &pnat_main; + + if (!pm->enabled) + return; + if (pool_elts(pm->translations)) + return; + + /* Delete flow cache table */ + clib_bihash_free_16_8(&pm->flowhash); + + pm->enabled = false; +} + +/* + * Ensure that a new rule lookup mask matches what's installed on interface + */ +static int pnat_interface_check_mask(u32 sw_if_index, + pnat_attachment_point_t attachment, + pnat_mask_t mask) { + pnat_interface_t *interface = pnat_interface_by_sw_if_index(sw_if_index); + if (!interface) + return 0; + if (!interface->enabled[attachment]) + return 0; + if (interface->lookup_mask[attachment] != mask) + return -1; + + return 0; +} + +int pnat_binding_add(pnat_5tuple_t *match, pnat_5tuple_t *rewrite, u32 *index) { + pnat_main_t *pm = &pnat_main; + + *index = -1; + + /* If we aren't matching or rewriting, why are we here? */ + if (match->mask == 0 || rewrite->mask == 0) + return -1; + + /* Check if protocol is set if ports are set */ + if ((match->dport || match->sport) && + (match->proto != IP_API_PROTO_UDP && match->proto != IP_API_PROTO_TCP)) + return -2; + + /* Create pool entry */ + pnat_translation_t *t; + pool_get_zero(pm->translations, t); + memcpy(&t->post_da, &rewrite->dst, 4); + memcpy(&t->post_sa, &rewrite->src, 4); + t->post_sp = rewrite->sport; + t->post_dp = rewrite->dport; + t->instructions = pnat_instructions_from_mask(rewrite->mask); + + /* These are only used for show commands and trace */ + t->match = *match; + + /* Rewrite of protocol is not supported, ignore. */ + t->rewrite = *rewrite; + t->rewrite.proto = 0; + + *index = t - pm->translations; + + return 0; +} +u32 pnat_flow_lookup(u32 sw_if_index, pnat_attachment_point_t attachment, + pnat_5tuple_t *match) { + pnat_main_t *pm = &pnat_main; + clib_bihash_kv_16_8_t kv, value; + pnat_calc_key_from_5tuple(sw_if_index, attachment, match, &kv); + if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) { + return value.value; + } + return ~0; +} + +int pnat_binding_attach(u32 sw_if_index, pnat_attachment_point_t attachment, + u32 binding_index) { + pnat_main_t *pm = &pnat_main; + + if (!pm->translations || + pool_is_free_index(pm->translations, binding_index)) + return -1; + + pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index); + + if (pnat_interface_check_mask(sw_if_index, attachment, t->match.mask) != 0) + return -2; + + pnat_enable(); + + /* Verify non-duplicate */ + clib_bihash_kv_16_8_t kv, value; + pnat_calc_key_from_5tuple(sw_if_index, attachment, &t->match, &kv); + if (clib_bihash_search_16_8(&pm->flowhash, &kv, &value) == 0) { + return -3; + } + + /* Create flow cache */ + kv.value = binding_index; + if (clib_bihash_add_del_16_8(&pm->flowhash, &kv, 1)) { + pool_put(pm->translations, t); + return -4; + } + + /* Register interface */ + pnat_enable_interface(sw_if_index, attachment, t->match.mask); + + return 0; +} + +int pnat_binding_detach(u32 sw_if_index, pnat_attachment_point_t attachment, + u32 binding_index) { + pnat_main_t *pm = &pnat_main; + + if (!pm->translations || + pool_is_free_index(pm->translations, binding_index)) + return -1; + + pnat_translation_t *t = pool_elt_at_index(pm->translations, binding_index); + + /* Verify non-duplicate */ + clib_bihash_kv_16_8_t kv; + pnat_calc_key_from_5tuple(sw_if_index, attachment, &t->match, &kv); + if (clib_bihash_add_del_16_8(&pm->flowhash, &kv, 0)) { + return -2; + } + + /* Deregister interface */ + pnat_disable_interface(sw_if_index, attachment); + + pnat_disable(); + + return 0; +} + +/* + * Delete a translation using the index returned from pnat_add_translation. + */ +int pnat_binding_del(u32 index) { + pnat_main_t *pm = &pnat_main; + + if (pool_is_free_index(pm->translations, index)) { + clib_warning("Binding delete: translation does not exist: %d", index); + return -1; + } + + pnat_translation_t *t = pool_elt_at_index(pm->translations, index); + pool_put(pm->translations, t); + + return 0; +} |