From cc9a1a0d39f22f653801f5d08bfe4892325254b5 Mon Sep 17 00:00:00 2001 From: Nathan Skrzypczak Date: Thu, 25 Feb 2021 12:06:11 +0100 Subject: [PATCH] cnat: add input feature node This allows to configure nat on a per-interface basis. Special care must be taken to ensure the configuration remains consistent. Type: feature Change-Id: I352b2dce182e09d30813ce958333bb1ff37d9b4e Signed-off-by: Aloys Augustin Signed-off-by: Nathan Skrzypczak --- src/plugins/cnat/CMakeLists.txt | 1 + src/plugins/cnat/cnat.api | 11 + src/plugins/cnat/cnat_api.c | 17 ++ src/plugins/cnat/cnat_node_feature.c | 409 +++++++++++++++++++++++++++++++++++ src/plugins/cnat/cnat_snat.c | 53 +++++ src/plugins/cnat/cnat_snat.h | 16 ++ 6 files changed, 507 insertions(+) create mode 100644 src/plugins/cnat/cnat_node_feature.c diff --git a/src/plugins/cnat/CMakeLists.txt b/src/plugins/cnat/CMakeLists.txt index 95b59e97d10..6eb5968e703 100644 --- a/src/plugins/cnat/CMakeLists.txt +++ b/src/plugins/cnat/CMakeLists.txt @@ -17,6 +17,7 @@ add_vpp_plugin(cnat cnat_client.c cnat_node_snat.c cnat_node_vip.c + cnat_node_feature.c cnat_scanner.c cnat_session.c cnat_translation.c diff --git a/src/plugins/cnat/cnat.api b/src/plugins/cnat/cnat.api index 2b79e0d1b8b..a5df8b1e578 100644 --- a/src/plugins/cnat/cnat.api +++ b/src/plugins/cnat/cnat.api @@ -167,6 +167,17 @@ autoreply define cnat_add_del_snat_prefix vl_api_prefix_t prefix; }; +enum cnat_snat_policies:u32 +{ + CNAT_SNAT_POLICY_NONE = 1, +}; + +autoreply define cnat_set_snat_policy +{ + u32 client_index; + u32 context; + vl_api_cnat_snat_policies_t policy; +}; /* * fd.io coding-style-patch-verification: ON * diff --git a/src/plugins/cnat/cnat_api.c b/src/plugins/cnat/cnat_api.c index 99d9c729282..652e5c09d1a 100644 --- a/src/plugins/cnat/cnat_api.c +++ b/src/plugins/cnat/cnat_api.c @@ -337,6 +337,23 @@ static void REPLY_MACRO (VL_API_CNAT_ADD_DEL_SNAT_PREFIX_REPLY); } +static void +vl_api_cnat_set_snat_policy_t_handler (vl_api_cnat_set_snat_policy_t *mp) +{ + vl_api_cnat_set_snat_policy_reply_t *rmp; + int rv = 0; + vl_api_cnat_snat_policies_t policy = clib_net_to_host_u32 (mp->policy); + switch (policy) + { + case CNAT_SNAT_POLICY_NONE: + cnat_set_snat_policy (NULL); + break; + default: + rv = 1; + } + + REPLY_MACRO (VL_API_CNAT_SET_SNAT_POLICY_REPLY); +} #include static clib_error_t * diff --git a/src/plugins/cnat/cnat_node_feature.c b/src/plugins/cnat/cnat_node_feature.c new file mode 100644 index 00000000000..10293de5069 --- /dev/null +++ b/src/plugins/cnat/cnat_node_feature.c @@ -0,0 +1,409 @@ +/* + * Copyright (c) 2020 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 +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +typedef enum cnat_feature_next_ +{ + CNAT_FEATURE_NEXT_DROP, + CNAT_FEATURE_N_NEXT, +} cnat_feature_next_t; + +vlib_node_registration_t cnat_input_feature_ip4_node; +vlib_node_registration_t cnat_input_feature_ip6_node; +vlib_node_registration_t cnat_output_feature_ip4_node; +vlib_node_registration_t cnat_output_feature_ip6_node; + +always_inline uword +cnat_input_feature_fn (vlib_main_t *vm, vlib_node_runtime_t *node, + vlib_buffer_t *b, cnat_node_ctx_t *ctx, + int session_not_found, cnat_session_t *session) +{ + vlib_combined_counter_main_t *cntm = &cnat_translation_counters; + const cnat_translation_t *ct = NULL; + ip4_header_t *ip4 = NULL; + ip_protocol_t iproto; + ip6_header_t *ip6 = NULL; + udp_header_t *udp0; + cnat_client_t *cc; + u32 next0; + index_t cti; + u8 trace_flags = 0; + + /* By default follow arc default next */ + vnet_feature_next (&next0, b); + + if (AF_IP4 == ctx->af) + { + ip4 = vlib_buffer_get_current (b); + iproto = ip4->protocol; + udp0 = (udp_header_t *) (ip4 + 1); + cc = cnat_client_ip4_find ( + &ip4->dst_address); /* TODO do this only if no session? */ + } + else + { + ip6 = vlib_buffer_get_current (b); + iproto = ip6->protocol; + udp0 = (udp_header_t *) (ip6 + 1); + cc = cnat_client_ip6_find (&ip6->dst_address); /* TODO: same as above */ + } + + if (session->key.cs_proto == 0) + goto trace; + + if (!session_not_found) + /* session table hit */ + cnat_timestamp_update (session->value.cs_ts_index, ctx->now); + else if (!cc) + goto trace; /* dst address is not a vip */ + else + { + ct = cnat_find_translation ( + cc->parent_cci, clib_host_to_net_u16 (udp0->dst_port), iproto); + if (NULL == ct) + /* Dont translate */ + /* TODO: create identity session to avoid slowpath ? */ + goto trace; + + /* New flow, create the sessions */ + const load_balance_t *lb0; + cnat_ep_trk_t *trk0; + u32 rsession_flags = CNAT_SESSION_FLAG_NO_CLIENT; + u32 dpoi_index = -1; + + lb0 = load_balance_get (ct->ct_lb.dpoi_index); + if (!lb0->lb_n_buckets) + /* Can't translate TODO: should drop / reject? */ + goto trace; + + /* session table miss */ + trk0 = cnat_load_balance (ct, ctx->af, ip4, ip6, &dpoi_index); + if (PREDICT_FALSE (NULL == trk0)) + { + /* Dont translate & Follow the fib programming */ + vnet_buffer (b)->ip.adj_index[VLIB_TX] = cc->cc_parent.dpoi_index; + next0 = cc->cc_parent.dpoi_next_node; + goto trace; + } + + ip46_address_copy (&session->value.cs_ip[VLIB_TX], + &trk0->ct_ep[VLIB_TX].ce_ip.ip); + + /* never source nat in this node */ + if (AF_IP4 == ctx->af) + ip46_address_set_ip4 (&session->value.cs_ip[VLIB_RX], + &ip4->src_address); + else + ip46_address_set_ip6 (&session->value.cs_ip[VLIB_RX], + &ip6->src_address); + + session->value.cs_port[VLIB_TX] = + clib_host_to_net_u16 (trk0->ct_ep[VLIB_TX].ce_port); + session->value.cs_port[VLIB_RX] = udp0->src_port; + + const dpo_id_t *dpo0; + const load_balance_t *lb1; + fib_entry_t *fib_entry; + fib_entry = fib_entry_get (trk0->ct_fei); + + lb1 = load_balance_get (fib_entry->fe_lb /*[fct] */.dpoi_index); + dpo0 = load_balance_get_bucket_i (lb1, 0); + + session->value.dpoi_next_node = dpo0->dpoi_next_node; + session->value.cs_lbi = dpo0->dpoi_index; + + if (trk0->ct_flags & CNAT_TRK_FLAG_NO_NAT) + session->value.flags |= CNAT_SESSION_FLAG_NO_NAT; + + /* refcnt session in current client */ + cnat_client_cnt_session (cc); + cnat_session_create (session, ctx, CNAT_LOCATION_OUTPUT, rsession_flags); + trace_flags |= CNAT_TRACE_SESSION_CREATED; + } + + next0 = session->value.dpoi_next_node; + vnet_buffer (b)->ip.adj_index[VLIB_TX] = session->value.cs_lbi; + + if (session->value.flags & CNAT_SESSION_FLAG_NO_NAT) + goto trace; + + if (AF_IP4 == ctx->af) + cnat_translation_ip4 (session, ip4, udp0); + else + cnat_translation_ip6 (session, ip6, udp0); + + if (NULL != ct) + { + cti = ct - cnat_translation_pool; + vlib_increment_combined_counter (cntm, ctx->thread_index, cti, 1, + vlib_buffer_length_in_chain (vm, b)); + } + +trace: + if (PREDICT_FALSE (ctx->do_trace)) + { + trace_flags |= session_not_found ? 0 : CNAT_TRACE_SESSION_FOUND; + cnat_add_trace (vm, node, b, session, ct, trace_flags); + } + return next0; +} + +VLIB_NODE_FN (cnat_input_feature_ip4_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE))) + return cnat_node_inline (vm, node, frame, cnat_input_feature_fn, AF_IP4, + CNAT_LOCATION_INPUT, 1 /* do_trace */); + return cnat_node_inline (vm, node, frame, cnat_input_feature_fn, AF_IP4, + CNAT_LOCATION_INPUT, 0 /* do_trace */); +} + +VLIB_REGISTER_NODE (cnat_input_feature_ip4_node) = { + .name = "cnat-input-ip4", + .vector_size = sizeof (u32), + .format_trace = format_cnat_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = CNAT_N_ERROR, + .error_strings = cnat_error_strings, + .n_next_nodes = IP_LOOKUP_N_NEXT, + .next_nodes = IP4_LOOKUP_NEXT_NODES, +}; + +VNET_FEATURE_INIT (cnat_in_ip4_feature, static) = { + .arc_name = "ip4-unicast", + .node_name = "cnat-input-ip4", + .runs_before = VNET_FEATURES ("acl-plugin-in-ip4-fa"), +}; + +VLIB_NODE_FN (cnat_input_feature_ip6_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE))) + return cnat_node_inline (vm, node, frame, cnat_input_feature_fn, AF_IP6, + CNAT_LOCATION_INPUT, 1 /* do_trace */); + return cnat_node_inline (vm, node, frame, cnat_input_feature_fn, AF_IP6, + CNAT_LOCATION_INPUT, 0 /* do_trace */); +} + +VLIB_REGISTER_NODE (cnat_input_feature_ip6_node) = { + .name = "cnat-input-ip6", + .vector_size = sizeof (u32), + .format_trace = format_cnat_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = CNAT_N_ERROR, + .error_strings = cnat_error_strings, + .n_next_nodes = CNAT_FEATURE_N_NEXT, + .next_nodes = { + [CNAT_FEATURE_NEXT_DROP] = "error-drop", + }, +}; + +VNET_FEATURE_INIT (cnat_in_ip6_feature, static) = { + .arc_name = "ip6-unicast", + .node_name = "cnat-input-ip6", + .runs_before = VNET_FEATURES ("acl-plugin-in-ip6-fa"), +}; + +/* output feature node, creates snat sessions when required and + * translates back for existing sessions */ +always_inline uword +cnat_output_feature_fn (vlib_main_t *vm, vlib_node_runtime_t *node, + vlib_buffer_t *b, cnat_node_ctx_t *ctx, + int session_not_found, cnat_session_t *session) +{ + cnat_main_t *cm = &cnat_main; + cnat_snat_policy_main_t *cms = &cnat_snat_policy_main; + ip4_header_t *ip4 = NULL; + ip_protocol_t iproto; + ip6_header_t *ip6 = NULL; + udp_header_t *udp0; + u32 iph_offset = 0; + u32 next0; + u16 sport; + u8 do_snat = 0; + u8 trace_flags = 0; + int rv; + + /* By default follow arc default next */ + vnet_feature_next (&next0, b); + iph_offset = vnet_buffer (b)->ip.save_rewrite_length; + + if (AF_IP4 == ctx->af) + { + ip4 = (ip4_header_t *) ((u8 *) vlib_buffer_get_current (b) + iph_offset); + iproto = ip4->protocol; + udp0 = (udp_header_t *) (ip4 + 1); + } + else + { + ip6 = (ip6_header_t *) ((u8 *) vlib_buffer_get_current (b) + iph_offset); + iproto = ip6->protocol; + udp0 = (udp_header_t *) (ip6 + 1); + } + + if (session->key.cs_proto == 0) + goto trace; + + if (!session_not_found) + { + /* session table hit */ + cnat_timestamp_update (session->value.cs_ts_index, ctx->now); + } + else if (!cms->snat_policy) + goto trace; + else + { + /* TODO: handle errors? */ + cms->snat_policy (vm, b, session, ctx, &do_snat); + if (do_snat != 1) + goto trace; + + if (AF_IP4 == ctx->af) + { + if (ip_address_is_zero (&cm->snat_ip4.ce_ip)) + goto trace; + + ip46_address_set_ip4 (&session->value.cs_ip[VLIB_RX], + &ip_addr_v4 (&cm->snat_ip4.ce_ip)); + ip46_address_set_ip4 (&session->value.cs_ip[VLIB_TX], + &ip4->dst_address); + } + else + { + if (ip_address_is_zero (&cm->snat_ip6.ce_ip)) + goto trace; + + ip46_address_set_ip6 (&session->value.cs_ip[VLIB_RX], + &ip_addr_v6 (&cm->snat_ip6.ce_ip)); + ip46_address_set_ip6 (&session->value.cs_ip[VLIB_TX], + &ip6->dst_address); + } + sport = 0; + rv = cnat_allocate_port (&sport, iproto); + if (rv) + { + vlib_node_increment_counter (vm, cnat_output_feature_ip6_node.index, + CNAT_ERROR_EXHAUSTED_PORTS, 1); + next0 = CNAT_FEATURE_NEXT_DROP; + goto trace; + } + session->value.cs_port[VLIB_RX] = sport; + session->value.cs_port[VLIB_TX] = sport; + if (iproto == IP_PROTOCOL_TCP || iproto == IP_PROTOCOL_UDP) + session->value.cs_port[VLIB_TX] = udp0->dst_port; + + session->value.cs_lbi = INDEX_INVALID; + session->value.flags = + CNAT_SESSION_FLAG_NO_CLIENT | CNAT_SESSION_FLAG_ALLOC_PORT; + + trace_flags |= CNAT_TRACE_SESSION_CREATED; + cnat_session_create (session, ctx, CNAT_LOCATION_INPUT, + CNAT_SESSION_FLAG_NO_CLIENT); + } + + if (AF_IP4 == ctx->af) + cnat_translation_ip4 (session, ip4, udp0); + else + cnat_translation_ip6 (session, ip6, udp0); + +trace: + if (PREDICT_FALSE (ctx->do_trace)) + { + trace_flags |= do_snat ? 0 : CNAT_TRACE_NO_NAT; + trace_flags |= session_not_found ? 0 : CNAT_TRACE_SESSION_FOUND; + cnat_add_trace (vm, node, b, session, NULL, trace_flags); + } + return next0; +} + +VLIB_NODE_FN (cnat_output_feature_ip4_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE))) + return cnat_node_inline (vm, node, frame, cnat_output_feature_fn, AF_IP4, + CNAT_LOCATION_OUTPUT, 1 /* do_trace */); + return cnat_node_inline (vm, node, frame, cnat_output_feature_fn, AF_IP4, + CNAT_LOCATION_OUTPUT, 0 /* do_trace */); +} + +VLIB_REGISTER_NODE (cnat_output_feature_ip4_node) = { + .name = "cnat-output-ip4", + .vector_size = sizeof (u32), + .format_trace = format_cnat_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = CNAT_N_ERROR, + .error_strings = cnat_error_strings, + .n_next_nodes = CNAT_FEATURE_N_NEXT, + .next_nodes = { + [CNAT_FEATURE_NEXT_DROP] = "error-drop", + }, +}; + +VNET_FEATURE_INIT (cnat_out_ip4_feature, static) = { + .arc_name = "ip4-output", + .node_name = "cnat-output-ip4", + .runs_before = VNET_FEATURES ("gso-ip4"), + .runs_after = VNET_FEATURES ("acl-plugin-out-ip4-fa"), +}; + +VLIB_NODE_FN (cnat_output_feature_ip6_node) +(vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame) +{ + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE))) + return cnat_node_inline (vm, node, frame, cnat_output_feature_fn, AF_IP6, + CNAT_LOCATION_OUTPUT, 1 /* do_trace */); + return cnat_node_inline (vm, node, frame, cnat_output_feature_fn, AF_IP6, + CNAT_LOCATION_OUTPUT, 0 /* do_trace */); +} + +VLIB_REGISTER_NODE (cnat_output_feature_ip6_node) = { + .name = "cnat-output-ip6", + .vector_size = sizeof (u32), + .format_trace = format_cnat_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = CNAT_N_ERROR, + .error_strings = cnat_error_strings, + .n_next_nodes = CNAT_FEATURE_N_NEXT, + .next_nodes = { + [CNAT_FEATURE_NEXT_DROP] = "error-drop", + }, +}; + +VNET_FEATURE_INIT (cnat_out_ip6_feature, static) = { + .arc_name = "ip6-output", + .node_name = "cnat-output-ip6", + .runs_before = VNET_FEATURES ("gso-ip6"), + .runs_after = VNET_FEATURES ("acl-plugin-out-ip6-fa"), +}; + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/cnat/cnat_snat.c b/src/plugins/cnat/cnat_snat.c index 68b2f2b11bf..b063715d585 100644 --- a/src/plugins/cnat/cnat_snat.c +++ b/src/plugins/cnat/cnat_snat.c @@ -17,6 +17,59 @@ #include #include +cnat_snat_policy_main_t cnat_snat_policy_main; + +void +cnat_set_snat_policy (cnat_snat_policy_t fp) +{ + cnat_snat_policy_main.snat_policy = fp; +} + +static clib_error_t * +cnat_snat_policy_cmd (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + cnat_snat_policy_t fp = NULL; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "none")) + ; + else + return clib_error_return (0, "unknown input '%U'", + format_unformat_error, input); + } + + cnat_set_snat_policy (fp); + return NULL; +} + +VLIB_CLI_COMMAND (cnat_snat_policy_command, static) = { + .path = "cnat set snat policy", + .short_help = "cnat set snat policy {none,k8s}", + .function = cnat_snat_policy_cmd, +}; + +static clib_error_t * +show_cnat_snat_policy_cmd (vlib_main_t *vm, unformat_input_t *input, + vlib_cli_command_t *cmd) +{ + u8 *s = format (NULL, "snat policy: "); + if (cnat_snat_policy_main.snat_policy == NULL) + s = format (s, "none"); + else + s = format (s, "unknown (%x)", cnat_snat_policy_main.snat_policy); + + vlib_cli_output (vm, (char *) s); + return NULL; +} + +VLIB_CLI_COMMAND (show_cnat_snat_policy_command, static) = { + .path = "show cnat snat policy", + .short_help = "show cnat snat policy", + .function = show_cnat_snat_policy_cmd, +}; + static void cnat_compute_prefix_lengths_in_search_order (cnat_snat_pfx_table_t * table, ip_address_family_t af) diff --git a/src/plugins/cnat/cnat_snat.h b/src/plugins/cnat/cnat_snat.h index 6ebb8034dbc..e34cf03bd92 100644 --- a/src/plugins/cnat/cnat_snat.h +++ b/src/plugins/cnat/cnat_snat.h @@ -17,12 +17,28 @@ #define __CNAT_SNAT_H__ #include +#include +/* function to use to decide whether to snat connections in the output + feature */ +typedef void (*cnat_snat_policy_t) (vlib_main_t *vm, vlib_buffer_t *b, + cnat_session_t *session, + cnat_node_ctx_t *ctx, u8 *do_snat); + +typedef struct cnat_snat_policy_main_t_ +{ + /* SNAT policy for the output feature node */ + cnat_snat_policy_t snat_policy; + +} cnat_snat_policy_main_t; + +extern cnat_snat_policy_main_t cnat_snat_policy_main; extern void cnat_set_snat (ip4_address_t * ip4, ip6_address_t * ip6, u32 sw_if_index); extern int cnat_add_snat_prefix (ip_prefix_t * pfx); extern int cnat_del_snat_prefix (ip_prefix_t * pfx); +extern void cnat_set_snat_policy (cnat_snat_policy_t fp); int cnat_search_snat_prefix (ip46_address_t * addr, ip_address_family_t af); -- 2.16.6