diff options
Diffstat (limited to 'src/plugins/nat')
-rw-r--r-- | src/plugins/nat/in2out.c | 3454 | ||||
-rw-r--r-- | src/plugins/nat/nat.api | 1513 | ||||
-rw-r--r-- | src/plugins/nat/nat.c | 2842 | ||||
-rw-r--r-- | src/plugins/nat/nat.h | 541 | ||||
-rw-r--r-- | src/plugins/nat/nat64.c | 858 | ||||
-rw-r--r-- | src/plugins/nat/nat64.h | 322 | ||||
-rw-r--r-- | src/plugins/nat/nat64_cli.c | 983 | ||||
-rw-r--r-- | src/plugins/nat/nat64_db.c | 603 | ||||
-rw-r--r-- | src/plugins/nat/nat64_db.h | 307 | ||||
-rw-r--r-- | src/plugins/nat/nat64_doc.md | 73 | ||||
-rw-r--r-- | src/plugins/nat/nat64_in2out.c | 1118 | ||||
-rw-r--r-- | src/plugins/nat/nat64_out2in.c | 494 | ||||
-rw-r--r-- | src/plugins/nat/nat_all_api_h.h | 19 | ||||
-rw-r--r-- | src/plugins/nat/nat_api.c | 3255 | ||||
-rw-r--r-- | src/plugins/nat/nat_det.c | 158 | ||||
-rw-r--r-- | src/plugins/nat/nat_det.h | 196 | ||||
-rw-r--r-- | src/plugins/nat/nat_ipfix_logging.c | 835 | ||||
-rw-r--r-- | src/plugins/nat/nat_ipfix_logging.h | 79 | ||||
-rw-r--r-- | src/plugins/nat/nat_msg_enum.h | 31 | ||||
-rw-r--r-- | src/plugins/nat/nat_test.c | 1167 | ||||
-rw-r--r-- | src/plugins/nat/out2in.c | 2294 |
21 files changed, 21142 insertions, 0 deletions
diff --git a/src/plugins/nat/in2out.c b/src/plugins/nat/in2out.c new file mode 100644 index 00000000000..62a11170ef3 --- /dev/null +++ b/src/plugins/nat/in2out.c @@ -0,0 +1,3454 @@ +/* + * Copyright (c) 2016 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 <vlib/vlib.h> +#include <vnet/vnet.h> +#include <vnet/pg/pg.h> +#include <vnet/handoff.h> + +#include <vnet/ip/ip.h> +#include <vnet/ethernet/ethernet.h> +#include <vnet/fib/ip4_fib.h> +#include <nat/nat.h> +#include <nat/nat_ipfix_logging.h> +#include <nat/nat_det.h> + +#include <vppinfra/hash.h> +#include <vppinfra/error.h> +#include <vppinfra/elog.h> + +typedef struct { + u32 sw_if_index; + u32 next_index; + u32 session_index; + u32 is_slow_path; +} snat_in2out_trace_t; + +typedef struct { + u32 next_worker_index; + u8 do_handoff; +} snat_in2out_worker_handoff_trace_t; + +/* packet trace format function */ +static u8 * format_snat_in2out_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + snat_in2out_trace_t * t = va_arg (*args, snat_in2out_trace_t *); + char * tag; + + tag = t->is_slow_path ? "NAT44_IN2OUT_SLOW_PATH" : "NAT44_IN2OUT_FAST_PATH"; + + s = format (s, "%s: sw_if_index %d, next index %d, session %d", tag, + t->sw_if_index, t->next_index, t->session_index); + + return s; +} + +static u8 * format_snat_in2out_fast_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + snat_in2out_trace_t * t = va_arg (*args, snat_in2out_trace_t *); + + s = format (s, "NAT44_IN2OUT_FAST: sw_if_index %d, next index %d", + t->sw_if_index, t->next_index); + + return s; +} + +static u8 * format_snat_in2out_worker_handoff_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + snat_in2out_worker_handoff_trace_t * t = + va_arg (*args, snat_in2out_worker_handoff_trace_t *); + char * m; + + m = t->do_handoff ? "next worker" : "same worker"; + s = format (s, "NAT44_IN2OUT_WORKER_HANDOFF: %s %d", m, t->next_worker_index); + + return s; +} + +vlib_node_registration_t snat_in2out_node; +vlib_node_registration_t snat_in2out_slowpath_node; +vlib_node_registration_t snat_in2out_fast_node; +vlib_node_registration_t snat_in2out_worker_handoff_node; +vlib_node_registration_t snat_det_in2out_node; +vlib_node_registration_t snat_in2out_output_node; +vlib_node_registration_t snat_in2out_output_slowpath_node; +vlib_node_registration_t snat_in2out_output_worker_handoff_node; +vlib_node_registration_t snat_hairpin_dst_node; +vlib_node_registration_t snat_hairpin_src_node; + + +#define foreach_snat_in2out_error \ +_(UNSUPPORTED_PROTOCOL, "Unsupported protocol") \ +_(IN2OUT_PACKETS, "Good in2out packets processed") \ +_(OUT_OF_PORTS, "Out of ports") \ +_(BAD_OUTSIDE_FIB, "Outside VRF ID not found") \ +_(BAD_ICMP_TYPE, "unsupported ICMP type") \ +_(NO_TRANSLATION, "No translation") + +typedef enum { +#define _(sym,str) SNAT_IN2OUT_ERROR_##sym, + foreach_snat_in2out_error +#undef _ + SNAT_IN2OUT_N_ERROR, +} snat_in2out_error_t; + +static char * snat_in2out_error_strings[] = { +#define _(sym,string) string, + foreach_snat_in2out_error +#undef _ +}; + +typedef enum { + SNAT_IN2OUT_NEXT_LOOKUP, + SNAT_IN2OUT_NEXT_DROP, + SNAT_IN2OUT_NEXT_ICMP_ERROR, + SNAT_IN2OUT_NEXT_SLOW_PATH, + SNAT_IN2OUT_N_NEXT, +} snat_in2out_next_t; + +typedef enum { + SNAT_HAIRPIN_SRC_NEXT_DROP, + SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT, + SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT_WH, + SNAT_HAIRPIN_SRC_NEXT_INTERFACE_OUTPUT, + SNAT_HAIRPIN_SRC_N_NEXT, +} snat_hairpin_next_t; + +/** + * @brief Check if packet should be translated + * + * Packets aimed at outside interface and external addresss with active session + * should be translated. + * + * @param sm NAT main + * @param rt NAT runtime data + * @param sw_if_index0 index of the inside interface + * @param ip0 IPv4 header + * @param proto0 NAT protocol + * @param rx_fib_index0 RX FIB index + * + * @returns 0 if packet should be translated otherwise 1 + */ +static inline int +snat_not_translate_fast (snat_main_t * sm, vlib_node_runtime_t *node, + u32 sw_if_index0, ip4_header_t * ip0, u32 proto0, + u32 rx_fib_index0) +{ + fib_node_index_t fei = FIB_NODE_INDEX_INVALID; + fib_prefix_t pfx = { + .fp_proto = FIB_PROTOCOL_IP4, + .fp_len = 32, + .fp_addr = { + .ip4.as_u32 = ip0->dst_address.as_u32, + }, + }; + + /* Don't NAT packet aimed at the intfc address */ + if (PREDICT_FALSE(is_interface_addr(sm, node, sw_if_index0, + ip0->dst_address.as_u32))) + return 1; + + fei = fib_table_lookup (rx_fib_index0, &pfx); + if (FIB_NODE_INDEX_INVALID != fei) + { + u32 sw_if_index = fib_entry_get_resolving_interface (fei); + if (sw_if_index == ~0) + { + fei = fib_table_lookup (sm->outside_fib_index, &pfx); + if (FIB_NODE_INDEX_INVALID != fei) + sw_if_index = fib_entry_get_resolving_interface (fei); + } + snat_interface_t *i; + pool_foreach (i, sm->interfaces, + ({ + /* NAT packet aimed at outside interface */ + if ((i->is_inside == 0) && (sw_if_index == i->sw_if_index)) + return 0; + })); + } + + return 1; +} + +static inline int +snat_not_translate (snat_main_t * sm, vlib_node_runtime_t *node, + u32 sw_if_index0, ip4_header_t * ip0, u32 proto0, + u32 rx_fib_index0) +{ + udp_header_t * udp0 = ip4_next_header (ip0); + snat_session_key_t key0, sm0; + clib_bihash_kv_8_8_t kv0, value0; + + key0.addr = ip0->dst_address; + key0.port = udp0->dst_port; + key0.protocol = proto0; + key0.fib_index = sm->outside_fib_index; + kv0.key = key0.as_u64; + + /* NAT packet aimed at external address if */ + /* has active sessions */ + if (clib_bihash_search_8_8 (&sm->out2in, &kv0, &value0)) + { + /* or is static mappings */ + if (!snat_static_mapping_match(sm, key0, &sm0, 1, 0)) + return 0; + } + else + return 0; + + return snat_not_translate_fast(sm, node, sw_if_index0, ip0, proto0, + rx_fib_index0); +} + +static u32 slow_path (snat_main_t *sm, vlib_buffer_t *b0, + ip4_header_t * ip0, + u32 rx_fib_index0, + snat_session_key_t * key0, + snat_session_t ** sessionp, + vlib_node_runtime_t * node, + u32 next0, + u32 thread_index) +{ + snat_user_t *u; + snat_user_key_t user_key; + snat_session_t *s; + clib_bihash_kv_8_8_t kv0, value0; + u32 oldest_per_user_translation_list_index; + dlist_elt_t * oldest_per_user_translation_list_elt; + dlist_elt_t * per_user_translation_list_elt; + dlist_elt_t * per_user_list_head_elt; + u32 session_index; + snat_session_key_t key1; + u32 address_index = ~0; + u32 outside_fib_index; + uword * p; + snat_worker_key_t worker_by_out_key; + + p = hash_get (sm->ip4_main->fib_index_by_table_id, sm->outside_vrf_id); + if (! p) + { + b0->error = node->errors[SNAT_IN2OUT_ERROR_BAD_OUTSIDE_FIB]; + return SNAT_IN2OUT_NEXT_DROP; + } + outside_fib_index = p[0]; + + key1.protocol = key0->protocol; + user_key.addr = ip0->src_address; + user_key.fib_index = rx_fib_index0; + kv0.key = user_key.as_u64; + + /* Ever heard of the "user" = src ip4 address before? */ + if (clib_bihash_search_8_8 (&sm->user_hash, &kv0, &value0)) + { + /* no, make a new one */ + pool_get (sm->per_thread_data[thread_index].users, u); + memset (u, 0, sizeof (*u)); + u->addr = ip0->src_address; + u->fib_index = rx_fib_index0; + + pool_get (sm->per_thread_data[thread_index].list_pool, per_user_list_head_elt); + + u->sessions_per_user_list_head_index = per_user_list_head_elt - + sm->per_thread_data[thread_index].list_pool; + + clib_dlist_init (sm->per_thread_data[thread_index].list_pool, + u->sessions_per_user_list_head_index); + + kv0.value = u - sm->per_thread_data[thread_index].users; + + /* add user */ + clib_bihash_add_del_8_8 (&sm->user_hash, &kv0, 1 /* is_add */); + } + else + { + u = pool_elt_at_index (sm->per_thread_data[thread_index].users, + value0.value); + } + + /* Over quota? Recycle the least recently used dynamic translation */ + if (u->nsessions >= sm->max_translations_per_user) + { + /* Remove the oldest dynamic translation */ + do { + oldest_per_user_translation_list_index = + clib_dlist_remove_head (sm->per_thread_data[thread_index].list_pool, + u->sessions_per_user_list_head_index); + + ASSERT (oldest_per_user_translation_list_index != ~0); + + /* add it back to the end of the LRU list */ + clib_dlist_addtail (sm->per_thread_data[thread_index].list_pool, + u->sessions_per_user_list_head_index, + oldest_per_user_translation_list_index); + /* Get the list element */ + oldest_per_user_translation_list_elt = + pool_elt_at_index (sm->per_thread_data[thread_index].list_pool, + oldest_per_user_translation_list_index); + + /* Get the session index from the list element */ + session_index = oldest_per_user_translation_list_elt->value; + + /* Get the session */ + s = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, + session_index); + } while (snat_is_session_static (s)); + + if (snat_is_unk_proto_session (s)) + { + clib_bihash_kv_16_8_t up_kv; + snat_unk_proto_ses_key_t key; + + /* Remove from lookup tables */ + key.l_addr = s->in2out.addr; + key.r_addr = s->ext_host_addr; + key.fib_index = s->in2out.fib_index; + key.proto = s->in2out.port; + up_kv.key[0] = key.as_u64[0]; + up_kv.key[1] = key.as_u64[1]; + if (clib_bihash_add_del_16_8 (&sm->in2out_unk_proto, &up_kv, 0)) + clib_warning ("in2out key del failed"); + + key.l_addr = s->out2in.addr; + key.fib_index = s->out2in.fib_index; + up_kv.key[0] = key.as_u64[0]; + up_kv.key[1] = key.as_u64[1]; + if (clib_bihash_add_del_16_8 (&sm->out2in_unk_proto, &up_kv, 0)) + clib_warning ("out2in key del failed"); + } + else + { + /* Remove in2out, out2in keys */ + kv0.key = s->in2out.as_u64; + if (clib_bihash_add_del_8_8 (&sm->in2out, &kv0, 0 /* is_add */)) + clib_warning ("in2out key delete failed"); + kv0.key = s->out2in.as_u64; + if (clib_bihash_add_del_8_8 (&sm->out2in, &kv0, 0 /* is_add */)) + clib_warning ("out2in key delete failed"); + + /* log NAT event */ + snat_ipfix_logging_nat44_ses_delete(s->in2out.addr.as_u32, + s->out2in.addr.as_u32, + s->in2out.protocol, + s->in2out.port, + s->out2in.port, + s->in2out.fib_index); + + snat_free_outside_address_and_port + (sm, &s->out2in, s->outside_address_index); + } + s->outside_address_index = ~0; + + if (snat_alloc_outside_address_and_port (sm, rx_fib_index0, thread_index, + &key1, &address_index)) + { + ASSERT(0); + + b0->error = node->errors[SNAT_IN2OUT_ERROR_OUT_OF_PORTS]; + return SNAT_IN2OUT_NEXT_DROP; + } + s->outside_address_index = address_index; + } + else + { + u8 static_mapping = 1; + + /* First try to match static mapping by local address and port */ + if (snat_static_mapping_match (sm, *key0, &key1, 0, 0)) + { + static_mapping = 0; + /* Try to create dynamic translation */ + if (snat_alloc_outside_address_and_port (sm, rx_fib_index0, + thread_index, &key1, + &address_index)) + { + b0->error = node->errors[SNAT_IN2OUT_ERROR_OUT_OF_PORTS]; + return SNAT_IN2OUT_NEXT_DROP; + } + } + + /* Create a new session */ + pool_get (sm->per_thread_data[thread_index].sessions, s); + memset (s, 0, sizeof (*s)); + + s->outside_address_index = address_index; + + if (static_mapping) + { + u->nstaticsessions++; + s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING; + } + else + { + u->nsessions++; + } + + /* Create list elts */ + pool_get (sm->per_thread_data[thread_index].list_pool, + per_user_translation_list_elt); + clib_dlist_init (sm->per_thread_data[thread_index].list_pool, + per_user_translation_list_elt - + sm->per_thread_data[thread_index].list_pool); + + per_user_translation_list_elt->value = + s - sm->per_thread_data[thread_index].sessions; + s->per_user_index = per_user_translation_list_elt - + sm->per_thread_data[thread_index].list_pool; + s->per_user_list_head_index = u->sessions_per_user_list_head_index; + + clib_dlist_addtail (sm->per_thread_data[thread_index].list_pool, + s->per_user_list_head_index, + per_user_translation_list_elt - + sm->per_thread_data[thread_index].list_pool); + } + + s->in2out = *key0; + s->out2in = key1; + s->out2in.protocol = key0->protocol; + s->out2in.fib_index = outside_fib_index; + s->ext_host_addr.as_u32 = ip0->dst_address.as_u32; + *sessionp = s; + + /* Add to translation hashes */ + kv0.key = s->in2out.as_u64; + kv0.value = s - sm->per_thread_data[thread_index].sessions; + if (clib_bihash_add_del_8_8 (&sm->in2out, &kv0, 1 /* is_add */)) + clib_warning ("in2out key add failed"); + + kv0.key = s->out2in.as_u64; + kv0.value = s - sm->per_thread_data[thread_index].sessions; + + if (clib_bihash_add_del_8_8 (&sm->out2in, &kv0, 1 /* is_add */)) + clib_warning ("out2in key add failed"); + + /* Add to translated packets worker lookup */ + worker_by_out_key.addr = s->out2in.addr; + worker_by_out_key.port = s->out2in.port; + worker_by_out_key.fib_index = s->out2in.fib_index; + kv0.key = worker_by_out_key.as_u64; + kv0.value = thread_index; + clib_bihash_add_del_8_8 (&sm->worker_by_out, &kv0, 1); + + /* log NAT event */ + snat_ipfix_logging_nat44_ses_create(s->in2out.addr.as_u32, + s->out2in.addr.as_u32, + s->in2out.protocol, + s->in2out.port, + s->out2in.port, + s->in2out.fib_index); + return next0; +} + +static_always_inline +snat_in2out_error_t icmp_get_key(ip4_header_t *ip0, + snat_session_key_t *p_key0) +{ + icmp46_header_t *icmp0; + snat_session_key_t key0; + icmp_echo_header_t *echo0, *inner_echo0 = 0; + ip4_header_t *inner_ip0 = 0; + void *l4_header = 0; + icmp46_header_t *inner_icmp0; + + icmp0 = (icmp46_header_t *) ip4_next_header (ip0); + echo0 = (icmp_echo_header_t *)(icmp0+1); + + if (!icmp_is_error_message (icmp0)) + { + key0.protocol = SNAT_PROTOCOL_ICMP; + key0.addr = ip0->src_address; + key0.port = echo0->identifier; + } + else + { + inner_ip0 = (ip4_header_t *)(echo0+1); + l4_header = ip4_next_header (inner_ip0); + key0.protocol = ip_proto_to_snat_proto (inner_ip0->protocol); + key0.addr = inner_ip0->dst_address; + switch (key0.protocol) + { + case SNAT_PROTOCOL_ICMP: + inner_icmp0 = (icmp46_header_t*)l4_header; + inner_echo0 = (icmp_echo_header_t *)(inner_icmp0+1); + key0.port = inner_echo0->identifier; + break; + case SNAT_PROTOCOL_UDP: + case SNAT_PROTOCOL_TCP: + key0.port = ((tcp_udp_header_t*)l4_header)->dst_port; + break; + default: + return SNAT_IN2OUT_ERROR_UNSUPPORTED_PROTOCOL; + } + } + *p_key0 = key0; + return -1; /* success */ +} + +/** + * Get address and port values to be used for ICMP packet translation + * and create session if needed + * + * @param[in,out] sm NAT main + * @param[in,out] node NAT node runtime + * @param[in] thread_index thread index + * @param[in,out] b0 buffer containing packet to be translated + * @param[out] p_proto protocol used for matching + * @param[out] p_value address and port after NAT translation + * @param[out] p_dont_translate if packet should not be translated + * @param d optional parameter + * @param e optional parameter + */ +u32 icmp_match_in2out_slow(snat_main_t *sm, vlib_node_runtime_t *node, + u32 thread_index, vlib_buffer_t *b0, u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, void *d, void *e) +{ + ip4_header_t *ip0; + icmp46_header_t *icmp0; + u32 sw_if_index0; + u32 rx_fib_index0; + snat_session_key_t key0; + snat_session_t *s0 = 0; + u8 dont_translate = 0; + clib_bihash_kv_8_8_t kv0, value0; + u32 next0 = ~0; + int err; + u32 iph_offset0 = 0; + + if (PREDICT_FALSE(vnet_buffer(b0)->sw_if_index[VLIB_TX] != ~0)) + { + iph_offset0 = vnet_buffer (b0)->ip.save_rewrite_length; + } + ip0 = (ip4_header_t *) ((u8 *) vlib_buffer_get_current (b0) + iph_offset0); + icmp0 = (icmp46_header_t *) ip4_next_header (ip0); + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index (sw_if_index0); + + err = icmp_get_key (ip0, &key0); + if (err != -1) + { + b0->error = node->errors[err]; + next0 = SNAT_IN2OUT_NEXT_DROP; + goto out; + } + key0.fib_index = rx_fib_index0; + + kv0.key = key0.as_u64; + + if (clib_bihash_search_8_8 (&sm->in2out, &kv0, &value0)) + { + if (PREDICT_FALSE(snat_not_translate(sm, node, sw_if_index0, ip0, + IP_PROTOCOL_ICMP, rx_fib_index0) && + vnet_buffer(b0)->sw_if_index[VLIB_TX] == ~0)) + { + dont_translate = 1; + goto out; + } + + if (PREDICT_FALSE(icmp_is_error_message (icmp0))) + { + b0->error = node->errors[SNAT_IN2OUT_ERROR_BAD_ICMP_TYPE]; + next0 = SNAT_IN2OUT_NEXT_DROP; + goto out; + } + + next0 = slow_path (sm, b0, ip0, rx_fib_index0, &key0, + &s0, node, next0, thread_index); + + if (PREDICT_FALSE (next0 == SNAT_IN2OUT_NEXT_DROP)) + goto out; + } + else + { + if (PREDICT_FALSE(icmp0->type != ICMP4_echo_request && + icmp0->type != ICMP4_echo_reply && + !icmp_is_error_message (icmp0))) + { + b0->error = node->errors[SNAT_IN2OUT_ERROR_BAD_ICMP_TYPE]; + next0 = SNAT_IN2OUT_NEXT_DROP; + goto out; + } + + s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, + value0.value); + } + +out: + *p_proto = key0.protocol; + if (s0) + *p_value = s0->out2in; + *p_dont_translate = dont_translate; + if (d) + *(snat_session_t**)d = s0; + return next0; +} + +/** + * Get address and port values to be used for ICMP packet translation + * + * @param[in] sm NAT main + * @param[in,out] node NAT node runtime + * @param[in] thread_index thread index + * @param[in,out] b0 buffer containing packet to be translated + * @param[out] p_proto protocol used for matching + * @param[out] p_value address and port after NAT translation + * @param[out] p_dont_translate if packet should not be translated + * @param d optional parameter + * @param e optional parameter + */ +u32 icmp_match_in2out_fast(snat_main_t *sm, vlib_node_runtime_t *node, + u32 thread_index, vlib_buffer_t *b0, u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, void *d, void *e) +{ + ip4_header_t *ip0; + icmp46_header_t *icmp0; + u32 sw_if_index0; + u32 rx_fib_index0; + snat_session_key_t key0; + snat_session_key_t sm0; + u8 dont_translate = 0; + u8 is_addr_only; + u32 next0 = ~0; + int err; + + ip0 = vlib_buffer_get_current (b0); + icmp0 = (icmp46_header_t *) ip4_next_header (ip0); + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index (sw_if_index0); + + err = icmp_get_key (ip0, &key0); + if (err != -1) + { + b0->error = node->errors[err]; + next0 = SNAT_IN2OUT_NEXT_DROP; + goto out2; + } + key0.fib_index = rx_fib_index0; + + if (snat_static_mapping_match(sm, key0, &sm0, 0, &is_addr_only)) + { + if (PREDICT_FALSE(snat_not_translate_fast(sm, node, sw_if_index0, ip0, + IP_PROTOCOL_ICMP, rx_fib_index0))) + { + dont_translate = 1; + goto out; + } + + if (icmp_is_error_message (icmp0)) + { + next0 = SNAT_IN2OUT_NEXT_DROP; + goto out; + } + + b0->error = node->errors[SNAT_IN2OUT_ERROR_NO_TRANSLATION]; + next0 = SNAT_IN2OUT_NEXT_DROP; + goto out; + } + + if (PREDICT_FALSE(icmp0->type != ICMP4_echo_request && + (icmp0->type != ICMP4_echo_reply || !is_addr_only) && + !icmp_is_error_message (icmp0))) + { + b0->error = node->errors[SNAT_IN2OUT_ERROR_BAD_ICMP_TYPE]; + next0 = SNAT_IN2OUT_NEXT_DROP; + goto out; + } + +out: + *p_value = sm0; +out2: + *p_proto = key0.protocol; + *p_dont_translate = dont_translate; + return next0; +} + +static inline u32 icmp_in2out (snat_main_t *sm, + vlib_buffer_t * b0, + ip4_header_t * ip0, + icmp46_header_t * icmp0, + u32 sw_if_index0, + u32 rx_fib_index0, + vlib_node_runtime_t * node, + u32 next0, + u32 thread_index, + void *d, + void *e) +{ + snat_session_key_t sm0; + u8 protocol; + icmp_echo_header_t *echo0, *inner_echo0 = 0; + ip4_header_t *inner_ip0; + void *l4_header = 0; + icmp46_header_t *inner_icmp0; + u8 dont_translate; + u32 new_addr0, old_addr0; + u16 old_id0, new_id0; + ip_csum_t sum0; + u16 checksum0; + u32 next0_tmp; + + echo0 = (icmp_echo_header_t *)(icmp0+1); + + next0_tmp = sm->icmp_match_in2out_cb(sm, node, thread_index, b0, + &protocol, &sm0, &dont_translate, d, e); + if (next0_tmp != ~0) + next0 = next0_tmp; + if (next0 == SNAT_IN2OUT_NEXT_DROP || dont_translate) + goto out; + + sum0 = ip_incremental_checksum (0, icmp0, + ntohs(ip0->length) - ip4_header_bytes (ip0)); + checksum0 = ~ip_csum_fold (sum0); + if (PREDICT_FALSE(checksum0 != 0 && checksum0 != 0xffff)) + { + next0 = SNAT_IN2OUT_NEXT_DROP; + goto out; + } + + old_addr0 = ip0->src_address.as_u32; + new_addr0 = ip0->src_address.as_u32 = sm0.addr.as_u32; + if (vnet_buffer(b0)->sw_if_index[VLIB_TX] == ~0) + vnet_buffer(b0)->sw_if_index[VLIB_TX] = sm0.fib_index; + + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t, + src_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + if (!icmp_is_error_message (icmp0)) + { + new_id0 = sm0.port; + if (PREDICT_FALSE(new_id0 != echo0->identifier)) + { + old_id0 = echo0->identifier; + new_id0 = sm0.port; + echo0->identifier = new_id0; + + sum0 = icmp0->checksum; + sum0 = ip_csum_update (sum0, old_id0, new_id0, icmp_echo_header_t, + identifier); + icmp0->checksum = ip_csum_fold (sum0); + } + } + else + { + inner_ip0 = (ip4_header_t *)(echo0+1); + l4_header = ip4_next_header (inner_ip0); + + if (!ip4_header_checksum_is_valid (inner_ip0)) + { + next0 = SNAT_IN2OUT_NEXT_DROP; + goto out; + } + + old_addr0 = inner_ip0->dst_address.as_u32; + inner_ip0->dst_address = sm0.addr; + new_addr0 = inner_ip0->dst_address.as_u32; + + sum0 = icmp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t, + dst_address /* changed member */); + icmp0->checksum = ip_csum_fold (sum0); + + switch (protocol) + { + case SNAT_PROTOCOL_ICMP: + inner_icmp0 = (icmp46_header_t*)l4_header; + inner_echo0 = (icmp_echo_header_t *)(inner_icmp0+1); + + old_id0 = inner_echo0->identifier; + new_id0 = sm0.port; + inner_echo0->identifier = new_id0; + + sum0 = icmp0->checksum; + sum0 = ip_csum_update (sum0, old_id0, new_id0, icmp_echo_header_t, + identifier); + icmp0->checksum = ip_csum_fold (sum0); + break; + case SNAT_PROTOCOL_UDP: + case SNAT_PROTOCOL_TCP: + old_id0 = ((tcp_udp_header_t*)l4_header)->dst_port; + new_id0 = sm0.port; + ((tcp_udp_header_t*)l4_header)->dst_port = new_id0; + + sum0 = icmp0->checksum; + sum0 = ip_csum_update (sum0, old_id0, new_id0, tcp_udp_header_t, + dst_port); + icmp0->checksum = ip_csum_fold (sum0); + break; + default: + ASSERT(0); + } + } + +out: + return next0; +} + +/** + * @brief Hairpinning + * + * Hairpinning allows two endpoints on the internal side of the NAT to + * communicate even if they only use each other's external IP addresses + * and ports. + * + * @param sm NAT main. + * @param b0 Vlib buffer. + * @param ip0 IP header. + * @param udp0 UDP header. + * @param tcp0 TCP header. + * @param proto0 NAT protocol. + */ +static inline void +snat_hairpinning (snat_main_t *sm, + vlib_buffer_t * b0, + ip4_header_t * ip0, + udp_header_t * udp0, + tcp_header_t * tcp0, + u32 proto0) +{ + snat_session_key_t key0, sm0; + snat_worker_key_t k0; + snat_session_t * s0; + clib_bihash_kv_8_8_t kv0, value0; + ip_csum_t sum0; + u32 new_dst_addr0 = 0, old_dst_addr0, ti = 0, si; + u16 new_dst_port0, old_dst_port0; + + key0.addr = ip0->dst_address; + key0.port = udp0->dst_port; + key0.protocol = proto0; + key0.fib_index = sm->outside_fib_index; + kv0.key = key0.as_u64; + + /* Check if destination is in active sessions */ + if (clib_bihash_search_8_8 (&sm->out2in, &kv0, &value0)) + { + /* or static mappings */ + if (!snat_static_mapping_match(sm, key0, &sm0, 1, 0)) + { + new_dst_addr0 = sm0.addr.as_u32; + new_dst_port0 = sm0.port; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = sm0.fib_index; + } + } + else + { + si = value0.value; + if (sm->num_workers > 1) + { + k0.addr = ip0->dst_address; + k0.port = udp0->dst_port; + k0.fib_index = sm->outside_fib_index; + kv0.key = k0.as_u64; + if (clib_bihash_search_8_8 (&sm->worker_by_out, &kv0, &value0)) + ASSERT(0); + else + ti = value0.value; + } + else + ti = sm->num_workers; + + s0 = pool_elt_at_index (sm->per_thread_data[ti].sessions, si); + new_dst_addr0 = s0->in2out.addr.as_u32; + new_dst_port0 = s0->in2out.port; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->in2out.fib_index; + } + + /* Destination is behind the same NAT, use internal address and port */ + if (new_dst_addr0) + { + old_dst_addr0 = ip0->dst_address.as_u32; + ip0->dst_address.as_u32 = new_dst_addr0; + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_dst_addr0, new_dst_addr0, + ip4_header_t, dst_address); + ip0->checksum = ip_csum_fold (sum0); + + old_dst_port0 = tcp0->dst; + if (PREDICT_TRUE(new_dst_port0 != old_dst_port0)) + { + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + tcp0->dst = new_dst_port0; + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_dst_addr0, new_dst_addr0, + ip4_header_t, dst_address); + sum0 = ip_csum_update (sum0, old_dst_port0, new_dst_port0, + ip4_header_t /* cheat */, length); + tcp0->checksum = ip_csum_fold(sum0); + } + else + { + udp0->dst_port = new_dst_port0; + udp0->checksum = 0; + } + } + else + { + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_dst_addr0, new_dst_addr0, + ip4_header_t, dst_address); + tcp0->checksum = ip_csum_fold(sum0); + } + } + } +} + +static inline void +snat_icmp_hairpinning (snat_main_t *sm, + vlib_buffer_t * b0, + ip4_header_t * ip0, + icmp46_header_t * icmp0) +{ + snat_session_key_t key0, sm0; + clib_bihash_kv_8_8_t kv0, value0; + snat_worker_key_t k0; + u32 new_dst_addr0 = 0, old_dst_addr0, si, ti = 0; + ip_csum_t sum0; + snat_session_t *s0; + + if (!icmp_is_error_message (icmp0)) + { + icmp_echo_header_t *echo0 = (icmp_echo_header_t *)(icmp0+1); + u16 icmp_id0 = echo0->identifier; + key0.addr = ip0->dst_address; + key0.port = icmp_id0; + key0.protocol = SNAT_PROTOCOL_ICMP; + key0.fib_index = sm->outside_fib_index; + kv0.key = key0.as_u64; + + /* Check if destination is in active sessions */ + if (clib_bihash_search_8_8 (&sm->out2in, &kv0, &value0)) + { + /* or static mappings */ + if (!snat_static_mapping_match(sm, key0, &sm0, 1, 0)) + { + new_dst_addr0 = sm0.addr.as_u32; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = sm0.fib_index; + } + } + else + { + si = value0.value; + if (sm->num_workers > 1) + { + k0.addr = ip0->dst_address; + k0.port = icmp_id0; + k0.fib_index = sm->outside_fib_index; + kv0.key = k0.as_u64; + if (clib_bihash_search_8_8 (&sm->worker_by_out, &kv0, &value0)) + ASSERT(0); + else + ti = value0.value; + } + else + ti = sm->num_workers; + + s0 = pool_elt_at_index (sm->per_thread_data[ti].sessions, si); + new_dst_addr0 = s0->in2out.addr.as_u32; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->in2out.fib_index; + echo0->identifier = s0->in2out.port; + sum0 = icmp0->checksum; + sum0 = ip_csum_update (sum0, icmp_id0, s0->in2out.port, + icmp_echo_header_t, identifier); + icmp0->checksum = ip_csum_fold (sum0); + } + + /* Destination is behind the same NAT, use internal address and port */ + if (new_dst_addr0) + { + old_dst_addr0 = ip0->dst_address.as_u32; + ip0->dst_address.as_u32 = new_dst_addr0; + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_dst_addr0, new_dst_addr0, + ip4_header_t, dst_address); + ip0->checksum = ip_csum_fold (sum0); + } + } + +} + +static inline u32 icmp_in2out_slow_path (snat_main_t *sm, + vlib_buffer_t * b0, + ip4_header_t * ip0, + icmp46_header_t * icmp0, + u32 sw_if_index0, + u32 rx_fib_index0, + vlib_node_runtime_t * node, + u32 next0, + f64 now, + u32 thread_index, + snat_session_t ** p_s0) +{ + next0 = icmp_in2out(sm, b0, ip0, icmp0, sw_if_index0, rx_fib_index0, node, + next0, thread_index, p_s0, 0); + snat_session_t * s0 = *p_s0; + if (PREDICT_TRUE(next0 != SNAT_IN2OUT_NEXT_DROP && s0)) + { + /* Hairpinning */ + if (vnet_buffer(b0)->sw_if_index[VLIB_TX] == 0) + snat_icmp_hairpinning(sm, b0, ip0, icmp0); + /* Accounting */ + s0->last_heard = now; + s0->total_pkts++; + s0->total_bytes += vlib_buffer_length_in_chain (sm->vlib_main, b0); + /* Per-user LRU list maintenance for dynamic translations */ + if (!snat_is_session_static (s0)) + { + clib_dlist_remove (sm->per_thread_data[thread_index].list_pool, + s0->per_user_index); + clib_dlist_addtail (sm->per_thread_data[thread_index].list_pool, + s0->per_user_list_head_index, + s0->per_user_index); + } + } + return next0; +} +static inline void +snat_hairpinning_unknown_proto (snat_main_t *sm, + vlib_buffer_t * b, + ip4_header_t * ip) +{ + u32 old_addr, new_addr = 0, ti = 0; + clib_bihash_kv_8_8_t kv, value; + clib_bihash_kv_16_8_t s_kv, s_value; + snat_unk_proto_ses_key_t key; + snat_session_key_t m_key; + snat_worker_key_t w_key; + snat_static_mapping_t *m; + ip_csum_t sum; + snat_session_t *s; + + old_addr = ip->dst_address.as_u32; + key.l_addr.as_u32 = ip->dst_address.as_u32; + key.r_addr.as_u32 = ip->src_address.as_u32; + key.fib_index = sm->outside_fib_index; + key.proto = ip->protocol; + key.rsvd[0] = key.rsvd[1] = key.rsvd[2] = 0; + s_kv.key[0] = key.as_u64[0]; + s_kv.key[1] = key.as_u64[1]; + if (clib_bihash_search_16_8 (&sm->out2in_unk_proto, &s_kv, &s_value)) + { + m_key.addr = ip->dst_address; + m_key.fib_index = sm->outside_fib_index; + m_key.port = 0; + m_key.protocol = 0; + kv.key = m_key.as_u64; + if (clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value)) + return; + + m = pool_elt_at_index (sm->static_mappings, value.value); + if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0) + vnet_buffer(b)->sw_if_index[VLIB_TX] = m->fib_index; + new_addr = ip->dst_address.as_u32 = m->local_addr.as_u32; + } + else + { + if (sm->num_workers > 1) + { + w_key.addr = ip->dst_address; + w_key.port = 0; + w_key.fib_index = sm->outside_fib_index; + kv.key = w_key.as_u64; + if (clib_bihash_search_8_8 (&sm->worker_by_out, &kv, &value)) + return; + else + ti = value.value; + } + else + ti = sm->num_workers; + + s = pool_elt_at_index (sm->per_thread_data[ti].sessions, s_value.value); + if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0) + vnet_buffer(b)->sw_if_index[VLIB_TX] = s->in2out.fib_index; + new_addr = ip->dst_address.as_u32 = s->in2out.addr.as_u32; + } + sum = ip->checksum; + sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, dst_address); + ip->checksum = ip_csum_fold (sum); +} + +static void +snat_in2out_unknown_proto (snat_main_t *sm, + vlib_buffer_t * b, + ip4_header_t * ip, + u32 rx_fib_index, + u32 thread_index, + f64 now, + vlib_main_t * vm) +{ + clib_bihash_kv_8_8_t kv, value; + clib_bihash_kv_16_8_t s_kv, s_value; + snat_static_mapping_t *m; + snat_session_key_t m_key; + u32 old_addr, new_addr = 0; + ip_csum_t sum; + snat_user_key_t u_key; + snat_user_t *u; + dlist_elt_t *head, *elt, *oldest; + snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index]; + u32 elt_index, head_index, ses_index, oldest_index; + snat_session_t * s; + snat_unk_proto_ses_key_t key; + u32 address_index = ~0; + int i; + u8 is_sm = 0; + + old_addr = ip->src_address.as_u32; + + key.l_addr = ip->src_address; + key.r_addr = ip->dst_address; + key.fib_index = rx_fib_index; + key.proto = ip->protocol; + key.rsvd[0] = key.rsvd[1] = key.rsvd[2] = 0; + s_kv.key[0] = key.as_u64[0]; + s_kv.key[1] = key.as_u64[1]; + + if (!clib_bihash_search_16_8 (&sm->in2out_unk_proto, &s_kv, &s_value)) + { + s = pool_elt_at_index (tsm->sessions, s_value.value); + new_addr = ip->src_address.as_u32 = s->out2in.addr.as_u32; + } + else + { + u_key.addr = ip->src_address; + u_key.fib_index = rx_fib_index; + kv.key = u_key.as_u64; + + /* Ever heard of the "user" = src ip4 address before? */ + if (clib_bihash_search_8_8 (&sm->user_hash, &kv, &value)) + { + /* no, make a new one */ + pool_get (tsm->users, u); + memset (u, 0, sizeof (*u)); + u->addr = ip->src_address; + u->fib_index = rx_fib_index; + + pool_get (tsm->list_pool, head); + u->sessions_per_user_list_head_index = head - tsm->list_pool; + + clib_dlist_init (tsm->list_pool, + u->sessions_per_user_list_head_index); + + kv.value = u - tsm->users; + + /* add user */ + clib_bihash_add_del_8_8 (&sm->user_hash, &kv, 1); + } + else + { + u = pool_elt_at_index (tsm->users, value.value); + } + + m_key.addr = ip->src_address; + m_key.port = 0; + m_key.protocol = 0; + m_key.fib_index = rx_fib_index; + kv.key = m_key.as_u64; + + /* Try to find static mapping first */ + if (!clib_bihash_search_8_8 (&sm->static_mapping_by_local, &kv, &value)) + { + m = pool_elt_at_index (sm->static_mappings, value.value); + new_addr = ip->src_address.as_u32 = m->external_addr.as_u32; + is_sm = 1; + goto create_ses; + } + /* Fallback to 3-tuple key */ + else + { + /* Choose same out address as for TCP/UDP session to same destination */ + if (!clib_bihash_search_8_8 (&sm->user_hash, &kv, &value)) + { + head_index = u->sessions_per_user_list_head_index; + head = pool_elt_at_index (tsm->list_pool, head_index); + elt_index = head->next; + elt = pool_elt_at_index (tsm->list_pool, elt_index); + ses_index = elt->value; + while (ses_index != ~0) + { + s = pool_elt_at_index (tsm->sessions, ses_index); + elt_index = elt->next; + elt = pool_elt_at_index (tsm->list_pool, elt_index); + ses_index = elt->value; + + if (s->ext_host_addr.as_u32 == ip->dst_address.as_u32) + { + new_addr = ip->src_address.as_u32 = s->out2in.addr.as_u32; + address_index = s->outside_address_index; + + key.fib_index = sm->outside_fib_index; + key.l_addr.as_u32 = new_addr; + s_kv.key[0] = key.as_u64[0]; + s_kv.key[1] = key.as_u64[1]; + if (clib_bihash_search_16_8 (&sm->out2in_unk_proto, &s_kv, &s_value)) + break; + + goto create_ses; + } + } + } + key.fib_index = sm->outside_fib_index; + for (i = 0; i < vec_len (sm->addresses); i++) + { + key.l_addr.as_u32 = sm->addresses[i].addr.as_u32; + s_kv.key[0] = key.as_u64[0]; + s_kv.key[1] = key.as_u64[1]; + if (clib_bihash_search_16_8 (&sm->out2in_unk_proto, &s_kv, &s_value)) + { + new_addr = ip->src_address.as_u32 = key.l_addr.as_u32; + address_index = i; + goto create_ses; + } + } + return; + } + +create_ses: + /* Over quota? Recycle the least recently used dynamic translation */ + if (u->nsessions >= sm->max_translations_per_user && !is_sm) + { + /* Remove the oldest dynamic translation */ + do { + oldest_index = clib_dlist_remove_head ( + tsm->list_pool, u->sessions_per_user_list_head_index); + + ASSERT (oldest_index != ~0); + + /* add it back to the end of the LRU list */ + clib_dlist_addtail (tsm->list_pool, + u->sessions_per_user_list_head_index, + oldest_index); + /* Get the list element */ + oldest = pool_elt_at_index (tsm->list_pool, oldest_index); + + /* Get the session index from the list element */ + ses_index = oldest->value; + + /* Get the session */ + s = pool_elt_at_index (tsm->sessions, ses_index); + } while (snat_is_session_static (s)); + + if (snat_is_unk_proto_session (s)) + { + /* Remove from lookup tables */ + key.l_addr = s->in2out.addr; + key.r_addr = s->ext_host_addr; + key.fib_index = s->in2out.fib_index; + key.proto = s->in2out.port; + s_kv.key[0] = key.as_u64[0]; + s_kv.key[1] = key.as_u64[1]; + if (clib_bihash_add_del_16_8 (&sm->in2out_unk_proto, &s_kv, 0)) + clib_warning ("in2out key del failed"); + + key.l_addr = s->out2in.addr; + key.fib_index = s->out2in.fib_index; + s_kv.key[0] = key.as_u64[0]; + s_kv.key[1] = key.as_u64[1]; + if (clib_bihash_add_del_16_8 (&sm->out2in_unk_proto, &s_kv, 0)) + clib_warning ("out2in key del failed"); + } + else + { + /* log NAT event */ + snat_ipfix_logging_nat44_ses_delete(s->in2out.addr.as_u32, + s->out2in.addr.as_u32, + s->in2out.protocol, + s->in2out.port, + s->out2in.port, + s->in2out.fib_index); + + snat_free_outside_address_and_port (sm, &s->out2in, + s->outside_address_index); + + /* Remove in2out, out2in keys */ + kv.key = s->in2out.as_u64; + if (clib_bihash_add_del_8_8 (&sm->in2out, &kv, 0)) + clib_warning ("in2out key del failed"); + kv.key = s->out2in.as_u64; + if (clib_bihash_add_del_8_8 (&sm->out2in, &kv, 0)) + clib_warning ("out2in key del failed"); + } + } + else + { + /* Create a new session */ + pool_get (tsm->sessions, s); + memset (s, 0, sizeof (*s)); + + /* Create list elts */ + pool_get (tsm->list_pool, elt); + clib_dlist_init (tsm->list_pool, elt - tsm->list_pool); + elt->value = s - tsm->sessions; + s->per_user_index = elt - tsm->list_pool; + s->per_user_list_head_index = u->sessions_per_user_list_head_index; + clib_dlist_addtail (tsm->list_pool, s->per_user_list_head_index, + s->per_user_index); + } + + s->ext_host_addr.as_u32 = ip->dst_address.as_u32; + s->flags |= SNAT_SESSION_FLAG_UNKNOWN_PROTO; + s->outside_address_index = address_index; + s->out2in.addr.as_u32 = new_addr; + s->out2in.fib_index = sm->outside_fib_index; + s->in2out.addr.as_u32 = old_addr; + s->in2out.fib_index = rx_fib_index; + s->in2out.port = s->out2in.port = ip->protocol; + if (is_sm) + { + u->nstaticsessions++; + s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING; + } + else + { + u->nsessions++; + } + + /* Add to lookup tables */ + key.l_addr.as_u32 = old_addr; + key.r_addr = ip->dst_address; + key.proto = ip->protocol; + key.fib_index = rx_fib_index; + s_kv.key[0] = key.as_u64[0]; + s_kv.key[1] = key.as_u64[1]; + s_kv.value = s - tsm->sessions; + if (clib_bihash_add_del_16_8 (&sm->in2out_unk_proto, &s_kv, 1)) + clib_warning ("in2out key add failed"); + + key.l_addr.as_u32 = new_addr; + key.fib_index = sm->outside_fib_index; + s_kv.key[0] = key.as_u64[0]; + s_kv.key[1] = key.as_u64[1]; + if (clib_bihash_add_del_16_8 (&sm->out2in_unk_proto, &s_kv, 1)) + clib_warning ("out2in key add failed"); + } + + /* Update IP checksum */ + sum = ip->checksum; + sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, src_address); + ip->checksum = ip_csum_fold (sum); + + /* Accounting */ + s->last_heard = now; + s->total_pkts++; + s->total_bytes += vlib_buffer_length_in_chain (vm, b); + /* Per-user LRU list maintenance */ + clib_dlist_remove (tsm->list_pool, s->per_user_index); + clib_dlist_addtail (tsm->list_pool, s->per_user_list_head_index, + s->per_user_index); + + /* Hairpinning */ + if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0) + snat_hairpinning_unknown_proto(sm, b, ip); + + if (vnet_buffer(b)->sw_if_index[VLIB_TX] == ~0) + vnet_buffer(b)->sw_if_index[VLIB_TX] = sm->outside_fib_index; +} + +static inline uword +snat_in2out_node_fn_inline (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame, int is_slow_path, + int is_output_feature) +{ + u32 n_left_from, * from, * to_next; + snat_in2out_next_t next_index; + u32 pkts_processed = 0; + snat_main_t * sm = &snat_main; + f64 now = vlib_time_now (vm); + u32 stats_node_index; + u32 thread_index = vlib_get_thread_index (); + + stats_node_index = is_slow_path ? snat_in2out_slowpath_node.index : + snat_in2out_node.index; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, + to_next, n_left_to_next); + + while (n_left_from >= 4 && n_left_to_next >= 2) + { + u32 bi0, bi1; + vlib_buffer_t * b0, * b1; + u32 next0, next1; + u32 sw_if_index0, sw_if_index1; + ip4_header_t * ip0, * ip1; + ip_csum_t sum0, sum1; + u32 new_addr0, old_addr0, new_addr1, old_addr1; + u16 old_port0, new_port0, old_port1, new_port1; + udp_header_t * udp0, * udp1; + tcp_header_t * tcp0, * tcp1; + icmp46_header_t * icmp0, * icmp1; + snat_session_key_t key0, key1; + u32 rx_fib_index0, rx_fib_index1; + u32 proto0, proto1; + snat_session_t * s0 = 0, * s1 = 0; + clib_bihash_kv_8_8_t kv0, value0, kv1, value1; + u32 iph_offset0 = 0, iph_offset1 = 0; + + /* Prefetch next iteration. */ + { + vlib_buffer_t * p2, * p3; + + p2 = vlib_get_buffer (vm, from[2]); + p3 = vlib_get_buffer (vm, from[3]); + + vlib_prefetch_buffer_header (p2, LOAD); + vlib_prefetch_buffer_header (p3, LOAD); + + CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE); + CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE); + } + + /* speculatively enqueue b0 and b1 to the current next frame */ + to_next[0] = bi0 = from[0]; + to_next[1] = bi1 = from[1]; + from += 2; + to_next += 2; + n_left_from -= 2; + n_left_to_next -= 2; + + b0 = vlib_get_buffer (vm, bi0); + b1 = vlib_get_buffer (vm, bi1); + + if (is_output_feature) + iph_offset0 = vnet_buffer (b0)->ip.save_rewrite_length; + + ip0 = (ip4_header_t *) ((u8 *) vlib_buffer_get_current (b0) + + iph_offset0); + + udp0 = ip4_next_header (ip0); + tcp0 = (tcp_header_t *) udp0; + icmp0 = (icmp46_header_t *) udp0; + + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = vec_elt (sm->ip4_main->fib_index_by_sw_if_index, + sw_if_index0); + + next0 = next1 = SNAT_IN2OUT_NEXT_LOOKUP; + + if (PREDICT_FALSE(ip0->ttl == 1)) + { + vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next0 = SNAT_IN2OUT_NEXT_ICMP_ERROR; + goto trace00; + } + + proto0 = ip_proto_to_snat_proto (ip0->protocol); + + /* Next configured feature, probably ip4-lookup */ + if (is_slow_path) + { + if (PREDICT_FALSE (proto0 == ~0)) + { + snat_in2out_unknown_proto (sm, b0, ip0, rx_fib_index0, + thread_index, now, vm); + goto trace00; + } + + if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP)) + { + next0 = icmp_in2out_slow_path + (sm, b0, ip0, icmp0, sw_if_index0, rx_fib_index0, + node, next0, now, thread_index, &s0); + goto trace00; + } + } + else + { + if (PREDICT_FALSE (proto0 == ~0 || proto0 == SNAT_PROTOCOL_ICMP)) + { + next0 = SNAT_IN2OUT_NEXT_SLOW_PATH; + goto trace00; + } + } + + key0.addr = ip0->src_address; + key0.port = udp0->src_port; + key0.protocol = proto0; + key0.fib_index = rx_fib_index0; + + kv0.key = key0.as_u64; + + if (PREDICT_FALSE (clib_bihash_search_8_8 (&sm->in2out, &kv0, &value0) != 0)) + { + if (is_slow_path) + { + if (PREDICT_FALSE(snat_not_translate(sm, node, sw_if_index0, + ip0, proto0, rx_fib_index0)) && !is_output_feature) + goto trace00; + + next0 = slow_path (sm, b0, ip0, rx_fib_index0, &key0, + &s0, node, next0, thread_index); + if (PREDICT_FALSE (next0 == SNAT_IN2OUT_NEXT_DROP)) + goto trace00; + } + else + { + next0 = SNAT_IN2OUT_NEXT_SLOW_PATH; + goto trace00; + } + } + else + s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, + value0.value); + + old_addr0 = ip0->src_address.as_u32; + ip0->src_address = s0->out2in.addr; + new_addr0 = ip0->src_address.as_u32; + if (!is_output_feature) + vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->out2in.fib_index; + + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + src_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + old_port0 = tcp0->src_port; + tcp0->src_port = s0->out2in.port; + new_port0 = tcp0->src_port; + + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + dst_address /* changed member */); + sum0 = ip_csum_update (sum0, old_port0, new_port0, + ip4_header_t /* cheat */, + length /* changed member */); + tcp0->checksum = ip_csum_fold(sum0); + } + else + { + old_port0 = udp0->src_port; + udp0->src_port = s0->out2in.port; + udp0->checksum = 0; + } + + /* Hairpinning */ + if (!is_output_feature) + snat_hairpinning (sm, b0, ip0, udp0, tcp0, proto0); + + /* Accounting */ + s0->last_heard = now; + s0->total_pkts++; + s0->total_bytes += vlib_buffer_length_in_chain (vm, b0); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s0)) + { + clib_dlist_remove (sm->per_thread_data[thread_index].list_pool, + s0->per_user_index); + clib_dlist_addtail (sm->per_thread_data[thread_index].list_pool, + s0->per_user_list_head_index, + s0->per_user_index); + } + trace00: + + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_in2out_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->is_slow_path = is_slow_path; + t->sw_if_index = sw_if_index0; + t->next_index = next0; + t->session_index = ~0; + if (s0) + t->session_index = s0 - sm->per_thread_data[thread_index].sessions; + } + + pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP; + + if (is_output_feature) + iph_offset1 = vnet_buffer (b1)->ip.save_rewrite_length; + + ip1 = (ip4_header_t *) ((u8 *) vlib_buffer_get_current (b1) + + iph_offset1); + + udp1 = ip4_next_header (ip1); + tcp1 = (tcp_header_t *) udp1; + icmp1 = (icmp46_header_t *) udp1; + + sw_if_index1 = vnet_buffer(b1)->sw_if_index[VLIB_RX]; + rx_fib_index1 = vec_elt (sm->ip4_main->fib_index_by_sw_if_index, + sw_if_index1); + + if (PREDICT_FALSE(ip1->ttl == 1)) + { + vnet_buffer (b1)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b1, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next1 = SNAT_IN2OUT_NEXT_ICMP_ERROR; + goto trace01; + } + + proto1 = ip_proto_to_snat_proto (ip1->protocol); + + /* Next configured feature, probably ip4-lookup */ + if (is_slow_path) + { + if (PREDICT_FALSE (proto1 == ~0)) + { + snat_in2out_unknown_proto (sm, b1, ip1, rx_fib_index1, + thread_index, now, vm); + goto trace01; + } + + if (PREDICT_FALSE (proto1 == SNAT_PROTOCOL_ICMP)) + { + next1 = icmp_in2out_slow_path + (sm, b1, ip1, icmp1, sw_if_index1, rx_fib_index1, node, + next1, now, thread_index, &s1); + goto trace01; + } + } + else + { + if (PREDICT_FALSE (proto1 == ~0 || proto1 == SNAT_PROTOCOL_ICMP)) + { + next1 = SNAT_IN2OUT_NEXT_SLOW_PATH; + goto trace01; + } + } + + key1.addr = ip1->src_address; + key1.port = udp1->src_port; + key1.protocol = proto1; + key1.fib_index = rx_fib_index1; + + kv1.key = key1.as_u64; + + if (PREDICT_FALSE(clib_bihash_search_8_8 (&sm->in2out, &kv1, &value1) != 0)) + { + if (is_slow_path) + { + if (PREDICT_FALSE(snat_not_translate(sm, node, sw_if_index1, + ip1, proto1, rx_fib_index1)) && !is_output_feature) + goto trace01; + + next1 = slow_path (sm, b1, ip1, rx_fib_index1, &key1, + &s1, node, next1, thread_index); + if (PREDICT_FALSE (next1 == SNAT_IN2OUT_NEXT_DROP)) + goto trace01; + } + else + { + next1 = SNAT_IN2OUT_NEXT_SLOW_PATH; + goto trace01; + } + } + else + s1 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, + value1.value); + + old_addr1 = ip1->src_address.as_u32; + ip1->src_address = s1->out2in.addr; + new_addr1 = ip1->src_address.as_u32; + if (!is_output_feature) + vnet_buffer(b1)->sw_if_index[VLIB_TX] = s1->out2in.fib_index; + + sum1 = ip1->checksum; + sum1 = ip_csum_update (sum1, old_addr1, new_addr1, + ip4_header_t, + src_address /* changed member */); + ip1->checksum = ip_csum_fold (sum1); + + if (PREDICT_TRUE(proto1 == SNAT_PROTOCOL_TCP)) + { + old_port1 = tcp1->src_port; + tcp1->src_port = s1->out2in.port; + new_port1 = tcp1->src_port; + + sum1 = tcp1->checksum; + sum1 = ip_csum_update (sum1, old_addr1, new_addr1, + ip4_header_t, + dst_address /* changed member */); + sum1 = ip_csum_update (sum1, old_port1, new_port1, + ip4_header_t /* cheat */, + length /* changed member */); + tcp1->checksum = ip_csum_fold(sum1); + } + else + { + old_port1 = udp1->src_port; + udp1->src_port = s1->out2in.port; + udp1->checksum = 0; + } + + /* Hairpinning */ + if (!is_output_feature) + snat_hairpinning (sm, b1, ip1, udp1, tcp1, proto1); + + /* Accounting */ + s1->last_heard = now; + s1->total_pkts++; + s1->total_bytes += vlib_buffer_length_in_chain (vm, b1); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s1)) + { + clib_dlist_remove (sm->per_thread_data[thread_index].list_pool, + s1->per_user_index); + clib_dlist_addtail (sm->per_thread_data[thread_index].list_pool, + s1->per_user_list_head_index, + s1->per_user_index); + } + trace01: + + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b1->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_in2out_trace_t *t = + vlib_add_trace (vm, node, b1, sizeof (*t)); + t->sw_if_index = sw_if_index1; + t->next_index = next1; + t->session_index = ~0; + if (s1) + t->session_index = s1 - sm->per_thread_data[thread_index].sessions; + } + + pkts_processed += next1 != SNAT_IN2OUT_NEXT_DROP; + + /* verify speculative enqueues, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x2 (vm, node, next_index, + to_next, n_left_to_next, + bi0, bi1, next0, next1); + } + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t * b0; + u32 next0; + u32 sw_if_index0; + ip4_header_t * ip0; + ip_csum_t sum0; + u32 new_addr0, old_addr0; + u16 old_port0, new_port0; + udp_header_t * udp0; + tcp_header_t * tcp0; + icmp46_header_t * icmp0; + snat_session_key_t key0; + u32 rx_fib_index0; + u32 proto0; + snat_session_t * s0 = 0; + clib_bihash_kv_8_8_t kv0, value0; + u32 iph_offset0 = 0; + + /* speculatively enqueue b0 to the current next frame */ + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + next0 = SNAT_IN2OUT_NEXT_LOOKUP; + + if (is_output_feature) + iph_offset0 = vnet_buffer (b0)->ip.save_rewrite_length; + + ip0 = (ip4_header_t *) ((u8 *) vlib_buffer_get_current (b0) + + iph_offset0); + + udp0 = ip4_next_header (ip0); + tcp0 = (tcp_header_t *) udp0; + icmp0 = (icmp46_header_t *) udp0; + + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = vec_elt (sm->ip4_main->fib_index_by_sw_if_index, + sw_if_index0); + + if (PREDICT_FALSE(ip0->ttl == 1)) + { + vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next0 = SNAT_IN2OUT_NEXT_ICMP_ERROR; + goto trace0; + } + + proto0 = ip_proto_to_snat_proto (ip0->protocol); + + /* Next configured feature, probably ip4-lookup */ + if (is_slow_path) + { + if (PREDICT_FALSE (proto0 == ~0)) + { + snat_in2out_unknown_proto (sm, b0, ip0, rx_fib_index0, + thread_index, now, vm); + goto trace0; + } + + if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP)) + { + next0 = icmp_in2out_slow_path + (sm, b0, ip0, icmp0, sw_if_index0, rx_fib_index0, node, + next0, now, thread_index, &s0); + goto trace0; + } + } + else + { + if (PREDICT_FALSE (proto0 == ~0 || proto0 == SNAT_PROTOCOL_ICMP)) + { + next0 = SNAT_IN2OUT_NEXT_SLOW_PATH; + goto trace0; + } + } + + key0.addr = ip0->src_address; + key0.port = udp0->src_port; + key0.protocol = proto0; + key0.fib_index = rx_fib_index0; + + kv0.key = key0.as_u64; + + if (clib_bihash_search_8_8 (&sm->in2out, &kv0, &value0)) + { + if (is_slow_path) + { + if (PREDICT_FALSE(snat_not_translate(sm, node, sw_if_index0, + ip0, proto0, rx_fib_index0)) && !is_output_feature) + goto trace0; + + next0 = slow_path (sm, b0, ip0, rx_fib_index0, &key0, + &s0, node, next0, thread_index); + + if (PREDICT_FALSE (next0 == SNAT_IN2OUT_NEXT_DROP)) + goto trace0; + } + else + { + next0 = SNAT_IN2OUT_NEXT_SLOW_PATH; + goto trace0; + } + } + else + s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, + value0.value); + + old_addr0 = ip0->src_address.as_u32; + ip0->src_address = s0->out2in.addr; + new_addr0 = ip0->src_address.as_u32; + if (!is_output_feature) + vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->out2in.fib_index; + + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + src_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + old_port0 = tcp0->src_port; + tcp0->src_port = s0->out2in.port; + new_port0 = tcp0->src_port; + + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + dst_address /* changed member */); + sum0 = ip_csum_update (sum0, old_port0, new_port0, + ip4_header_t /* cheat */, + length /* changed member */); + tcp0->checksum = ip_csum_fold(sum0); + } + else + { + old_port0 = udp0->src_port; + udp0->src_port = s0->out2in.port; + udp0->checksum = 0; + } + + /* Hairpinning */ + if (!is_output_feature) + snat_hairpinning (sm, b0, ip0, udp0, tcp0, proto0); + + /* Accounting */ + s0->last_heard = now; + s0->total_pkts++; + s0->total_bytes += vlib_buffer_length_in_chain (vm, b0); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s0)) + { + clib_dlist_remove (sm->per_thread_data[thread_index].list_pool, + s0->per_user_index); + clib_dlist_addtail (sm->per_thread_data[thread_index].list_pool, + s0->per_user_list_head_index, + s0->per_user_index); + } + + trace0: + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_in2out_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->is_slow_path = is_slow_path; + t->sw_if_index = sw_if_index0; + t->next_index = next0; + t->session_index = ~0; + if (s0) + t->session_index = s0 - sm->per_thread_data[thread_index].sessions; + } + + pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP; + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + vlib_node_increment_counter (vm, stats_node_index, + SNAT_IN2OUT_ERROR_IN2OUT_PACKETS, + pkts_processed); + return frame->n_vectors; +} + +static uword +snat_in2out_fast_path_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return snat_in2out_node_fn_inline (vm, node, frame, 0 /* is_slow_path */, 0); +} + +VLIB_REGISTER_NODE (snat_in2out_node) = { + .function = snat_in2out_fast_path_fn, + .name = "nat44-in2out", + .vector_size = sizeof (u32), + .format_trace = format_snat_in2out_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(snat_in2out_error_strings), + .error_strings = snat_in2out_error_strings, + + .runtime_data_bytes = sizeof (snat_runtime_t), + + .n_next_nodes = SNAT_IN2OUT_N_NEXT, + + /* edit / add dispositions here */ + .next_nodes = { + [SNAT_IN2OUT_NEXT_DROP] = "error-drop", + [SNAT_IN2OUT_NEXT_LOOKUP] = "ip4-lookup", + [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-in2out-slowpath", + [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (snat_in2out_node, snat_in2out_fast_path_fn); + +static uword +snat_in2out_output_fast_path_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return snat_in2out_node_fn_inline (vm, node, frame, 0 /* is_slow_path */, 1); +} + +VLIB_REGISTER_NODE (snat_in2out_output_node) = { + .function = snat_in2out_output_fast_path_fn, + .name = "nat44-in2out-output", + .vector_size = sizeof (u32), + .format_trace = format_snat_in2out_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(snat_in2out_error_strings), + .error_strings = snat_in2out_error_strings, + + .runtime_data_bytes = sizeof (snat_runtime_t), + + .n_next_nodes = SNAT_IN2OUT_N_NEXT, + + /* edit / add dispositions here */ + .next_nodes = { + [SNAT_IN2OUT_NEXT_DROP] = "error-drop", + [SNAT_IN2OUT_NEXT_LOOKUP] = "interface-output", + [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-in2out-output-slowpath", + [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (snat_in2out_output_node, + snat_in2out_output_fast_path_fn); + +static uword +snat_in2out_slow_path_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return snat_in2out_node_fn_inline (vm, node, frame, 1 /* is_slow_path */, 0); +} + +VLIB_REGISTER_NODE (snat_in2out_slowpath_node) = { + .function = snat_in2out_slow_path_fn, + .name = "nat44-in2out-slowpath", + .vector_size = sizeof (u32), + .format_trace = format_snat_in2out_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(snat_in2out_error_strings), + .error_strings = snat_in2out_error_strings, + + .runtime_data_bytes = sizeof (snat_runtime_t), + + .n_next_nodes = SNAT_IN2OUT_N_NEXT, + + /* edit / add dispositions here */ + .next_nodes = { + [SNAT_IN2OUT_NEXT_DROP] = "error-drop", + [SNAT_IN2OUT_NEXT_LOOKUP] = "ip4-lookup", + [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-in2out-slowpath", + [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (snat_in2out_slowpath_node, + snat_in2out_slow_path_fn); + +static uword +snat_in2out_output_slow_path_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return snat_in2out_node_fn_inline (vm, node, frame, 1 /* is_slow_path */, 1); +} + +VLIB_REGISTER_NODE (snat_in2out_output_slowpath_node) = { + .function = snat_in2out_output_slow_path_fn, + .name = "nat44-in2out-output-slowpath", + .vector_size = sizeof (u32), + .format_trace = format_snat_in2out_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(snat_in2out_error_strings), + .error_strings = snat_in2out_error_strings, + + .runtime_data_bytes = sizeof (snat_runtime_t), + + .n_next_nodes = SNAT_IN2OUT_N_NEXT, + + /* edit / add dispositions here */ + .next_nodes = { + [SNAT_IN2OUT_NEXT_DROP] = "error-drop", + [SNAT_IN2OUT_NEXT_LOOKUP] = "interface-output", + [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-in2out-output-slowpath", + [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (snat_in2out_output_slowpath_node, + snat_in2out_output_slow_path_fn); + +/**************************/ +/*** deterministic mode ***/ +/**************************/ +static uword +snat_det_in2out_node_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + u32 n_left_from, * from, * to_next; + snat_in2out_next_t next_index; + u32 pkts_processed = 0; + snat_main_t * sm = &snat_main; + u32 now = (u32) vlib_time_now (vm); + u32 thread_index = vlib_get_thread_index (); + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, + to_next, n_left_to_next); + + while (n_left_from >= 4 && n_left_to_next >= 2) + { + u32 bi0, bi1; + vlib_buffer_t * b0, * b1; + u32 next0, next1; + u32 sw_if_index0, sw_if_index1; + ip4_header_t * ip0, * ip1; + ip_csum_t sum0, sum1; + ip4_address_t new_addr0, old_addr0, new_addr1, old_addr1; + u16 old_port0, new_port0, lo_port0, i0; + u16 old_port1, new_port1, lo_port1, i1; + udp_header_t * udp0, * udp1; + tcp_header_t * tcp0, * tcp1; + u32 proto0, proto1; + snat_det_out_key_t key0, key1; + snat_det_map_t * dm0, * dm1; + snat_det_session_t * ses0 = 0, * ses1 = 0; + u32 rx_fib_index0, rx_fib_index1; + icmp46_header_t * icmp0, * icmp1; + + /* Prefetch next iteration. */ + { + vlib_buffer_t * p2, * p3; + + p2 = vlib_get_buffer (vm, from[2]); + p3 = vlib_get_buffer (vm, from[3]); + + vlib_prefetch_buffer_header (p2, LOAD); + vlib_prefetch_buffer_header (p3, LOAD); + + CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE); + CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE); + } + + /* speculatively enqueue b0 and b1 to the current next frame */ + to_next[0] = bi0 = from[0]; + to_next[1] = bi1 = from[1]; + from += 2; + to_next += 2; + n_left_from -= 2; + n_left_to_next -= 2; + + b0 = vlib_get_buffer (vm, bi0); + b1 = vlib_get_buffer (vm, bi1); + + next0 = SNAT_IN2OUT_NEXT_LOOKUP; + next1 = SNAT_IN2OUT_NEXT_LOOKUP; + + ip0 = vlib_buffer_get_current (b0); + udp0 = ip4_next_header (ip0); + tcp0 = (tcp_header_t *) udp0; + + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + + if (PREDICT_FALSE(ip0->ttl == 1)) + { + vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next0 = SNAT_IN2OUT_NEXT_ICMP_ERROR; + goto trace0; + } + + proto0 = ip_proto_to_snat_proto (ip0->protocol); + + if (PREDICT_FALSE(proto0 == SNAT_PROTOCOL_ICMP)) + { + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index(sw_if_index0); + icmp0 = (icmp46_header_t *) udp0; + + next0 = icmp_in2out(sm, b0, ip0, icmp0, sw_if_index0, + rx_fib_index0, node, next0, thread_index, + &ses0, &dm0); + goto trace0; + } + + dm0 = snat_det_map_by_user(sm, &ip0->src_address); + if (PREDICT_FALSE(!dm0)) + { + clib_warning("no match for internal host %U", + format_ip4_address, &ip0->src_address); + next0 = SNAT_IN2OUT_NEXT_DROP; + b0->error = node->errors[SNAT_IN2OUT_ERROR_NO_TRANSLATION]; + goto trace0; + } + + snat_det_forward(dm0, &ip0->src_address, &new_addr0, &lo_port0); + + key0.ext_host_addr = ip0->dst_address; + key0.ext_host_port = tcp0->dst; + + ses0 = snat_det_find_ses_by_in(dm0, &ip0->src_address, tcp0->src, key0); + if (PREDICT_FALSE(!ses0)) + { + for (i0 = 0; i0 < dm0->ports_per_host; i0++) + { + key0.out_port = clib_host_to_net_u16 (lo_port0 + + ((i0 + clib_net_to_host_u16 (tcp0->src)) % dm0->ports_per_host)); + + if (snat_det_get_ses_by_out (dm0, &ip0->src_address, key0.as_u64)) + continue; + + ses0 = snat_det_ses_create(dm0, &ip0->src_address, tcp0->src, &key0); + break; + } + if (PREDICT_FALSE(!ses0)) + { + /* too many sessions for user, send ICMP error packet */ + + vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b0, ICMP4_destination_unreachable, + ICMP4_destination_unreachable_destination_unreachable_host, + 0); + next0 = SNAT_IN2OUT_NEXT_ICMP_ERROR; + goto trace0; + } + } + + new_port0 = ses0->out.out_port; + + old_addr0.as_u32 = ip0->src_address.as_u32; + ip0->src_address.as_u32 = new_addr0.as_u32; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = sm->outside_fib_index; + + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_addr0.as_u32, new_addr0.as_u32, + ip4_header_t, + src_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + if (tcp0->flags & TCP_FLAG_SYN) + ses0->state = SNAT_SESSION_TCP_SYN_SENT; + else if (tcp0->flags & TCP_FLAG_ACK && ses0->state == SNAT_SESSION_TCP_SYN_SENT) + ses0->state = SNAT_SESSION_TCP_ESTABLISHED; + else if (tcp0->flags & TCP_FLAG_FIN && ses0->state == SNAT_SESSION_TCP_ESTABLISHED) + ses0->state = SNAT_SESSION_TCP_FIN_WAIT; + else if (tcp0->flags & TCP_FLAG_ACK && ses0->state == SNAT_SESSION_TCP_FIN_WAIT) + snat_det_ses_close(dm0, ses0); + else if (tcp0->flags & TCP_FLAG_FIN && ses0->state == SNAT_SESSION_TCP_CLOSE_WAIT) + ses0->state = SNAT_SESSION_TCP_LAST_ACK; + else if (tcp0->flags == 0 && ses0->state == SNAT_SESSION_UNKNOWN) + ses0->state = SNAT_SESSION_TCP_ESTABLISHED; + + old_port0 = tcp0->src; + tcp0->src = new_port0; + + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0.as_u32, new_addr0.as_u32, + ip4_header_t, + dst_address /* changed member */); + sum0 = ip_csum_update (sum0, old_port0, new_port0, + ip4_header_t /* cheat */, + length /* changed member */); + tcp0->checksum = ip_csum_fold(sum0); + } + else + { + ses0->state = SNAT_SESSION_UDP_ACTIVE; + old_port0 = udp0->src_port; + udp0->src_port = new_port0; + udp0->checksum = 0; + } + + switch(ses0->state) + { + case SNAT_SESSION_UDP_ACTIVE: + ses0->expire = now + sm->udp_timeout; + break; + case SNAT_SESSION_TCP_SYN_SENT: + case SNAT_SESSION_TCP_FIN_WAIT: + case SNAT_SESSION_TCP_CLOSE_WAIT: + case SNAT_SESSION_TCP_LAST_ACK: + ses0->expire = now + sm->tcp_transitory_timeout; + break; + case SNAT_SESSION_TCP_ESTABLISHED: + ses0->expire = now + sm->tcp_established_timeout; + break; + } + + trace0: + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_in2out_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->is_slow_path = 0; + t->sw_if_index = sw_if_index0; + t->next_index = next0; + t->session_index = ~0; + if (ses0) + t->session_index = ses0 - dm0->sessions; + } + + pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP; + + ip1 = vlib_buffer_get_current (b1); + udp1 = ip4_next_header (ip1); + tcp1 = (tcp_header_t *) udp1; + + sw_if_index1 = vnet_buffer(b1)->sw_if_index[VLIB_RX]; + + if (PREDICT_FALSE(ip1->ttl == 1)) + { + vnet_buffer (b1)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b1, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next1 = SNAT_IN2OUT_NEXT_ICMP_ERROR; + goto trace1; + } + + proto1 = ip_proto_to_snat_proto (ip1->protocol); + + if (PREDICT_FALSE(proto1 == SNAT_PROTOCOL_ICMP)) + { + rx_fib_index1 = ip4_fib_table_get_index_for_sw_if_index(sw_if_index1); + icmp1 = (icmp46_header_t *) udp1; + + next1 = icmp_in2out(sm, b1, ip1, icmp1, sw_if_index1, + rx_fib_index1, node, next1, thread_index, + &ses1, &dm1); + goto trace1; + } + + dm1 = snat_det_map_by_user(sm, &ip1->src_address); + if (PREDICT_FALSE(!dm1)) + { + clib_warning("no match for internal host %U", + format_ip4_address, &ip0->src_address); + next1 = SNAT_IN2OUT_NEXT_DROP; + b1->error = node->errors[SNAT_IN2OUT_ERROR_NO_TRANSLATION]; + goto trace1; + } + + snat_det_forward(dm1, &ip1->src_address, &new_addr1, &lo_port1); + + key1.ext_host_addr = ip1->dst_address; + key1.ext_host_port = tcp1->dst; + + ses1 = snat_det_find_ses_by_in(dm1, &ip1->src_address, tcp1->src, key1); + if (PREDICT_FALSE(!ses1)) + { + for (i1 = 0; i1 < dm1->ports_per_host; i1++) + { + key1.out_port = clib_host_to_net_u16 (lo_port1 + + ((i1 + clib_net_to_host_u16 (tcp1->src)) % dm1->ports_per_host)); + + if (snat_det_get_ses_by_out (dm1, &ip1->src_address, key1.as_u64)) + continue; + + ses1 = snat_det_ses_create(dm1, &ip1->src_address, tcp1->src, &key1); + break; + } + if (PREDICT_FALSE(!ses1)) + { + /* too many sessions for user, send ICMP error packet */ + + vnet_buffer (b1)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b1, ICMP4_destination_unreachable, + ICMP4_destination_unreachable_destination_unreachable_host, + 0); + next1 = SNAT_IN2OUT_NEXT_ICMP_ERROR; + goto trace1; + } + } + + new_port1 = ses1->out.out_port; + + old_addr1.as_u32 = ip1->src_address.as_u32; + ip1->src_address.as_u32 = new_addr1.as_u32; + vnet_buffer(b1)->sw_if_index[VLIB_TX] = sm->outside_fib_index; + + sum1 = ip1->checksum; + sum1 = ip_csum_update (sum1, old_addr1.as_u32, new_addr1.as_u32, + ip4_header_t, + src_address /* changed member */); + ip1->checksum = ip_csum_fold (sum1); + + if (PREDICT_TRUE(proto1 == SNAT_PROTOCOL_TCP)) + { + if (tcp1->flags & TCP_FLAG_SYN) + ses1->state = SNAT_SESSION_TCP_SYN_SENT; + else if (tcp1->flags & TCP_FLAG_ACK && ses1->state == SNAT_SESSION_TCP_SYN_SENT) + ses1->state = SNAT_SESSION_TCP_ESTABLISHED; + else if (tcp1->flags & TCP_FLAG_FIN && ses1->state == SNAT_SESSION_TCP_ESTABLISHED) + ses1->state = SNAT_SESSION_TCP_FIN_WAIT; + else if (tcp1->flags & TCP_FLAG_ACK && ses1->state == SNAT_SESSION_TCP_FIN_WAIT) + snat_det_ses_close(dm1, ses1); + else if (tcp1->flags & TCP_FLAG_FIN && ses1->state == SNAT_SESSION_TCP_CLOSE_WAIT) + ses1->state = SNAT_SESSION_TCP_LAST_ACK; + else if (tcp1->flags == 0 && ses1->state == SNAT_SESSION_UNKNOWN) + ses1->state = SNAT_SESSION_TCP_ESTABLISHED; + + old_port1 = tcp1->src; + tcp1->src = new_port1; + + sum1 = tcp1->checksum; + sum1 = ip_csum_update (sum1, old_addr1.as_u32, new_addr1.as_u32, + ip4_header_t, + dst_address /* changed member */); + sum1 = ip_csum_update (sum1, old_port1, new_port1, + ip4_header_t /* cheat */, + length /* changed member */); + tcp1->checksum = ip_csum_fold(sum1); + } + else + { + ses1->state = SNAT_SESSION_UDP_ACTIVE; + old_port1 = udp1->src_port; + udp1->src_port = new_port1; + udp1->checksum = 0; + } + + switch(ses1->state) + { + case SNAT_SESSION_UDP_ACTIVE: + ses1->expire = now + sm->udp_timeout; + break; + case SNAT_SESSION_TCP_SYN_SENT: + case SNAT_SESSION_TCP_FIN_WAIT: + case SNAT_SESSION_TCP_CLOSE_WAIT: + case SNAT_SESSION_TCP_LAST_ACK: + ses1->expire = now + sm->tcp_transitory_timeout; + break; + case SNAT_SESSION_TCP_ESTABLISHED: + ses1->expire = now + sm->tcp_established_timeout; + break; + } + + trace1: + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b1->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_in2out_trace_t *t = + vlib_add_trace (vm, node, b1, sizeof (*t)); + t->is_slow_path = 0; + t->sw_if_index = sw_if_index1; + t->next_index = next1; + t->session_index = ~0; + if (ses1) + t->session_index = ses1 - dm1->sessions; + } + + pkts_processed += next1 != SNAT_IN2OUT_NEXT_DROP; + + /* verify speculative enqueues, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x2 (vm, node, next_index, + to_next, n_left_to_next, + bi0, bi1, next0, next1); + } + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t * b0; + u32 next0; + u32 sw_if_index0; + ip4_header_t * ip0; + ip_csum_t sum0; + ip4_address_t new_addr0, old_addr0; + u16 old_port0, new_port0, lo_port0, i0; + udp_header_t * udp0; + tcp_header_t * tcp0; + u32 proto0; + snat_det_out_key_t key0; + snat_det_map_t * dm0; + snat_det_session_t * ses0 = 0; + u32 rx_fib_index0; + icmp46_header_t * icmp0; + + /* speculatively enqueue b0 to the current next frame */ + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + next0 = SNAT_IN2OUT_NEXT_LOOKUP; + + ip0 = vlib_buffer_get_current (b0); + udp0 = ip4_next_header (ip0); + tcp0 = (tcp_header_t *) udp0; + + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + + if (PREDICT_FALSE(ip0->ttl == 1)) + { + vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next0 = SNAT_IN2OUT_NEXT_ICMP_ERROR; + goto trace00; + } + + proto0 = ip_proto_to_snat_proto (ip0->protocol); + + if (PREDICT_FALSE(proto0 == SNAT_PROTOCOL_ICMP)) + { + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index(sw_if_index0); + icmp0 = (icmp46_header_t *) udp0; + + next0 = icmp_in2out(sm, b0, ip0, icmp0, sw_if_index0, + rx_fib_index0, node, next0, thread_index, + &ses0, &dm0); + goto trace00; + } + + dm0 = snat_det_map_by_user(sm, &ip0->src_address); + if (PREDICT_FALSE(!dm0)) + { + clib_warning("no match for internal host %U", + format_ip4_address, &ip0->src_address); + next0 = SNAT_IN2OUT_NEXT_DROP; + b0->error = node->errors[SNAT_IN2OUT_ERROR_NO_TRANSLATION]; + goto trace00; + } + + snat_det_forward(dm0, &ip0->src_address, &new_addr0, &lo_port0); + + key0.ext_host_addr = ip0->dst_address; + key0.ext_host_port = tcp0->dst; + + ses0 = snat_det_find_ses_by_in(dm0, &ip0->src_address, tcp0->src, key0); + if (PREDICT_FALSE(!ses0)) + { + for (i0 = 0; i0 < dm0->ports_per_host; i0++) + { + key0.out_port = clib_host_to_net_u16 (lo_port0 + + ((i0 + clib_net_to_host_u16 (tcp0->src)) % dm0->ports_per_host)); + + if (snat_det_get_ses_by_out (dm0, &ip0->src_address, key0.as_u64)) + continue; + + ses0 = snat_det_ses_create(dm0, &ip0->src_address, tcp0->src, &key0); + break; + } + if (PREDICT_FALSE(!ses0)) + { + /* too many sessions for user, send ICMP error packet */ + + vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b0, ICMP4_destination_unreachable, + ICMP4_destination_unreachable_destination_unreachable_host, + 0); + next0 = SNAT_IN2OUT_NEXT_ICMP_ERROR; + goto trace00; + } + } + + new_port0 = ses0->out.out_port; + + old_addr0.as_u32 = ip0->src_address.as_u32; + ip0->src_address.as_u32 = new_addr0.as_u32; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = sm->outside_fib_index; + + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_addr0.as_u32, new_addr0.as_u32, + ip4_header_t, + src_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + if (tcp0->flags & TCP_FLAG_SYN) + ses0->state = SNAT_SESSION_TCP_SYN_SENT; + else if (tcp0->flags & TCP_FLAG_ACK && ses0->state == SNAT_SESSION_TCP_SYN_SENT) + ses0->state = SNAT_SESSION_TCP_ESTABLISHED; + else if (tcp0->flags & TCP_FLAG_FIN && ses0->state == SNAT_SESSION_TCP_ESTABLISHED) + ses0->state = SNAT_SESSION_TCP_FIN_WAIT; + else if (tcp0->flags & TCP_FLAG_ACK && ses0->state == SNAT_SESSION_TCP_FIN_WAIT) + snat_det_ses_close(dm0, ses0); + else if (tcp0->flags & TCP_FLAG_FIN && ses0->state == SNAT_SESSION_TCP_CLOSE_WAIT) + ses0->state = SNAT_SESSION_TCP_LAST_ACK; + else if (tcp0->flags == 0 && ses0->state == SNAT_SESSION_UNKNOWN) + ses0->state = SNAT_SESSION_TCP_ESTABLISHED; + + old_port0 = tcp0->src; + tcp0->src = new_port0; + + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0.as_u32, new_addr0.as_u32, + ip4_header_t, + dst_address /* changed member */); + sum0 = ip_csum_update (sum0, old_port0, new_port0, + ip4_header_t /* cheat */, + length /* changed member */); + tcp0->checksum = ip_csum_fold(sum0); + } + else + { + ses0->state = SNAT_SESSION_UDP_ACTIVE; + old_port0 = udp0->src_port; + udp0->src_port = new_port0; + udp0->checksum = 0; + } + + switch(ses0->state) + { + case SNAT_SESSION_UDP_ACTIVE: + ses0->expire = now + sm->udp_timeout; + break; + case SNAT_SESSION_TCP_SYN_SENT: + case SNAT_SESSION_TCP_FIN_WAIT: + case SNAT_SESSION_TCP_CLOSE_WAIT: + case SNAT_SESSION_TCP_LAST_ACK: + ses0->expire = now + sm->tcp_transitory_timeout; + break; + case SNAT_SESSION_TCP_ESTABLISHED: + ses0->expire = now + sm->tcp_established_timeout; + break; + } + + trace00: + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_in2out_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->is_slow_path = 0; + t->sw_if_index = sw_if_index0; + t->next_index = next0; + t->session_index = ~0; + if (ses0) + t->session_index = ses0 - dm0->sessions; + } + + pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP; + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + vlib_node_increment_counter (vm, snat_det_in2out_node.index, + SNAT_IN2OUT_ERROR_IN2OUT_PACKETS, + pkts_processed); + return frame->n_vectors; +} + +VLIB_REGISTER_NODE (snat_det_in2out_node) = { + .function = snat_det_in2out_node_fn, + .name = "nat44-det-in2out", + .vector_size = sizeof (u32), + .format_trace = format_snat_in2out_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(snat_in2out_error_strings), + .error_strings = snat_in2out_error_strings, + + .runtime_data_bytes = sizeof (snat_runtime_t), + + .n_next_nodes = 3, + + /* edit / add dispositions here */ + .next_nodes = { + [SNAT_IN2OUT_NEXT_DROP] = "error-drop", + [SNAT_IN2OUT_NEXT_LOOKUP] = "ip4-lookup", + [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (snat_det_in2out_node, snat_det_in2out_node_fn); + +/** + * Get address and port values to be used for ICMP packet translation + * and create session if needed + * + * @param[in,out] sm NAT main + * @param[in,out] node NAT node runtime + * @param[in] thread_index thread index + * @param[in,out] b0 buffer containing packet to be translated + * @param[out] p_proto protocol used for matching + * @param[out] p_value address and port after NAT translation + * @param[out] p_dont_translate if packet should not be translated + * @param d optional parameter + * @param e optional parameter + */ +u32 icmp_match_in2out_det(snat_main_t *sm, vlib_node_runtime_t *node, + u32 thread_index, vlib_buffer_t *b0, u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, void *d, void *e) +{ + ip4_header_t *ip0; + icmp46_header_t *icmp0; + u32 sw_if_index0; + u32 rx_fib_index0; + u8 protocol; + snat_det_out_key_t key0; + u8 dont_translate = 0; + u32 next0 = ~0; + icmp_echo_header_t *echo0, *inner_echo0 = 0; + ip4_header_t *inner_ip0; + void *l4_header = 0; + icmp46_header_t *inner_icmp0; + snat_det_map_t * dm0 = 0; + ip4_address_t new_addr0; + u16 lo_port0, i0; + snat_det_session_t * ses0 = 0; + ip4_address_t in_addr; + u16 in_port; + + ip0 = vlib_buffer_get_current (b0); + icmp0 = (icmp46_header_t *) ip4_next_header (ip0); + echo0 = (icmp_echo_header_t *)(icmp0+1); + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index (sw_if_index0); + + if (!icmp_is_error_message (icmp0)) + { + protocol = SNAT_PROTOCOL_ICMP; + in_addr = ip0->src_address; + in_port = echo0->identifier; + } + else + { + inner_ip0 = (ip4_header_t *)(echo0+1); + l4_header = ip4_next_header (inner_ip0); + protocol = ip_proto_to_snat_proto (inner_ip0->protocol); + in_addr = inner_ip0->dst_address; + switch (protocol) + { + case SNAT_PROTOCOL_ICMP: + inner_icmp0 = (icmp46_header_t*)l4_header; + inner_echo0 = (icmp_echo_header_t *)(inner_icmp0+1); + in_port = inner_echo0->identifier; + break; + case SNAT_PROTOCOL_UDP: + case SNAT_PROTOCOL_TCP: + in_port = ((tcp_udp_header_t*)l4_header)->dst_port; + break; + default: + b0->error = node->errors[SNAT_IN2OUT_ERROR_UNSUPPORTED_PROTOCOL]; + next0 = SNAT_IN2OUT_NEXT_DROP; + goto out; + } + } + + dm0 = snat_det_map_by_user(sm, &in_addr); + if (PREDICT_FALSE(!dm0)) + { + clib_warning("no match for internal host %U", + format_ip4_address, &in_addr); + if (PREDICT_FALSE(snat_not_translate_fast(sm, node, sw_if_index0, ip0, + IP_PROTOCOL_ICMP, rx_fib_index0))) + { + dont_translate = 1; + goto out; + } + next0 = SNAT_IN2OUT_NEXT_DROP; + b0->error = node->errors[SNAT_IN2OUT_ERROR_NO_TRANSLATION]; + goto out; + } + + snat_det_forward(dm0, &in_addr, &new_addr0, &lo_port0); + + key0.ext_host_addr = ip0->dst_address; + key0.ext_host_port = 0; + + ses0 = snat_det_find_ses_by_in(dm0, &in_addr, in_port, key0); + if (PREDICT_FALSE(!ses0)) + { + if (PREDICT_FALSE(snat_not_translate_fast(sm, node, sw_if_index0, ip0, + IP_PROTOCOL_ICMP, rx_fib_index0))) + { + dont_translate = 1; + goto out; + } + if (icmp0->type != ICMP4_echo_request) + { + b0->error = node->errors[SNAT_IN2OUT_ERROR_BAD_ICMP_TYPE]; + next0 = SNAT_IN2OUT_NEXT_DROP; + goto out; + } + for (i0 = 0; i0 < dm0->ports_per_host; i0++) + { + key0.out_port = clib_host_to_net_u16 (lo_port0 + + ((i0 + clib_net_to_host_u16 (echo0->identifier)) % dm0->ports_per_host)); + + if (snat_det_get_ses_by_out (dm0, &in_addr, key0.as_u64)) + continue; + + ses0 = snat_det_ses_create(dm0, &in_addr, echo0->identifier, &key0); + break; + } + if (PREDICT_FALSE(!ses0)) + { + next0 = SNAT_IN2OUT_NEXT_DROP; + b0->error = node->errors[SNAT_IN2OUT_ERROR_OUT_OF_PORTS]; + goto out; + } + } + + if (PREDICT_FALSE(icmp0->type != ICMP4_echo_request && + !icmp_is_error_message (icmp0))) + { + b0->error = node->errors[SNAT_IN2OUT_ERROR_BAD_ICMP_TYPE]; + next0 = SNAT_IN2OUT_NEXT_DROP; + goto out; + } + + u32 now = (u32) vlib_time_now (sm->vlib_main); + + ses0->state = SNAT_SESSION_ICMP_ACTIVE; + ses0->expire = now + sm->icmp_timeout; + +out: + *p_proto = protocol; + if (ses0) + { + p_value->addr = new_addr0; + p_value->fib_index = sm->outside_fib_index; + p_value->port = ses0->out.out_port; + } + *p_dont_translate = dont_translate; + if (d) + *(snat_det_session_t**)d = ses0; + if (e) + *(snat_det_map_t**)e = dm0; + return next0; +} + +/**********************/ +/*** worker handoff ***/ +/**********************/ +static inline uword +snat_in2out_worker_handoff_fn_inline (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame, + u8 is_output) +{ + snat_main_t *sm = &snat_main; + vlib_thread_main_t *tm = vlib_get_thread_main (); + u32 n_left_from, *from, *to_next = 0; + static __thread vlib_frame_queue_elt_t **handoff_queue_elt_by_worker_index; + static __thread vlib_frame_queue_t **congested_handoff_queue_by_worker_index + = 0; + vlib_frame_queue_elt_t *hf = 0; + vlib_frame_t *f = 0; + int i; + u32 n_left_to_next_worker = 0, *to_next_worker = 0; + u32 next_worker_index = 0; + u32 current_worker_index = ~0; + u32 thread_index = vlib_get_thread_index (); + u32 fq_index; + u32 to_node_index; + + ASSERT (vec_len (sm->workers)); + + if (is_output) + { + fq_index = sm->fq_in2out_output_index; + to_node_index = sm->in2out_output_node_index; + } + else + { + fq_index = sm->fq_in2out_index; + to_node_index = sm->in2out_node_index; + } + + if (PREDICT_FALSE (handoff_queue_elt_by_worker_index == 0)) + { + vec_validate (handoff_queue_elt_by_worker_index, tm->n_vlib_mains - 1); + + vec_validate_init_empty (congested_handoff_queue_by_worker_index, + sm->first_worker_index + sm->num_workers - 1, + (vlib_frame_queue_t *) (~0)); + } + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + + while (n_left_from > 0) + { + u32 bi0; + vlib_buffer_t *b0; + u32 sw_if_index0; + u32 rx_fib_index0; + ip4_header_t * ip0; + u8 do_handoff; + + bi0 = from[0]; + from += 1; + n_left_from -= 1; + + b0 = vlib_get_buffer (vm, bi0); + + sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index(sw_if_index0); + + ip0 = vlib_buffer_get_current (b0); + + next_worker_index = sm->worker_in2out_cb(ip0, rx_fib_index0); + + if (PREDICT_FALSE (next_worker_index != thread_index)) + { + do_handoff = 1; + + if (next_worker_index != current_worker_index) + { + if (hf) + hf->n_vectors = VLIB_FRAME_SIZE - n_left_to_next_worker; + + hf = vlib_get_worker_handoff_queue_elt (fq_index, + next_worker_index, + handoff_queue_elt_by_worker_index); + + n_left_to_next_worker = VLIB_FRAME_SIZE - hf->n_vectors; + to_next_worker = &hf->buffer_index[hf->n_vectors]; + current_worker_index = next_worker_index; + } + + /* enqueue to correct worker thread */ + to_next_worker[0] = bi0; + to_next_worker++; + n_left_to_next_worker--; + + if (n_left_to_next_worker == 0) + { + hf->n_vectors = VLIB_FRAME_SIZE; + vlib_put_frame_queue_elt (hf); + current_worker_index = ~0; + handoff_queue_elt_by_worker_index[next_worker_index] = 0; + hf = 0; + } + } + else + { + do_handoff = 0; + /* if this is 1st frame */ + if (!f) + { + f = vlib_get_frame_to_node (vm, to_node_index); + to_next = vlib_frame_vector_args (f); + } + + to_next[0] = bi0; + to_next += 1; + f->n_vectors++; + } + + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_in2out_worker_handoff_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->next_worker_index = next_worker_index; + t->do_handoff = do_handoff; + } + } + + if (f) + vlib_put_frame_to_node (vm, to_node_index, f); + + if (hf) + hf->n_vectors = VLIB_FRAME_SIZE - n_left_to_next_worker; + + /* Ship frames to the worker nodes */ + for (i = 0; i < vec_len (handoff_queue_elt_by_worker_index); i++) + { + if (handoff_queue_elt_by_worker_index[i]) + { + hf = handoff_queue_elt_by_worker_index[i]; + /* + * It works better to let the handoff node + * rate-adapt, always ship the handoff queue element. + */ + if (1 || hf->n_vectors == hf->last_n_vectors) + { + vlib_put_frame_queue_elt (hf); + handoff_queue_elt_by_worker_index[i] = 0; + } + else + hf->last_n_vectors = hf->n_vectors; + } + congested_handoff_queue_by_worker_index[i] = + (vlib_frame_queue_t *) (~0); + } + hf = 0; + current_worker_index = ~0; + return frame->n_vectors; +} + +static uword +snat_in2out_worker_handoff_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return snat_in2out_worker_handoff_fn_inline (vm, node, frame, 0); +} + +VLIB_REGISTER_NODE (snat_in2out_worker_handoff_node) = { + .function = snat_in2out_worker_handoff_fn, + .name = "nat44-in2out-worker-handoff", + .vector_size = sizeof (u32), + .format_trace = format_snat_in2out_worker_handoff_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_next_nodes = 1, + + .next_nodes = { + [0] = "error-drop", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (snat_in2out_worker_handoff_node, + snat_in2out_worker_handoff_fn); + +static uword +snat_in2out_output_worker_handoff_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return snat_in2out_worker_handoff_fn_inline (vm, node, frame, 1); +} + +VLIB_REGISTER_NODE (snat_in2out_output_worker_handoff_node) = { + .function = snat_in2out_output_worker_handoff_fn, + .name = "nat44-in2out-output-worker-handoff", + .vector_size = sizeof (u32), + .format_trace = format_snat_in2out_worker_handoff_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_next_nodes = 1, + + .next_nodes = { + [0] = "error-drop", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (snat_in2out_output_worker_handoff_node, + snat_in2out_output_worker_handoff_fn); + +static_always_inline int +is_hairpinning (snat_main_t *sm, ip4_address_t * dst_addr) +{ + snat_address_t * ap; + clib_bihash_kv_8_8_t kv, value; + snat_session_key_t m_key; + + vec_foreach (ap, sm->addresses) + { + if (ap->addr.as_u32 == dst_addr->as_u32) + return 1; + } + + m_key.addr.as_u32 = dst_addr->as_u32; + m_key.fib_index = sm->outside_fib_index; + m_key.port = 0; + m_key.protocol = 0; + kv.key = m_key.as_u64; + if (!clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value)) + return 1; + + return 0; +} + +static uword +snat_hairpin_dst_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + u32 n_left_from, * from, * to_next; + snat_in2out_next_t next_index; + u32 pkts_processed = 0; + snat_main_t * sm = &snat_main; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, + to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t * b0; + u32 next0; + ip4_header_t * ip0; + u32 proto0; + + /* speculatively enqueue b0 to the current next frame */ + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + next0 = SNAT_IN2OUT_NEXT_LOOKUP; + ip0 = vlib_buffer_get_current (b0); + + proto0 = ip_proto_to_snat_proto (ip0->protocol); + + vnet_buffer (b0)->snat.flags = 0; + if (PREDICT_FALSE (is_hairpinning (sm, &ip0->dst_address))) + { + if (proto0 == SNAT_PROTOCOL_TCP || proto0 == SNAT_PROTOCOL_UDP) + { + udp_header_t * udp0 = ip4_next_header (ip0); + tcp_header_t * tcp0 = (tcp_header_t *) udp0; + + snat_hairpinning (sm, b0, ip0, udp0, tcp0, proto0); + } + else if (proto0 == SNAT_PROTOCOL_ICMP) + { + icmp46_header_t * icmp0 = ip4_next_header (ip0); + + snat_icmp_hairpinning (sm, b0, ip0, icmp0); + } + else + { + snat_hairpinning_unknown_proto (sm, b0, ip0); + } + + vnet_buffer (b0)->snat.flags = SNAT_FLAG_HAIRPINNING; + clib_warning("is hairpinning"); + } + + pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP; + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + vlib_node_increment_counter (vm, snat_hairpin_dst_node.index, + SNAT_IN2OUT_ERROR_IN2OUT_PACKETS, + pkts_processed); + return frame->n_vectors; +} + +VLIB_REGISTER_NODE (snat_hairpin_dst_node) = { + .function = snat_hairpin_dst_fn, + .name = "nat44-hairpin-dst", + .vector_size = sizeof (u32), + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = ARRAY_LEN(snat_in2out_error_strings), + .error_strings = snat_in2out_error_strings, + .n_next_nodes = 2, + .next_nodes = { + [SNAT_IN2OUT_NEXT_DROP] = "error-drop", + [SNAT_IN2OUT_NEXT_LOOKUP] = "ip4-lookup", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (snat_hairpin_dst_node, + snat_hairpin_dst_fn); + +static uword +snat_hairpin_src_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + u32 n_left_from, * from, * to_next; + snat_in2out_next_t next_index; + u32 pkts_processed = 0; + snat_main_t *sm = &snat_main; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, + to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t * b0; + u32 next0; + + /* speculatively enqueue b0 to the current next frame */ + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + next0 = SNAT_HAIRPIN_SRC_NEXT_INTERFACE_OUTPUT; + + if (PREDICT_FALSE ((vnet_buffer (b0)->snat.flags) & SNAT_FLAG_HAIRPINNING)) + { + if (PREDICT_TRUE (sm->num_workers > 1)) + next0 = SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT_WH; + else + next0 = SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT; + } + + pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP; + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + vlib_node_increment_counter (vm, snat_hairpin_src_node.index, + SNAT_IN2OUT_ERROR_IN2OUT_PACKETS, + pkts_processed); + return frame->n_vectors; +} + +VLIB_REGISTER_NODE (snat_hairpin_src_node) = { + .function = snat_hairpin_src_fn, + .name = "nat44-hairpin-src", + .vector_size = sizeof (u32), + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = ARRAY_LEN(snat_in2out_error_strings), + .error_strings = snat_in2out_error_strings, + .n_next_nodes = SNAT_HAIRPIN_SRC_N_NEXT, + .next_nodes = { + [SNAT_HAIRPIN_SRC_NEXT_DROP] = "error-drop", + [SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT] = "nat44-in2out-output", + [SNAT_HAIRPIN_SRC_NEXT_INTERFACE_OUTPUT] = "interface-output", + [SNAT_HAIRPIN_SRC_NEXT_SNAT_IN2OUT_WH] = "nat44-in2out-output-worker-handoff", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (snat_hairpin_src_node, + snat_hairpin_src_fn); + +static uword +snat_in2out_fast_static_map_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + u32 n_left_from, * from, * to_next; + snat_in2out_next_t next_index; + u32 pkts_processed = 0; + snat_main_t * sm = &snat_main; + u32 stats_node_index; + + stats_node_index = snat_in2out_fast_node.index; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, + to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t * b0; + u32 next0; + u32 sw_if_index0; + ip4_header_t * ip0; + ip_csum_t sum0; + u32 new_addr0, old_addr0; + u16 old_port0, new_port0; + udp_header_t * udp0; + tcp_header_t * tcp0; + icmp46_header_t * icmp0; + snat_session_key_t key0, sm0; + u32 proto0; + u32 rx_fib_index0; + + /* speculatively enqueue b0 to the current next frame */ + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + next0 = SNAT_IN2OUT_NEXT_LOOKUP; + + ip0 = vlib_buffer_get_current (b0); + udp0 = ip4_next_header (ip0); + tcp0 = (tcp_header_t *) udp0; + icmp0 = (icmp46_header_t *) udp0; + + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index(sw_if_index0); + + if (PREDICT_FALSE(ip0->ttl == 1)) + { + vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next0 = SNAT_IN2OUT_NEXT_ICMP_ERROR; + goto trace0; + } + + proto0 = ip_proto_to_snat_proto (ip0->protocol); + + if (PREDICT_FALSE (proto0 == ~0)) + goto trace0; + + if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP)) + { + next0 = icmp_in2out(sm, b0, ip0, icmp0, sw_if_index0, + rx_fib_index0, node, next0, ~0, 0, 0); + goto trace0; + } + + key0.addr = ip0->src_address; + key0.port = udp0->src_port; + key0.fib_index = rx_fib_index0; + + if (snat_static_mapping_match(sm, key0, &sm0, 0, 0)) + { + b0->error = node->errors[SNAT_IN2OUT_ERROR_NO_TRANSLATION]; + next0= SNAT_IN2OUT_NEXT_DROP; + goto trace0; + } + + new_addr0 = sm0.addr.as_u32; + new_port0 = sm0.port; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = sm0.fib_index; + old_addr0 = ip0->src_address.as_u32; + ip0->src_address.as_u32 = new_addr0; + + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + src_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + if (PREDICT_FALSE(new_port0 != udp0->dst_port)) + { + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + old_port0 = tcp0->src_port; + tcp0->src_port = new_port0; + + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + dst_address /* changed member */); + sum0 = ip_csum_update (sum0, old_port0, new_port0, + ip4_header_t /* cheat */, + length /* changed member */); + tcp0->checksum = ip_csum_fold(sum0); + } + else + { + old_port0 = udp0->src_port; + udp0->src_port = new_port0; + udp0->checksum = 0; + } + } + else + { + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + dst_address /* changed member */); + tcp0->checksum = ip_csum_fold(sum0); + } + } + + /* Hairpinning */ + snat_hairpinning (sm, b0, ip0, udp0, tcp0, proto0); + + trace0: + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_in2out_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->sw_if_index = sw_if_index0; + t->next_index = next0; + } + + pkts_processed += next0 != SNAT_IN2OUT_NEXT_DROP; + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + vlib_node_increment_counter (vm, stats_node_index, + SNAT_IN2OUT_ERROR_IN2OUT_PACKETS, + pkts_processed); + return frame->n_vectors; +} + + +VLIB_REGISTER_NODE (snat_in2out_fast_node) = { + .function = snat_in2out_fast_static_map_fn, + .name = "nat44-in2out-fast", + .vector_size = sizeof (u32), + .format_trace = format_snat_in2out_fast_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(snat_in2out_error_strings), + .error_strings = snat_in2out_error_strings, + + .runtime_data_bytes = sizeof (snat_runtime_t), + + .n_next_nodes = SNAT_IN2OUT_N_NEXT, + + /* edit / add dispositions here */ + .next_nodes = { + [SNAT_IN2OUT_NEXT_DROP] = "error-drop", + [SNAT_IN2OUT_NEXT_LOOKUP] = "ip4-lookup", + [SNAT_IN2OUT_NEXT_SLOW_PATH] = "nat44-in2out-slowpath", + [SNAT_IN2OUT_NEXT_ICMP_ERROR] = "ip4-icmp-error", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (snat_in2out_fast_node, snat_in2out_fast_static_map_fn); diff --git a/src/plugins/nat/nat.api b/src/plugins/nat/nat.api new file mode 100644 index 00000000000..7245cb078b9 --- /dev/null +++ b/src/plugins/nat/nat.api @@ -0,0 +1,1513 @@ +/* + * Copyright (c) 2016 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. + */ +/** + * @file nat.api + * @brief VPP control-plane API messages. + * + * This file defines VPP control-plane API messages which are generally + * called through a shared memory interface. + */ + +/* + * Old "snat" APIs, will be deprecated after 17.10 + */ + +/** \brief Add/del NAT44 address range + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_ip4 - 1 if address type is IPv4 + @param first_ip_address - first IP address + @param last_ip_address - last IP address + @param vrf_id - VRF id of tenant, ~0 means independent of VRF + @param is_add - 1 if add, 0 if delete +*/ +autoreply define snat_add_address_range { + u32 client_index; + u32 context; + u8 is_ip4; + u8 first_ip_address[16]; + u8 last_ip_address[16]; + u32 vrf_id; + u8 is_add; +}; + +/** \brief Dump NAT44 addresses + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define snat_address_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT44 address details response + @param context - sender context, to match reply w/ request + @param is_ip4 - 1 if address type is IPv4 + @param ip_address - IP address + @param vrf_id - VRF id of tenant, ~0 means independent of VRF +*/ +define snat_address_details { + u32 context; + u8 is_ip4; + u8 ip_address[16]; + u32 vrf_id; +}; + +/** \brief Enable/disable NAT44 feature on the interface + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_add - 1 if add, 0 if delete + @param is_inside - 1 if inside, 0 if outside + @param sw_if_index - software index of the interface +*/ +autoreply define snat_interface_add_del_feature { + u32 client_index; + u32 context; + u8 is_add; + u8 is_inside; + u32 sw_if_index; +}; + +/** \brief Dump interfaces with NAT44 feature + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define snat_interface_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT44 interface details response + @param context - sender context, to match reply w/ request + @param is_inside - 1 if inside, 0 if outside + @param sw_if_index - software index of the interface +*/ +define snat_interface_details { + u32 context; + u8 is_inside; + u32 sw_if_index; +}; + +/** \brief Enable/disbale NAT44 as an interface output feature (postrouting + in2out translation) + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_add - 1 if add, 0 if delete + @param is_inside - 1 if inside, 0 if outside + @param sw_if_index - software index of the interface +*/ +autoreply define snat_interface_add_del_output_feature { + u32 client_index; + u32 context; + u8 is_add; + u8 is_inside; + u32 sw_if_index; +}; + +/** \brief Dump interfaces with NAT44 output feature + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define snat_interface_output_feature_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT44 interface with output feature details response + @param context - sender context, to match reply w/ request + @param is_inside - 1 if inside, 0 if outside + @param sw_if_index - software index of the interface +*/ +define snat_interface_output_feature_details { + u32 context; + u8 is_inside; + u32 sw_if_index; +}; + +/** \brief Add/delete NAT44 static mapping + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_add - 1 if add, 0 if delete + @param is_ip4 - 1 if address type is IPv4 + @param addr_only - 1 if address only mapping + @param local_ip_address - local IP address + @param external_ip_address - external IP address + @param protocol - IP protocol + @param local_port - local port number + @param external_port - external port number + @param external_sw_if_index - external interface (if set + external_ip_address is ignored, ~0 means not + used) + @param vfr_id - VRF ID +*/ +autoreply define snat_add_static_mapping { + u32 client_index; + u32 context; + u8 is_add; + u8 is_ip4; + u8 addr_only; + u8 local_ip_address[16]; + u8 external_ip_address[16]; + u8 protocol; + u16 local_port; + u16 external_port; + u32 external_sw_if_index; + u32 vrf_id; +}; + +/** \brief Dump NAT44 static mappings + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define snat_static_mapping_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT44 static mapping details response + @param context - sender context, to match reply w/ request + @param is_ip4 - 1 if address type is IPv4 + @param addr_only - 1 if address only mapping + @param local_ip_address - local IP address + @param external_ip_address - external IP address + @param protocol - IP protocol + @param local_port - local port number + @param external_port - external port number + @param external_sw_if_index - external interface + @param vfr_id - VRF ID +*/ +define snat_static_mapping_details { + u32 context; + u8 is_ip4; + u8 addr_only; + u8 local_ip_address[16]; + u8 external_ip_address[16]; + u8 protocol; + u16 local_port; + u16 external_port; + u32 external_sw_if_index; + u32 vrf_id; +}; + +/** \brief Control ping from client to api server request + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define snat_control_ping +{ + u32 client_index; + u32 context; +}; + +/** \brief Control ping from the client to the server response + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param retval - return code for the request + @param vpe_pid - the pid of the vpe, returned by the server +*/ +define snat_control_ping_reply +{ + u32 context; + i32 retval; + u32 client_index; + u32 vpe_pid; +}; + +/** \brief Show NAT plugin startup config + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define snat_show_config +{ + u32 client_index; + u32 context; +}; + +/** \brief Show NAT plugin startup config reply + @param context - sender context, to match reply w/ request + @param retval - return code for the request + @param static_mapping_only - if 1 dynamic translations disabled + @param static_mapping_connection_tracking - if 1 create session data + @param deterministic - if 1 deterministic mapping + @param translation_buckets - number of translation hash buckets + @param translation_memory_size - translation hash memory size + @param user_buckets - number of user hash buckets + @param user_memory_size - user hash memory size + @param max_translations_per_user - maximum number of translations per user + @param outside_vrf_id - outside VRF id + @param inside_vrf_id - default inside VRF id +*/ +define snat_show_config_reply +{ + u32 context; + i32 retval; + u8 static_mapping_only; + u8 static_mapping_connection_tracking; + u8 deterministic; + u32 translation_buckets; + u32 translation_memory_size; + u32 user_buckets; + u32 user_memory_size; + u32 max_translations_per_user; + u32 outside_vrf_id; + u32 inside_vrf_id; +}; + +/** \brief Set NAT workers + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param worker_mask - NAT workers mask +*/ +autoreply define snat_set_workers { + u32 client_index; + u32 context; + u64 worker_mask; +}; + +/** \brief Dump NAT workers + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define snat_worker_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT workers details response + @param context - sender context, to match reply w/ request + @param worker_index - worker index + @param lcore_id - lcore ID + @param name - worker name +*/ +define snat_worker_details { + u32 context; + u32 worker_index; + u32 lcore_id; + u8 name[64]; +}; + +/** \brief Add/delete NAT44 pool address from specific interfce + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_add - 1 if add, 0 if delete + @param sw_if_index - software index of the interface +*/ +autoreply define snat_add_del_interface_addr { + u32 client_index; + u32 context; + u8 is_add; + u8 is_inside; + u32 sw_if_index; +}; + +/** \brief Dump NAT44 pool addresses interfaces + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define snat_interface_addr_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT44 pool addresses interfaces details response + @param context - sender context, to match reply w/ request + @param sw_if_index - software index of the interface +*/ +define snat_interface_addr_details { + u32 context; + u32 sw_if_index; +}; + +/** \brief Enable/disable NAT IPFIX logging + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param domain_id - observation domain ID + @param src_port - source port number + @param enable - 1 if enable, 0 if disable +*/ +autoreply define snat_ipfix_enable_disable { + u32 client_index; + u32 context; + u32 domain_id; + u16 src_port; + u8 enable; +}; + +/** \brief Dump NAT44 users + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define snat_user_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT44 users response + @param context - sender context, to match reply w/ request + @vrf_id - VRF ID + @param is_ip4 - 1 if address type is IPv4 + @param ip_adress - IP address + @param nsessions - number of dynamic sessions + @param nstaticsessions - number of static sessions +*/ +define snat_user_details { + u32 context; + u32 vrf_id; + u8 is_ip4; + u8 ip_address[16]; + u32 nsessions; + u32 nstaticsessions; +}; + +/** \brief NAT44 user's sessions + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_ip4 - 1 if address type is IPv4 + @param user_ip - IP address of the user to dump + @param vrf_id - VRF_ID +*/ +define snat_user_session_dump { + u32 client_index; + u32 context; + u8 is_ip4; + u8 ip_address[16]; + u32 vrf_id; +}; + +/** \brief NAT44 user's sessions response + @param context - sender context, to match reply w/ request + @param is_ip4 - 1 if address type is IPv4 + @param outside_ip_address - outside IP address + @param outside_port - outside port + @param inside_ip_address - inside IP address + @param inside_port - inside port + @param protocol - protocol + @param is_static - 1 if session is static + @param last_heard - last heard timer + @param total_bytes - count of bytes sent through session + @param total_pkts - count of pakets sent through session +*/ +define snat_user_session_details { + u32 context; + u8 is_ip4; + u8 outside_ip_address[16]; + u16 outside_port; + u8 inside_ip_address[16]; + u16 inside_port; + u16 protocol; + u8 is_static; + u64 last_heard; + u64 total_bytes; + u32 total_pkts; +}; + +/** \brief Add/delete NAT deterministic mapping + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_add - 1 if add, 0 if delete + @param is_ip4 - 1 if address type is IPv4 + @param in_addr - inside IP address + @param in_plen - inside IP address prefix length + @param out_addr - outside IP address + @param out_addr - outside IP address prefix length +*/ +autoreply define snat_add_det_map { + u32 client_index; + u32 context; + u8 is_add; + u8 is_ip4; + u8 addr_only; + u8 in_addr[16]; + u8 in_plen; + u8 out_addr[16]; + u8 out_plen; +}; + +/** \brief Get outside address and port range from inside address + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_ip4 - 1 if address type is IPv4 + @param in_addr - inside IP address +*/ +define snat_det_forward { + u32 client_index; + u32 context; + u8 is_ip4; + u8 in_addr[16]; +}; + +/** \brief Get outside address and port range from inside address + @param context - sender context, to match reply w/ request + @param retval - return code + @param out_port_lo - outside port range start + @param out_port_hi - outside port range end + @param is_ip4 - 1 if address type is IPv4 + @param out_addr - outside IP address +*/ +define snat_det_forward_reply { + u32 context; + i32 retval; + u16 out_port_lo; + u16 out_port_hi; + u8 is_ip4; + u8 out_addr[16]; +}; + +/** \brief Get inside address from outside address and port + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param out_port - outside port + @param is_ip4 - 1 if address type is IPv4 + @param out_addr - outside IP address +*/ +define snat_det_reverse { + u32 client_index; + u32 context; + u16 out_port; + u8 is_ip4; + u8 out_addr[16]; +}; + +/** \brief Get inside address from outside address and port reply + @param context - sender context, to match reply w/ request + @param retval - return code + @param is_ip4 - 1 if address type is IPv4 + @param in_addr - inside IP address +*/ +define snat_det_reverse_reply { + u32 context; + i32 retval; + u8 is_ip4; + u8 in_addr[16]; +}; + +/** \brief Dump NAT deterministic mappings + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define snat_det_map_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT users response + @param context - sender context, to match reply w/ request + @param is_ip4 - 1 if address type is IPv4 + @param in_addr - inside IP address + @param in_plen - inside IP address prefix length + @param out_addr - outside IP address + @param out_plen - outside IP address prefix length + @param sharing_ratio - outside to inside address sharing ratio + @param ports_per_host - number of ports available to a host + @param ses_num - number of sessions belonging to this mapping +*/ +define snat_det_map_details { + u32 context; + u8 is_ip4; + u8 in_addr[16]; + u8 in_plen; + u8 out_addr[16]; + u8 out_plen; + u32 sharing_ratio; + u16 ports_per_host; + u32 ses_num; +}; + +/** \brief Set values of timeouts for deterministic NAT (seconds, 0 = default) + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param udp - UDP timeout (default 300sec) + @param tcp_established - TCP established timeout (default 7440sec) + @param tcp_transitory - TCP transitory timeout (default 240sec) + @param icmp - ICMP timeout (default 60sec) +*/ +autoreply define snat_det_set_timeouts { + u32 client_index; + u32 context; + u32 udp; + u32 tcp_established; + u32 tcp_transitory; + u32 icmp; +}; + +/** \brief Get values of timeouts for deterministic NAT (seconds) + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define snat_det_get_timeouts { + u32 client_index; + u32 context; +}; + +/** \brief Get values of timeouts for deterministic NAT reply + @param context - sender context, to match reply w/ request + @param retval - return code + @param udp - UDP timeout (default 300sec) + @param tcp_established - TCP established timeout (default 7440sec) + @param tcp_transitory - TCP transitory timeout (default 240sec) + @param icmp - ICMP timeout (default 60sec) +*/ +define snat_det_get_timeouts_reply { + u32 context; + i32 retval; + u32 udp; + u32 tcp_established; + u32 tcp_transitory; + u32 icmp; +}; + +/** \brief Close deterministic NAT session by outside address and port + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_ip4 - 1 if address type is IPv4 + @param out_addr - outside IP address + @param out_port - outside port + @param ext_addr - external host address + @param ext_port - external host port +*/ +autoreply define snat_det_close_session_out { + u32 client_index; + u32 context; + u8 is_ip4; + u8 out_addr[16]; + u16 out_port; + u8 ext_addr[16]; + u16 ext_port; +}; + +/** \brief Close deterministic NAT session by inside address and port + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_ip4 - 1 if address type is IPv4 + @param in_addr - inside IP address + @param in_port - inside port + @param ext_addr - external host address + @param ext_port - external host port +*/ +autoreply define snat_det_close_session_in { + u32 client_index; + u32 context; + u8 is_ip4; + u8 in_addr[16]; + u16 in_port; + u8 ext_addr[16]; + u16 ext_port; +}; + +/** \brief Dump determinstic NAT sessions + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_ip4 - 1 if address type is IPv4 + @param user_addr - address of an inside user whose sessions to dump +*/ +define snat_det_session_dump { + u32 client_index; + u32 context; + u8 is_ip4; + u8 user_addr[16]; +}; + +/** \brief Deterministic NAT sessions reply + @param context - sender context, to match reply w/ request + @param is_ip4 - 1 if address type is IPv4 + @param in_port - inside port + @param ext_addr - external host address + @param ext_port - external host port + @param out_port - outside NAT port + @param state - session state + @param expire - session expiration timestamp +*/ +define snat_det_session_details { + u32 client_index; + u32 context; + u8 is_ip4; + u16 in_port; + u8 ext_addr[16]; + u16 ext_port; + u16 out_port; + u8 state; + u32 expire; +}; + +/* + * Common NAT plugin APIs + */ + +/** \brief Control ping from client to api server request + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat_control_ping +{ + u32 client_index; + u32 context; +}; + +/** \brief Control ping from the client to the server response + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param retval - return code for the request + @param vpe_pid - the pid of the vpe, returned by the server +*/ +define nat_control_ping_reply +{ + u32 context; + i32 retval; + u32 client_index; + u32 vpe_pid; +}; + +/** \brief Show NAT plugin startup config + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat_show_config +{ + u32 client_index; + u32 context; +}; + +/** \brief Show NAT plugin startup config reply + @param context - sender context, to match reply w/ request + @param retval - return code for the request + @param static_mapping_only - if 1 dynamic translations disabled + @param static_mapping_connection_tracking - if 1 create session data + @param deterministic - if 1 deterministic mapping + @param translation_buckets - number of translation hash buckets + @param translation_memory_size - translation hash memory size + @param user_buckets - number of user hash buckets + @param user_memory_size - user hash memory size + @param max_translations_per_user - maximum number of translations per user + @param outside_vrf_id - outside VRF id + @param inside_vrf_id - default inside VRF id +*/ +define nat_show_config_reply +{ + u32 context; + i32 retval; + u8 static_mapping_only; + u8 static_mapping_connection_tracking; + u8 deterministic; + u32 translation_buckets; + u32 translation_memory_size; + u32 user_buckets; + u32 user_memory_size; + u32 max_translations_per_user; + u32 outside_vrf_id; + u32 inside_vrf_id; +}; + +/** \brief Set NAT workers + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param worker_mask - NAT workers mask +*/ +autoreply define nat_set_workers { + u32 client_index; + u32 context; + u64 worker_mask; +}; + +/** \brief Dump NAT workers + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat_worker_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT workers details response + @param context - sender context, to match reply w/ request + @param worker_index - worker index + @param lcore_id - lcore ID + @param name - worker name +*/ +define nat_worker_details { + u32 context; + u32 worker_index; + u32 lcore_id; + u8 name[64]; +}; + +/** \brief Enable/disable NAT IPFIX logging + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param domain_id - observation domain ID + @param src_port - source port number + @param enable - 1 if enable, 0 if disable +*/ +autoreply define nat_ipfix_enable_disable { + u32 client_index; + u32 context; + u32 domain_id; + u16 src_port; + u8 enable; +}; + +/* + * NAT44 APIs + */ + +/** \brief Add/del NAT44 address range + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param first_ip_address - first IPv4 address + @param last_ip_address - last IPv4 address + @param vrf_id - VRF id of tenant, ~0 means independent of VRF + @param is_add - 1 if add, 0 if delete +*/ +autoreply define nat44_add_del_address_range { + u32 client_index; + u32 context; + u8 first_ip_address[4]; + u8 last_ip_address[4]; + u32 vrf_id; + u8 is_add; +}; + +/** \brief Dump NAT44 addresses + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat44_address_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT44 address details response + @param context - sender context, to match reply w/ request + @param ip_address - IPv4 address + @param vrf_id - VRF id of tenant, ~0 means independent of VRF +*/ +define nat44_address_details { + u32 context; + u8 ip_address[4]; + u32 vrf_id; +}; + +/** \brief Enable/disable NAT44 feature on the interface + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_add - 1 if add, 0 if delete + @param is_inside - 1 if inside, 0 if outside + @param sw_if_index - software index of the interface +*/ +autoreply define nat44_interface_add_del_feature { + u32 client_index; + u32 context; + u8 is_add; + u8 is_inside; + u32 sw_if_index; +}; + +/** \brief Dump interfaces with NAT44 feature + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat44_interface_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT44 interface details response + @param context - sender context, to match reply w/ request + @param is_inside - 1 if inside, 0 if outside + @param sw_if_index - software index of the interface +*/ +define nat44_interface_details { + u32 context; + u8 is_inside; + u32 sw_if_index; +}; + +/** \brief Enable/disbale NAT44 as an interface output feature (postrouting + in2out translation) + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_add - 1 if add, 0 if delete + @param is_inside - 1 if inside, 0 if outside + @param sw_if_index - software index of the interface +*/ +autoreply define nat44_interface_add_del_output_feature { + u32 client_index; + u32 context; + u8 is_add; + u8 is_inside; + u32 sw_if_index; +}; + +/** \brief Dump interfaces with NAT44 output feature + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat44_interface_output_feature_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT44 interface with output feature details response + @param context - sender context, to match reply w/ request + @param is_inside - 1 if inside, 0 if outside + @param sw_if_index - software index of the interface +*/ +define nat44_interface_output_feature_details { + u32 context; + u8 is_inside; + u32 sw_if_index; +}; + +/** \brief Add/delete NAT44 static mapping + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_add - 1 if add, 0 if delete + @param addr_only - 1 if address only mapping + @param local_ip_address - local IPv4 address + @param external_ip_address - external IPv4 address + @param protocol - IP protocol + @param local_port - local port number + @param external_port - external port number + @param external_sw_if_index - external interface (if set + external_ip_address is ignored, ~0 means not + used) + @param vfr_id - VRF ID +*/ +autoreply define nat44_add_del_static_mapping { + u32 client_index; + u32 context; + u8 is_add; + u8 addr_only; + u8 local_ip_address[4]; + u8 external_ip_address[4]; + u8 protocol; + u16 local_port; + u16 external_port; + u32 external_sw_if_index; + u32 vrf_id; +}; + +/** \brief Dump NAT44 static mappings + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat44_static_mapping_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT44 static mapping details response + @param context - sender context, to match reply w/ request + @param addr_only - 1 if address only mapping + @param local_ip_address - local IPv4 address + @param external_ip_address - external IPv4 address + @param protocol - IP protocol + @param local_port - local port number + @param external_port - external port number + @param external_sw_if_index - external interface + @param vfr_id - VRF ID +*/ +define nat44_static_mapping_details { + u32 context; + u8 addr_only; + u8 local_ip_address[4]; + u8 external_ip_address[4]; + u8 protocol; + u16 local_port; + u16 external_port; + u32 external_sw_if_index; + u32 vrf_id; +}; + +/** \brief Add/delete NAT44 pool address from specific interfce + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_add - 1 if add, 0 if delete + @param sw_if_index - software index of the interface +*/ +autoreply define nat44_add_del_interface_addr { + u32 client_index; + u32 context; + u8 is_add; + u8 is_inside; + u32 sw_if_index; +}; + +/** \brief Dump NAT44 pool addresses interfaces + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat44_interface_addr_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT44 pool addresses interfaces details response + @param context - sender context, to match reply w/ request + @param sw_if_index - software index of the interface +*/ +define nat44_interface_addr_details { + u32 context; + u32 sw_if_index; +}; + +/** \brief Dump NAT44 users + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat44_user_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT44 users response + @param context - sender context, to match reply w/ request + @vrf_id - VRF ID + @param ip_adress - IPv4 address + @param nsessions - number of dynamic sessions + @param nstaticsessions - number of static sessions +*/ +define nat44_user_details { + u32 context; + u32 vrf_id; + u8 ip_address[4]; + u32 nsessions; + u32 nstaticsessions; +}; + +/** \brief NAT44 user's sessions + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_ip4 - 1 if address type is IPv4 + @param user_ip - IP address of the user to dump + @param vrf_id - VRF_ID +*/ +define nat44_user_session_dump { + u32 client_index; + u32 context; + u8 ip_address[4]; + u32 vrf_id; +}; + +/** \brief NAT44 user's sessions response + @param context - sender context, to match reply w/ request + @param outside_ip_address - outside IPv4 address + @param outside_port - outside port + @param inside_ip_address - inside IPv4 address + @param inside_port - inside port + @param protocol - protocol + @param is_static - 1 if session is static + @param last_heard - last heard timer + @param total_bytes - count of bytes sent through session + @param total_pkts - count of pakets sent through session +*/ +define nat44_user_session_details { + u32 context; + u8 outside_ip_address[4]; + u16 outside_port; + u8 inside_ip_address[4]; + u16 inside_port; + u16 protocol; + u8 is_static; + u64 last_heard; + u64 total_bytes; + u32 total_pkts; +}; + +/* + * Deterministic NAT (CGN) APIs + */ + +/** \brief Add/delete NAT deterministic mapping + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_add - 1 if add, 0 if delete + @param is_nat44 - 1 if NAT44 + @param in_addr - inside IP address + @param in_plen - inside IP address prefix length + @param out_addr - outside IPv4 address + @param out_addr - outside IPv4 address prefix length +*/ +autoreply define nat_det_add_del_map { + u32 client_index; + u32 context; + u8 is_add; + u8 is_nat44; + u8 addr_only; + u8 in_addr[16]; + u8 in_plen; + u8 out_addr[4]; + u8 out_plen; +}; + +/** \brief Get outside address and port range from inside address + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_nat44 - 1 if NAT44 + @param in_addr - inside IP address +*/ +define nat_det_forward { + u32 client_index; + u32 context; + u8 is_nat44; + u8 in_addr[16]; +}; + +/** \brief Get outside address and port range from inside address + @param context - sender context, to match reply w/ request + @param retval - return code + @param out_port_lo - outside port range start + @param out_port_hi - outside port range end + @param out_addr - outside IPv4 address +*/ +define nat_det_forward_reply { + u32 context; + i32 retval; + u16 out_port_lo; + u16 out_port_hi; + u8 out_addr[4]; +}; + +/** \brief Get inside address from outside address and port + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param out_port - outside port + @param out_addr - outside IPv4 address +*/ +define nat_det_reverse { + u32 client_index; + u32 context; + u16 out_port; + u8 out_addr[4]; +}; + +/** \brief Get inside address from outside address and port reply + @param context - sender context, to match reply w/ request + @param retval - return code + @param is_nat44 - 1 if NAT44 + @param in_addr - inside IP address +*/ +define nat_det_reverse_reply { + u32 context; + i32 retval; + u8 is_nat44; + u8 in_addr[16]; +}; + +/** \brief Dump NAT deterministic mappings + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat_det_map_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT users response + @param context - sender context, to match reply w/ request + @param is_nat44 - 1 if NAT44 + @param in_addr - inside IP address + @param in_plen - inside IP address prefix length + @param out_addr - outside IPv4 address + @param out_plen - outside IPv4 address prefix length + @param sharing_ratio - outside to inside address sharing ratio + @param ports_per_host - number of ports available to a host + @param ses_num - number of sessions belonging to this mapping +*/ +define nat_det_map_details { + u32 context; + u8 is_nat44; + u8 in_addr[16]; + u8 in_plen; + u8 out_addr[4]; + u8 out_plen; + u32 sharing_ratio; + u16 ports_per_host; + u32 ses_num; +}; + +/** \brief Set values of timeouts for deterministic NAT (seconds, 0 = default) + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param udp - UDP timeout (default 300sec) + @param tcp_established - TCP established timeout (default 7440sec) + @param tcp_transitory - TCP transitory timeout (default 240sec) + @param icmp - ICMP timeout (default 60sec) +*/ +autoreply define nat_det_set_timeouts { + u32 client_index; + u32 context; + u32 udp; + u32 tcp_established; + u32 tcp_transitory; + u32 icmp; +}; + +/** \brief Get values of timeouts for deterministic NAT (seconds) + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat_det_get_timeouts { + u32 client_index; + u32 context; +}; + +/** \brief Get values of timeouts for deterministic NAT reply + @param context - sender context, to match reply w/ request + @param retval - return code + @param udp - UDP timeout (default 300sec) + @param tcp_established - TCP established timeout (default 7440sec) + @param tcp_transitory - TCP transitory timeout (default 240sec) + @param icmp - ICMP timeout (default 60sec) +*/ +define nat_det_get_timeouts_reply { + u32 context; + i32 retval; + u32 udp; + u32 tcp_established; + u32 tcp_transitory; + u32 icmp; +}; + +/** \brief Close deterministic NAT session by outside address and port + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param out_addr - outside IPv4 address + @param out_port - outside port + @param ext_addr - external host IPv4 address + @param ext_port - external host port +*/ +autoreply define nat_det_close_session_out { + u32 client_index; + u32 context; + u8 out_addr[4]; + u16 out_port; + u8 ext_addr[4]; + u16 ext_port; +}; + +/** \brief Close deterministic NAT session by inside address and port + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_nat44 - 1 if NAT44 + @param in_addr - inside IP address + @param in_port - inside port + @param ext_addr - external host IP address + @param ext_port - external host port +*/ +autoreply define nat_det_close_session_in { + u32 client_index; + u32 context; + u8 is_nat44; + u8 in_addr[16]; + u16 in_port; + u8 ext_addr[16]; + u16 ext_port; +}; + +/** \brief Dump determinstic NAT sessions + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_nat44 - 1 if NAT44 + @param user_addr - address of an inside user whose sessions to dump +*/ +define nat_det_session_dump { + u32 client_index; + u32 context; + u8 is_nat44; + u8 user_addr[16]; +}; + +/** \brief Deterministic NAT sessions reply + @param context - sender context, to match reply w/ request + @param in_port - inside port + @param ext_addr - external host address + @param ext_port - external host port + @param out_port - outside NAT port + @param state - session state + @param expire - session expiration timestamp +*/ +define nat_det_session_details { + u32 client_index; + u32 context; + u16 in_port; + u8 ext_addr[4]; + u16 ext_port; + u16 out_port; + u8 state; + u32 expire; +}; + +/* + * NAT64 APIs + */ + +/** \brief Add/delete address range to NAT64 pool + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param start_addr - start address of the range + @param end_addr - end address of the range + @param vrf_id - VRF id of tenant, ~0 means independent of VRF + @param is_add - 1 if add, 0 if delete +*/ +autoreply define nat64_add_del_pool_addr_range { + u32 client_index; + u32 context; + u8 start_addr[4]; + u8 end_addr[4]; + u32 vrf_id; + u8 is_add; +}; + +/** \brief Dump NAT64 pool addresses + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat64_pool_addr_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT64 pool address details response + @param context - sender context, to match reply w/ request + @param address - IPv4 address + @param vfr_id - VRF id of tenant, ~0 means independent of VRF +*/ +define nat64_pool_addr_details { + u32 context; + u8 address[4]; + u32 vrf_id; +}; + +/** \brief Enable/disable NAT64 feature on the interface + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - index of the interface + @param is_inside - 1 if inside, 0 if outside + @param is_add - 1 if add, 0 if delete +*/ +autoreply define nat64_add_del_interface { + u32 client_index; + u32 context; + u32 sw_if_index; + u8 is_inside; + u8 is_add; +}; + +/** \brief Dump interfaces with NAT64 feature + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat64_interface_dump { + u32 client_index; + u32 context; +}; + +/** \brief NAT64 interface details response + @param context - sender context, to match reply w/ request + @param is_inside - 1 if inside, 0 if outside + @param sw_if_index - index of the interface +*/ +define nat64_interface_details { + u32 context; + u8 is_inside; + u32 sw_if_index; +}; + +/** \brief Add/delete NAT64 static BIB entry + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param i_addr - inside IPv6 address + @param o_addr - outside IPv4 address + @param i_port - inside port number + @param o_port - outside port number + @param vrf_id - VRF id of tenant + @param proto - protocol number + @param is_add - 1 if add, 0 if delete +*/ + autoreply define nat64_add_del_static_bib { + u32 client_index; + u32 context; + u8 i_addr[16]; + u8 o_addr[4]; + u16 i_port; + u16 o_port; + u32 vrf_id; + u8 proto; + u8 is_add; +}; + +/** \brief Dump NAT64 BIB + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param proto - protocol of the BIB: 255 - all BIBs + 6 - TCP BIB + 17 - UDP BIB + 1/58 - ICMP BIB + otherwise - "unknown" protocol BIB +*/ +define nat64_bib_dump { + u32 client_index; + u32 context; + u8 proto; +}; + +/** \brief NAT64 BIB details response + @param context - sender context, to match reply w/ request + @param i_addr - inside IPv6 address + @param o_addr - outside IPv4 address + @param i_port - inside port number + @param o_port - outside port number + @param vrf_id - VRF id of tenant + @param proto - protocol number + @param is_static - 1 if static BIB entry, 0 if dynamic + @param ses_num - number of sessions associated with the BIB entry +*/ +define nat64_bib_details { + u32 context; + u8 i_addr[16]; + u8 o_addr[4]; + u16 i_port; + u16 o_port; + u32 vrf_id; + u8 proto; + u8 is_static; + u32 ses_num; +}; + +/** \brief Set values of timeouts for NAT64 (seconds, 0 = default) + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param udp - UDP timeout (default 300sec) + @param icmp - ICMP timeout (default 60sec) + @param tcp_trans - TCP transitory timeout (default 240sec) + @param tcp_est - TCP established timeout (default 7440sec) + @param tcp_incoming_syn - TCP incoming SYN timeout (default 6sec) +*/ +autoreply define nat64_set_timeouts { + u32 client_index; + u32 context; + u32 udp; + u32 icmp; + u32 tcp_trans; + u32 tcp_est; + u32 tcp_incoming_syn; +}; + +/** \brief Get values of timeouts for NAT64 (seconds) + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat64_get_timeouts { + u32 client_index; + u32 context; +}; + +/** \brief Get values of timeouts for NAT64 reply + @param context - sender context, to match reply w/ request + @param retval - return code + @param udp - UDP timeout + @param icmp - ICMP timeout + @param tcp_trans - TCP transitory timeout + @param tcp_est - TCP established timeout + @param tcp_incoming_syn - TCP incoming SYN timeout +*/ +define nat64_get_timeouts_reply { + u32 context; + i32 retval; + u32 udp; + u32 icmp; + u32 tcp_trans; + u32 tcp_est; + u32 tcp_incoming_syn; +}; + +/** \brief Dump NAT64 session table + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param proto - protocol of the session table: 255 - all STs + 6 - TCP ST + 17 - UDP ST + 1/58 - ICMP ST + otherwise - "unknown" proto ST +*/ +define nat64_st_dump { + u32 client_index; + u32 context; + u8 proto; +}; + +/** \brief NAT64 session table details response + @param context - sender context, to match reply w/ request + @param il_addr - inside IPv6 address of the local host + @param ol_addr - outside IPv4 address of the local host + @param il_port - inside port number id of the local host/inside ICMP id + @param ol_port - outside port number of the local host/outside ICMP id + @param il_addr - inside IPv6 address of the remote host + @param ol_addr - outside IPv4 address of the remote host + @param l_port - port number of the remote host (not used for ICMP) + @param vrf_id - VRF id of tenant + @param proto - protocol number +*/ +define nat64_st_details { + u32 context; + u8 il_addr[16]; + u8 ol_addr[4]; + u16 il_port; + u16 ol_port; + u8 ir_addr[16]; + u8 or_addr[4]; + u16 r_port; + u32 vrf_id; + u8 proto; +}; + +/** \brief Add/del NAT64 prefix + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param prefix - NAT64 prefix + @param prefix - NAT64 prefix length + @param vrf_id - VRF id of tenant + @param is_add - 1 if add, 0 if delete +*/ +autoreply define nat64_add_del_prefix { + u32 client_index; + u32 context; + u8 prefix[16]; + u8 prefix_len; + u32 vrf_id; + u8 is_add; +}; + +/** \brief Dump NAT64 prefix + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define nat64_prefix_dump { + u32 client_index; + u32 context; +}; + +/** \brief Dump NAT64 prefix details response + @param context - sender context, to match reply w/ request + @param prefix - NAT64 prefix + @param prefix - NAT64 prefix length + @param vrf_id - VRF id of tenant +*/ +define nat64_prefix_details { + u32 context; + u8 prefix[16]; + u8 prefix_len; + u32 vrf_id; +}; diff --git a/src/plugins/nat/nat.c b/src/plugins/nat/nat.c new file mode 100644 index 00000000000..ac39be956a8 --- /dev/null +++ b/src/plugins/nat/nat.c @@ -0,0 +1,2842 @@ +/* + * snat.c - simple nat plugin + * + * Copyright (c) 2016 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 <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/ip/ip4.h> +#include <vnet/plugin/plugin.h> +#include <nat/nat.h> +#include <nat/nat_ipfix_logging.h> +#include <nat/nat_det.h> +#include <nat/nat64.h> +#include <vnet/fib/fib_table.h> +#include <vnet/fib/ip4_fib.h> + +#include <vpp/app/version.h> + +snat_main_t snat_main; + + +/* Hook up input features */ +VNET_FEATURE_INIT (ip4_snat_in2out, static) = { + .arc_name = "ip4-unicast", + .node_name = "nat44-in2out", + .runs_before = VNET_FEATURES ("nat44-out2in"), +}; +VNET_FEATURE_INIT (ip4_snat_out2in, static) = { + .arc_name = "ip4-unicast", + .node_name = "nat44-out2in", + .runs_before = VNET_FEATURES ("ip4-lookup"), +}; +VNET_FEATURE_INIT (ip4_snat_det_in2out, static) = { + .arc_name = "ip4-unicast", + .node_name = "nat44-det-in2out", + .runs_before = VNET_FEATURES ("nat44-det-out2in"), +}; +VNET_FEATURE_INIT (ip4_snat_det_out2in, static) = { + .arc_name = "ip4-unicast", + .node_name = "nat44-det-out2in", + .runs_before = VNET_FEATURES ("ip4-lookup"), +}; +VNET_FEATURE_INIT (ip4_snat_in2out_worker_handoff, static) = { + .arc_name = "ip4-unicast", + .node_name = "nat44-in2out-worker-handoff", + .runs_before = VNET_FEATURES ("nat44-out2in-worker-handoff"), +}; +VNET_FEATURE_INIT (ip4_snat_out2in_worker_handoff, static) = { + .arc_name = "ip4-unicast", + .node_name = "nat44-out2in-worker-handoff", + .runs_before = VNET_FEATURES ("ip4-lookup"), +}; +VNET_FEATURE_INIT (ip4_snat_in2out_fast, static) = { + .arc_name = "ip4-unicast", + .node_name = "nat44-in2out-fast", + .runs_before = VNET_FEATURES ("nat44-out2in-fast"), +}; +VNET_FEATURE_INIT (ip4_snat_out2in_fast, static) = { + .arc_name = "ip4-unicast", + .node_name = "nat44-out2in-fast", + .runs_before = VNET_FEATURES ("ip4-lookup"), +}; +VNET_FEATURE_INIT (ip4_snat_hairpin_dst, static) = { + .arc_name = "ip4-unicast", + .node_name = "nat44-hairpin-dst", + .runs_before = VNET_FEATURES ("ip4-lookup"), +}; + +/* Hook up output features */ +VNET_FEATURE_INIT (ip4_snat_in2out_output, static) = { + .arc_name = "ip4-output", + .node_name = "nat44-in2out-output", + .runs_before = VNET_FEATURES ("interface-output"), +}; +VNET_FEATURE_INIT (ip4_snat_in2out_output_worker_handoff, static) = { + .arc_name = "ip4-output", + .node_name = "nat44-in2out-output-worker-handoff", + .runs_before = VNET_FEATURES ("interface-output"), +}; +VNET_FEATURE_INIT (ip4_snat_hairpin_src, static) = { + .arc_name = "ip4-output", + .node_name = "nat44-hairpin-src", + .runs_before = VNET_FEATURES ("interface-output"), +}; + + +/* *INDENT-OFF* */ +VLIB_PLUGIN_REGISTER () = { + .version = VPP_BUILD_VER, + .description = "Network Address Translation", +}; +/* *INDENT-ON* */ + +/** + * @brief Add/del NAT address to FIB. + * + * Add the external NAT address to the FIB as receive entries. This ensures + * that VPP will reply to ARP for this address and we don't need to enable + * proxy ARP on the outside interface. + * + * @param addr IPv4 address. + * @param plen address prefix length + * @param sw_if_index Interface. + * @param is_add If 0 delete, otherwise add. + */ +void +snat_add_del_addr_to_fib (ip4_address_t * addr, u8 p_len, u32 sw_if_index, + int is_add) +{ + fib_prefix_t prefix = { + .fp_len = p_len, + .fp_proto = FIB_PROTOCOL_IP4, + .fp_addr = { + .ip4.as_u32 = addr->as_u32, + }, + }; + u32 fib_index = ip4_fib_table_get_index_for_sw_if_index(sw_if_index); + + if (is_add) + fib_table_entry_update_one_path(fib_index, + &prefix, + FIB_SOURCE_PLUGIN_HI, + (FIB_ENTRY_FLAG_CONNECTED | + FIB_ENTRY_FLAG_LOCAL | + FIB_ENTRY_FLAG_EXCLUSIVE), + DPO_PROTO_IP4, + NULL, + sw_if_index, + ~0, + 1, + NULL, + FIB_ROUTE_PATH_FLAG_NONE); + else + fib_table_entry_delete(fib_index, + &prefix, + FIB_SOURCE_PLUGIN_HI); +} + +void snat_add_address (snat_main_t *sm, ip4_address_t *addr, u32 vrf_id) +{ + snat_address_t * ap; + snat_interface_t *i; + + if (vrf_id != ~0) + sm->vrf_mode = 1; + + /* Check if address already exists */ + vec_foreach (ap, sm->addresses) + { + if (ap->addr.as_u32 == addr->as_u32) + return; + } + + vec_add2 (sm->addresses, ap, 1); + ap->addr = *addr; + if (vrf_id != ~0) + ap->fib_index = + fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP4, vrf_id); + else + ap->fib_index = ~0; +#define _(N, i, n, s) \ + clib_bitmap_alloc (ap->busy_##n##_port_bitmap, 65535); + foreach_snat_protocol +#undef _ + + /* Add external address to FIB */ + pool_foreach (i, sm->interfaces, + ({ + if (i->is_inside) + continue; + + snat_add_del_addr_to_fib(addr, 32, i->sw_if_index, 1); + break; + })); + pool_foreach (i, sm->output_feature_interfaces, + ({ + if (i->is_inside) + continue; + + snat_add_del_addr_to_fib(addr, 32, i->sw_if_index, 1); + break; + })); +} + +static int is_snat_address_used_in_static_mapping (snat_main_t *sm, + ip4_address_t addr) +{ + snat_static_mapping_t *m; + pool_foreach (m, sm->static_mappings, + ({ + if (m->external_addr.as_u32 == addr.as_u32) + return 1; + })); + + return 0; +} + +void increment_v4_address (ip4_address_t * a) +{ + u32 v; + + v = clib_net_to_host_u32(a->as_u32) + 1; + a->as_u32 = clib_host_to_net_u32(v); +} + +static void +snat_add_static_mapping_when_resolved (snat_main_t * sm, + ip4_address_t l_addr, + u16 l_port, + u32 sw_if_index, + u16 e_port, + u32 vrf_id, + snat_protocol_t proto, + int addr_only, + int is_add) +{ + snat_static_map_resolve_t *rp; + + vec_add2 (sm->to_resolve, rp, 1); + rp->l_addr.as_u32 = l_addr.as_u32; + rp->l_port = l_port; + rp->sw_if_index = sw_if_index; + rp->e_port = e_port; + rp->vrf_id = vrf_id; + rp->proto = proto; + rp->addr_only = addr_only; + rp->is_add = is_add; +} + +/** + * @brief Add static mapping. + * + * Create static mapping between local addr+port and external addr+port. + * + * @param l_addr Local IPv4 address. + * @param e_addr External IPv4 address. + * @param l_port Local port number. + * @param e_port External port number. + * @param vrf_id VRF ID. + * @param addr_only If 0 address port and pair mapping, otherwise address only. + * @param sw_if_index External port instead of specific IP address. + * @param is_add If 0 delete static mapping, otherwise add. + * + * @returns + */ +int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr, + u16 l_port, u16 e_port, u32 vrf_id, int addr_only, + u32 sw_if_index, snat_protocol_t proto, int is_add) +{ + snat_main_t * sm = &snat_main; + snat_static_mapping_t *m; + snat_session_key_t m_key; + clib_bihash_kv_8_8_t kv, value; + snat_address_t *a = 0; + u32 fib_index = ~0; + uword * p; + snat_interface_t *interface; + int i; + + /* If the external address is a specific interface address */ + if (sw_if_index != ~0) + { + ip4_address_t * first_int_addr; + + /* Might be already set... */ + first_int_addr = ip4_interface_first_address + (sm->ip4_main, sw_if_index, 0 /* just want the address*/); + + /* DHCP resolution required? */ + if (first_int_addr == 0) + { + snat_add_static_mapping_when_resolved + (sm, l_addr, l_port, sw_if_index, e_port, vrf_id, proto, + addr_only, is_add); + return 0; + } + else + e_addr.as_u32 = first_int_addr->as_u32; + } + + m_key.addr = e_addr; + m_key.port = addr_only ? 0 : e_port; + m_key.protocol = addr_only ? 0 : proto; + m_key.fib_index = sm->outside_fib_index; + kv.key = m_key.as_u64; + if (clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value)) + m = 0; + else + m = pool_elt_at_index (sm->static_mappings, value.value); + + if (is_add) + { + if (m) + return VNET_API_ERROR_VALUE_EXIST; + + /* Convert VRF id to FIB index */ + if (vrf_id != ~0) + { + p = hash_get (sm->ip4_main->fib_index_by_table_id, vrf_id); + if (!p) + return VNET_API_ERROR_NO_SUCH_FIB; + fib_index = p[0]; + } + /* If not specified use inside VRF id from SNAT plugin startup config */ + else + { + fib_index = sm->inside_fib_index; + vrf_id = sm->inside_vrf_id; + } + + /* Find external address in allocated addresses and reserve port for + address and port pair mapping when dynamic translations enabled */ + if (!addr_only && !(sm->static_mapping_only)) + { + for (i = 0; i < vec_len (sm->addresses); i++) + { + if (sm->addresses[i].addr.as_u32 == e_addr.as_u32) + { + a = sm->addresses + i; + /* External port must be unused */ + switch (proto) + { +#define _(N, j, n, s) \ + case SNAT_PROTOCOL_##N: \ + if (clib_bitmap_get_no_check (a->busy_##n##_port_bitmap, e_port)) \ + return VNET_API_ERROR_INVALID_VALUE; \ + clib_bitmap_set_no_check (a->busy_##n##_port_bitmap, e_port, 1); \ + if (e_port > 1024) \ + a->busy_##n##_ports++; \ + break; + foreach_snat_protocol +#undef _ + default: + clib_warning("unknown_protocol"); + return VNET_API_ERROR_INVALID_VALUE_2; + } + break; + } + } + /* External address must be allocated */ + if (!a) + return VNET_API_ERROR_NO_SUCH_ENTRY; + } + + pool_get (sm->static_mappings, m); + memset (m, 0, sizeof (*m)); + m->local_addr = l_addr; + m->external_addr = e_addr; + m->addr_only = addr_only; + m->vrf_id = vrf_id; + m->fib_index = fib_index; + if (!addr_only) + { + m->local_port = l_port; + m->external_port = e_port; + m->proto = proto; + } + + m_key.addr = m->local_addr; + m_key.port = m->local_port; + m_key.protocol = m->proto; + m_key.fib_index = m->fib_index; + kv.key = m_key.as_u64; + kv.value = m - sm->static_mappings; + clib_bihash_add_del_8_8(&sm->static_mapping_by_local, &kv, 1); + + m_key.addr = m->external_addr; + m_key.port = m->external_port; + m_key.fib_index = sm->outside_fib_index; + kv.key = m_key.as_u64; + kv.value = m - sm->static_mappings; + clib_bihash_add_del_8_8(&sm->static_mapping_by_external, &kv, 1); + + /* Assign worker */ + if (sm->workers) + { + snat_user_key_t w_key0; + snat_worker_key_t w_key1; + + w_key0.addr = m->local_addr; + w_key0.fib_index = m->fib_index; + kv.key = w_key0.as_u64; + + if (clib_bihash_search_8_8 (&sm->worker_by_in, &kv, &value)) + { + kv.value = sm->first_worker_index + + sm->workers[sm->next_worker++ % vec_len (sm->workers)]; + + clib_bihash_add_del_8_8 (&sm->worker_by_in, &kv, 1); + } + else + { + kv.value = value.value; + } + + w_key1.addr = m->external_addr; + w_key1.port = clib_host_to_net_u16 (m->external_port); + w_key1.fib_index = sm->outside_fib_index; + kv.key = w_key1.as_u64; + clib_bihash_add_del_8_8 (&sm->worker_by_out, &kv, 1); + } + } + else + { + if (!m) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + /* Free external address port */ + if (!addr_only && !(sm->static_mapping_only)) + { + for (i = 0; i < vec_len (sm->addresses); i++) + { + if (sm->addresses[i].addr.as_u32 == e_addr.as_u32) + { + a = sm->addresses + i; + switch (proto) + { +#define _(N, j, n, s) \ + case SNAT_PROTOCOL_##N: \ + clib_bitmap_set_no_check (a->busy_##n##_port_bitmap, e_port, 0); \ + if (e_port > 1024) \ + a->busy_##n##_ports--; \ + break; + foreach_snat_protocol +#undef _ + default: + clib_warning("unknown_protocol"); + return VNET_API_ERROR_INVALID_VALUE_2; + } + break; + } + } + } + + m_key.addr = m->local_addr; + m_key.port = m->local_port; + m_key.protocol = m->proto; + m_key.fib_index = m->fib_index; + kv.key = m_key.as_u64; + clib_bihash_add_del_8_8(&sm->static_mapping_by_local, &kv, 0); + + m_key.addr = m->external_addr; + m_key.port = m->external_port; + m_key.fib_index = sm->outside_fib_index; + kv.key = m_key.as_u64; + clib_bihash_add_del_8_8(&sm->static_mapping_by_external, &kv, 0); + + /* Delete session(s) for static mapping if exist */ + if (!(sm->static_mapping_only) || + (sm->static_mapping_only && sm->static_mapping_connection_tracking)) + { + snat_user_key_t u_key; + snat_user_t *u; + dlist_elt_t * head, * elt; + u32 elt_index, head_index, del_elt_index; + u32 ses_index; + u64 user_index; + snat_session_t * s; + snat_main_per_thread_data_t *tsm; + + u_key.addr = m->local_addr; + u_key.fib_index = m->fib_index; + kv.key = u_key.as_u64; + if (!clib_bihash_search_8_8 (&sm->user_hash, &kv, &value)) + { + user_index = value.value; + if (!clib_bihash_search_8_8 (&sm->worker_by_in, &kv, &value)) + tsm = vec_elt_at_index (sm->per_thread_data, value.value); + else + tsm = vec_elt_at_index (sm->per_thread_data, sm->num_workers); + u = pool_elt_at_index (tsm->users, user_index); + if (u->nstaticsessions) + { + head_index = u->sessions_per_user_list_head_index; + head = pool_elt_at_index (tsm->list_pool, head_index); + elt_index = head->next; + elt = pool_elt_at_index (tsm->list_pool, elt_index); + ses_index = elt->value; + while (ses_index != ~0) + { + s = pool_elt_at_index (tsm->sessions, ses_index); + del_elt_index = elt_index; + elt_index = elt->next; + elt = pool_elt_at_index (tsm->list_pool, elt_index); + ses_index = elt->value; + + if (!addr_only) + { + if ((s->out2in.addr.as_u32 != e_addr.as_u32) && + (clib_net_to_host_u16 (s->out2in.port) != e_port)) + continue; + } + + if (snat_is_unk_proto_session (s)) + { + clib_bihash_kv_16_8_t up_kv; + snat_unk_proto_ses_key_t up_key; + up_key.l_addr = s->in2out.addr; + up_key.r_addr = s->ext_host_addr; + up_key.fib_index = s->in2out.fib_index; + up_key.proto = s->in2out.port; + up_key.rsvd[0] = up_key.rsvd[1] = up_key.rsvd[2] = 0; + up_kv.key[0] = up_key.as_u64[0]; + up_kv.key[1] = up_key.as_u64[1]; + if (clib_bihash_add_del_16_8 (&sm->in2out_unk_proto, + &up_kv, 0)) + clib_warning ("in2out key del failed"); + + up_key.l_addr = s->out2in.addr; + up_key.fib_index = s->out2in.fib_index; + up_kv.key[0] = up_key.as_u64[0]; + up_kv.key[1] = up_key.as_u64[1]; + if (clib_bihash_add_del_16_8 (&sm->out2in_unk_proto, + &up_kv, 0)) + clib_warning ("out2in key del failed"); + + goto delete; + } + /* log NAT event */ + snat_ipfix_logging_nat44_ses_delete(s->in2out.addr.as_u32, + s->out2in.addr.as_u32, + s->in2out.protocol, + s->in2out.port, + s->out2in.port, + s->in2out.fib_index); + + value.key = s->in2out.as_u64; + if (clib_bihash_add_del_8_8 (&sm->in2out, &value, 0)) + clib_warning ("in2out key del failed"); + value.key = s->out2in.as_u64; + if (clib_bihash_add_del_8_8 (&sm->out2in, &value, 0)) + clib_warning ("out2in key del failed"); +delete: + pool_put (tsm->sessions, s); + + clib_dlist_remove (tsm->list_pool, del_elt_index); + pool_put_index (tsm->list_pool, del_elt_index); + u->nstaticsessions--; + + if (!addr_only) + break; + } + if (addr_only) + { + pool_put (tsm->users, u); + clib_bihash_add_del_8_8 (&sm->user_hash, &kv, 0); + } + } + } + } + + /* Delete static mapping from pool */ + pool_put (sm->static_mappings, m); + } + + if (!addr_only) + return 0; + + /* Add/delete external address to FIB */ + pool_foreach (interface, sm->interfaces, + ({ + if (interface->is_inside) + continue; + + snat_add_del_addr_to_fib(&e_addr, 32, interface->sw_if_index, is_add); + break; + })); + pool_foreach (interface, sm->output_feature_interfaces, + ({ + if (interface->is_inside) + continue; + + snat_add_del_addr_to_fib(&e_addr, 32, interface->sw_if_index, is_add); + break; + })); + + return 0; +} + +int snat_del_address (snat_main_t *sm, ip4_address_t addr, u8 delete_sm) +{ + snat_address_t *a = 0; + snat_session_t *ses; + u32 *ses_to_be_removed = 0, *ses_index; + clib_bihash_kv_8_8_t kv, value; + snat_user_key_t user_key; + snat_user_t *u; + snat_main_per_thread_data_t *tsm; + snat_static_mapping_t *m; + snat_interface_t *interface; + int i; + + /* Find SNAT address */ + for (i=0; i < vec_len (sm->addresses); i++) + { + if (sm->addresses[i].addr.as_u32 == addr.as_u32) + { + a = sm->addresses + i; + break; + } + } + if (!a) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + if (delete_sm) + { + pool_foreach (m, sm->static_mappings, + ({ + if (m->external_addr.as_u32 == addr.as_u32) + (void) snat_add_static_mapping (m->local_addr, m->external_addr, + m->local_port, m->external_port, + m->vrf_id, m->addr_only, ~0, + m->proto, 0); + })); + } + else + { + /* Check if address is used in some static mapping */ + if (is_snat_address_used_in_static_mapping(sm, addr)) + { + clib_warning ("address used in static mapping"); + return VNET_API_ERROR_UNSPECIFIED; + } + } + + if (a->fib_index != ~0) + fib_table_unlock(a->fib_index, FIB_PROTOCOL_IP4); + + /* Delete sessions using address */ + if (a->busy_tcp_ports || a->busy_udp_ports || a->busy_icmp_ports) + { + vec_foreach (tsm, sm->per_thread_data) + { + pool_foreach (ses, tsm->sessions, ({ + if (ses->out2in.addr.as_u32 == addr.as_u32) + { + if (snat_is_unk_proto_session (ses)) + { + clib_bihash_kv_16_8_t up_kv; + snat_unk_proto_ses_key_t up_key; + up_key.l_addr = ses->in2out.addr; + up_key.r_addr = ses->ext_host_addr; + up_key.fib_index = ses->in2out.fib_index; + up_key.proto = ses->in2out.port; + up_key.rsvd[0] = up_key.rsvd[1] = up_key.rsvd[2] = 0; + up_kv.key[0] = up_key.as_u64[0]; + up_kv.key[1] = up_key.as_u64[1]; + if (clib_bihash_add_del_16_8 (&sm->in2out_unk_proto, + &up_kv, 0)) + clib_warning ("in2out key del failed"); + + up_key.l_addr = ses->out2in.addr; + up_key.fib_index = ses->out2in.fib_index; + up_kv.key[0] = up_key.as_u64[0]; + up_kv.key[1] = up_key.as_u64[1]; + if (clib_bihash_add_del_16_8 (&sm->out2in_unk_proto, + &up_kv, 0)) + clib_warning ("out2in key del failed"); + } + else + { + /* log NAT event */ + snat_ipfix_logging_nat44_ses_delete(ses->in2out.addr.as_u32, + ses->out2in.addr.as_u32, + ses->in2out.protocol, + ses->in2out.port, + ses->out2in.port, + ses->in2out.fib_index); + kv.key = ses->in2out.as_u64; + clib_bihash_add_del_8_8 (&sm->in2out, &kv, 0); + kv.key = ses->out2in.as_u64; + clib_bihash_add_del_8_8 (&sm->out2in, &kv, 0); + } + vec_add1 (ses_to_be_removed, ses - tsm->sessions); + clib_dlist_remove (tsm->list_pool, ses->per_user_index); + user_key.addr = ses->in2out.addr; + user_key.fib_index = ses->in2out.fib_index; + kv.key = user_key.as_u64; + if (!clib_bihash_search_8_8 (&sm->user_hash, &kv, &value)) + { + u = pool_elt_at_index (tsm->users, value.value); + u->nsessions--; + } + } + })); + + vec_foreach (ses_index, ses_to_be_removed) + pool_put_index (tsm->sessions, ses_index[0]); + + vec_free (ses_to_be_removed); + } + } + + vec_del1 (sm->addresses, i); + + /* Delete external address from FIB */ + pool_foreach (interface, sm->interfaces, + ({ + if (interface->is_inside) + continue; + + snat_add_del_addr_to_fib(&addr, 32, interface->sw_if_index, 0); + break; + })); + pool_foreach (interface, sm->output_feature_interfaces, + ({ + if (interface->is_inside) + continue; + + snat_add_del_addr_to_fib(&addr, 32, interface->sw_if_index, 0); + break; + })); + + return 0; +} + +int snat_interface_add_del (u32 sw_if_index, u8 is_inside, int is_del) +{ + snat_main_t *sm = &snat_main; + snat_interface_t *i; + const char * feature_name; + snat_address_t * ap; + snat_static_mapping_t * m; + snat_det_map_t * dm; + + if (sm->static_mapping_only && !(sm->static_mapping_connection_tracking)) + feature_name = is_inside ? "nat44-in2out-fast" : "nat44-out2in-fast"; + else + { + if (sm->num_workers > 1 && !sm->deterministic) + feature_name = is_inside ? "nat44-in2out-worker-handoff" : "nat44-out2in-worker-handoff"; + else if (sm->deterministic) + feature_name = is_inside ? "nat44-det-in2out" : "nat44-det-out2in"; + else + feature_name = is_inside ? "nat44-in2out" : "nat44-out2in"; + } + + vnet_feature_enable_disable ("ip4-unicast", feature_name, sw_if_index, + !is_del, 0, 0); + + if (sm->fq_in2out_index == ~0 && !sm->deterministic && sm->num_workers > 1) + sm->fq_in2out_index = vlib_frame_queue_main_init (sm->in2out_node_index, 0); + + if (sm->fq_out2in_index == ~0 && !sm->deterministic && sm->num_workers > 1) + sm->fq_out2in_index = vlib_frame_queue_main_init (sm->out2in_node_index, 0); + + pool_foreach (i, sm->interfaces, + ({ + if (i->sw_if_index == sw_if_index) + { + if (is_del) + pool_put (sm->interfaces, i); + else + return VNET_API_ERROR_VALUE_EXIST; + + goto fib; + } + })); + + if (is_del) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + pool_get (sm->interfaces, i); + i->sw_if_index = sw_if_index; + i->is_inside = is_inside; + + /* Add/delete external addresses to FIB */ +fib: + if (is_inside) + return 0; + + vec_foreach (ap, sm->addresses) + snat_add_del_addr_to_fib(&ap->addr, 32, sw_if_index, !is_del); + + pool_foreach (m, sm->static_mappings, + ({ + if (!(m->addr_only)) + continue; + + snat_add_del_addr_to_fib(&m->external_addr, 32, sw_if_index, !is_del); + })); + + pool_foreach (dm, sm->det_maps, + ({ + snat_add_del_addr_to_fib(&dm->out_addr, dm->out_plen, sw_if_index, !is_del); + })); + + return 0; +} + +int snat_interface_add_del_output_feature (u32 sw_if_index, + u8 is_inside, + int is_del) +{ + snat_main_t *sm = &snat_main; + snat_interface_t *i; + snat_address_t * ap; + snat_static_mapping_t * m; + + if (sm->deterministic || + (sm->static_mapping_only && !(sm->static_mapping_connection_tracking))) + return VNET_API_ERROR_UNSUPPORTED; + + if (is_inside) + { + vnet_feature_enable_disable ("ip4-unicast", "nat44-hairpin-dst", + sw_if_index, !is_del, 0, 0); + vnet_feature_enable_disable ("ip4-output", "nat44-hairpin-src", + sw_if_index, !is_del, 0, 0); + goto fq; + } + + if (sm->num_workers > 1) + { + vnet_feature_enable_disable ("ip4-unicast", "nat44-out2in-worker-handoff", + sw_if_index, !is_del, 0, 0); + vnet_feature_enable_disable ("ip4-output", + "nat44-in2out-output-worker-handoff", + sw_if_index, !is_del, 0, 0); + } + else + { + vnet_feature_enable_disable ("ip4-unicast", "nat44-out2in", sw_if_index, + !is_del, 0, 0); + vnet_feature_enable_disable ("ip4-output", "nat44-in2out-output", + sw_if_index, !is_del, 0, 0); + } + +fq: + if (sm->fq_in2out_output_index == ~0 && sm->num_workers > 1) + sm->fq_in2out_output_index = + vlib_frame_queue_main_init (sm->in2out_output_node_index, 0); + + if (sm->fq_out2in_index == ~0 && sm->num_workers > 1) + sm->fq_out2in_index = vlib_frame_queue_main_init (sm->out2in_node_index, 0); + + pool_foreach (i, sm->output_feature_interfaces, + ({ + if (i->sw_if_index == sw_if_index) + { + if (is_del) + pool_put (sm->output_feature_interfaces, i); + else + return VNET_API_ERROR_VALUE_EXIST; + + goto fib; + } + })); + + if (is_del) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + pool_get (sm->output_feature_interfaces, i); + i->sw_if_index = sw_if_index; + i->is_inside = is_inside; + + /* Add/delete external addresses to FIB */ +fib: + if (is_inside) + return 0; + + vec_foreach (ap, sm->addresses) + snat_add_del_addr_to_fib(&ap->addr, 32, sw_if_index, !is_del); + + pool_foreach (m, sm->static_mappings, + ({ + if (!(m->addr_only)) + continue; + + snat_add_del_addr_to_fib(&m->external_addr, 32, sw_if_index, !is_del); + })); + + return 0; +} + +int snat_set_workers (uword * bitmap) +{ + snat_main_t *sm = &snat_main; + int i, j = 0; + + if (sm->num_workers < 2) + return VNET_API_ERROR_FEATURE_DISABLED; + + if (clib_bitmap_last_set (bitmap) >= sm->num_workers) + return VNET_API_ERROR_INVALID_WORKER; + + vec_free (sm->workers); + clib_bitmap_foreach (i, bitmap, + ({ + vec_add1(sm->workers, i); + sm->per_thread_data[i].snat_thread_index = j; + j++; + })); + + sm->port_per_thread = (0xffff - 1024) / _vec_len (sm->workers); + sm->num_snat_thread = _vec_len (sm->workers); + + return 0; +} + + +static void +snat_ip4_add_del_interface_address_cb (ip4_main_t * im, + uword opaque, + u32 sw_if_index, + ip4_address_t * address, + u32 address_length, + u32 if_address_index, + u32 is_delete); + +static clib_error_t * snat_init (vlib_main_t * vm) +{ + snat_main_t * sm = &snat_main; + clib_error_t * error = 0; + ip4_main_t * im = &ip4_main; + ip_lookup_main_t * lm = &im->lookup_main; + uword *p; + vlib_thread_registration_t *tr; + vlib_thread_main_t *tm = vlib_get_thread_main (); + uword *bitmap = 0; + u32 i; + ip4_add_del_interface_address_callback_t cb4; + + sm->vlib_main = vm; + sm->vnet_main = vnet_get_main(); + sm->ip4_main = im; + sm->ip4_lookup_main = lm; + sm->api_main = &api_main; + sm->first_worker_index = 0; + sm->next_worker = 0; + sm->num_workers = 0; + sm->num_snat_thread = 1; + sm->workers = 0; + sm->port_per_thread = 0xffff - 1024; + sm->fq_in2out_index = ~0; + sm->fq_out2in_index = ~0; + sm->udp_timeout = SNAT_UDP_TIMEOUT; + sm->tcp_established_timeout = SNAT_TCP_ESTABLISHED_TIMEOUT; + sm->tcp_transitory_timeout = SNAT_TCP_TRANSITORY_TIMEOUT; + sm->icmp_timeout = SNAT_ICMP_TIMEOUT; + + p = hash_get_mem (tm->thread_registrations_by_name, "workers"); + if (p) + { + tr = (vlib_thread_registration_t *) p[0]; + if (tr) + { + sm->num_workers = tr->count; + sm->first_worker_index = tr->first_index; + } + } + + vec_validate (sm->per_thread_data, tm->n_vlib_mains - 1); + + /* Use all available workers by default */ + if (sm->num_workers > 1) + { + for (i=0; i < sm->num_workers; i++) + bitmap = clib_bitmap_set (bitmap, i, 1); + snat_set_workers(bitmap); + clib_bitmap_free (bitmap); + } + else + { + sm->per_thread_data[0].snat_thread_index = 0; + } + + error = snat_api_init(vm, sm); + if (error) + return error; + + /* Set up the interface address add/del callback */ + cb4.function = snat_ip4_add_del_interface_address_cb; + cb4.function_opaque = 0; + + vec_add1 (im->add_del_interface_address_callbacks, cb4); + + /* Init IPFIX logging */ + snat_ipfix_logging_init(vm); + + error = nat64_init(vm); + + return error; +} + +VLIB_INIT_FUNCTION (snat_init); + +void snat_free_outside_address_and_port (snat_main_t * sm, + snat_session_key_t * k, + u32 address_index) +{ + snat_address_t *a; + u16 port_host_byte_order = clib_net_to_host_u16 (k->port); + + ASSERT (address_index < vec_len (sm->addresses)); + + a = sm->addresses + address_index; + + switch (k->protocol) + { +#define _(N, i, n, s) \ + case SNAT_PROTOCOL_##N: \ + ASSERT (clib_bitmap_get_no_check (a->busy_##n##_port_bitmap, \ + port_host_byte_order) == 1); \ + clib_bitmap_set_no_check (a->busy_##n##_port_bitmap, \ + port_host_byte_order, 0); \ + a->busy_##n##_ports--; \ + break; + foreach_snat_protocol +#undef _ + default: + clib_warning("unknown_protocol"); + return; + } +} + +/** + * @brief Match NAT44 static mapping. + * + * @param sm NAT main. + * @param match Address and port to match. + * @param mapping External or local address and port of the matched mapping. + * @param by_external If 0 match by local address otherwise match by external + * address. + * @param is_addr_only If matched mapping is address only + * + * @returns 0 if match found otherwise 1. + */ +int snat_static_mapping_match (snat_main_t * sm, + snat_session_key_t match, + snat_session_key_t * mapping, + u8 by_external, + u8 *is_addr_only) +{ + clib_bihash_kv_8_8_t kv, value; + snat_static_mapping_t *m; + snat_session_key_t m_key; + clib_bihash_8_8_t *mapping_hash = &sm->static_mapping_by_local; + + if (by_external) + mapping_hash = &sm->static_mapping_by_external; + + m_key.addr = match.addr; + m_key.port = clib_net_to_host_u16 (match.port); + m_key.protocol = match.protocol; + m_key.fib_index = match.fib_index; + + kv.key = m_key.as_u64; + + if (clib_bihash_search_8_8 (mapping_hash, &kv, &value)) + { + /* Try address only mapping */ + m_key.port = 0; + m_key.protocol = 0; + kv.key = m_key.as_u64; + if (clib_bihash_search_8_8 (mapping_hash, &kv, &value)) + return 1; + } + + m = pool_elt_at_index (sm->static_mappings, value.value); + + if (by_external) + { + mapping->addr = m->local_addr; + /* Address only mapping doesn't change port */ + mapping->port = m->addr_only ? match.port + : clib_host_to_net_u16 (m->local_port); + mapping->fib_index = m->fib_index; + } + else + { + mapping->addr = m->external_addr; + /* Address only mapping doesn't change port */ + mapping->port = m->addr_only ? match.port + : clib_host_to_net_u16 (m->external_port); + mapping->fib_index = sm->outside_fib_index; + } + + if (PREDICT_FALSE(is_addr_only != 0)) + *is_addr_only = m->addr_only; + + return 0; +} + +static_always_inline u16 +snat_random_port (snat_main_t * sm, u16 min, u16 max) +{ + return min + random_u32 (&sm->random_seed) / + (random_u32_max() / (max - min + 1) + 1); +} + +int snat_alloc_outside_address_and_port (snat_main_t * sm, + u32 fib_index, + u32 thread_index, + snat_session_key_t * k, + u32 * address_indexp) +{ + int i; + snat_address_t *a; + u32 portnum; + + for (i = 0; i < vec_len (sm->addresses); i++) + { + a = sm->addresses + i; + if (sm->vrf_mode && a->fib_index != ~0 && a->fib_index != fib_index) + continue; + switch (k->protocol) + { +#define _(N, j, n, s) \ + case SNAT_PROTOCOL_##N: \ + if (a->busy_##n##_ports < (sm->port_per_thread * sm->num_snat_thread)) \ + { \ + while (1) \ + { \ + portnum = (sm->port_per_thread * \ + sm->per_thread_data[thread_index].snat_thread_index) + \ + snat_random_port(sm, 0, sm->port_per_thread) + 1024; \ + if (clib_bitmap_get_no_check (a->busy_##n##_port_bitmap, portnum)) \ + continue; \ + clib_bitmap_set_no_check (a->busy_##n##_port_bitmap, portnum, 1); \ + a->busy_##n##_ports++; \ + k->addr = a->addr; \ + k->port = clib_host_to_net_u16(portnum); \ + *address_indexp = i; \ + return 0; \ + } \ + } \ + break; + foreach_snat_protocol +#undef _ + default: + clib_warning("unknown protocol"); + return 1; + } + + } + /* Totally out of translations to use... */ + snat_ipfix_logging_addresses_exhausted(0); + return 1; +} + + +static clib_error_t * +add_address_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + snat_main_t * sm = &snat_main; + ip4_address_t start_addr, end_addr, this_addr; + u32 start_host_order, end_host_order; + u32 vrf_id = ~0; + int i, count; + int is_add = 1; + int rv = 0; + clib_error_t *error = 0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "%U - %U", + unformat_ip4_address, &start_addr, + unformat_ip4_address, &end_addr)) + ; + else if (unformat (line_input, "tenant-vrf %u", &vrf_id)) + ; + else if (unformat (line_input, "%U", unformat_ip4_address, &start_addr)) + end_addr = start_addr; + else if (unformat (line_input, "del")) + is_add = 0; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + if (sm->static_mapping_only) + { + error = clib_error_return (0, "static mapping only mode"); + goto done; + } + + start_host_order = clib_host_to_net_u32 (start_addr.as_u32); + end_host_order = clib_host_to_net_u32 (end_addr.as_u32); + + if (end_host_order < start_host_order) + { + error = clib_error_return (0, "end address less than start address"); + goto done; + } + + count = (end_host_order - start_host_order) + 1; + + if (count > 1024) + clib_warning ("%U - %U, %d addresses...", + format_ip4_address, &start_addr, + format_ip4_address, &end_addr, + count); + + this_addr = start_addr; + + for (i = 0; i < count; i++) + { + if (is_add) + snat_add_address (sm, &this_addr, vrf_id); + else + rv = snat_del_address (sm, this_addr, 0); + + switch (rv) + { + case VNET_API_ERROR_NO_SUCH_ENTRY: + error = clib_error_return (0, "S-NAT address not exist."); + goto done; + case VNET_API_ERROR_UNSPECIFIED: + error = clib_error_return (0, "S-NAT address used in static mapping."); + goto done; + default: + break; + } + + increment_v4_address (&this_addr); + } + +done: + unformat_free (line_input); + + return error; +} + +VLIB_CLI_COMMAND (add_address_command, static) = { + .path = "nat44 add address", + .short_help = "nat44 add addresses <ip4-range-start> [- <ip4-range-end>] " + "[tenant-vrf <vrf-id>] [del]", + .function = add_address_command_fn, +}; + +static clib_error_t * +snat_feature_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + vnet_main_t * vnm = vnet_get_main(); + clib_error_t * error = 0; + u32 sw_if_index; + u32 * inside_sw_if_indices = 0; + u32 * outside_sw_if_indices = 0; + u8 is_output_feature = 0; + int is_del = 0; + int i; + + sw_if_index = ~0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "in %U", unformat_vnet_sw_interface, + vnm, &sw_if_index)) + vec_add1 (inside_sw_if_indices, sw_if_index); + else if (unformat (line_input, "out %U", unformat_vnet_sw_interface, + vnm, &sw_if_index)) + vec_add1 (outside_sw_if_indices, sw_if_index); + else if (unformat (line_input, "output-feature")) + is_output_feature = 1; + else if (unformat (line_input, "del")) + is_del = 1; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + if (vec_len (inside_sw_if_indices)) + { + for (i = 0; i < vec_len(inside_sw_if_indices); i++) + { + sw_if_index = inside_sw_if_indices[i]; + if (is_output_feature) + { + if (snat_interface_add_del_output_feature (sw_if_index, 1, is_del)) + { + error = clib_error_return (0, "%s %U failed", + is_del ? "del" : "add", + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, + sw_if_index)); + goto done; + } + } + else + { + if (snat_interface_add_del (sw_if_index, 1, is_del)) + { + error = clib_error_return (0, "%s %U failed", + is_del ? "del" : "add", + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, + sw_if_index)); + goto done; + } + } + } + } + + if (vec_len (outside_sw_if_indices)) + { + for (i = 0; i < vec_len(outside_sw_if_indices); i++) + { + sw_if_index = outside_sw_if_indices[i]; + if (is_output_feature) + { + if (snat_interface_add_del_output_feature (sw_if_index, 0, is_del)) + { + error = clib_error_return (0, "%s %U failed", + is_del ? "del" : "add", + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, + sw_if_index)); + goto done; + } + } + else + { + if (snat_interface_add_del (sw_if_index, 0, is_del)) + { + error = clib_error_return (0, "%s %U failed", + is_del ? "del" : "add", + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, + sw_if_index)); + goto done; + } + } + } + } + +done: + unformat_free (line_input); + vec_free (inside_sw_if_indices); + vec_free (outside_sw_if_indices); + + return error; +} + +VLIB_CLI_COMMAND (set_interface_snat_command, static) = { + .path = "set interface nat44", + .function = snat_feature_command_fn, + .short_help = "set interface nat44 in <intfc> out <intfc> [output-feature] " + "[del]", +}; + +uword +unformat_snat_protocol (unformat_input_t * input, va_list * args) +{ + u32 *r = va_arg (*args, u32 *); + + if (0); +#define _(N, i, n, s) else if (unformat (input, s)) *r = SNAT_PROTOCOL_##N; + foreach_snat_protocol +#undef _ + else + return 0; + return 1; +} + +u8 * +format_snat_protocol (u8 * s, va_list * args) +{ + u32 i = va_arg (*args, u32); + u8 *t = 0; + + switch (i) + { +#define _(N, j, n, str) case SNAT_PROTOCOL_##N: t = (u8 *) str; break; + foreach_snat_protocol +#undef _ + default: + s = format (s, "unknown"); + return s; + } + s = format (s, "%s", t); + return s; +} + +static clib_error_t * +add_static_mapping_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + clib_error_t * error = 0; + ip4_address_t l_addr, e_addr; + u32 l_port = 0, e_port = 0, vrf_id = ~0; + int is_add = 1; + int addr_only = 1; + u32 sw_if_index = ~0; + vnet_main_t * vnm = vnet_get_main(); + int rv; + snat_protocol_t proto; + u8 proto_set = 0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "local %U %u", unformat_ip4_address, &l_addr, + &l_port)) + addr_only = 0; + else if (unformat (line_input, "local %U", unformat_ip4_address, &l_addr)) + ; + else if (unformat (line_input, "external %U %u", unformat_ip4_address, + &e_addr, &e_port)) + addr_only = 0; + else if (unformat (line_input, "external %U", unformat_ip4_address, + &e_addr)) + ; + else if (unformat (line_input, "external %U %u", + unformat_vnet_sw_interface, vnm, &sw_if_index, + &e_port)) + addr_only = 0; + + else if (unformat (line_input, "external %U", + unformat_vnet_sw_interface, vnm, &sw_if_index)) + ; + else if (unformat (line_input, "vrf %u", &vrf_id)) + ; + else if (unformat (line_input, "%U", unformat_snat_protocol, &proto)) + proto_set = 1; + else if (unformat (line_input, "del")) + is_add = 0; + else + { + error = clib_error_return (0, "unknown input: '%U'", + format_unformat_error, line_input); + goto done; + } + } + + if (!addr_only && !proto_set) + { + error = clib_error_return (0, "missing protocol"); + goto done; + } + + rv = snat_add_static_mapping(l_addr, e_addr, (u16) l_port, (u16) e_port, + vrf_id, addr_only, sw_if_index, proto, is_add); + + switch (rv) + { + case VNET_API_ERROR_INVALID_VALUE: + error = clib_error_return (0, "External port already in use."); + goto done; + case VNET_API_ERROR_NO_SUCH_ENTRY: + if (is_add) + error = clib_error_return (0, "External addres must be allocated."); + else + error = clib_error_return (0, "Mapping not exist."); + goto done; + case VNET_API_ERROR_NO_SUCH_FIB: + error = clib_error_return (0, "No such VRF id."); + goto done; + case VNET_API_ERROR_VALUE_EXIST: + error = clib_error_return (0, "Mapping already exist."); + goto done; + default: + break; + } + +done: + unformat_free (line_input); + + return error; +} + +/*? + * @cliexpar + * @cliexstart{snat add static mapping} + * Static mapping allows hosts on the external network to initiate connection + * to to the local network host. + * To create static mapping between local host address 10.0.0.3 port 6303 and + * external address 4.4.4.4 port 3606 for TCP protocol use: + * vpp# nat44 add static mapping local tcp 10.0.0.3 6303 external 4.4.4.4 3606 + * If not runnig "static mapping only" NAT plugin mode use before: + * vpp# nat44 add address 4.4.4.4 + * To create static mapping between local and external address use: + * vpp# nat44 add static mapping local 10.0.0.3 external 4.4.4.4 + * @cliexend +?*/ +VLIB_CLI_COMMAND (add_static_mapping_command, static) = { + .path = "nat44 add static mapping", + .function = add_static_mapping_command_fn, + .short_help = + "nat44 add static mapping local tcp|udp|icmp <addr> [<port>] external <addr> [<port>] [vrf <table-id>] [del]", +}; + +static clib_error_t * +set_workers_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + uword *bitmap = 0; + int rv = 0; + clib_error_t *error = 0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "%U", unformat_bitmap_list, &bitmap)) + ; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + if (bitmap == 0) + { + error = clib_error_return (0, "List of workers must be specified."); + goto done; + } + + rv = snat_set_workers(bitmap); + + clib_bitmap_free (bitmap); + + switch (rv) + { + case VNET_API_ERROR_INVALID_WORKER: + error = clib_error_return (0, "Invalid worker(s)."); + goto done; + case VNET_API_ERROR_FEATURE_DISABLED: + error = clib_error_return (0, + "Supported only if 2 or more workes available."); + goto done; + default: + break; + } + +done: + unformat_free (line_input); + + return error; +} + +/*? + * @cliexpar + * @cliexstart{set snat workers} + * Set NAT workers if 2 or more workers available, use: + * vpp# set snat workers 0-2,5 + * @cliexend +?*/ +VLIB_CLI_COMMAND (set_workers_command, static) = { + .path = "set nat workers", + .function = set_workers_command_fn, + .short_help = + "set nat workers <workers-list>", +}; + +static clib_error_t * +snat_ipfix_logging_enable_disable_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + unformat_input_t _line_input, *line_input = &_line_input; + u32 domain_id = 0; + u32 src_port = 0; + u8 enable = 1; + int rv = 0; + clib_error_t *error = 0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "domain %d", &domain_id)) + ; + else if (unformat (line_input, "src-port %d", &src_port)) + ; + else if (unformat (line_input, "disable")) + enable = 0; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + rv = snat_ipfix_logging_enable_disable (enable, domain_id, (u16) src_port); + + if (rv) + { + error = clib_error_return (0, "ipfix logging enable failed"); + goto done; + } + +done: + unformat_free (line_input); + + return error; +} + +/*? + * @cliexpar + * @cliexstart{snat ipfix logging} + * To enable NAT IPFIX logging use: + * vpp# nat ipfix logging + * To set IPFIX exporter use: + * vpp# set ipfix exporter collector 10.10.10.3 src 10.10.10.1 + * @cliexend +?*/ +VLIB_CLI_COMMAND (snat_ipfix_logging_enable_disable_command, static) = { + .path = "nat ipfix logging", + .function = snat_ipfix_logging_enable_disable_command_fn, + .short_help = "nat ipfix logging [domain <domain-id>] [src-port <port>] [disable]", +}; + +static u32 +snat_get_worker_in2out_cb (ip4_header_t * ip0, u32 rx_fib_index0) +{ + snat_main_t *sm = &snat_main; + snat_user_key_t key0; + clib_bihash_kv_8_8_t kv0, value0; + u32 next_worker_index = 0; + + key0.addr = ip0->src_address; + key0.fib_index = rx_fib_index0; + + kv0.key = key0.as_u64; + + /* Ever heard of of the "user" before? */ + if (clib_bihash_search_8_8 (&sm->worker_by_in, &kv0, &value0)) + { + /* No, assign next available worker (RR) */ + next_worker_index = sm->first_worker_index; + if (vec_len (sm->workers)) + { + next_worker_index += + sm->workers[sm->next_worker++ % _vec_len (sm->workers)]; + } + + /* add non-traslated packets worker lookup */ + kv0.value = next_worker_index; + clib_bihash_add_del_8_8 (&sm->worker_by_in, &kv0, 1); + } + else + next_worker_index = value0.value; + + return next_worker_index; +} + +static u32 +snat_get_worker_out2in_cb (ip4_header_t * ip0, u32 rx_fib_index0) +{ + snat_main_t *sm = &snat_main; + snat_worker_key_t key0; + clib_bihash_kv_8_8_t kv0, value0; + udp_header_t * udp0; + u32 next_worker_index = 0; + + udp0 = ip4_next_header (ip0); + + key0.addr = ip0->dst_address; + key0.port = udp0->dst_port; + key0.fib_index = rx_fib_index0; + + if (PREDICT_FALSE(ip0->protocol == IP_PROTOCOL_ICMP)) + { + icmp46_header_t * icmp0 = (icmp46_header_t *) udp0; + icmp_echo_header_t *echo0 = (icmp_echo_header_t *)(icmp0+1); + key0.port = echo0->identifier; + } + + kv0.key = key0.as_u64; + + /* Ever heard of of the "user" before? */ + if (clib_bihash_search_8_8 (&sm->worker_by_out, &kv0, &value0)) + { + key0.port = 0; + kv0.key = key0.as_u64; + + if (clib_bihash_search_8_8 (&sm->worker_by_out, &kv0, &value0)) + { + /* No, assign next available worker (RR) */ + next_worker_index = sm->first_worker_index; + if (vec_len (sm->workers)) + { + next_worker_index += + sm->workers[sm->next_worker++ % _vec_len (sm->workers)]; + } + } + else + { + /* Static mapping without port */ + next_worker_index = value0.value; + } + + /* Add to translated packets worker lookup */ + kv0.value = next_worker_index; + clib_bihash_add_del_8_8 (&sm->worker_by_out, &kv0, 1); + } + else + next_worker_index = value0.value; + + return next_worker_index; +} + +static clib_error_t * +snat_config (vlib_main_t * vm, unformat_input_t * input) +{ + snat_main_t * sm = &snat_main; + u32 translation_buckets = 1024; + u32 translation_memory_size = 128<<20; + u32 user_buckets = 128; + u32 user_memory_size = 64<<20; + u32 max_translations_per_user = 100; + u32 outside_vrf_id = 0; + u32 inside_vrf_id = 0; + u32 static_mapping_buckets = 1024; + u32 static_mapping_memory_size = 64<<20; + u8 static_mapping_only = 0; + u8 static_mapping_connection_tracking = 0; + + sm->deterministic = 0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "translation hash buckets %d", &translation_buckets)) + ; + else if (unformat (input, "translation hash memory %d", + &translation_memory_size)); + else if (unformat (input, "user hash buckets %d", &user_buckets)) + ; + else if (unformat (input, "user hash memory %d", + &user_memory_size)) + ; + else if (unformat (input, "max translations per user %d", + &max_translations_per_user)) + ; + else if (unformat (input, "outside VRF id %d", + &outside_vrf_id)) + ; + else if (unformat (input, "inside VRF id %d", + &inside_vrf_id)) + ; + else if (unformat (input, "static mapping only")) + { + static_mapping_only = 1; + if (unformat (input, "connection tracking")) + static_mapping_connection_tracking = 1; + } + else if (unformat (input, "deterministic")) + sm->deterministic = 1; + else + return clib_error_return (0, "unknown input '%U'", + format_unformat_error, input); + } + + /* for show commands, etc. */ + sm->translation_buckets = translation_buckets; + sm->translation_memory_size = translation_memory_size; + sm->user_buckets = user_buckets; + sm->user_memory_size = user_memory_size; + sm->max_translations_per_user = max_translations_per_user; + sm->outside_vrf_id = outside_vrf_id; + sm->outside_fib_index = fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP4, + outside_vrf_id); + sm->inside_vrf_id = inside_vrf_id; + sm->inside_fib_index = fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP4, + inside_vrf_id); + sm->static_mapping_only = static_mapping_only; + sm->static_mapping_connection_tracking = static_mapping_connection_tracking; + + if (sm->deterministic) + { + sm->in2out_node_index = snat_det_in2out_node.index; + sm->in2out_output_node_index = ~0; + sm->out2in_node_index = snat_det_out2in_node.index; + sm->icmp_match_in2out_cb = icmp_match_in2out_det; + sm->icmp_match_out2in_cb = icmp_match_out2in_det; + } + else + { + sm->worker_in2out_cb = snat_get_worker_in2out_cb; + sm->worker_out2in_cb = snat_get_worker_out2in_cb; + sm->in2out_node_index = snat_in2out_node.index; + sm->in2out_output_node_index = snat_in2out_output_node.index; + sm->out2in_node_index = snat_out2in_node.index; + if (!static_mapping_only || + (static_mapping_only && static_mapping_connection_tracking)) + { + sm->icmp_match_in2out_cb = icmp_match_in2out_slow; + sm->icmp_match_out2in_cb = icmp_match_out2in_slow; + + clib_bihash_init_8_8 (&sm->worker_by_in, "worker-by-in", user_buckets, + user_memory_size); + + clib_bihash_init_8_8 (&sm->worker_by_out, "worker-by-out", user_buckets, + user_memory_size); + + clib_bihash_init_8_8 (&sm->in2out, "in2out", translation_buckets, + translation_memory_size); + + clib_bihash_init_8_8 (&sm->out2in, "out2in", translation_buckets, + translation_memory_size); + + clib_bihash_init_8_8 (&sm->user_hash, "users", user_buckets, + user_memory_size); + + clib_bihash_init_16_8 (&sm->in2out_unk_proto, "in2out-unk-proto", + translation_buckets, translation_memory_size); + + clib_bihash_init_16_8 (&sm->out2in_unk_proto, "out2in-unk-proto", + translation_buckets, translation_memory_size); + } + else + { + sm->icmp_match_in2out_cb = icmp_match_in2out_fast; + sm->icmp_match_out2in_cb = icmp_match_out2in_fast; + } + clib_bihash_init_8_8 (&sm->static_mapping_by_local, + "static_mapping_by_local", static_mapping_buckets, + static_mapping_memory_size); + + clib_bihash_init_8_8 (&sm->static_mapping_by_external, + "static_mapping_by_external", static_mapping_buckets, + static_mapping_memory_size); + } + + return 0; +} + +VLIB_CONFIG_FUNCTION (snat_config, "nat"); + +u8 * format_snat_session_state (u8 * s, va_list * args) +{ + u32 i = va_arg (*args, u32); + u8 *t = 0; + + switch (i) + { +#define _(v, N, str) case SNAT_SESSION_##N: t = (u8 *) str; break; + foreach_snat_session_state +#undef _ + default: + t = format (t, "unknown"); + } + s = format (s, "%s", t); + return s; +} + +u8 * format_snat_key (u8 * s, va_list * args) +{ + snat_session_key_t * key = va_arg (*args, snat_session_key_t *); + char * protocol_string = "unknown"; + static char *protocol_strings[] = { + "UDP", + "TCP", + "ICMP", + }; + + if (key->protocol < ARRAY_LEN(protocol_strings)) + protocol_string = protocol_strings[key->protocol]; + + s = format (s, "%U proto %s port %d fib %d", + format_ip4_address, &key->addr, protocol_string, + clib_net_to_host_u16 (key->port), key->fib_index); + return s; +} + +u8 * format_snat_session (u8 * s, va_list * args) +{ + snat_main_t * sm __attribute__((unused)) = va_arg (*args, snat_main_t *); + snat_session_t * sess = va_arg (*args, snat_session_t *); + + if (snat_is_unk_proto_session (sess)) + { + s = format (s, " i2o %U proto %u fib %u\n", + format_ip4_address, &sess->in2out.addr, sess->in2out.port, + sess->in2out.fib_index); + s = format (s, " o2i %U proto %u fib %u\n", + format_ip4_address, &sess->out2in.addr, sess->out2in.port, + sess->out2in.fib_index); + } + else + { + s = format (s, " i2o %U\n", format_snat_key, &sess->in2out); + s = format (s, " o2i %U\n", format_snat_key, &sess->out2in); + } + s = format (s, " last heard %.2f\n", sess->last_heard); + s = format (s, " total pkts %d, total bytes %lld\n", + sess->total_pkts, sess->total_bytes); + if (snat_is_session_static (sess)) + s = format (s, " static translation\n"); + else + s = format (s, " dynamic translation\n"); + + return s; +} + +u8 * format_snat_user (u8 * s, va_list * args) +{ + snat_main_per_thread_data_t * sm = va_arg (*args, snat_main_per_thread_data_t *); + snat_user_t * u = va_arg (*args, snat_user_t *); + int verbose = va_arg (*args, int); + dlist_elt_t * head, * elt; + u32 elt_index, head_index; + u32 session_index; + snat_session_t * sess; + + s = format (s, "%U: %d dynamic translations, %d static translations\n", + format_ip4_address, &u->addr, u->nsessions, u->nstaticsessions); + + if (verbose == 0) + return s; + + if (u->nsessions || u->nstaticsessions) + { + head_index = u->sessions_per_user_list_head_index; + head = pool_elt_at_index (sm->list_pool, head_index); + + elt_index = head->next; + elt = pool_elt_at_index (sm->list_pool, elt_index); + session_index = elt->value; + + while (session_index != ~0) + { + sess = pool_elt_at_index (sm->sessions, session_index); + + s = format (s, " %U\n", format_snat_session, sm, sess); + + elt_index = elt->next; + elt = pool_elt_at_index (sm->list_pool, elt_index); + session_index = elt->value; + } + } + + return s; +} + +u8 * format_snat_static_mapping (u8 * s, va_list * args) +{ + snat_static_mapping_t *m = va_arg (*args, snat_static_mapping_t *); + + if (m->addr_only) + s = format (s, "local %U external %U vrf %d", + format_ip4_address, &m->local_addr, + format_ip4_address, &m->external_addr, + m->vrf_id); + else + s = format (s, "%U local %U:%d external %U:%d vrf %d", + format_snat_protocol, m->proto, + format_ip4_address, &m->local_addr, m->local_port, + format_ip4_address, &m->external_addr, m->external_port, + m->vrf_id); + + return s; +} + +u8 * format_snat_static_map_to_resolve (u8 * s, va_list * args) +{ + snat_static_map_resolve_t *m = va_arg (*args, snat_static_map_resolve_t *); + vnet_main_t *vnm = vnet_get_main(); + + if (m->addr_only) + s = format (s, "local %U external %U vrf %d", + format_ip4_address, &m->l_addr, + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, m->sw_if_index), + m->vrf_id); + else + s = format (s, "%U local %U:%d external %U:%d vrf %d", + format_snat_protocol, m->proto, + format_ip4_address, &m->l_addr, m->l_port, + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, m->sw_if_index), m->e_port, + m->vrf_id); + + return s; +} + +u8 * format_det_map_ses (u8 * s, va_list * args) +{ + snat_det_map_t * det_map = va_arg (*args, snat_det_map_t *); + ip4_address_t in_addr, out_addr; + u32 in_offset, out_offset; + snat_det_session_t * ses = va_arg (*args, snat_det_session_t *); + u32 * i = va_arg (*args, u32 *); + + u32 user_index = *i / SNAT_DET_SES_PER_USER; + in_addr.as_u32 = clib_host_to_net_u32 ( + clib_net_to_host_u32(det_map->in_addr.as_u32) + user_index); + in_offset = clib_net_to_host_u32(in_addr.as_u32) - + clib_net_to_host_u32(det_map->in_addr.as_u32); + out_offset = in_offset / det_map->sharing_ratio; + out_addr.as_u32 = clib_host_to_net_u32( + clib_net_to_host_u32(det_map->out_addr.as_u32) + out_offset); + s = format (s, "in %U:%d out %U:%d external host %U:%d state: %U expire: %d\n", + format_ip4_address, &in_addr, + clib_net_to_host_u16 (ses->in_port), + format_ip4_address, &out_addr, + clib_net_to_host_u16 (ses->out.out_port), + format_ip4_address, &ses->out.ext_host_addr, + clib_net_to_host_u16 (ses->out.ext_host_port), + format_snat_session_state, ses->state, + ses->expire); + + return s; +} + +static clib_error_t * +show_snat_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + int verbose = 0; + snat_main_t * sm = &snat_main; + snat_user_t * u; + snat_static_mapping_t *m; + snat_interface_t *i; + snat_address_t * ap; + vnet_main_t *vnm = vnet_get_main(); + snat_main_per_thread_data_t *tsm; + u32 users_num = 0, sessions_num = 0, *worker, *sw_if_index; + uword j = 0; + snat_static_map_resolve_t *rp; + snat_det_map_t * dm; + snat_det_session_t * ses; + + if (unformat (input, "detail")) + verbose = 1; + else if (unformat (input, "verbose")) + verbose = 2; + + if (sm->static_mapping_only) + { + if (sm->static_mapping_connection_tracking) + vlib_cli_output (vm, "NAT plugin mode: static mapping only connection " + "tracking"); + else + vlib_cli_output (vm, "NAT plugin mode: static mapping only"); + } + else if (sm->deterministic) + { + vlib_cli_output (vm, "NAT plugin mode: deterministic mapping"); + } + else + { + vlib_cli_output (vm, "NAT plugin mode: dynamic translations enabled"); + } + + if (verbose > 0) + { + pool_foreach (i, sm->interfaces, + ({ + vlib_cli_output (vm, "%U %s", format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, i->sw_if_index), + i->is_inside ? "in" : "out"); + })); + + pool_foreach (i, sm->output_feature_interfaces, + ({ + vlib_cli_output (vm, "%U output-feature %s", + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, i->sw_if_index), + i->is_inside ? "in" : "out"); + })); + + if (vec_len (sm->auto_add_sw_if_indices)) + { + vlib_cli_output (vm, "NAT44 pool addresses interfaces:"); + vec_foreach (sw_if_index, sm->auto_add_sw_if_indices) + { + vlib_cli_output (vm, "%U", format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, *sw_if_index)); + } + } + + vec_foreach (ap, sm->addresses) + { + vlib_cli_output (vm, "%U", format_ip4_address, &ap->addr); + if (ap->fib_index != ~0) + vlib_cli_output (vm, " tenant VRF: %u", + ip4_fib_get(ap->fib_index)->table_id); + else + vlib_cli_output (vm, " tenant VRF independent"); +#define _(N, i, n, s) \ + vlib_cli_output (vm, " %d busy %s ports", ap->busy_##n##_ports, s); + foreach_snat_protocol +#undef _ + } + } + + if (sm->num_workers > 1) + { + vlib_cli_output (vm, "%d workers", vec_len (sm->workers)); + if (verbose > 0) + { + vec_foreach (worker, sm->workers) + { + vlib_worker_thread_t *w = + vlib_worker_threads + *worker + sm->first_worker_index; + vlib_cli_output (vm, " %s", w->name); + } + } + } + + if (sm->deterministic) + { + vlib_cli_output (vm, "udp timeout: %dsec", sm->udp_timeout); + vlib_cli_output (vm, "tcp-established timeout: %dsec", + sm->tcp_established_timeout); + vlib_cli_output (vm, "tcp-transitory timeout: %dsec", + sm->tcp_transitory_timeout); + vlib_cli_output (vm, "icmp timeout: %dsec", sm->icmp_timeout); + vlib_cli_output (vm, "%d deterministic mappings", + pool_elts (sm->det_maps)); + if (verbose > 0) + { + pool_foreach (dm, sm->det_maps, + ({ + vlib_cli_output (vm, "in %U/%d out %U/%d\n", + format_ip4_address, &dm->in_addr, dm->in_plen, + format_ip4_address, &dm->out_addr, dm->out_plen); + vlib_cli_output (vm, " outside address sharing ratio: %d\n", + dm->sharing_ratio); + vlib_cli_output (vm, " number of ports per inside host: %d\n", + dm->ports_per_host); + vlib_cli_output (vm, " sessions number: %d\n", dm->ses_num); + if (verbose > 1) + { + vec_foreach_index (j, dm->sessions) + { + ses = vec_elt_at_index (dm->sessions, j); + if (ses->in_port) + vlib_cli_output (vm, " %U", format_det_map_ses, dm, ses, + &j); + } + } + })); + } + } + else + { + if (sm->static_mapping_only && !(sm->static_mapping_connection_tracking)) + { + vlib_cli_output (vm, "%d static mappings", + pool_elts (sm->static_mappings)); + + if (verbose > 0) + { + pool_foreach (m, sm->static_mappings, + ({ + vlib_cli_output (vm, "%U", format_snat_static_mapping, m); + })); + } + } + else + { + vec_foreach (tsm, sm->per_thread_data) + { + users_num += pool_elts (tsm->users); + sessions_num += pool_elts (tsm->sessions); + } + + vlib_cli_output (vm, "%d users, %d outside addresses, %d active sessions," + " %d static mappings", + users_num, + vec_len (sm->addresses), + sessions_num, + pool_elts (sm->static_mappings)); + + if (verbose > 0) + { + vlib_cli_output (vm, "%U", format_bihash_8_8, &sm->in2out, + verbose - 1); + vlib_cli_output (vm, "%U", format_bihash_8_8, &sm->out2in, + verbose - 1); + vlib_cli_output (vm, "%U", format_bihash_8_8, &sm->worker_by_in, + verbose - 1); + vlib_cli_output (vm, "%U", format_bihash_8_8, &sm->worker_by_out, + verbose - 1); + vec_foreach_index (j, sm->per_thread_data) + { + tsm = vec_elt_at_index (sm->per_thread_data, j); + + if (pool_elts (tsm->users) == 0) + continue; + + vlib_worker_thread_t *w = vlib_worker_threads + j; + vlib_cli_output (vm, "Thread %d (%s at lcore %u):", j, w->name, + w->lcore_id); + vlib_cli_output (vm, " %d list pool elements", + pool_elts (tsm->list_pool)); + + pool_foreach (u, tsm->users, + ({ + vlib_cli_output (vm, " %U", format_snat_user, tsm, u, + verbose - 1); + })); + } + + if (pool_elts (sm->static_mappings)) + { + vlib_cli_output (vm, "static mappings:"); + pool_foreach (m, sm->static_mappings, + ({ + vlib_cli_output (vm, "%U", format_snat_static_mapping, m); + })); + for (j = 0; j < vec_len (sm->to_resolve); j++) + { + rp = sm->to_resolve + j; + vlib_cli_output (vm, "%U", + format_snat_static_map_to_resolve, rp); + } + } + } + } + } + return 0; +} + +VLIB_CLI_COMMAND (show_snat_command, static) = { + .path = "show nat44", + .short_help = "show nat44", + .function = show_snat_command_fn, +}; + + +static void +snat_ip4_add_del_interface_address_cb (ip4_main_t * im, + uword opaque, + u32 sw_if_index, + ip4_address_t * address, + u32 address_length, + u32 if_address_index, + u32 is_delete) +{ + snat_main_t *sm = &snat_main; + snat_static_map_resolve_t *rp; + u32 *indices_to_delete = 0; + int i, j; + int rv; + + for (i = 0; i < vec_len(sm->auto_add_sw_if_indices); i++) + { + if (sw_if_index == sm->auto_add_sw_if_indices[i]) + { + if (!is_delete) + { + /* Don't trip over lease renewal, static config */ + for (j = 0; j < vec_len(sm->addresses); j++) + if (sm->addresses[j].addr.as_u32 == address->as_u32) + return; + + snat_add_address (sm, address, ~0); + /* Scan static map resolution vector */ + for (j = 0; j < vec_len (sm->to_resolve); j++) + { + rp = sm->to_resolve + j; + /* On this interface? */ + if (rp->sw_if_index == sw_if_index) + { + /* Add the static mapping */ + rv = snat_add_static_mapping (rp->l_addr, + address[0], + rp->l_port, + rp->e_port, + rp->vrf_id, + rp->addr_only, + ~0 /* sw_if_index */, + rp->proto, + rp->is_add); + if (rv) + clib_warning ("snat_add_static_mapping returned %d", + rv); + vec_add1 (indices_to_delete, j); + } + } + /* If we resolved any of the outstanding static mappings */ + if (vec_len(indices_to_delete)) + { + /* Delete them */ + for (j = vec_len(indices_to_delete)-1; j >= 0; j--) + vec_delete(sm->to_resolve, 1, j); + vec_free(indices_to_delete); + } + return; + } + else + { + (void) snat_del_address(sm, address[0], 1); + return; + } + } + } +} + + +int snat_add_interface_address (snat_main_t *sm, u32 sw_if_index, int is_del) +{ + ip4_main_t * ip4_main = sm->ip4_main; + ip4_address_t * first_int_addr; + snat_static_map_resolve_t *rp; + u32 *indices_to_delete = 0; + int i, j; + + first_int_addr = ip4_interface_first_address (ip4_main, sw_if_index, + 0 /* just want the address*/); + + for (i = 0; i < vec_len(sm->auto_add_sw_if_indices); i++) + { + if (sm->auto_add_sw_if_indices[i] == sw_if_index) + { + if (is_del) + { + /* if have address remove it */ + if (first_int_addr) + (void) snat_del_address (sm, first_int_addr[0], 1); + else + { + for (j = 0; j < vec_len (sm->to_resolve); j++) + { + rp = sm->to_resolve + j; + if (rp->sw_if_index == sw_if_index) + vec_add1 (indices_to_delete, j); + } + if (vec_len(indices_to_delete)) + { + for (j = vec_len(indices_to_delete)-1; j >= 0; j--) + vec_del1(sm->to_resolve, j); + vec_free(indices_to_delete); + } + } + vec_del1(sm->auto_add_sw_if_indices, i); + } + else + return VNET_API_ERROR_VALUE_EXIST; + + return 0; + } + } + + if (is_del) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + /* add to the auto-address list */ + vec_add1(sm->auto_add_sw_if_indices, sw_if_index); + + /* If the address is already bound - or static - add it now */ + if (first_int_addr) + snat_add_address (sm, first_int_addr, ~0); + + return 0; +} + +static clib_error_t * +snat_add_interface_address_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + snat_main_t *sm = &snat_main; + unformat_input_t _line_input, *line_input = &_line_input; + u32 sw_if_index; + int rv; + int is_del = 0; + clib_error_t *error = 0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "%U", unformat_vnet_sw_interface, + sm->vnet_main, &sw_if_index)) + ; + else if (unformat (line_input, "del")) + is_del = 1; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + rv = snat_add_interface_address (sm, sw_if_index, is_del); + + switch (rv) + { + case 0: + break; + + default: + error = clib_error_return (0, "snat_add_interface_address returned %d", + rv); + goto done; + } + +done: + unformat_free (line_input); + + return error; +} + +VLIB_CLI_COMMAND (snat_add_interface_address_command, static) = { + .path = "nat44 add interface address", + .short_help = "nat44 add interface address <interface> [del]", + .function = snat_add_interface_address_command_fn, +}; + +static clib_error_t * +snat_det_map_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + snat_main_t *sm = &snat_main; + unformat_input_t _line_input, *line_input = &_line_input; + ip4_address_t in_addr, out_addr; + u32 in_plen, out_plen; + int is_add = 1, rv; + clib_error_t *error = 0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "in %U/%u", unformat_ip4_address, &in_addr, &in_plen)) + ; + else if (unformat (line_input, "out %U/%u", unformat_ip4_address, &out_addr, &out_plen)) + ; + else if (unformat (line_input, "del")) + is_add = 0; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + unformat_free (line_input); + + rv = snat_det_add_map(sm, &in_addr, (u8) in_plen, &out_addr, (u8)out_plen, + is_add); + + if (rv) + { + error = clib_error_return (0, "snat_det_add_map return %d", rv); + goto done; + } + +done: + unformat_free (line_input); + + return error; +} + +/*? + * @cliexpar + * @cliexstart{snat deterministic add} + * Create bijective mapping of inside address to outside address and port range + * pairs, with the purpose of enabling deterministic NAT to reduce logging in + * CGN deployments. + * To create deterministic mapping between inside network 10.0.0.0/18 and + * outside network 1.1.1.0/30 use: + * # vpp# nat44 deterministic add in 10.0.0.0/18 out 1.1.1.0/30 + * @cliexend +?*/ +VLIB_CLI_COMMAND (snat_det_map_command, static) = { + .path = "nat44 deterministic add", + .short_help = "nat44 deterministic add in <addr>/<plen> out <addr>/<plen> [del]", + .function = snat_det_map_command_fn, +}; + +static clib_error_t * +snat_det_forward_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + snat_main_t *sm = &snat_main; + unformat_input_t _line_input, *line_input = &_line_input; + ip4_address_t in_addr, out_addr; + u16 lo_port; + snat_det_map_t * dm; + clib_error_t *error = 0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "%U", unformat_ip4_address, &in_addr)) + ; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + unformat_free (line_input); + + dm = snat_det_map_by_user(sm, &in_addr); + if (!dm) + vlib_cli_output (vm, "no match"); + else + { + snat_det_forward (dm, &in_addr, &out_addr, &lo_port); + vlib_cli_output (vm, "%U:<%d-%d>", format_ip4_address, &out_addr, + lo_port, lo_port + dm->ports_per_host - 1); + } + +done: + unformat_free (line_input); + + return error; +} + +/*? + * @cliexpar + * @cliexstart{snat deterministic forward} + * Return outside address and port range from inside address for deterministic + * NAT. + * To obtain outside address and port of inside host use: + * vpp# nat44 deterministic forward 10.0.0.2 + * 1.1.1.0:<1054-1068> + * @cliexend +?*/ +VLIB_CLI_COMMAND (snat_det_forward_command, static) = { + .path = "nat44 deterministic forward", + .short_help = "nat44 deterministic forward <addr>", + .function = snat_det_forward_command_fn, +}; + +static clib_error_t * +snat_det_reverse_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + snat_main_t *sm = &snat_main; + unformat_input_t _line_input, *line_input = &_line_input; + ip4_address_t in_addr, out_addr; + u32 out_port; + snat_det_map_t * dm; + clib_error_t *error = 0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "%U:%d", unformat_ip4_address, &out_addr, &out_port)) + ; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + } + } + + unformat_free (line_input); + + if (out_port < 1024 || out_port > 65535) + { + error = clib_error_return (0, "wrong port, must be <1024-65535>"); + goto done; + } + + dm = snat_det_map_by_out(sm, &out_addr); + if (!dm) + vlib_cli_output (vm, "no match"); + else + { + snat_det_reverse (dm, &out_addr, (u16) out_port, &in_addr); + vlib_cli_output (vm, "%U", format_ip4_address, &in_addr); + } + +done: + unformat_free (line_input); + + return error; +} + +/*? + * @cliexpar + * @cliexstart{snat deterministic reverse} + * Return inside address from outside address and port for deterministic NAT. + * To obtain inside host address from outside address and port use: + * #vpp nat44 deterministic reverse 1.1.1.1:1276 + * 10.0.16.16 + * @cliexend +?*/ +VLIB_CLI_COMMAND (snat_det_reverse_command, static) = { + .path = "nat44 deterministic reverse", + .short_help = "nat44 deterministic reverse <addr>:<port>", + .function = snat_det_reverse_command_fn, +}; + +static clib_error_t * +set_timeout_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + snat_main_t *sm = &snat_main; + unformat_input_t _line_input, *line_input = &_line_input; + clib_error_t *error = 0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "udp %u", &sm->udp_timeout)) + ; + else if (unformat (line_input, "tcp-established %u", + &sm->tcp_established_timeout)) + ; + else if (unformat (line_input, "tcp-transitory %u", + &sm->tcp_transitory_timeout)) + ; + else if (unformat (line_input, "icmp %u", &sm->icmp_timeout)) + ; + else if (unformat (line_input, "reset")) + { + sm->udp_timeout = SNAT_UDP_TIMEOUT; + sm->tcp_established_timeout = SNAT_TCP_ESTABLISHED_TIMEOUT; + sm->tcp_transitory_timeout = SNAT_TCP_TRANSITORY_TIMEOUT; + sm->icmp_timeout = SNAT_ICMP_TIMEOUT; + } + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + unformat_free (line_input); + +done: + unformat_free (line_input); + + return error; +} + +/*? + * @cliexpar + * @cliexstart{set snat deterministic timeout} + * Set values of timeouts for deterministic NAT (in seconds), use: + * vpp# set nat44 deterministic timeout udp 120 tcp-established 7500 + * tcp-transitory 250 icmp 90 + * To reset default values use: + * vpp# set nat44 deterministic timeout reset + * @cliexend +?*/ +VLIB_CLI_COMMAND (set_timeout_command, static) = { + .path = "set nat44 deterministic timeout", + .function = set_timeout_command_fn, + .short_help = + "set nat44 deterministic timeout [udp <sec> | tcp-established <sec> " + "tcp-transitory <sec> | icmp <sec> | reset]", +}; + +static clib_error_t * +snat_det_close_session_out_fn (vlib_main_t *vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + snat_main_t *sm = &snat_main; + unformat_input_t _line_input, *line_input = &_line_input; + ip4_address_t out_addr, ext_addr, in_addr; + u16 out_port, ext_port; + snat_det_map_t * dm; + snat_det_session_t * ses; + snat_det_out_key_t key; + clib_error_t *error = 0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "%U:%d %U:%d", + unformat_ip4_address, &out_addr, &out_port, + unformat_ip4_address, &ext_addr, &ext_port)) + ; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + unformat_free (line_input); + + dm = snat_det_map_by_out(sm, &out_addr); + if (!dm) + vlib_cli_output (vm, "no match"); + else + { + snat_det_reverse(dm, &ext_addr, out_port, &in_addr); + key.ext_host_addr = out_addr; + key.ext_host_port = ntohs(ext_port); + key.out_port = ntohs(out_port); + ses = snat_det_get_ses_by_out(dm, &out_addr, key.as_u64); + if (!ses) + vlib_cli_output (vm, "no match"); + else + snat_det_ses_close(dm, ses); + } + +done: + unformat_free (line_input); + + return error; +} + +/*? + * @cliexpar + * @cliexstart{snat deterministic close session out} + * Close session using outside ip address and port + * and external ip address and port, use: + * vpp# nat44 deterministic close session out 1.1.1.1:1276 2.2.2.2:2387 + * @cliexend +?*/ +VLIB_CLI_COMMAND (snat_det_close_sesion_out_command, static) = { + .path = "nat44 deterministic close session out", + .short_help = "nat44 deterministic close session out " + "<out_addr>:<out_port> <ext_addr>:<ext_port>", + .function = snat_det_close_session_out_fn, +}; + +static clib_error_t * +snat_det_close_session_in_fn (vlib_main_t *vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + snat_main_t *sm = &snat_main; + unformat_input_t _line_input, *line_input = &_line_input; + ip4_address_t in_addr, ext_addr; + u16 in_port, ext_port; + snat_det_map_t * dm; + snat_det_session_t * ses; + snat_det_out_key_t key; + clib_error_t *error = 0; + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "%U:%d %U:%d", + unformat_ip4_address, &in_addr, &in_port, + unformat_ip4_address, &ext_addr, &ext_port)) + ; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + unformat_free (line_input); + + dm = snat_det_map_by_user (sm, &in_addr); + if (!dm) + vlib_cli_output (vm, "no match"); + else + { + key.ext_host_addr = ext_addr; + key.ext_host_port = ntohs (ext_port); + ses = snat_det_find_ses_by_in (dm, &in_addr, ntohs(in_port), key); + if (!ses) + vlib_cli_output (vm, "no match"); + else + snat_det_ses_close(dm, ses); + } + +done: + unformat_free(line_input); + + return error; +} + +/*? + * @cliexpar + * @cliexstart{snat deterministic close_session_in} + * Close session using inside ip address and port + * and external ip address and port, use: + * vpp# nat44 deterministic close session in 3.3.3.3:3487 2.2.2.2:2387 + * @cliexend +?*/ +VLIB_CLI_COMMAND (snat_det_close_session_in_command, static) = { + .path = "nat44 deterministic close session in", + .short_help = "nat44 deterministic close session in " + "<in_addr>:<in_port> <ext_addr>:<ext_port>", + .function = snat_det_close_session_in_fn, +}; diff --git a/src/plugins/nat/nat.h b/src/plugins/nat/nat.h new file mode 100644 index 00000000000..04c466dc6f5 --- /dev/null +++ b/src/plugins/nat/nat.h @@ -0,0 +1,541 @@ + +/* + * nat.h - NAT plugin definitions + * + * Copyright (c) 2016 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. + */ +#ifndef __included_nat_h__ +#define __included_nat_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/ethernet/ethernet.h> +#include <vnet/ip/icmp46_packet.h> +#include <vnet/api_errno.h> +#include <vppinfra/bihash_8_8.h> +#include <vppinfra/bihash_16_8.h> +#include <vppinfra/dlist.h> +#include <vppinfra/error.h> +#include <vlibapi/api.h> + + +#define SNAT_UDP_TIMEOUT 300 +#define SNAT_UDP_TIMEOUT_MIN 120 +#define SNAT_TCP_TRANSITORY_TIMEOUT 240 +#define SNAT_TCP_ESTABLISHED_TIMEOUT 7440 +#define SNAT_TCP_INCOMING_SYN 6 +#define SNAT_ICMP_TIMEOUT 60 + +#define SNAT_FLAG_HAIRPINNING (1 << 0) + +/* Key */ +typedef struct { + union + { + struct + { + ip4_address_t addr; + u16 port; + u16 protocol:3, + fib_index:13; + }; + u64 as_u64; + }; +} snat_session_key_t; + +typedef struct { + union + { + struct + { + ip4_address_t l_addr; + ip4_address_t r_addr; + u32 fib_index; + u8 proto; + u8 rsvd[3]; + }; + u64 as_u64[2]; + }; +} snat_unk_proto_ses_key_t; + +typedef struct { + union + { + struct + { + ip4_address_t ext_host_addr; + u16 ext_host_port; + u16 out_port; + }; + u64 as_u64; + }; +} snat_det_out_key_t; + +typedef struct { + union + { + struct + { + ip4_address_t addr; + u32 fib_index; + }; + u64 as_u64; + }; +} snat_user_key_t; + +typedef struct { + union + { + struct + { + ip4_address_t addr; + u16 port; + u16 fib_index; + }; + u64 as_u64; + }; +} snat_worker_key_t; + + +#define foreach_snat_protocol \ + _(UDP, 0, udp, "udp") \ + _(TCP, 1, tcp, "tcp") \ + _(ICMP, 2, icmp, "icmp") + +typedef enum { +#define _(N, i, n, s) SNAT_PROTOCOL_##N = i, + foreach_snat_protocol +#undef _ +} snat_protocol_t; + + +#define foreach_snat_session_state \ + _(0, UNKNOWN, "unknown") \ + _(1, UDP_ACTIVE, "udp-active") \ + _(2, TCP_SYN_SENT, "tcp-syn-sent") \ + _(3, TCP_ESTABLISHED, "tcp-established") \ + _(4, TCP_FIN_WAIT, "tcp-fin-wait") \ + _(5, TCP_CLOSE_WAIT, "tcp-close-wait") \ + _(6, TCP_LAST_ACK, "tcp-last-ack") \ + _(7, ICMP_ACTIVE, "icmp-active") + +typedef enum { +#define _(v, N, s) SNAT_SESSION_##N = v, + foreach_snat_session_state +#undef _ +} snat_session_state_t; + + +#define SNAT_SESSION_FLAG_STATIC_MAPPING 1 +#define SNAT_SESSION_FLAG_UNKNOWN_PROTO 2 + +typedef CLIB_PACKED(struct { + snat_session_key_t out2in; /* 0-15 */ + + snat_session_key_t in2out; /* 16-31 */ + + u32 flags; /* 32-35 */ + + /* per-user translations */ + u32 per_user_index; /* 36-39 */ + + u32 per_user_list_head_index; /* 40-43 */ + + /* Last heard timer */ + f64 last_heard; /* 44-51 */ + + u64 total_bytes; /* 52-59 */ + + u32 total_pkts; /* 60-63 */ + + /* Outside address */ + u32 outside_address_index; /* 64-67 */ + + /* External host address */ + ip4_address_t ext_host_addr; /* 68-71 */ + +}) snat_session_t; + + +typedef struct { + ip4_address_t addr; + u32 fib_index; + u32 sessions_per_user_list_head_index; + u32 nsessions; + u32 nstaticsessions; +} snat_user_t; + +typedef struct { + ip4_address_t addr; + u32 fib_index; +#define _(N, i, n, s) \ + u32 busy_##n##_ports; \ + uword * busy_##n##_port_bitmap; + foreach_snat_protocol +#undef _ +} snat_address_t; + +typedef struct { + u16 in_port; + snat_det_out_key_t out; + u8 state; + u32 expire; +} snat_det_session_t; + +typedef struct { + ip4_address_t in_addr; + u8 in_plen; + ip4_address_t out_addr; + u8 out_plen; + u32 sharing_ratio; + u16 ports_per_host; + u32 ses_num; + /* vector of sessions */ + snat_det_session_t * sessions; +} snat_det_map_t; + +typedef struct { + ip4_address_t local_addr; + ip4_address_t external_addr; + u16 local_port; + u16 external_port; + u8 addr_only; + u32 vrf_id; + u32 fib_index; + snat_protocol_t proto; +} snat_static_mapping_t; + +typedef struct { + u32 sw_if_index; + u8 is_inside; +} snat_interface_t; + +typedef struct { + ip4_address_t l_addr; + u16 l_port; + u16 e_port; + u32 sw_if_index; + u32 vrf_id; + snat_protocol_t proto; + int addr_only; + int is_add; +} snat_static_map_resolve_t; + +typedef struct { + /* User pool */ + snat_user_t * users; + + /* Session pool */ + snat_session_t * sessions; + + /* Pool of doubly-linked list elements */ + dlist_elt_t * list_pool; + + u32 snat_thread_index; +} snat_main_per_thread_data_t; + +struct snat_main_s; + +typedef u32 snat_icmp_match_function_t (struct snat_main_s *sm, + vlib_node_runtime_t *node, + u32 thread_index, + vlib_buffer_t *b0, + u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, + void *d, + void *e); + +typedef u32 (snat_get_worker_function_t) (ip4_header_t * ip, u32 rx_fib_index); + +typedef struct snat_main_s { + /* Main lookup tables */ + clib_bihash_8_8_t out2in; + clib_bihash_8_8_t in2out; + + /* Unknown protocol sessions lookup tables */ + clib_bihash_16_8_t out2in_unk_proto; + clib_bihash_16_8_t in2out_unk_proto; + + /* Find-a-user => src address lookup */ + clib_bihash_8_8_t user_hash; + + /* Non-translated packets worker lookup => src address + VRF */ + clib_bihash_8_8_t worker_by_in; + + /* Translated packets worker lookup => IP address + port number */ + clib_bihash_8_8_t worker_by_out; + + snat_icmp_match_function_t * icmp_match_in2out_cb; + snat_icmp_match_function_t * icmp_match_out2in_cb; + + u32 num_workers; + u32 first_worker_index; + u32 next_worker; + u32 * workers; + snat_get_worker_function_t * worker_in2out_cb; + snat_get_worker_function_t * worker_out2in_cb; + u16 port_per_thread; + u32 num_snat_thread; + + /* Per thread data */ + snat_main_per_thread_data_t * per_thread_data; + + /* Find a static mapping by local */ + clib_bihash_8_8_t static_mapping_by_local; + + /* Find a static mapping by external */ + clib_bihash_8_8_t static_mapping_by_external; + + /* Static mapping pool */ + snat_static_mapping_t * static_mappings; + + /* Interface pool */ + snat_interface_t * interfaces; + snat_interface_t * output_feature_interfaces; + + /* Vector of outside addresses */ + snat_address_t * addresses; + + /* sw_if_indices whose intfc addresses should be auto-added */ + u32 * auto_add_sw_if_indices; + + /* vector of interface address static mappings to resolve. */ + snat_static_map_resolve_t *to_resolve; + + /* Randomize port allocation order */ + u32 random_seed; + + /* Worker handoff index */ + u32 fq_in2out_index; + u32 fq_in2out_output_index; + u32 fq_out2in_index; + + /* in2out and out2in node index */ + u32 in2out_node_index; + u32 in2out_output_node_index; + u32 out2in_node_index; + + /* Deterministic NAT */ + snat_det_map_t * det_maps; + + /* Config parameters */ + u8 static_mapping_only; + u8 static_mapping_connection_tracking; + u8 deterministic; + u32 translation_buckets; + u32 translation_memory_size; + u32 user_buckets; + u32 user_memory_size; + u32 max_translations_per_user; + u32 outside_vrf_id; + u32 outside_fib_index; + u32 inside_vrf_id; + u32 inside_fib_index; + + /* tenant VRF aware address pool activation flag */ + u8 vrf_mode; + + /* values of various timeouts */ + u32 udp_timeout; + u32 tcp_established_timeout; + u32 tcp_transitory_timeout; + u32 icmp_timeout; + + /* API message ID base */ + u16 msg_id_base; + + /* convenience */ + vlib_main_t * vlib_main; + vnet_main_t * vnet_main; + ip4_main_t * ip4_main; + ip_lookup_main_t * ip4_lookup_main; + api_main_t * api_main; +} snat_main_t; + +extern snat_main_t snat_main; +extern vlib_node_registration_t snat_in2out_node; +extern vlib_node_registration_t snat_in2out_output_node; +extern vlib_node_registration_t snat_out2in_node; +extern vlib_node_registration_t snat_in2out_fast_node; +extern vlib_node_registration_t snat_out2in_fast_node; +extern vlib_node_registration_t snat_in2out_worker_handoff_node; +extern vlib_node_registration_t snat_in2out_output_worker_handoff_node; +extern vlib_node_registration_t snat_out2in_worker_handoff_node; +extern vlib_node_registration_t snat_det_in2out_node; +extern vlib_node_registration_t snat_det_out2in_node; +extern vlib_node_registration_t snat_hairpin_dst_node; +extern vlib_node_registration_t snat_hairpin_src_node; + +void snat_free_outside_address_and_port (snat_main_t * sm, + snat_session_key_t * k, + u32 address_index); + +int snat_alloc_outside_address_and_port (snat_main_t * sm, + u32 fib_index, + u32 thread_index, + snat_session_key_t * k, + u32 * address_indexp); + +int snat_static_mapping_match (snat_main_t * sm, + snat_session_key_t match, + snat_session_key_t * mapping, + u8 by_external, + u8 *is_addr_only); + +void snat_add_del_addr_to_fib (ip4_address_t * addr, + u8 p_len, + u32 sw_if_index, + int is_add); + +format_function_t format_snat_user; + +typedef struct { + u32 cached_sw_if_index; + u32 cached_ip4_address; +} snat_runtime_t; + +/** \brief Check if SNAT session is created from static mapping. + @param s SNAT session + @return 1 if SNAT session is created from static mapping otherwise 0 +*/ +#define snat_is_session_static(s) s->flags & SNAT_SESSION_FLAG_STATIC_MAPPING + +/** \brief Check if SNAT session for unknown protocol. + @param s SNAT session + @return 1 if SNAT session for unknown protocol otherwise 0 +*/ +#define snat_is_unk_proto_session(s) s->flags & SNAT_SESSION_FLAG_UNKNOWN_PROTO + +/* + * Why is this here? Because we don't need to touch this layer to + * simply reply to an icmp. We need to change id to a unique + * value to NAT an echo request/reply. + */ + +typedef struct { + u16 identifier; + u16 sequence; +} icmp_echo_header_t; + +always_inline u32 +ip_proto_to_snat_proto (u8 ip_proto) +{ + u32 snat_proto = ~0; + + snat_proto = (ip_proto == IP_PROTOCOL_UDP) ? SNAT_PROTOCOL_UDP : snat_proto; + snat_proto = (ip_proto == IP_PROTOCOL_TCP) ? SNAT_PROTOCOL_TCP : snat_proto; + snat_proto = (ip_proto == IP_PROTOCOL_ICMP) ? SNAT_PROTOCOL_ICMP : snat_proto; + snat_proto = (ip_proto == IP_PROTOCOL_ICMP6) ? SNAT_PROTOCOL_ICMP : snat_proto; + + return snat_proto; +} + +always_inline u8 +snat_proto_to_ip_proto (snat_protocol_t snat_proto) +{ + u8 ip_proto = ~0; + + ip_proto = (snat_proto == SNAT_PROTOCOL_UDP) ? IP_PROTOCOL_UDP : ip_proto; + ip_proto = (snat_proto == SNAT_PROTOCOL_TCP) ? IP_PROTOCOL_TCP : ip_proto; + ip_proto = (snat_proto == SNAT_PROTOCOL_ICMP) ? IP_PROTOCOL_ICMP : ip_proto; + + return ip_proto; +} + +typedef struct { + u16 src_port, dst_port; +} tcp_udp_header_t; + +u32 icmp_match_in2out_fast(snat_main_t *sm, vlib_node_runtime_t *node, + u32 thread_index, vlib_buffer_t *b0, u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, void *d, void *e); +u32 icmp_match_in2out_slow(snat_main_t *sm, vlib_node_runtime_t *node, + u32 thread_index, vlib_buffer_t *b0, u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, void *d, void *e); +u32 icmp_match_in2out_det(snat_main_t *sm, vlib_node_runtime_t *node, + u32 thread_index, vlib_buffer_t *b0, u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, void *d, void *e); +u32 icmp_match_out2in_fast(snat_main_t *sm, vlib_node_runtime_t *node, + u32 thread_index, vlib_buffer_t *b0, u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, void *d, void *e); +u32 icmp_match_out2in_slow(snat_main_t *sm, vlib_node_runtime_t *node, + u32 thread_index, vlib_buffer_t *b0, u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, void *d, void *e); +u32 icmp_match_out2in_det(snat_main_t *sm, vlib_node_runtime_t *node, + u32 thread_index, vlib_buffer_t *b0, u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, void *d, void *e); +void increment_v4_address(ip4_address_t * a); +void snat_add_address(snat_main_t *sm, ip4_address_t *addr, u32 vrf_id); +int snat_del_address(snat_main_t *sm, ip4_address_t addr, u8 delete_sm); +int snat_add_static_mapping(ip4_address_t l_addr, ip4_address_t e_addr, + u16 l_port, u16 e_port, u32 vrf_id, int addr_only, + u32 sw_if_index, snat_protocol_t proto, int is_add); +clib_error_t * snat_api_init(vlib_main_t * vm, snat_main_t * sm); +int snat_set_workers (uword * bitmap); +int snat_interface_add_del(u32 sw_if_index, u8 is_inside, int is_del); +int snat_interface_add_del_output_feature(u32 sw_if_index, u8 is_inside, + int is_del); +int snat_add_interface_address(snat_main_t *sm, u32 sw_if_index, int is_del); +uword unformat_snat_protocol(unformat_input_t * input, va_list * args); +u8 * format_snat_protocol(u8 * s, va_list * args); + +static_always_inline u8 +icmp_is_error_message (icmp46_header_t * icmp) +{ + switch(icmp->type) + { + case ICMP4_destination_unreachable: + case ICMP4_time_exceeded: + case ICMP4_parameter_problem: + case ICMP4_source_quench: + case ICMP4_redirect: + case ICMP4_alternate_host_address: + return 1; + } + return 0; +} + +static_always_inline u8 +is_interface_addr(snat_main_t *sm, vlib_node_runtime_t *node, u32 sw_if_index0, + u32 ip4_addr) +{ + snat_runtime_t *rt = (snat_runtime_t *) node->runtime_data; + ip4_address_t * first_int_addr; + + if (PREDICT_FALSE(rt->cached_sw_if_index != sw_if_index0)) + { + first_int_addr = + ip4_interface_first_address (sm->ip4_main, sw_if_index0, + 0 /* just want the address */); + rt->cached_sw_if_index = sw_if_index0; + if (first_int_addr) + rt->cached_ip4_address = first_int_addr->as_u32; + else + rt->cached_ip4_address = 0; + } + + if (PREDICT_FALSE(ip4_addr == rt->cached_ip4_address)) + return 1; + else + return 0; +} + +#endif /* __included_nat_h__ */ diff --git a/src/plugins/nat/nat64.c b/src/plugins/nat/nat64.c new file mode 100644 index 00000000000..b04901fade4 --- /dev/null +++ b/src/plugins/nat/nat64.c @@ -0,0 +1,858 @@ +/* + * Copyright (c) 2017 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. + */ +/** + * @file + * @brief NAT64 implementation + */ + +#include <nat/nat64.h> +#include <nat/nat64_db.h> +#include <vnet/fib/ip4_fib.h> + + +nat64_main_t nat64_main; + +/* *INDENT-OFF* */ + +/* Hook up input features */ +VNET_FEATURE_INIT (nat64_in2out, static) = { + .arc_name = "ip6-unicast", + .node_name = "nat64-in2out", + .runs_before = VNET_FEATURES ("ip6-lookup"), +}; +VNET_FEATURE_INIT (nat64_out2in, static) = { + .arc_name = "ip4-unicast", + .node_name = "nat64-out2in", + .runs_before = VNET_FEATURES ("ip4-lookup"), +}; + +static u8 well_known_prefix[] = { + 0x00, 0x64, 0xff, 0x9b, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +/* *INDENT-ON* */ + +clib_error_t * +nat64_init (vlib_main_t * vm) +{ + nat64_main_t *nm = &nat64_main; + clib_error_t *error = 0; + vlib_thread_main_t *tm = vlib_get_thread_main (); + + nm->is_disabled = 0; + + if (tm->n_vlib_mains > 1) + { + nm->is_disabled = 1; + goto error; + } + + if (nat64_db_init (&nm->db)) + { + error = clib_error_return (0, "NAT64 DB init failed"); + goto error; + } + + /* set session timeouts to default values */ + nm->udp_timeout = SNAT_UDP_TIMEOUT; + nm->icmp_timeout = SNAT_ICMP_TIMEOUT; + nm->tcp_trans_timeout = SNAT_TCP_TRANSITORY_TIMEOUT; + nm->tcp_est_timeout = SNAT_TCP_ESTABLISHED_TIMEOUT; + nm->tcp_incoming_syn_timeout = SNAT_TCP_INCOMING_SYN; + +error: + return error; +} + +int +nat64_add_del_pool_addr (ip4_address_t * addr, u32 vrf_id, u8 is_add) +{ + nat64_main_t *nm = &nat64_main; + snat_address_t *a = 0; + snat_interface_t *interface; + int i; + + /* Check if address already exists */ + for (i = 0; i < vec_len (nm->addr_pool); i++) + { + if (nm->addr_pool[i].addr.as_u32 == addr->as_u32) + { + a = nm->addr_pool + i; + break; + } + } + + if (is_add) + { + if (a) + return VNET_API_ERROR_VALUE_EXIST; + + vec_add2 (nm->addr_pool, a, 1); + a->addr = *addr; + a->fib_index = 0; + if (vrf_id != ~0) + a->fib_index = + fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP6, vrf_id); +#define _(N, i, n, s) \ + clib_bitmap_alloc (a->busy_##n##_port_bitmap, 65535); + foreach_snat_protocol +#undef _ + } + else + { + if (!a) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + if (a->fib_index) + fib_table_unlock (a->fib_index, FIB_PROTOCOL_IP6); + +#define _(N, id, n, s) \ + clib_bitmap_free (a->busy_##n##_port_bitmap); + foreach_snat_protocol +#undef _ + /* Delete sessions using address */ + nat64_db_free_out_addr (&nm->db, &a->addr); + vec_del1 (nm->addr_pool, i); + } + + /* Add/del external address to FIB */ + /* *INDENT-OFF* */ + pool_foreach (interface, nm->interfaces, + ({ + if (interface->is_inside) + continue; + + snat_add_del_addr_to_fib (addr, 32, interface->sw_if_index, is_add); + break; + })); + /* *INDENT-ON* */ + + return 0; +} + +void +nat64_pool_addr_walk (nat64_pool_addr_walk_fn_t fn, void *ctx) +{ + nat64_main_t *nm = &nat64_main; + snat_address_t *a = 0; + + /* *INDENT-OFF* */ + vec_foreach (a, nm->addr_pool) + { + if (fn (a, ctx)) + break; + }; + /* *INDENT-ON* */ +} + +int +nat64_add_del_interface (u32 sw_if_index, u8 is_inside, u8 is_add) +{ + nat64_main_t *nm = &nat64_main; + snat_interface_t *interface = 0, *i; + snat_address_t *ap; + const char *feature_name, *arc_name; + + /* Check if address already exists */ + /* *INDENT-OFF* */ + pool_foreach (i, nm->interfaces, + ({ + if (i->sw_if_index == sw_if_index) + { + interface = i; + break; + } + })); + /* *INDENT-ON* */ + + if (is_add) + { + if (interface) + return VNET_API_ERROR_VALUE_EXIST; + + pool_get (nm->interfaces, interface); + interface->sw_if_index = sw_if_index; + interface->is_inside = is_inside; + + } + else + { + if (!interface) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + pool_put (nm->interfaces, interface); + } + + if (!is_inside) + { + /* *INDENT-OFF* */ + vec_foreach (ap, nm->addr_pool) + snat_add_del_addr_to_fib(&ap->addr, 32, sw_if_index, is_add); + /* *INDENT-ON* */ + } + + arc_name = is_inside ? "ip6-unicast" : "ip4-unicast"; + feature_name = is_inside ? "nat64-in2out" : "nat64-out2in"; + + return vnet_feature_enable_disable (arc_name, feature_name, sw_if_index, + is_add, 0, 0); +} + +void +nat64_interfaces_walk (nat64_interface_walk_fn_t fn, void *ctx) +{ + nat64_main_t *nm = &nat64_main; + snat_interface_t *i = 0; + + /* *INDENT-OFF* */ + pool_foreach (i, nm->interfaces, + ({ + if (fn (i, ctx)) + break; + })); + /* *INDENT-ON* */ +} + +int +nat64_alloc_out_addr_and_port (u32 fib_index, snat_protocol_t proto, + ip4_address_t * addr, u16 * port) +{ + nat64_main_t *nm = &nat64_main; + snat_main_t *sm = &snat_main; + int i; + snat_address_t *a, *ga = 0; + u32 portnum; + + for (i = 0; i < vec_len (nm->addr_pool); i++) + { + a = nm->addr_pool + i; + switch (proto) + { +#define _(N, j, n, s) \ + case SNAT_PROTOCOL_##N: \ + if (a->busy_##n##_ports < (65535-1024)) \ + { \ + if (a->fib_index == fib_index) \ + { \ + while (1) \ + { \ + portnum = random_u32 (&sm->random_seed); \ + portnum &= 0xFFFF; \ + if (portnum < 1024) \ + continue; \ + if (clib_bitmap_get_no_check (a->busy_##n##_port_bitmap, \ + portnum)) \ + continue; \ + clib_bitmap_set_no_check (a->busy_##n##_port_bitmap, \ + portnum, 1); \ + a->busy_##n##_ports++; \ + *port = portnum; \ + addr->as_u32 = a->addr.as_u32; \ + return 0; \ + } \ + } \ + else if (a->fib_index == 0) \ + ga = a; \ + } \ + break; + foreach_snat_protocol +#undef _ + default: + clib_warning ("unknown protocol"); + return 1; + } + } + + if (ga) + { + switch (proto) + { +#define _(N, j, n, s) \ + case SNAT_PROTOCOL_##N: \ + while (1) \ + { \ + portnum = random_u32 (&sm->random_seed); \ + portnum &= 0xFFFF; \ + if (portnum < 1024) \ + continue; \ + if (clib_bitmap_get_no_check (a->busy_##n##_port_bitmap, \ + portnum)) \ + continue; \ + clib_bitmap_set_no_check (a->busy_##n##_port_bitmap, \ + portnum, 1); \ + a->busy_##n##_ports++; \ + *port = portnum; \ + addr->as_u32 = a->addr.as_u32; \ + return 0; \ + } + break; + foreach_snat_protocol +#undef _ + default: + clib_warning ("unknown protocol"); + return 1; + } + } + + /* Totally out of translations to use... */ + //TODO: IPFix + return 1; +} + +void +nat64_free_out_addr_and_port (ip4_address_t * addr, u16 port, + snat_protocol_t proto) +{ + nat64_main_t *nm = &nat64_main; + int i; + snat_address_t *a; + + for (i = 0; i < vec_len (nm->addr_pool); i++) + { + a = nm->addr_pool + i; + if (addr->as_u32 != a->addr.as_u32) + continue; + switch (proto) + { +#define _(N, j, n, s) \ + case SNAT_PROTOCOL_##N: \ + ASSERT (clib_bitmap_get_no_check (a->busy_##n##_port_bitmap, \ + port) == 1); \ + clib_bitmap_set_no_check (a->busy_##n##_port_bitmap, port, 0); \ + a->busy_##n##_ports--; \ + break; + foreach_snat_protocol +#undef _ + default: + clib_warning ("unknown protocol"); + return; + } + break; + } +} + +int +nat64_add_del_static_bib_entry (ip6_address_t * in_addr, + ip4_address_t * out_addr, u16 in_port, + u16 out_port, u8 proto, u32 vrf_id, u8 is_add) +{ + nat64_main_t *nm = &nat64_main; + nat64_db_bib_entry_t *bibe; + u32 fib_index = + fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP6, vrf_id); + snat_protocol_t p = ip_proto_to_snat_proto (proto); + ip46_address_t addr; + int i; + snat_address_t *a; + + addr.as_u64[0] = in_addr->as_u64[0]; + addr.as_u64[1] = in_addr->as_u64[1]; + bibe = + nat64_db_bib_entry_find (&nm->db, &addr, clib_host_to_net_u16 (in_port), + proto, fib_index, 1); + + if (is_add) + { + if (bibe) + return VNET_API_ERROR_VALUE_EXIST; + + for (i = 0; i < vec_len (nm->addr_pool); i++) + { + a = nm->addr_pool + i; + if (out_addr->as_u32 != a->addr.as_u32) + continue; + switch (p) + { +#define _(N, j, n, s) \ + case SNAT_PROTOCOL_##N: \ + if (clib_bitmap_get_no_check (a->busy_##n##_port_bitmap, \ + out_port)) \ + return VNET_API_ERROR_INVALID_VALUE; \ + clib_bitmap_set_no_check (a->busy_##n##_port_bitmap, \ + out_port, 1); \ + if (out_port > 1024) \ + a->busy_##n##_ports++; \ + break; + foreach_snat_protocol +#undef _ + default: + memset (&addr, 0, sizeof (addr)); + addr.ip4.as_u32 = out_addr->as_u32; + if (nat64_db_bib_entry_find + (&nm->db, &addr, 0, proto, fib_index, 0)) + return VNET_API_ERROR_INVALID_VALUE; + } + break; + } + bibe = + nat64_db_bib_entry_create (&nm->db, in_addr, out_addr, + clib_host_to_net_u16 (in_port), + clib_host_to_net_u16 (out_port), fib_index, + proto, 1); + if (!bibe) + return VNET_API_ERROR_UNSPECIFIED; + } + else + { + if (!bibe) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + nat64_free_out_addr_and_port (out_addr, out_port, p); + nat64_db_bib_entry_free (&nm->db, bibe); + } + + return 0; +} + +int +nat64_set_udp_timeout (u32 timeout) +{ + nat64_main_t *nm = &nat64_main; + + if (timeout == 0) + nm->udp_timeout = SNAT_UDP_TIMEOUT; + else if (timeout < SNAT_UDP_TIMEOUT_MIN) + return VNET_API_ERROR_INVALID_VALUE; + else + nm->udp_timeout = timeout; + + return 0; +} + +u32 +nat64_get_udp_timeout (void) +{ + nat64_main_t *nm = &nat64_main; + + return nm->udp_timeout; +} + +int +nat64_set_icmp_timeout (u32 timeout) +{ + nat64_main_t *nm = &nat64_main; + + if (timeout == 0) + nm->icmp_timeout = SNAT_ICMP_TIMEOUT; + else + nm->icmp_timeout = timeout; + + return 0; +} + +u32 +nat64_get_icmp_timeout (void) +{ + nat64_main_t *nm = &nat64_main; + + return nm->icmp_timeout; +} + +int +nat64_set_tcp_timeouts (u32 trans, u32 est, u32 incoming_syn) +{ + nat64_main_t *nm = &nat64_main; + + if (trans == 0) + nm->tcp_trans_timeout = SNAT_TCP_TRANSITORY_TIMEOUT; + else + nm->tcp_trans_timeout = trans; + + if (est == 0) + nm->tcp_est_timeout = SNAT_TCP_ESTABLISHED_TIMEOUT; + else + nm->tcp_est_timeout = est; + + if (incoming_syn == 0) + nm->tcp_incoming_syn_timeout = SNAT_TCP_INCOMING_SYN; + else + nm->tcp_incoming_syn_timeout = incoming_syn; + + return 0; +} + +u32 +nat64_get_tcp_trans_timeout (void) +{ + nat64_main_t *nm = &nat64_main; + + return nm->tcp_trans_timeout; +} + +u32 +nat64_get_tcp_est_timeout (void) +{ + nat64_main_t *nm = &nat64_main; + + return nm->tcp_est_timeout; +} + +u32 +nat64_get_tcp_incoming_syn_timeout (void) +{ + nat64_main_t *nm = &nat64_main; + + return nm->tcp_incoming_syn_timeout; +} + +void +nat64_session_reset_timeout (nat64_db_st_entry_t * ste, vlib_main_t * vm) +{ + nat64_main_t *nm = &nat64_main; + u32 now = (u32) vlib_time_now (vm); + + switch (ip_proto_to_snat_proto (ste->proto)) + { + case SNAT_PROTOCOL_ICMP: + ste->expire = now + nm->icmp_timeout; + return; + case SNAT_PROTOCOL_TCP: + { + switch (ste->tcp_state) + { + case NAT64_TCP_STATE_V4_INIT: + case NAT64_TCP_STATE_V6_INIT: + case NAT64_TCP_STATE_V4_FIN_RCV: + case NAT64_TCP_STATE_V6_FIN_RCV: + case NAT64_TCP_STATE_V6_FIN_V4_FIN_RCV: + case NAT64_TCP_STATE_TRANS: + ste->expire = now + nm->tcp_trans_timeout; + return; + case NAT64_TCP_STATE_ESTABLISHED: + ste->expire = now + nm->tcp_est_timeout; + return; + default: + return; + } + } + case SNAT_PROTOCOL_UDP: + ste->expire = now + nm->udp_timeout; + return; + default: + ste->expire = now + nm->udp_timeout; + return; + } +} + +void +nat64_tcp_session_set_state (nat64_db_st_entry_t * ste, tcp_header_t * tcp, + u8 is_ip6) +{ + switch (ste->tcp_state) + { + case NAT64_TCP_STATE_CLOSED: + { + if (tcp->flags & TCP_FLAG_SYN) + { + if (is_ip6) + ste->tcp_state = NAT64_TCP_STATE_V6_INIT; + else + ste->tcp_state = NAT64_TCP_STATE_V4_INIT; + } + return; + } + case NAT64_TCP_STATE_V4_INIT: + { + if (is_ip6 && (tcp->flags & TCP_FLAG_SYN)) + ste->tcp_state = NAT64_TCP_STATE_ESTABLISHED; + return; + } + case NAT64_TCP_STATE_V6_INIT: + { + if (!is_ip6 && (tcp->flags & TCP_FLAG_SYN)) + ste->tcp_state = NAT64_TCP_STATE_ESTABLISHED; + return; + } + case NAT64_TCP_STATE_ESTABLISHED: + { + if (tcp->flags & TCP_FLAG_FIN) + { + if (is_ip6) + ste->tcp_state = NAT64_TCP_STATE_V6_FIN_RCV; + else + ste->tcp_state = NAT64_TCP_STATE_V4_FIN_RCV; + } + else if (tcp->flags & TCP_FLAG_RST) + { + ste->tcp_state = NAT64_TCP_STATE_TRANS; + } + return; + } + case NAT64_TCP_STATE_V4_FIN_RCV: + { + if (is_ip6 && (tcp->flags & TCP_FLAG_FIN)) + ste->tcp_state = NAT64_TCP_STATE_V6_FIN_V4_FIN_RCV; + return; + } + case NAT64_TCP_STATE_V6_FIN_RCV: + { + if (!is_ip6 && (tcp->flags & TCP_FLAG_FIN)) + ste->tcp_state = NAT64_TCP_STATE_V6_FIN_V4_FIN_RCV; + return; + } + case NAT64_TCP_STATE_TRANS: + { + if (!(tcp->flags & TCP_FLAG_RST)) + ste->tcp_state = NAT64_TCP_STATE_ESTABLISHED; + return; + } + default: + return; + } +} + +int +nat64_add_del_prefix (ip6_address_t * prefix, u8 plen, u32 vrf_id, u8 is_add) +{ + nat64_main_t *nm = &nat64_main; + nat64_prefix_t *p = 0; + int i; + + /* Verify prefix length */ + if (plen != 32 && plen != 40 && plen != 48 && plen != 56 && plen != 64 + && plen != 96) + return VNET_API_ERROR_INVALID_VALUE; + + /* Check if tenant already have prefix */ + for (i = 0; i < vec_len (nm->pref64); i++) + { + if (nm->pref64[i].vrf_id == vrf_id) + { + p = nm->pref64 + i; + break; + } + } + + if (is_add) + { + if (!p) + { + vec_add2 (nm->pref64, p, 1); + p->fib_index = + fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP6, vrf_id); + p->vrf_id = vrf_id; + } + + p->prefix.as_u64[0] = prefix->as_u64[0]; + p->prefix.as_u64[1] = prefix->as_u64[1]; + p->plen = plen; + } + else + { + if (!p) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + vec_del1 (nm->pref64, i); + } + + return 0; +} + +void +nat64_prefix_walk (nat64_prefix_walk_fn_t fn, void *ctx) +{ + nat64_main_t *nm = &nat64_main; + nat64_prefix_t *p = 0; + + /* *INDENT-OFF* */ + vec_foreach (p, nm->pref64) + { + if (fn (p, ctx)) + break; + }; + /* *INDENT-ON* */ +} + +void +nat64_compose_ip6 (ip6_address_t * ip6, ip4_address_t * ip4, u32 fib_index) +{ + nat64_main_t *nm = &nat64_main; + nat64_prefix_t *p, *gp = 0, *prefix = 0; + + /* *INDENT-OFF* */ + vec_foreach (p, nm->pref64) + { + if (p->fib_index == fib_index) + { + prefix = p; + break; + } + + if (p->fib_index == 0) + gp = p; + }; + /* *INDENT-ON* */ + + if (!prefix) + prefix = gp; + + if (prefix) + { + memset (ip6, 0, 16); + memcpy (ip6, &p->prefix, p->plen); + switch (p->plen) + { + case 32: + ip6->as_u32[1] = ip4->as_u32; + break; + case 40: + ip6->as_u8[5] = ip4->as_u8[0]; + ip6->as_u8[6] = ip4->as_u8[1]; + ip6->as_u8[7] = ip4->as_u8[2]; + ip6->as_u8[9] = ip4->as_u8[3]; + break; + case 48: + ip6->as_u8[6] = ip4->as_u8[0]; + ip6->as_u8[7] = ip4->as_u8[1]; + ip6->as_u8[9] = ip4->as_u8[2]; + ip6->as_u8[10] = ip4->as_u8[3]; + break; + case 56: + ip6->as_u8[7] = ip4->as_u8[0]; + ip6->as_u8[9] = ip4->as_u8[1]; + ip6->as_u8[10] = ip4->as_u8[2]; + ip6->as_u8[11] = ip4->as_u8[3]; + break; + case 64: + ip6->as_u8[9] = ip4->as_u8[0]; + ip6->as_u8[10] = ip4->as_u8[1]; + ip6->as_u8[11] = ip4->as_u8[2]; + ip6->as_u8[12] = ip4->as_u8[3]; + break; + case 96: + ip6->as_u32[3] = ip4->as_u32; + break; + default: + clib_warning ("invalid prefix length"); + break; + } + } + else + { + memcpy (ip6, well_known_prefix, 16); + ip6->as_u32[3] = ip4->as_u32; + } +} + +void +nat64_extract_ip4 (ip6_address_t * ip6, ip4_address_t * ip4, u32 fib_index) +{ + nat64_main_t *nm = &nat64_main; + nat64_prefix_t *p, *gp = 0; + u8 plen = 0; + + /* *INDENT-OFF* */ + vec_foreach (p, nm->pref64) + { + if (p->fib_index == fib_index) + { + plen = p->plen; + break; + } + + if (p->vrf_id == 0) + gp = p; + }; + /* *INDENT-ON* */ + + if (!plen) + { + if (gp) + plen = gp->plen; + else + plen = 96; + } + + switch (plen) + { + case 32: + ip4->as_u32 = ip6->as_u32[1]; + break; + case 40: + ip4->as_u8[0] = ip6->as_u8[5]; + ip4->as_u8[1] = ip6->as_u8[6]; + ip4->as_u8[2] = ip6->as_u8[7]; + ip4->as_u8[3] = ip6->as_u8[9]; + break; + case 48: + ip4->as_u8[0] = ip6->as_u8[6]; + ip4->as_u8[1] = ip6->as_u8[7]; + ip4->as_u8[2] = ip6->as_u8[9]; + ip4->as_u8[3] = ip6->as_u8[10]; + break; + case 56: + ip4->as_u8[0] = ip6->as_u8[7]; + ip4->as_u8[1] = ip6->as_u8[9]; + ip4->as_u8[2] = ip6->as_u8[10]; + ip4->as_u8[3] = ip6->as_u8[11]; + break; + case 64: + ip4->as_u8[0] = ip6->as_u8[9]; + ip4->as_u8[1] = ip6->as_u8[10]; + ip4->as_u8[2] = ip6->as_u8[11]; + ip4->as_u8[3] = ip6->as_u8[12]; + break; + case 96: + ip4->as_u32 = ip6->as_u32[3]; + break; + default: + clib_warning ("invalid prefix length"); + break; + } +} + +/** + * @brief The 'nat64-expire-walk' process's main loop. + * + * Check expire time for NAT64 sessions. + */ +static uword +nat64_expire_walk_fn (vlib_main_t * vm, vlib_node_runtime_t * rt, + vlib_frame_t * f) +{ + nat64_main_t *nm = &nat64_main; + + while (!nm->is_disabled) + { + vlib_process_wait_for_event_or_clock (vm, 10.0); + vlib_process_get_events (vm, NULL); + u32 now = (u32) vlib_time_now (vm); + + nad64_db_st_free_expired (&nm->db, now); + } + + return 0; +} + +static vlib_node_registration_t nat64_expire_walk_node; + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (nat64_expire_walk_node, static) = { + .function = nat64_expire_walk_fn, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "nat64-expire-walk", +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/nat/nat64.h b/src/plugins/nat/nat64.h new file mode 100644 index 00000000000..68224cab013 --- /dev/null +++ b/src/plugins/nat/nat64.h @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2017 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. + */ +/** + * @file + * @brief NAT64 global declarations + */ +#ifndef __included_nat64_h__ +#define __included_nat64_h__ + +#include <nat/nat.h> +#include <nat/nat64_db.h> + +#define foreach_nat64_tcp_ses_state \ + _(0, CLOSED, "closed") \ + _(1, V4_INIT, "v4-init") \ + _(2, V6_INIT, "v6-init") \ + _(3, ESTABLISHED, "established") \ + _(4, V4_FIN_RCV, "v4-fin-rcv") \ + _(5, V6_FIN_RCV, "v6-fin-rcv") \ + _(6, V6_FIN_V4_FIN_RCV, "v6-fin-v4-fin-rcv") \ + _(7, TRANS, "trans") + +typedef enum +{ +#define _(v, N, s) NAT64_TCP_STATE_##N = v, + foreach_nat64_tcp_ses_state +#undef _ +} nat64_tcp_ses_state_t; + +typedef struct +{ + ip6_address_t prefix; + u8 plen; + u32 vrf_id; + u32 fib_index; +} nat64_prefix_t; + +typedef struct +{ + /** Interface pool */ + snat_interface_t *interfaces; + + /** Address pool vector */ + snat_address_t *addr_pool; + + /** Pref64 vector */ + nat64_prefix_t *pref64; + + /** BIB and session DB */ + nat64_db_t db; + + /* values of various timeouts */ + u32 udp_timeout; + u32 icmp_timeout; + u32 tcp_trans_timeout; + u32 tcp_est_timeout; + u32 tcp_incoming_syn_timeout; + + u8 is_disabled; + + snat_main_t *sm; +} nat64_main_t; + +extern nat64_main_t nat64_main; +extern vlib_node_registration_t nat64_in2out_node; +extern vlib_node_registration_t nat64_out2in_node; + +/** + * @brief Add/delete address to NAT64 pool. + * + * @param addr IPv4 address. + * @param vrf_id VRF id of tenant, ~0 means independent of VRF. + * @param is_add 1 if add, 0 if delete. + * + * @returns 0 on success, non-zero value otherwise. + */ +int nat64_add_del_pool_addr (ip4_address_t * addr, u32 vrf_id, u8 is_add); + +/** + * @brief Call back function when walking addresses in NAT64 pool, non-zero + * return value stop walk. + */ +typedef int (*nat64_pool_addr_walk_fn_t) (snat_address_t * addr, void *ctx); + +/** + * @brief Walk NAT64 pool. + * + * @param fn The function to invoke on each entry visited. + * @param ctx A context passed in the visit function. + */ +void nat64_pool_addr_walk (nat64_pool_addr_walk_fn_t fn, void *ctx); + +/** + * @brief Enable/disable NAT64 feature on the interface. + * + * @param sw_if_index Index of the interface. + * @param is_inside 1 if inside, 0 if outside. + * @param is_add 1 if add, 0 if delete. + * + * @returns 0 on success, non-zero value otherwise. + */ +int nat64_add_del_interface (u32 sw_if_index, u8 is_inside, u8 is_add); + +/** + * @brief Call back function when walking interfaces with NAT64 feature, + * non-zero return value stop walk. + */ +typedef int (*nat64_interface_walk_fn_t) (snat_interface_t * i, void *ctx); + +/** + * @brief Walk NAT64 interfaces. + * + * @param fn The function to invoke on each entry visited. + * @param ctx A context passed in the visit function. + */ +void nat64_interfaces_walk (nat64_interface_walk_fn_t fn, void *ctx); + +/** + * @brief Initialize NAT64. + * + * @param vm vlib main. + * + * @return error code. + */ +clib_error_t *nat64_init (vlib_main_t * vm); + +/** + * @brief Add/delete static NAT64 BIB entry. + * + * @param in_addr Inside IPv6 address. + * @param out_addr Outside IPv4 address. + * @param in_port Inside port number. + * @param out_port Outside port number. + * @param proto L4 protocol. + * @param vrf_id VRF id of tenant. + * @param is_add 1 if add, 0 if delete. + * + * @returns 0 on success, non-zero value otherwise. + */ +int nat64_add_del_static_bib_entry (ip6_address_t * in_addr, + ip4_address_t * out_addr, u16 in_port, + u16 out_port, u8 proto, u32 vrf_id, + u8 is_add); + +/** + * @brief Alloce IPv4 address and port pair from NAT64 pool. + * + * @param fib_index FIB index of tenant. + * @param proto L4 protocol. + * @param addr Allocated IPv4 address. + * @param port Allocated port number. + * + * @returns 0 on success, non-zero value otherwise. + */ +int nat64_alloc_out_addr_and_port (u32 fib_index, snat_protocol_t proto, + ip4_address_t * addr, u16 * port); + +/** + * @brief Free IPv4 address and port pair from NAT64 pool. + * + * @param addr IPv4 address to free. + * @param port Port number to free. + * @param proto L4 protocol. + * + * @returns 0 on success, non-zero value otherwise. + */ +void nat64_free_out_addr_and_port (ip4_address_t * addr, u16 port, + snat_protocol_t proto); + +/** + * @brief Set UDP session timeout. + * + * @param timeout Timeout value in seconds (if 0 reset to default value 300sec). + * + * @returns 0 on success, non-zero value otherwise. + */ +int nat64_set_udp_timeout (u32 timeout); + +/** + * @brief Get UDP session timeout. + * + * @returns UDP session timeout in seconds. + */ +u32 nat64_get_udp_timeout (void); + +/** + * @brief Set ICMP session timeout. + * + * @param timeout Timeout value in seconds (if 0 reset to default value 60sec). + * + * @returns 0 on success, non-zero value otherwise. + */ +int nat64_set_icmp_timeout (u32 timeout); + +/** + * @brief Get ICMP session timeout. + * + * @returns ICMP session timeout in seconds. + */ +u32 nat64_get_icmp_timeout (void); + +/** + * @brief Set TCP session timeouts. + * + * @param trans Transitory timeout in seconds (if 0 reset to default value 240sec). + * @param est Established timeout in seconds (if 0 reset to default value 7440sec). + * @param incoming_syn Incoming SYN timeout in seconds (if 0 reset to default value 6sec). + * + * @returns 0 on success, non-zero value otherwise. + */ +int nat64_set_tcp_timeouts (u32 trans, u32 est, u32 incoming_syn); + +/** + * @brief Get TCP transitory timeout. + * + * @returns TCP transitory timeout in seconds. + */ +u32 nat64_get_tcp_trans_timeout (void); + +/** + * @brief Get TCP established timeout. + * + * @returns TCP established timeout in seconds. + */ +u32 nat64_get_tcp_est_timeout (void); + +/** + * @brief Get TCP incoming SYN timeout. + * + * @returns TCP incoming SYN timeout in seconds. + */ +u32 nat64_get_tcp_incoming_syn_timeout (void); + +/** + * @brief Reset NAT64 session timeout. + * + * @param ste Session table entry. + * @param vm VLIB main. + **/ +void nat64_session_reset_timeout (nat64_db_st_entry_t * ste, + vlib_main_t * vm); + +/** + * @brief Set NAT64 TCP session state. + * + * @param ste Session table entry. + * @param tcp TCP header. + * @param is_ip6 1 if IPv6 packet, 0 if IPv4. + */ +void nat64_tcp_session_set_state (nat64_db_st_entry_t * ste, + tcp_header_t * tcp, u8 is_ip6); + +/** + * @brief Add/delete NAT64 prefix. + * + * @param prefix NAT64 prefix. + * @param plen Prefix length. + * @param vrf_id VRF id of tenant. + * @param is_add 1 if add, 0 if delete. + * + * @returns 0 on success, non-zero value otherwise. + */ +int nat64_add_del_prefix (ip6_address_t * prefix, u8 plen, u32 vrf_id, + u8 is_add); + +/** + * @brief Call back function when walking addresses in NAT64 prefixes, non-zero + * return value stop walk. + */ +typedef int (*nat64_prefix_walk_fn_t) (nat64_prefix_t * pref64, void *ctx); + +/** + * @brief Walk NAT64 prefixes. + * + * @param fn The function to invoke on each entry visited. + * @param ctx A context passed in the visit function. + */ +void nat64_prefix_walk (nat64_prefix_walk_fn_t fn, void *ctx); + +/** + * Compose IPv4-embedded IPv6 addresses. + * @param ip6 IPv4-embedded IPv6 addresses. + * @param ip4 IPv4 address. + * @param fib_index Tenant FIB index. + */ +void nat64_compose_ip6 (ip6_address_t * ip6, ip4_address_t * ip4, + u32 fib_index); + +/** + * Extract IPv4 address from the IPv4-embedded IPv6 addresses. + * + * @param ip6 IPv4-embedded IPv6 addresses. + * @param ip4 IPv4 address. + * @param fib_index Tenant FIB index. + */ +void nat64_extract_ip4 (ip6_address_t * ip6, ip4_address_t * ip4, + u32 fib_index); + +#define u8_ptr_add(ptr, index) (((u8 *)ptr) + index) +#define u16_net_add(u, val) clib_host_to_net_u16(clib_net_to_host_u16(u) + (val)) + +#endif /* __included_nat64_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/nat/nat64_cli.c b/src/plugins/nat/nat64_cli.c new file mode 100644 index 00000000000..bb62ecf2967 --- /dev/null +++ b/src/plugins/nat/nat64_cli.c @@ -0,0 +1,983 @@ +/* + * Copyright (c) 2017 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. + */ +/** + * @file + * @brief NAT64 CLI + */ + +#include <nat/nat64.h> +#include <nat/nat.h> +#include <vnet/fib/fib_table.h> + +static clib_error_t * +nat64_add_del_pool_addr_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + nat64_main_t *nm = &nat64_main; + unformat_input_t _line_input, *line_input = &_line_input; + ip4_address_t start_addr, end_addr, this_addr; + u32 start_host_order, end_host_order; + int i, count, rv; + u32 vrf_id = ~0; + u8 is_add = 1; + clib_error_t *error = 0; + + if (nm->is_disabled) + return clib_error_return (0, + "NAT64 disabled, multi thread not supported"); + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "%U - %U", + unformat_ip4_address, &start_addr, + unformat_ip4_address, &end_addr)) + ; + else if (unformat (line_input, "tenant-vrf %u", &vrf_id)) + ; + else if (unformat (line_input, "%U", unformat_ip4_address, &start_addr)) + end_addr = start_addr; + else if (unformat (line_input, "del")) + is_add = 0; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + start_host_order = clib_host_to_net_u32 (start_addr.as_u32); + end_host_order = clib_host_to_net_u32 (end_addr.as_u32); + + if (end_host_order < start_host_order) + { + error = clib_error_return (0, "end address less than start address"); + goto done; + } + + count = (end_host_order - start_host_order) + 1; + this_addr = start_addr; + + for (i = 0; i < count; i++) + { + rv = nat64_add_del_pool_addr (&this_addr, vrf_id, is_add); + + switch (rv) + { + case VNET_API_ERROR_NO_SUCH_ENTRY: + error = + clib_error_return (0, "NAT64 pool address %U not exist.", + format_ip4_address, &this_addr); + goto done; + case VNET_API_ERROR_VALUE_EXIST: + error = + clib_error_return (0, "NAT64 pool address %U exist.", + format_ip4_address, &this_addr); + goto done; + default: + break; + + } + increment_v4_address (&this_addr); + } + +done: + unformat_free (line_input); + + return error; +} + +static int +nat64_cli_pool_walk (snat_address_t * ap, void *ctx) +{ + vlib_main_t *vm = ctx; + + if (ap->fib_index != ~0) + { + fib_table_t *fib; + fib = fib_table_get (ap->fib_index, FIB_PROTOCOL_IP6); + if (!fib) + return -1; + vlib_cli_output (vm, " %U tenant VRF: %u", format_ip4_address, + &ap->addr, fib->ft_table_id); + } + else + vlib_cli_output (vm, " %U", format_ip4_address, &ap->addr); + + return 0; +} + +static clib_error_t * +nat64_show_pool_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + nat64_main_t *nm = &nat64_main; + + if (nm->is_disabled) + return clib_error_return (0, + "NAT64 disabled, multi thread not supported"); + + vlib_cli_output (vm, "NAT64 pool:"); + nat64_pool_addr_walk (nat64_cli_pool_walk, vm); + + return 0; +} + +static clib_error_t * +nat64_interface_feature_command_fn (vlib_main_t * vm, + unformat_input_t * + input, vlib_cli_command_t * cmd) +{ + nat64_main_t *nm = &nat64_main; + unformat_input_t _line_input, *line_input = &_line_input; + vnet_main_t *vnm = vnet_get_main (); + clib_error_t *error = 0; + u32 sw_if_index; + u32 *inside_sw_if_indices = 0; + u32 *outside_sw_if_indices = 0; + u8 is_add = 1; + int i, rv; + + if (nm->is_disabled) + return clib_error_return (0, + "NAT64 disabled, multi thread not supported"); + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "in %U", unformat_vnet_sw_interface, + vnm, &sw_if_index)) + vec_add1 (inside_sw_if_indices, sw_if_index); + else if (unformat (line_input, "out %U", unformat_vnet_sw_interface, + vnm, &sw_if_index)) + vec_add1 (outside_sw_if_indices, sw_if_index); + else if (unformat (line_input, "del")) + is_add = 0; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + if (vec_len (inside_sw_if_indices)) + { + for (i = 0; i < vec_len (inside_sw_if_indices); i++) + { + sw_if_index = inside_sw_if_indices[i]; + rv = nat64_add_del_interface (sw_if_index, 1, is_add); + switch (rv) + { + case VNET_API_ERROR_NO_SUCH_ENTRY: + error = + clib_error_return (0, "%U NAT64 feature not enabled.", + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, sw_if_index)); + goto done; + case VNET_API_ERROR_VALUE_EXIST: + error = + clib_error_return (0, "%U NAT64 feature already enabled.", + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, sw_if_index)); + goto done; + case VNET_API_ERROR_INVALID_VALUE: + case VNET_API_ERROR_INVALID_VALUE_2: + error = + clib_error_return (0, + "%U NAT64 feature enable/disable failed.", + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, sw_if_index)); + goto done; + default: + break; + + } + } + } + + if (vec_len (outside_sw_if_indices)) + { + for (i = 0; i < vec_len (outside_sw_if_indices); i++) + { + sw_if_index = outside_sw_if_indices[i]; + rv = nat64_add_del_interface (sw_if_index, 0, is_add); + switch (rv) + { + case VNET_API_ERROR_NO_SUCH_ENTRY: + error = + clib_error_return (0, "%U NAT64 feature not enabled.", + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, sw_if_index)); + goto done; + case VNET_API_ERROR_VALUE_EXIST: + error = + clib_error_return (0, "%U NAT64 feature already enabled.", + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, sw_if_index)); + goto done; + case VNET_API_ERROR_INVALID_VALUE: + case VNET_API_ERROR_INVALID_VALUE_2: + error = + clib_error_return (0, + "%U NAT64 feature enable/disable failed.", + format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, sw_if_index)); + goto done; + default: + break; + + } + } + } + +done: + unformat_free (line_input); + vec_free (inside_sw_if_indices); + vec_free (outside_sw_if_indices); + + return error; +} + +static int +nat64_cli_interface_walk (snat_interface_t * i, void *ctx) +{ + vlib_main_t *vm = ctx; + vnet_main_t *vnm = vnet_get_main (); + + vlib_cli_output (vm, " %U %s", format_vnet_sw_interface_name, vnm, + vnet_get_sw_interface (vnm, i->sw_if_index), + i->is_inside ? "in" : "out"); + return 0; +} + +static clib_error_t * +nat64_show_interfaces_command_fn (vlib_main_t * vm, + unformat_input_t * + input, vlib_cli_command_t * cmd) +{ + nat64_main_t *nm = &nat64_main; + + if (nm->is_disabled) + return clib_error_return (0, + "NAT64 disabled, multi thread not supported"); + + vlib_cli_output (vm, "NAT64 interfaces:"); + nat64_interfaces_walk (nat64_cli_interface_walk, vm); + + return 0; +} + +static clib_error_t * +nat64_add_del_static_bib_command_fn (vlib_main_t * + vm, + unformat_input_t + * input, vlib_cli_command_t * cmd) +{ + nat64_main_t *nm = &nat64_main; + unformat_input_t _line_input, *line_input = &_line_input; + clib_error_t *error = 0; + u8 is_add = 1; + ip6_address_t in_addr; + ip4_address_t out_addr; + u16 in_port = 0; + u16 out_port = 0; + u32 vrf_id = 0, protocol; + snat_protocol_t proto = 0; + u8 p = 0; + int rv; + + if (nm->is_disabled) + return clib_error_return (0, + "NAT64 disabled, multi thread not supported"); + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "%U %u", unformat_ip6_address, + &in_addr, &in_port)) + ; + else if (unformat (line_input, "%U %u", unformat_ip4_address, + &out_addr, &out_port)) + ; + else if (unformat (line_input, "vrf %u", &vrf_id)) + ; + else if (unformat (line_input, "%U", unformat_snat_protocol, &proto)) + ; + else + if (unformat + (line_input, "%U %U %u", unformat_ip6_address, &in_addr, + unformat_ip4_address, &out_addr, &protocol)) + p = (u8) protocol; + else if (unformat (line_input, "del")) + is_add = 0; + else + { + error = clib_error_return (0, "unknown input: '%U'", + format_unformat_error, line_input); + goto done; + } + } + + if (!p) + { + if (!in_port) + { + error = + clib_error_return (0, "inside port and address must be set"); + goto done; + } + + if (!out_port) + { + error = + clib_error_return (0, "outside port and address must be set"); + goto done; + } + + p = snat_proto_to_ip_proto (proto); + } + + rv = + nat64_add_del_static_bib_entry (&in_addr, &out_addr, in_port, out_port, p, + vrf_id, is_add); + + switch (rv) + { + case VNET_API_ERROR_NO_SUCH_ENTRY: + error = clib_error_return (0, "NAT64 BIB entry not exist."); + goto done; + case VNET_API_ERROR_VALUE_EXIST: + error = clib_error_return (0, "NAT64 BIB entry exist."); + goto done; + case VNET_API_ERROR_UNSPECIFIED: + error = clib_error_return (0, "Crerate NAT64 BIB entry failed."); + goto done; + case VNET_API_ERROR_INVALID_VALUE: + error = + clib_error_return (0, "Outside addres %U and port %u already in use.", + format_ip4_address, &out_addr, out_port); + goto done; + default: + break; + } + +done: + unformat_free (line_input); + + return error; +} + +static int +nat64_cli_bib_walk (nat64_db_bib_entry_t * bibe, void *ctx) +{ + vlib_main_t *vm = ctx; + fib_table_t *fib; + + fib = fib_table_get (bibe->fib_index, FIB_PROTOCOL_IP6); + if (!fib) + return -1; + + switch (bibe->proto) + { + case IP_PROTOCOL_ICMP: + case IP_PROTOCOL_TCP: + case IP_PROTOCOL_UDP: + vlib_cli_output (vm, " %U %u %U %u protocol %U vrf %u %s %u sessions", + format_ip6_address, &bibe->in_addr, + clib_net_to_host_u16 (bibe->in_port), + format_ip4_address, &bibe->out_addr, + clib_net_to_host_u16 (bibe->out_port), + format_snat_protocol, + ip_proto_to_snat_proto (bibe->proto), fib->ft_table_id, + bibe->is_static ? "static" : "dynamic", bibe->ses_num); + break; + default: + vlib_cli_output (vm, " %U %U protocol %u vrf %u %s %u sessions", + format_ip6_address, &bibe->in_addr, + format_ip4_address, &bibe->out_addr, + bibe->proto, fib->ft_table_id, + bibe->is_static ? "static" : "dynamic", bibe->ses_num); + } + return 0; +} + +static clib_error_t * +nat64_show_bib_command_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + nat64_main_t *nm = &nat64_main; + unformat_input_t _line_input, *line_input = &_line_input; + clib_error_t *error = 0; + u32 proto = ~0; + u8 p = 255; + + if (nm->is_disabled) + return clib_error_return (0, + "NAT64 disabled, multi thread not supported"); + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + if (unformat (line_input, "%U", unformat_snat_protocol, &proto)) + p = snat_proto_to_ip_proto (proto); + else if (unformat (line_input, "unknown")) + p = 0; + else if (unformat (line_input, "all")) + ; + else + { + error = clib_error_return (0, "unknown input: '%U'", + format_unformat_error, line_input); + goto done; + } + + if (p == 255) + vlib_cli_output (vm, "NAT64 BIB entries:"); + else + vlib_cli_output (vm, "NAT64 %U BIB entries:", format_snat_protocol, + proto); + nat64_db_bib_walk (&nm->db, p, nat64_cli_bib_walk, vm); + +done: + unformat_free (line_input); + + return error; +} + +static clib_error_t * +nat64_set_timeouts_command_fn (vlib_main_t * vm, unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + nat64_main_t *nm = &nat64_main; + unformat_input_t _line_input, *line_input = &_line_input; + clib_error_t *error = 0; + u32 timeout, tcp_trans, tcp_est, tcp_incoming_syn; + + tcp_trans = nat64_get_tcp_trans_timeout (); + tcp_est = nat64_get_tcp_est_timeout (); + tcp_incoming_syn = nat64_get_tcp_incoming_syn_timeout (); + + if (nm->is_disabled) + return clib_error_return (0, + "NAT64 disabled, multi thread not supported"); + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "udp %u", &timeout)) + { + if (nat64_set_udp_timeout (timeout)) + { + error = clib_error_return (0, "Invalid UDP timeout value"); + goto done; + } + } + else if (unformat (line_input, "icmp %u", &timeout)) + { + if (nat64_set_icmp_timeout (timeout)) + { + error = clib_error_return (0, "Invalid ICMP timeout value"); + goto done; + } + } + else if (unformat (line_input, "tcp-trans %u", &tcp_trans)) + { + if (nat64_set_tcp_timeouts (tcp_trans, tcp_est, tcp_incoming_syn)) + { + error = + clib_error_return (0, "Invalid TCP transitory tiemout value"); + goto done; + } + } + else if (unformat (line_input, "tcp-est %u", &tcp_est)) + { + if (nat64_set_tcp_timeouts (tcp_trans, tcp_est, tcp_incoming_syn)) + { + error = + clib_error_return (0, + "Invalid TCP established tiemout value"); + goto done; + } + } + else + if (unformat (line_input, "tcp-incoming-syn %u", &tcp_incoming_syn)) + { + if (nat64_set_tcp_timeouts (tcp_trans, tcp_est, tcp_incoming_syn)) + { + error = + clib_error_return (0, + "Invalid TCP incoming SYN tiemout value"); + goto done; + } + } + else if (unformat (line_input, "reset")) + { + nat64_set_udp_timeout (0); + nat64_set_icmp_timeout (0); + nat64_set_tcp_timeouts (0, 0, 0); + } + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + +done: + unformat_free (line_input); + + return error; +} + +static clib_error_t * +nat64_show_timeouts_command_fn (vlib_main_t * vm, unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + nat64_main_t *nm = &nat64_main; + + if (nm->is_disabled) + return clib_error_return (0, + "NAT64 disabled, multi thread not supported"); + + vlib_cli_output (vm, "NAT64 session timeouts:"); + vlib_cli_output (vm, " UDP %usec", nat64_get_udp_timeout ()); + vlib_cli_output (vm, " ICMP %usec", nat64_get_icmp_timeout ()); + vlib_cli_output (vm, " TCP transitory %usec", + nat64_get_tcp_trans_timeout ()); + vlib_cli_output (vm, " TCP established %usec", + nat64_get_tcp_est_timeout ()); + vlib_cli_output (vm, " TCP incoming SYN %usec", + nat64_get_tcp_incoming_syn_timeout ()); + + return 0; +} + +static int +nat64_cli_st_walk (nat64_db_st_entry_t * ste, void *ctx) +{ + vlib_main_t *vm = ctx; + nat64_main_t *nm = &nat64_main; + nat64_db_bib_entry_t *bibe; + fib_table_t *fib; + + bibe = nat64_db_bib_entry_by_index (&nm->db, ste->proto, ste->bibe_index); + if (!bibe) + return -1; + + fib = fib_table_get (bibe->fib_index, FIB_PROTOCOL_IP6); + if (!fib) + return -1; + + u32 vrf_id = fib->ft_table_id; + + if (ste->proto == IP_PROTOCOL_ICMP) + vlib_cli_output (vm, " %U %U %u %U %U %u protocol %U vrf %u", + format_ip6_address, &bibe->in_addr, + format_ip6_address, &ste->in_r_addr, + clib_net_to_host_u16 (bibe->in_port), + format_ip4_address, &bibe->out_addr, + format_ip4_address, &ste->out_r_addr, + clib_net_to_host_u16 (bibe->out_port), + format_snat_protocol, + ip_proto_to_snat_proto (bibe->proto), vrf_id); + else if (ste->proto == IP_PROTOCOL_TCP || ste->proto == IP_PROTOCOL_UDP) + vlib_cli_output (vm, " %U %u %U %u %U %u %U %u protcol %U vrf %u", + format_ip6_address, &bibe->in_addr, + clib_net_to_host_u16 (bibe->in_port), + format_ip6_address, &ste->in_r_addr, + clib_net_to_host_u16 (ste->r_port), + format_ip4_address, &bibe->out_addr, + clib_net_to_host_u16 (bibe->out_port), + format_ip4_address, &ste->out_r_addr, + clib_net_to_host_u16 (ste->r_port), + format_snat_protocol, + ip_proto_to_snat_proto (bibe->proto), vrf_id); + else + vlib_cli_output (vm, " %U %U %U %U protocol %u vrf %u", + format_ip6_address, &bibe->in_addr, + format_ip6_address, &ste->in_r_addr, + format_ip4_address, &bibe->out_addr, + format_ip4_address, &ste->out_r_addr, + bibe->proto, vrf_id); + + return 0; +} + +static clib_error_t * +nat64_show_st_command_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + nat64_main_t *nm = &nat64_main; + unformat_input_t _line_input, *line_input = &_line_input; + clib_error_t *error = 0; + u32 proto = ~0; + u8 p = 255; + + if (nm->is_disabled) + return clib_error_return (0, + "NAT64 disabled, multi thread not supported"); + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + if (unformat (line_input, "%U", unformat_snat_protocol, &proto)) + p = snat_proto_to_ip_proto (proto); + else if (unformat (line_input, "unknown")) + p = 0; + else if (unformat (line_input, "all")) + ; + else + { + error = clib_error_return (0, "unknown input: '%U'", + format_unformat_error, line_input); + goto done; + } + + if (p == 255) + vlib_cli_output (vm, "NAT64 sessions:"); + else + vlib_cli_output (vm, "NAT64 %U sessions:", format_snat_protocol, proto); + nat64_db_st_walk (&nm->db, p, nat64_cli_st_walk, vm); + +done: + unformat_free (line_input); + + return error; +} + +static clib_error_t * +nat64_add_del_prefix_command_fn (vlib_main_t * vm, unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + nat64_main_t *nm = &nat64_main; + clib_error_t *error = 0; + unformat_input_t _line_input, *line_input = &_line_input; + u8 is_add = 1; + u32 vrf_id = 0; + ip6_address_t prefix; + u32 plen = 0; + int rv; + + if (nm->is_disabled) + return clib_error_return (0, + "NAT64 disabled, multi thread not supported"); + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat + (line_input, "%U/%u", unformat_ip6_address, &prefix, &plen)) + ; + else if (unformat (line_input, "tenant-vrf %u", &vrf_id)) + ; + else if (unformat (line_input, "del")) + is_add = 0; + else + { + error = clib_error_return (0, "unknown input: '%U'", + format_unformat_error, line_input); + goto done; + } + } + + if (!plen) + { + error = clib_error_return (0, "NAT64 prefix must be set."); + goto done; + } + + rv = nat64_add_del_prefix (&prefix, (u8) plen, vrf_id, is_add); + + switch (rv) + { + case VNET_API_ERROR_NO_SUCH_ENTRY: + error = clib_error_return (0, "NAT64 prefix not exist."); + goto done; + case VNET_API_ERROR_INVALID_VALUE: + error = clib_error_return (0, "Invalid prefix length."); + goto done; + default: + break; + } + +done: + unformat_free (line_input); + + return error; +} + +static int +nat64_cli_prefix_walk (nat64_prefix_t * p, void *ctx) +{ + vlib_main_t *vm = ctx; + + vlib_cli_output (vm, " %U/%u tenant-vrf %u", + format_ip6_address, &p->prefix, p->plen, p->vrf_id); + + return 0; +} + +static clib_error_t * +nat64_show_prefix_command_fn (vlib_main_t * vm, unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + nat64_main_t *nm = &nat64_main; + + if (nm->is_disabled) + return clib_error_return (0, + "NAT64 disabled, multi thread not supported"); + + vlib_cli_output (vm, "NAT64 prefix:"); + nat64_prefix_walk (nat64_cli_prefix_walk, vm); + + return 0; +} + +/* *INDENT-OFF* */ + +/*? + * @cliexpar + * @cliexstart{nat64 add pool address} + * Add/delete NAT64 pool address. + * To add single NAT64 pool address use: + * vpp# nat64 add pool address 10.1.1.10 + * To add NAT64 pool address range use: + * vpp# nat64 add pool address 10.1.1.2 - 10.1.1.5 + * To add NAT64 pool address for specific tenant use: + * vpp# nat64 add pool address 10.1.1.100 tenant-vrf 100 + * @cliexend +?*/ +VLIB_CLI_COMMAND (nat64_add_pool_address_command, static) = { + .path = "nat64 add pool address", + .short_help = "nat64 add pool address <ip4-range-start> [- <ip4-range-end>] " + "[tenant-vrf <vrf-id>] [del]", + .function = nat64_add_del_pool_addr_command_fn, +}; + +/*? + * @cliexpar + * @cliexstart{show nat64 pool} + * Show NAT64 pool. + * vpp# show nat64 pool + * NAT64 pool: + * 10.1.1.3 tenant VRF: 0 + * 10.1.1.10 tenant VRF: 10 + * @cliexend +?*/ +VLIB_CLI_COMMAND (show_nat64_pool_command, static) = { + .path = "show nat64 pool", + .short_help = "show nat64 pool", + .function = nat64_show_pool_command_fn, +}; + +/*? + * @cliexpar + * @cliexstart{set interface nat64} + * Enable/disable NAT64 feature on the interface. + * To enable NAT64 feature with local (IPv6) network interface + * GigabitEthernet0/8/0 and external (IPv4) network interface + * GigabitEthernet0/a/0 use: + * vpp# set interface nat64 in GigabitEthernet0/8/0 out GigabitEthernet0/a/0 + * @cliexend +?*/ +VLIB_CLI_COMMAND (set_interface_nat64_command, static) = { + .path = "set interface nat64", + .short_help = "set interface nat64 in|out <intfc> [del]", + .function = nat64_interface_feature_command_fn, +}; + +/*? + * @cliexpar + * @cliexstart{show nat64 interfaces} + * Show interfaces with NAT64 feature. + * To show interfaces with NAT64 feature use: + * vpp# show nat64 interfaces + * NAT64 interfaces: + * GigabitEthernet0/8/0 in + * GigabitEthernet0/a/0 out + * @cliexend +?*/ +VLIB_CLI_COMMAND (show_nat64_interfaces_command, static) = { + .path = "show nat64 interfaces", + .short_help = "show nat64 interfaces", + .function = nat64_show_interfaces_command_fn, +}; + +/*? + * @cliexpar + * @cliexstart{nat64 add static bib} + * Add/delete NAT64 static BIB entry. + * To create NAT64 satatic BIB entry use: + * vpp# nat64 add static bib 2001:db8:c000:221:: 1234 10.1.1.3 5678 tcp + * vpp# nat64 add static bib 2001:db8:c000:221:: 1234 10.1.1.3 5678 udp vrf 10 + * @cliexend +?*/ +VLIB_CLI_COMMAND (nat64_add_del_static_bib_command, static) = { + .path = "nat64 add static bib", + .short_help = "nat64 add static bib <ip6-addr> <port> <ip4-addr> <port> " + "tcp|udp|icmp [vfr <table-id>] [del]", + .function = nat64_add_del_static_bib_command_fn, +}; + +/*? + * @cliexpar + * @cliexstart{show nat64 bib} + * Show NAT64 BIB entries. + * To show NAT64 TCP BIB entries use: + * vpp# show nat64 bib tcp + * NAT64 tcp BIB: + * fd01:1::2 6303 10.0.0.3 62303 tcp vrf 0 dynamic 1 sessions + * 2001:db8:c000:221:: 1234 10.1.1.3 5678 tcp vrf 0 static 2 sessions + * To show NAT64 UDP BIB entries use: + * vpp# show nat64 bib udp + * NAT64 udp BIB: + * fd01:1::2 6304 10.0.0.3 10546 udp vrf 0 dynamic 10 sessions + * 2001:db8:c000:221:: 1234 10.1.1.3 5678 udp vrf 10 static 0 sessions + * To show NAT64 ICMP BIB entries use: + * vpp# show nat64 bib icmp + * NAT64 icmp BIB: + * fd01:1::2 6305 10.0.0.3 63209 icmp vrf 10 dynamic 1 sessions + * @cliexend +?*/ +VLIB_CLI_COMMAND (show_nat64_bib_command, static) = { + .path = "show nat64 bib", + .short_help = "show nat64 bib all|tcp|udp|icmp|unknown", + .function = nat64_show_bib_command_fn, +}; + +/*? + * @cliexpar + * @cliexstart{set nat64 timeouts} + * Set NAT64 session timeouts (in seconds). + * To set NAT64 session timeoutes use use: + * vpp# set nat64 timeouts udp 200 icmp 30 tcp-trans 250 tcp-est 7450 + * To reset NAT64 session timeoutes to default values use: + * vpp# set nat64 timeouts reset + * @cliexend +?*/ +VLIB_CLI_COMMAND (set_nat64_timeouts_command, static) = { + .path = "set nat64 timeouts", + .short_help = "set nat64 timeouts udp <sec> icmp <sec> tcp-trans <sec> " + "tcp-est <sec> tcp-incoming-syn <sec> | reset", + .function = nat64_set_timeouts_command_fn, +}; + +/*? + * @cliexpar + * @cliexstart{show nat64 tiemouts} + * Show NAT64 session timeouts: + * vpp# show nat64 tiemouts + * NAT64 session timeouts: + * UDP 300sec + * ICMP 60sec + * TCP transitory 240sec + * TCP established 7440sec + * TCP incoming SYN 6sec + * @cliexend +?*/ +VLIB_CLI_COMMAND (show_nat64_timeouts_command, static) = { + .path = "show nat64 tiemouts", + .short_help = "show nat64 tiemouts", + .function = nat64_show_timeouts_command_fn, +}; + +/*? + * @cliexpar + * @cliexstart{show nat64 session table} + * Show NAT64 session table. + * To show NAT64 TCP session table use: + * vpp# show nat64 session table tcp + * NAT64 tcp session table: + * fd01:1::2 6303 64:ff9b::ac10:202 20 10.0.0.3 62303 172.16.2.2 20 tcp vrf 0 + * fd01:3::2 6303 64:ff9b::ac10:202 20 10.0.10.3 21300 172.16.2.2 20 tcp vrf 10 + * To show NAT64 UDP session table use: + * #vpp show nat64 session table udp + * NAT64 udp session table: + * fd01:1::2 6304 64:ff9b::ac10:202 20 10.0.0.3 10546 172.16.2.2 20 udp vrf 0 + * fd01:3::2 6304 64:ff9b::ac10:202 20 10.0.10.3 58627 172.16.2.2 20 udp vrf 10 + * fd01:1::2 1235 64:ff9b::a00:3 4023 10.0.0.3 24488 10.0.0.3 4023 udp vrf 0 + * fd01:1::3 23 64:ff9b::a00:3 24488 10.0.0.3 4023 10.0.0.3 24488 udp vrf 0 + * To show NAT64 ICMP session table use: + * #vpp show nat64 session table icmp + * NAT64 icmp session table: + * fd01:1::2 64:ff9b::ac10:202 6305 10.0.0.3 172.16.2.2 63209 icmp vrf 0 + * @cliexend +?*/ +VLIB_CLI_COMMAND (show_nat64_st_command, static) = { + .path = "show nat64 session table", + .short_help = "show nat64 session table all|tcp|udp|icmp|unknown", + .function = nat64_show_st_command_fn, +}; + +/*? + * @cliexpar + * @cliexstart{nat64 add prefix} + * Set NAT64 prefix for generating IPv6 representations of IPv4 addresses. + * To set NAT64 global prefix use: + * vpp# nat64 add prefix 2001:db8::/32 + * To set NAT64 prefix for specific tenant use: + * vpp# nat64 add prefix 2001:db8:122:300::/56 tenant-vrf 10 + * @cliexend +?*/ +VLIB_CLI_COMMAND (nat64_add_del_prefix_command, static) = { + .path = "nat64 add prefix", + .short_help = "nat64 add prefix <ip6-prefix>/<plen> [tenant-vrf <vrf-id>] " + "[del]", + .function = nat64_add_del_prefix_command_fn, +}; + +/*? + * @cliexpar + * @cliexstart{show nat64 prefix} + * Show NAT64 prefix. + * To show NAT64 prefix use: + * vpp# show nat64 prefix + * NAT64 prefix: + * 2001:db8::/32 tenant-vrf 0 + * 2001:db8:122:300::/56 tenant-vrf 10 + * @cliexend +?*/ +VLIB_CLI_COMMAND (show_nat64_prefix_command, static) = { + .path = "show nat64 prefix", + .short_help = "show nat64 prefix", + .function = nat64_show_prefix_command_fn, +}; + +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/nat/nat64_db.c b/src/plugins/nat/nat64_db.c new file mode 100644 index 00000000000..da73ceee2d1 --- /dev/null +++ b/src/plugins/nat/nat64_db.c @@ -0,0 +1,603 @@ +/* + * Copyright (c) 2017 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. + */ +/** + * @file + * @brief NAT64 DB + */ +#include <nat/nat64_db.h> + +int +nat64_db_init (nat64_db_t * db) +{ + u32 bib_buckets = 1024; + u32 bib_memory_size = 128 << 20; + u32 st_buckets = 2048; + u32 st_memory_size = 256 << 20; + + clib_bihash_init_24_8 (&db->bib.in2out, "bib-in2out", bib_buckets, + bib_memory_size); + + clib_bihash_init_24_8 (&db->bib.out2in, "bib-out2in", bib_buckets, + bib_memory_size); + + clib_bihash_init_48_8 (&db->st.in2out, "st-in2out", st_buckets, + st_memory_size); + + clib_bihash_init_48_8 (&db->st.out2in, "st-out2in", st_buckets, + st_memory_size); + + return 0; +} + +nat64_db_bib_entry_t * +nat64_db_bib_entry_create (nat64_db_t * db, ip6_address_t * in_addr, + ip4_address_t * out_addr, u16 in_port, + u16 out_port, u32 fib_index, u8 proto, + u8 is_static) +{ + nat64_db_bib_entry_t *bibe; + nat64_db_bib_entry_key_t bibe_key; + clib_bihash_kv_24_8_t kv; + + /* create pool entry */ + switch (ip_proto_to_snat_proto (proto)) + { +/* *INDENT-OFF* */ +#define _(N, i, n, s) \ + case SNAT_PROTOCOL_##N: \ + pool_get (db->bib._##n##_bib, bibe); \ + kv.value = bibe - db->bib._##n##_bib; \ + break; + foreach_snat_protocol +#undef _ +/* *INDENT-ON* */ + default: + pool_get (db->bib._unk_proto_bib, bibe); + kv.value = bibe - db->bib._unk_proto_bib; + break; + } + memset (bibe, 0, sizeof (*bibe)); + bibe->in_addr.as_u64[0] = in_addr->as_u64[0]; + bibe->in_addr.as_u64[1] = in_addr->as_u64[1]; + bibe->in_port = in_port; + bibe->out_addr.as_u32 = out_addr->as_u32; + bibe->out_port = out_port; + bibe->fib_index = fib_index; + bibe->proto = proto; + bibe->is_static = is_static; + + /* create hash lookup */ + bibe_key.addr.as_u64[0] = bibe->in_addr.as_u64[0]; + bibe_key.addr.as_u64[1] = bibe->in_addr.as_u64[1]; + bibe_key.fib_index = bibe->fib_index; + bibe_key.port = bibe->in_port; + bibe_key.proto = bibe->proto; + bibe_key.rsvd = 0; + kv.key[0] = bibe_key.as_u64[0]; + kv.key[1] = bibe_key.as_u64[1]; + kv.key[2] = bibe_key.as_u64[2]; + clib_bihash_add_del_24_8 (&db->bib.in2out, &kv, 1); + + memset (&bibe_key.addr, 0, sizeof (bibe_key.addr)); + bibe_key.addr.ip4.as_u32 = bibe->out_addr.as_u32; + bibe_key.fib_index = 0; + bibe_key.port = bibe->out_port; + kv.key[0] = bibe_key.as_u64[0]; + kv.key[1] = bibe_key.as_u64[1]; + kv.key[2] = bibe_key.as_u64[2]; + clib_bihash_add_del_24_8 (&db->bib.out2in, &kv, 1); + + return bibe; +} + +void +nat64_db_bib_entry_free (nat64_db_t * db, nat64_db_bib_entry_t * bibe) +{ + nat64_db_bib_entry_key_t bibe_key; + clib_bihash_kv_24_8_t kv; + nat64_db_bib_entry_t *bib; + u32 *ste_to_be_free = 0, *ste_index, bibe_index; + nat64_db_st_entry_t *st, *ste; + + switch (ip_proto_to_snat_proto (bibe->proto)) + { +/* *INDENT-OFF* */ +#define _(N, i, n, s) \ + case SNAT_PROTOCOL_##N: \ + bib = db->bib._##n##_bib; \ + st = db->st._##n##_st; \ + break; + foreach_snat_protocol +#undef _ +/* *INDENT-ON* */ + default: + bib = db->bib._unk_proto_bib; + st = db->st._unk_proto_st; + break; + } + + bibe_index = bibe - bib; + + /* delete ST entries for static BIB entry */ + if (bibe->is_static) + { + pool_foreach (ste, st, ( + { + if (ste->bibe_index == bibe_index) + vec_add1 (ste_to_be_free, ste - st);} + )); + vec_foreach (ste_index, ste_to_be_free) + nat64_db_st_entry_free (db, pool_elt_at_index (st, ste_index[0])); + vec_free (ste_to_be_free); + } + + /* delete hash lookup */ + bibe_key.addr.as_u64[0] = bibe->in_addr.as_u64[0]; + bibe_key.addr.as_u64[1] = bibe->in_addr.as_u64[1]; + bibe_key.fib_index = bibe->fib_index; + bibe_key.port = bibe->in_port; + bibe_key.proto = bibe->proto; + bibe_key.rsvd = 0; + kv.key[0] = bibe_key.as_u64[0]; + kv.key[1] = bibe_key.as_u64[1]; + kv.key[2] = bibe_key.as_u64[2]; + clib_bihash_add_del_24_8 (&db->bib.in2out, &kv, 0); + + memset (&bibe_key.addr, 0, sizeof (bibe_key.addr)); + bibe_key.addr.ip4.as_u32 = bibe->out_addr.as_u32; + bibe_key.fib_index = 0; + bibe_key.port = bibe->out_port; + kv.key[0] = bibe_key.as_u64[0]; + kv.key[1] = bibe_key.as_u64[1]; + kv.key[2] = bibe_key.as_u64[2]; + clib_bihash_add_del_24_8 (&db->bib.out2in, &kv, 0); + + /* delete from pool */ + pool_put (bib, bibe); + +} + +nat64_db_bib_entry_t * +nat64_db_bib_entry_find (nat64_db_t * db, ip46_address_t * addr, u16 port, + u8 proto, u32 fib_index, u8 is_ip6) +{ + nat64_db_bib_entry_t *bibe = 0; + nat64_db_bib_entry_key_t bibe_key; + clib_bihash_kv_24_8_t kv, value; + nat64_db_bib_entry_t *bib; + + switch (ip_proto_to_snat_proto (proto)) + { +/* *INDENT-OFF* */ +#define _(N, i, n, s) \ + case SNAT_PROTOCOL_##N: \ + bib = db->bib._##n##_bib; \ + break; + foreach_snat_protocol +#undef _ +/* *INDENT-ON* */ + default: + bib = db->bib._unk_proto_bib; + break; + } + + bibe_key.addr.as_u64[0] = addr->as_u64[0]; + bibe_key.addr.as_u64[1] = addr->as_u64[1]; + bibe_key.fib_index = fib_index; + bibe_key.port = port; + bibe_key.proto = proto; + bibe_key.rsvd = 0; + + kv.key[0] = bibe_key.as_u64[0]; + kv.key[1] = bibe_key.as_u64[1]; + kv.key[2] = bibe_key.as_u64[2]; + + if (!clib_bihash_search_24_8 + (is_ip6 ? &db->bib.in2out : &db->bib.out2in, &kv, &value)) + bibe = pool_elt_at_index (bib, value.value); + + return bibe; +} + +void +nat64_db_bib_walk (nat64_db_t * db, u8 proto, + nat64_db_bib_walk_fn_t fn, void *ctx) +{ + nat64_db_bib_entry_t *bib, *bibe; + + if (proto == 255) + { + /* *INDENT-OFF* */ + #define _(N, i, n, s) \ + bib = db->bib._##n##_bib; \ + pool_foreach (bibe, bib, ({ \ + if (fn (bibe, ctx)) \ + return; \ + })); + foreach_snat_protocol + #undef _ + bib = db->bib._unk_proto_bib; + pool_foreach (bibe, bib, ({ + if (fn (bibe, ctx)) + return; + })); + /* *INDENT-ON* */ + } + else + { + switch (ip_proto_to_snat_proto (proto)) + { + /* *INDENT-OFF* */ + #define _(N, i, n, s) \ + case SNAT_PROTOCOL_##N: \ + bib = db->bib._##n##_bib; \ + break; + foreach_snat_protocol + #undef _ + /* *INDENT-ON* */ + default: + bib = db->bib._unk_proto_bib; + break; + } + + /* *INDENT-OFF* */ + pool_foreach (bibe, bib, + ({ + if (fn (bibe, ctx)) + return; + })); + /* *INDENT-ON* */ + } +} + +nat64_db_bib_entry_t * +nat64_db_bib_entry_by_index (nat64_db_t * db, u8 proto, u32 bibe_index) +{ + nat64_db_bib_entry_t *bib; + + switch (ip_proto_to_snat_proto (proto)) + { +/* *INDENT-OFF* */ +#define _(N, i, n, s) \ + case SNAT_PROTOCOL_##N: \ + bib = db->bib._##n##_bib; \ + break; + foreach_snat_protocol +#undef _ +/* *INDENT-ON* */ + default: + bib = db->bib._unk_proto_bib; + break; + } + + return pool_elt_at_index (bib, bibe_index); +} + +void +nat64_db_st_walk (nat64_db_t * db, u8 proto, + nat64_db_st_walk_fn_t fn, void *ctx) +{ + nat64_db_st_entry_t *st, *ste; + + if (proto == 255) + { + /* *INDENT-OFF* */ + #define _(N, i, n, s) \ + st = db->st._##n##_st; \ + pool_foreach (ste, st, ({ \ + if (fn (ste, ctx)) \ + return; \ + })); + foreach_snat_protocol + #undef _ + st = db->st._unk_proto_st; + pool_foreach (ste, st, ({ + if (fn (ste, ctx)) + return; + })); + /* *INDENT-ON* */ + } + else + { + switch (ip_proto_to_snat_proto (proto)) + { + /* *INDENT-OFF* */ + #define _(N, i, n, s) \ + case SNAT_PROTOCOL_##N: \ + st = db->st._##n##_st; \ + break; + foreach_snat_protocol + #undef _ + /* *INDENT-ON* */ + default: + st = db->st._unk_proto_st; + break; + } + + /* *INDENT-OFF* */ + pool_foreach (ste, st, + ({ + if (fn (ste, ctx)) + return; + })); + /* *INDENT-ON* */ + } +} + +nat64_db_st_entry_t * +nat64_db_st_entry_create (nat64_db_t * db, nat64_db_bib_entry_t * bibe, + ip6_address_t * in_r_addr, + ip4_address_t * out_r_addr, u16 r_port) +{ + nat64_db_st_entry_t *ste; + nat64_db_bib_entry_t *bib; + nat64_db_st_entry_key_t ste_key; + clib_bihash_kv_48_8_t kv; + + /* create pool entry */ + switch (ip_proto_to_snat_proto (bibe->proto)) + { +/* *INDENT-OFF* */ +#define _(N, i, n, s) \ + case SNAT_PROTOCOL_##N: \ + pool_get (db->st._##n##_st, ste); \ + kv.value = ste - db->st._##n##_st; \ + bib = db->bib._##n##_bib; \ + break; + foreach_snat_protocol +#undef _ +/* *INDENT-ON* */ + default: + pool_get (db->st._unk_proto_st, ste); + kv.value = ste - db->st._unk_proto_st; + bib = db->bib._unk_proto_bib; + break; + } + memset (ste, 0, sizeof (*ste)); + ste->in_r_addr.as_u64[0] = in_r_addr->as_u64[0]; + ste->in_r_addr.as_u64[1] = in_r_addr->as_u64[1]; + ste->out_r_addr.as_u32 = out_r_addr->as_u32; + ste->r_port = r_port; + ste->bibe_index = bibe - bib; + ste->proto = bibe->proto; + + /* increment session number for BIB entry */ + bibe->ses_num++; + + /* create hash lookup */ + memset (&ste_key, 0, sizeof (ste_key)); + ste_key.l_addr.as_u64[0] = bibe->in_addr.as_u64[0]; + ste_key.l_addr.as_u64[1] = bibe->in_addr.as_u64[1]; + ste_key.r_addr.as_u64[0] = ste->in_r_addr.as_u64[0]; + ste_key.r_addr.as_u64[1] = ste->in_r_addr.as_u64[1]; + ste_key.fib_index = bibe->fib_index; + ste_key.l_port = bibe->in_port; + ste_key.r_port = ste->r_port; + ste_key.proto = ste->proto; + kv.key[0] = ste_key.as_u64[0]; + kv.key[1] = ste_key.as_u64[1]; + kv.key[2] = ste_key.as_u64[2]; + kv.key[3] = ste_key.as_u64[3]; + kv.key[4] = ste_key.as_u64[4]; + kv.key[5] = ste_key.as_u64[5]; + clib_bihash_add_del_48_8 (&db->st.in2out, &kv, 1); + + memset (&ste_key, 0, sizeof (ste_key)); + ste_key.l_addr.ip4.as_u32 = bibe->out_addr.as_u32; + ste_key.r_addr.ip4.as_u32 = ste->out_r_addr.as_u32; + ste_key.l_port = bibe->out_port; + ste_key.r_port = ste->r_port; + ste_key.proto = ste->proto; + kv.key[0] = ste_key.as_u64[0]; + kv.key[1] = ste_key.as_u64[1]; + kv.key[2] = ste_key.as_u64[2]; + kv.key[3] = ste_key.as_u64[3]; + kv.key[4] = ste_key.as_u64[4]; + kv.key[5] = ste_key.as_u64[5]; + clib_bihash_add_del_48_8 (&db->st.out2in, &kv, 1); + + return ste; +} + +void +nat64_db_st_entry_free (nat64_db_t * db, nat64_db_st_entry_t * ste) +{ + nat64_db_st_entry_t *st; + nat64_db_bib_entry_t *bib, *bibe; + nat64_db_st_entry_key_t ste_key; + clib_bihash_kv_48_8_t kv; + + switch (ip_proto_to_snat_proto (ste->proto)) + { +/* *INDENT-OFF* */ +#define _(N, i, n, s) \ + case SNAT_PROTOCOL_##N: \ + st = db->st._##n##_st; \ + bib = db->bib._##n##_bib; \ + break; + foreach_snat_protocol +#undef _ +/* *INDENT-ON* */ + default: + st = db->st._unk_proto_st; + bib = db->bib._unk_proto_bib; + break; + } + + bibe = pool_elt_at_index (bib, ste->bibe_index); + + /* delete hash lookup */ + memset (&ste_key, 0, sizeof (ste_key)); + ste_key.l_addr.as_u64[0] = bibe->in_addr.as_u64[0]; + ste_key.l_addr.as_u64[1] = bibe->in_addr.as_u64[1]; + ste_key.r_addr.as_u64[0] = ste->in_r_addr.as_u64[0]; + ste_key.r_addr.as_u64[1] = ste->in_r_addr.as_u64[1]; + ste_key.fib_index = bibe->fib_index; + ste_key.l_port = bibe->in_port; + ste_key.r_port = ste->r_port; + ste_key.proto = ste->proto; + kv.key[0] = ste_key.as_u64[0]; + kv.key[1] = ste_key.as_u64[1]; + kv.key[2] = ste_key.as_u64[2]; + kv.key[3] = ste_key.as_u64[3]; + kv.key[4] = ste_key.as_u64[4]; + kv.key[5] = ste_key.as_u64[5]; + clib_bihash_add_del_48_8 (&db->st.in2out, &kv, 0); + + memset (&ste_key, 0, sizeof (ste_key)); + ste_key.l_addr.ip4.as_u32 = bibe->out_addr.as_u32; + ste_key.r_addr.ip4.as_u32 = ste->out_r_addr.as_u32; + ste_key.l_port = bibe->out_port; + ste_key.r_port = ste->r_port; + ste_key.proto = ste->proto; + kv.key[0] = ste_key.as_u64[0]; + kv.key[1] = ste_key.as_u64[1]; + kv.key[2] = ste_key.as_u64[2]; + kv.key[3] = ste_key.as_u64[3]; + kv.key[4] = ste_key.as_u64[4]; + kv.key[5] = ste_key.as_u64[5]; + clib_bihash_add_del_48_8 (&db->st.out2in, &kv, 0); + + /* delete from pool */ + pool_put (st, ste); + + /* decrement session number for BIB entry */ + bibe->ses_num--; + + /* delete BIB entry if last session and dynamic */ + if (!bibe->is_static && !bibe->ses_num) + nat64_db_bib_entry_free (db, bibe); +} + +nat64_db_st_entry_t * +nat64_db_st_entry_find (nat64_db_t * db, ip46_address_t * l_addr, + ip46_address_t * r_addr, u16 l_port, u16 r_port, + u8 proto, u32 fib_index, u8 is_ip6) +{ + nat64_db_st_entry_t *ste = 0; + nat64_db_st_entry_t *st; + nat64_db_st_entry_key_t ste_key; + clib_bihash_kv_48_8_t kv, value; + + switch (ip_proto_to_snat_proto (proto)) + { +/* *INDENT-OFF* */ +#define _(N, i, n, s) \ + case SNAT_PROTOCOL_##N: \ + st = db->st._##n##_st; \ + break; + foreach_snat_protocol +#undef _ +/* *INDENT-ON* */ + default: + st = db->st._unk_proto_st; + break; + } + + memset (&ste_key, 0, sizeof (ste_key)); + ste_key.l_addr.as_u64[0] = l_addr->as_u64[0]; + ste_key.l_addr.as_u64[1] = l_addr->as_u64[1]; + ste_key.r_addr.as_u64[0] = r_addr->as_u64[0]; + ste_key.r_addr.as_u64[1] = r_addr->as_u64[1]; + ste_key.fib_index = fib_index; + ste_key.l_port = l_port; + ste_key.r_port = r_port; + ste_key.proto = proto; + kv.key[0] = ste_key.as_u64[0]; + kv.key[1] = ste_key.as_u64[1]; + kv.key[2] = ste_key.as_u64[2]; + kv.key[3] = ste_key.as_u64[3]; + kv.key[4] = ste_key.as_u64[4]; + kv.key[5] = ste_key.as_u64[5]; + + if (!clib_bihash_search_48_8 + (is_ip6 ? &db->st.in2out : &db->st.out2in, &kv, &value)) + ste = pool_elt_at_index (st, value.value); + + return ste; +} + +void +nad64_db_st_free_expired (nat64_db_t * db, u32 now) +{ + u32 *ste_to_be_free = 0, *ste_index; + nat64_db_st_entry_t *st, *ste; + +/* *INDENT-OFF* */ +#define _(N, i, n, s) \ + st = db->st._##n##_st; \ + pool_foreach (ste, st, ({\ + if (i == SNAT_PROTOCOL_TCP && !ste->tcp_state) \ + continue; \ + if (ste->expire < now) \ + vec_add1 (ste_to_be_free, ste - st); \ + })); \ + vec_foreach (ste_index, ste_to_be_free) \ + nat64_db_st_entry_free (db, pool_elt_at_index(st, ste_index[0])); \ + vec_free (ste_to_be_free); \ + ste_to_be_free = 0; + foreach_snat_protocol +#undef _ + st = db->st._unk_proto_st; + pool_foreach (ste, st, ({ + if (ste->expire < now) + vec_add1 (ste_to_be_free, ste - st); + })); + vec_foreach (ste_index, ste_to_be_free) + nat64_db_st_entry_free (db, pool_elt_at_index(st, ste_index[0])); + vec_free (ste_to_be_free); +/* *INDENT-ON* */ +} + +void +nat64_db_free_out_addr (nat64_db_t * db, ip4_address_t * out_addr) +{ + u32 *ste_to_be_free = 0, *ste_index; + nat64_db_st_entry_t *st, *ste; + nat64_db_bib_entry_t *bibe; + +/* *INDENT-OFF* */ +#define _(N, i, n, s) \ + st = db->st._##n##_st; \ + pool_foreach (ste, st, ({ \ + bibe = pool_elt_at_index (db->bib._##n##_bib, ste->bibe_index); \ + if (bibe->out_addr.as_u32 == out_addr->as_u32) \ + vec_add1 (ste_to_be_free, ste - st); \ + })); \ + vec_foreach (ste_index, ste_to_be_free) \ + nat64_db_st_entry_free (db, pool_elt_at_index(st, ste_index[0])); \ + vec_free (ste_to_be_free); \ + ste_to_be_free = 0; + foreach_snat_protocol +#undef _ + st = db->st._unk_proto_st; + pool_foreach (ste, st, ({ + bibe = pool_elt_at_index (db->bib._unk_proto_bib, ste->bibe_index); + if (bibe->out_addr.as_u32 == out_addr->as_u32) + vec_add1 (ste_to_be_free, ste - st); + })); + vec_foreach (ste_index, ste_to_be_free) + nat64_db_st_entry_free (db, pool_elt_at_index(st, ste_index[0])); + vec_free (ste_to_be_free); +/* *INDENT-ON* */ +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/nat/nat64_db.h b/src/plugins/nat/nat64_db.h new file mode 100644 index 00000000000..394ca875bbb --- /dev/null +++ b/src/plugins/nat/nat64_db.h @@ -0,0 +1,307 @@ +/* + * Copyright (c) 2017 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. + */ +/** + * @file + * @brief NAT64 DB + */ +#ifndef __included_nat64_db_h__ +#define __included_nat64_db_h__ + +#include <vppinfra/bihash_24_8.h> +#include <vppinfra/bihash_48_8.h> +#include <nat/nat.h> + + +typedef struct +{ + union + { + struct + { + ip46_address_t addr; + u32 fib_index; + u16 port; + u8 proto; + u8 rsvd; + }; + u64 as_u64[3]; + }; +} nat64_db_bib_entry_key_t; + +/* *INDENT-OFF* */ +typedef CLIB_PACKED(struct +{ + ip6_address_t in_addr; + u16 in_port; + ip4_address_t out_addr; + u16 out_port; + u32 fib_index; + u32 ses_num; + u8 proto; + u8 is_static; +}) nat64_db_bib_entry_t; +/* *INDENT-ON* */ + +typedef struct +{ + /* BIBs */ +/* *INDENT-OFF* */ +#define _(N, i, n, s) \ + nat64_db_bib_entry_t *_##n##_bib; + foreach_snat_protocol +#undef _ +/* *INDENT-ON* */ + nat64_db_bib_entry_t *_unk_proto_bib; + + /* BIB lookup */ + clib_bihash_24_8_t in2out; + clib_bihash_24_8_t out2in; +} nat64_db_bib_t; + +typedef struct +{ + union + { + struct + { + ip46_address_t l_addr; + ip46_address_t r_addr; + u32 fib_index; + u16 l_port; + u16 r_port; + u8 proto; + u8 rsvd[7]; + }; + u64 as_u64[6]; + }; +} nat64_db_st_entry_key_t; + +/* *INDENT-OFF* */ +typedef CLIB_PACKED(struct +{ + ip6_address_t in_r_addr; + ip4_address_t out_r_addr; + u16 r_port; + u32 bibe_index; + u32 expire; + u8 proto; + u8 tcp_state; +}) nat64_db_st_entry_t; +/* *INDENT-ON* */ + +typedef struct +{ + /* session tables */ +/* *INDENT-OFF* */ +#define _(N, i, n, s) \ + nat64_db_st_entry_t *_##n##_st; + foreach_snat_protocol +#undef _ +/* *INDENT-ON* */ + nat64_db_st_entry_t *_unk_proto_st; + + /* session lookup */ + clib_bihash_48_8_t in2out; + clib_bihash_48_8_t out2in; +} nat64_db_st_t; + +typedef struct +{ + nat64_db_bib_t bib; + nat64_db_st_t st; +} nat64_db_t; + +/** + * @brief Initialize NAT64 DB. + * + * @param db NAT64 DB. + * + * @returns 0 on success, non-zero value otherwise. + */ +int nat64_db_init (nat64_db_t * db); + +/** + * @brief Create new NAT64 BIB entry. + * + * @param db NAT64 DB. + * @param in_addr Inside IPv6 address. + * @param out_addr Outside IPv4 address. + * @param in_port Inside port number. + * @param out_port Outside port number. + * @param fib_index FIB index. + * @param proto L4 protocol. + * @param is_static 1 if static, 0 if dynamic. + * + * @returns BIB entry on success, 0 otherwise. + */ +nat64_db_bib_entry_t *nat64_db_bib_entry_create (nat64_db_t * db, + ip6_address_t * in_addr, + ip4_address_t * out_addr, + u16 in_port, u16 out_port, + u32 fib_index, + u8 proto, u8 is_static); + +/** + * @brief Free NAT64 BIB entry. + * + * @param db NAT64 DB. + * @param bibe BIB entry. + */ +void nat64_db_bib_entry_free (nat64_db_t * db, nat64_db_bib_entry_t * bibe); + +/** + * @brief Call back function when walking NAT64 BIB, non-zero + * return value stop walk. + */ +typedef int (*nat64_db_bib_walk_fn_t) (nat64_db_bib_entry_t * bibe, + void *ctx); +/** + * @brief Walk NAT64 BIB. + * + * @param db NAT64 DB. + * @param proto BIB L4 protocol: + * - 255 all BIBs + * - 6 TCP BIB + * - 17 UDP BIB + * - 1/58 ICMP BIB + * - otherwise "unknown" protocol BIB + * @param fn The function to invoke on each entry visited. + * @param ctx A context passed in the visit function. + */ +void nat64_db_bib_walk (nat64_db_t * db, u8 proto, + nat64_db_bib_walk_fn_t fn, void *ctx); + +/** + * @brief Find NAT64 BIB entry. + * + * @param db NAT64 DB. + * @param addr IP address. + * @param port Port number. + * @param proto L4 protocol. + * @param fib_index FIB index. + * @param is_ip6 1 if find by IPv6 (inside) address, 0 by IPv4 (outside). + * + * @return BIB entry if found. + */ +nat64_db_bib_entry_t *nat64_db_bib_entry_find (nat64_db_t * db, + ip46_address_t * addr, + u16 port, + u8 proto, + u32 fib_index, u8 is_ip6); + +/** + * @brief Get BIB entry by index and protocol. + * + * @param db NAT64 DB. + * @param proto L4 protocol. + * @param bibe_index BIB entry index. + * + * @return BIB entry if found. + */ +nat64_db_bib_entry_t *nat64_db_bib_entry_by_index (nat64_db_t * db, + u8 proto, u32 bibe_index); +/** + * @brief Create new NAT64 session table entry. + * + * @param db NAT64 DB. + * @param bibe Corresponding BIB entry. + * @param in_r_addr Inside IPv6 address of the remote host. + * @param out_r_addr Outside IPv4 address of the remote host. + * @param r_port Remote host port number. + * + * @returns BIB entry on success, 0 otherwise. + */ +nat64_db_st_entry_t *nat64_db_st_entry_create (nat64_db_t * db, + nat64_db_bib_entry_t * bibe, + ip6_address_t * in_r_addr, + ip4_address_t * out_r_addr, + u16 r_port); + +/** + * @brief Free NAT64 session table entry. + * + * @param db NAT64 DB. + * @param ste Session table entry. + */ +void nat64_db_st_entry_free (nat64_db_t * db, nat64_db_st_entry_t * ste); + +/** + * @brief Find NAT64 session table entry. + * + * @param db NAT64 DB. + * @param l_addr Local host address. + * @param r_addr Remote host address. + * @param l_port Local host port number. + * @param r_port Remote host port number. + * @param proto L4 protocol. + * @param fib_index FIB index. + * @param is_ip6 1 if find by IPv6 (inside) address, 0 by IPv4 (outside). + * + * @return BIB entry if found. + */ +nat64_db_st_entry_t *nat64_db_st_entry_find (nat64_db_t * db, + ip46_address_t * l_addr, + ip46_address_t * r_addr, + u16 l_port, u16 r_port, + u8 proto, + u32 fib_index, u8 is_ip6); + +/** + * @brief Call back function when walking NAT64 session table, non-zero + * return value stop walk. + */ +typedef int (*nat64_db_st_walk_fn_t) (nat64_db_st_entry_t * ste, void *ctx); + +/** + * @brief Walk NAT64 session table. + * + * @param db NAT64 DB. + * @param proto L4 protocol: + * - 255 all session tables + * - 6 TCP session table + * - 17 UDP session table + * - 1/58 ICMP session table + * - otherwise "unknown" protocol session table + * @param fn The function to invoke on each entry visited. + * @param ctx A context passed in the visit function. + */ +void nat64_db_st_walk (nat64_db_t * db, u8 proto, + nat64_db_st_walk_fn_t fn, void *ctx); + +/** + * @brief Free expired session entries in session tables. + * + * @param db NAT64 DB. + * @param now Current time. + */ +void nad64_db_st_free_expired (nat64_db_t * db, u32 now); + +/** + * @brief Free sessions using specific outside address. + * + * @param db NAT64 DB. + * @param out_addr Outside address to match. + */ +void nat64_db_free_out_addr (nat64_db_t * db, ip4_address_t * out_addr); + +#endif /* __included_nat64_db_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/nat/nat64_doc.md b/src/plugins/nat/nat64_doc.md new file mode 100644 index 00000000000..f94467dade1 --- /dev/null +++ b/src/plugins/nat/nat64_doc.md @@ -0,0 +1,73 @@ +# Stateful NAT64: Network Address and Protocol Translation from IPv6 Clients to IPv4 Servers {#nat64_doc} + +## Introduction + +Stateful NAT64 in VPP allows IPv6-only clients to contact IPv4 servers using unicast UDP, TCP, or ICMP based on RFC 6146. + +## Configuration + +### Enable/disable NAT64 feature on the interface + +> set interface nat64 in|out <intfc> [del] + +in: inside/local/IPv6 network +out: outside/external/IPv4 network +intfc: interface name + +### Add/delete NAT64 pool address + +One or more public IPv4 addresses assigned to a NAT64 are shared among several IPv6-only clients. + +> nat64 add pool address <ip4-range-start> [- <ip4-range-end>] [tenant-vrf <tenant-vrf-id>] [del] + +ip4-range-start: First IPv4 address of the range +ip4-range-end: Last IPv4 address of the range (optional, not used for single address) +tenant-vrf-id: VRF id of the tenant associated with the pool address (optional, if not set pool address is global) + +### Add/delete static BIB entry + +Stateful NAT64 also supports IPv4-initiated communications to a subset of the IPv6 hosts through staticaly configured bindings. + +> nat64 add static bib <ip6-addr> <in-port> <ip4-addr> <out-port> tcp|udp|icmp [vfr <table-id>] [del] + +ip6-addr: inside IPv6 address of the host +in-port: inside port or ICMPv6 identifier +ip4-addr: outside IPv4 address of the host +out-port: outside port or ICMPv4 identifier +table-id: VRF id of the tenant associated with the BIB entry (optional, default use global VRF) + +### Set NAT64 session timeouts + +Session is deleted when timer expires. If all sessions corresponding to a dynamically create BIB entry are deleted, then the BIB entry is also deleted. When packets are flowing sessiom timer is refreshed to keep the session alive. + +> set nat64 timeouts udp <sec> icmp <sec> tcp-trans <sec> tcp-est <sec> tcp-incoming-syn <sec> | reset + +udp: UDP session timeout value (default 300sec) +icmp: ICMP session timeout value (default 60sec) +tcp-trans: transitory TCP session timeout value (default 240sec) +tcp-est: established TCP session timeout value (default 7440sec) +tcp-incoming-syn: incoming SYN TCP session timeout value (default 6sec) +reset: reset timers to default values + +### Set NAT64 prefix + +Stateful NAT64 support the algorithm for generating IPv6 representations of IPv4 addresses defined in RFC 6052. If no prefix is configured, Well-Known Prefix (64:ff9b::/96) is used. + +> nat64 add prefix <ip6-prefix>/<plen> [tenant-vrf <vrf-id>] [del] + +ip6-prefix: IPv6 prefix +plen: prefix length (valid values: 32, 40, 48, 56, 64, or 96) +tenant-vrf: VRF id of the tenant associated with the prefix + +### Show commands + +> show nat64 pool +> show nat64 interfaces +> show nat64 bib tcp|udp|icmp +> show nat64 session table tcp|udp|icmp +> show nat64 tiemouts +> show nat64 prefix + +## Notes + +Multi thread is not supported yet (CLI/API commands are disabled when VPP runs with multiple threads). diff --git a/src/plugins/nat/nat64_in2out.c b/src/plugins/nat/nat64_in2out.c new file mode 100644 index 00000000000..f78baff4e12 --- /dev/null +++ b/src/plugins/nat/nat64_in2out.c @@ -0,0 +1,1118 @@ +/* + * Copyright (c) 2017 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. + */ +/** + * @file + * @brief NAT64 IPv6 to IPv4 translation (inside to outside network) + */ + +#include <nat/nat64.h> +#include <vnet/ip/ip6_to_ip4.h> +#include <vnet/fib/fib_table.h> + +typedef struct +{ + u32 sw_if_index; + u32 next_index; + u8 is_slow_path; +} nat64_in2out_trace_t; + +static u8 * +format_nat64_in2out_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + nat64_in2out_trace_t *t = va_arg (*args, nat64_in2out_trace_t *); + char *tag; + + tag = t->is_slow_path ? "NAT64-in2out-slowpath" : "NAT64-in2out"; + + s = + format (s, "%s: sw_if_index %d, next index %d", tag, t->sw_if_index, + t->next_index); + + return s; +} + +vlib_node_registration_t nat64_in2out_node; +vlib_node_registration_t nat64_in2out_slowpath_node; + +#define foreach_nat64_in2out_error \ +_(UNSUPPORTED_PROTOCOL, "unsupported protocol") \ +_(IN2OUT_PACKETS, "good in2out packets processed") \ +_(NO_TRANSLATION, "no translation") \ +_(UNKNOWN, "unknown") + +typedef enum +{ +#define _(sym,str) NAT64_IN2OUT_ERROR_##sym, + foreach_nat64_in2out_error +#undef _ + NAT64_IN2OUT_N_ERROR, +} nat64_in2out_error_t; + +static char *nat64_in2out_error_strings[] = { +#define _(sym,string) string, + foreach_nat64_in2out_error +#undef _ +}; + +typedef enum +{ + NAT64_IN2OUT_NEXT_IP4_LOOKUP, + NAT64_IN2OUT_NEXT_IP6_LOOKUP, + NAT64_IN2OUT_NEXT_DROP, + NAT64_IN2OUT_NEXT_SLOWPATH, + NAT64_IN2OUT_N_NEXT, +} nat64_in2out_next_t; + +typedef struct nat64_in2out_set_ctx_t_ +{ + vlib_buffer_t *b; + vlib_main_t *vm; +} nat64_in2out_set_ctx_t; + +/** + * @brief Check whether is a hairpinning. + * + * If the destination IP address of the packet is an IPv4 address assigned to + * the NAT64 itself, then the packet is a hairpin packet. + * + * param dst_addr Destination address of the packet. + * + * @returns 1 if hairpinning, otherwise 0. + */ +static_always_inline int +is_hairpinning (ip6_address_t * dst_addr) +{ + nat64_main_t *nm = &nat64_main; + int i; + + for (i = 0; i < vec_len (nm->addr_pool); i++) + { + if (nm->addr_pool[i].addr.as_u32 == dst_addr->as_u32[3]) + return 1; + } + + return 0; +} + +static int +nat64_in2out_tcp_udp_set_cb (ip6_header_t * ip6, ip4_header_t * ip4, + void *arg) +{ + nat64_main_t *nm = &nat64_main; + nat64_in2out_set_ctx_t *ctx = arg; + nat64_db_bib_entry_t *bibe; + nat64_db_st_entry_t *ste; + ip46_address_t saddr, daddr; + u32 sw_if_index, fib_index; + udp_header_t *udp = ip6_next_header (ip6); + u8 proto = ip6->protocol; + u16 sport = udp->src_port; + u16 dport = udp->dst_port; + + sw_if_index = vnet_buffer (ctx->b)->sw_if_index[VLIB_RX]; + fib_index = + fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6, sw_if_index); + + saddr.as_u64[0] = ip6->src_address.as_u64[0]; + saddr.as_u64[1] = ip6->src_address.as_u64[1]; + daddr.as_u64[0] = ip6->dst_address.as_u64[0]; + daddr.as_u64[1] = ip6->dst_address.as_u64[1]; + + ste = + nat64_db_st_entry_find (&nm->db, &saddr, &daddr, sport, dport, proto, + fib_index, 1); + + if (ste) + { + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + } + else + { + bibe = + nat64_db_bib_entry_find (&nm->db, &saddr, sport, proto, fib_index, 1); + + if (!bibe) + { + u16 out_port; + ip4_address_t out_addr; + if (nat64_alloc_out_addr_and_port + (fib_index, ip_proto_to_snat_proto (proto), &out_addr, + &out_port)) + return -1; + + bibe = + nat64_db_bib_entry_create (&nm->db, &ip6->src_address, &out_addr, + sport, clib_host_to_net_u16 (out_port), + fib_index, proto, 0); + if (!bibe) + return -1; + } + + nat64_extract_ip4 (&ip6->dst_address, &daddr.ip4, fib_index); + ste = + nat64_db_st_entry_create (&nm->db, bibe, &ip6->dst_address, + &daddr.ip4, dport); + if (!ste) + return -1; + } + + nat64_session_reset_timeout (ste, ctx->vm); + + ip4->src_address.as_u32 = bibe->out_addr.as_u32; + udp->src_port = bibe->out_port; + + ip4->dst_address.as_u32 = ste->out_r_addr.as_u32; + + if (proto == IP_PROTOCOL_TCP) + { + u16 *checksum; + ip_csum_t csum; + tcp_header_t *tcp = ip6_next_header (ip6); + + checksum = &tcp->checksum; + csum = ip_csum_sub_even (*checksum, sport); + csum = ip_csum_add_even (csum, udp->src_port); + *checksum = ip_csum_fold (csum); + } + + return 0; +} + +static int +nat64_in2out_icmp_set_cb (ip6_header_t * ip6, ip4_header_t * ip4, void *arg) +{ + nat64_main_t *nm = &nat64_main; + nat64_in2out_set_ctx_t *ctx = arg; + nat64_db_bib_entry_t *bibe; + nat64_db_st_entry_t *ste; + ip46_address_t saddr, daddr; + u32 sw_if_index, fib_index; + icmp46_header_t *icmp = ip6_next_header (ip6); + + sw_if_index = vnet_buffer (ctx->b)->sw_if_index[VLIB_RX]; + fib_index = + fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6, sw_if_index); + + saddr.as_u64[0] = ip6->src_address.as_u64[0]; + saddr.as_u64[1] = ip6->src_address.as_u64[1]; + daddr.as_u64[0] = ip6->dst_address.as_u64[0]; + daddr.as_u64[1] = ip6->dst_address.as_u64[1]; + + if (icmp->type == ICMP4_echo_request || icmp->type == ICMP4_echo_reply) + { + u16 in_id = ((u16 *) (icmp))[2]; + ste = + nat64_db_st_entry_find (&nm->db, &saddr, &daddr, in_id, 0, + IP_PROTOCOL_ICMP, fib_index, 1); + + if (ste) + { + bibe = + nat64_db_bib_entry_by_index (&nm->db, IP_PROTOCOL_ICMP, + ste->bibe_index); + if (!bibe) + return -1; + } + else + { + bibe = + nat64_db_bib_entry_find (&nm->db, &saddr, in_id, + IP_PROTOCOL_ICMP, fib_index, 1); + + if (!bibe) + { + u16 out_id; + ip4_address_t out_addr; + if (nat64_alloc_out_addr_and_port + (fib_index, SNAT_PROTOCOL_ICMP, &out_addr, &out_id)) + return -1; + + bibe = + nat64_db_bib_entry_create (&nm->db, &ip6->src_address, + &out_addr, in_id, + clib_host_to_net_u16 (out_id), + fib_index, IP_PROTOCOL_ICMP, 0); + if (!bibe) + return -1; + } + + nat64_extract_ip4 (&ip6->dst_address, &daddr.ip4, fib_index); + ste = + nat64_db_st_entry_create (&nm->db, bibe, &ip6->dst_address, + &daddr.ip4, 0); + if (!ste) + return -1; + } + + nat64_session_reset_timeout (ste, ctx->vm); + + ip4->src_address.as_u32 = bibe->out_addr.as_u32; + ((u16 *) (icmp))[2] = bibe->out_port; + + ip4->dst_address.as_u32 = ste->out_r_addr.as_u32; + } + else + { + if (!vec_len (nm->addr_pool)) + return -1; + + ip4->src_address.as_u32 = nm->addr_pool[0].addr.as_u32; + nat64_extract_ip4 (&ip6->dst_address, &ip4->dst_address, fib_index); + } + + return 0; +} + +static int +nat64_in2out_inner_icmp_set_cb (ip6_header_t * ip6, ip4_header_t * ip4, + void *arg) +{ + nat64_main_t *nm = &nat64_main; + nat64_in2out_set_ctx_t *ctx = arg; + nat64_db_st_entry_t *ste; + nat64_db_bib_entry_t *bibe; + ip46_address_t saddr, daddr; + u32 sw_if_index, fib_index; + u8 proto = ip6->protocol; + + sw_if_index = vnet_buffer (ctx->b)->sw_if_index[VLIB_RX]; + fib_index = + fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6, sw_if_index); + + saddr.as_u64[0] = ip6->src_address.as_u64[0]; + saddr.as_u64[1] = ip6->src_address.as_u64[1]; + daddr.as_u64[0] = ip6->dst_address.as_u64[0]; + daddr.as_u64[1] = ip6->dst_address.as_u64[1]; + + if (proto == IP_PROTOCOL_ICMP6) + { + icmp46_header_t *icmp = ip6_next_header (ip6); + u16 in_id = ((u16 *) (icmp))[2]; + proto = IP_PROTOCOL_ICMP; + + if (! + (icmp->type == ICMP4_echo_request + || icmp->type == ICMP4_echo_reply)) + return -1; + + ste = + nat64_db_st_entry_find (&nm->db, &daddr, &saddr, in_id, 0, proto, + fib_index, 1); + if (!ste) + return -1; + + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + + ip4->dst_address.as_u32 = bibe->out_addr.as_u32; + ((u16 *) (icmp))[2] = bibe->out_port; + ip4->src_address.as_u32 = ste->out_r_addr.as_u32; + } + else + { + udp_header_t *udp = ip6_next_header (ip6); + tcp_header_t *tcp = ip6_next_header (ip6); + u16 *checksum; + ip_csum_t csum; + + u16 sport = udp->src_port; + u16 dport = udp->dst_port; + + ste = + nat64_db_st_entry_find (&nm->db, &daddr, &saddr, dport, sport, proto, + fib_index, 1); + if (!ste) + return -1; + + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + + ip4->dst_address.as_u32 = bibe->out_addr.as_u32; + udp->dst_port = bibe->out_port; + ip4->src_address.as_u32 = ste->out_r_addr.as_u32; + + if (proto == IP_PROTOCOL_TCP) + checksum = &tcp->checksum; + else + checksum = &udp->checksum; + csum = ip_csum_sub_even (*checksum, dport); + csum = ip_csum_add_even (csum, udp->dst_port); + *checksum = ip_csum_fold (csum); + } + + return 0; +} + +typedef struct unk_proto_st_walk_ctx_t_ +{ + ip6_address_t src_addr; + ip6_address_t dst_addr; + ip4_address_t out_addr; + u32 fib_index; + u8 proto; +} unk_proto_st_walk_ctx_t; + +static int +unk_proto_st_walk (nat64_db_st_entry_t * ste, void *arg) +{ + nat64_main_t *nm = &nat64_main; + unk_proto_st_walk_ctx_t *ctx = arg; + nat64_db_bib_entry_t *bibe; + ip46_address_t saddr, daddr; + + if (ip46_address_is_equal (&ste->in_r_addr, &ctx->dst_addr)) + { + bibe = + nat64_db_bib_entry_by_index (&nm->db, ste->proto, ste->bibe_index); + if (!bibe) + return -1; + + if (ip46_address_is_equal (&bibe->in_addr, &ctx->src_addr) + && bibe->fib_index == ctx->fib_index) + { + memset (&saddr, 0, sizeof (saddr)); + saddr.ip4.as_u32 = bibe->out_addr.as_u32; + memset (&daddr, 0, sizeof (daddr)); + nat64_extract_ip4 (&ctx->dst_addr, &daddr.ip4, ctx->fib_index); + + if (nat64_db_st_entry_find + (&nm->db, &daddr, &saddr, 0, 0, ctx->proto, ctx->fib_index, 0)) + return -1; + + ctx->out_addr.as_u32 = bibe->out_addr.as_u32; + return 1; + } + } + + return 0; +} + +static int +nat64_in2out_unk_proto_set_cb (ip6_header_t * ip6, ip4_header_t * ip4, + void *arg) +{ + nat64_main_t *nm = &nat64_main; + nat64_in2out_set_ctx_t *ctx = arg; + nat64_db_bib_entry_t *bibe; + nat64_db_st_entry_t *ste; + ip46_address_t saddr, daddr, addr; + u32 sw_if_index, fib_index; + u8 proto = ip6->protocol; + int i; + + sw_if_index = vnet_buffer (ctx->b)->sw_if_index[VLIB_RX]; + fib_index = + fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6, sw_if_index); + + saddr.as_u64[0] = ip6->src_address.as_u64[0]; + saddr.as_u64[1] = ip6->src_address.as_u64[1]; + daddr.as_u64[0] = ip6->dst_address.as_u64[0]; + daddr.as_u64[1] = ip6->dst_address.as_u64[1]; + + ste = + nat64_db_st_entry_find (&nm->db, &saddr, &daddr, 0, 0, proto, fib_index, + 1); + + if (ste) + { + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + } + else + { + bibe = + nat64_db_bib_entry_find (&nm->db, &saddr, 0, proto, fib_index, 1); + + if (!bibe) + { + /* Choose same out address as for TCP/UDP session to same dst */ + unk_proto_st_walk_ctx_t ctx = { + .src_addr.as_u64[0] = ip6->src_address.as_u64[0], + .src_addr.as_u64[1] = ip6->src_address.as_u64[1], + .dst_addr.as_u64[0] = ip6->dst_address.as_u64[0], + .dst_addr.as_u64[1] = ip6->dst_address.as_u64[1], + .out_addr.as_u32 = 0, + .fib_index = fib_index, + .proto = proto, + }; + + nat64_db_st_walk (&nm->db, IP_PROTOCOL_TCP, unk_proto_st_walk, + &ctx); + + if (!ctx.out_addr.as_u32) + nat64_db_st_walk (&nm->db, IP_PROTOCOL_UDP, unk_proto_st_walk, + &ctx); + + /* Verify if out address is not already in use for protocol */ + memset (&addr, 0, sizeof (addr)); + addr.ip4.as_u32 = ctx.out_addr.as_u32; + if (nat64_db_bib_entry_find (&nm->db, &addr, 0, proto, 0, 0)) + ctx.out_addr.as_u32 = 0; + + if (!ctx.out_addr.as_u32) + { + for (i = 0; i < vec_len (nm->addr_pool); i++) + { + addr.ip4.as_u32 = nm->addr_pool[i].addr.as_u32; + if (!nat64_db_bib_entry_find + (&nm->db, &addr, 0, proto, 0, 0)) + break; + } + } + + if (!ctx.out_addr.as_u32) + return -1; + + bibe = + nat64_db_bib_entry_create (&nm->db, &ip6->src_address, + &ctx.out_addr, 0, 0, fib_index, proto, + 0); + if (!bibe) + return -1; + } + + nat64_extract_ip4 (&ip6->dst_address, &daddr.ip4, fib_index); + ste = + nat64_db_st_entry_create (&nm->db, bibe, &ip6->dst_address, + &daddr.ip4, 0); + if (!ste) + return -1; + } + + nat64_session_reset_timeout (ste, ctx->vm); + + ip4->src_address.as_u32 = bibe->out_addr.as_u32; + ip4->dst_address.as_u32 = ste->out_r_addr.as_u32; + + return 0; +} + + + +static int +nat64_in2out_tcp_udp_hairpinning (vlib_main_t * vm, vlib_buffer_t * b, + ip6_header_t * ip6) +{ + nat64_main_t *nm = &nat64_main; + nat64_db_bib_entry_t *bibe; + nat64_db_st_entry_t *ste; + ip46_address_t saddr, daddr; + u32 sw_if_index, fib_index; + udp_header_t *udp = ip6_next_header (ip6); + tcp_header_t *tcp = ip6_next_header (ip6); + u8 proto = ip6->protocol; + u16 sport = udp->src_port; + u16 dport = udp->dst_port; + u16 *checksum; + ip_csum_t csum; + + sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX]; + fib_index = + fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6, sw_if_index); + + saddr.as_u64[0] = ip6->src_address.as_u64[0]; + saddr.as_u64[1] = ip6->src_address.as_u64[1]; + daddr.as_u64[0] = ip6->dst_address.as_u64[0]; + daddr.as_u64[1] = ip6->dst_address.as_u64[1]; + + if (proto == IP_PROTOCOL_UDP) + checksum = &udp->checksum; + else + checksum = &tcp->checksum; + + csum = ip_csum_sub_even (*checksum, ip6->src_address.as_u64[0]); + csum = ip_csum_sub_even (csum, ip6->src_address.as_u64[1]); + csum = ip_csum_sub_even (csum, ip6->dst_address.as_u64[0]); + csum = ip_csum_sub_even (csum, ip6->dst_address.as_u64[1]); + csum = ip_csum_sub_even (csum, sport); + csum = ip_csum_sub_even (csum, dport); + + ste = + nat64_db_st_entry_find (&nm->db, &saddr, &daddr, sport, dport, proto, + fib_index, 1); + + if (ste) + { + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + } + else + { + bibe = + nat64_db_bib_entry_find (&nm->db, &saddr, sport, proto, fib_index, 1); + + if (!bibe) + { + u16 out_port; + ip4_address_t out_addr; + if (nat64_alloc_out_addr_and_port + (fib_index, ip_proto_to_snat_proto (proto), &out_addr, + &out_port)) + return -1; + + bibe = + nat64_db_bib_entry_create (&nm->db, &ip6->src_address, &out_addr, + sport, clib_host_to_net_u16 (out_port), + fib_index, proto, 0); + if (!bibe) + return -1; + } + + nat64_extract_ip4 (&ip6->dst_address, &daddr.ip4, fib_index); + ste = + nat64_db_st_entry_create (&nm->db, bibe, &ip6->dst_address, + &daddr.ip4, dport); + if (!ste) + return -1; + } + + nat64_session_reset_timeout (ste, vm); + + sport = udp->src_port = bibe->out_port; + nat64_compose_ip6 (&ip6->src_address, &bibe->out_addr, fib_index); + + memset (&saddr, 0, sizeof (saddr)); + memset (&daddr, 0, sizeof (daddr)); + saddr.ip4.as_u32 = bibe->out_addr.as_u32; + daddr.ip4.as_u32 = ste->out_r_addr.as_u32; + + ste = + nat64_db_st_entry_find (&nm->db, &daddr, &saddr, dport, sport, proto, 0, + 0); + + if (ste) + { + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + } + else + { + bibe = nat64_db_bib_entry_find (&nm->db, &daddr, dport, proto, 0, 0); + + if (!bibe) + return -1; + + ste = + nat64_db_st_entry_create (&nm->db, bibe, &ip6->src_address, + &saddr.ip4, sport); + } + + ip6->dst_address.as_u64[0] = bibe->in_addr.as_u64[0]; + ip6->dst_address.as_u64[1] = bibe->in_addr.as_u64[1]; + udp->dst_port = bibe->in_port; + + csum = ip_csum_add_even (csum, ip6->src_address.as_u64[0]); + csum = ip_csum_add_even (csum, ip6->src_address.as_u64[1]); + csum = ip_csum_add_even (csum, ip6->dst_address.as_u64[0]); + csum = ip_csum_add_even (csum, ip6->dst_address.as_u64[1]); + csum = ip_csum_add_even (csum, udp->src_port); + csum = ip_csum_add_even (csum, udp->dst_port); + *checksum = ip_csum_fold (csum); + + return 0; +} + +static int +nat64_in2out_icmp_hairpinning (vlib_main_t * vm, vlib_buffer_t * b, + ip6_header_t * ip6) +{ + nat64_main_t *nm = &nat64_main; + nat64_db_bib_entry_t *bibe; + nat64_db_st_entry_t *ste; + icmp46_header_t *icmp = ip6_next_header (ip6); + ip6_header_t *inner_ip6; + ip46_address_t saddr, daddr; + u32 sw_if_index, fib_index; + u8 proto; + udp_header_t *udp; + tcp_header_t *tcp; + u16 *checksum, sport, dport; + ip_csum_t csum; + + if (icmp->type == ICMP6_echo_request || icmp->type == ICMP6_echo_reply) + return -1; + + inner_ip6 = (ip6_header_t *) u8_ptr_add (icmp, 8); + + proto = inner_ip6->protocol; + + if (proto == IP_PROTOCOL_ICMP6) + return -1; + + sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX]; + fib_index = + fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6, sw_if_index); + + saddr.as_u64[0] = inner_ip6->src_address.as_u64[0]; + saddr.as_u64[1] = inner_ip6->src_address.as_u64[1]; + daddr.as_u64[0] = inner_ip6->dst_address.as_u64[0]; + daddr.as_u64[1] = inner_ip6->dst_address.as_u64[1]; + + udp = ip6_next_header (inner_ip6); + tcp = ip6_next_header (inner_ip6); + + sport = udp->src_port; + dport = udp->dst_port; + + if (proto == IP_PROTOCOL_UDP) + checksum = &udp->checksum; + else + checksum = &tcp->checksum; + + csum = ip_csum_sub_even (*checksum, inner_ip6->src_address.as_u64[0]); + csum = ip_csum_sub_even (csum, inner_ip6->src_address.as_u64[1]); + csum = ip_csum_sub_even (csum, inner_ip6->dst_address.as_u64[0]); + csum = ip_csum_sub_even (csum, inner_ip6->dst_address.as_u64[1]); + csum = ip_csum_sub_even (csum, sport); + csum = ip_csum_sub_even (csum, dport); + + ste = + nat64_db_st_entry_find (&nm->db, &daddr, &saddr, dport, sport, proto, + fib_index, 1); + if (!ste) + return -1; + + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + + dport = udp->dst_port = bibe->out_port; + nat64_compose_ip6 (&inner_ip6->dst_address, &bibe->out_addr, fib_index); + + memset (&saddr, 0, sizeof (saddr)); + memset (&daddr, 0, sizeof (daddr)); + saddr.ip4.as_u32 = ste->out_r_addr.as_u32; + daddr.ip4.as_u32 = bibe->out_addr.as_u32; + + ste = + nat64_db_st_entry_find (&nm->db, &saddr, &daddr, sport, dport, proto, 0, + 0); + if (!ste) + return -1; + + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + + inner_ip6->src_address.as_u64[0] = bibe->in_addr.as_u64[0]; + inner_ip6->src_address.as_u64[1] = bibe->in_addr.as_u64[1]; + udp->src_port = bibe->in_port; + + csum = ip_csum_add_even (csum, inner_ip6->src_address.as_u64[0]); + csum = ip_csum_add_even (csum, inner_ip6->src_address.as_u64[1]); + csum = ip_csum_add_even (csum, inner_ip6->dst_address.as_u64[0]); + csum = ip_csum_add_even (csum, inner_ip6->dst_address.as_u64[1]); + csum = ip_csum_add_even (csum, udp->src_port); + csum = ip_csum_add_even (csum, udp->dst_port); + *checksum = ip_csum_fold (csum); + + if (!vec_len (nm->addr_pool)) + return -1; + + nat64_compose_ip6 (&ip6->src_address, &nm->addr_pool[0].addr, fib_index); + ip6->dst_address.as_u64[0] = inner_ip6->src_address.as_u64[0]; + ip6->dst_address.as_u64[1] = inner_ip6->src_address.as_u64[1]; + + icmp->checksum = 0; + csum = ip_csum_with_carry (0, ip6->payload_length); + csum = ip_csum_with_carry (csum, clib_host_to_net_u16 (ip6->protocol)); + csum = ip_csum_with_carry (csum, ip6->src_address.as_u64[0]); + csum = ip_csum_with_carry (csum, ip6->src_address.as_u64[1]); + csum = ip_csum_with_carry (csum, ip6->dst_address.as_u64[0]); + csum = ip_csum_with_carry (csum, ip6->dst_address.as_u64[1]); + csum = + ip_incremental_checksum (csum, icmp, + clib_net_to_host_u16 (ip6->payload_length)); + icmp->checksum = ~ip_csum_fold (csum); + + return 0; +} + +static int +nat64_in2out_unk_proto_hairpinning (vlib_main_t * vm, vlib_buffer_t * b, + ip6_header_t * ip6) +{ + nat64_main_t *nm = &nat64_main; + nat64_db_bib_entry_t *bibe; + nat64_db_st_entry_t *ste; + ip46_address_t saddr, daddr, addr; + u32 sw_if_index, fib_index; + u8 proto = ip6->protocol; + int i; + + sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX]; + fib_index = + fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6, sw_if_index); + + saddr.as_u64[0] = ip6->src_address.as_u64[0]; + saddr.as_u64[1] = ip6->src_address.as_u64[1]; + daddr.as_u64[0] = ip6->dst_address.as_u64[0]; + daddr.as_u64[1] = ip6->dst_address.as_u64[1]; + + ste = + nat64_db_st_entry_find (&nm->db, &saddr, &daddr, 0, 0, proto, fib_index, + 1); + + if (ste) + { + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + } + else + { + bibe = + nat64_db_bib_entry_find (&nm->db, &saddr, 0, proto, fib_index, 1); + + if (!bibe) + { + /* Choose same out address as for TCP/UDP session to same dst */ + unk_proto_st_walk_ctx_t ctx = { + .src_addr.as_u64[0] = ip6->src_address.as_u64[0], + .src_addr.as_u64[1] = ip6->src_address.as_u64[1], + .dst_addr.as_u64[0] = ip6->dst_address.as_u64[0], + .dst_addr.as_u64[1] = ip6->dst_address.as_u64[1], + .out_addr.as_u32 = 0, + .fib_index = fib_index, + .proto = proto, + }; + + nat64_db_st_walk (&nm->db, IP_PROTOCOL_TCP, unk_proto_st_walk, + &ctx); + + if (!ctx.out_addr.as_u32) + nat64_db_st_walk (&nm->db, IP_PROTOCOL_UDP, unk_proto_st_walk, + &ctx); + + /* Verify if out address is not already in use for protocol */ + memset (&addr, 0, sizeof (addr)); + addr.ip4.as_u32 = ctx.out_addr.as_u32; + if (nat64_db_bib_entry_find (&nm->db, &addr, 0, proto, 0, 0)) + ctx.out_addr.as_u32 = 0; + + if (!ctx.out_addr.as_u32) + { + for (i = 0; i < vec_len (nm->addr_pool); i++) + { + addr.ip4.as_u32 = nm->addr_pool[i].addr.as_u32; + if (!nat64_db_bib_entry_find + (&nm->db, &addr, 0, proto, 0, 0)) + break; + } + } + + if (!ctx.out_addr.as_u32) + return -1; + + bibe = + nat64_db_bib_entry_create (&nm->db, &ip6->src_address, + &ctx.out_addr, 0, 0, fib_index, proto, + 0); + if (!bibe) + return -1; + } + + nat64_extract_ip4 (&ip6->dst_address, &daddr.ip4, fib_index); + ste = + nat64_db_st_entry_create (&nm->db, bibe, &ip6->dst_address, + &daddr.ip4, 0); + if (!ste) + return -1; + } + + nat64_session_reset_timeout (ste, vm); + + nat64_compose_ip6 (&ip6->src_address, &bibe->out_addr, fib_index); + + memset (&saddr, 0, sizeof (saddr)); + memset (&daddr, 0, sizeof (daddr)); + saddr.ip4.as_u32 = bibe->out_addr.as_u32; + daddr.ip4.as_u32 = ste->out_r_addr.as_u32; + + ste = nat64_db_st_entry_find (&nm->db, &daddr, &saddr, 0, 0, proto, 0, 0); + + if (ste) + { + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + } + else + { + bibe = nat64_db_bib_entry_find (&nm->db, &daddr, 0, proto, 0, 0); + + if (!bibe) + return -1; + + ste = + nat64_db_st_entry_create (&nm->db, bibe, &ip6->src_address, + &saddr.ip4, 0); + } + + ip6->dst_address.as_u64[0] = bibe->in_addr.as_u64[0]; + ip6->dst_address.as_u64[1] = bibe->in_addr.as_u64[1]; + + return 0; +} + +static inline uword +nat64_in2out_node_fn_inline (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame, u8 is_slow_path) +{ + u32 n_left_from, *from, *to_next; + nat64_in2out_next_t next_index; + u32 pkts_processed = 0; + u32 stats_node_index; + + stats_node_index = + is_slow_path ? nat64_in2out_slowpath_node.index : nat64_in2out_node.index; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t *b0; + u32 next0; + ip6_header_t *ip60; + u16 l4_offset0, frag_offset0; + u8 l4_protocol0; + u32 proto0; + nat64_in2out_set_ctx_t ctx0; + + /* speculatively enqueue b0 to the current next frame */ + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + ip60 = vlib_buffer_get_current (b0); + + ctx0.b = b0; + ctx0.vm = vm; + + next0 = NAT64_IN2OUT_NEXT_IP4_LOOKUP; + + if (PREDICT_FALSE + (ip6_parse + (ip60, b0->current_length, &l4_protocol0, &l4_offset0, + &frag_offset0))) + { + next0 = NAT64_IN2OUT_NEXT_DROP; + b0->error = node->errors[NAT64_IN2OUT_ERROR_UNKNOWN]; + goto trace0; + } + + proto0 = ip_proto_to_snat_proto (l4_protocol0); + if (frag_offset0 != 0) + { + next0 = NAT64_IN2OUT_NEXT_DROP; + b0->error = + node->errors[NAT64_IN2OUT_ERROR_UNSUPPORTED_PROTOCOL]; + goto trace0; + } + + if (is_slow_path) + { + if (PREDICT_TRUE (proto0 == ~0)) + { + if (is_hairpinning (&ip60->dst_address)) + { + next0 = NAT64_IN2OUT_NEXT_IP6_LOOKUP; + if (nat64_in2out_unk_proto_hairpinning (vm, b0, ip60)) + { + next0 = NAT64_IN2OUT_NEXT_DROP; + b0->error = + node->errors[NAT64_IN2OUT_ERROR_NO_TRANSLATION]; + } + goto trace0; + } + + if (ip6_to_ip4 (b0, nat64_in2out_unk_proto_set_cb, &ctx0)) + { + next0 = NAT64_IN2OUT_NEXT_DROP; + b0->error = + node->errors[NAT64_IN2OUT_ERROR_NO_TRANSLATION]; + goto trace0; + } + } + goto trace0; + } + else + { + if (PREDICT_FALSE (proto0 == ~0)) + { + next0 = NAT64_IN2OUT_NEXT_SLOWPATH; + goto trace0; + } + } + + if (proto0 == SNAT_PROTOCOL_ICMP) + { + if (is_hairpinning (&ip60->dst_address)) + { + next0 = NAT64_IN2OUT_NEXT_IP6_LOOKUP; + if (nat64_in2out_icmp_hairpinning (vm, b0, ip60)) + { + next0 = NAT64_IN2OUT_NEXT_DROP; + b0->error = + node->errors[NAT64_IN2OUT_ERROR_NO_TRANSLATION]; + } + goto trace0; + } + + if (icmp6_to_icmp + (b0, nat64_in2out_icmp_set_cb, &ctx0, + nat64_in2out_inner_icmp_set_cb, &ctx0)) + { + next0 = NAT64_IN2OUT_NEXT_DROP; + b0->error = node->errors[NAT64_IN2OUT_ERROR_NO_TRANSLATION]; + goto trace0; + } + } + else if (proto0 == SNAT_PROTOCOL_TCP || proto0 == SNAT_PROTOCOL_UDP) + { + if (is_hairpinning (&ip60->dst_address)) + { + next0 = NAT64_IN2OUT_NEXT_IP6_LOOKUP; + if (nat64_in2out_tcp_udp_hairpinning (vm, b0, ip60)) + { + next0 = NAT64_IN2OUT_NEXT_DROP; + b0->error = + node->errors[NAT64_IN2OUT_ERROR_NO_TRANSLATION]; + } + goto trace0; + } + + if (ip6_to_ip4_tcp_udp + (b0, nat64_in2out_tcp_udp_set_cb, &ctx0, 0)) + { + next0 = NAT64_IN2OUT_NEXT_DROP; + b0->error = node->errors[NAT64_IN2OUT_ERROR_NO_TRANSLATION]; + goto trace0; + } + } + + trace0: + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + nat64_in2out_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + t->next_index = next0; + t->is_slow_path = is_slow_path; + } + + pkts_processed += next0 != NAT64_IN2OUT_NEXT_DROP; + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, + n_left_to_next, bi0, next0); + } + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + vlib_node_increment_counter (vm, stats_node_index, + NAT64_IN2OUT_ERROR_IN2OUT_PACKETS, + pkts_processed); + return frame->n_vectors; +} + +static uword +nat64_in2out_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return nat64_in2out_node_fn_inline (vm, node, frame, 0); +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (nat64_in2out_node) = { + .function = nat64_in2out_node_fn, + .name = "nat64-in2out", + .vector_size = sizeof (u32), + .format_trace = format_nat64_in2out_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = ARRAY_LEN (nat64_in2out_error_strings), + .error_strings = nat64_in2out_error_strings, + .n_next_nodes = NAT64_IN2OUT_N_NEXT, + /* edit / add dispositions here */ + .next_nodes = { + [NAT64_IN2OUT_NEXT_DROP] = "error-drop", + [NAT64_IN2OUT_NEXT_IP4_LOOKUP] = "ip4-lookup", + [NAT64_IN2OUT_NEXT_IP6_LOOKUP] = "ip6-lookup", + [NAT64_IN2OUT_NEXT_SLOWPATH] = "nat64-in2out-slowpath", + }, +}; +/* *INDENT-ON* */ + +VLIB_NODE_FUNCTION_MULTIARCH (nat64_in2out_node, nat64_in2out_node_fn); + +static uword +nat64_in2out_slowpath_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + return nat64_in2out_node_fn_inline (vm, node, frame, 1); +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (nat64_in2out_slowpath_node) = { + .function = nat64_in2out_slowpath_node_fn, + .name = "nat64-in2out-slowpath", + .vector_size = sizeof (u32), + .format_trace = format_nat64_in2out_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = ARRAY_LEN (nat64_in2out_error_strings), + .error_strings = nat64_in2out_error_strings, + .n_next_nodes = NAT64_IN2OUT_N_NEXT, + /* edit / add dispositions here */ + .next_nodes = { + [NAT64_IN2OUT_NEXT_DROP] = "error-drop", + [NAT64_IN2OUT_NEXT_IP4_LOOKUP] = "ip4-lookup", + [NAT64_IN2OUT_NEXT_IP6_LOOKUP] = "ip6-lookup", + [NAT64_IN2OUT_NEXT_SLOWPATH] = "nat64-in2out-slowpath", + }, +}; +/* *INDENT-ON* */ + +VLIB_NODE_FUNCTION_MULTIARCH (nat64_in2out_slowpath_node, + nat64_in2out_slowpath_node_fn); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/nat/nat64_out2in.c b/src/plugins/nat/nat64_out2in.c new file mode 100644 index 00000000000..61e88a7f304 --- /dev/null +++ b/src/plugins/nat/nat64_out2in.c @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2017 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. + */ +/** + * @file + * @brief NAT64 IPv4 to IPv6 translation (otside to inside network) + */ + +#include <nat/nat64.h> +#include <vnet/ip/ip4_to_ip6.h> +#include <vnet/fib/ip4_fib.h> + +typedef struct +{ + u32 sw_if_index; + u32 next_index; +} nat64_out2in_trace_t; + +static u8 * +format_nat64_out2in_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + nat64_out2in_trace_t *t = va_arg (*args, nat64_out2in_trace_t *); + + s = + format (s, "NAT64-out2in: sw_if_index %d, next index %d", t->sw_if_index, + t->next_index); + + return s; +} + +vlib_node_registration_t nat64_out2in_node; + +#define foreach_nat64_out2in_error \ +_(UNSUPPORTED_PROTOCOL, "Unsupported protocol") \ +_(OUT2IN_PACKETS, "Good out2in packets processed") \ +_(NO_TRANSLATION, "No translation") \ +_(UNKNOWN, "unknown") + +typedef enum +{ +#define _(sym,str) NAT64_OUT2IN_ERROR_##sym, + foreach_nat64_out2in_error +#undef _ + NAT64_OUT2IN_N_ERROR, +} nat64_out2in_error_t; + +static char *nat64_out2in_error_strings[] = { +#define _(sym,string) string, + foreach_nat64_out2in_error +#undef _ +}; + +typedef enum +{ + NAT64_OUT2IN_NEXT_LOOKUP, + NAT64_OUT2IN_NEXT_DROP, + NAT64_OUT2IN_N_NEXT, +} nat64_out2in_next_t; + +typedef struct nat64_out2in_set_ctx_t_ +{ + vlib_buffer_t *b; + vlib_main_t *vm; +} nat64_out2in_set_ctx_t; + +static int +nat64_out2in_tcp_udp_set_cb (ip4_header_t * ip4, ip6_header_t * ip6, + void *arg) +{ + nat64_main_t *nm = &nat64_main; + nat64_out2in_set_ctx_t *ctx = arg; + nat64_db_bib_entry_t *bibe; + nat64_db_st_entry_t *ste; + ip46_address_t saddr, daddr; + ip6_address_t ip6_saddr; + udp_header_t *udp = ip4_next_header (ip4); + tcp_header_t *tcp = ip4_next_header (ip4); + u8 proto = ip4->protocol; + u16 dport = udp->dst_port; + u16 sport = udp->src_port; + u32 sw_if_index, fib_index; + u16 *checksum; + ip_csum_t csum; + + sw_if_index = vnet_buffer (ctx->b)->sw_if_index[VLIB_RX]; + fib_index = ip4_fib_table_get_index_for_sw_if_index (sw_if_index); + + memset (&saddr, 0, sizeof (saddr)); + saddr.ip4.as_u32 = ip4->src_address.as_u32; + memset (&daddr, 0, sizeof (daddr)); + daddr.ip4.as_u32 = ip4->dst_address.as_u32; + + ste = + nat64_db_st_entry_find (&nm->db, &daddr, &saddr, dport, sport, proto, + fib_index, 0); + if (ste) + { + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + } + else + { + bibe = + nat64_db_bib_entry_find (&nm->db, &daddr, dport, proto, fib_index, 0); + + if (!bibe) + return -1; + + nat64_compose_ip6 (&ip6_saddr, &ip4->src_address, bibe->fib_index); + ste = + nat64_db_st_entry_create (&nm->db, bibe, &ip6_saddr, &saddr.ip4, + sport); + } + + nat64_session_reset_timeout (ste, ctx->vm); + + ip6->src_address.as_u64[0] = ste->in_r_addr.as_u64[0]; + ip6->src_address.as_u64[1] = ste->in_r_addr.as_u64[1]; + + ip6->dst_address.as_u64[0] = bibe->in_addr.as_u64[0]; + ip6->dst_address.as_u64[1] = bibe->in_addr.as_u64[1]; + udp->dst_port = bibe->in_port; + + if (proto == IP_PROTOCOL_UDP) + checksum = &udp->checksum; + else + checksum = &tcp->checksum; + csum = ip_csum_sub_even (*checksum, dport); + csum = ip_csum_add_even (csum, udp->dst_port); + *checksum = ip_csum_fold (csum); + + vnet_buffer (ctx->b)->sw_if_index[VLIB_TX] = bibe->fib_index; + + return 0; +} + +static int +nat64_out2in_icmp_set_cb (ip4_header_t * ip4, ip6_header_t * ip6, void *arg) +{ + nat64_main_t *nm = &nat64_main; + nat64_out2in_set_ctx_t *ctx = arg; + nat64_db_bib_entry_t *bibe; + nat64_db_st_entry_t *ste; + ip46_address_t saddr, daddr; + ip6_address_t ip6_saddr; + u32 sw_if_index, fib_index; + icmp46_header_t *icmp = ip4_next_header (ip4); + + sw_if_index = vnet_buffer (ctx->b)->sw_if_index[VLIB_RX]; + fib_index = ip4_fib_table_get_index_for_sw_if_index (sw_if_index); + + memset (&saddr, 0, sizeof (saddr)); + saddr.ip4.as_u32 = ip4->src_address.as_u32; + memset (&daddr, 0, sizeof (daddr)); + daddr.ip4.as_u32 = ip4->dst_address.as_u32; + + if (icmp->type == ICMP6_echo_request || icmp->type == ICMP6_echo_reply) + { + u16 out_id = ((u16 *) (icmp))[2]; + ste = + nat64_db_st_entry_find (&nm->db, &daddr, &saddr, out_id, 0, + IP_PROTOCOL_ICMP, fib_index, 0); + + if (ste) + { + bibe = + nat64_db_bib_entry_by_index (&nm->db, IP_PROTOCOL_ICMP, + ste->bibe_index); + if (!bibe) + return -1; + } + else + { + bibe = + nat64_db_bib_entry_find (&nm->db, &daddr, out_id, + IP_PROTOCOL_ICMP, fib_index, 0); + if (!bibe) + return -1; + + nat64_compose_ip6 (&ip6_saddr, &ip4->src_address, bibe->fib_index); + ste = + nat64_db_st_entry_create (&nm->db, bibe, &ip6_saddr, &saddr.ip4, + 0); + } + + nat64_session_reset_timeout (ste, ctx->vm); + + ip6->src_address.as_u64[0] = ste->in_r_addr.as_u64[0]; + ip6->src_address.as_u64[1] = ste->in_r_addr.as_u64[1]; + + ip6->dst_address.as_u64[0] = bibe->in_addr.as_u64[0]; + ip6->dst_address.as_u64[1] = bibe->in_addr.as_u64[1]; + ((u16 *) (icmp))[2] = bibe->in_port; + + vnet_buffer (ctx->b)->sw_if_index[VLIB_TX] = bibe->fib_index; + } + else + { + ip6_header_t *inner_ip6 = (ip6_header_t *) u8_ptr_add (icmp, 8); + + nat64_compose_ip6 (&ip6->src_address, &ip4->src_address, + vnet_buffer (ctx->b)->sw_if_index[VLIB_TX]); + ip6->dst_address.as_u64[0] = inner_ip6->src_address.as_u64[0]; + ip6->dst_address.as_u64[1] = inner_ip6->src_address.as_u64[1]; + } + + return 0; +} + +static int +nat64_out2in_inner_icmp_set_cb (ip4_header_t * ip4, ip6_header_t * ip6, + void *arg) +{ + nat64_main_t *nm = &nat64_main; + nat64_out2in_set_ctx_t *ctx = arg; + nat64_db_bib_entry_t *bibe; + nat64_db_st_entry_t *ste; + ip46_address_t saddr, daddr; + u32 sw_if_index, fib_index; + u8 proto = ip4->protocol; + + sw_if_index = vnet_buffer (ctx->b)->sw_if_index[VLIB_RX]; + fib_index = + fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6, sw_if_index); + + memset (&saddr, 0, sizeof (saddr)); + saddr.ip4.as_u32 = ip4->src_address.as_u32; + memset (&daddr, 0, sizeof (daddr)); + daddr.ip4.as_u32 = ip4->dst_address.as_u32; + + if (proto == IP_PROTOCOL_ICMP6) + { + icmp46_header_t *icmp = ip4_next_header (ip4); + u16 out_id = ((u16 *) (icmp))[2]; + proto = IP_PROTOCOL_ICMP; + + if (! + (icmp->type == ICMP6_echo_request + || icmp->type == ICMP6_echo_reply)) + return -1; + + ste = + nat64_db_st_entry_find (&nm->db, &saddr, &daddr, out_id, 0, proto, + fib_index, 0); + if (!ste) + return -1; + + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + + ip6->dst_address.as_u64[0] = ste->in_r_addr.as_u64[0]; + ip6->dst_address.as_u64[1] = ste->in_r_addr.as_u64[1]; + ip6->src_address.as_u64[0] = bibe->in_addr.as_u64[0]; + ip6->src_address.as_u64[1] = bibe->in_addr.as_u64[1]; + ((u16 *) (icmp))[2] = bibe->in_port; + + vnet_buffer (ctx->b)->sw_if_index[VLIB_TX] = bibe->fib_index; + } + else + { + udp_header_t *udp = ip4_next_header (ip4); + tcp_header_t *tcp = ip4_next_header (ip4); + u16 dport = udp->dst_port; + u16 sport = udp->src_port; + u16 *checksum; + ip_csum_t csum; + + ste = + nat64_db_st_entry_find (&nm->db, &saddr, &daddr, sport, dport, proto, + fib_index, 0); + if (!ste) + return -1; + + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + + nat64_compose_ip6 (&ip6->dst_address, &daddr.ip4, bibe->fib_index); + ip6->src_address.as_u64[0] = bibe->in_addr.as_u64[0]; + ip6->src_address.as_u64[1] = bibe->in_addr.as_u64[1]; + udp->src_port = bibe->in_port; + + if (proto == IP_PROTOCOL_UDP) + checksum = &udp->checksum; + else + checksum = &tcp->checksum; + if (*checksum) + { + csum = ip_csum_sub_even (*checksum, sport); + csum = ip_csum_add_even (csum, udp->src_port); + *checksum = ip_csum_fold (csum); + } + + vnet_buffer (ctx->b)->sw_if_index[VLIB_TX] = bibe->fib_index; + } + + return 0; +} + +static int +nat64_out2in_unk_proto_set_cb (ip4_header_t * ip4, ip6_header_t * ip6, + void *arg) +{ + nat64_main_t *nm = &nat64_main; + nat64_out2in_set_ctx_t *ctx = arg; + nat64_db_bib_entry_t *bibe; + nat64_db_st_entry_t *ste; + ip46_address_t saddr, daddr; + ip6_address_t ip6_saddr; + u32 sw_if_index, fib_index; + u8 proto = ip4->protocol; + + sw_if_index = vnet_buffer (ctx->b)->sw_if_index[VLIB_RX]; + fib_index = ip4_fib_table_get_index_for_sw_if_index (sw_if_index); + + memset (&saddr, 0, sizeof (saddr)); + saddr.ip4.as_u32 = ip4->src_address.as_u32; + memset (&daddr, 0, sizeof (daddr)); + daddr.ip4.as_u32 = ip4->dst_address.as_u32; + + ste = + nat64_db_st_entry_find (&nm->db, &daddr, &saddr, 0, 0, proto, fib_index, + 0); + if (ste) + { + bibe = nat64_db_bib_entry_by_index (&nm->db, proto, ste->bibe_index); + if (!bibe) + return -1; + } + else + { + bibe = + nat64_db_bib_entry_find (&nm->db, &daddr, 0, proto, fib_index, 0); + + if (!bibe) + return -1; + + nat64_compose_ip6 (&ip6_saddr, &ip4->src_address, bibe->fib_index); + ste = + nat64_db_st_entry_create (&nm->db, bibe, &ip6_saddr, &saddr.ip4, 0); + } + + nat64_session_reset_timeout (ste, ctx->vm); + + ip6->src_address.as_u64[0] = ste->in_r_addr.as_u64[0]; + ip6->src_address.as_u64[1] = ste->in_r_addr.as_u64[1]; + + ip6->dst_address.as_u64[0] = bibe->in_addr.as_u64[0]; + ip6->dst_address.as_u64[1] = bibe->in_addr.as_u64[1]; + + vnet_buffer (ctx->b)->sw_if_index[VLIB_TX] = bibe->fib_index; + + return 0; +} + +static uword +nat64_out2in_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + u32 n_left_from, *from, *to_next; + nat64_out2in_next_t next_index; + u32 pkts_processed = 0; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t *b0; + u32 next0; + ip4_header_t *ip40; + u32 proto0; + nat64_out2in_set_ctx_t ctx0; + + /* speculatively enqueue b0 to the current next frame */ + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + ip40 = vlib_buffer_get_current (b0); + + ctx0.b = b0; + ctx0.vm = vm; + + next0 = NAT64_OUT2IN_NEXT_LOOKUP; + + proto0 = ip_proto_to_snat_proto (ip40->protocol); + + if (proto0 == SNAT_PROTOCOL_ICMP) + { + if (icmp_to_icmp6 + (b0, nat64_out2in_icmp_set_cb, &ctx0, + nat64_out2in_inner_icmp_set_cb, &ctx0)) + { + next0 = NAT64_OUT2IN_NEXT_DROP; + b0->error = node->errors[NAT64_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace0; + } + } + else if (proto0 == SNAT_PROTOCOL_TCP || proto0 == SNAT_PROTOCOL_UDP) + { + if (ip4_to_ip6_tcp_udp (b0, nat64_out2in_tcp_udp_set_cb, &ctx0)) + { + next0 = NAT64_OUT2IN_NEXT_DROP; + b0->error = node->errors[NAT64_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace0; + } + } + else + { + if (ip4_to_ip6 (b0, nat64_out2in_unk_proto_set_cb, &ctx0)) + { + next0 = NAT64_OUT2IN_NEXT_DROP; + b0->error = node->errors[NAT64_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace0; + } + } + + trace0: + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + nat64_out2in_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + t->next_index = next0; + } + + pkts_processed += next0 != NAT64_OUT2IN_NEXT_DROP; + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next, + n_left_to_next, bi0, next0); + } + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + vlib_node_increment_counter (vm, nat64_out2in_node.index, + NAT64_OUT2IN_ERROR_OUT2IN_PACKETS, + pkts_processed); + return frame->n_vectors; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (nat64_out2in_node) = { + .function = nat64_out2in_node_fn, + .name = "nat64-out2in", + .vector_size = sizeof (u32), + .format_trace = format_nat64_out2in_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + .n_errors = ARRAY_LEN (nat64_out2in_error_strings), + .error_strings = nat64_out2in_error_strings,.n_next_nodes = 2, + /* edit / add dispositions here */ + .next_nodes = { + [NAT64_OUT2IN_NEXT_DROP] = "error-drop", + [NAT64_OUT2IN_NEXT_LOOKUP] = "ip6-lookup", + }, +}; +/* *INDENT-ON* */ + +VLIB_NODE_FUNCTION_MULTIARCH (nat64_out2in_node, nat64_out2in_node_fn); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/nat/nat_all_api_h.h b/src/plugins/nat/nat_all_api_h.h new file mode 100644 index 00000000000..acd9ba1cac9 --- /dev/null +++ b/src/plugins/nat/nat_all_api_h.h @@ -0,0 +1,19 @@ + +/* + * nat_all_api_h.h - skeleton vpp engine plug-in api #include file + * + * Copyright (c) <current-year> <your-organization> + * 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 the generated file, see BUILT_SOURCES in Makefile.am */ +#include <nat/nat.api.h> diff --git a/src/plugins/nat/nat_api.c b/src/plugins/nat/nat_api.c new file mode 100644 index 00000000000..0a2141f2a05 --- /dev/null +++ b/src/plugins/nat/nat_api.c @@ -0,0 +1,3255 @@ +/* + * Copyright (c) 2017 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. + */ +/** + * @file + * @brief NAT plugin API implementation + */ + +#include <nat/nat.h> +#include <nat/nat_det.h> +#include <nat/nat64.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <vlibsocket/api.h> +#include <nat/nat_msg_enum.h> +#include <vnet/fib/fib_table.h> + +/* define message structures */ +#define vl_typedefs +#include <nat/nat_all_api_h.h> +#undef vl_typedefs + +/* define generated endian-swappers */ +#define vl_endianfun +#include <nat/nat_all_api_h.h> +#undef vl_endianfun + +#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__) + +#define REPLY_MSG_ID_BASE sm->msg_id_base +#include <vlibapi/api_helper_macros.h> + +/* Get the API version number */ +#define vl_api_version(n,v) static u32 api_version=(v); +#include <nat/nat_all_api_h.h> +#undef vl_api_version + +/* Macro to finish up custom dump fns */ +#define FINISH \ + vec_add1 (s, 0); \ + vl_print (handle, (char *)s); \ + vec_free (s); \ + return handle; + +static void + vl_api_snat_add_address_range_t_handler + (vl_api_snat_add_address_range_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_add_address_range_reply_t *rmp; + ip4_address_t this_addr; + u32 start_host_order, end_host_order; + u32 vrf_id; + int i, count; + int rv = 0; + u32 *tmp; + + if (mp->is_ip4 != 1) + { + rv = VNET_API_ERROR_UNIMPLEMENTED; + goto send_reply; + } + + if (sm->static_mapping_only) + { + rv = VNET_API_ERROR_FEATURE_DISABLED; + goto send_reply; + } + + tmp = (u32 *) mp->first_ip_address; + start_host_order = clib_host_to_net_u32 (tmp[0]); + tmp = (u32 *) mp->last_ip_address; + end_host_order = clib_host_to_net_u32 (tmp[0]); + + count = (end_host_order - start_host_order) + 1; + + vrf_id = clib_host_to_net_u32 (mp->vrf_id); + + if (count > 1024) + clib_warning ("%U - %U, %d addresses...", + format_ip4_address, mp->first_ip_address, + format_ip4_address, mp->last_ip_address, count); + + memcpy (&this_addr.as_u8, mp->first_ip_address, 4); + + for (i = 0; i < count; i++) + { + if (mp->is_add) + snat_add_address (sm, &this_addr, vrf_id); + else + rv = snat_del_address (sm, this_addr, 0); + + if (rv) + goto send_reply; + + increment_v4_address (&this_addr); + } + +send_reply: + REPLY_MACRO (VL_API_SNAT_ADD_ADDRESS_RANGE_REPLY); +} + +static void *vl_api_snat_add_address_range_t_print + (vl_api_snat_add_address_range_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_add_address_range "); + s = format (s, "%U ", format_ip4_address, mp->first_ip_address); + if (memcmp (mp->first_ip_address, mp->last_ip_address, 4)) + { + s = format (s, " - %U ", format_ip4_address, mp->last_ip_address); + } + FINISH; +} + +static void + send_snat_address_details + (snat_address_t * a, unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_snat_address_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_SNAT_ADDRESS_DETAILS + sm->msg_id_base); + rmp->is_ip4 = 1; + clib_memcpy (rmp->ip_address, &(a->addr), 4); + if (a->fib_index != ~0) + { + fib_table_t *fib = fib_table_get (a->fib_index, FIB_PROTOCOL_IP4); + rmp->vrf_id = ntohl (fib->ft_table_id); + } + else + rmp->vrf_id = ~0; + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_snat_address_dump_t_handler (vl_api_snat_address_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_address_t *a; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + vec_foreach (a, sm->addresses) + send_snat_address_details (a, q, mp->context); + /* *INDENT-ON* */ +} + +static void *vl_api_snat_address_dump_t_print + (vl_api_snat_address_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_address_dump "); + + FINISH; +} + +static void + vl_api_snat_interface_add_del_feature_t_handler + (vl_api_snat_interface_add_del_feature_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_interface_add_del_feature_reply_t *rmp; + u8 is_del = mp->is_add == 0; + u32 sw_if_index = ntohl (mp->sw_if_index); + int rv = 0; + + VALIDATE_SW_IF_INDEX (mp); + + rv = snat_interface_add_del (sw_if_index, mp->is_inside, is_del); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (VL_API_SNAT_INTERFACE_ADD_DEL_FEATURE_REPLY); +} + +static void *vl_api_snat_interface_add_del_feature_t_print + (vl_api_snat_interface_add_del_feature_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_interface_add_del_feature "); + s = format (s, "sw_if_index %d %s %s", + clib_host_to_net_u32 (mp->sw_if_index), + mp->is_inside ? "in" : "out", mp->is_add ? "" : "del"); + + FINISH; +} + +static void + send_snat_interface_details + (snat_interface_t * i, unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_snat_interface_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_SNAT_INTERFACE_DETAILS + sm->msg_id_base); + rmp->sw_if_index = ntohl (i->sw_if_index); + rmp->is_inside = i->is_inside; + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_snat_interface_dump_t_handler (vl_api_snat_interface_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_interface_t *i; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + pool_foreach (i, sm->interfaces, + ({ + send_snat_interface_details(i, q, mp->context); + })); + /* *INDENT-ON* */ +} + +static void *vl_api_snat_interface_dump_t_print + (vl_api_snat_interface_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_interface_dump "); + + FINISH; +} + +static void + vl_api_snat_interface_add_del_output_feature_t_handler + (vl_api_snat_interface_add_del_output_feature_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_interface_add_del_output_feature_reply_t *rmp; + u8 is_del = mp->is_add == 0; + u32 sw_if_index = ntohl (mp->sw_if_index); + int rv = 0; + + VALIDATE_SW_IF_INDEX (mp); + + rv = snat_interface_add_del_output_feature (sw_if_index, mp->is_inside, + is_del); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (VL_API_SNAT_INTERFACE_ADD_DEL_OUTPUT_FEATURE_REPLY); +} + +static void *vl_api_snat_interface_add_del_output_feature_t_print + (vl_api_snat_interface_add_del_output_feature_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_interface_add_del_output_feature "); + s = format (s, "sw_if_index %d %s %s", + clib_host_to_net_u32 (mp->sw_if_index), + mp->is_inside ? "in" : "out", mp->is_add ? "" : "del"); + + FINISH; +} + +static void +send_snat_interface_output_feature_details (snat_interface_t * i, + unix_shared_memory_queue_t * q, + u32 context) +{ + vl_api_snat_interface_output_feature_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = + ntohs (VL_API_SNAT_INTERFACE_OUTPUT_FEATURE_DETAILS + sm->msg_id_base); + rmp->sw_if_index = ntohl (i->sw_if_index); + rmp->context = context; + rmp->is_inside = i->is_inside; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void + vl_api_snat_interface_output_feature_dump_t_handler + (vl_api_snat_interface_output_feature_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_interface_t *i; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + pool_foreach (i, sm->output_feature_interfaces, + ({ + send_snat_interface_output_feature_details(i, q, mp->context); + })); + /* *INDENT-ON* */ +} + +static void *vl_api_snat_interface_output_feature_dump_t_print + (vl_api_snat_interface_output_feature_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_interface_output_feature_dump "); + + FINISH; +} + +static void + vl_api_snat_add_static_mapping_t_handler + (vl_api_snat_add_static_mapping_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_add_static_mapping_reply_t *rmp; + ip4_address_t local_addr, external_addr; + u16 local_port = 0, external_port = 0; + u32 vrf_id, external_sw_if_index; + int rv = 0; + snat_protocol_t proto; + + if (mp->is_ip4 != 1) + { + rv = VNET_API_ERROR_UNIMPLEMENTED; + goto send_reply; + } + + memcpy (&local_addr.as_u8, mp->local_ip_address, 4); + memcpy (&external_addr.as_u8, mp->external_ip_address, 4); + if (mp->addr_only == 0) + { + local_port = clib_net_to_host_u16 (mp->local_port); + external_port = clib_net_to_host_u16 (mp->external_port); + } + vrf_id = clib_net_to_host_u32 (mp->vrf_id); + external_sw_if_index = clib_net_to_host_u32 (mp->external_sw_if_index); + proto = ip_proto_to_snat_proto (mp->protocol); + + rv = snat_add_static_mapping (local_addr, external_addr, local_port, + external_port, vrf_id, mp->addr_only, + external_sw_if_index, proto, mp->is_add); + +send_reply: + REPLY_MACRO (VL_API_SNAT_ADD_ADDRESS_RANGE_REPLY); +} + +static void *vl_api_snat_add_static_mapping_t_print + (vl_api_snat_add_static_mapping_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_add_static_mapping "); + s = format (s, "protocol %d local_addr %U external_addr %U ", + mp->protocol, + format_ip4_address, mp->local_ip_address, + format_ip4_address, mp->external_ip_address); + + if (mp->addr_only == 0) + s = format (s, "local_port %d external_port %d ", + clib_net_to_host_u16 (mp->local_port), + clib_net_to_host_u16 (mp->external_port)); + + if (mp->vrf_id != ~0) + s = format (s, "vrf %d", clib_net_to_host_u32 (mp->vrf_id)); + + if (mp->external_sw_if_index != ~0) + s = format (s, "external_sw_if_index %d", + clib_net_to_host_u32 (mp->external_sw_if_index)); + FINISH; +} + +static void + send_snat_static_mapping_details + (snat_static_mapping_t * m, unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_snat_static_mapping_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = + ntohs (VL_API_SNAT_STATIC_MAPPING_DETAILS + sm->msg_id_base); + rmp->is_ip4 = 1; + rmp->addr_only = m->addr_only; + clib_memcpy (rmp->local_ip_address, &(m->local_addr), 4); + clib_memcpy (rmp->external_ip_address, &(m->external_addr), 4); + rmp->local_port = htons (m->local_port); + rmp->external_port = htons (m->external_port); + rmp->external_sw_if_index = ~0; + rmp->vrf_id = htonl (m->vrf_id); + rmp->protocol = snat_proto_to_ip_proto (m->proto); + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void + send_snat_static_map_resolve_details + (snat_static_map_resolve_t * m, unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_snat_static_mapping_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = + ntohs (VL_API_SNAT_STATIC_MAPPING_DETAILS + sm->msg_id_base); + rmp->is_ip4 = 1; + rmp->addr_only = m->addr_only; + clib_memcpy (rmp->local_ip_address, &(m->l_addr), 4); + rmp->local_port = htons (m->l_port); + rmp->external_port = htons (m->e_port); + rmp->external_sw_if_index = htonl (m->sw_if_index); + rmp->vrf_id = htonl (m->vrf_id); + rmp->protocol = snat_proto_to_ip_proto (m->proto); + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void + vl_api_snat_static_mapping_dump_t_handler + (vl_api_snat_static_mapping_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_static_mapping_t *m; + snat_static_map_resolve_t *rp; + int j; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + pool_foreach (m, sm->static_mappings, + ({ + send_snat_static_mapping_details (m, q, mp->context); + })); + /* *INDENT-ON* */ + + for (j = 0; j < vec_len (sm->to_resolve); j++) + { + rp = sm->to_resolve + j; + send_snat_static_map_resolve_details (rp, q, mp->context); + } +} + +static void *vl_api_snat_static_mapping_dump_t_print + (vl_api_snat_static_mapping_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_static_mapping_dump "); + + FINISH; +} + +static void +vl_api_snat_control_ping_t_handler (vl_api_snat_control_ping_t * mp) +{ + vl_api_snat_control_ping_reply_t *rmp; + snat_main_t *sm = &snat_main; + int rv = 0; + + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_SNAT_CONTROL_PING_REPLY, + ({ + rmp->vpe_pid = ntohl (getpid ()); + })); + /* *INDENT-ON* */ +} + +static void *vl_api_snat_control_ping_t_print + (vl_api_snat_control_ping_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_control_ping "); + + FINISH; +} + +static void +vl_api_snat_show_config_t_handler (vl_api_snat_show_config_t * mp) +{ + vl_api_snat_show_config_reply_t *rmp; + snat_main_t *sm = &snat_main; + int rv = 0; + + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_SNAT_SHOW_CONFIG_REPLY, + ({ + rmp->translation_buckets = htonl (sm->translation_buckets); + rmp->translation_memory_size = htonl (sm->translation_memory_size); + rmp->user_buckets = htonl (sm->user_buckets); + rmp->user_memory_size = htonl (sm->user_memory_size); + rmp->max_translations_per_user = htonl (sm->max_translations_per_user); + rmp->outside_vrf_id = htonl (sm->outside_vrf_id); + rmp->inside_vrf_id = htonl (sm->inside_vrf_id); + rmp->static_mapping_only = sm->static_mapping_only; + rmp->static_mapping_connection_tracking = + sm->static_mapping_connection_tracking; + rmp->deterministic = sm->deterministic; + })); + /* *INDENT-ON* */ +} + +static void *vl_api_snat_show_config_t_print + (vl_api_snat_show_config_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_show_config "); + + FINISH; +} + +static void +vl_api_snat_set_workers_t_handler (vl_api_snat_set_workers_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_set_workers_reply_t *rmp; + int rv = 0; + uword *bitmap = 0; + u64 mask = clib_net_to_host_u64 (mp->worker_mask); + + if (sm->num_workers < 2) + { + rv = VNET_API_ERROR_FEATURE_DISABLED; + goto send_reply; + } + + bitmap = clib_bitmap_set_multiple (bitmap, 0, mask, BITS (mask)); + rv = snat_set_workers (bitmap); + clib_bitmap_free (bitmap); + +send_reply: + REPLY_MACRO (VL_API_SNAT_SET_WORKERS_REPLY); +} + +static void *vl_api_snat_set_workers_t_print + (vl_api_snat_set_workers_t * mp, void *handle) +{ + u8 *s; + uword *bitmap = 0; + u8 first = 1; + int i; + u64 mask = clib_net_to_host_u64 (mp->worker_mask); + + s = format (0, "SCRIPT: snat_set_workers "); + bitmap = clib_bitmap_set_multiple (bitmap, 0, mask, BITS (mask)); + /* *INDENT-OFF* */ + clib_bitmap_foreach (i, bitmap, + ({ + if (first) + s = format (s, "%d", i); + else + s = format (s, ",%d", i); + first = 0; + })); + /* *INDENT-ON* */ + clib_bitmap_free (bitmap); + FINISH; +} + +static void + send_snat_worker_details + (u32 worker_index, unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_snat_worker_details_t *rmp; + snat_main_t *sm = &snat_main; + vlib_worker_thread_t *w = + vlib_worker_threads + worker_index + sm->first_worker_index; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_SNAT_WORKER_DETAILS + sm->msg_id_base); + rmp->context = context; + rmp->worker_index = htonl (worker_index); + rmp->lcore_id = htonl (w->lcore_id); + strncpy ((char *) rmp->name, (char *) w->name, ARRAY_LEN (rmp->name) - 1); + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_snat_worker_dump_t_handler (vl_api_snat_worker_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + u32 *worker_index; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + vec_foreach (worker_index, sm->workers) + send_snat_worker_details(*worker_index, q, mp->context); + /* *INDENT-ON* */ +} + +static void *vl_api_snat_worker_dump_t_print + (vl_api_snat_worker_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_worker_dump "); + + FINISH; +} + +static void + vl_api_snat_add_del_interface_addr_t_handler + (vl_api_snat_add_del_interface_addr_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_add_del_interface_addr_reply_t *rmp; + u8 is_del = mp->is_add == 0; + u32 sw_if_index = ntohl (mp->sw_if_index); + int rv = 0; + + VALIDATE_SW_IF_INDEX (mp); + + rv = snat_add_interface_address (sm, sw_if_index, is_del); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (VL_API_SNAT_ADD_DEL_INTERFACE_ADDR_REPLY); +} + +static void *vl_api_snat_add_del_interface_addr_t_print + (vl_api_snat_add_del_interface_addr_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_add_del_interface_addr "); + s = format (s, "sw_if_index %d %s", + clib_host_to_net_u32 (mp->sw_if_index), + mp->is_add ? "" : "del"); + + FINISH; +} + +static void + send_snat_interface_addr_details + (u32 sw_if_index, unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_snat_interface_addr_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = + ntohs (VL_API_SNAT_INTERFACE_ADDR_DETAILS + sm->msg_id_base); + rmp->sw_if_index = ntohl (sw_if_index); + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void + vl_api_snat_interface_addr_dump_t_handler + (vl_api_snat_interface_addr_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + u32 *i; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + vec_foreach (i, sm->auto_add_sw_if_indices) + send_snat_interface_addr_details(*i, q, mp->context); + /* *INDENT-ON* */ +} + +static void *vl_api_snat_interface_addr_dump_t_print + (vl_api_snat_interface_addr_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_interface_addr_dump "); + + FINISH; +} + +static void + vl_api_snat_ipfix_enable_disable_t_handler + (vl_api_snat_ipfix_enable_disable_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_ipfix_enable_disable_reply_t *rmp; + int rv = 0; + + rv = snat_ipfix_logging_enable_disable (mp->enable, + clib_host_to_net_u32 + (mp->domain_id), + clib_host_to_net_u16 + (mp->src_port)); + + REPLY_MACRO (VL_API_SNAT_IPFIX_ENABLE_DISABLE_REPLY); +} + +static void *vl_api_snat_ipfix_enable_disable_t_print + (vl_api_snat_ipfix_enable_disable_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_ipfix_enable_disable "); + if (mp->domain_id) + s = format (s, "domain %d ", clib_net_to_host_u32 (mp->domain_id)); + if (mp->src_port) + s = format (s, "src_port %d ", clib_net_to_host_u16 (mp->src_port)); + if (!mp->enable) + s = format (s, "disable "); + + FINISH; +} + +static void + send_snat_user_details + (snat_user_t * u, unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_snat_user_details_t *rmp; + snat_main_t *sm = &snat_main; + fib_table_t *fib = fib_table_get (u->fib_index, FIB_PROTOCOL_IP4); + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_SNAT_USER_DETAILS + sm->msg_id_base); + + rmp->vrf_id = ntohl (fib->ft_table_id); + + rmp->is_ip4 = 1; + clib_memcpy (rmp->ip_address, &(u->addr), 4); + rmp->nsessions = ntohl (u->nsessions); + rmp->nstaticsessions = ntohl (u->nstaticsessions); + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_snat_user_dump_t_handler (vl_api_snat_user_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_main_per_thread_data_t *tsm; + snat_user_t *u; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + vec_foreach (tsm, sm->per_thread_data) + vec_foreach (u, tsm->users) + send_snat_user_details (u, q, mp->context); + /* *INDENT-ON* */ +} + +static void *vl_api_snat_user_dump_t_print + (vl_api_snat_user_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_user_dump "); + + FINISH; +} + +static void + send_snat_user_session_details + (snat_session_t * s, unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_snat_user_session_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = + ntohs (VL_API_SNAT_USER_SESSION_DETAILS + sm->msg_id_base); + rmp->is_ip4 = 1; + clib_memcpy (rmp->outside_ip_address, (&s->out2in.addr), 4); + clib_memcpy (rmp->inside_ip_address, (&s->in2out.addr), 4); + rmp->is_static = s->flags & SNAT_SESSION_FLAG_STATIC_MAPPING ? 1 : 0; + rmp->last_heard = clib_host_to_net_u64 ((u64) s->last_heard); + rmp->total_bytes = clib_host_to_net_u64 (s->total_bytes); + rmp->total_pkts = ntohl (s->total_pkts); + rmp->context = context; + if (snat_is_unk_proto_session (s)) + { + rmp->outside_port = 0; + rmp->inside_port = 0; + rmp->protocol = ntohs (s->in2out.port); + } + else + { + rmp->outside_port = s->out2in.port; + rmp->inside_port = s->in2out.port; + rmp->protocol = ntohs (snat_proto_to_ip_proto (s->in2out.protocol)); + } + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void + vl_api_snat_user_session_dump_t_handler + (vl_api_snat_user_session_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_main_per_thread_data_t *tsm; + snat_session_t *s; + clib_bihash_kv_8_8_t key, value; + snat_user_key_t ukey; + snat_user_t *u; + u32 session_index, head_index, elt_index; + dlist_elt_t *head, *elt; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + if (!mp->is_ip4) + return; + + clib_memcpy (&ukey.addr, mp->ip_address, 4); + ukey.fib_index = fib_table_find (FIB_PROTOCOL_IP4, ntohl (mp->vrf_id)); + key.key = ukey.as_u64; + if (!clib_bihash_search_8_8 (&sm->worker_by_in, &key, &value)) + tsm = vec_elt_at_index (sm->per_thread_data, value.value); + else + tsm = vec_elt_at_index (sm->per_thread_data, sm->num_workers); + if (clib_bihash_search_8_8 (&sm->user_hash, &key, &value)) + return; + u = pool_elt_at_index (tsm->users, value.value); + if (!u->nsessions && !u->nstaticsessions) + return; + + head_index = u->sessions_per_user_list_head_index; + head = pool_elt_at_index (tsm->list_pool, head_index); + elt_index = head->next; + elt = pool_elt_at_index (tsm->list_pool, elt_index); + session_index = elt->value; + while (session_index != ~0) + { + s = pool_elt_at_index (tsm->sessions, session_index); + + send_snat_user_session_details (s, q, mp->context); + + elt_index = elt->next; + elt = pool_elt_at_index (tsm->list_pool, elt_index); + session_index = elt->value; + } +} + +static void *vl_api_snat_user_session_dump_t_print + (vl_api_snat_user_session_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_user_session_dump "); + s = format (s, "ip_address %U vrf_id %d\n", + format_ip4_address, mp->ip_address, + clib_net_to_host_u32 (mp->vrf_id)); + + FINISH; +} + +/******************************************************************/ +/*** detrministic NAT/CGN (old, will be deprecated after 17.10) ***/ +/******************************************************************/ + +static void +vl_api_snat_add_det_map_t_handler (vl_api_snat_add_det_map_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_add_det_map_reply_t *rmp; + int rv = 0; + ip4_address_t in_addr, out_addr; + + clib_memcpy (&in_addr, mp->in_addr, 4); + clib_memcpy (&out_addr, mp->out_addr, 4); + rv = snat_det_add_map (sm, &in_addr, mp->in_plen, &out_addr, + mp->out_plen, mp->is_add); + + REPLY_MACRO (VL_API_SNAT_ADD_DET_MAP_REPLY); +} + +static void *vl_api_snat_add_det_map_t_print + (vl_api_snat_add_det_map_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_add_det_map "); + s = format (s, "inside address %U/%d outside address %U/%d\n", + format_ip4_address, mp->in_addr, mp->in_plen, + format_ip4_address, mp->out_addr, mp->out_plen); + + FINISH; +} + +static void +vl_api_snat_det_forward_t_handler (vl_api_snat_det_forward_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_det_forward_reply_t *rmp; + int rv = 0; + u16 lo_port = 0, hi_port = 0; + snat_det_map_t *dm; + ip4_address_t in_addr, out_addr; + + out_addr.as_u32 = 0; + clib_memcpy (&in_addr, mp->in_addr, 4); + dm = snat_det_map_by_user (sm, &in_addr); + if (!dm) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + goto send_reply; + } + + snat_det_forward (dm, &in_addr, &out_addr, &lo_port); + hi_port = lo_port + dm->ports_per_host - 1; + +send_reply: + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_SNAT_DET_FORWARD_REPLY, + ({ + rmp->out_port_lo = ntohs (lo_port); + rmp->out_port_hi = ntohs (hi_port); + rmp->is_ip4 = 1; + memset (rmp->out_addr, 0, 16); + clib_memcpy (rmp->out_addr, &out_addr, 4); + })) + /* *INDENT-ON* */ +} + +static void *vl_api_snat_det_forward_t_print + (vl_api_snat_det_forward_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: smat_det_forward_t"); + s = format (s, "inside ip address %U\n", format_ip4_address, mp->in_addr); + + FINISH; +} + +static void +vl_api_snat_det_reverse_t_handler (vl_api_snat_det_reverse_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_det_reverse_reply_t *rmp; + int rv = 0; + ip4_address_t out_addr, in_addr; + snat_det_map_t *dm; + + in_addr.as_u32 = 0; + clib_memcpy (&out_addr, mp->out_addr, 4); + dm = snat_det_map_by_out (sm, &out_addr); + if (!dm) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + goto send_reply; + } + + snat_det_reverse (dm, &out_addr, htons (mp->out_port), &in_addr); + +send_reply: + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_SNAT_DET_REVERSE_REPLY, + ({ + rmp->is_ip4 = 1; + memset (rmp->in_addr, 0, 16); + clib_memcpy (rmp->in_addr, &in_addr, 4); + })) + /* *INDENT-ON* */ +} + +static void *vl_api_snat_det_reverse_t_print + (vl_api_snat_det_reverse_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: smat_det_reverse_t"); + s = format (s, "outside ip address %U outside port %d", + format_ip4_address, mp->out_addr, ntohs (mp->out_port)); + + FINISH; +} + +static void + sent_snat_det_map_details + (snat_det_map_t * m, unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_snat_det_map_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_SNAT_DET_MAP_DETAILS + sm->msg_id_base); + rmp->is_ip4 = 1; + clib_memcpy (rmp->in_addr, &m->in_addr, 4); + rmp->in_plen = m->in_plen; + clib_memcpy (rmp->out_addr, &m->out_addr, 4); + rmp->out_plen = m->out_plen; + rmp->sharing_ratio = htonl (m->sharing_ratio); + rmp->ports_per_host = htons (m->ports_per_host); + rmp->ses_num = htonl (m->ses_num); + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_snat_det_map_dump_t_handler (vl_api_snat_det_map_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_det_map_t *m; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + vec_foreach(m, sm->det_maps) + sent_snat_det_map_details(m, q, mp->context); + /* *INDENT-ON* */ +} + +static void *vl_api_snat_det_map_dump_t_print + (vl_api_snat_det_map_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_det_map_dump "); + + FINISH; +} + +static void +vl_api_snat_det_set_timeouts_t_handler (vl_api_snat_det_set_timeouts_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_det_set_timeouts_reply_t *rmp; + int rv = 0; + + sm->udp_timeout = ntohl (mp->udp); + sm->tcp_established_timeout = ntohl (mp->tcp_established); + sm->tcp_transitory_timeout = ntohl (mp->tcp_transitory); + sm->icmp_timeout = ntohl (mp->icmp); + + REPLY_MACRO (VL_API_SNAT_DET_SET_TIMEOUTS_REPLY); +} + +static void *vl_api_snat_det_set_timeouts_t_print + (vl_api_snat_det_set_timeouts_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_det_set_timeouts "); + s = format (s, "udp %d tcp_established %d tcp_transitory %d icmp %d\n", + ntohl (mp->udp), + ntohl (mp->tcp_established), + ntohl (mp->tcp_transitory), ntohl (mp->icmp)); + + FINISH; +} + +static void +vl_api_snat_det_get_timeouts_t_handler (vl_api_snat_det_get_timeouts_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_det_get_timeouts_reply_t *rmp; + int rv = 0; + + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_SNAT_DET_GET_TIMEOUTS_REPLY, + ({ + rmp->udp = htonl (sm->udp_timeout); + rmp->tcp_established = htonl (sm->tcp_established_timeout); + rmp->tcp_transitory = htonl (sm->tcp_transitory_timeout); + rmp->icmp = htonl (sm->icmp_timeout); + })) + /* *INDENT-ON* */ +} + +static void *vl_api_snat_det_get_timeouts_t_print + (vl_api_snat_det_get_timeouts_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_det_get_timeouts"); + + FINISH; +} + +static void + vl_api_snat_det_close_session_out_t_handler + (vl_api_snat_det_close_session_out_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_det_close_session_out_reply_t *rmp; + ip4_address_t out_addr, ext_addr, in_addr; + snat_det_out_key_t key; + snat_det_map_t *dm; + snat_det_session_t *ses; + int rv = 0; + + clib_memcpy (&out_addr, mp->out_addr, 4); + clib_memcpy (&ext_addr, mp->ext_addr, 4); + + dm = snat_det_map_by_out (sm, &out_addr); + if (!dm) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + goto send_reply; + } + snat_det_reverse (dm, &ext_addr, ntohs (mp->out_port), &in_addr); + key.ext_host_addr = ext_addr; + key.ext_host_port = mp->ext_port; + key.out_port = mp->out_port; + ses = snat_det_get_ses_by_out (dm, &in_addr, key.as_u64); + if (!ses) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + goto send_reply; + } + snat_det_ses_close (dm, ses); + +send_reply: + REPLY_MACRO (VL_API_SNAT_DET_CLOSE_SESSION_OUT_REPLY); +} + +static void *vl_api_snat_det_close_session_out_t_print + (vl_api_snat_det_close_session_out_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_det_close_session_out "); + s = format (s, "out_addr %U out_port %d " + "ext_addr %U ext_port %d\n", + format_ip4_address, mp->out_addr, ntohs (mp->out_port), + format_ip4_address, mp->ext_addr, ntohs (mp->ext_port)); + + FINISH; +} + +static void + vl_api_snat_det_close_session_in_t_handler + (vl_api_snat_det_close_session_in_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_det_close_session_in_reply_t *rmp; + ip4_address_t in_addr, ext_addr; + snat_det_out_key_t key; + snat_det_map_t *dm; + snat_det_session_t *ses; + int rv = 0; + + clib_memcpy (&in_addr, mp->in_addr, 4); + clib_memcpy (&ext_addr, mp->ext_addr, 4); + + dm = snat_det_map_by_user (sm, &in_addr); + if (!dm) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + goto send_reply; + } + key.ext_host_addr = ext_addr; + key.ext_host_port = mp->ext_port; + ses = snat_det_find_ses_by_in (dm, &in_addr, mp->in_port, key); + if (!ses) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + goto send_reply; + } + snat_det_ses_close (dm, ses); + +send_reply: + REPLY_MACRO (VL_API_SNAT_DET_CLOSE_SESSION_OUT_REPLY); +} + +static void *vl_api_snat_det_close_session_in_t_print + (vl_api_snat_det_close_session_in_t * mp, void *handle) +{ + u8 *s; + s = format (0, "SCRIPT: snat_det_close_session_in "); + s = format (s, "in_addr %U in_port %d " + "ext_addr %U ext_port %d\n", + format_ip4_address, mp->in_addr, ntohs (mp->in_port), + format_ip4_address, mp->ext_addr, ntohs (mp->ext_port)); + + FINISH; +} + +static void + send_snat_det_session_details + (snat_det_session_t * s, unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_snat_det_session_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_SNAT_DET_SESSION_DETAILS + sm->msg_id_base); + rmp->is_ip4 = 1; + rmp->in_port = s->in_port; + clib_memcpy (rmp->ext_addr, &s->out.ext_host_addr, 4); + rmp->ext_port = s->out.ext_host_port; + rmp->out_port = s->out.out_port; + rmp->state = s->state; + rmp->expire = ntohl (s->expire); + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_snat_det_session_dump_t_handler (vl_api_snat_det_session_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + ip4_address_t user_addr; + snat_det_map_t *dm; + snat_det_session_t *s, empty_ses; + u16 i; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + if (!mp->is_ip4) + return; + + memset (&empty_ses, 0, sizeof (empty_ses)); + clib_memcpy (&user_addr, mp->user_addr, 4); + dm = snat_det_map_by_user (sm, &user_addr); + if (!dm) + return; + + s = dm->sessions + snat_det_user_ses_offset (&user_addr, dm->in_plen); + for (i = 0; i < SNAT_DET_SES_PER_USER; i++) + { + if (s->out.as_u64) + send_snat_det_session_details (s, q, mp->context); + s++; + } +} + +static void *vl_api_snat_det_session_dump_t_print + (vl_api_snat_det_session_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_det_session_dump "); + s = format (s, "user_addr %U\n", format_ip4_address, mp->user_addr); + + FINISH; +} + +/******************************/ +/*** Common NAT plugin APIs ***/ +/******************************/ + +static void +vl_api_nat_control_ping_t_handler (vl_api_nat_control_ping_t * mp) +{ + vl_api_nat_control_ping_reply_t *rmp; + snat_main_t *sm = &snat_main; + int rv = 0; + + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_NAT_CONTROL_PING_REPLY, + ({ + rmp->vpe_pid = ntohl (getpid ()); + })); + /* *INDENT-ON* */ +} + +static void * +vl_api_nat_control_ping_t_print (vl_api_nat_control_ping_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_control_ping "); + + FINISH; +} + +static void +vl_api_nat_show_config_t_handler (vl_api_nat_show_config_t * mp) +{ + vl_api_nat_show_config_reply_t *rmp; + snat_main_t *sm = &snat_main; + int rv = 0; + + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_NAT_SHOW_CONFIG_REPLY, + ({ + rmp->translation_buckets = htonl (sm->translation_buckets); + rmp->translation_memory_size = htonl (sm->translation_memory_size); + rmp->user_buckets = htonl (sm->user_buckets); + rmp->user_memory_size = htonl (sm->user_memory_size); + rmp->max_translations_per_user = htonl (sm->max_translations_per_user); + rmp->outside_vrf_id = htonl (sm->outside_vrf_id); + rmp->inside_vrf_id = htonl (sm->inside_vrf_id); + rmp->static_mapping_only = sm->static_mapping_only; + rmp->static_mapping_connection_tracking = + sm->static_mapping_connection_tracking; + rmp->deterministic = sm->deterministic; + })); + /* *INDENT-ON* */ +} + +static void * +vl_api_nat_show_config_t_print (vl_api_nat_show_config_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_show_config "); + + FINISH; +} + +static void +vl_api_nat_set_workers_t_handler (vl_api_nat_set_workers_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_snat_set_workers_reply_t *rmp; + int rv = 0; + uword *bitmap = 0; + u64 mask = clib_net_to_host_u64 (mp->worker_mask); + + if (sm->num_workers < 2) + { + rv = VNET_API_ERROR_FEATURE_DISABLED; + goto send_reply; + } + + bitmap = clib_bitmap_set_multiple (bitmap, 0, mask, BITS (mask)); + rv = snat_set_workers (bitmap); + clib_bitmap_free (bitmap); + +send_reply: + REPLY_MACRO (VL_API_NAT_SET_WORKERS_REPLY); +} + +static void * +vl_api_nat_set_workers_t_print (vl_api_nat_set_workers_t * mp, void *handle) +{ + u8 *s; + uword *bitmap = 0; + u8 first = 1; + int i; + u64 mask = clib_net_to_host_u64 (mp->worker_mask); + + s = format (0, "SCRIPT: nat_set_workers "); + bitmap = clib_bitmap_set_multiple (bitmap, 0, mask, BITS (mask)); + /* *INDENT-OFF* */ + clib_bitmap_foreach (i, bitmap, + ({ + if (first) + s = format (s, "%d", i); + else + s = format (s, ",%d", i); + first = 0; + })); + /* *INDENT-ON* */ + clib_bitmap_free (bitmap); + FINISH; +} + +static void +send_nat_worker_details (u32 worker_index, unix_shared_memory_queue_t * q, + u32 context) +{ + vl_api_nat_worker_details_t *rmp; + snat_main_t *sm = &snat_main; + vlib_worker_thread_t *w = + vlib_worker_threads + worker_index + sm->first_worker_index; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_NAT_WORKER_DETAILS + sm->msg_id_base); + rmp->context = context; + rmp->worker_index = htonl (worker_index); + rmp->lcore_id = htonl (w->lcore_id); + strncpy ((char *) rmp->name, (char *) w->name, ARRAY_LEN (rmp->name) - 1); + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_nat_worker_dump_t_handler (vl_api_nat_worker_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + u32 *worker_index; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + vec_foreach (worker_index, sm->workers) + send_nat_worker_details(*worker_index, q, mp->context); + /* *INDENT-ON* */ +} + +static void * +vl_api_nat_worker_dump_t_print (vl_api_nat_worker_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_worker_dump "); + + FINISH; +} + +static void +vl_api_nat_ipfix_enable_disable_t_handler (vl_api_nat_ipfix_enable_disable_t * + mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat_ipfix_enable_disable_reply_t *rmp; + int rv = 0; + + rv = snat_ipfix_logging_enable_disable (mp->enable, + clib_host_to_net_u32 + (mp->domain_id), + clib_host_to_net_u16 + (mp->src_port)); + + REPLY_MACRO (VL_API_NAT_IPFIX_ENABLE_DISABLE_REPLY); +} + +static void * +vl_api_nat_ipfix_enable_disable_t_print (vl_api_nat_ipfix_enable_disable_t * + mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_ipfix_enable_disable "); + if (mp->domain_id) + s = format (s, "domain %d ", clib_net_to_host_u32 (mp->domain_id)); + if (mp->src_port) + s = format (s, "src_port %d ", clib_net_to_host_u16 (mp->src_port)); + if (!mp->enable) + s = format (s, "disable "); + + FINISH; +} + +/*************/ +/*** NAT44 ***/ +/*************/ +static void + vl_api_nat44_add_del_address_range_t_handler + (vl_api_nat44_add_del_address_range_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat44_add_del_address_range_reply_t *rmp; + ip4_address_t this_addr; + u32 start_host_order, end_host_order; + u32 vrf_id; + int i, count; + int rv = 0; + u32 *tmp; + + if (sm->static_mapping_only) + { + rv = VNET_API_ERROR_FEATURE_DISABLED; + goto send_reply; + } + + tmp = (u32 *) mp->first_ip_address; + start_host_order = clib_host_to_net_u32 (tmp[0]); + tmp = (u32 *) mp->last_ip_address; + end_host_order = clib_host_to_net_u32 (tmp[0]); + + count = (end_host_order - start_host_order) + 1; + + vrf_id = clib_host_to_net_u32 (mp->vrf_id); + + if (count > 1024) + clib_warning ("%U - %U, %d addresses...", + format_ip4_address, mp->first_ip_address, + format_ip4_address, mp->last_ip_address, count); + + memcpy (&this_addr.as_u8, mp->first_ip_address, 4); + + for (i = 0; i < count; i++) + { + if (mp->is_add) + snat_add_address (sm, &this_addr, vrf_id); + else + rv = snat_del_address (sm, this_addr, 0); + + if (rv) + goto send_reply; + + increment_v4_address (&this_addr); + } + +send_reply: + REPLY_MACRO (VL_API_NAT44_ADD_DEL_ADDRESS_RANGE_REPLY); +} + +static void *vl_api_nat44_add_del_address_range_t_print + (vl_api_nat44_add_del_address_range_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_add_address_range "); + s = format (s, "%U ", format_ip4_address, mp->first_ip_address); + if (memcmp (mp->first_ip_address, mp->last_ip_address, 4)) + { + s = format (s, " - %U ", format_ip4_address, mp->last_ip_address); + } + FINISH; +} + +static void +send_nat44_address_details (snat_address_t * a, + unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_nat44_address_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_NAT44_ADDRESS_DETAILS + sm->msg_id_base); + clib_memcpy (rmp->ip_address, &(a->addr), 4); + if (a->fib_index != ~0) + { + fib_table_t *fib = fib_table_get (a->fib_index, FIB_PROTOCOL_IP4); + rmp->vrf_id = ntohl (fib->ft_table_id); + } + else + rmp->vrf_id = ~0; + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_nat44_address_dump_t_handler (vl_api_nat44_address_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_address_t *a; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + vec_foreach (a, sm->addresses) + send_nat44_address_details (a, q, mp->context); + /* *INDENT-ON* */ +} + +static void * +vl_api_nat44_address_dump_t_print (vl_api_nat44_address_dump_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_address_dump "); + + FINISH; +} + +static void + vl_api_nat44_interface_add_del_feature_t_handler + (vl_api_nat44_interface_add_del_feature_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat44_interface_add_del_feature_reply_t *rmp; + u8 is_del = mp->is_add == 0; + u32 sw_if_index = ntohl (mp->sw_if_index); + int rv = 0; + + VALIDATE_SW_IF_INDEX (mp); + + rv = snat_interface_add_del (sw_if_index, mp->is_inside, is_del); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (VL_API_NAT44_INTERFACE_ADD_DEL_FEATURE_REPLY); +} + +static void *vl_api_nat44_interface_add_del_feature_t_print + (vl_api_nat44_interface_add_del_feature_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_interface_add_del_feature "); + s = format (s, "sw_if_index %d %s %s", + clib_host_to_net_u32 (mp->sw_if_index), + mp->is_inside ? "in" : "out", mp->is_add ? "" : "del"); + + FINISH; +} + +static void +send_nat44_interface_details (snat_interface_t * i, + unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_nat44_interface_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_NAT44_INTERFACE_DETAILS + sm->msg_id_base); + rmp->sw_if_index = ntohl (i->sw_if_index); + rmp->is_inside = i->is_inside; + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_nat44_interface_dump_t_handler (vl_api_nat44_interface_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_interface_t *i; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + pool_foreach (i, sm->interfaces, + ({ + send_nat44_interface_details(i, q, mp->context); + })); + /* *INDENT-ON* */ +} + +static void * +vl_api_nat44_interface_dump_t_print (vl_api_nat44_interface_dump_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_interface_dump "); + + FINISH; +} + +static void + vl_api_nat44_interface_add_del_output_feature_t_handler + (vl_api_nat44_interface_add_del_output_feature_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat44_interface_add_del_output_feature_reply_t *rmp; + u8 is_del = mp->is_add == 0; + u32 sw_if_index = ntohl (mp->sw_if_index); + int rv = 0; + + VALIDATE_SW_IF_INDEX (mp); + + rv = snat_interface_add_del_output_feature (sw_if_index, mp->is_inside, + is_del); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (VL_API_NAT44_INTERFACE_ADD_DEL_OUTPUT_FEATURE_REPLY); +} + +static void *vl_api_nat44_interface_add_del_output_feature_t_print + (vl_api_nat44_interface_add_del_output_feature_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_interface_add_del_output_feature "); + s = format (s, "sw_if_index %d %s %s", + clib_host_to_net_u32 (mp->sw_if_index), + mp->is_inside ? "in" : "out", mp->is_add ? "" : "del"); + + FINISH; +} + +static void +send_nat44_interface_output_feature_details (snat_interface_t * i, + unix_shared_memory_queue_t * q, + u32 context) +{ + vl_api_nat44_interface_output_feature_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = + ntohs (VL_API_NAT44_INTERFACE_OUTPUT_FEATURE_DETAILS + sm->msg_id_base); + rmp->sw_if_index = ntohl (i->sw_if_index); + rmp->context = context; + rmp->is_inside = i->is_inside; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void + vl_api_nat44_interface_output_feature_dump_t_handler + (vl_api_nat44_interface_output_feature_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_interface_t *i; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + pool_foreach (i, sm->output_feature_interfaces, + ({ + send_nat44_interface_output_feature_details(i, q, mp->context); + })); + /* *INDENT-ON* */ +} + +static void *vl_api_nat44_interface_output_feature_dump_t_print + (vl_api_nat44_interface_output_feature_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_interface_output_feature_dump "); + + FINISH; +} + +static void + vl_api_nat44_add_del_static_mapping_t_handler + (vl_api_nat44_add_del_static_mapping_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat44_add_del_static_mapping_reply_t *rmp; + ip4_address_t local_addr, external_addr; + u16 local_port = 0, external_port = 0; + u32 vrf_id, external_sw_if_index; + int rv = 0; + snat_protocol_t proto; + + memcpy (&local_addr.as_u8, mp->local_ip_address, 4); + memcpy (&external_addr.as_u8, mp->external_ip_address, 4); + if (mp->addr_only == 0) + { + local_port = clib_net_to_host_u16 (mp->local_port); + external_port = clib_net_to_host_u16 (mp->external_port); + } + vrf_id = clib_net_to_host_u32 (mp->vrf_id); + external_sw_if_index = clib_net_to_host_u32 (mp->external_sw_if_index); + proto = ip_proto_to_snat_proto (mp->protocol); + + rv = snat_add_static_mapping (local_addr, external_addr, local_port, + external_port, vrf_id, mp->addr_only, + external_sw_if_index, proto, mp->is_add); + + REPLY_MACRO (VL_API_NAT44_ADD_DEL_STATIC_MAPPING_REPLY); +} + +static void *vl_api_nat44_add_del_static_mapping_t_print + (vl_api_nat44_add_del_static_mapping_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_add_del_static_mapping "); + s = format (s, "protocol %d local_addr %U external_addr %U ", + mp->protocol, + format_ip4_address, mp->local_ip_address, + format_ip4_address, mp->external_ip_address); + + if (mp->addr_only == 0) + s = format (s, "local_port %d external_port %d ", + clib_net_to_host_u16 (mp->local_port), + clib_net_to_host_u16 (mp->external_port)); + + if (mp->vrf_id != ~0) + s = format (s, "vrf %d", clib_net_to_host_u32 (mp->vrf_id)); + + if (mp->external_sw_if_index != ~0) + s = format (s, "external_sw_if_index %d", + clib_net_to_host_u32 (mp->external_sw_if_index)); + FINISH; +} + +static void +send_nat44_static_mapping_details (snat_static_mapping_t * m, + unix_shared_memory_queue_t * q, + u32 context) +{ + vl_api_nat44_static_mapping_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = + ntohs (VL_API_NAT44_STATIC_MAPPING_DETAILS + sm->msg_id_base); + rmp->addr_only = m->addr_only; + clib_memcpy (rmp->local_ip_address, &(m->local_addr), 4); + clib_memcpy (rmp->external_ip_address, &(m->external_addr), 4); + rmp->local_port = htons (m->local_port); + rmp->external_port = htons (m->external_port); + rmp->external_sw_if_index = ~0; + rmp->vrf_id = htonl (m->vrf_id); + rmp->protocol = snat_proto_to_ip_proto (m->proto); + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +send_nat44_static_map_resolve_details (snat_static_map_resolve_t * m, + unix_shared_memory_queue_t * q, + u32 context) +{ + vl_api_nat44_static_mapping_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = + ntohs (VL_API_NAT44_STATIC_MAPPING_DETAILS + sm->msg_id_base); + rmp->addr_only = m->addr_only; + clib_memcpy (rmp->local_ip_address, &(m->l_addr), 4); + rmp->local_port = htons (m->l_port); + rmp->external_port = htons (m->e_port); + rmp->external_sw_if_index = htonl (m->sw_if_index); + rmp->vrf_id = htonl (m->vrf_id); + rmp->protocol = snat_proto_to_ip_proto (m->proto); + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_nat44_static_mapping_dump_t_handler (vl_api_nat44_static_mapping_dump_t + * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_static_mapping_t *m; + snat_static_map_resolve_t *rp; + int j; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + pool_foreach (m, sm->static_mappings, + ({ + send_nat44_static_mapping_details (m, q, mp->context); + })); + /* *INDENT-ON* */ + + for (j = 0; j < vec_len (sm->to_resolve); j++) + { + rp = sm->to_resolve + j; + send_nat44_static_map_resolve_details (rp, q, mp->context); + } +} + +static void * +vl_api_nat44_static_mapping_dump_t_print (vl_api_nat44_static_mapping_dump_t * + mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_static_mapping_dump "); + + FINISH; +} + +static void + vl_api_nat44_add_del_interface_addr_t_handler + (vl_api_nat44_add_del_interface_addr_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat44_add_del_interface_addr_reply_t *rmp; + u8 is_del = mp->is_add == 0; + u32 sw_if_index = ntohl (mp->sw_if_index); + int rv = 0; + + VALIDATE_SW_IF_INDEX (mp); + + rv = snat_add_interface_address (sm, sw_if_index, is_del); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (VL_API_NAT44_ADD_DEL_INTERFACE_ADDR_REPLY); +} + +static void *vl_api_nat44_add_del_interface_addr_t_print + (vl_api_nat44_add_del_interface_addr_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_add_del_interface_addr "); + s = format (s, "sw_if_index %d %s", + clib_host_to_net_u32 (mp->sw_if_index), + mp->is_add ? "" : "del"); + + FINISH; +} + +static void +send_nat44_interface_addr_details (u32 sw_if_index, + unix_shared_memory_queue_t * q, + u32 context) +{ + vl_api_nat44_interface_addr_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = + ntohs (VL_API_NAT44_INTERFACE_ADDR_DETAILS + sm->msg_id_base); + rmp->sw_if_index = ntohl (sw_if_index); + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_nat44_interface_addr_dump_t_handler (vl_api_nat44_interface_addr_dump_t + * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + u32 *i; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + vec_foreach (i, sm->auto_add_sw_if_indices) + send_nat44_interface_addr_details(*i, q, mp->context); + /* *INDENT-ON* */ +} + +static void * +vl_api_nat44_interface_addr_dump_t_print (vl_api_nat44_interface_addr_dump_t * + mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_interface_addr_dump "); + + FINISH; +} + +static void +send_nat44_user_details (snat_user_t * u, unix_shared_memory_queue_t * q, + u32 context) +{ + vl_api_nat44_user_details_t *rmp; + snat_main_t *sm = &snat_main; + fib_table_t *fib = fib_table_get (u->fib_index, FIB_PROTOCOL_IP4); + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_NAT44_USER_DETAILS + sm->msg_id_base); + + rmp->vrf_id = ntohl (fib->ft_table_id); + + clib_memcpy (rmp->ip_address, &(u->addr), 4); + rmp->nsessions = ntohl (u->nsessions); + rmp->nstaticsessions = ntohl (u->nstaticsessions); + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_nat44_user_dump_t_handler (vl_api_nat44_user_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_main_per_thread_data_t *tsm; + snat_user_t *u; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + vec_foreach (tsm, sm->per_thread_data) + vec_foreach (u, tsm->users) + send_nat44_user_details (u, q, mp->context); + /* *INDENT-ON* */ +} + +static void * +vl_api_nat44_user_dump_t_print (vl_api_nat44_user_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_user_dump "); + + FINISH; +} + +static void +send_nat44_user_session_details (snat_session_t * s, + unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_nat44_user_session_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = + ntohs (VL_API_NAT44_USER_SESSION_DETAILS + sm->msg_id_base); + clib_memcpy (rmp->outside_ip_address, (&s->out2in.addr), 4); + clib_memcpy (rmp->inside_ip_address, (&s->in2out.addr), 4); + rmp->is_static = s->flags & SNAT_SESSION_FLAG_STATIC_MAPPING ? 1 : 0; + rmp->last_heard = clib_host_to_net_u64 ((u64) s->last_heard); + rmp->total_bytes = clib_host_to_net_u64 (s->total_bytes); + rmp->total_pkts = ntohl (s->total_pkts); + rmp->context = context; + if (snat_is_unk_proto_session (s)) + { + rmp->outside_port = 0; + rmp->inside_port = 0; + rmp->protocol = ntohs (s->in2out.port); + } + else + { + rmp->outside_port = s->out2in.port; + rmp->inside_port = s->in2out.port; + rmp->protocol = ntohs (snat_proto_to_ip_proto (s->in2out.protocol)); + } + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_nat44_user_session_dump_t_handler (vl_api_nat44_user_session_dump_t * + mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_main_per_thread_data_t *tsm; + snat_session_t *s; + clib_bihash_kv_8_8_t key, value; + snat_user_key_t ukey; + snat_user_t *u; + u32 session_index, head_index, elt_index; + dlist_elt_t *head, *elt; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + clib_memcpy (&ukey.addr, mp->ip_address, 4); + ukey.fib_index = fib_table_find (FIB_PROTOCOL_IP4, ntohl (mp->vrf_id)); + key.key = ukey.as_u64; + if (!clib_bihash_search_8_8 (&sm->worker_by_in, &key, &value)) + tsm = vec_elt_at_index (sm->per_thread_data, value.value); + else + tsm = vec_elt_at_index (sm->per_thread_data, sm->num_workers); + if (clib_bihash_search_8_8 (&sm->user_hash, &key, &value)) + return; + u = pool_elt_at_index (tsm->users, value.value); + if (!u->nsessions && !u->nstaticsessions) + return; + + head_index = u->sessions_per_user_list_head_index; + head = pool_elt_at_index (tsm->list_pool, head_index); + elt_index = head->next; + elt = pool_elt_at_index (tsm->list_pool, elt_index); + session_index = elt->value; + while (session_index != ~0) + { + s = pool_elt_at_index (tsm->sessions, session_index); + + send_nat44_user_session_details (s, q, mp->context); + + elt_index = elt->next; + elt = pool_elt_at_index (tsm->list_pool, elt_index); + session_index = elt->value; + } +} + +static void * +vl_api_nat44_user_session_dump_t_print (vl_api_nat44_user_session_dump_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_user_session_dump "); + s = format (s, "ip_address %U vrf_id %d\n", + format_ip4_address, mp->ip_address, + clib_net_to_host_u32 (mp->vrf_id)); + + FINISH; +} + +/*******************************/ +/*** Deterministic NAT (CGN) ***/ +/*******************************/ + +static void +vl_api_nat_det_add_del_map_t_handler (vl_api_nat_det_add_del_map_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat_det_add_del_map_reply_t *rmp; + int rv = 0; + ip4_address_t in_addr, out_addr; + + if (!mp->is_nat44) + { + rv = VNET_API_ERROR_UNIMPLEMENTED; + goto send_reply; + } + + clib_memcpy (&in_addr, mp->in_addr, 4); + clib_memcpy (&out_addr, mp->out_addr, 4); + rv = snat_det_add_map (sm, &in_addr, mp->in_plen, &out_addr, + mp->out_plen, mp->is_add); + +send_reply: + REPLY_MACRO (VL_API_NAT_DET_ADD_DEL_MAP_REPLY); +} + +static void * +vl_api_nat_det_add_del_map_t_print (vl_api_nat_det_add_del_map_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_det_add_del_map "); + s = format (s, "inside address %U/%d outside address %U/%d\n", + format_ip4_address, mp->in_addr, mp->in_plen, + format_ip4_address, mp->out_addr, mp->out_plen); + + FINISH; +} + +static void +vl_api_nat_det_forward_t_handler (vl_api_nat_det_forward_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat_det_forward_reply_t *rmp; + int rv = 0; + u16 lo_port = 0, hi_port = 0; + snat_det_map_t *dm; + ip4_address_t in_addr, out_addr; + + if (!mp->is_nat44) + { + out_addr.as_u32 = 0; + rv = VNET_API_ERROR_UNIMPLEMENTED; + goto send_reply; + } + + out_addr.as_u32 = 0; + clib_memcpy (&in_addr, mp->in_addr, 4); + dm = snat_det_map_by_user (sm, &in_addr); + if (!dm) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + goto send_reply; + } + + snat_det_forward (dm, &in_addr, &out_addr, &lo_port); + hi_port = lo_port + dm->ports_per_host - 1; + +send_reply: + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_NAT_DET_FORWARD_REPLY, + ({ + rmp->out_port_lo = ntohs (lo_port); + rmp->out_port_hi = ntohs (hi_port); + clib_memcpy (rmp->out_addr, &out_addr, 4); + })) + /* *INDENT-ON* */ +} + +static void * +vl_api_nat_det_forward_t_print (vl_api_nat_det_forward_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_det_forward"); + s = format (s, "inside ip address %U\n", format_ip4_address, mp->in_addr); + + FINISH; +} + +static void +vl_api_nat_det_reverse_t_handler (vl_api_nat_det_reverse_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat_det_reverse_reply_t *rmp; + int rv = 0; + ip4_address_t out_addr, in_addr; + snat_det_map_t *dm; + + in_addr.as_u32 = 0; + clib_memcpy (&out_addr, mp->out_addr, 4); + dm = snat_det_map_by_out (sm, &out_addr); + if (!dm) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + goto send_reply; + } + + snat_det_reverse (dm, &out_addr, htons (mp->out_port), &in_addr); + +send_reply: + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_NAT_DET_REVERSE_REPLY, + ({ + rmp->is_nat44 = 1; + memset (rmp->in_addr, 0, 16); + clib_memcpy (rmp->in_addr, &in_addr, 4); + })) + /* *INDENT-ON* */ +} + +static void * +vl_api_nat_det_reverse_t_print (vl_api_nat_det_reverse_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_det_reverse"); + s = format (s, "outside ip address %U outside port %d", + format_ip4_address, mp->out_addr, ntohs (mp->out_port)); + + FINISH; +} + +static void +sent_nat_det_map_details (snat_det_map_t * m, unix_shared_memory_queue_t * q, + u32 context) +{ + vl_api_nat_det_map_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_NAT_DET_MAP_DETAILS + sm->msg_id_base); + rmp->is_nat44 = 1; + clib_memcpy (rmp->in_addr, &m->in_addr, 4); + rmp->in_plen = m->in_plen; + clib_memcpy (rmp->out_addr, &m->out_addr, 4); + rmp->out_plen = m->out_plen; + rmp->sharing_ratio = htonl (m->sharing_ratio); + rmp->ports_per_host = htons (m->ports_per_host); + rmp->ses_num = htonl (m->ses_num); + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_nat_det_map_dump_t_handler (vl_api_nat_det_map_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + snat_det_map_t *m; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + /* *INDENT-OFF* */ + vec_foreach(m, sm->det_maps) + sent_nat_det_map_details(m, q, mp->context); + /* *INDENT-ON* */ +} + +static void * +vl_api_nat_det_map_dump_t_print (vl_api_nat_det_map_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_det_map_dump "); + + FINISH; +} + +static void +vl_api_nat_det_set_timeouts_t_handler (vl_api_nat_det_set_timeouts_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat_det_set_timeouts_reply_t *rmp; + int rv = 0; + + sm->udp_timeout = ntohl (mp->udp); + sm->tcp_established_timeout = ntohl (mp->tcp_established); + sm->tcp_transitory_timeout = ntohl (mp->tcp_transitory); + sm->icmp_timeout = ntohl (mp->icmp); + + REPLY_MACRO (VL_API_NAT_DET_SET_TIMEOUTS_REPLY); +} + +static void * +vl_api_nat_det_set_timeouts_t_print (vl_api_nat_det_set_timeouts_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_det_set_timeouts "); + s = format (s, "udp %d tcp_established %d tcp_transitory %d icmp %d\n", + ntohl (mp->udp), + ntohl (mp->tcp_established), + ntohl (mp->tcp_transitory), ntohl (mp->icmp)); + + FINISH; +} + +static void +vl_api_nat_det_get_timeouts_t_handler (vl_api_nat_det_get_timeouts_t * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat_det_get_timeouts_reply_t *rmp; + int rv = 0; + + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_NAT_DET_GET_TIMEOUTS_REPLY, + ({ + rmp->udp = htonl (sm->udp_timeout); + rmp->tcp_established = htonl (sm->tcp_established_timeout); + rmp->tcp_transitory = htonl (sm->tcp_transitory_timeout); + rmp->icmp = htonl (sm->icmp_timeout); + })) + /* *INDENT-ON* */ +} + +static void * +vl_api_nat_det_get_timeouts_t_print (vl_api_nat_det_get_timeouts_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_det_get_timeouts"); + + FINISH; +} + +static void +vl_api_nat_det_close_session_out_t_handler (vl_api_nat_det_close_session_out_t + * mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat_det_close_session_out_reply_t *rmp; + ip4_address_t out_addr, ext_addr, in_addr; + snat_det_out_key_t key; + snat_det_map_t *dm; + snat_det_session_t *ses; + int rv = 0; + + clib_memcpy (&out_addr, mp->out_addr, 4); + clib_memcpy (&ext_addr, mp->ext_addr, 4); + + dm = snat_det_map_by_out (sm, &out_addr); + if (!dm) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + goto send_reply; + } + snat_det_reverse (dm, &ext_addr, ntohs (mp->out_port), &in_addr); + key.ext_host_addr = ext_addr; + key.ext_host_port = mp->ext_port; + key.out_port = mp->out_port; + ses = snat_det_get_ses_by_out (dm, &in_addr, key.as_u64); + if (!ses) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + goto send_reply; + } + snat_det_ses_close (dm, ses); + +send_reply: + REPLY_MACRO (VL_API_NAT_DET_CLOSE_SESSION_OUT_REPLY); +} + +static void * +vl_api_nat_det_close_session_out_t_print (vl_api_nat_det_close_session_out_t * + mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_det_close_session_out "); + s = format (s, "out_addr %U out_port %d " + "ext_addr %U ext_port %d\n", + format_ip4_address, mp->out_addr, ntohs (mp->out_port), + format_ip4_address, mp->ext_addr, ntohs (mp->ext_port)); + + FINISH; +} + +static void +vl_api_nat_det_close_session_in_t_handler (vl_api_nat_det_close_session_in_t * + mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat_det_close_session_in_reply_t *rmp; + ip4_address_t in_addr, ext_addr; + snat_det_out_key_t key; + snat_det_map_t *dm; + snat_det_session_t *ses; + int rv = 0; + + if (!mp->is_nat44) + { + rv = VNET_API_ERROR_UNIMPLEMENTED; + goto send_reply; + } + + clib_memcpy (&in_addr, mp->in_addr, 4); + clib_memcpy (&ext_addr, mp->ext_addr, 4); + + dm = snat_det_map_by_user (sm, &in_addr); + if (!dm) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + goto send_reply; + } + key.ext_host_addr = ext_addr; + key.ext_host_port = mp->ext_port; + ses = snat_det_find_ses_by_in (dm, &in_addr, mp->in_port, key); + if (!ses) + { + rv = VNET_API_ERROR_NO_SUCH_ENTRY; + goto send_reply; + } + snat_det_ses_close (dm, ses); + +send_reply: + REPLY_MACRO (VL_API_NAT_DET_CLOSE_SESSION_OUT_REPLY); +} + +static void * +vl_api_nat_det_close_session_in_t_print (vl_api_nat_det_close_session_in_t * + mp, void *handle) +{ + u8 *s; + s = format (0, "SCRIPT: nat_det_close_session_in "); + s = format (s, "in_addr %U in_port %d ext_addr %U ext_port %d\n", + format_ip4_address, mp->in_addr, ntohs (mp->in_port), + format_ip4_address, mp->ext_addr, ntohs (mp->ext_port)); + + FINISH; +} + +static void +send_nat_det_session_details (snat_det_session_t * s, + unix_shared_memory_queue_t * q, u32 context) +{ + vl_api_nat_det_session_details_t *rmp; + snat_main_t *sm = &snat_main; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_NAT_DET_SESSION_DETAILS + sm->msg_id_base); + rmp->in_port = s->in_port; + clib_memcpy (rmp->ext_addr, &s->out.ext_host_addr, 4); + rmp->ext_port = s->out.ext_host_port; + rmp->out_port = s->out.out_port; + rmp->state = s->state; + rmp->expire = ntohl (s->expire); + rmp->context = context; + + vl_msg_api_send_shmem (q, (u8 *) & rmp); +} + +static void +vl_api_nat_det_session_dump_t_handler (vl_api_nat_det_session_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + snat_main_t *sm = &snat_main; + ip4_address_t user_addr; + snat_det_map_t *dm; + snat_det_session_t *s, empty_ses; + u16 i; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + if (!mp->is_nat44) + return; + + memset (&empty_ses, 0, sizeof (empty_ses)); + clib_memcpy (&user_addr, mp->user_addr, 4); + dm = snat_det_map_by_user (sm, &user_addr); + if (!dm) + return; + + s = dm->sessions + snat_det_user_ses_offset (&user_addr, dm->in_plen); + for (i = 0; i < SNAT_DET_SES_PER_USER; i++) + { + if (s->out.as_u64) + send_nat_det_session_details (s, q, mp->context); + s++; + } +} + +static void * +vl_api_nat_det_session_dump_t_print (vl_api_nat_det_session_dump_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat_det_session_dump "); + s = format (s, "user_addr %U\n", format_ip4_address, mp->user_addr); + + FINISH; +} + +/*************/ +/*** NAT64 ***/ +/*************/ + +static void + vl_api_nat64_add_del_pool_addr_range_t_handler + (vl_api_nat64_add_del_pool_addr_range_t * mp) +{ + vl_api_nat64_add_del_pool_addr_range_reply_t *rmp; + snat_main_t *sm = &snat_main; + nat64_main_t *nm = &nat64_main; + int rv = 0; + ip4_address_t this_addr; + u32 start_host_order, end_host_order; + u32 vrf_id; + int i, count; + u32 *tmp; + + if (nm->is_disabled) + { + rv = VNET_API_ERROR_FEATURE_DISABLED; + goto send_reply; + } + + tmp = (u32 *) mp->start_addr; + start_host_order = clib_host_to_net_u32 (tmp[0]); + tmp = (u32 *) mp->end_addr; + end_host_order = clib_host_to_net_u32 (tmp[0]); + + count = (end_host_order - start_host_order) + 1; + + vrf_id = clib_host_to_net_u32 (mp->vrf_id); + + memcpy (&this_addr.as_u8, mp->start_addr, 4); + + for (i = 0; i < count; i++) + { + if ((rv = nat64_add_del_pool_addr (&this_addr, vrf_id, mp->is_add))) + goto send_reply; + + increment_v4_address (&this_addr); + } + +send_reply: + REPLY_MACRO (VL_API_NAT64_ADD_DEL_POOL_ADDR_RANGE_REPLY); +} + +static void *vl_api_nat64_add_del_pool_addr_range_t_print + (vl_api_nat64_add_del_pool_addr_range_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat64_add_del_pool_addr_range "); + s = format (s, "%U - %U vrf_id %u %s\n", + format_ip4_address, mp->start_addr, + format_ip4_address, mp->end_addr, + ntohl (mp->vrf_id), mp->is_add ? "" : "del"); + + FINISH; +} + +typedef struct nat64_api_walk_ctx_t_ +{ + unix_shared_memory_queue_t *q; + u32 context; +} nat64_api_walk_ctx_t; + +static int +nat64_api_pool_walk (snat_address_t * a, void *arg) +{ + vl_api_nat64_pool_addr_details_t *rmp; + snat_main_t *sm = &snat_main; + nat64_api_walk_ctx_t *ctx = arg; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_NAT64_POOL_ADDR_DETAILS + sm->msg_id_base); + clib_memcpy (rmp->address, &(a->addr), 4); + if (a->fib_index != ~0) + { + fib_table_t *fib = fib_table_get (a->fib_index, FIB_PROTOCOL_IP6); + if (!fib) + return -1; + rmp->vrf_id = ntohl (fib->ft_table_id); + } + else + rmp->vrf_id = ~0; + rmp->context = ctx->context; + + vl_msg_api_send_shmem (ctx->q, (u8 *) & rmp); + + return 0; +} + +static void +vl_api_nat64_pool_addr_dump_t_handler (vl_api_nat64_pool_addr_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + nat64_main_t *nm = &nat64_main; + + if (nm->is_disabled) + return; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + nat64_api_walk_ctx_t ctx = { + .q = q, + .context = mp->context, + }; + + nat64_pool_addr_walk (nat64_api_pool_walk, &ctx); +} + +static void * +vl_api_nat64_pool_addr_dump_t_print (vl_api_nat64_pool_addr_dump_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat64_pool_addr_dump\n"); + + FINISH; +} + +static void +vl_api_nat64_add_del_interface_t_handler (vl_api_nat64_add_del_interface_t * + mp) +{ + snat_main_t *sm = &snat_main; + nat64_main_t *nm = &nat64_main; + vl_api_nat64_add_del_interface_reply_t *rmp; + int rv = 0; + + if (nm->is_disabled) + { + rv = VNET_API_ERROR_FEATURE_DISABLED; + goto send_reply; + } + + VALIDATE_SW_IF_INDEX (mp); + + rv = + nat64_add_del_interface (ntohl (mp->sw_if_index), mp->is_inside, + mp->is_add); + + BAD_SW_IF_INDEX_LABEL; + +send_reply: + REPLY_MACRO (VL_API_NAT64_ADD_DEL_INTERFACE_REPLY); +} + +static void * +vl_api_nat64_add_del_interface_t_print (vl_api_nat64_add_del_interface_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat64_add_del_interface "); + s = format (s, "sw_if_index %d %s %s", + clib_host_to_net_u32 (mp->sw_if_index), + mp->is_inside ? "in" : "out", mp->is_add ? "" : "del"); + + FINISH; +} + +static int +nat64_api_interface_walk (snat_interface_t * i, void *arg) +{ + vl_api_nat64_interface_details_t *rmp; + snat_main_t *sm = &snat_main; + nat64_api_walk_ctx_t *ctx = arg; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_NAT64_INTERFACE_DETAILS + sm->msg_id_base); + rmp->sw_if_index = ntohl (i->sw_if_index); + rmp->is_inside = i->is_inside; + rmp->context = ctx->context; + + vl_msg_api_send_shmem (ctx->q, (u8 *) & rmp); + + return 0; +} + +static void +vl_api_nat64_interface_dump_t_handler (vl_api_nat64_interface_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + nat64_main_t *nm = &nat64_main; + + if (nm->is_disabled) + return; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + nat64_api_walk_ctx_t ctx = { + .q = q, + .context = mp->context, + }; + + nat64_interfaces_walk (nat64_api_interface_walk, &ctx); +} + +static void * +vl_api_nat64_interface_dump_t_print (vl_api_nat64_interface_dump_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_interface_dump "); + + FINISH; +} + +static void + vl_api_nat64_add_del_static_bib_t_handler + (vl_api_nat64_add_del_static_bib_t * mp) +{ + snat_main_t *sm = &snat_main; + nat64_main_t *nm = &nat64_main; + vl_api_nat64_add_del_static_bib_reply_t *rmp; + ip6_address_t in_addr; + ip4_address_t out_addr; + int rv = 0; + + if (nm->is_disabled) + { + rv = VNET_API_ERROR_FEATURE_DISABLED; + goto send_reply; + } + + memcpy (&in_addr.as_u8, mp->i_addr, 16); + memcpy (&out_addr.as_u8, mp->o_addr, 4); + + rv = + nat64_add_del_static_bib_entry (&in_addr, &out_addr, + clib_net_to_host_u16 (mp->i_port), + clib_net_to_host_u16 (mp->o_port), + mp->proto, + clib_net_to_host_u32 (mp->vrf_id), + mp->is_add); + +send_reply: + REPLY_MACRO (VL_API_NAT64_ADD_DEL_STATIC_BIB_REPLY); +} + +static void *vl_api_nat64_add_del_static_bib_t_print + (vl_api_nat64_add_del_static_bib_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat64_add_del_static_bib "); + s = format (s, "protocol %d i_addr %U o_addr %U ", + mp->proto, + format_ip6_address, mp->i_addr, format_ip4_address, mp->o_addr); + + if (mp->vrf_id != ~0) + s = format (s, "vrf %d", clib_net_to_host_u32 (mp->vrf_id)); + + FINISH; +} + +static int +nat64_api_bib_walk (nat64_db_bib_entry_t * bibe, void *arg) +{ + vl_api_nat64_bib_details_t *rmp; + snat_main_t *sm = &snat_main; + nat64_api_walk_ctx_t *ctx = arg; + fib_table_t *fib; + + fib = fib_table_get (bibe->fib_index, FIB_PROTOCOL_IP6); + if (!fib) + return -1; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_NAT64_BIB_DETAILS + sm->msg_id_base); + rmp->context = ctx->context; + clib_memcpy (rmp->i_addr, &(bibe->in_addr), 16); + clib_memcpy (rmp->o_addr, &(bibe->out_addr), 4); + rmp->i_port = bibe->in_port; + rmp->o_port = bibe->out_port; + rmp->vrf_id = ntohl (fib->ft_table_id); + rmp->proto = bibe->proto; + rmp->is_static = bibe->is_static; + rmp->ses_num = ntohl (bibe->ses_num); + + vl_msg_api_send_shmem (ctx->q, (u8 *) & rmp); + + return 0; +} + +static void +vl_api_nat64_bib_dump_t_handler (vl_api_nat64_bib_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + nat64_main_t *nm = &nat64_main; + + if (nm->is_disabled) + return; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + nat64_api_walk_ctx_t ctx = { + .q = q, + .context = mp->context, + }; + + nat64_db_bib_walk (&nm->db, mp->proto, nat64_api_bib_walk, &ctx); +} + +static void * +vl_api_nat64_bib_dump_t_print (vl_api_nat64_bib_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_bib_dump protocol %d", mp->proto); + + FINISH; +} + +static void +vl_api_nat64_set_timeouts_t_handler (vl_api_nat64_set_timeouts_t * mp) +{ + snat_main_t *sm = &snat_main; + nat64_main_t *nm = &nat64_main; + vl_api_nat64_set_timeouts_reply_t *rmp; + int rv = 0; + + if (nm->is_disabled) + { + rv = VNET_API_ERROR_FEATURE_DISABLED; + goto send_reply; + } + + rv = nat64_set_icmp_timeout (ntohl (mp->icmp)); + if (rv) + goto send_reply; + rv = nat64_set_udp_timeout (ntohl (mp->udp)); + if (rv) + goto send_reply; + rv = + nat64_set_tcp_timeouts (ntohl (mp->tcp_trans), ntohl (mp->tcp_est), + ntohl (mp->tcp_incoming_syn)); + +send_reply: + REPLY_MACRO (VL_API_NAT64_SET_TIMEOUTS_REPLY); +} + +static void *vl_api_nat64_set_timeouts_t_print + (vl_api_nat64_set_timeouts_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat64_set_timeouts "); + s = + format (s, + "udp %d icmp %d, tcp_trans %d, tcp_est %d, tcp_incoming_syn %d\n", + ntohl (mp->udp), ntohl (mp->icmp), ntohl (mp->tcp_trans), + ntohl (mp->tcp_est), ntohl (mp->tcp_incoming_syn)); + + FINISH; +} + +static void +vl_api_nat64_get_timeouts_t_handler (vl_api_nat64_get_timeouts_t * mp) +{ + snat_main_t *sm = &snat_main; + nat64_main_t *nm = &nat64_main; + vl_api_nat64_get_timeouts_reply_t *rmp; + int rv = 0; + + if (nm->is_disabled) + return; + + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_NAT64_GET_TIMEOUTS_REPLY, + ({ + rmp->udp = htonl (nat64_get_udp_timeout()); + rmp->icmp = htonl (nat64_get_icmp_timeout()); + rmp->tcp_trans = htonl (nat64_get_tcp_trans_timeout()); + rmp->tcp_est = htonl (nat64_get_tcp_est_timeout()); + rmp->tcp_incoming_syn = htonl (nat64_get_tcp_incoming_syn_timeout()); + })) + /* *INDENT-ON* */ +} + +static void *vl_api_nat64_get_timeouts_t_print + (vl_api_nat64_get_timeouts_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat64_get_timeouts"); + + FINISH; +} + +static int +nat64_api_st_walk (nat64_db_st_entry_t * ste, void *arg) +{ + vl_api_nat64_st_details_t *rmp; + snat_main_t *sm = &snat_main; + nat64_api_walk_ctx_t *ctx = arg; + nat64_main_t *nm = &nat64_main; + nat64_db_bib_entry_t *bibe; + fib_table_t *fib; + + bibe = nat64_db_bib_entry_by_index (&nm->db, ste->proto, ste->bibe_index); + if (!bibe) + return -1; + + fib = fib_table_get (bibe->fib_index, FIB_PROTOCOL_IP6); + if (!fib) + return -1; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_NAT64_ST_DETAILS + sm->msg_id_base); + rmp->context = ctx->context; + clib_memcpy (rmp->il_addr, &(bibe->in_addr), 16); + clib_memcpy (rmp->ol_addr, &(bibe->out_addr), 4); + rmp->il_port = bibe->in_port; + rmp->ol_port = bibe->out_port; + clib_memcpy (rmp->ir_addr, &(ste->in_r_addr), 16); + clib_memcpy (rmp->or_addr, &(ste->out_r_addr), 4); + rmp->il_port = ste->r_port; + rmp->vrf_id = ntohl (fib->ft_table_id); + rmp->proto = ste->proto; + + vl_msg_api_send_shmem (ctx->q, (u8 *) & rmp); + + return 0; +} + +static void +vl_api_nat64_st_dump_t_handler (vl_api_nat64_st_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + nat64_main_t *nm = &nat64_main; + + if (nm->is_disabled) + return; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + nat64_api_walk_ctx_t ctx = { + .q = q, + .context = mp->context, + }; + + nat64_db_st_walk (&nm->db, mp->proto, nat64_api_st_walk, &ctx); +} + +static void * +vl_api_nat64_st_dump_t_print (vl_api_nat64_st_dump_t * mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: snat_st_dump protocol %d", mp->proto); + + FINISH; +} + +static void +vl_api_nat64_add_del_prefix_t_handler (vl_api_nat64_add_del_prefix_t * mp) +{ + vl_api_nat64_add_del_prefix_reply_t *rmp; + snat_main_t *sm = &snat_main; + nat64_main_t *nm = &nat64_main; + ip6_address_t prefix; + int rv = 0; + + if (nm->is_disabled) + { + rv = VNET_API_ERROR_FEATURE_DISABLED; + goto send_reply; + } + + memcpy (&prefix.as_u8, mp->prefix, 16); + + rv = + nat64_add_del_prefix (&prefix, mp->prefix_len, + clib_net_to_host_u32 (mp->vrf_id), mp->is_add); +send_reply: + REPLY_MACRO (VL_API_NAT64_ADD_DEL_PREFIX_REPLY); +} + +static void * +vl_api_nat64_add_del_prefix_t_print (vl_api_nat64_add_del_prefix_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat64_add_del_prefix %U/%u vrf_id %u %s\n", + format_ip6_address, mp->prefix, mp->prefix_len, + ntohl (mp->vrf_id), mp->is_add ? "" : "del"); + + FINISH; +} + +static int +nat64_api_prefix_walk (nat64_prefix_t * p, void *arg) +{ + vl_api_nat64_prefix_details_t *rmp; + snat_main_t *sm = &snat_main; + nat64_api_walk_ctx_t *ctx = arg; + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_NAT64_PREFIX_DETAILS + sm->msg_id_base); + clib_memcpy (rmp->prefix, &(p->prefix), 16); + rmp->prefix_len = p->plen; + rmp->vrf_id = ntohl (p->vrf_id); + rmp->context = ctx->context; + + vl_msg_api_send_shmem (ctx->q, (u8 *) & rmp); + + return 0; +} + +static void +vl_api_nat64_prefix_dump_t_handler (vl_api_nat64_prefix_dump_t * mp) +{ + unix_shared_memory_queue_t *q; + nat64_main_t *nm = &nat64_main; + + if (nm->is_disabled) + return; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + nat64_api_walk_ctx_t ctx = { + .q = q, + .context = mp->context, + }; + + nat64_prefix_walk (nat64_api_prefix_walk, &ctx); +} + +static void * +vl_api_nat64_prefix_dump_t_print (vl_api_nat64_prefix_dump_t * mp, + void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat64_prefix_dump\n"); + + FINISH; +} + +/* List of message types that this plugin understands */ +#define foreach_snat_plugin_api_msg \ +_(SNAT_ADD_ADDRESS_RANGE, snat_add_address_range) \ +_(SNAT_INTERFACE_ADD_DEL_FEATURE, snat_interface_add_del_feature) \ +_(SNAT_ADD_STATIC_MAPPING, snat_add_static_mapping) \ +_(SNAT_CONTROL_PING, snat_control_ping) \ +_(SNAT_STATIC_MAPPING_DUMP, snat_static_mapping_dump) \ +_(SNAT_SHOW_CONFIG, snat_show_config) \ +_(SNAT_ADDRESS_DUMP, snat_address_dump) \ +_(SNAT_INTERFACE_DUMP, snat_interface_dump) \ +_(SNAT_SET_WORKERS, snat_set_workers) \ +_(SNAT_WORKER_DUMP, snat_worker_dump) \ +_(SNAT_ADD_DEL_INTERFACE_ADDR, snat_add_del_interface_addr) \ +_(SNAT_INTERFACE_ADDR_DUMP, snat_interface_addr_dump) \ +_(SNAT_IPFIX_ENABLE_DISABLE, snat_ipfix_enable_disable) \ +_(SNAT_USER_DUMP, snat_user_dump) \ +_(SNAT_USER_SESSION_DUMP, snat_user_session_dump) \ +_(SNAT_INTERFACE_ADD_DEL_OUTPUT_FEATURE, \ + snat_interface_add_del_output_feature) \ +_(SNAT_INTERFACE_OUTPUT_FEATURE_DUMP, \ + snat_interface_output_feature_dump) \ +_(SNAT_ADD_DET_MAP, snat_add_det_map) \ +_(SNAT_DET_FORWARD, snat_det_forward) \ +_(SNAT_DET_REVERSE, snat_det_reverse) \ +_(SNAT_DET_MAP_DUMP, snat_det_map_dump) \ +_(SNAT_DET_SET_TIMEOUTS, snat_det_set_timeouts) \ +_(SNAT_DET_GET_TIMEOUTS, snat_det_get_timeouts) \ +_(SNAT_DET_CLOSE_SESSION_OUT, snat_det_close_session_out) \ +_(SNAT_DET_CLOSE_SESSION_IN, snat_det_close_session_in) \ +_(SNAT_DET_SESSION_DUMP, snat_det_session_dump) \ +_(NAT_CONTROL_PING, nat_control_ping) \ +_(NAT_SHOW_CONFIG, nat_show_config) \ +_(NAT_SET_WORKERS, nat_set_workers) \ +_(NAT_WORKER_DUMP, nat_worker_dump) \ +_(NAT_IPFIX_ENABLE_DISABLE, nat_ipfix_enable_disable) \ +_(NAT44_ADD_DEL_ADDRESS_RANGE, nat44_add_del_address_range) \ +_(NAT44_INTERFACE_ADD_DEL_FEATURE, nat44_interface_add_del_feature) \ +_(NAT44_ADD_DEL_STATIC_MAPPING, nat44_add_del_static_mapping) \ +_(NAT44_STATIC_MAPPING_DUMP, nat44_static_mapping_dump) \ +_(NAT44_ADDRESS_DUMP, nat44_address_dump) \ +_(NAT44_INTERFACE_DUMP, nat44_interface_dump) \ +_(NAT44_ADD_DEL_INTERFACE_ADDR, nat44_add_del_interface_addr) \ +_(NAT44_INTERFACE_ADDR_DUMP, nat44_interface_addr_dump) \ +_(NAT44_USER_DUMP, nat44_user_dump) \ +_(NAT44_USER_SESSION_DUMP, nat44_user_session_dump) \ +_(NAT44_INTERFACE_ADD_DEL_OUTPUT_FEATURE, \ + nat44_interface_add_del_output_feature) \ +_(NAT44_INTERFACE_OUTPUT_FEATURE_DUMP, \ + nat44_interface_output_feature_dump) \ +_(NAT_DET_ADD_DEL_MAP, nat_det_add_del_map) \ +_(NAT_DET_FORWARD, nat_det_forward) \ +_(NAT_DET_REVERSE, nat_det_reverse) \ +_(NAT_DET_MAP_DUMP, nat_det_map_dump) \ +_(NAT_DET_SET_TIMEOUTS, nat_det_set_timeouts) \ +_(NAT_DET_GET_TIMEOUTS, nat_det_get_timeouts) \ +_(NAT_DET_CLOSE_SESSION_OUT, nat_det_close_session_out) \ +_(NAT_DET_CLOSE_SESSION_IN, nat_det_close_session_in) \ +_(NAT_DET_SESSION_DUMP, nat_det_session_dump) \ +_(NAT64_ADD_DEL_POOL_ADDR_RANGE, nat64_add_del_pool_addr_range) \ +_(NAT64_POOL_ADDR_DUMP, nat64_pool_addr_dump) \ +_(NAT64_ADD_DEL_INTERFACE, nat64_add_del_interface) \ +_(NAT64_INTERFACE_DUMP, nat64_interface_dump) \ +_(NAT64_ADD_DEL_STATIC_BIB, nat64_add_del_static_bib) \ +_(NAT64_BIB_DUMP, nat64_bib_dump) \ +_(NAT64_SET_TIMEOUTS, nat64_set_timeouts) \ +_(NAT64_GET_TIMEOUTS, nat64_get_timeouts) \ +_(NAT64_ST_DUMP, nat64_st_dump) \ +_(NAT64_ADD_DEL_PREFIX, nat64_add_del_prefix) \ +_(NAT64_PREFIX_DUMP, nat64_prefix_dump) + +/* Set up the API message handling tables */ +static clib_error_t * +snat_plugin_api_hookup (vlib_main_t * vm) +{ + snat_main_t *sm __attribute__ ((unused)) = &snat_main; +#define _(N,n) \ + vl_msg_api_set_handlers((VL_API_##N + sm->msg_id_base), \ + #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_api_##n##_t_endian, \ + vl_api_##n##_t_print, \ + sizeof(vl_api_##n##_t), 1); + foreach_snat_plugin_api_msg; +#undef _ + + return 0; +} + +#define vl_msg_name_crc_list +#include <nat/nat_all_api_h.h> +#undef vl_msg_name_crc_list + +static void +setup_message_id_table (snat_main_t * sm, api_main_t * am) +{ +#define _(id,n,crc) \ + vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id + sm->msg_id_base); + foreach_vl_msg_name_crc_nat; +#undef _ +} + +static void +plugin_custom_dump_configure (snat_main_t * sm) +{ +#define _(n,f) sm->api_main->msg_print_handlers \ + [VL_API_##n + sm->msg_id_base] \ + = (void *) vl_api_##f##_t_print; + foreach_snat_plugin_api_msg; +#undef _ +} + +clib_error_t * +snat_api_init (vlib_main_t * vm, snat_main_t * sm) +{ + u8 *name; + clib_error_t *error = 0; + + name = format (0, "snat_%08x%c", api_version, 0); + + /* Ask for a correctly-sized block of API message decode slots */ + sm->msg_id_base = + vl_msg_api_get_msg_ids ((char *) name, VL_MSG_FIRST_AVAILABLE); + + error = snat_plugin_api_hookup (vm); + + /* Add our API messages to the global name_crc hash table */ + setup_message_id_table (sm, sm->api_main); + + plugin_custom_dump_configure (sm); + + vec_free (name); + + return error; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/nat/nat_det.c b/src/plugins/nat/nat_det.c new file mode 100644 index 00000000000..3af6698c09a --- /dev/null +++ b/src/plugins/nat/nat_det.c @@ -0,0 +1,158 @@ +/* + * snat_det.c - deterministic NAT + * + * Copyright (c) 2017 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. + */ +/** + * @file + * @brief deterministic NAT + */ + +#include <nat/nat_det.h> + + +/** + * @brief Add/delete deterministic NAT mapping. + * + * Create bijective mapping of inside address to outside address and port range + * pairs, with the purpose of enabling deterministic NAT to reduce logging in + * CGN deployments. + * + * @param sm SNAT main. + * @param in_addr Inside network address. + * @param in_plen Inside network prefix length. + * @param out_addr Outside network address. + * @param out_plen Outside network prefix length. + * @param is_add If 0 delete, otherwise add. + */ +int +snat_det_add_map (snat_main_t * sm, ip4_address_t * in_addr, u8 in_plen, + ip4_address_t * out_addr, u8 out_plen, int is_add) +{ + snat_det_map_t *det_map; + static snat_det_session_t empty_snat_det_session = { 0 }; + snat_interface_t *i; + ip4_address_t in_cmp, out_cmp; + u8 found = 0; + + in_cmp.as_u32 = in_addr->as_u32 & ip4_main.fib_masks[in_plen]; + out_cmp.as_u32 = out_addr->as_u32 & ip4_main.fib_masks[out_plen]; + vec_foreach (det_map, sm->det_maps) + { + /* Checking for overlapping addresses to be added here */ + if (det_map->in_addr.as_u32 == in_cmp.as_u32 && + det_map->in_plen == in_plen && + det_map->out_addr.as_u32 == out_cmp.as_u32 && + det_map->out_plen == out_plen) + { + found = 1; + break; + } + } + + /* If found, don't add again */ + if (found && is_add) + return VNET_API_ERROR_VALUE_EXIST; + + /* If not found, don't delete */ + if (!found && !is_add) + return VNET_API_ERROR_NO_SUCH_ENTRY; + + if (is_add) + { + pool_get (sm->det_maps, det_map); + memset (det_map, 0, sizeof (*det_map)); + det_map->in_addr.as_u32 = in_cmp.as_u32; + det_map->in_plen = in_plen; + det_map->out_addr.as_u32 = out_cmp.as_u32; + det_map->out_plen = out_plen; + det_map->sharing_ratio = (1 << (32 - in_plen)) / (1 << (32 - out_plen)); + det_map->ports_per_host = (65535 - 1023) / det_map->sharing_ratio; + + vec_validate_init_empty (det_map->sessions, + SNAT_DET_SES_PER_USER * (1 << (32 - in_plen)) - + 1, empty_snat_det_session); + } + else + { + vec_free (det_map->sessions); + vec_del1 (sm->det_maps, det_map - sm->det_maps); + } + + /* Add/del external address range to FIB */ + /* *INDENT-OFF* */ + pool_foreach (i, sm->interfaces, + ({ + if (i->is_inside) + continue; + + snat_add_del_addr_to_fib(out_addr, out_plen, i->sw_if_index, is_add); + break; + })); + /* *INDENT-ON* */ + return 0; +} + +/** + * @brief The 'nat-det-expire-walk' process's main loop. + * + * Check expire time for active sessions. + */ +static uword +snat_det_expire_walk_fn (vlib_main_t * vm, vlib_node_runtime_t * rt, + vlib_frame_t * f) +{ + snat_main_t *sm = &snat_main; + snat_det_map_t *dm; + snat_det_session_t *ses; + + while (sm->deterministic) + { + vlib_process_wait_for_event_or_clock (vm, 10.0); + vlib_process_get_events (vm, NULL); + u32 now = (u32) vlib_time_now (vm); + /* *INDENT-OFF* */ + pool_foreach (dm, sm->det_maps, + ({ + vec_foreach(ses, dm->sessions) + { + /* Delete if session expired */ + if (ses->in_port && (ses->expire < now)) + snat_det_ses_close (dm, ses); + } + })); + /* *INDENT-ON* */ + } + + return 0; +} + +static vlib_node_registration_t snat_det_expire_walk_node; + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (snat_det_expire_walk_node, static) = { + .function = snat_det_expire_walk_fn, + .type = VLIB_NODE_TYPE_PROCESS, + .name = + "nat-det-expire-walk", +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/nat/nat_det.h b/src/plugins/nat/nat_det.h new file mode 100644 index 00000000000..2ab7f27eece --- /dev/null +++ b/src/plugins/nat/nat_det.h @@ -0,0 +1,196 @@ +/* + * snat_det.h - deterministic NAT definitions + * + * Copyright (c) 2017 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. + */ +/** + * @file + * @brief deterministic NAT definitions + */ + +#ifndef __included_nat_det_h__ +#define __included_nat_det_h__ + +#include <vnet/ip/ip.h> +#include <nat/nat.h> +#include <nat/nat_ipfix_logging.h> + + +#define SNAT_DET_SES_PER_USER 1000 + + +int snat_det_add_map (snat_main_t * sm, ip4_address_t * in_addr, u8 in_plen, + ip4_address_t * out_addr, u8 out_plen, int is_add); + +always_inline int +is_addr_in_net (ip4_address_t * addr, ip4_address_t * net, u8 plen) +{ + if (net->as_u32 == (addr->as_u32 & ip4_main.fib_masks[plen])) + return 1; + return 0; +} + +always_inline snat_det_map_t * +snat_det_map_by_user (snat_main_t * sm, ip4_address_t * user_addr) +{ + snat_det_map_t *dm; + + /* *INDENT-OFF* */ + pool_foreach (dm, sm->det_maps, + ({ + if (is_addr_in_net(user_addr, &dm->in_addr, dm->in_plen)) + return dm; + })); + /* *INDENT-ON* */ + return 0; +} + +always_inline snat_det_map_t * +snat_det_map_by_out (snat_main_t * sm, ip4_address_t * out_addr) +{ + snat_det_map_t *dm; + + /* *INDENT-OFF* */ + pool_foreach (dm, sm->det_maps, + ({ + if (is_addr_in_net(out_addr, &dm->out_addr, dm->out_plen)) + return dm; + })); + /* *INDENT-ON* */ + return 0; +} + +always_inline void +snat_det_forward (snat_det_map_t * dm, ip4_address_t * in_addr, + ip4_address_t * out_addr, u16 * lo_port) +{ + u32 in_offset, out_offset; + + in_offset = clib_net_to_host_u32 (in_addr->as_u32) - + clib_net_to_host_u32 (dm->in_addr.as_u32); + out_offset = in_offset / dm->sharing_ratio; + out_addr->as_u32 = + clib_host_to_net_u32 (clib_net_to_host_u32 (dm->out_addr.as_u32) + + out_offset); + *lo_port = 1024 + dm->ports_per_host * (in_offset % dm->sharing_ratio); +} + +always_inline void +snat_det_reverse (snat_det_map_t * dm, ip4_address_t * out_addr, u16 out_port, + ip4_address_t * in_addr) +{ + u32 in_offset1, in_offset2, out_offset; + + out_offset = clib_net_to_host_u32 (out_addr->as_u32) - + clib_net_to_host_u32 (dm->out_addr.as_u32); + in_offset1 = out_offset * dm->sharing_ratio; + in_offset2 = (out_port - 1024) / dm->ports_per_host; + in_addr->as_u32 = + clib_host_to_net_u32 (clib_net_to_host_u32 (dm->in_addr.as_u32) + + in_offset1 + in_offset2); +} + +always_inline u32 +snat_det_user_ses_offset (ip4_address_t * addr, u8 plen) +{ + return (clib_net_to_host_u32 (addr->as_u32) & pow2_mask (32 - plen)) * + SNAT_DET_SES_PER_USER; +} + +always_inline snat_det_session_t * +snat_det_get_ses_by_out (snat_det_map_t * dm, ip4_address_t * in_addr, + u64 out_key) +{ + u32 user_offset; + u16 i; + + user_offset = snat_det_user_ses_offset (in_addr, dm->in_plen); + for (i = 0; i < SNAT_DET_SES_PER_USER; i++) + { + if (dm->sessions[i + user_offset].out.as_u64 == out_key) + return &dm->sessions[i + user_offset]; + } + + return 0; +} + +always_inline snat_det_session_t * +snat_det_find_ses_by_in (snat_det_map_t * dm, ip4_address_t * in_addr, + u16 in_port, snat_det_out_key_t out_key) +{ + snat_det_session_t *ses; + u32 user_offset; + u16 i; + + user_offset = snat_det_user_ses_offset (in_addr, dm->in_plen); + for (i = 0; i < SNAT_DET_SES_PER_USER; i++) + { + ses = &dm->sessions[i + user_offset]; + if (ses->in_port == in_port && + ses->out.ext_host_addr.as_u32 == out_key.ext_host_addr.as_u32 && + ses->out.ext_host_port == out_key.ext_host_port) + return &dm->sessions[i + user_offset]; + } + + return 0; +} + +always_inline snat_det_session_t * +snat_det_ses_create (snat_det_map_t * dm, ip4_address_t * in_addr, + u16 in_port, snat_det_out_key_t * out) +{ + u32 user_offset; + u16 i; + + user_offset = snat_det_user_ses_offset (in_addr, dm->in_plen); + + for (i = 0; i < SNAT_DET_SES_PER_USER; i++) + { + if (!dm->sessions[i + user_offset].in_port) + { + if (__sync_bool_compare_and_swap + (&dm->sessions[i + user_offset].in_port, 0, in_port)) + { + dm->sessions[i + user_offset].out.as_u64 = out->as_u64; + dm->sessions[i + user_offset].state = SNAT_SESSION_UNKNOWN; + dm->sessions[i + user_offset].expire = 0; + __sync_add_and_fetch (&dm->ses_num, 1); + return &dm->sessions[i + user_offset]; + } + } + } + + snat_ipfix_logging_max_entries_per_user (in_addr->as_u32); + return 0; +} + +always_inline void +snat_det_ses_close (snat_det_map_t * dm, snat_det_session_t * ses) +{ + if (__sync_bool_compare_and_swap (&ses->in_port, ses->in_port, 0)) + { + ses->out.as_u64 = 0; + __sync_add_and_fetch (&dm->ses_num, -1); + } +} + +#endif /* __included_nat_det_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/nat/nat_ipfix_logging.c b/src/plugins/nat/nat_ipfix_logging.c new file mode 100644 index 00000000000..0cc0f82a337 --- /dev/null +++ b/src/plugins/nat/nat_ipfix_logging.c @@ -0,0 +1,835 @@ +/* + * nat_ipfix_logging.c - NAT Events IPFIX logging + * + * Copyright (c) 2016 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 <vnet/flow/flow_report.h> +#include <vlibmemory/api.h> +#include <nat/nat.h> +#include <nat/nat_ipfix_logging.h> + +snat_ipfix_logging_main_t snat_ipfix_logging_main; + +#define NAT44_SESSION_CREATE_LEN 26 +#define NAT_ADDRESSES_EXHAUTED_LEN 13 +#define MAX_ENTRIES_PER_USER_LEN 17 + +#define NAT44_SESSION_CREATE_FIELD_COUNT 8 +#define NAT_ADDRESSES_EXHAUTED_FIELD_COUNT 3 +#define MAX_ENTRIES_PER_USER_FIELD_COUNT 4 + +typedef struct { + u8 nat_event; + u32 src_ip; + u32 nat_src_ip; + snat_protocol_t snat_proto; + u16 src_port; + u16 nat_src_port; + u32 vrf_id; +} snat_ipfix_logging_nat44_ses_args_t; + +typedef struct { + u32 pool_id; +} snat_ipfix_logging_addr_exhausted_args_t; + +typedef struct { + u32 src_ip; +} snat_ipfix_logging_max_entries_per_user_args_t; + +/** + * @brief Create an IPFIX template packet rewrite string + * + * @param frm flow report main + * @param fr flow report + * @param collector_address collector address + * @param src_address source address + * @param collector_port collector + * @param event NAT event ID + * @param quota_event NAT quota exceeded event ID + * + * @returns template packet + */ +static inline u8 * +snat_template_rewrite (flow_report_main_t * frm, + flow_report_t * fr, + ip4_address_t * collector_address, + ip4_address_t * src_address, + u16 collector_port, + nat_event_t event, + quota_exceed_event_t quota_event) +{ + snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main; + ip4_header_t *ip; + udp_header_t *udp; + ipfix_message_header_t *h; + ipfix_set_header_t *s; + ipfix_template_header_t *t; + ipfix_field_specifier_t *f; + ipfix_field_specifier_t *first_field; + u8 *rewrite = 0; + ip4_ipfix_template_packet_t *tp; + u32 field_count = 0; + flow_report_stream_t *stream; + + stream = &frm->streams[fr->stream_index]; + silm->stream_index = fr->stream_index; + + if (event == NAT_ADDRESSES_EXHAUTED) + { + field_count = NAT_ADDRESSES_EXHAUTED_FIELD_COUNT; + silm->addr_exhausted_template_id = fr->template_id; + } + else if (event == NAT44_SESSION_CREATE) + { + field_count = NAT44_SESSION_CREATE_FIELD_COUNT; + silm->nat44_session_template_id = fr->template_id; + } + else if (event == QUOTA_EXCEEDED) + { + if (quota_event == MAX_ENTRIES_PER_USER) + { + field_count = MAX_ENTRIES_PER_USER_FIELD_COUNT; + silm->max_entries_per_user_template_id = fr->template_id; + } + } + + /* allocate rewrite space */ + vec_validate_aligned (rewrite, + sizeof (ip4_ipfix_template_packet_t) + + field_count * sizeof (ipfix_field_specifier_t) - 1, + CLIB_CACHE_LINE_BYTES); + + tp = (ip4_ipfix_template_packet_t *) rewrite; + ip = (ip4_header_t *) & tp->ip4; + udp = (udp_header_t *) (ip + 1); + h = (ipfix_message_header_t *) (udp + 1); + s = (ipfix_set_header_t *) (h + 1); + t = (ipfix_template_header_t *) (s + 1); + first_field = f = (ipfix_field_specifier_t *) (t + 1); + + ip->ip_version_and_header_length = 0x45; + ip->ttl = 254; + ip->protocol = IP_PROTOCOL_UDP; + ip->src_address.as_u32 = src_address->as_u32; + ip->dst_address.as_u32 = collector_address->as_u32; + udp->src_port = clib_host_to_net_u16 (stream->src_port); + udp->dst_port = clib_host_to_net_u16 (collector_port); + udp->length = clib_host_to_net_u16 (vec_len (rewrite) - sizeof (*ip)); + + /* FIXUP: message header export_time */ + h->domain_id = clib_host_to_net_u32 (stream->domain_id); + + /* Add TLVs to the template */ + if (event == NAT_ADDRESSES_EXHAUTED) + { + f->e_id_length = ipfix_e_id_length (0, observationTimeMilliseconds, 8); + f++; + f->e_id_length = ipfix_e_id_length (0, natEvent, 1); + f++; + f->e_id_length = ipfix_e_id_length (0, natPoolId, 4); + f++; + } + else if (event == NAT44_SESSION_CREATE) + { + f->e_id_length = ipfix_e_id_length (0, observationTimeMilliseconds, 8); + f++; + f->e_id_length = ipfix_e_id_length (0, natEvent, 1); + f++; + f->e_id_length = ipfix_e_id_length (0, sourceIPv4Address, 4); + f++; + f->e_id_length = ipfix_e_id_length (0, postNATSourceIPv4Address, 4); + f++; + f->e_id_length = ipfix_e_id_length (0, protocolIdentifier, 1); + f++; + f->e_id_length = ipfix_e_id_length (0, sourceTransportPort, 2); + f++; + f->e_id_length = ipfix_e_id_length (0, postNAPTSourceTransportPort, 2); + f++; + f->e_id_length = ipfix_e_id_length (0, ingressVRFID, 4); + f++; + } + else if (event == QUOTA_EXCEEDED) + { + if (quota_event == MAX_ENTRIES_PER_USER) + { + f->e_id_length = ipfix_e_id_length (0, observationTimeMilliseconds, + 8); + f++; + f->e_id_length = ipfix_e_id_length (0, natEvent, 1); + f++; + f->e_id_length = ipfix_e_id_length (0, natQuotaExceededEvent, 4); + f++; + f->e_id_length = ipfix_e_id_length (0, sourceIPv4Address, 4); + f++; + } + } + + /* Back to the template packet... */ + ip = (ip4_header_t *) & tp->ip4; + udp = (udp_header_t *) (ip + 1); + + ASSERT (f - first_field); + /* Field count in this template */ + t->id_count = ipfix_id_count (fr->template_id, f - first_field); + + /* set length in octets */ + s->set_id_length = + ipfix_set_id_length (2 /* set_id */ , (u8 *) f - (u8 *) s); + + /* message length in octets */ + h->version_length = version_length ((u8 *) f - (u8 *) h); + + ip->length = clib_host_to_net_u16 ((u8 *) f - (u8 *) ip); + ip->checksum = ip4_header_checksum (ip); + + return rewrite; +} + +u8 * +snat_template_rewrite_addr_exhausted (flow_report_main_t * frm, + flow_report_t * fr, + ip4_address_t * collector_address, + ip4_address_t * src_address, + u16 collector_port) +{ + return snat_template_rewrite (frm, fr, collector_address, src_address, + collector_port, NAT_ADDRESSES_EXHAUTED, 0); +} + +u8 * +snat_template_rewrite_nat44_session (flow_report_main_t * frm, + flow_report_t * fr, + ip4_address_t * collector_address, + ip4_address_t * src_address, + u16 collector_port) +{ + return snat_template_rewrite (frm, fr, collector_address, src_address, + collector_port, NAT44_SESSION_CREATE, 0); +} + +u8 * +snat_template_rewrite_max_entries_per_usr (flow_report_main_t * frm, + flow_report_t * fr, + ip4_address_t * collector_address, + ip4_address_t * src_address, + u16 collector_port) +{ + return snat_template_rewrite (frm, fr, collector_address, src_address, + collector_port, QUOTA_EXCEEDED, + MAX_ENTRIES_PER_USER); +} + +static inline void +snat_ipfix_header_create (flow_report_main_t * frm, + vlib_buffer_t * b0, + u32 * offset) +{ + snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main; + flow_report_stream_t *stream; + ip4_ipfix_template_packet_t * tp; + ipfix_message_header_t * h = 0; + ipfix_set_header_t * s = 0; + ip4_header_t * ip; + udp_header_t * udp; + + stream = &frm->streams[silm->stream_index]; + + b0->current_data = 0; + b0->current_length = sizeof (*ip) + sizeof (*udp) + sizeof (*h) + + sizeof (*s); + b0->flags |= (VLIB_BUFFER_TOTAL_LENGTH_VALID | VLIB_BUFFER_FLOW_REPORT); + vnet_buffer (b0)->sw_if_index[VLIB_RX] = 0; + vnet_buffer (b0)->sw_if_index[VLIB_TX] = frm->fib_index; + tp = vlib_buffer_get_current (b0); + ip = (ip4_header_t *) &tp->ip4; + udp = (udp_header_t *) (ip+1); + h = (ipfix_message_header_t *)(udp+1); + s = (ipfix_set_header_t *)(h+1); + + ip->ip_version_and_header_length = 0x45; + ip->ttl = 254; + ip->protocol = IP_PROTOCOL_UDP; + ip->flags_and_fragment_offset = 0; + ip->src_address.as_u32 = frm->src_address.as_u32; + ip->dst_address.as_u32 = frm->ipfix_collector.as_u32; + udp->src_port = clib_host_to_net_u16 (stream->src_port); + udp->dst_port = clib_host_to_net_u16 (frm->collector_port); + udp->checksum = 0; + + h->export_time = clib_host_to_net_u32 ( + (u32) (((f64)frm->unix_time_0) + (vlib_time_now(frm->vlib_main) - + frm->vlib_time_0))); + h->sequence_number = clib_host_to_net_u32 (stream->sequence_number++); + h->domain_id = clib_host_to_net_u32 (stream->domain_id); + + *offset = (u32) (((u8 *)(s+1)) - (u8 *)tp); +} + +static inline void +snat_ipfix_send (flow_report_main_t * frm, + vlib_frame_t * f, + vlib_buffer_t * b0, + u16 template_id) +{ + ip4_ipfix_template_packet_t * tp; + ipfix_message_header_t * h = 0; + ipfix_set_header_t * s = 0; + ip4_header_t * ip; + udp_header_t * udp; + vlib_main_t * vm = frm->vlib_main; + + tp = vlib_buffer_get_current (b0); + ip = (ip4_header_t *) & tp->ip4; + udp = (udp_header_t *) (ip + 1); + h = (ipfix_message_header_t *) (udp + 1); + s = (ipfix_set_header_t *) (h + 1); + + s->set_id_length = ipfix_set_id_length (template_id, + b0->current_length - + (sizeof (*ip) + sizeof (*udp) + + sizeof (*h))); + h->version_length = version_length (b0->current_length - + (sizeof (*ip) + sizeof (*udp))); + + ip->length = clib_host_to_net_u16 (b0->current_length); + ip->checksum = ip4_header_checksum (ip); + udp->length = clib_host_to_net_u16 (b0->current_length - sizeof (*ip)); + + if (frm->udp_checksum) + { + udp->checksum = ip4_tcp_udp_compute_checksum (vm, b0, ip); + if (udp->checksum == 0) + udp->checksum = 0xffff; + } + + ASSERT (ip->checksum == ip4_header_checksum (ip)); + + vlib_put_frame_to_node (vm, ip4_lookup_node.index, f); +} + +static void +snat_ipfix_logging_nat44_ses (u8 nat_event, u32 src_ip, u32 nat_src_ip, + snat_protocol_t snat_proto, u16 src_port, + u16 nat_src_port, u32 vrf_id, int do_flush) +{ + snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main; + flow_report_main_t *frm = &flow_report_main; + vlib_frame_t *f; + vlib_buffer_t *b0 = 0; + u32 bi0 = ~0; + u32 offset; + vlib_main_t * vm = frm->vlib_main; + u64 now; + vlib_buffer_free_list_t *fl; + u8 proto = ~0; + + if (!silm->enabled) + return; + + proto = snat_proto_to_ip_proto (snat_proto); + + now = (u64) ((vlib_time_now (vm) - silm->vlib_time_0) * 1e3); + now += silm->milisecond_time_0; + + b0 = silm->nat44_session_buffer; + + if (PREDICT_FALSE (b0 == 0)) + { + if (do_flush) + return; + + if (vlib_buffer_alloc (vm, &bi0, 1) != 1) + { + clib_warning ("can't allocate buffer for NAT IPFIX event"); + return; + } + + b0 = silm->nat44_session_buffer = + vlib_get_buffer (vm, bi0); + fl = vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX); + vlib_buffer_init_for_free_list (b0, fl); + VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b0); + offset = 0; + } + else + { + bi0 = vlib_get_buffer_index (vm, b0); + offset = silm->nat44_session_next_record_offset; + } + + f = silm->nat44_session_frame; + if (PREDICT_FALSE (f == 0)) + { + u32 * to_next; + f = vlib_get_frame_to_node (vm, ip4_lookup_node.index); + silm->nat44_session_frame = f; + to_next = vlib_frame_vector_args (f); + to_next[0] = bi0; + f->n_vectors = 1; + } + + if (PREDICT_FALSE (offset == 0)) + snat_ipfix_header_create (frm, b0, &offset); + + if (PREDICT_TRUE (do_flush == 0)) + { + u64 time_stamp = clib_host_to_net_u64 (now); + clib_memcpy (b0->data + offset, &time_stamp, sizeof (time_stamp)); + offset += sizeof (time_stamp); + + clib_memcpy (b0->data + offset, &nat_event, sizeof (nat_event)); + offset += sizeof (nat_event); + + clib_memcpy (b0->data + offset, &src_ip, sizeof (src_ip)); + offset += sizeof (src_ip); + + clib_memcpy (b0->data + offset, &nat_src_ip, sizeof (nat_src_ip)); + offset += sizeof (nat_src_ip); + + clib_memcpy (b0->data + offset, &proto, sizeof (proto)); + offset += sizeof (proto); + + clib_memcpy (b0->data + offset, &src_port, sizeof (src_port)); + offset += sizeof (src_port); + + clib_memcpy (b0->data + offset, &nat_src_port, sizeof (nat_src_port)); + offset += sizeof (nat_src_port); + + clib_memcpy (b0->data + offset, &vrf_id, sizeof(vrf_id)); + offset += sizeof (vrf_id); + + b0->current_length += NAT44_SESSION_CREATE_LEN; + } + + if (PREDICT_FALSE (do_flush || (offset + NAT44_SESSION_CREATE_LEN) > frm->path_mtu)) + { + snat_ipfix_send (frm, f, b0, silm->nat44_session_template_id); + silm->nat44_session_frame = 0; + silm->nat44_session_buffer = 0; + offset = 0; + } + silm->nat44_session_next_record_offset = offset; + } + +static void +snat_ipfix_logging_addr_exhausted (u32 pool_id, int do_flush) +{ + snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main; + flow_report_main_t *frm = &flow_report_main; + vlib_frame_t *f; + vlib_buffer_t *b0 = 0; + u32 bi0 = ~0; + u32 offset; + vlib_main_t * vm = frm->vlib_main; + u64 now; + vlib_buffer_free_list_t *fl; + u8 nat_event = NAT_ADDRESSES_EXHAUTED; + + if (!silm->enabled) + return; + + now = (u64) ((vlib_time_now (vm) - silm->vlib_time_0) * 1e3); + now += silm->milisecond_time_0; + + b0 = silm->addr_exhausted_buffer; + + if (PREDICT_FALSE (b0 == 0)) + { + if (do_flush) + return; + + if (vlib_buffer_alloc (vm, &bi0, 1) != 1) + { + clib_warning ("can't allocate buffer for NAT IPFIX event"); + return; + } + + b0 = silm->addr_exhausted_buffer = + vlib_get_buffer (vm, bi0); + fl = vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX); + vlib_buffer_init_for_free_list (b0, fl); + VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b0); + offset = 0; + } + else + { + bi0 = vlib_get_buffer_index (vm, b0); + offset = silm->addr_exhausted_next_record_offset; + } + + f = silm->addr_exhausted_frame; + if (PREDICT_FALSE (f == 0)) + { + u32 * to_next; + f = vlib_get_frame_to_node (vm, ip4_lookup_node.index); + silm->addr_exhausted_frame = f; + to_next = vlib_frame_vector_args (f); + to_next[0] = bi0; + f->n_vectors = 1; + } + + if (PREDICT_FALSE (offset == 0)) + snat_ipfix_header_create (frm, b0, &offset); + + if (PREDICT_TRUE (do_flush == 0)) + { + u64 time_stamp = clib_host_to_net_u64 (now); + clib_memcpy (b0->data + offset, &time_stamp, sizeof (time_stamp)); + offset += sizeof (time_stamp); + + clib_memcpy (b0->data + offset, &nat_event, sizeof (nat_event)); + offset += sizeof (nat_event); + + clib_memcpy (b0->data + offset, &pool_id, sizeof(pool_id)); + offset += sizeof (pool_id); + + b0->current_length += NAT_ADDRESSES_EXHAUTED_LEN; + } + + if (PREDICT_FALSE (do_flush || (offset + NAT_ADDRESSES_EXHAUTED_LEN) > frm->path_mtu)) + { + snat_ipfix_send (frm, f, b0, silm->addr_exhausted_template_id); + silm->addr_exhausted_frame = 0; + silm->addr_exhausted_buffer = 0; + offset = 0; + } + silm->addr_exhausted_next_record_offset = offset; +} + +static void +snat_ipfix_logging_max_entries_per_usr (u32 src_ip, int do_flush) +{ + snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main; + flow_report_main_t *frm = &flow_report_main; + vlib_frame_t *f; + vlib_buffer_t *b0 = 0; + u32 bi0 = ~0; + u32 offset; + vlib_main_t * vm = frm->vlib_main; + u64 now; + vlib_buffer_free_list_t *fl; + u8 nat_event = QUOTA_EXCEEDED; + u32 quota_event = MAX_ENTRIES_PER_USER; + + if (!silm->enabled) + return; + + now = (u64) ((vlib_time_now (vm) - silm->vlib_time_0) * 1e3); + now += silm->milisecond_time_0; + + b0 = silm->max_entries_per_user_buffer; + + if (PREDICT_FALSE (b0 == 0)) + { + if (do_flush) + return; + + if (vlib_buffer_alloc (vm, &bi0, 1) != 1) + { + clib_warning ("can't allocate buffer for NAT IPFIX event"); + return; + } + + b0 = silm->max_entries_per_user_buffer = + vlib_get_buffer (vm, bi0); + fl = vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX); + vlib_buffer_init_for_free_list (b0, fl); + VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b0); + offset = 0; + } + else + { + bi0 = vlib_get_buffer_index (vm, b0); + offset = silm->max_entries_per_user_next_record_offset; + } + + f = silm->max_entries_per_user_frame; + if (PREDICT_FALSE (f == 0)) + { + u32 * to_next; + f = vlib_get_frame_to_node (vm, ip4_lookup_node.index); + silm->max_entries_per_user_frame = f; + to_next = vlib_frame_vector_args (f); + to_next[0] = bi0; + f->n_vectors = 1; + } + + if (PREDICT_FALSE (offset == 0)) + snat_ipfix_header_create (frm, b0, &offset); + + if (PREDICT_TRUE (do_flush == 0)) + { + u64 time_stamp = clib_host_to_net_u64 (now); + clib_memcpy (b0->data + offset, &time_stamp, sizeof (time_stamp)); + offset += sizeof (time_stamp); + + clib_memcpy (b0->data + offset, &nat_event, sizeof (nat_event)); + offset += sizeof (nat_event); + + clib_memcpy (b0->data + offset, "a_event, sizeof(quota_event)); + offset += sizeof (quota_event); + + clib_memcpy (b0->data + offset, &src_ip, sizeof (src_ip)); + offset += sizeof (src_ip); + + b0->current_length += MAX_ENTRIES_PER_USER_LEN; + } + + if (PREDICT_FALSE (do_flush || (offset + MAX_ENTRIES_PER_USER_LEN) > frm->path_mtu)) + { + snat_ipfix_send (frm, f, b0, silm->max_entries_per_user_template_id); + silm->max_entries_per_user_frame = 0; + silm->max_entries_per_user_buffer = 0; + offset = 0; + } + silm->max_entries_per_user_next_record_offset = offset; +} + +static void +snat_ipfix_logging_nat44_ses_rpc_cb (snat_ipfix_logging_nat44_ses_args_t *a) +{ + snat_ipfix_logging_nat44_ses(a->nat_event, a->src_ip, a->nat_src_ip, + a->snat_proto, a->src_port, a->nat_src_port, + a->vrf_id, 0); +} + +/** + * @brief Generate NAT44 session create event + * + * @param src_ip source IPv4 address + * @param nat_src_ip transaltes source IPv4 address + * @param snat_proto NAT transport protocol + * @param src_port source port + * @param nat_src_port translated source port + * @param vrf_id VRF ID + */ +void +snat_ipfix_logging_nat44_ses_create (u32 src_ip, + u32 nat_src_ip, + snat_protocol_t snat_proto, + u16 src_port, + u16 nat_src_port, + u32 vrf_id) +{ + snat_ipfix_logging_nat44_ses_args_t a; + + a.nat_event = NAT44_SESSION_CREATE; + a.src_ip = src_ip; + a.nat_src_ip = nat_src_ip; + a.snat_proto = snat_proto; + a.src_port = src_port; + a.nat_src_port = nat_src_port; + a.vrf_id = vrf_id; + + vl_api_rpc_call_main_thread (snat_ipfix_logging_nat44_ses_rpc_cb, (u8 *) &a, + sizeof (a)); +} + +/** + * @brief Generate NAT44 session delete event + * + * @param src_ip source IPv4 address + * @param nat_src_ip transaltes source IPv4 address + * @param snat_proto NAT transport protocol + * @param src_port source port + * @param nat_src_port translated source port + * @param vrf_id VRF ID + */ +void +snat_ipfix_logging_nat44_ses_delete (u32 src_ip, + u32 nat_src_ip, + snat_protocol_t snat_proto, + u16 src_port, + u16 nat_src_port, + u32 vrf_id) +{ + snat_ipfix_logging_nat44_ses_args_t a; + + a.nat_event = NAT44_SESSION_DELETE; + a.src_ip = src_ip; + a.nat_src_ip = nat_src_ip; + a.snat_proto = snat_proto; + a.src_port = src_port; + a.nat_src_port = nat_src_port; + a.vrf_id = vrf_id; + + vl_api_rpc_call_main_thread (snat_ipfix_logging_nat44_ses_rpc_cb, (u8 *) &a, + sizeof (a)); +} + +vlib_frame_t * +snat_data_callback_nat44_session (flow_report_main_t * frm, + flow_report_t * fr, + vlib_frame_t * f, + u32 * to_next, + u32 node_index) +{ + snat_ipfix_logging_nat44_ses(0, 0, 0, 0, 0, 0, 0, 1); + return f; +} + +static void +snat_ipfix_logging_addr_exhausted_rpc_cb + (snat_ipfix_logging_addr_exhausted_args_t * a) +{ + snat_ipfix_logging_addr_exhausted(a->pool_id, 0); +} + +/** + * @brief Generate NAT addresses exhausted event + * + * @param pool_id NAT pool ID + */ +void +snat_ipfix_logging_addresses_exhausted(u32 pool_id) +{ + //TODO: This event SHOULD be rate limited + snat_ipfix_logging_addr_exhausted_args_t a; + + a.pool_id = pool_id; + + vl_api_rpc_call_main_thread (snat_ipfix_logging_addr_exhausted_rpc_cb, + (u8 *) &a, sizeof (a)); +} + +vlib_frame_t * +snat_data_callback_addr_exhausted (flow_report_main_t * frm, + flow_report_t * fr, + vlib_frame_t * f, + u32 * to_next, + u32 node_index) +{ + snat_ipfix_logging_addr_exhausted(0, 1); + return f; +} + +static void +snat_ipfix_logging_max_entries_per_usr_rpc_cb + (snat_ipfix_logging_max_entries_per_user_args_t * a) +{ + snat_ipfix_logging_max_entries_per_usr(a->src_ip, 0); +} + +/** + * @brief Generate maximum entries per user exceeded event + * + * @param src_ip source IPv4 address + */ +void +snat_ipfix_logging_max_entries_per_user(u32 src_ip) +{ + //TODO: This event SHOULD be rate limited + snat_ipfix_logging_max_entries_per_user_args_t a; + + a.src_ip = src_ip; + + vl_api_rpc_call_main_thread (snat_ipfix_logging_max_entries_per_usr_rpc_cb, + (u8 *) &a, sizeof (a)); +} + +vlib_frame_t * +snat_data_callback_max_entries_per_usr (flow_report_main_t * frm, + flow_report_t * fr, + vlib_frame_t * f, + u32 * to_next, + u32 node_index) +{ + snat_ipfix_logging_max_entries_per_usr(0, 1); + return f; +} + +/** + * @brief Enable/disable NAT plugin IPFIX logging + * + * @param enable 1 if enable, 0 if disable + * @param domain_id observation domain ID + * @param src_port source port number + * + * @returns 0 if success + */ +int +snat_ipfix_logging_enable_disable (int enable, u32 domain_id, u16 src_port) +{ + snat_main_t * sm = &snat_main; + snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main; + flow_report_main_t *frm = &flow_report_main; + vnet_flow_report_add_del_args_t a; + int rv; + u8 e = enable ? 1 : 0; + + if (silm->enabled == e) + return 0; + + silm->enabled = e; + + memset (&a, 0, sizeof (a)); + a.is_add = enable; + a.domain_id = domain_id ? domain_id : 1; + a.src_port = src_port ? src_port : UDP_DST_PORT_ipfix; + + if (sm->deterministic) + { + a.rewrite_callback = snat_template_rewrite_max_entries_per_usr; + a.flow_data_callback = snat_data_callback_max_entries_per_usr; + + rv = vnet_flow_report_add_del (frm, &a, NULL); + if (rv) + { + clib_warning ("vnet_flow_report_add_del returned %d", rv); + return -1; + } + } + else + { + a.rewrite_callback = snat_template_rewrite_nat44_session; + a.flow_data_callback = snat_data_callback_nat44_session; + + rv = vnet_flow_report_add_del (frm, &a, NULL); + if (rv) + { + clib_warning ("vnet_flow_report_add_del returned %d", rv); + return -1; + } + + a.rewrite_callback = snat_template_rewrite_addr_exhausted; + a.flow_data_callback = snat_data_callback_addr_exhausted; + + rv = vnet_flow_report_add_del (frm, &a, NULL); + if (rv) + { + clib_warning ("vnet_flow_report_add_del returned %d", rv); + return -1; + } + } + + return 0; +} + +/** + * @brief Initialize NAT plugin IPFIX logging + * + * @param vm vlib main + */ +void +snat_ipfix_logging_init (vlib_main_t * vm) +{ + snat_ipfix_logging_main_t *silm = &snat_ipfix_logging_main; + + silm->enabled = 0; + + /* Set up time reference pair */ + silm->vlib_time_0 = vlib_time_now (vm); + silm->milisecond_time_0 = unix_time_now_nsec () * 1e-6; +} diff --git a/src/plugins/nat/nat_ipfix_logging.h b/src/plugins/nat/nat_ipfix_logging.h new file mode 100644 index 00000000000..6dbf6627fba --- /dev/null +++ b/src/plugins/nat/nat_ipfix_logging.h @@ -0,0 +1,79 @@ +/* + * nat_ipfix_logging.h - NAT Events IPFIX logging + * + * Copyright (c) 2016 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. + */ +#ifndef __included_nat_ipfix_logging_h__ +#define __included_nat_ipfix_logging_h__ + +typedef enum { + NAT_ADDRESSES_EXHAUTED = 3, + NAT44_SESSION_CREATE = 4, + NAT44_SESSION_DELETE = 5, + NAT_PORTS_EXHAUSTED = 12, + QUOTA_EXCEEDED = 13, +} nat_event_t; + +typedef enum { + MAX_ENTRIES_PER_USER = 3, +} quota_exceed_event_t; + +typedef struct { + /** NAT plugin IPFIX logging enabled */ + u8 enabled; + + /** ipfix buffers under construction */ + vlib_buffer_t *nat44_session_buffer; + vlib_buffer_t *addr_exhausted_buffer; + vlib_buffer_t *max_entries_per_user_buffer; + + /** frames containing ipfix buffers */ + vlib_frame_t *nat44_session_frame; + vlib_frame_t *addr_exhausted_frame; + vlib_frame_t *max_entries_per_user_frame; + + /** next record offset */ + u32 nat44_session_next_record_offset; + u32 addr_exhausted_next_record_offset; + u32 max_entries_per_user_next_record_offset; + + /** Time reference pair */ + u64 milisecond_time_0; + f64 vlib_time_0; + + /** template IDs */ + u16 nat44_session_template_id; + u16 addr_exhausted_template_id; + u16 max_entries_per_user_template_id; + + /** stream index */ + u32 stream_index; +} snat_ipfix_logging_main_t; + +extern snat_ipfix_logging_main_t snat_ipfix_logging_main; + +void snat_ipfix_logging_init (vlib_main_t * vm); +int snat_ipfix_logging_enable_disable (int enable, u32 domain_id, u16 src_port); +void snat_ipfix_logging_nat44_ses_create (u32 src_ip, u32 nat_src_ip, + snat_protocol_t snat_proto, + u16 src_port, u16 nat_src_port, + u32 vrf_id); +void snat_ipfix_logging_nat44_ses_delete (u32 src_ip, u32 nat_src_ip, + snat_protocol_t snat_proto, + u16 src_port, u16 nat_src_port, + u32 vrf_id); +void snat_ipfix_logging_addresses_exhausted(u32 pool_id); +void snat_ipfix_logging_max_entries_per_user(u32 src_ip); + +#endif /* __included_nat_ipfix_logging_h__ */ diff --git a/src/plugins/nat/nat_msg_enum.h b/src/plugins/nat/nat_msg_enum.h new file mode 100644 index 00000000000..710b631c73d --- /dev/null +++ b/src/plugins/nat/nat_msg_enum.h @@ -0,0 +1,31 @@ + +/* + * nat_msg_enum.h - skeleton vpp engine plug-in message enumeration + * + * Copyright (c) <current-year> <your-organization> + * 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. + */ +#ifndef included_nat_msg_enum_h +#define included_nat_msg_enum_h + +#include <vppinfra/byte_order.h> + +#define vl_msg_id(n,h) n, +typedef enum { +#include <nat/nat_all_api_h.h> + /* We'll want to know how many messages IDs we need... */ + VL_MSG_FIRST_AVAILABLE, +} vl_msg_id_t; +#undef vl_msg_id + +#endif /* included_nat_msg_enum_h */ diff --git a/src/plugins/nat/nat_test.c b/src/plugins/nat/nat_test.c new file mode 100644 index 00000000000..b653b77e40c --- /dev/null +++ b/src/plugins/nat/nat_test.c @@ -0,0 +1,1167 @@ + +/* + * nat.c - skeleton vpp-api-test plug-in + * + * Copyright (c) <current-year> <your-organization> + * 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 <vat/vat.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <vlibsocket/api.h> +#include <vppinfra/error.h> +#include <vnet/ip/ip.h> +#include <nat/nat.h> + +#define __plugin_msg_base snat_test_main.msg_id_base +#include <vlibapi/vat_helper_macros.h> + +uword unformat_sw_if_index (unformat_input_t * input, va_list * args); + +/* Declare message IDs */ +#include <nat/nat_msg_enum.h> + +/* define message structures */ +#define vl_typedefs +#include <nat/nat_all_api_h.h> +#undef vl_typedefs + +/* declare message handlers for each api */ + +#define vl_endianfun /* define message structures */ +#include <nat/nat_all_api_h.h> +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) +#define vl_printfun +#include <nat/nat_all_api_h.h> +#undef vl_printfun + +/* Get the API version number. */ +#define vl_api_version(n,v) static u32 api_version=(v); +#include <nat/nat_all_api_h.h> +#undef vl_api_version + +typedef struct { + /* API message ID base */ + u16 msg_id_base; + vat_main_t *vat_main; +} snat_test_main_t; + +snat_test_main_t snat_test_main; + +#define foreach_standard_reply_retval_handler \ +_(snat_add_address_range_reply) \ +_(snat_interface_add_del_feature_reply) \ +_(snat_add_static_mapping_reply) \ +_(snat_set_workers_reply) \ +_(snat_add_del_interface_addr_reply) \ +_(snat_ipfix_enable_disable_reply) \ +_(snat_add_det_map_reply) \ +_(snat_det_set_timeouts_reply) \ +_(snat_det_close_session_out_reply) \ +_(snat_det_close_session_in_reply) + +#define _(n) \ + static void vl_api_##n##_t_handler \ + (vl_api_##n##_t * mp) \ + { \ + vat_main_t * vam = snat_test_main.vat_main; \ + i32 retval = ntohl(mp->retval); \ + if (vam->async_mode) { \ + vam->async_errors += (retval < 0); \ + } else { \ + vam->retval = retval; \ + vam->result_ready = 1; \ + } \ + } +foreach_standard_reply_retval_handler; +#undef _ + +/* + * Table of message reply handlers, must include boilerplate handlers + * we just generated + */ +#define foreach_vpe_api_reply_msg \ +_(SNAT_ADD_ADDRESS_RANGE_REPLY, snat_add_address_range_reply) \ +_(SNAT_INTERFACE_ADD_DEL_FEATURE_REPLY, \ + snat_interface_add_del_feature_reply) \ +_(SNAT_ADD_STATIC_MAPPING_REPLY, snat_add_static_mapping_reply) \ +_(SNAT_CONTROL_PING_REPLY, snat_control_ping_reply) \ +_(SNAT_STATIC_MAPPING_DETAILS, snat_static_mapping_details) \ +_(SNAT_SHOW_CONFIG_REPLY, snat_show_config_reply) \ +_(SNAT_ADDRESS_DETAILS, snat_address_details) \ +_(SNAT_INTERFACE_DETAILS, snat_interface_details) \ +_(SNAT_SET_WORKERS_REPLY, snat_set_workers_reply) \ +_(SNAT_WORKER_DETAILS, snat_worker_details) \ +_(SNAT_ADD_DEL_INTERFACE_ADDR_REPLY, \ + snat_add_del_interface_addr_reply) \ +_(SNAT_INTERFACE_ADDR_DETAILS, snat_interface_addr_details) \ +_(SNAT_IPFIX_ENABLE_DISABLE_REPLY, \ + snat_ipfix_enable_disable_reply) \ +_(SNAT_USER_DETAILS, snat_user_details) \ +_(SNAT_USER_SESSION_DETAILS, snat_user_session_details) \ +_(SNAT_ADD_DET_MAP_REPLY, snat_add_det_map_reply) \ +_(SNAT_DET_FORWARD_REPLY, snat_det_forward_reply) \ +_(SNAT_DET_REVERSE_REPLY, snat_det_reverse_reply) \ +_(SNAT_DET_MAP_DETAILS, snat_det_map_details) \ +_(SNAT_DET_SET_TIMEOUTS_REPLY, snat_det_set_timeouts_reply) \ +_(SNAT_DET_GET_TIMEOUTS_REPLY, snat_det_get_timeouts_reply) \ +_(SNAT_DET_CLOSE_SESSION_OUT_REPLY, \ + snat_det_close_session_out_reply) \ +_(SNAT_DET_CLOSE_SESSION_IN_REPLY, \ + snat_det_close_session_in_reply) \ +_(SNAT_DET_SESSION_DETAILS, snat_det_session_details) + +static int api_snat_add_address_range (vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + ip4_address_t start_addr, end_addr; + u32 start_host_order, end_host_order; + vl_api_snat_add_address_range_t * mp; + u8 is_add = 1; + int count; + int ret; + + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "%U - %U", + unformat_ip4_address, &start_addr, + unformat_ip4_address, &end_addr)) + ; + else if (unformat (i, "%U", unformat_ip4_address, &start_addr)) + end_addr = start_addr; + else if (unformat (i, "del")) + is_add = 0; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + } + + start_host_order = clib_host_to_net_u32 (start_addr.as_u32); + end_host_order = clib_host_to_net_u32 (end_addr.as_u32); + + if (end_host_order < start_host_order) + { + errmsg ("end address less than start address\n"); + return -99; + } + + count = (end_host_order - start_host_order) + 1; + + if (count > 1024) + { + errmsg ("%U - %U, %d addresses...\n", + format_ip4_address, &start_addr, + format_ip4_address, &end_addr, + count); + } + + M(SNAT_ADD_ADDRESS_RANGE, mp); + + memcpy (mp->first_ip_address, &start_addr, 4); + memcpy (mp->last_ip_address, &end_addr, 4); + mp->is_ip4 = 1; + mp->is_add = is_add; + + S(mp); + W (ret); + return ret; +} + +static int api_snat_interface_add_del_feature (vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + vl_api_snat_interface_add_del_feature_t * mp; + u32 sw_if_index; + u8 sw_if_index_set = 0; + u8 is_inside = 1; + u8 is_add = 1; + int ret; + + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index)) + sw_if_index_set = 1; + else if (unformat (i, "sw_if_index %d", &sw_if_index)) + sw_if_index_set = 1; + else if (unformat (i, "out")) + is_inside = 0; + else if (unformat (i, "in")) + is_inside = 1; + else if (unformat (i, "del")) + is_add = 0; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + } + + if (sw_if_index_set == 0) + { + errmsg ("interface / sw_if_index required\n"); + return -99; + } + + M(SNAT_INTERFACE_ADD_DEL_FEATURE, mp); + mp->sw_if_index = ntohl(sw_if_index); + mp->is_add = is_add; + mp->is_inside = is_inside; + + S(mp); + W (ret); + return ret; +} + +static int api_snat_add_static_mapping(vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + vl_api_snat_add_static_mapping_t * mp; + u8 external_addr_set = 0; + u8 local_addr_set = 0; + u8 is_add = 1; + u8 addr_only = 1; + ip4_address_t local_addr, external_addr; + u32 local_port = 0, external_port = 0, vrf_id = ~0; + u32 sw_if_index = ~0; + u8 sw_if_index_set = 0; + u32 proto = ~0; + u8 proto_set = 0; + int ret; + + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "local_addr %U", unformat_ip4_address, &local_addr)) + local_addr_set = 1; + else if (unformat (i, "external_addr %U", unformat_ip4_address, + &external_addr)) + external_addr_set = 1; + else if (unformat (i, "local_port %u", &local_port)) + addr_only = 0; + else if (unformat (i, "external_port %u", &external_port)) + addr_only = 0; + else if (unformat (i, "external_if %U", unformat_sw_if_index, vam, + &sw_if_index)) + sw_if_index_set = 1; + else if (unformat (i, "external_sw_if_index %d", &sw_if_index)) + sw_if_index_set = 1; + else if (unformat (i, "vrf %u", &vrf_id)) + ; + else if (unformat (i, "protocol %u", &proto)) + proto_set = 1; + else if (unformat (i, "del")) + is_add = 0; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + } + + if (!addr_only && !proto_set) + { + errmsg ("protocol required\n"); + return -99; + } + + if (!local_addr_set) + { + errmsg ("local addr required\n"); + return -99; + } + if (!external_addr_set && !sw_if_index_set) + { + errmsg ("external addr or interface required\n"); + return -99; + } + + M(SNAT_ADD_STATIC_MAPPING, mp); + mp->is_add = is_add; + mp->is_ip4 = 1; + mp->addr_only = addr_only; + mp->local_port = ntohs ((u16) local_port); + mp->external_port = ntohs ((u16) external_port); + mp->external_sw_if_index = ntohl (sw_if_index); + mp->vrf_id = ntohl (vrf_id); + mp->protocol = (u8) proto; + memcpy (mp->local_ip_address, &local_addr, 4); + memcpy (mp->external_ip_address, &external_addr, 4); + + S(mp); + W (ret); + return ret; +} + +static void vl_api_snat_control_ping_reply_t_handler + (vl_api_snat_control_ping_reply_t * mp) +{ + vat_main_t *vam = &vat_main; + i32 retval = ntohl (mp->retval); + if (vam->async_mode) + { + vam->async_errors += (retval < 0); + } + else + { + vam->retval = retval; + vam->result_ready = 1; + } +} + +static void vl_api_snat_static_mapping_details_t_handler + (vl_api_snat_static_mapping_details_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + + if (mp->addr_only && mp->external_sw_if_index != ~0) + fformat (vam->ofp, "%15U%6s%15d%6s%11d%6d\n", + format_ip4_address, &mp->local_ip_address, "", + ntohl (mp->external_sw_if_index), "", + ntohl (mp->vrf_id), + mp->protocol); + else if (mp->addr_only && mp->external_sw_if_index == ~0) + fformat (vam->ofp, "%15U%6s%15U%6s%11d%6d\n", + format_ip4_address, &mp->local_ip_address, "", + format_ip4_address, &mp->external_ip_address, "", + ntohl (mp->vrf_id), + mp->protocol); + else if (!mp->addr_only && mp->external_sw_if_index != ~0) + fformat (vam->ofp, "%15U%6d%15d%6d%11d%6d\n", + format_ip4_address, &mp->local_ip_address, + ntohs (mp->local_port), + ntohl (mp->external_sw_if_index), + ntohs (mp->external_port), + ntohl (mp->vrf_id), + mp->protocol); + else + fformat (vam->ofp, "%15U%6d%15U%6d%11d%6d\n", + format_ip4_address, &mp->local_ip_address, + ntohs (mp->local_port), + format_ip4_address, &mp->external_ip_address, + ntohs (mp->external_port), + ntohl (mp->vrf_id), + mp->protocol); + +} + +static int api_snat_static_mapping_dump(vat_main_t * vam) +{ + vl_api_snat_static_mapping_dump_t * mp; + vl_api_snat_control_ping_t *mp_ping; + int ret; + + if (vam->json_output) + { + clib_warning ("JSON output not supported for snat_static_mapping_dump"); + return -99; + } + + fformat (vam->ofp, "%21s%21s\n", "local", "external"); + fformat (vam->ofp, "%15s%6s%15s%6s%11s%6s\n", "address", "port", + "address/if_idx", "port", "vrf", "proto"); + + M(SNAT_STATIC_MAPPING_DUMP, mp); + S(mp); + + /* Use a control ping for synchronization */ + M(SNAT_CONTROL_PING, mp_ping); + S(mp_ping); + + W (ret); + return ret; +} + +static void vl_api_snat_show_config_reply_t_handler + (vl_api_snat_show_config_reply_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + i32 retval = ntohl (mp->retval); + + if (retval >= 0) + { + fformat (vam->ofp, "translation hash buckets %d\n", + ntohl (mp->translation_buckets)); + fformat (vam->ofp, "translation hash memory %d\n", + ntohl (mp->translation_memory_size)); + fformat (vam->ofp, "user hash buckets %d\n", ntohl (mp->user_buckets)); + fformat (vam->ofp, "user hash memory %d\n", ntohl (mp->user_memory_size)); + fformat (vam->ofp, "max translations per user %d\n", + ntohl (mp->max_translations_per_user)); + fformat (vam->ofp, "outside VRF id %d\n", ntohl (mp->outside_vrf_id)); + fformat (vam->ofp, "inside VRF id %d\n", ntohl (mp->inside_vrf_id)); + if (mp->static_mapping_only) + { + fformat (vam->ofp, "static mapping only"); + if (mp->static_mapping_connection_tracking) + fformat (vam->ofp, " connection tracking"); + fformat (vam->ofp, "\n"); + } + } + vam->retval = retval; + vam->result_ready = 1; +} + +static int api_snat_show_config(vat_main_t * vam) +{ + vl_api_snat_show_config_t * mp; + int ret; + + if (vam->json_output) + { + clib_warning ("JSON output not supported for snat_show_config"); + return -99; + } + + M(SNAT_SHOW_CONFIG, mp); + S(mp); + W (ret); + return ret; +} + +static void vl_api_snat_address_details_t_handler + (vl_api_snat_address_details_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + + fformat (vam->ofp, "%U\n", format_ip4_address, &mp->ip_address); +} + +static int api_snat_address_dump(vat_main_t * vam) +{ + vl_api_snat_address_dump_t * mp; + vl_api_snat_control_ping_t *mp_ping; + int ret; + + if (vam->json_output) + { + clib_warning ("JSON output not supported for snat_address_dump"); + return -99; + } + + M(SNAT_ADDRESS_DUMP, mp); + S(mp); + + /* Use a control ping for synchronization */ + M(SNAT_CONTROL_PING, mp_ping); + S(mp_ping); + + W (ret); + return ret; +} + +static void vl_api_snat_interface_details_t_handler + (vl_api_snat_interface_details_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + + fformat (vam->ofp, "sw_if_index %d %s\n", ntohl (mp->sw_if_index), + mp->is_inside ? "in" : "out"); +} + +static int api_snat_interface_dump(vat_main_t * vam) +{ + vl_api_snat_interface_dump_t * mp; + vl_api_snat_control_ping_t *mp_ping; + int ret; + + if (vam->json_output) + { + clib_warning ("JSON output not supported for snat_address_dump"); + return -99; + } + + M(SNAT_INTERFACE_DUMP, mp); + S(mp); + + /* Use a control ping for synchronization */ + M(SNAT_CONTROL_PING, mp_ping); + S(mp_ping); + + W (ret); + return ret; +} + +static int api_snat_set_workers (vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + vl_api_snat_set_workers_t * mp; + uword *bitmap; + int ret; + + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "%U", unformat_bitmap_list, &bitmap)) + ; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + } + + M(SNAT_SET_WORKERS, mp); + mp->worker_mask = clib_host_to_net_u64 (bitmap[0]); + + S(mp); + W (ret); + return ret; +} + +static void vl_api_snat_worker_details_t_handler + (vl_api_snat_worker_details_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + + fformat (vam->ofp, "worker_index %d (%s at lcore %u)\n", + ntohl (mp->worker_index), mp->name, ntohl (mp->lcore_id)); +} + +static int api_snat_worker_dump(vat_main_t * vam) +{ + vl_api_snat_worker_dump_t * mp; + vl_api_snat_control_ping_t *mp_ping; + int ret; + + if (vam->json_output) + { + clib_warning ("JSON output not supported for snat_address_dump"); + return -99; + } + + M(SNAT_WORKER_DUMP, mp); + S(mp); + + /* Use a control ping for synchronization */ + M(SNAT_CONTROL_PING, mp_ping); + S(mp_ping); + + W (ret); + return ret; +} + +static int api_snat_add_del_interface_addr (vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + vl_api_snat_add_del_interface_addr_t * mp; + u32 sw_if_index; + u8 sw_if_index_set = 0; + u8 is_add = 1; + int ret; + + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index)) + sw_if_index_set = 1; + else if (unformat (i, "sw_if_index %d", &sw_if_index)) + sw_if_index_set = 1; + else if (unformat (i, "del")) + is_add = 0; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + } + + if (sw_if_index_set == 0) + { + errmsg ("interface / sw_if_index required\n"); + return -99; + } + + M(SNAT_ADD_DEL_INTERFACE_ADDR, mp); + mp->sw_if_index = ntohl(sw_if_index); + mp->is_add = is_add; + + S(mp); + W (ret); + return ret; +} + +static void vl_api_snat_interface_addr_details_t_handler + (vl_api_snat_interface_addr_details_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + + fformat (vam->ofp, "sw_if_index %d\n", ntohl (mp->sw_if_index)); +} + +static int api_snat_interface_addr_dump(vat_main_t * vam) +{ + vl_api_snat_interface_addr_dump_t * mp; + vl_api_snat_control_ping_t *mp_ping; + int ret; + + if (vam->json_output) + { + clib_warning ("JSON output not supported for snat_address_dump"); + return -99; + } + + M(SNAT_INTERFACE_ADDR_DUMP, mp); + S(mp); + + /* Use a control ping for synchronization */ + M(SNAT_CONTROL_PING, mp_ping); + S(mp_ping); + + W (ret); + return ret; +} + +static int api_snat_ipfix_enable_disable (vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + vl_api_snat_ipfix_enable_disable_t * mp; + u32 domain_id = 0; + u32 src_port = 0; + u8 enable = 1; + int ret; + + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "domain %d", &domain_id)) + ; + else if (unformat (i, "src_port %d", &src_port)) + ; + else if (unformat (i, "disable")) + enable = 0; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + } + + M(SNAT_IPFIX_ENABLE_DISABLE, mp); + mp->domain_id = htonl(domain_id); + mp->src_port = htons((u16) src_port); + mp->enable = enable; + + S(mp); + W (ret); + return ret; +} + +static void vl_api_snat_user_session_details_t_handler + (vl_api_snat_user_session_details_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + + fformat(vam->ofp, "%s session %U:%d to %U:%d protocol id %d " + "total packets %d total bytes %d\n", + mp->is_static ? "static" : "dynamic", + format_ip4_address, mp->inside_ip_address, ntohl(mp->inside_port), + format_ip4_address, mp->outside_ip_address, ntohl(mp->outside_port), + ntohl(mp->protocol), ntohl(mp->total_pkts), ntohl(mp->total_bytes)); +} + +static int api_snat_user_session_dump(vat_main_t * vam) +{ + unformat_input_t* i = vam->input; + vl_api_snat_user_session_dump_t * mp; + vl_api_snat_control_ping_t *mp_ping; + ip4_address_t addr; + u32 vrf_id = ~0; + int ret; + + if (vam->json_output) + { + clib_warning ("JSON output not supported for snat_address_dump"); + return -99; + } + + if (unformat (i, "ip_address %U vrf_id %d", + unformat_ip4_address, &addr, &vrf_id)) + ; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + + M(SNAT_USER_SESSION_DUMP, mp); + S(mp); + + /* Use a control ping for synchronization */ + M(SNAT_CONTROL_PING, mp_ping); + memset(mp->ip_address, 0, 16); + clib_memcpy(mp->ip_address, &addr, 4); + mp->vrf_id = htonl(vrf_id); + mp->is_ip4 = 1; + S(mp_ping); + + W (ret); + return ret; +} + +static void vl_api_snat_user_details_t_handler + (vl_api_snat_user_details_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + + fformat(vam->ofp, "user with ip %U with vrf_id %d " + "with %d sessions and %d static sessions\n", + format_ip4_address, mp->ip_address, ntohl(mp->vrf_id), + ntohl(mp->nsessions), ntohl(mp->nstaticsessions)); +} + +static int api_snat_user_dump(vat_main_t * vam) +{ + vl_api_snat_user_dump_t * mp; + vl_api_snat_control_ping_t *mp_ping; + int ret; + + if (vam->json_output) + { + clib_warning ("JSON output not supported for snat_address_dump"); + return -99; + } + + M(SNAT_USER_DUMP, mp); + S(mp); + + /* Use a control ping for synchronization */ + M(SNAT_CONTROL_PING, mp_ping); + S(mp_ping); + + W (ret); + return ret; +} + +static int api_snat_add_det_map (vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + vl_api_snat_add_det_map_t * mp; + ip4_address_t in_addr, out_addr; + u32 in_plen, out_plen; + u8 is_add = 1; + int ret; + + if (unformat (i, "in %U/%d out %U/%d", + unformat_ip4_address, &in_addr, &in_plen, + unformat_ip4_address, &out_addr, &out_plen)) + ; + else if (unformat (i, "del")) + is_add = 0; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + + M(SNAT_ADD_DET_MAP, mp); + clib_memcpy(mp->in_addr, &in_addr, 4); + mp->in_plen = in_plen; + clib_memcpy(mp->out_addr, &out_addr, 4); + mp->out_plen = out_plen; + mp->is_add = is_add; + + S(mp); + W (ret); + return ret; +} + +static void vl_api_snat_det_forward_reply_t_handler + (vl_api_snat_det_forward_reply_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + i32 retval = ntohl(mp->retval); + + if (retval >= 0) + { + fformat (vam->ofp, "outside address %U", format_ip4_address, &mp->out_addr); + fformat (vam->ofp, " outside port range start %d", ntohs(mp->out_port_lo)); + fformat (vam->ofp, " outside port range end %d\n", ntohs(mp->out_port_hi)); + } + + vam->retval = retval; + vam->result_ready = 1; +} + +static int api_snat_det_forward (vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + vl_api_snat_det_forward_t * mp; + ip4_address_t in_addr; + int ret; + + if (unformat (i, "%U", unformat_ip4_address, &in_addr)) + ; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + + M(SNAT_DET_FORWARD, mp); + clib_memcpy(mp->in_addr, &in_addr, 4); + + S(mp); + W(ret); + return ret; +} + +static void vl_api_snat_det_reverse_reply_t_handler + (vl_api_snat_det_reverse_reply_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + i32 retval = ntohl(mp->retval); + + if (retval >= 0) + { + fformat (vam->ofp, "inside address %U\n", format_ip4_address, &mp->in_addr); + } + + vam->retval = retval; + vam->result_ready = 1; +} + +static int api_snat_det_reverse (vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + vl_api_snat_det_reverse_t * mp; + ip4_address_t out_addr; + u16 out_port; + int ret; + + if (unformat (i, "%U %d", unformat_ip4_address, &out_addr, &out_port)) + ; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + + M(SNAT_DET_REVERSE, mp); + clib_memcpy(mp->out_addr, &out_addr, 4); + mp->out_port = htons(out_port); + + S(mp); + W(ret); + return ret; +} + +static void vl_api_snat_det_map_details_t_handler + (vl_api_snat_det_map_details_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + + fformat (vam->ofp, "Deterministic S-NAT mapping in %U/%d out %U/%d " + "ports per host %d sharing ratio %d " + "number of sessions %d", + format_ip4_address, mp->in_addr, mp->in_plen, + format_ip4_address, mp->out_addr, mp->out_plen, + ntohs(mp->ports_per_host), ntohl(mp->sharing_ratio), + ntohl(mp->ses_num)); +} + +static int api_snat_det_map_dump(vat_main_t * vam) +{ + vl_api_snat_det_map_dump_t * mp; + vl_api_snat_control_ping_t *mp_ping; + int ret; + + if (vam->json_output) + { + clib_warning ("JSON output not supported for snat_det_map_dump"); + return -99; + } + + M(SNAT_DET_MAP_DUMP, mp); + S(mp); + + /* Use a control ping for synchronization */ + M(SNAT_CONTROL_PING, mp_ping); + S(mp_ping); + + W (ret); + return ret; +} + +static int api_snat_det_set_timeouts (vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + vl_api_snat_det_set_timeouts_t * mp; + u32 udp = SNAT_UDP_TIMEOUT; + u32 tcp_established = SNAT_TCP_ESTABLISHED_TIMEOUT; + u32 tcp_transitory = SNAT_TCP_TRANSITORY_TIMEOUT; + u32 icmp = SNAT_ICMP_TIMEOUT; + int ret; + + if (unformat (i, "udp %d", &udp)) + ; + else if (unformat (i, "tcp_established %d", &tcp_established)) + ; + else if (unformat (i, "tcp_transitory %d", &tcp_transitory)) + ; + else if (unformat (i, "icmp %d", &icmp)) + ; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + + M(SNAT_DET_SET_TIMEOUTS, mp); + mp->udp = htonl(udp); + mp->tcp_established = htonl(tcp_established); + mp->tcp_transitory = htonl(tcp_transitory); + mp->icmp = htonl(icmp); + + S(mp); + W (ret); + return ret; +} + +static void vl_api_snat_det_get_timeouts_reply_t_handler + (vl_api_snat_det_get_timeouts_reply_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + i32 retval = ntohl (mp->retval); + + if (retval >= 0) + { + fformat (vam->ofp, "udp timeout: %dsec\n", ntohl (mp->udp)); + fformat (vam->ofp, "tcp-established timeout: %dsec", + ntohl (mp->tcp_established)); + fformat (vam->ofp, "tcp-transitory timeout: %dsec", + ntohl (mp->tcp_transitory)); + fformat (vam->ofp, "icmp timeout: %dsec", ntohl (mp->icmp)); + } + vam->retval = retval; + vam->result_ready = 1; +} + +static int api_snat_det_get_timeouts(vat_main_t * vam) +{ + vl_api_snat_det_get_timeouts_t * mp; + int ret; + + if (vam->json_output) + { + clib_warning ("JSON output not supported for snat_show_config"); + return -99; + } + + M(SNAT_DET_GET_TIMEOUTS, mp); + S(mp); + W (ret); + return ret; +} + +static int api_snat_det_close_session_out (vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + vl_api_snat_det_close_session_out_t * mp; + ip4_address_t out_addr, ext_addr; + u16 out_port, ext_port; + int ret; + + if (unformat (i, "%U:%d %U:%d", + unformat_ip4_address, &out_addr, &out_port, + unformat_ip4_address, &ext_addr, &ext_port)) + ; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + + M(SNAT_DET_CLOSE_SESSION_OUT, mp); + clib_memcpy(mp->out_addr, &out_addr, 4); + mp->out_port = ntohs(out_port); + clib_memcpy(mp->ext_addr, &ext_addr, 4); + mp->ext_port = ntohs(ext_port); + + S(mp); + W (ret); + return ret; +} + +static int api_snat_det_close_session_in (vat_main_t * vam) +{ + unformat_input_t * i = vam->input; + vl_api_snat_det_close_session_in_t * mp; + ip4_address_t in_addr, ext_addr; + u16 in_port, ext_port; + int ret; + + if (unformat (i, "%U:%d %U:%d", + unformat_ip4_address, &in_addr, &in_port, + unformat_ip4_address, &ext_addr, &ext_port)) + ; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + + M(SNAT_DET_CLOSE_SESSION_IN, mp); + clib_memcpy(mp->in_addr, &in_addr, 4); + mp->in_port = ntohs(in_port); + clib_memcpy(mp->ext_addr, &ext_addr, 4); + mp->ext_port = ntohs(ext_port); + + S(mp); + W (ret); + return ret; +} + +static void vl_api_snat_det_session_details_t_handler + (vl_api_snat_det_session_details_t *mp) +{ + snat_test_main_t * sm = &snat_test_main; + vat_main_t *vam = sm->vat_main; + + fformat(vam->ofp, "deterministic session, external host address %U, " + "external host port %d, outer port %d, inside port %d", + format_ip4_address, mp->ext_addr, mp->ext_port, + mp->out_port, mp->in_port); +} + +static int api_snat_det_session_dump(vat_main_t * vam) +{ + unformat_input_t* i = vam->input; + vl_api_snat_det_session_dump_t * mp; + vl_api_snat_control_ping_t *mp_ping; + ip4_address_t user_addr; + int ret; + + if (vam->json_output) + { + clib_warning ("JSON output not supported for snat_det_session_dump"); + return -99; + } + + if (unformat (i, "user_addr %U", unformat_ip4_address, &user_addr)) + ; + else + { + clib_warning ("unknown input '%U'", format_unformat_error, i); + return -99; + } + + M(SNAT_DET_SESSION_DUMP, mp); + clib_memcpy (&mp->user_addr, &user_addr, 4); + S(mp); + + /* Use a control ping for synchronization */ + M(SNAT_CONTROL_PING, mp_ping); + S(mp_ping); + + W (ret); + return ret; +} + +/* + * List of messages that the api test plugin sends, + * and that the data plane plugin processes + */ +#define foreach_vpe_api_msg \ +_(snat_add_address_range, "<start-addr> [- <end-addr] [del]") \ +_(snat_interface_add_del_feature, \ + "<intfc> | sw_if_index <id> [in] [out] [del]") \ +_(snat_add_static_mapping, "local_addr <ip> (external_addr <ip>" \ + " | external_if <intfc> | external_sw_if_ndex <id>) " \ + "[local_port <n>] [external_port <n>] [vrf <table-id>] [del] " \ + "protocol <n>") \ +_(snat_set_workers, "<wokrers_bitmap>") \ +_(snat_static_mapping_dump, "") \ +_(snat_show_config, "") \ +_(snat_address_dump, "") \ +_(snat_interface_dump, "") \ +_(snat_worker_dump, "") \ +_(snat_add_del_interface_addr, \ + "<intfc> | sw_if_index <id> [del]") \ +_(snat_interface_addr_dump, "") \ +_(snat_ipfix_enable_disable, "[domain <id>] [src_port <n>] " \ + "[disable]") \ +_(snat_user_dump, "") \ +_(snat_user_session_dump, "ip_address <ip> vrf_id <table-id>") \ +_(snat_add_det_map, "in <in_addr>/<in_plen> out " \ + "<out_addr>/<out_plen> [del]") \ +_(snat_det_forward, "<in_addr>") \ +_(snat_det_reverse, "<out_addr> <out_port>") \ +_(snat_det_map_dump, "") \ +_(snat_det_set_timeouts, "[udp <sec> | tcp_established <sec> | " \ + "tcp_transitory <sec> | icmp <sec>]") \ +_(snat_det_get_timeouts, "") \ +_(snat_det_close_session_out, "<out_addr>:<out_port> " \ + "<ext_addr>:<ext_port>") \ +_(snat_det_close_session_in, "<in_addr>:<in_port> " \ + "<out_addr>:<out_port>") \ +_(snat_det_session_dump, "ip_address <user_addr>") + +static void +snat_vat_api_hookup (vat_main_t *vam) +{ + snat_test_main_t * sm __attribute__((unused)) = &snat_test_main; + /* Hook up handlers for replies from the data plane plug-in */ +#define _(N,n) \ + vl_msg_api_set_handlers((VL_API_##N + sm->msg_id_base), \ + #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_api_##n##_t_endian, \ + vl_api_##n##_t_print, \ + sizeof(vl_api_##n##_t), 1); + foreach_vpe_api_reply_msg; +#undef _ + + /* API messages we can send */ +#define _(n,h) \ + hash_set_mem (vam->function_by_name, #n, api_##n); + foreach_vpe_api_msg; +#undef _ + + /* Help strings */ +#define _(n,h) hash_set_mem (vam->help_by_name, #n, h); + foreach_vpe_api_msg; +#undef _ +} + +clib_error_t * vat_plugin_register (vat_main_t *vam) +{ + snat_test_main_t * sm = &snat_test_main; + u8 * name; + + sm->vat_main = vam; + + /* Ask the vpp engine for the first assigned message-id */ + name = format (0, "snat_%08x%c", api_version, 0); + sm->msg_id_base = vl_client_get_first_plugin_msg_id ((char *) name); + + if (sm->msg_id_base != (u16) ~0) + snat_vat_api_hookup (vam); + + vec_free(name); + + return 0; +} diff --git a/src/plugins/nat/out2in.c b/src/plugins/nat/out2in.c new file mode 100644 index 00000000000..679500668c5 --- /dev/null +++ b/src/plugins/nat/out2in.c @@ -0,0 +1,2294 @@ +/* + * Copyright (c) 2016 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 <vlib/vlib.h> +#include <vnet/vnet.h> +#include <vnet/pg/pg.h> +#include <vnet/handoff.h> + +#include <vnet/ip/ip.h> +#include <vnet/udp/udp.h> +#include <vnet/ethernet/ethernet.h> +#include <vnet/fib/ip4_fib.h> +#include <nat/nat.h> +#include <nat/nat_ipfix_logging.h> +#include <nat/nat_det.h> + +#include <vppinfra/hash.h> +#include <vppinfra/error.h> +#include <vppinfra/elog.h> + +typedef struct { + u32 sw_if_index; + u32 next_index; + u32 session_index; +} snat_out2in_trace_t; + +typedef struct { + u32 next_worker_index; + u8 do_handoff; +} snat_out2in_worker_handoff_trace_t; + +/* packet trace format function */ +static u8 * format_snat_out2in_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + snat_out2in_trace_t * t = va_arg (*args, snat_out2in_trace_t *); + + s = format (s, "NAT44_OUT2IN: sw_if_index %d, next index %d, session index %d", + t->sw_if_index, t->next_index, t->session_index); + return s; +} + +static u8 * format_snat_out2in_fast_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + snat_out2in_trace_t * t = va_arg (*args, snat_out2in_trace_t *); + + s = format (s, "NAT44_OUT2IN_FAST: sw_if_index %d, next index %d", + t->sw_if_index, t->next_index); + return s; +} + +static u8 * format_snat_out2in_worker_handoff_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + snat_out2in_worker_handoff_trace_t * t = + va_arg (*args, snat_out2in_worker_handoff_trace_t *); + char * m; + + m = t->do_handoff ? "next worker" : "same worker"; + s = format (s, "NAT44_OUT2IN_WORKER_HANDOFF: %s %d", m, t->next_worker_index); + + return s; +} + +vlib_node_registration_t snat_out2in_node; +vlib_node_registration_t snat_out2in_fast_node; +vlib_node_registration_t snat_out2in_worker_handoff_node; +vlib_node_registration_t snat_det_out2in_node; + +#define foreach_snat_out2in_error \ +_(UNSUPPORTED_PROTOCOL, "Unsupported protocol") \ +_(OUT2IN_PACKETS, "Good out2in packets processed") \ +_(BAD_ICMP_TYPE, "unsupported ICMP type") \ +_(NO_TRANSLATION, "No translation") + +typedef enum { +#define _(sym,str) SNAT_OUT2IN_ERROR_##sym, + foreach_snat_out2in_error +#undef _ + SNAT_OUT2IN_N_ERROR, +} snat_out2in_error_t; + +static char * snat_out2in_error_strings[] = { +#define _(sym,string) string, + foreach_snat_out2in_error +#undef _ +}; + +typedef enum { + SNAT_OUT2IN_NEXT_DROP, + SNAT_OUT2IN_NEXT_LOOKUP, + SNAT_OUT2IN_NEXT_ICMP_ERROR, + SNAT_OUT2IN_N_NEXT, +} snat_out2in_next_t; + +/** + * @brief Create session for static mapping. + * + * Create NAT session initiated by host from external network with static + * mapping. + * + * @param sm NAT main. + * @param b0 Vlib buffer. + * @param in2out In2out NAT44 session key. + * @param out2in Out2in NAT44 session key. + * @param node Vlib node. + * + * @returns SNAT session if successfully created otherwise 0. + */ +static inline snat_session_t * +create_session_for_static_mapping (snat_main_t *sm, + vlib_buffer_t *b0, + snat_session_key_t in2out, + snat_session_key_t out2in, + vlib_node_runtime_t * node, + u32 thread_index) +{ + snat_user_t *u; + snat_user_key_t user_key; + snat_session_t *s; + clib_bihash_kv_8_8_t kv0, value0; + dlist_elt_t * per_user_translation_list_elt; + dlist_elt_t * per_user_list_head_elt; + ip4_header_t *ip0; + + ip0 = vlib_buffer_get_current (b0); + + user_key.addr = in2out.addr; + user_key.fib_index = in2out.fib_index; + kv0.key = user_key.as_u64; + + /* Ever heard of the "user" = inside ip4 address before? */ + if (clib_bihash_search_8_8 (&sm->user_hash, &kv0, &value0)) + { + /* no, make a new one */ + pool_get (sm->per_thread_data[thread_index].users, u); + memset (u, 0, sizeof (*u)); + u->addr = in2out.addr; + u->fib_index = in2out.fib_index; + + pool_get (sm->per_thread_data[thread_index].list_pool, + per_user_list_head_elt); + + u->sessions_per_user_list_head_index = per_user_list_head_elt - + sm->per_thread_data[thread_index].list_pool; + + clib_dlist_init (sm->per_thread_data[thread_index].list_pool, + u->sessions_per_user_list_head_index); + + kv0.value = u - sm->per_thread_data[thread_index].users; + + /* add user */ + clib_bihash_add_del_8_8 (&sm->user_hash, &kv0, 1 /* is_add */); + + /* add non-traslated packets worker lookup */ + kv0.value = thread_index; + clib_bihash_add_del_8_8 (&sm->worker_by_in, &kv0, 1); + } + else + { + u = pool_elt_at_index (sm->per_thread_data[thread_index].users, + value0.value); + } + + pool_get (sm->per_thread_data[thread_index].sessions, s); + memset (s, 0, sizeof (*s)); + + s->outside_address_index = ~0; + s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING; + s->ext_host_addr.as_u32 = ip0->dst_address.as_u32; + u->nstaticsessions++; + + /* Create list elts */ + pool_get (sm->per_thread_data[thread_index].list_pool, + per_user_translation_list_elt); + clib_dlist_init (sm->per_thread_data[thread_index].list_pool, + per_user_translation_list_elt - + sm->per_thread_data[thread_index].list_pool); + + per_user_translation_list_elt->value = + s - sm->per_thread_data[thread_index].sessions; + s->per_user_index = + per_user_translation_list_elt - sm->per_thread_data[thread_index].list_pool; + s->per_user_list_head_index = u->sessions_per_user_list_head_index; + + clib_dlist_addtail (sm->per_thread_data[thread_index].list_pool, + s->per_user_list_head_index, + per_user_translation_list_elt - + sm->per_thread_data[thread_index].list_pool); + + s->in2out = in2out; + s->out2in = out2in; + s->in2out.protocol = out2in.protocol; + + /* Add to translation hashes */ + kv0.key = s->in2out.as_u64; + kv0.value = s - sm->per_thread_data[thread_index].sessions; + if (clib_bihash_add_del_8_8 (&sm->in2out, &kv0, 1 /* is_add */)) + clib_warning ("in2out key add failed"); + + kv0.key = s->out2in.as_u64; + kv0.value = s - sm->per_thread_data[thread_index].sessions; + + if (clib_bihash_add_del_8_8 (&sm->out2in, &kv0, 1 /* is_add */)) + clib_warning ("out2in key add failed"); + + /* log NAT event */ + snat_ipfix_logging_nat44_ses_create(s->in2out.addr.as_u32, + s->out2in.addr.as_u32, + s->in2out.protocol, + s->in2out.port, + s->out2in.port, + s->in2out.fib_index); + return s; +} + +static_always_inline +snat_out2in_error_t icmp_get_key(ip4_header_t *ip0, + snat_session_key_t *p_key0) +{ + icmp46_header_t *icmp0; + snat_session_key_t key0; + icmp_echo_header_t *echo0, *inner_echo0 = 0; + ip4_header_t *inner_ip0; + void *l4_header = 0; + icmp46_header_t *inner_icmp0; + + icmp0 = (icmp46_header_t *) ip4_next_header (ip0); + echo0 = (icmp_echo_header_t *)(icmp0+1); + + if (!icmp_is_error_message (icmp0)) + { + key0.protocol = SNAT_PROTOCOL_ICMP; + key0.addr = ip0->dst_address; + key0.port = echo0->identifier; + } + else + { + inner_ip0 = (ip4_header_t *)(echo0+1); + l4_header = ip4_next_header (inner_ip0); + key0.protocol = ip_proto_to_snat_proto (inner_ip0->protocol); + key0.addr = inner_ip0->src_address; + switch (key0.protocol) + { + case SNAT_PROTOCOL_ICMP: + inner_icmp0 = (icmp46_header_t*)l4_header; + inner_echo0 = (icmp_echo_header_t *)(inner_icmp0+1); + key0.port = inner_echo0->identifier; + break; + case SNAT_PROTOCOL_UDP: + case SNAT_PROTOCOL_TCP: + key0.port = ((tcp_udp_header_t*)l4_header)->src_port; + break; + default: + return SNAT_OUT2IN_ERROR_UNSUPPORTED_PROTOCOL; + } + } + *p_key0 = key0; + return -1; /* success */ +} + +/** + * Get address and port values to be used for ICMP packet translation + * and create session if needed + * + * @param[in,out] sm NAT main + * @param[in,out] node NAT node runtime + * @param[in] thread_index thread index + * @param[in,out] b0 buffer containing packet to be translated + * @param[out] p_proto protocol used for matching + * @param[out] p_value address and port after NAT translation + * @param[out] p_dont_translate if packet should not be translated + * @param d optional parameter + * @param e optional parameter + */ +u32 icmp_match_out2in_slow(snat_main_t *sm, vlib_node_runtime_t *node, + u32 thread_index, vlib_buffer_t *b0, u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, void *d, void *e) +{ + ip4_header_t *ip0; + icmp46_header_t *icmp0; + u32 sw_if_index0; + u32 rx_fib_index0; + snat_session_key_t key0; + snat_session_key_t sm0; + snat_session_t *s0 = 0; + u8 dont_translate = 0; + clib_bihash_kv_8_8_t kv0, value0; + u8 is_addr_only; + u32 next0 = ~0; + int err; + + ip0 = vlib_buffer_get_current (b0); + icmp0 = (icmp46_header_t *) ip4_next_header (ip0); + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index (sw_if_index0); + + key0.protocol = 0; + + err = icmp_get_key (ip0, &key0); + if (err != -1) + { + b0->error = node->errors[SNAT_OUT2IN_ERROR_UNSUPPORTED_PROTOCOL]; + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out; + } + key0.fib_index = rx_fib_index0; + + kv0.key = key0.as_u64; + + if (clib_bihash_search_8_8 (&sm->out2in, &kv0, &value0)) + { + /* Try to match static mapping by external address and port, + destination address and port in packet */ + if (snat_static_mapping_match(sm, key0, &sm0, 1, &is_addr_only)) + { + /* Don't NAT packet aimed at the intfc address */ + if (PREDICT_FALSE(is_interface_addr(sm, node, sw_if_index0, + ip0->dst_address.as_u32))) + { + dont_translate = 1; + goto out; + } + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out; + } + + if (PREDICT_FALSE(icmp0->type != ICMP4_echo_reply && + (icmp0->type != ICMP4_echo_request || !is_addr_only))) + { + b0->error = node->errors[SNAT_OUT2IN_ERROR_BAD_ICMP_TYPE]; + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out; + } + + /* Create session initiated by host from external network */ + s0 = create_session_for_static_mapping(sm, b0, sm0, key0, + node, thread_index); + + if (!s0) + { + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out; + } + } + else + { + if (PREDICT_FALSE(icmp0->type != ICMP4_echo_reply && + icmp0->type != ICMP4_echo_request && + !icmp_is_error_message (icmp0))) + { + b0->error = node->errors[SNAT_OUT2IN_ERROR_BAD_ICMP_TYPE]; + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out; + } + + s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, + value0.value); + } + +out: + *p_proto = key0.protocol; + if (s0) + *p_value = s0->in2out; + *p_dont_translate = dont_translate; + if (d) + *(snat_session_t**)d = s0; + return next0; +} + +/** + * Get address and port values to be used for ICMP packet translation + * + * @param[in] sm NAT main + * @param[in,out] node NAT node runtime + * @param[in] thread_index thread index + * @param[in,out] b0 buffer containing packet to be translated + * @param[out] p_proto protocol used for matching + * @param[out] p_value address and port after NAT translation + * @param[out] p_dont_translate if packet should not be translated + * @param d optional parameter + * @param e optional parameter + */ +u32 icmp_match_out2in_fast(snat_main_t *sm, vlib_node_runtime_t *node, + u32 thread_index, vlib_buffer_t *b0, u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, void *d, void *e) +{ + ip4_header_t *ip0; + icmp46_header_t *icmp0; + u32 sw_if_index0; + u32 rx_fib_index0; + snat_session_key_t key0; + snat_session_key_t sm0; + u8 dont_translate = 0; + u8 is_addr_only; + u32 next0 = ~0; + int err; + + ip0 = vlib_buffer_get_current (b0); + icmp0 = (icmp46_header_t *) ip4_next_header (ip0); + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index (sw_if_index0); + + err = icmp_get_key (ip0, &key0); + if (err != -1) + { + b0->error = node->errors[err]; + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out2; + } + key0.fib_index = rx_fib_index0; + + if (snat_static_mapping_match(sm, key0, &sm0, 1, &is_addr_only)) + { + /* Don't NAT packet aimed at the intfc address */ + if (is_interface_addr(sm, node, sw_if_index0, ip0->dst_address.as_u32)) + { + dont_translate = 1; + goto out; + } + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out; + } + + if (PREDICT_FALSE(icmp0->type != ICMP4_echo_reply && + (icmp0->type != ICMP4_echo_request || !is_addr_only) && + !icmp_is_error_message (icmp0))) + { + b0->error = node->errors[SNAT_OUT2IN_ERROR_BAD_ICMP_TYPE]; + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out; + } + +out: + *p_value = sm0; +out2: + *p_proto = key0.protocol; + *p_dont_translate = dont_translate; + return next0; +} + +static inline u32 icmp_out2in (snat_main_t *sm, + vlib_buffer_t * b0, + ip4_header_t * ip0, + icmp46_header_t * icmp0, + u32 sw_if_index0, + u32 rx_fib_index0, + vlib_node_runtime_t * node, + u32 next0, + u32 thread_index, + void *d, + void *e) +{ + snat_session_key_t sm0; + u8 protocol; + icmp_echo_header_t *echo0, *inner_echo0 = 0; + ip4_header_t *inner_ip0 = 0; + void *l4_header = 0; + icmp46_header_t *inner_icmp0; + u8 dont_translate; + u32 new_addr0, old_addr0; + u16 old_id0, new_id0; + ip_csum_t sum0; + u16 checksum0; + u32 next0_tmp; + + echo0 = (icmp_echo_header_t *)(icmp0+1); + + next0_tmp = sm->icmp_match_out2in_cb(sm, node, thread_index, b0, + &protocol, &sm0, &dont_translate, d, e); + if (next0_tmp != ~0) + next0 = next0_tmp; + if (next0 == SNAT_OUT2IN_NEXT_DROP || dont_translate) + goto out; + + sum0 = ip_incremental_checksum (0, icmp0, + ntohs(ip0->length) - ip4_header_bytes (ip0)); + checksum0 = ~ip_csum_fold (sum0); + if (checksum0 != 0 && checksum0 != 0xffff) + { + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out; + } + + old_addr0 = ip0->dst_address.as_u32; + new_addr0 = ip0->dst_address.as_u32 = sm0.addr.as_u32; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = sm0.fib_index; + + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t, + dst_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + if (!icmp_is_error_message (icmp0)) + { + new_id0 = sm0.port; + if (PREDICT_FALSE(new_id0 != echo0->identifier)) + { + old_id0 = echo0->identifier; + new_id0 = sm0.port; + echo0->identifier = new_id0; + + sum0 = icmp0->checksum; + sum0 = ip_csum_update (sum0, old_id0, new_id0, icmp_echo_header_t, + identifier /* changed member */); + icmp0->checksum = ip_csum_fold (sum0); + } + } + else + { + inner_ip0 = (ip4_header_t *)(echo0+1); + l4_header = ip4_next_header (inner_ip0); + + if (!ip4_header_checksum_is_valid (inner_ip0)) + { + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out; + } + + old_addr0 = inner_ip0->src_address.as_u32; + inner_ip0->src_address = sm0.addr; + new_addr0 = inner_ip0->src_address.as_u32; + + sum0 = icmp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, ip4_header_t, + src_address /* changed member */); + icmp0->checksum = ip_csum_fold (sum0); + + switch (protocol) + { + case SNAT_PROTOCOL_ICMP: + inner_icmp0 = (icmp46_header_t*)l4_header; + inner_echo0 = (icmp_echo_header_t *)(inner_icmp0+1); + + old_id0 = inner_echo0->identifier; + new_id0 = sm0.port; + inner_echo0->identifier = new_id0; + + sum0 = icmp0->checksum; + sum0 = ip_csum_update (sum0, old_id0, new_id0, icmp_echo_header_t, + identifier); + icmp0->checksum = ip_csum_fold (sum0); + break; + case SNAT_PROTOCOL_UDP: + case SNAT_PROTOCOL_TCP: + old_id0 = ((tcp_udp_header_t*)l4_header)->src_port; + new_id0 = sm0.port; + ((tcp_udp_header_t*)l4_header)->src_port = new_id0; + + sum0 = icmp0->checksum; + sum0 = ip_csum_update (sum0, old_id0, new_id0, tcp_udp_header_t, + src_port); + icmp0->checksum = ip_csum_fold (sum0); + break; + default: + ASSERT(0); + } + } + +out: + return next0; +} + + +static inline u32 icmp_out2in_slow_path (snat_main_t *sm, + vlib_buffer_t * b0, + ip4_header_t * ip0, + icmp46_header_t * icmp0, + u32 sw_if_index0, + u32 rx_fib_index0, + vlib_node_runtime_t * node, + u32 next0, f64 now, + u32 thread_index, + snat_session_t ** p_s0) +{ + next0 = icmp_out2in(sm, b0, ip0, icmp0, sw_if_index0, rx_fib_index0, node, + next0, thread_index, p_s0, 0); + snat_session_t * s0 = *p_s0; + if (PREDICT_TRUE(next0 != SNAT_OUT2IN_NEXT_DROP && s0)) + { + /* Accounting */ + s0->last_heard = now; + s0->total_pkts++; + s0->total_bytes += vlib_buffer_length_in_chain (sm->vlib_main, b0); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s0)) + { + clib_dlist_remove (sm->per_thread_data[thread_index].list_pool, + s0->per_user_index); + clib_dlist_addtail (sm->per_thread_data[thread_index].list_pool, + s0->per_user_list_head_index, + s0->per_user_index); + } + } + return next0; +} + +static void +snat_out2in_unknown_proto (snat_main_t *sm, + vlib_buffer_t * b, + ip4_header_t * ip, + u32 rx_fib_index, + u32 thread_index, + f64 now, + vlib_main_t * vm) +{ + clib_bihash_kv_8_8_t kv, value; + clib_bihash_kv_16_8_t s_kv, s_value; + snat_static_mapping_t *m; + snat_session_key_t m_key; + u32 old_addr, new_addr; + ip_csum_t sum; + snat_unk_proto_ses_key_t key; + snat_session_t * s; + snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index]; + snat_user_key_t u_key; + snat_user_t *u; + dlist_elt_t *head, *elt; + + old_addr = ip->dst_address.as_u32; + + key.l_addr = ip->dst_address; + key.r_addr = ip->src_address; + key.fib_index = rx_fib_index; + key.proto = ip->protocol; + key.rsvd[0] = key.rsvd[1] = key.rsvd[2] = 0; + s_kv.key[0] = key.as_u64[0]; + s_kv.key[1] = key.as_u64[1]; + + if (!clib_bihash_search_16_8 (&sm->out2in_unk_proto, &s_kv, &s_value)) + { + s = pool_elt_at_index (tsm->sessions, s_value.value); + new_addr = ip->dst_address.as_u32 = s->in2out.addr.as_u32; + } + else + { + m_key.addr = ip->dst_address; + m_key.port = 0; + m_key.protocol = 0; + m_key.fib_index = rx_fib_index; + kv.key = m_key.as_u64; + if (clib_bihash_search_8_8 (&sm->static_mapping_by_external, &kv, &value)) + return; + + m = pool_elt_at_index (sm->static_mappings, value.value); + + new_addr = ip->dst_address.as_u32 = m->local_addr.as_u32; + + u_key.addr = ip->src_address; + u_key.fib_index = m->fib_index; + kv.key = u_key.as_u64; + + /* Ever heard of the "user" = src ip4 address before? */ + if (clib_bihash_search_8_8 (&sm->user_hash, &kv, &value)) + { + /* no, make a new one */ + pool_get (tsm->users, u); + memset (u, 0, sizeof (*u)); + u->addr = ip->src_address; + u->fib_index = rx_fib_index; + + pool_get (tsm->list_pool, head); + u->sessions_per_user_list_head_index = head - tsm->list_pool; + + clib_dlist_init (tsm->list_pool, + u->sessions_per_user_list_head_index); + + kv.value = u - tsm->users; + + /* add user */ + clib_bihash_add_del_8_8 (&sm->user_hash, &kv, 1); + } + else + { + u = pool_elt_at_index (tsm->users, value.value); + } + + /* Create a new session */ + pool_get (tsm->sessions, s); + memset (s, 0, sizeof (*s)); + + s->ext_host_addr.as_u32 = ip->src_address.as_u32; + s->flags |= SNAT_SESSION_FLAG_UNKNOWN_PROTO; + s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING; + s->outside_address_index = ~0; + s->out2in.addr.as_u32 = old_addr; + s->out2in.fib_index = rx_fib_index; + s->in2out.addr.as_u32 = new_addr; + s->in2out.fib_index = m->fib_index; + s->in2out.port = s->out2in.port = ip->protocol; + u->nstaticsessions++; + + /* Create list elts */ + pool_get (tsm->list_pool, elt); + clib_dlist_init (tsm->list_pool, elt - tsm->list_pool); + elt->value = s - tsm->sessions; + s->per_user_index = elt - tsm->list_pool; + s->per_user_list_head_index = u->sessions_per_user_list_head_index; + clib_dlist_addtail (tsm->list_pool, s->per_user_list_head_index, + s->per_user_index); + + /* Add to lookup tables */ + s_kv.value = s - tsm->sessions; + if (clib_bihash_add_del_16_8 (&sm->out2in_unk_proto, &s_kv, 1)) + clib_warning ("out2in key add failed"); + + key.l_addr = ip->dst_address; + key.fib_index = m->fib_index; + s_kv.key[0] = key.as_u64[0]; + s_kv.key[1] = key.as_u64[1]; + if (clib_bihash_add_del_16_8 (&sm->in2out_unk_proto, &s_kv, 1)) + clib_warning ("in2out key add failed"); + } + + /* Update IP checksum */ + sum = ip->checksum; + sum = ip_csum_update (sum, old_addr, new_addr, ip4_header_t, dst_address); + ip->checksum = ip_csum_fold (sum); + + vnet_buffer(b)->sw_if_index[VLIB_TX] = s->in2out.fib_index; + + /* Accounting */ + s->last_heard = now; + s->total_pkts++; + s->total_bytes += vlib_buffer_length_in_chain (vm, b); + /* Per-user LRU list maintenance */ + clib_dlist_remove (tsm->list_pool, s->per_user_index); + clib_dlist_addtail (tsm->list_pool, s->per_user_list_head_index, + s->per_user_index); +} + +static uword +snat_out2in_node_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + u32 n_left_from, * from, * to_next; + snat_out2in_next_t next_index; + u32 pkts_processed = 0; + snat_main_t * sm = &snat_main; + f64 now = vlib_time_now (vm); + u32 thread_index = vlib_get_thread_index (); + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, + to_next, n_left_to_next); + + while (n_left_from >= 4 && n_left_to_next >= 2) + { + u32 bi0, bi1; + vlib_buffer_t * b0, * b1; + u32 next0 = SNAT_OUT2IN_NEXT_LOOKUP; + u32 next1 = SNAT_OUT2IN_NEXT_LOOKUP; + u32 sw_if_index0, sw_if_index1; + ip4_header_t * ip0, *ip1; + ip_csum_t sum0, sum1; + u32 new_addr0, old_addr0; + u16 new_port0, old_port0; + u32 new_addr1, old_addr1; + u16 new_port1, old_port1; + udp_header_t * udp0, * udp1; + tcp_header_t * tcp0, * tcp1; + icmp46_header_t * icmp0, * icmp1; + snat_session_key_t key0, key1, sm0, sm1; + u32 rx_fib_index0, rx_fib_index1; + u32 proto0, proto1; + snat_session_t * s0 = 0, * s1 = 0; + clib_bihash_kv_8_8_t kv0, kv1, value0, value1; + + /* Prefetch next iteration. */ + { + vlib_buffer_t * p2, * p3; + + p2 = vlib_get_buffer (vm, from[2]); + p3 = vlib_get_buffer (vm, from[3]); + + vlib_prefetch_buffer_header (p2, LOAD); + vlib_prefetch_buffer_header (p3, LOAD); + + CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE); + CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE); + } + + /* speculatively enqueue b0 and b1 to the current next frame */ + to_next[0] = bi0 = from[0]; + to_next[1] = bi1 = from[1]; + from += 2; + to_next += 2; + n_left_from -= 2; + n_left_to_next -= 2; + + b0 = vlib_get_buffer (vm, bi0); + b1 = vlib_get_buffer (vm, bi1); + + vnet_buffer (b0)->snat.flags = 0; + vnet_buffer (b1)->snat.flags = 0; + + ip0 = vlib_buffer_get_current (b0); + udp0 = ip4_next_header (ip0); + tcp0 = (tcp_header_t *) udp0; + icmp0 = (icmp46_header_t *) udp0; + + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = vec_elt (sm->ip4_main->fib_index_by_sw_if_index, + sw_if_index0); + + if (PREDICT_FALSE(ip0->ttl == 1)) + { + vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next0 = SNAT_OUT2IN_NEXT_ICMP_ERROR; + goto trace0; + } + + proto0 = ip_proto_to_snat_proto (ip0->protocol); + + if (PREDICT_FALSE (proto0 == ~0)) + { + snat_out2in_unknown_proto(sm, b0, ip0, rx_fib_index0, + thread_index, now, vm); + goto trace0; + } + + if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP)) + { + next0 = icmp_out2in_slow_path + (sm, b0, ip0, icmp0, sw_if_index0, rx_fib_index0, node, + next0, now, thread_index, &s0); + goto trace0; + } + + key0.addr = ip0->dst_address; + key0.port = udp0->dst_port; + key0.protocol = proto0; + key0.fib_index = rx_fib_index0; + + kv0.key = key0.as_u64; + + if (clib_bihash_search_8_8 (&sm->out2in, &kv0, &value0)) + { + /* Try to match static mapping by external address and port, + destination address and port in packet */ + if (snat_static_mapping_match(sm, key0, &sm0, 1, 0)) + { + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + /* + * Send DHCP packets to the ipv4 stack, or we won't + * be able to use dhcp client on the outside interface + */ + if (proto0 != SNAT_PROTOCOL_UDP + || (udp0->dst_port + != clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client))) + next0 = SNAT_OUT2IN_NEXT_DROP; + goto trace0; + } + + /* Create session initiated by host from external network */ + s0 = create_session_for_static_mapping(sm, b0, sm0, key0, node, + thread_index); + if (!s0) + { + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + next0 = SNAT_OUT2IN_NEXT_DROP; + goto trace0; + } + } + else + s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, + value0.value); + + old_addr0 = ip0->dst_address.as_u32; + ip0->dst_address = s0->in2out.addr; + new_addr0 = ip0->dst_address.as_u32; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->in2out.fib_index; + + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + dst_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + old_port0 = tcp0->dst_port; + tcp0->dst_port = s0->in2out.port; + new_port0 = tcp0->dst_port; + + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + dst_address /* changed member */); + + sum0 = ip_csum_update (sum0, old_port0, new_port0, + ip4_header_t /* cheat */, + length /* changed member */); + tcp0->checksum = ip_csum_fold(sum0); + } + else + { + old_port0 = udp0->dst_port; + udp0->dst_port = s0->in2out.port; + udp0->checksum = 0; + } + + /* Accounting */ + s0->last_heard = now; + s0->total_pkts++; + s0->total_bytes += vlib_buffer_length_in_chain (vm, b0); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s0)) + { + clib_dlist_remove (sm->per_thread_data[thread_index].list_pool, + s0->per_user_index); + clib_dlist_addtail (sm->per_thread_data[thread_index].list_pool, + s0->per_user_list_head_index, + s0->per_user_index); + } + trace0: + + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_out2in_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->sw_if_index = sw_if_index0; + t->next_index = next0; + t->session_index = ~0; + if (s0) + t->session_index = s0 - sm->per_thread_data[thread_index].sessions; + } + + pkts_processed += next0 != SNAT_OUT2IN_NEXT_DROP; + + + ip1 = vlib_buffer_get_current (b1); + udp1 = ip4_next_header (ip1); + tcp1 = (tcp_header_t *) udp1; + icmp1 = (icmp46_header_t *) udp1; + + sw_if_index1 = vnet_buffer(b1)->sw_if_index[VLIB_RX]; + rx_fib_index1 = vec_elt (sm->ip4_main->fib_index_by_sw_if_index, + sw_if_index1); + + if (PREDICT_FALSE(ip1->ttl == 1)) + { + vnet_buffer (b1)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b1, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next1 = SNAT_OUT2IN_NEXT_ICMP_ERROR; + goto trace1; + } + + proto1 = ip_proto_to_snat_proto (ip1->protocol); + + if (PREDICT_FALSE (proto1 == ~0)) + { + snat_out2in_unknown_proto(sm, b1, ip1, rx_fib_index1, + thread_index, now, vm); + goto trace1; + } + + if (PREDICT_FALSE (proto1 == SNAT_PROTOCOL_ICMP)) + { + next1 = icmp_out2in_slow_path + (sm, b1, ip1, icmp1, sw_if_index1, rx_fib_index1, node, + next1, now, thread_index, &s1); + goto trace1; + } + + key1.addr = ip1->dst_address; + key1.port = udp1->dst_port; + key1.protocol = proto1; + key1.fib_index = rx_fib_index1; + + kv1.key = key1.as_u64; + + if (clib_bihash_search_8_8 (&sm->out2in, &kv1, &value1)) + { + /* Try to match static mapping by external address and port, + destination address and port in packet */ + if (snat_static_mapping_match(sm, key1, &sm1, 1, 0)) + { + b1->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + /* + * Send DHCP packets to the ipv4 stack, or we won't + * be able to use dhcp client on the outside interface + */ + if (proto1 != SNAT_PROTOCOL_UDP + || (udp1->dst_port + != clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client))) + next1 = SNAT_OUT2IN_NEXT_DROP; + goto trace1; + } + + /* Create session initiated by host from external network */ + s1 = create_session_for_static_mapping(sm, b1, sm1, key1, node, + thread_index); + if (!s1) + { + b1->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + next1 = SNAT_OUT2IN_NEXT_DROP; + goto trace1; + } + } + else + s1 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, + value1.value); + + old_addr1 = ip1->dst_address.as_u32; + ip1->dst_address = s1->in2out.addr; + new_addr1 = ip1->dst_address.as_u32; + vnet_buffer(b1)->sw_if_index[VLIB_TX] = s1->in2out.fib_index; + + sum1 = ip1->checksum; + sum1 = ip_csum_update (sum1, old_addr1, new_addr1, + ip4_header_t, + dst_address /* changed member */); + ip1->checksum = ip_csum_fold (sum1); + + if (PREDICT_TRUE(proto1 == SNAT_PROTOCOL_TCP)) + { + old_port1 = tcp1->dst_port; + tcp1->dst_port = s1->in2out.port; + new_port1 = tcp1->dst_port; + + sum1 = tcp1->checksum; + sum1 = ip_csum_update (sum1, old_addr1, new_addr1, + ip4_header_t, + dst_address /* changed member */); + + sum1 = ip_csum_update (sum1, old_port1, new_port1, + ip4_header_t /* cheat */, + length /* changed member */); + tcp1->checksum = ip_csum_fold(sum1); + } + else + { + old_port1 = udp1->dst_port; + udp1->dst_port = s1->in2out.port; + udp1->checksum = 0; + } + + /* Accounting */ + s1->last_heard = now; + s1->total_pkts++; + s1->total_bytes += vlib_buffer_length_in_chain (vm, b1); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s1)) + { + clib_dlist_remove (sm->per_thread_data[thread_index].list_pool, + s1->per_user_index); + clib_dlist_addtail (sm->per_thread_data[thread_index].list_pool, + s1->per_user_list_head_index, + s1->per_user_index); + } + trace1: + + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b1->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_out2in_trace_t *t = + vlib_add_trace (vm, node, b1, sizeof (*t)); + t->sw_if_index = sw_if_index1; + t->next_index = next1; + t->session_index = ~0; + if (s1) + t->session_index = s1 - sm->per_thread_data[thread_index].sessions; + } + + pkts_processed += next1 != SNAT_OUT2IN_NEXT_DROP; + + /* verify speculative enqueues, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x2 (vm, node, next_index, + to_next, n_left_to_next, + bi0, bi1, next0, next1); + } + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t * b0; + u32 next0 = SNAT_OUT2IN_NEXT_LOOKUP; + u32 sw_if_index0; + ip4_header_t * ip0; + ip_csum_t sum0; + u32 new_addr0, old_addr0; + u16 new_port0, old_port0; + udp_header_t * udp0; + tcp_header_t * tcp0; + icmp46_header_t * icmp0; + snat_session_key_t key0, sm0; + u32 rx_fib_index0; + u32 proto0; + snat_session_t * s0 = 0; + clib_bihash_kv_8_8_t kv0, value0; + + /* speculatively enqueue b0 to the current next frame */ + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + + vnet_buffer (b0)->snat.flags = 0; + + ip0 = vlib_buffer_get_current (b0); + udp0 = ip4_next_header (ip0); + tcp0 = (tcp_header_t *) udp0; + icmp0 = (icmp46_header_t *) udp0; + + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = vec_elt (sm->ip4_main->fib_index_by_sw_if_index, + sw_if_index0); + + proto0 = ip_proto_to_snat_proto (ip0->protocol); + + if (PREDICT_FALSE (proto0 == ~0)) + { + snat_out2in_unknown_proto(sm, b0, ip0, rx_fib_index0, + thread_index, now, vm); + goto trace00; + } + + if (PREDICT_FALSE(ip0->ttl == 1)) + { + vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next0 = SNAT_OUT2IN_NEXT_ICMP_ERROR; + goto trace00; + } + + if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP)) + { + next0 = icmp_out2in_slow_path + (sm, b0, ip0, icmp0, sw_if_index0, rx_fib_index0, node, + next0, now, thread_index, &s0); + goto trace00; + } + + key0.addr = ip0->dst_address; + key0.port = udp0->dst_port; + key0.protocol = proto0; + key0.fib_index = rx_fib_index0; + + kv0.key = key0.as_u64; + + if (clib_bihash_search_8_8 (&sm->out2in, &kv0, &value0)) + { + /* Try to match static mapping by external address and port, + destination address and port in packet */ + if (snat_static_mapping_match(sm, key0, &sm0, 1, 0)) + { + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + /* + * Send DHCP packets to the ipv4 stack, or we won't + * be able to use dhcp client on the outside interface + */ + if (proto0 != SNAT_PROTOCOL_UDP + || (udp0->dst_port + != clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client))) + + next0 = SNAT_OUT2IN_NEXT_DROP; + goto trace00; + } + + /* Create session initiated by host from external network */ + s0 = create_session_for_static_mapping(sm, b0, sm0, key0, node, + thread_index); + if (!s0) + { + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + next0 = SNAT_OUT2IN_NEXT_DROP; + goto trace00; + } + } + else + s0 = pool_elt_at_index (sm->per_thread_data[thread_index].sessions, + value0.value); + + old_addr0 = ip0->dst_address.as_u32; + ip0->dst_address = s0->in2out.addr; + new_addr0 = ip0->dst_address.as_u32; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->in2out.fib_index; + + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + dst_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + old_port0 = tcp0->dst_port; + tcp0->dst_port = s0->in2out.port; + new_port0 = tcp0->dst_port; + + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + dst_address /* changed member */); + + sum0 = ip_csum_update (sum0, old_port0, new_port0, + ip4_header_t /* cheat */, + length /* changed member */); + tcp0->checksum = ip_csum_fold(sum0); + } + else + { + old_port0 = udp0->dst_port; + udp0->dst_port = s0->in2out.port; + udp0->checksum = 0; + } + + /* Accounting */ + s0->last_heard = now; + s0->total_pkts++; + s0->total_bytes += vlib_buffer_length_in_chain (vm, b0); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s0)) + { + clib_dlist_remove (sm->per_thread_data[thread_index].list_pool, + s0->per_user_index); + clib_dlist_addtail (sm->per_thread_data[thread_index].list_pool, + s0->per_user_list_head_index, + s0->per_user_index); + } + trace00: + + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_out2in_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->sw_if_index = sw_if_index0; + t->next_index = next0; + t->session_index = ~0; + if (s0) + t->session_index = s0 - sm->per_thread_data[thread_index].sessions; + } + + pkts_processed += next0 != SNAT_OUT2IN_NEXT_DROP; + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + vlib_node_increment_counter (vm, snat_out2in_node.index, + SNAT_OUT2IN_ERROR_OUT2IN_PACKETS, + pkts_processed); + return frame->n_vectors; +} + +VLIB_REGISTER_NODE (snat_out2in_node) = { + .function = snat_out2in_node_fn, + .name = "nat44-out2in", + .vector_size = sizeof (u32), + .format_trace = format_snat_out2in_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(snat_out2in_error_strings), + .error_strings = snat_out2in_error_strings, + + .runtime_data_bytes = sizeof (snat_runtime_t), + + .n_next_nodes = SNAT_OUT2IN_N_NEXT, + + /* edit / add dispositions here */ + .next_nodes = { + [SNAT_OUT2IN_NEXT_DROP] = "error-drop", + [SNAT_OUT2IN_NEXT_LOOKUP] = "ip4-lookup", + [SNAT_OUT2IN_NEXT_ICMP_ERROR] = "ip4-icmp-error", + }, +}; +VLIB_NODE_FUNCTION_MULTIARCH (snat_out2in_node, snat_out2in_node_fn); + +/**************************/ +/*** deterministic mode ***/ +/**************************/ +static uword +snat_det_out2in_node_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + u32 n_left_from, * from, * to_next; + snat_out2in_next_t next_index; + u32 pkts_processed = 0; + snat_main_t * sm = &snat_main; + u32 thread_index = vlib_get_thread_index (); + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, + to_next, n_left_to_next); + + while (n_left_from >= 4 && n_left_to_next >= 2) + { + u32 bi0, bi1; + vlib_buffer_t * b0, * b1; + u32 next0 = SNAT_OUT2IN_NEXT_LOOKUP; + u32 next1 = SNAT_OUT2IN_NEXT_LOOKUP; + u32 sw_if_index0, sw_if_index1; + ip4_header_t * ip0, * ip1; + ip_csum_t sum0, sum1; + ip4_address_t new_addr0, old_addr0, new_addr1, old_addr1; + u16 new_port0, old_port0, old_port1, new_port1; + udp_header_t * udp0, * udp1; + tcp_header_t * tcp0, * tcp1; + u32 proto0, proto1; + snat_det_out_key_t key0, key1; + snat_det_map_t * dm0, * dm1; + snat_det_session_t * ses0 = 0, * ses1 = 0; + u32 rx_fib_index0, rx_fib_index1; + icmp46_header_t * icmp0, * icmp1; + + /* Prefetch next iteration. */ + { + vlib_buffer_t * p2, * p3; + + p2 = vlib_get_buffer (vm, from[2]); + p3 = vlib_get_buffer (vm, from[3]); + + vlib_prefetch_buffer_header (p2, LOAD); + vlib_prefetch_buffer_header (p3, LOAD); + + CLIB_PREFETCH (p2->data, CLIB_CACHE_LINE_BYTES, STORE); + CLIB_PREFETCH (p3->data, CLIB_CACHE_LINE_BYTES, STORE); + } + + /* speculatively enqueue b0 and b1 to the current next frame */ + to_next[0] = bi0 = from[0]; + to_next[1] = bi1 = from[1]; + from += 2; + to_next += 2; + n_left_from -= 2; + n_left_to_next -= 2; + + b0 = vlib_get_buffer (vm, bi0); + b1 = vlib_get_buffer (vm, bi1); + + ip0 = vlib_buffer_get_current (b0); + udp0 = ip4_next_header (ip0); + tcp0 = (tcp_header_t *) udp0; + + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + + if (PREDICT_FALSE(ip0->ttl == 1)) + { + vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next0 = SNAT_OUT2IN_NEXT_ICMP_ERROR; + goto trace0; + } + + proto0 = ip_proto_to_snat_proto (ip0->protocol); + + if (PREDICT_FALSE(proto0 == SNAT_PROTOCOL_ICMP)) + { + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index(sw_if_index0); + icmp0 = (icmp46_header_t *) udp0; + + next0 = icmp_out2in(sm, b0, ip0, icmp0, sw_if_index0, + rx_fib_index0, node, next0, thread_index, + &ses0, &dm0); + goto trace0; + } + + key0.ext_host_addr = ip0->src_address; + key0.ext_host_port = tcp0->src; + key0.out_port = tcp0->dst; + + dm0 = snat_det_map_by_out(sm, &ip0->dst_address); + if (PREDICT_FALSE(!dm0)) + { + clib_warning("unknown dst address: %U", + format_ip4_address, &ip0->dst_address); + next0 = SNAT_OUT2IN_NEXT_DROP; + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace0; + } + + snat_det_reverse(dm0, &ip0->dst_address, + clib_net_to_host_u16(tcp0->dst), &new_addr0); + + ses0 = snat_det_get_ses_by_out (dm0, &new_addr0, key0.as_u64); + if (PREDICT_FALSE(!ses0)) + { + clib_warning("no match src %U:%d dst %U:%d for user %U", + format_ip4_address, &ip0->src_address, + clib_net_to_host_u16 (tcp0->src), + format_ip4_address, &ip0->dst_address, + clib_net_to_host_u16 (tcp0->dst), + format_ip4_address, &new_addr0); + next0 = SNAT_OUT2IN_NEXT_DROP; + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace0; + } + new_port0 = ses0->in_port; + + old_addr0 = ip0->dst_address; + ip0->dst_address = new_addr0; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = sm->inside_fib_index; + + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_addr0.as_u32, new_addr0.as_u32, + ip4_header_t, + dst_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + if (tcp0->flags & TCP_FLAG_FIN && ses0->state == SNAT_SESSION_TCP_ESTABLISHED) + ses0->state = SNAT_SESSION_TCP_CLOSE_WAIT; + else if (tcp0->flags & TCP_FLAG_ACK && ses0->state == SNAT_SESSION_TCP_LAST_ACK) + snat_det_ses_close(dm0, ses0); + + old_port0 = tcp0->dst; + tcp0->dst = new_port0; + + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0.as_u32, new_addr0.as_u32, + ip4_header_t, + dst_address /* changed member */); + + sum0 = ip_csum_update (sum0, old_port0, new_port0, + ip4_header_t /* cheat */, + length /* changed member */); + tcp0->checksum = ip_csum_fold(sum0); + } + else + { + old_port0 = udp0->dst_port; + udp0->dst_port = new_port0; + udp0->checksum = 0; + } + + trace0: + + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_out2in_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->sw_if_index = sw_if_index0; + t->next_index = next0; + t->session_index = ~0; + if (ses0) + t->session_index = ses0 - dm0->sessions; + } + + pkts_processed += next0 != SNAT_OUT2IN_NEXT_DROP; + + b1 = vlib_get_buffer (vm, bi1); + + ip1 = vlib_buffer_get_current (b1); + udp1 = ip4_next_header (ip1); + tcp1 = (tcp_header_t *) udp1; + + sw_if_index1 = vnet_buffer(b1)->sw_if_index[VLIB_RX]; + + if (PREDICT_FALSE(ip1->ttl == 1)) + { + vnet_buffer (b1)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b1, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next1 = SNAT_OUT2IN_NEXT_ICMP_ERROR; + goto trace1; + } + + proto1 = ip_proto_to_snat_proto (ip1->protocol); + + if (PREDICT_FALSE(proto1 == SNAT_PROTOCOL_ICMP)) + { + rx_fib_index1 = ip4_fib_table_get_index_for_sw_if_index(sw_if_index1); + icmp1 = (icmp46_header_t *) udp1; + + next1 = icmp_out2in(sm, b1, ip1, icmp1, sw_if_index1, + rx_fib_index1, node, next1, thread_index, + &ses1, &dm1); + goto trace1; + } + + key1.ext_host_addr = ip1->src_address; + key1.ext_host_port = tcp1->src; + key1.out_port = tcp1->dst; + + dm1 = snat_det_map_by_out(sm, &ip1->dst_address); + if (PREDICT_FALSE(!dm1)) + { + clib_warning("unknown dst address: %U", + format_ip4_address, &ip1->dst_address); + next1 = SNAT_OUT2IN_NEXT_DROP; + b1->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace1; + } + + snat_det_reverse(dm1, &ip1->dst_address, + clib_net_to_host_u16(tcp1->dst), &new_addr1); + + ses1 = snat_det_get_ses_by_out (dm1, &new_addr1, key1.as_u64); + if (PREDICT_FALSE(!ses1)) + { + clib_warning("no match src %U:%d dst %U:%d for user %U", + format_ip4_address, &ip1->src_address, + clib_net_to_host_u16 (tcp1->src), + format_ip4_address, &ip1->dst_address, + clib_net_to_host_u16 (tcp1->dst), + format_ip4_address, &new_addr1); + next1 = SNAT_OUT2IN_NEXT_DROP; + b1->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace1; + } + new_port1 = ses1->in_port; + + old_addr1 = ip1->dst_address; + ip1->dst_address = new_addr1; + vnet_buffer(b1)->sw_if_index[VLIB_TX] = sm->inside_fib_index; + + sum1 = ip1->checksum; + sum1 = ip_csum_update (sum1, old_addr1.as_u32, new_addr1.as_u32, + ip4_header_t, + dst_address /* changed member */); + ip1->checksum = ip_csum_fold (sum1); + + if (PREDICT_TRUE(proto1 == SNAT_PROTOCOL_TCP)) + { + if (tcp1->flags & TCP_FLAG_FIN && ses1->state == SNAT_SESSION_TCP_ESTABLISHED) + ses1->state = SNAT_SESSION_TCP_CLOSE_WAIT; + else if (tcp1->flags & TCP_FLAG_ACK && ses1->state == SNAT_SESSION_TCP_LAST_ACK) + snat_det_ses_close(dm1, ses1); + + old_port1 = tcp1->dst; + tcp1->dst = new_port1; + + sum1 = tcp1->checksum; + sum1 = ip_csum_update (sum1, old_addr1.as_u32, new_addr1.as_u32, + ip4_header_t, + dst_address /* changed member */); + + sum1 = ip_csum_update (sum1, old_port1, new_port1, + ip4_header_t /* cheat */, + length /* changed member */); + tcp1->checksum = ip_csum_fold(sum1); + } + else + { + old_port1 = udp1->dst_port; + udp1->dst_port = new_port1; + udp1->checksum = 0; + } + + trace1: + + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b1->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_out2in_trace_t *t = + vlib_add_trace (vm, node, b1, sizeof (*t)); + t->sw_if_index = sw_if_index1; + t->next_index = next1; + t->session_index = ~0; + if (ses1) + t->session_index = ses1 - dm1->sessions; + } + + pkts_processed += next1 != SNAT_OUT2IN_NEXT_DROP; + + /* verify speculative enqueues, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x2 (vm, node, next_index, + to_next, n_left_to_next, + bi0, bi1, next0, next1); + } + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t * b0; + u32 next0 = SNAT_OUT2IN_NEXT_LOOKUP; + u32 sw_if_index0; + ip4_header_t * ip0; + ip_csum_t sum0; + ip4_address_t new_addr0, old_addr0; + u16 new_port0, old_port0; + udp_header_t * udp0; + tcp_header_t * tcp0; + u32 proto0; + snat_det_out_key_t key0; + snat_det_map_t * dm0; + snat_det_session_t * ses0 = 0; + u32 rx_fib_index0; + icmp46_header_t * icmp0; + + /* speculatively enqueue b0 to the current next frame */ + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + + ip0 = vlib_buffer_get_current (b0); + udp0 = ip4_next_header (ip0); + tcp0 = (tcp_header_t *) udp0; + + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + + if (PREDICT_FALSE(ip0->ttl == 1)) + { + vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next0 = SNAT_OUT2IN_NEXT_ICMP_ERROR; + goto trace00; + } + + proto0 = ip_proto_to_snat_proto (ip0->protocol); + + if (PREDICT_FALSE(proto0 == SNAT_PROTOCOL_ICMP)) + { + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index(sw_if_index0); + icmp0 = (icmp46_header_t *) udp0; + + next0 = icmp_out2in(sm, b0, ip0, icmp0, sw_if_index0, + rx_fib_index0, node, next0, thread_index, + &ses0, &dm0); + goto trace00; + } + + key0.ext_host_addr = ip0->src_address; + key0.ext_host_port = tcp0->src; + key0.out_port = tcp0->dst; + + dm0 = snat_det_map_by_out(sm, &ip0->dst_address); + if (PREDICT_FALSE(!dm0)) + { + clib_warning("unknown dst address: %U", + format_ip4_address, &ip0->dst_address); + next0 = SNAT_OUT2IN_NEXT_DROP; + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace00; + } + + snat_det_reverse(dm0, &ip0->dst_address, + clib_net_to_host_u16(tcp0->dst), &new_addr0); + + ses0 = snat_det_get_ses_by_out (dm0, &new_addr0, key0.as_u64); + if (PREDICT_FALSE(!ses0)) + { + clib_warning("no match src %U:%d dst %U:%d for user %U", + format_ip4_address, &ip0->src_address, + clib_net_to_host_u16 (tcp0->src), + format_ip4_address, &ip0->dst_address, + clib_net_to_host_u16 (tcp0->dst), + format_ip4_address, &new_addr0); + next0 = SNAT_OUT2IN_NEXT_DROP; + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace00; + } + new_port0 = ses0->in_port; + + old_addr0 = ip0->dst_address; + ip0->dst_address = new_addr0; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = sm->inside_fib_index; + + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_addr0.as_u32, new_addr0.as_u32, + ip4_header_t, + dst_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + if (tcp0->flags & TCP_FLAG_FIN && ses0->state == SNAT_SESSION_TCP_ESTABLISHED) + ses0->state = SNAT_SESSION_TCP_CLOSE_WAIT; + else if (tcp0->flags & TCP_FLAG_ACK && ses0->state == SNAT_SESSION_TCP_LAST_ACK) + snat_det_ses_close(dm0, ses0); + + old_port0 = tcp0->dst; + tcp0->dst = new_port0; + + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0.as_u32, new_addr0.as_u32, + ip4_header_t, + dst_address /* changed member */); + + sum0 = ip_csum_update (sum0, old_port0, new_port0, + ip4_header_t /* cheat */, + length /* changed member */); + tcp0->checksum = ip_csum_fold(sum0); + } + else + { + old_port0 = udp0->dst_port; + udp0->dst_port = new_port0; + udp0->checksum = 0; + } + + trace00: + + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_out2in_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->sw_if_index = sw_if_index0; + t->next_index = next0; + t->session_index = ~0; + if (ses0) + t->session_index = ses0 - dm0->sessions; + } + + pkts_processed += next0 != SNAT_OUT2IN_NEXT_DROP; + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + vlib_node_increment_counter (vm, snat_det_out2in_node.index, + SNAT_OUT2IN_ERROR_OUT2IN_PACKETS, + pkts_processed); + return frame->n_vectors; +} + +VLIB_REGISTER_NODE (snat_det_out2in_node) = { + .function = snat_det_out2in_node_fn, + .name = "nat44-det-out2in", + .vector_size = sizeof (u32), + .format_trace = format_snat_out2in_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(snat_out2in_error_strings), + .error_strings = snat_out2in_error_strings, + + .runtime_data_bytes = sizeof (snat_runtime_t), + + .n_next_nodes = SNAT_OUT2IN_N_NEXT, + + /* edit / add dispositions here */ + .next_nodes = { + [SNAT_OUT2IN_NEXT_DROP] = "error-drop", + [SNAT_OUT2IN_NEXT_LOOKUP] = "ip4-lookup", + [SNAT_OUT2IN_NEXT_ICMP_ERROR] = "ip4-icmp-error", + }, +}; +VLIB_NODE_FUNCTION_MULTIARCH (snat_det_out2in_node, snat_det_out2in_node_fn); + +/** + * Get address and port values to be used for ICMP packet translation + * and create session if needed + * + * @param[in,out] sm NAT main + * @param[in,out] node NAT node runtime + * @param[in] thread_index thread index + * @param[in,out] b0 buffer containing packet to be translated + * @param[out] p_proto protocol used for matching + * @param[out] p_value address and port after NAT translation + * @param[out] p_dont_translate if packet should not be translated + * @param d optional parameter + * @param e optional parameter + */ +u32 icmp_match_out2in_det(snat_main_t *sm, vlib_node_runtime_t *node, + u32 thread_index, vlib_buffer_t *b0, u8 *p_proto, + snat_session_key_t *p_value, + u8 *p_dont_translate, void *d, void *e) +{ + ip4_header_t *ip0; + icmp46_header_t *icmp0; + u32 sw_if_index0; + u8 protocol; + snat_det_out_key_t key0; + u8 dont_translate = 0; + u32 next0 = ~0; + icmp_echo_header_t *echo0, *inner_echo0 = 0; + ip4_header_t *inner_ip0; + void *l4_header = 0; + icmp46_header_t *inner_icmp0; + snat_det_map_t * dm0 = 0; + ip4_address_t new_addr0 = {{0}}; + snat_det_session_t * ses0 = 0; + ip4_address_t out_addr; + + ip0 = vlib_buffer_get_current (b0); + icmp0 = (icmp46_header_t *) ip4_next_header (ip0); + echo0 = (icmp_echo_header_t *)(icmp0+1); + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + + if (!icmp_is_error_message (icmp0)) + { + protocol = SNAT_PROTOCOL_ICMP; + key0.ext_host_addr = ip0->src_address; + key0.ext_host_port = 0; + key0.out_port = echo0->identifier; + out_addr = ip0->dst_address; + } + else + { + inner_ip0 = (ip4_header_t *)(echo0+1); + l4_header = ip4_next_header (inner_ip0); + protocol = ip_proto_to_snat_proto (inner_ip0->protocol); + key0.ext_host_addr = inner_ip0->dst_address; + out_addr = inner_ip0->src_address; + switch (protocol) + { + case SNAT_PROTOCOL_ICMP: + inner_icmp0 = (icmp46_header_t*)l4_header; + inner_echo0 = (icmp_echo_header_t *)(inner_icmp0+1); + key0.ext_host_port = 0; + key0.out_port = inner_echo0->identifier; + break; + case SNAT_PROTOCOL_UDP: + case SNAT_PROTOCOL_TCP: + key0.ext_host_port = ((tcp_udp_header_t*)l4_header)->dst_port; + key0.out_port = ((tcp_udp_header_t*)l4_header)->src_port; + break; + default: + b0->error = node->errors[SNAT_OUT2IN_ERROR_UNSUPPORTED_PROTOCOL]; + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out; + } + } + + dm0 = snat_det_map_by_out(sm, &out_addr); + if (PREDICT_FALSE(!dm0)) + { + /* Don't NAT packet aimed at the intfc address */ + if (PREDICT_FALSE(is_interface_addr(sm, node, sw_if_index0, + ip0->dst_address.as_u32))) + { + dont_translate = 1; + goto out; + } + clib_warning("unknown dst address: %U", + format_ip4_address, &ip0->dst_address); + goto out; + } + + snat_det_reverse(dm0, &ip0->dst_address, + clib_net_to_host_u16(key0.out_port), &new_addr0); + + ses0 = snat_det_get_ses_by_out (dm0, &new_addr0, key0.as_u64); + if (PREDICT_FALSE(!ses0)) + { + /* Don't NAT packet aimed at the intfc address */ + if (PREDICT_FALSE(is_interface_addr(sm, node, sw_if_index0, + ip0->dst_address.as_u32))) + { + dont_translate = 1; + goto out; + } + clib_warning("no match src %U:%d dst %U:%d for user %U", + format_ip4_address, &key0.ext_host_addr, + clib_net_to_host_u16 (key0.ext_host_port), + format_ip4_address, &out_addr, + clib_net_to_host_u16 (key0.out_port), + format_ip4_address, &new_addr0); + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out; + } + + if (PREDICT_FALSE(icmp0->type != ICMP4_echo_reply && + !icmp_is_error_message (icmp0))) + { + b0->error = node->errors[SNAT_OUT2IN_ERROR_BAD_ICMP_TYPE]; + next0 = SNAT_OUT2IN_NEXT_DROP; + goto out; + } + + goto out; + +out: + *p_proto = protocol; + if (ses0) + { + p_value->addr = new_addr0; + p_value->fib_index = sm->inside_fib_index; + p_value->port = ses0->in_port; + } + *p_dont_translate = dont_translate; + if (d) + *(snat_det_session_t**)d = ses0; + if (e) + *(snat_det_map_t**)e = dm0; + return next0; +} + +/**********************/ +/*** worker handoff ***/ +/**********************/ +static uword +snat_out2in_worker_handoff_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + snat_main_t *sm = &snat_main; + vlib_thread_main_t *tm = vlib_get_thread_main (); + u32 n_left_from, *from, *to_next = 0; + static __thread vlib_frame_queue_elt_t **handoff_queue_elt_by_worker_index; + static __thread vlib_frame_queue_t **congested_handoff_queue_by_worker_index + = 0; + vlib_frame_queue_elt_t *hf = 0; + vlib_frame_t *f = 0; + int i; + u32 n_left_to_next_worker = 0, *to_next_worker = 0; + u32 next_worker_index = 0; + u32 current_worker_index = ~0; + u32 thread_index = vlib_get_thread_index (); + + ASSERT (vec_len (sm->workers)); + + if (PREDICT_FALSE (handoff_queue_elt_by_worker_index == 0)) + { + vec_validate (handoff_queue_elt_by_worker_index, tm->n_vlib_mains - 1); + + vec_validate_init_empty (congested_handoff_queue_by_worker_index, + sm->first_worker_index + sm->num_workers - 1, + (vlib_frame_queue_t *) (~0)); + } + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + + while (n_left_from > 0) + { + u32 bi0; + vlib_buffer_t *b0; + u32 sw_if_index0; + u32 rx_fib_index0; + ip4_header_t * ip0; + u8 do_handoff; + + bi0 = from[0]; + from += 1; + n_left_from -= 1; + + b0 = vlib_get_buffer (vm, bi0); + + sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index(sw_if_index0); + + ip0 = vlib_buffer_get_current (b0); + + next_worker_index = sm->worker_out2in_cb(ip0, rx_fib_index0); + + if (PREDICT_FALSE (next_worker_index != thread_index)) + { + do_handoff = 1; + + if (next_worker_index != current_worker_index) + { + if (hf) + hf->n_vectors = VLIB_FRAME_SIZE - n_left_to_next_worker; + + hf = vlib_get_worker_handoff_queue_elt (sm->fq_out2in_index, + next_worker_index, + handoff_queue_elt_by_worker_index); + + n_left_to_next_worker = VLIB_FRAME_SIZE - hf->n_vectors; + to_next_worker = &hf->buffer_index[hf->n_vectors]; + current_worker_index = next_worker_index; + } + + /* enqueue to correct worker thread */ + to_next_worker[0] = bi0; + to_next_worker++; + n_left_to_next_worker--; + + if (n_left_to_next_worker == 0) + { + hf->n_vectors = VLIB_FRAME_SIZE; + vlib_put_frame_queue_elt (hf); + current_worker_index = ~0; + handoff_queue_elt_by_worker_index[next_worker_index] = 0; + hf = 0; + } + } + else + { + do_handoff = 0; + /* if this is 1st frame */ + if (!f) + { + f = vlib_get_frame_to_node (vm, sm->out2in_node_index); + to_next = vlib_frame_vector_args (f); + } + + to_next[0] = bi0; + to_next += 1; + f->n_vectors++; + } + + if (PREDICT_FALSE ((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_out2in_worker_handoff_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->next_worker_index = next_worker_index; + t->do_handoff = do_handoff; + } + } + + if (f) + vlib_put_frame_to_node (vm, sm->out2in_node_index, f); + + if (hf) + hf->n_vectors = VLIB_FRAME_SIZE - n_left_to_next_worker; + + /* Ship frames to the worker nodes */ + for (i = 0; i < vec_len (handoff_queue_elt_by_worker_index); i++) + { + if (handoff_queue_elt_by_worker_index[i]) + { + hf = handoff_queue_elt_by_worker_index[i]; + /* + * It works better to let the handoff node + * rate-adapt, always ship the handoff queue element. + */ + if (1 || hf->n_vectors == hf->last_n_vectors) + { + vlib_put_frame_queue_elt (hf); + handoff_queue_elt_by_worker_index[i] = 0; + } + else + hf->last_n_vectors = hf->n_vectors; + } + congested_handoff_queue_by_worker_index[i] = + (vlib_frame_queue_t *) (~0); + } + hf = 0; + current_worker_index = ~0; + return frame->n_vectors; +} + +VLIB_REGISTER_NODE (snat_out2in_worker_handoff_node) = { + .function = snat_out2in_worker_handoff_fn, + .name = "nat44-out2in-worker-handoff", + .vector_size = sizeof (u32), + .format_trace = format_snat_out2in_worker_handoff_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_next_nodes = 1, + + .next_nodes = { + [0] = "error-drop", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (snat_out2in_worker_handoff_node, snat_out2in_worker_handoff_fn); + +static uword +snat_out2in_fast_node_fn (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + u32 n_left_from, * from, * to_next; + snat_out2in_next_t next_index; + u32 pkts_processed = 0; + snat_main_t * sm = &snat_main; + + from = vlib_frame_vector_args (frame); + n_left_from = frame->n_vectors; + next_index = node->cached_next_index; + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, + to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + u32 bi0; + vlib_buffer_t * b0; + u32 next0 = SNAT_OUT2IN_NEXT_DROP; + u32 sw_if_index0; + ip4_header_t * ip0; + ip_csum_t sum0; + u32 new_addr0, old_addr0; + u16 new_port0, old_port0; + udp_header_t * udp0; + tcp_header_t * tcp0; + icmp46_header_t * icmp0; + snat_session_key_t key0, sm0; + u32 proto0; + u32 rx_fib_index0; + + /* speculatively enqueue b0 to the current next frame */ + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + + ip0 = vlib_buffer_get_current (b0); + udp0 = ip4_next_header (ip0); + tcp0 = (tcp_header_t *) udp0; + icmp0 = (icmp46_header_t *) udp0; + + sw_if_index0 = vnet_buffer(b0)->sw_if_index[VLIB_RX]; + rx_fib_index0 = ip4_fib_table_get_index_for_sw_if_index(sw_if_index0); + + vnet_feature_next (sw_if_index0, &next0, b0); + + if (PREDICT_FALSE(ip0->ttl == 1)) + { + vnet_buffer (b0)->sw_if_index[VLIB_TX] = (u32) ~ 0; + icmp4_error_set_vnet_buffer (b0, ICMP4_time_exceeded, + ICMP4_time_exceeded_ttl_exceeded_in_transit, + 0); + next0 = SNAT_OUT2IN_NEXT_ICMP_ERROR; + goto trace00; + } + + proto0 = ip_proto_to_snat_proto (ip0->protocol); + + if (PREDICT_FALSE (proto0 == ~0)) + goto trace00; + + if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP)) + { + next0 = icmp_out2in(sm, b0, ip0, icmp0, sw_if_index0, + rx_fib_index0, node, next0, ~0, 0, 0); + goto trace00; + } + + key0.addr = ip0->dst_address; + key0.port = udp0->dst_port; + key0.fib_index = rx_fib_index0; + + if (snat_static_mapping_match(sm, key0, &sm0, 1, 0)) + { + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace00; + } + + new_addr0 = sm0.addr.as_u32; + new_port0 = sm0.port; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = sm0.fib_index; + old_addr0 = ip0->dst_address.as_u32; + ip0->dst_address.as_u32 = new_addr0; + + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + dst_address /* changed member */); + ip0->checksum = ip_csum_fold (sum0); + + if (PREDICT_FALSE(new_port0 != udp0->dst_port)) + { + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + old_port0 = tcp0->dst_port; + tcp0->dst_port = new_port0; + + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + dst_address /* changed member */); + + sum0 = ip_csum_update (sum0, old_port0, new_port0, + ip4_header_t /* cheat */, + length /* changed member */); + tcp0->checksum = ip_csum_fold(sum0); + } + else + { + old_port0 = udp0->dst_port; + udp0->dst_port = new_port0; + udp0->checksum = 0; + } + } + else + { + if (PREDICT_TRUE(proto0 == SNAT_PROTOCOL_TCP)) + { + sum0 = tcp0->checksum; + sum0 = ip_csum_update (sum0, old_addr0, new_addr0, + ip4_header_t, + dst_address /* changed member */); + + tcp0->checksum = ip_csum_fold(sum0); + } + } + + trace00: + + if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) + && (b0->flags & VLIB_BUFFER_IS_TRACED))) + { + snat_out2in_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->sw_if_index = sw_if_index0; + t->next_index = next0; + } + + pkts_processed += next0 != SNAT_OUT2IN_NEXT_DROP; + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + vlib_node_increment_counter (vm, snat_out2in_fast_node.index, + SNAT_OUT2IN_ERROR_OUT2IN_PACKETS, + pkts_processed); + return frame->n_vectors; +} + +VLIB_REGISTER_NODE (snat_out2in_fast_node) = { + .function = snat_out2in_fast_node_fn, + .name = "nat44-out2in-fast", + .vector_size = sizeof (u32), + .format_trace = format_snat_out2in_fast_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(snat_out2in_error_strings), + .error_strings = snat_out2in_error_strings, + + .runtime_data_bytes = sizeof (snat_runtime_t), + + .n_next_nodes = SNAT_OUT2IN_N_NEXT, + + /* edit / add dispositions here */ + .next_nodes = { + [SNAT_OUT2IN_NEXT_LOOKUP] = "ip4-lookup", + [SNAT_OUT2IN_NEXT_DROP] = "error-drop", + [SNAT_OUT2IN_NEXT_ICMP_ERROR] = "ip4-icmp-error", + }, +}; +VLIB_NODE_FUNCTION_MULTIARCH (snat_out2in_fast_node, snat_out2in_fast_node_fn); |