diff options
author | Matus Fabian <matfabia@cisco.com> | 2016-08-26 05:45:27 -0700 |
---|---|---|
committer | Ole Trøan <otroan@employees.org> | 2016-10-05 11:41:51 +0000 |
commit | db6498848126839010eaad0bc79571a9ccbfc1bc (patch) | |
tree | ed1ad922173bc6187bc7da77b8596f7164272ed2 /plugins | |
parent | b8602b595fc89fe8b8fb00cef4820eedf225a93e (diff) |
VPP-339: SNAT static mapping
1:1 NAT
1:1 NAT with ports
1:1 NAT static mapping only - add "static mapping only [connection tracking]"
to snat startup config
Change-Id: I37cd06a9d71d1943eb6618034d7c547c4a9348c4
Signed-off-by: Matus Fabian <matfabia@cisco.com>
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/snat-plugin/snat/in2out.c | 422 | ||||
-rw-r--r-- | plugins/snat-plugin/snat/out2in.c | 510 | ||||
-rw-r--r-- | plugins/snat-plugin/snat/snat.api | 173 | ||||
-rw-r--r-- | plugins/snat-plugin/snat/snat.c | 808 | ||||
-rw-r--r-- | plugins/snat-plugin/snat/snat.h | 56 | ||||
-rw-r--r-- | plugins/snat-plugin/snat/snat_test.c | 203 |
6 files changed, 2017 insertions, 155 deletions
diff --git a/plugins/snat-plugin/snat/in2out.c b/plugins/snat-plugin/snat/in2out.c index 3f7df910..25dad934 100644 --- a/plugins/snat-plugin/snat/in2out.c +++ b/plugins/snat-plugin/snat/in2out.c @@ -25,8 +25,6 @@ #include <vppinfra/error.h> #include <vppinfra/elog.h> -vlib_node_registration_t snat_in2out_node, snat_in2out_slowpath_node; - typedef struct { u32 sw_if_index; u32 next_index; @@ -50,14 +48,29 @@ static u8 * format_snat_in2out_trace (u8 * s, va_list * args) 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, "SANT_IN2OUT_FAST: sw_if_index %d, next index %d", + t->sw_if_index, t->next_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; #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, "icmp type not echo-request") +_(BAD_ICMP_TYPE, "icmp type not echo-request") \ +_(NO_TRANSLATION, "No translation") typedef enum { #define _(sym,str) SNAT_IN2OUT_ERROR_##sym, @@ -98,7 +111,7 @@ static u32 slow_path (snat_main_t *sm, vlib_buffer_t *b0, dlist_elt_t * per_user_list_head_elt; u32 session_index; snat_session_key_t key1; - u32 address_index; + u32 address_index = ~0; u32 outside_fib_index; uword * p; @@ -128,9 +141,9 @@ static u32 slow_path (snat_main_t *sm, vlib_buffer_t *b0, sm->list_pool; clib_dlist_init (sm->list_pool, u->sessions_per_user_list_head_index); - + kv0.value = u - sm->users; - + /* add user */ clib_bihash_add_del_8_8 (&sm->user_hash, &kv0, 1 /* is_add */); } @@ -139,30 +152,32 @@ static u32 slow_path (snat_main_t *sm, vlib_buffer_t *b0, u = pool_elt_at_index (sm->users, value0.value); } - /* Over quota? Recycle the least recently used translation */ + /* Over quota? Recycle the least recently used dynamic translation */ if (u->nsessions >= sm->max_translations_per_user) { - /* Remove the oldest translation */ - oldest_per_user_translation_list_index = - clib_dlist_remove_head - (sm->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->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->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->sessions, session_index); + /* Remove the oldest dynamic translation */ + do { + oldest_per_user_translation_list_index = + clib_dlist_remove_head + (sm->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->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->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->sessions, session_index); + } while (!snat_is_session_static (s)); /* Remove in2out, out2in keys */ kv0.key = s->in2out.as_u64; @@ -187,12 +202,18 @@ static u32 slow_path (snat_main_t *sm, vlib_buffer_t *b0, } else { - if (snat_alloc_outside_address_and_port (sm, &key1, &address_index)) - { - ASSERT(0); + u8 static_mapping = 1; - b0->error = node->errors[SNAT_IN2OUT_ERROR_OUT_OF_PORTS]; - return SNAT_IN2OUT_NEXT_DROP; + /* First try to match static mapping by local address and port */ + if (snat_static_mapping_match (sm, *key0, &key1, 0)) + { + static_mapping = 0; + /* Try to create dynamic translation */ + if (snat_alloc_outside_address_and_port (sm, &key1, &address_index)) + { + b0->error = node->errors[SNAT_IN2OUT_ERROR_OUT_OF_PORTS]; + return SNAT_IN2OUT_NEXT_DROP; + } } /* Create a new session */ @@ -201,19 +222,28 @@ static u32 slow_path (snat_main_t *sm, vlib_buffer_t *b0, 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->list_pool, per_user_translation_list_elt); clib_dlist_init (sm->list_pool, per_user_translation_list_elt - sm->list_pool); - + per_user_translation_list_elt->value = s - sm->sessions; s->per_user_index = per_user_translation_list_elt - sm->list_pool; s->per_user_list_head_index = u->sessions_per_user_list_head_index; - + clib_dlist_addtail (sm->list_pool, s->per_user_list_head_index, per_user_translation_list_elt - sm->list_pool); - u->nsessions++; - } + } s->in2out = *key0; s->out2in = key1; @@ -317,13 +347,17 @@ static inline u32 icmp_in2out_slow_path (snat_main_t *sm, identifier); icmp0->checksum = ip_csum_fold (sum0); - /* Accounting, per-user LRU list maintenance */ + /* Accounting */ s0->last_heard = now; s0->total_pkts++; s0->total_bytes += vlib_buffer_length_in_chain (sm->vlib_main, b0); - clib_dlist_remove (sm->list_pool, s0->per_user_index); - clib_dlist_addtail (sm->list_pool, s0->per_user_list_head_index, - s0->per_user_index); + /* Per-user LRU list maintenance for dynamic translations */ + if (!snat_is_session_static (s0)) + { + clib_dlist_remove (sm->list_pool, s0->per_user_index); + clib_dlist_addtail (sm->list_pool, s0->per_user_list_head_index, + s0->per_user_index); + } return next0; } @@ -523,13 +557,17 @@ snat_in2out_node_fn_inline (vlib_main_t * vm, udp0->checksum = 0; } - /* Accounting, per-user LRU list maintenance */ + /* Accounting */ s0->last_heard = now; s0->total_pkts++; s0->total_bytes += vlib_buffer_length_in_chain (vm, b0); - clib_dlist_remove (sm->list_pool, s0->per_user_index); - clib_dlist_addtail (sm->list_pool, s0->per_user_list_head_index, - s0->per_user_index); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s0)) + { + clib_dlist_remove (sm->list_pool, s0->per_user_index); + clib_dlist_addtail (sm->list_pool, s0->per_user_list_head_index, + s0->per_user_index); + } trace00: if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) @@ -668,13 +706,17 @@ snat_in2out_node_fn_inline (vlib_main_t * vm, udp1->checksum = 0; } - /* Accounting, per-user LRU list maintenance */ + /* Accounting */ s1->last_heard = now; s1->total_pkts++; s1->total_bytes += vlib_buffer_length_in_chain (vm, b1); - clib_dlist_remove (sm->list_pool, s1->per_user_index); - clib_dlist_addtail (sm->list_pool, s1->per_user_list_head_index, - s1->per_user_index); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s1)) + { + clib_dlist_remove (sm->list_pool, s1->per_user_index); + clib_dlist_addtail (sm->list_pool, s1->per_user_list_head_index, + s1->per_user_index); + } trace01: if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) @@ -849,13 +891,17 @@ snat_in2out_node_fn_inline (vlib_main_t * vm, udp0->checksum = 0; } - /* Accounting, per-user LRU list maintenance */ + /* Accounting */ s0->last_heard = now; s0->total_pkts++; s0->total_bytes += vlib_buffer_length_in_chain (vm, b0); - clib_dlist_remove (sm->list_pool, s0->per_user_index); - clib_dlist_addtail (sm->list_pool, s0->per_user_list_head_index, - s0->per_user_index); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s0)) + { + clib_dlist_remove (sm->list_pool, s0->per_user_index); + clib_dlist_addtail (sm->list_pool, s0->per_user_list_head_index, + s0->per_user_index); + } trace0: if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) @@ -951,3 +997,273 @@ VLIB_REGISTER_NODE (snat_in2out_slowpath_node) = { }; VLIB_NODE_FUNCTION_MULTIARCH (snat_in2out_slowpath_node, snat_in2out_slow_path_fn); + +static inline u32 icmp_in2out_static_map (snat_main_t *sm, + vlib_buffer_t * b0, + ip4_header_t * ip0, + icmp46_header_t * icmp0, + u32 sw_if_index0, + vlib_node_runtime_t * node, + u32 next0) +{ + snat_session_key_t key0, sm0; + icmp_echo_header_t *echo0; + u32 new_addr0, old_addr0; + u16 old_id0, new_id0; + ip_csum_t sum0; + snat_runtime_t * rt = (snat_runtime_t *)node->runtime_data; + + echo0 = (icmp_echo_header_t *)(icmp0+1); + + key0.addr = ip0->src_address; + key0.port = echo0->identifier; + + if (snat_static_mapping_match(sm, key0, &sm0, 0)) + { + 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; + rt->cached_ip4_address = first_int_addr->as_u32; + } + + /* Don't NAT packet aimed at the intfc address */ + if (PREDICT_FALSE(ip0->dst_address.as_u32 == + rt->cached_ip4_address)) + return next0; + + b0->error = node->errors[SNAT_IN2OUT_ERROR_NO_TRANSLATION]; + return SNAT_IN2OUT_NEXT_DROP; + } + + new_addr0 = sm0.addr.as_u32; + new_id0 = 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_id0 != echo0->identifier)) + { + old_id0 = echo0->identifier; + 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); + } + + return next0; +} + +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; + snat_runtime_t * rt = (snat_runtime_t *)node->runtime_data; + 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; + + /* 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]; + + proto0 = ~0; + proto0 = (ip0->protocol == IP_PROTOCOL_UDP) + ? SNAT_PROTOCOL_UDP : proto0; + proto0 = (ip0->protocol == IP_PROTOCOL_TCP) + ? SNAT_PROTOCOL_TCP : proto0; + proto0 = (ip0->protocol == IP_PROTOCOL_ICMP) + ? SNAT_PROTOCOL_ICMP : proto0; + + if (PREDICT_FALSE (proto0 == ~0)) + goto trace0; + + if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP)) + { + 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; + rt->cached_ip4_address = first_int_addr->as_u32; + } + + /* Don't NAT packet aimed at the intfc address */ + if (PREDICT_FALSE(ip0->dst_address.as_u32 == + rt->cached_ip4_address)) + goto trace0; + + next0 = icmp_in2out_static_map + (sm, b0, ip0, icmp0, sw_if_index0, node, next0); + goto trace0; + } + + key0.addr = ip0->src_address; + key0.port = udp0->src_port; + + if (snat_static_mapping_match(sm, key0, &sm0, 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->ports.src; + tcp0->ports.src = 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); + } + } + + 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 = "snat-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] = "snat-in2out-slowpath", + }, +}; + +VLIB_NODE_FUNCTION_MULTIARCH (snat_in2out_fast_node, snat_in2out_fast_static_map_fn); diff --git a/plugins/snat-plugin/snat/out2in.c b/plugins/snat-plugin/snat/out2in.c index 861dae63..b3497c40 100644 --- a/plugins/snat-plugin/snat/out2in.c +++ b/plugins/snat-plugin/snat/out2in.c @@ -25,8 +25,6 @@ #include <vppinfra/error.h> #include <vppinfra/elog.h> -vlib_node_registration_t snat_out2in_node; - typedef struct { u32 sw_if_index; u32 next_index; @@ -45,7 +43,20 @@ static u8 * format_snat_out2in_trace (u8 * s, va_list * args) 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, "SNAT_OUT2IN_FAST: sw_if_index %d, next index %d", + t->sw_if_index, t->next_index); + return s; +} + + vlib_node_registration_t snat_out2in_node; +vlib_node_registration_t snat_out2in_fast_node; #define foreach_snat_out2in_error \ _(UNSUPPORTED_PROTOCOL, "Unsupported protocol") \ @@ -71,6 +82,101 @@ typedef enum { 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 SNAT main. + * @param b0 Vlib buffer. + * @param in2out In2out SNAT session key. + * @param out2in Out2in SNAT 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) +{ + 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; + + 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->users, u); + memset (u, 0, sizeof (*u)); + u->addr = in2out.addr; + + pool_get (sm->list_pool, per_user_list_head_elt); + + u->sessions_per_user_list_head_index = per_user_list_head_elt - + sm->list_pool; + + clib_dlist_init (sm->list_pool, u->sessions_per_user_list_head_index); + + kv0.value = u - sm->users; + + /* add user */ + clib_bihash_add_del_8_8 (&sm->user_hash, &kv0, 1 /* is_add */); + } + else + { + u = pool_elt_at_index (sm->users, value0.value); + } + + pool_get (sm->sessions, s); + memset (s, 0, sizeof (*s)); + + s->outside_address_index = ~0; + s->flags |= SNAT_SESSION_FLAG_STATIC_MAPPING; + u->nstaticsessions++; + + /* Create list elts */ + pool_get (sm->list_pool, per_user_translation_list_elt); + clib_dlist_init (sm->list_pool, per_user_translation_list_elt - + sm->list_pool); + + per_user_translation_list_elt->value = s - sm->sessions; + s->per_user_index = per_user_translation_list_elt - sm->list_pool; + s->per_user_list_head_index = u->sessions_per_user_list_head_index; + + clib_dlist_addtail (sm->list_pool, s->per_user_list_head_index, + per_user_translation_list_elt - sm->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->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->sessions; + + if (clib_bihash_add_del_8_8 (&sm->out2in, &kv0, 1 /* is_add */)) + clib_warning ("out2in key add failed"); + + return s; +} + static inline u32 icmp_out2in_slow_path (snat_main_t *sm, vlib_buffer_t * b0, ip4_header_t * ip0, @@ -80,7 +186,7 @@ static inline u32 icmp_out2in_slow_path (snat_main_t *sm, vlib_node_runtime_t * node, u32 next0, f64 now) { - snat_session_key_t key0; + snat_session_key_t key0, sm0; icmp_echo_header_t *echo0; clib_bihash_kv_8_8_t kv0, value0; snat_session_t * s0; @@ -100,24 +206,35 @@ static inline u32 icmp_out2in_slow_path (snat_main_t *sm, if (clib_bihash_search_8_8 (&sm->out2in, &kv0, &value0)) { - ip4_address_t * first_int_addr; - - if (PREDICT_FALSE(rt->cached_sw_if_index != sw_if_index0)) + /* 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)) { - 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; - rt->cached_ip4_address = first_int_addr->as_u32; + 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; + rt->cached_ip4_address = first_int_addr->as_u32; + } + + /* Don't NAT packet aimed at the intfc address */ + if (PREDICT_FALSE(ip0->dst_address.as_u32 == + rt->cached_ip4_address)) + return next0; + + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + return SNAT_OUT2IN_NEXT_DROP; } - - /* Don't NAT packet aimed at the intfc address */ - if (PREDICT_FALSE(ip0->dst_address.as_u32 == - rt->cached_ip4_address)) - return next0; - b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; - return SNAT_OUT2IN_NEXT_DROP; + /* Create session initiated by host from external network */ + s0 = create_session_for_static_mapping(sm, b0, sm0, key0, + node); + if (!s0) + return SNAT_OUT2IN_NEXT_DROP; } else s0 = pool_elt_at_index (sm->sessions, value0.value); @@ -142,13 +259,17 @@ static inline u32 icmp_out2in_slow_path (snat_main_t *sm, identifier); icmp0->checksum = ip_csum_fold (sum0); - /* Accounting, per-user LRU list maintenance */ + /* Accounting */ s0->last_heard = now; s0->total_pkts++; s0->total_bytes += vlib_buffer_length_in_chain (sm->vlib_main, b0); - clib_dlist_remove (sm->list_pool, s0->per_user_index); - clib_dlist_addtail (sm->list_pool, s0->per_user_list_head_index, - s0->per_user_index); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s0)) + { + clib_dlist_remove (sm->list_pool, s0->per_user_index); + clib_dlist_addtail (sm->list_pool, s0->per_user_list_head_index, + s0->per_user_index); + } return next0; } @@ -193,7 +314,7 @@ snat_out2in_node_fn (vlib_main_t * vm, udp_header_t * udp0, * udp1; tcp_header_t * tcp0, * tcp1; icmp46_header_t * icmp0, * icmp1; - snat_session_key_t key0, key1; + 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; @@ -264,14 +385,27 @@ snat_out2in_node_fn (vlib_main_t * vm, kv0.key = key0.as_u64; if (clib_bihash_search_8_8 (&sm->out2in, &kv0, &value0)) - goto trace0; + { + /* 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)) + { + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace0; + } + + /* Create session initiated by host from external network */ + s0 = create_session_for_static_mapping(sm, b0, sm0, key0, node); + if (!s0) + goto trace0; + } else s0 = pool_elt_at_index (sm->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->out2in.fib_index; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->in2out.fib_index; sum0 = ip0->checksum; sum0 = ip_csum_update (sum0, old_addr0, new_addr0, @@ -302,13 +436,17 @@ snat_out2in_node_fn (vlib_main_t * vm, udp0->checksum = 0; } - /* Accounting, per-user LRU list maintenance */ + /* Accounting */ s0->last_heard = now; s0->total_pkts++; s0->total_bytes += vlib_buffer_length_in_chain (vm, b0); - clib_dlist_remove (sm->list_pool, s0->per_user_index); - clib_dlist_addtail (sm->list_pool, s0->per_user_list_head_index, - s0->per_user_index); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s0)) + { + clib_dlist_remove (sm->list_pool, s0->per_user_index); + clib_dlist_addtail (sm->list_pool, s0->per_user_list_head_index, + s0->per_user_index); + } trace0: if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) @@ -366,14 +504,27 @@ snat_out2in_node_fn (vlib_main_t * vm, kv1.key = key1.as_u64; if (clib_bihash_search_8_8 (&sm->out2in, &kv1, &value1)) - goto trace1; + { + /* 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)) + { + b1->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace1; + } + + /* Create session initiated by host from external network */ + s1 = create_session_for_static_mapping(sm, b1, sm1, key1, node); + if (!s1) + goto trace1; + } else s1 = pool_elt_at_index (sm->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->out2in.fib_index; + vnet_buffer(b1)->sw_if_index[VLIB_TX] = s1->in2out.fib_index; sum1 = ip1->checksum; sum1 = ip_csum_update (sum1, old_addr1, new_addr1, @@ -404,13 +555,17 @@ snat_out2in_node_fn (vlib_main_t * vm, udp1->checksum = 0; } - /* Accounting, per-user LRU list maintenance */ + /* Accounting */ s1->last_heard = now; s1->total_pkts++; s1->total_bytes += vlib_buffer_length_in_chain (vm, b1); - clib_dlist_remove (sm->list_pool, s1->per_user_index); - clib_dlist_addtail (sm->list_pool, s1->per_user_list_head_index, - s1->per_user_index); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s1)) + { + clib_dlist_remove (sm->list_pool, s1->per_user_index); + clib_dlist_addtail (sm->list_pool, s1->per_user_list_head_index, + s1->per_user_index); + } trace1: if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) @@ -447,7 +602,7 @@ snat_out2in_node_fn (vlib_main_t * vm, udp_header_t * udp0; tcp_header_t * tcp0; icmp46_header_t * icmp0; - snat_session_key_t key0; + snat_session_key_t key0, sm0; u32 rx_fib_index0; u32 proto0; snat_session_t * s0 = 0; @@ -503,14 +658,27 @@ snat_out2in_node_fn (vlib_main_t * vm, kv0.key = key0.as_u64; if (clib_bihash_search_8_8 (&sm->out2in, &kv0, &value0)) - goto trace00; + { + /* 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)) + { + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + goto trace00; + } + + /* Create session initiated by host from external network */ + s0 = create_session_for_static_mapping(sm, b0, sm0, key0, node); + if (!s0) + goto trace00; + } else s0 = pool_elt_at_index (sm->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->out2in.fib_index; + vnet_buffer(b0)->sw_if_index[VLIB_TX] = s0->in2out.fib_index; sum0 = ip0->checksum; sum0 = ip_csum_update (sum0, old_addr0, new_addr0, @@ -541,13 +709,17 @@ snat_out2in_node_fn (vlib_main_t * vm, udp0->checksum = 0; } - /* Accounting, per-user LRU list maintenance */ + /* Accounting */ s0->last_heard = now; s0->total_pkts++; s0->total_bytes += vlib_buffer_length_in_chain (vm, b0); - clib_dlist_remove (sm->list_pool, s0->per_user_index); - clib_dlist_addtail (sm->list_pool, s0->per_user_list_head_index, - s0->per_user_index); + /* Per-user LRU list maintenance for dynamic translation */ + if (!snat_is_session_static (s0)) + { + clib_dlist_remove (sm->list_pool, s0->per_user_index); + clib_dlist_addtail (sm->list_pool, s0->per_user_list_head_index, + s0->per_user_index); + } trace00: if (PREDICT_FALSE((node->flags & VLIB_NODE_FLAG_TRACE) @@ -599,3 +771,257 @@ VLIB_REGISTER_NODE (snat_out2in_node) = { }, }; VLIB_NODE_FUNCTION_MULTIARCH (snat_out2in_node, snat_out2in_node_fn); + +static inline u32 icmp_out2in_fast (snat_main_t *sm, + vlib_buffer_t * b0, + ip4_header_t * ip0, + icmp46_header_t * icmp0, + u32 sw_if_index0, + vlib_node_runtime_t * node, + u32 next0) +{ + snat_session_key_t key0, sm0; + icmp_echo_header_t *echo0; + u32 new_addr0, old_addr0; + u16 old_id0, new_id0; + ip_csum_t sum0; + snat_runtime_t * rt = (snat_runtime_t *)node->runtime_data; + + echo0 = (icmp_echo_header_t *)(icmp0+1); + + key0.addr = ip0->dst_address; + key0.port = echo0->identifier; + + if (snat_static_mapping_match(sm, key0, &sm0, 1)) + { + 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; + rt->cached_ip4_address = first_int_addr->as_u32; + } + + /* Don't NAT packet aimed at the intfc address */ + if (PREDICT_FALSE(ip0->dst_address.as_u32 == + rt->cached_ip4_address)) + return next0; + + b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION]; + return SNAT_OUT2IN_NEXT_DROP; + } + + new_addr0 = sm0.addr.as_u32; + new_id0 = 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_id0 != echo0->identifier)) + { + old_id0 = echo0->identifier; + 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); + } + + return next0; +} + +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; + ip_lookup_main_t * lm = sm->ip4_lookup_main; + ip_config_main_t * cm = &lm->feature_config_mains[VNET_IP_RX_UNICAST_FEAT]; + + 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; + + /* 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]; + + vnet_get_config_data (&cm->config_main, + &b0->current_config_index, + &next0, + 0 /* sizeof config data */); + proto0 = ~0; + proto0 = (ip0->protocol == IP_PROTOCOL_UDP) + ? SNAT_PROTOCOL_UDP : proto0; + proto0 = (ip0->protocol == IP_PROTOCOL_TCP) + ? SNAT_PROTOCOL_TCP : proto0; + proto0 = (ip0->protocol == IP_PROTOCOL_ICMP) + ? SNAT_PROTOCOL_ICMP : proto0; + + if (PREDICT_FALSE (proto0 == ~0)) + goto trace00; + + if (PREDICT_FALSE (proto0 == SNAT_PROTOCOL_ICMP)) + { + next0 = icmp_out2in_fast + (sm, b0, ip0, icmp0, sw_if_index0, node, next0); + goto trace00; + } + + key0.addr = ip0->dst_address; + key0.port = udp0->dst_port; + + if (snat_static_mapping_match(sm, key0, &sm0, 1)) + { + 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->ports.dst; + tcp0->ports.dst = 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 = "snat-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_DROP] = "error-drop", + }, +}; +VLIB_NODE_FUNCTION_MULTIARCH (snat_out2in_fast_node, snat_out2in_fast_node_fn); diff --git a/plugins/snat-plugin/snat/snat.api b/plugins/snat-plugin/snat/snat.api index 0c5f4373..daacf9ff 100644 --- a/plugins/snat-plugin/snat/snat.api +++ b/plugins/snat-plugin/snat/snat.api @@ -1,3 +1,32 @@ +/* + * 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 snat.api + * @brief VPP control-plane API messages. + * + * This file defines VPP control-plane API messages which are generally + * called through a shared memory interface. + */ + +/** \brief Add S-NAT 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 + @first_ip_address - first IP address + @last_ip_address - last IP address +*/ define snat_add_address_range { u32 client_index; u32 context; @@ -6,11 +35,23 @@ define snat_add_address_range { u8 last_ip_address[16]; }; +/** \brief Add S-NAT address range reply + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param retval - return code +*/ define snat_add_address_range_reply { u32 context; i32 retval; }; +/** \brief Enable/disable S-NAT 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 +*/ define snat_interface_add_del_feature { u32 client_index; u32 context; @@ -19,7 +60,139 @@ define snat_interface_add_del_feature { u32 sw_if_index; }; +/** \brief Enable/disable S-NAT feature on the interface reply + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param retval - return code +*/ define snat_interface_add_del_feature_reply { u32 context; i32 retval; }; + +/** \brief Add/delete S-NAT 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 local_port - local port number + @param external_port - external port number + @param vfr_id - VRF ID +*/ +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]; + u16 local_port; + u16 external_port; + u32 vrf_id; +}; + +/** \brief Add/delete S-NAT static mapping reply + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param retval - return code +*/ +define snat_add_static_mapping_reply { + u32 context; + i32 retval; +}; + +/** \brief Dump S-NAT 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 S-NAT 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 local_port - local port number + @param external_port - external port number + @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]; + u16 local_port; + u16 external_port; + 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 S-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 S-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 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; + 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; +}; diff --git a/plugins/snat-plugin/snat/snat.c b/plugins/snat-plugin/snat/snat.c index f3e278bf..3fd9ecb4 100644 --- a/plugins/snat-plugin/snat/snat.c +++ b/plugins/snat-plugin/snat/snat.c @@ -76,6 +76,21 @@ do { \ vl_msg_api_send_shmem (q, (u8 *)&rmp); \ } while(0); +#define REPLY_MACRO2(t, body) \ +do { \ + unix_shared_memory_queue_t * q = \ + vl_api_client_index_to_input_queue (mp->client_index); \ + if (!q) \ + return; \ + \ + rmp = vl_msg_api_alloc (sizeof (*rmp)); \ + rmp->_vl_msg_id = ntohs((t)+sm->msg_id_base); \ + rmp->context = mp->context; \ + rmp->retval = ntohl(rv); \ + do {body;} while (0); \ + vl_msg_api_send_shmem (q, (u8 *)&rmp); \ +} while(0); + /* Hook up input features */ VNET_IP4_UNICAST_FEATURE_INIT (ip4_snat_in2out, static) = { @@ -88,6 +103,17 @@ VNET_IP4_UNICAST_FEATURE_INIT (ip4_snat_out2in, static) = { .runs_before = (char *[]){"ip4-lookup", 0}, .feature_index = &snat_main.rx_feature_out2in, }; +VNET_IP4_UNICAST_FEATURE_INIT (ip4_snat_in2out_fast, static) = { + .node_name = "snat-in2out-fast", + .runs_before = (char *[]){"snat-out2in-fast", 0}, + .feature_index = &snat_main.rx_feature_in2out_fast, +}; +VNET_IP4_UNICAST_FEATURE_INIT (ip4_snat_out2in_fast, static) = { + .node_name = "snat-out2in-fast", + .runs_before = (char *[]){"ip4-lookup", 0}, + .feature_index = &snat_main.rx_feature_out2in_fast, +}; + /* * This routine exists to convince the vlib plugin framework that @@ -189,6 +215,240 @@ static void increment_v4_address (ip4_address_t * a) a->as_u32 = clib_host_to_net_u32(v); } +/** + * @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 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, + int is_add) +{ + snat_main_t * sm = &snat_main; + snat_static_mapping_t *m; + snat_static_mapping_key_t m_key; + clib_bihash_kv_8_8_t kv, value; + snat_address_t *a = 0; + u32 fib_index = ~0; + uword * p; + int i; + + m_key.addr = e_addr; + m_key.port = addr_only ? 0 : e_port; + m_key.pad = 0; + 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 + { + if (sm->inside_fib_index == ~0) + { + p = hash_get (sm->ip4_main->fib_index_by_table_id, sm->inside_vrf_id); + if (!p) + return VNET_API_ERROR_NO_SUCH_FIB; + fib_index = p[0]; + sm->inside_fib_index = fib_index; + } + else + fib_index = sm->inside_fib_index; + + vrf_id = sm->inside_vrf_id; + } + + /* If outside FIB index is not resolved yet */ + if (sm->outside_fib_index == ~0) + { + p = hash_get (sm->ip4_main->fib_index_by_table_id, sm->outside_vrf_id); + if (!p) + return VNET_API_ERROR_NO_SUCH_FIB; + sm->outside_fib_index = p[0]; + } + + /* 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 */ + if (clib_bitmap_get (a->busy_port_bitmap, e_port)) + return VNET_API_ERROR_INVALID_VALUE; + a->busy_port_bitmap = clib_bitmap_set (a->busy_port_bitmap, + e_port, 1); + if (e_port > 1024) + a->busy_ports++; + + 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_key.addr = m->local_addr; + m_key.port = m->local_port; + m_key.pad = 0; + 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; + 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); + } + 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; + a->busy_port_bitmap = clib_bitmap_set (a->busy_port_bitmap, + e_port, 0); + a->busy_ports--; + + break; + } + } + } + + m_key.addr = m->local_addr; + m_key.port = m->local_port; + m_key.pad = 0; + 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; + 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; + u32 ses_index; + snat_session_t * s; + + 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)) + { + u = pool_elt_at_index (sm->users, value.value); + if (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); + ses_index = elt->value; + while (ses_index != ~0) + { + s = pool_elt_at_index (sm->sessions, ses_index); + + if (!addr_only) + { + if ((s->out2in.addr.as_u32 != e_addr.as_u32) && + (clib_net_to_host_u16 (s->out2in.port) != e_port)) + continue; + } + value.key = s->in2out.as_u64; + clib_bihash_add_del_8_8 (&sm->in2out, &value, 0); + value.key = s->out2in.as_u64; + clib_bihash_add_del_8_8 (&sm->out2in, &value, 0); + pool_put (sm->sessions, s); + + if (!addr_only) + break; + + elt_index = elt->next; + elt = pool_elt_at_index (sm->list_pool, elt_index); + ses_index = elt->value; + } + if (addr_only) + { + while ((elt_index = clib_dlist_remove_head(sm->list_pool, head_index)) != ~0) + pool_put_index (sm->list_pool, elt_index); + pool_put (sm->users, u); + clib_bihash_add_del_8_8 (&sm->user_hash, &kv, 0); + } + else + { + if (ses_index != ~0) + { + clib_dlist_remove (sm->list_pool, elt_index); + pool_put (sm->list_pool, elt); + u->nstaticsessions--; + } + } + } + } + } + + /* Delete static mapping from pool */ + pool_put (sm->static_mappings, m); + } + + return 0; +} + static void vl_api_snat_add_address_range_t_handler (vl_api_snat_add_address_range_t * mp) @@ -207,6 +467,12 @@ vl_api_snat_add_address_range_t_handler 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; @@ -263,8 +529,12 @@ vl_api_snat_interface_add_del_feature_t_handler VALIDATE_SW_IF_INDEX(mp); - feature_index = mp->is_inside ? sm->rx_feature_in2out - : sm->rx_feature_out2in; + if (sm->static_mapping_only && !(sm->static_mapping_connection_tracking)) + feature_index = mp->is_inside ? sm->rx_feature_in2out_fast + : sm->rx_feature_out2in_fast; + else + feature_index = mp->is_inside ? sm->rx_feature_in2out + : sm->rx_feature_out2in; ci = rx_cm->config_index_by_sw_if_index[sw_if_index]; ci = (is_del @@ -296,10 +566,176 @@ static void *vl_api_snat_interface_add_del_feature_t_print 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; + int rv = 0; + + 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); + + rv = snat_add_static_mapping(local_addr, external_addr, local_port, + external_port, vrf_id, mp->addr_only, + 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, "local_addr %U external_addr %U ", + 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)); + + 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->vrf_id = htonl (m->vrf_id); + 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; + + q = vl_api_client_index_to_input_queue (mp->client_index); + if (q == 0) + return; + + pool_foreach (m, sm->static_mappings, + ({ + send_snat_static_mapping_details (m, 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; + + REPLY_MACRO2(VL_API_SNAT_CONTROL_PING_REPLY, + ({ + rmp->vpe_pid = ntohl (getpid()); + })); +} + +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; + + REPLY_MACRO2(VL_API_SNAT_SHOW_CONFIG_REPLY, + ({ + rmp->translation_buckets = htons (sm->translation_buckets); + rmp->translation_memory_size = htons (sm->translation_memory_size); + rmp->user_buckets = htons (sm->user_buckets); + rmp->user_memory_size = htons (sm->user_memory_size); + rmp->max_translations_per_user = htons (sm->max_translations_per_user); + rmp->outside_vrf_id = htons (sm->outside_vrf_id); + rmp->inside_vrf_id = htons (sm->inside_vrf_id); + rmp->static_mapping_only = sm->static_mapping_only; + rmp->static_mapping_connection_tracking = + sm->static_mapping_connection_tracking; + })); +} + +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; +} + /* 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_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) /* Set up the API message handling tables */ static clib_error_t * @@ -376,6 +812,67 @@ void snat_free_outside_address_and_port (snat_main_t * sm, a->busy_ports--; } +/** + * @brief Match SNAT static mapping. + * + * @param sm SNAT 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. + * + * @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) +{ + clib_bihash_kv_8_8_t kv, value; + snat_static_mapping_t *m; + snat_static_mapping_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.pad = 0; + + kv.key = m_key.as_u64; + + if (clib_bihash_search_8_8 (mapping_hash, &kv, &value)) + { + /* Try address only mapping */ + m_key.port = 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; + } + + return 0; +} + int snat_alloc_outside_address_and_port (snat_main_t * sm, snat_session_key_t * k, u32 * address_indexp) @@ -419,17 +916,29 @@ 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; int i, count; - if (unformat (input, "%U - %U", + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + if (unformat (line_input, "%U - %U", unformat_ip4_address, &start_addr, unformat_ip4_address, &end_addr)) ; - else if (unformat (input, "%U", unformat_ip4_address, &start_addr)) + else if (unformat (line_input, "%U", unformat_ip4_address, &start_addr)) end_addr = start_addr; + else + return clib_error_return (0, "unknown input '%U'", format_unformat_error, + input); + unformat_free (line_input); + + if (sm->static_mapping_only) + return clib_error_return (0, "static mapping only mode"); start_host_order = clib_host_to_net_u32 (start_addr.as_u32); end_host_order = clib_host_to_net_u32 (end_addr.as_u32); @@ -467,6 +976,7 @@ 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(); snat_main_t * sm = &snat_main; ip4_main_t * im = &ip4_main; @@ -482,23 +992,32 @@ snat_feature_command_fn (vlib_main_t * vm, sw_if_index = ~0; - while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + /* 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 (input, "in %U", unformat_vnet_sw_interface, + 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 (input, "out %U", unformat_vnet_sw_interface, + 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 (input, "del")) + else if (unformat (line_input, "del")) is_del = 1; else - break; + return clib_error_return (0, "unknown input '%U'", + format_unformat_error, input); } + unformat_free (line_input); if (vec_len (inside_sw_if_indices)) { - feature_index = sm->rx_feature_in2out; + if (sm->static_mapping_only && !(sm->static_mapping_connection_tracking)) + feature_index = sm->rx_feature_in2out_fast; + else + feature_index = sm->rx_feature_in2out; for (i = 0; i < vec_len(inside_sw_if_indices); i++) { @@ -518,7 +1037,10 @@ snat_feature_command_fn (vlib_main_t * vm, if (vec_len (outside_sw_if_indices)) { - feature_index = sm->rx_feature_out2in; + if (sm->static_mapping_only && !(sm->static_mapping_connection_tracking)) + feature_index = sm->rx_feature_out2in_fast; + else + feature_index = sm->rx_feature_out2in; for (i = 0; i < vec_len(outside_sw_if_indices); i++) { @@ -549,6 +1071,92 @@ VLIB_CLI_COMMAND (set_interface_snat_command, static) = { }; 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; + int rv; + + /* 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, "vrf %u", &vrf_id)) + ; + else if (unformat (line_input, "del")) + is_add = 0; + else + return clib_error_return (0, "unknown input: '%U'", + format_unformat_error, line_input); + } + unformat_free (line_input); + + rv = snat_add_static_mapping(l_addr, e_addr, (u16) l_port, (u16) e_port, + vrf_id, addr_only, is_add); + + switch (rv) + { + case VNET_API_ERROR_INVALID_VALUE: + return clib_error_return (0, "External port already in use."); + break; + case VNET_API_ERROR_NO_SUCH_ENTRY: + if (is_add) + return clib_error_return (0, "External addres must be allocated."); + else + return clib_error_return (0, "Mapping not exist."); + break; + case VNET_API_ERROR_NO_SUCH_FIB: + return clib_error_return (0, "No such VRF id."); + case VNET_API_ERROR_VALUE_EXIST: + return clib_error_return (0, "Mapping already exist."); + default: + break; + } + + 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 use: + * vpp# snat add static mapping local 10.0.0.3 6303 external 4.4.4.4 3606 + * If not runnig "static mapping only" S-NAT plugin mode use before: + * vpp# snat add address 4.4.4.4 + * To create static mapping between local and external address use: + * vpp# snat add static mapping local 10.0.0.3 external 4.4.4.4 + * @cliexend +?*/ +VLIB_CLI_COMMAND (add_static_mapping_command, static) = { + .path = "snat add static mapping", + .function = add_static_mapping_command_fn, + .short_help = + "snat add static mapping local <addr> [<port>] external <addr> [<port>] [vrf <table-id>] [del]", +}; + +static clib_error_t * snat_config (vlib_main_t * vm, unformat_input_t * input) { snat_main_t * sm = &snat_main; @@ -558,6 +1166,11 @@ snat_config (vlib_main_t * vm, unformat_input_t * input) 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; while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) { @@ -576,8 +1189,17 @@ snat_config (vlib_main_t * vm, unformat_input_t * input) 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 - return clib_error_return (0, "unknown input `%U'", + return clib_error_return (0, "unknown input '%U'", format_unformat_error, input); } @@ -588,15 +1210,31 @@ snat_config (vlib_main_t * vm, unformat_input_t * input) 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 = ~0; + sm->inside_vrf_id = inside_vrf_id; + sm->inside_fib_index = ~0; + sm->static_mapping_only = static_mapping_only; + sm->static_mapping_connection_tracking = static_mapping_connection_tracking; + + if (!static_mapping_only || + (static_mapping_only && static_mapping_connection_tracking)) + { + clib_bihash_init_8_8 (&sm->in2out, "in2out", translation_buckets, + translation_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->out2in, "out2in", translation_buckets, + translation_memory_size); - clib_bihash_init_8_8 (&sm->user_hash, "users", user_buckets, - user_memory_size); + clib_bihash_init_8_8 (&sm->user_hash, "users", user_buckets, + user_memory_size); + } + 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; } @@ -617,7 +1255,7 @@ u8 * format_snat_key (u8 * s, va_list * args) s = format (s, "%U proto %s port %d fib %d", format_ip4_address, &key->addr, protocol_string, - key->port, key->fib_index); + clib_net_to_host_u16 (key->port), key->fib_index); return s; } @@ -631,6 +1269,10 @@ u8 * format_snat_session (u8 * s, va_list * args) 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; } @@ -645,33 +1287,54 @@ u8 * format_snat_user (u8 * s, va_list * args) u32 session_index; snat_session_t * sess; - s = format (s, "%U: %d translations\n", - format_ip4_address, &u->addr, u->nsessions); + 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; - 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) + if (u->nsessions || u->nstaticsessions) { - sess = pool_elt_at_index (sm->sessions, session_index); - - s = format (s, " %U\n", format_snat_session, sm, sess); + head_index = u->sessions_per_user_list_head_index; + head = pool_elt_at_index (sm->list_pool, head_index); - elt_index = elt->next; + 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, "local %U:%d external %U:%d vrf %d", + format_ip4_address, &m->local_addr, m->local_port, + format_ip4_address, &m->external_addr, m->external_port, + m->vrf_id); + + return s; +} + static clib_error_t * show_snat_command_fn (vlib_main_t * vm, unformat_input_t * input, @@ -680,30 +1343,71 @@ show_snat_command_fn (vlib_main_t * vm, int verbose = 0; snat_main_t * sm = &snat_main; snat_user_t * u; + snat_static_mapping_t *m; if (unformat (input, "detail")) verbose = 1; else if (unformat (input, "verbose")) verbose = 2; - vlib_cli_output (vm, "%d users, %d outside addresses, %d active sessions", - pool_elts (sm->users), - vec_len (sm->addresses), - pool_elts (sm->sessions)); - - if (verbose > 0) + if (sm->static_mapping_only) + { + if (sm->static_mapping_connection_tracking) + vlib_cli_output (vm, "SNAT mode: static mapping only connection " + "tracking"); + else + vlib_cli_output (vm, "SNAT mode: static mapping only"); + } + else + { + vlib_cli_output (vm, "SNAT mode: dynamic translations enabled"); + } + + 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 { - 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, "%d list pool elements", - pool_elts (sm->list_pool)); - - pool_foreach (u, sm->users, - ({ - vlib_cli_output (vm, "%U", format_snat_user, sm, u, verbose - 1); - })); + vlib_cli_output (vm, "%d users, %d outside addresses, %d active sessions," + " %d static mappings", + pool_elts (sm->users), + vec_len (sm->addresses), + pool_elts (sm->sessions), + 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, "%d list pool elements", + pool_elts (sm->list_pool)); + + pool_foreach (u, sm->users, + ({ + vlib_cli_output (vm, "%U", format_snat_user, sm, 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); + })); + } + } } return 0; diff --git a/plugins/snat-plugin/snat/snat.h b/plugins/snat-plugin/snat/snat.h index e3aa5627..823b8608 100644 --- a/plugins/snat-plugin/snat/snat.h +++ b/plugins/snat-plugin/snat/snat.h @@ -55,6 +55,19 @@ typedef struct { }; } snat_user_key_t; +typedef struct { + union + { + struct + { + ip4_address_t addr; + u16 port; + u16 pad; + }; + u64 as_u64; + }; +} snat_static_mapping_key_t; + typedef enum { SNAT_PROTOCOL_UDP = 0, @@ -63,6 +76,8 @@ typedef enum { } snat_protocol_t; +#define SNAT_SESSION_FLAG_STATIC_MAPPING 1 + typedef CLIB_PACKED(struct { snat_session_key_t out2in; /* 0-15 */ @@ -87,12 +102,12 @@ typedef CLIB_PACKED(struct { }) snat_session_t; -#define SNAT_SESSION_STATIC (1<<0) typedef struct { ip4_address_t addr; u32 sessions_per_user_list_head_index; u32 nsessions; + u32 nstaticsessions; } snat_user_t; typedef struct { @@ -102,6 +117,16 @@ typedef struct { } snat_address_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_static_mapping_t; + +typedef struct { /* Main lookup tables */ clib_bihash_8_8_t out2in; clib_bihash_8_8_t in2out; @@ -109,12 +134,21 @@ typedef struct { /* Find-a-user => src address lookup */ clib_bihash_8_8_t user_hash; + /* 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; + /* User pool */ snat_user_t * users; /* Session pool */ snat_session_t * sessions; + /* Static mapping pool */ + snat_static_mapping_t * static_mappings; + /* Vector of outside addresses */ snat_address_t * addresses; @@ -127,8 +161,12 @@ typedef struct { /* ip4 feature path indices */ u32 rx_feature_in2out; u32 rx_feature_out2in; + u32 rx_feature_in2out_fast; + u32 rx_feature_out2in_fast; /* Config parameters */ + u8 static_mapping_only; + u8 static_mapping_connection_tracking; u32 translation_buckets; u32 translation_memory_size; u32 user_buckets; @@ -136,6 +174,8 @@ typedef struct { u32 max_translations_per_user; u32 outside_vrf_id; u32 outside_fib_index; + u32 inside_vrf_id; + u32 inside_fib_index; /* API message ID base */ u16 msg_id_base; @@ -152,6 +192,8 @@ typedef struct { extern snat_main_t snat_main; extern vlib_node_registration_t snat_in2out_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; void snat_free_outside_address_and_port (snat_main_t * sm, snat_session_key_t * k, @@ -160,6 +202,12 @@ void snat_free_outside_address_and_port (snat_main_t * sm, int snat_alloc_outside_address_and_port (snat_main_t * sm, 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); + format_function_t format_snat_user; typedef struct { @@ -167,6 +215,12 @@ typedef struct { 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 + /* * 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 diff --git a/plugins/snat-plugin/snat/snat_test.c b/plugins/snat-plugin/snat/snat_test.c index cd2656cb..453c63f2 100644 --- a/plugins/snat-plugin/snat/snat_test.c +++ b/plugins/snat-plugin/snat/snat_test.c @@ -59,7 +59,8 @@ 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_interface_add_del_feature_reply) \ +_(snat_add_static_mapping_reply) #define _(n) \ static void vl_api_##n##_t_handler \ @@ -83,8 +84,12 @@ foreach_standard_reply_retval_handler; */ #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_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) /* M: construct, but don't yet send a message */ #define M(T,t) \ @@ -137,6 +142,11 @@ static int api_snat_add_address_range (vat_main_t * vam) ; else if (unformat (i, "%U", unformat_ip4_address, &start_addr)) end_addr = start_addr; + 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); @@ -192,6 +202,11 @@ static int api_snat_interface_add_del_feature (vat_main_t * vam) 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) @@ -210,14 +225,188 @@ static int api_snat_interface_add_del_feature (vat_main_t * vam) return 0; } +static int api_snat_add_static_mapping(vat_main_t * vam) +{ + snat_test_main_t * sm = &snat_test_main; + unformat_input_t * i = vam->input; + f64 timeout; + vl_api_snat_add_static_mapping_t * mp; + u8 addr_set_n = 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; + + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "local_addr %U", unformat_ip4_address, &local_addr)) + addr_set_n++; + else if (unformat (i, "external_addr %U", unformat_ip4_address, + &external_addr)) + addr_set_n++; + 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, "vrf %u", &vrf_id)) + ; + else if (unformat (i, "del")) + is_add = 0; + else + { + clib_warning("unknown input '%U'", format_unformat_error, i); + return -99; + } + } + + if (addr_set_n != 2) + { + errmsg ("local_addr and remote_addr required\n"); + return -99; + } + + M(SNAT_ADD_STATIC_MAPPING, snat_add_static_mapping); + 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->vrf_id = ntohl (vrf_id); + memcpy (mp->local_ip_address, &local_addr, 4); + memcpy (mp->external_ip_address, &external_addr, 4); + + S; W; + /* NOTREACHED */ + return 0; +} + +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) + fformat (vam->ofp, "%15U%6s%15U%6s%11d\n", + format_ip4_address, &mp->local_ip_address, "", + format_ip4_address, &mp->external_ip_address, "", + ntohl (mp->vrf_id)); + else + fformat (vam->ofp, "%15U%6d%15U%6d%11d\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)); + +} + +static int api_snat_static_mapping_dump(vat_main_t * vam) +{ + snat_test_main_t * sm = &snat_test_main; + f64 timeout; + vl_api_snat_static_mapping_dump_t * mp; + + 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\n", "address", "port", "address", + "port", "vrf"); + + M(SNAT_STATIC_MAPPING_DUMP, snat_static_mapping_dump); + S; + /* Use a control ping for synchronization */ + { + vl_api_snat_control_ping_t *mp; + M (SNAT_CONTROL_PING, snat_control_ping); + S; + } + W; + /* NOTREACHED */ + return 0; +} + +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) +{ + snat_test_main_t * sm = &snat_test_main; + f64 timeout; + vl_api_snat_show_config_t * mp; + + if (vam->json_output) + { + clib_warning ("JSON output not supported for snat_show_config"); + return -99; + } + + M(SNAT_SHOW_CONFIG, snat_show_config); + S; W; + /* NOTREACHED */ + return 0; +} + /* * 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]") \ -_(snat_interface_add_del_feature, \ - "<intfc> | sw_if_index <id> [in] [out] [del]") +#define foreach_vpe_api_msg \ +_(snat_add_address_range, "<start-addr> [- <end-addr]") \ +_(snat_interface_add_del_feature, \ + "<intfc> | sw_if_index <id> [in] [out] [del]") \ +_(snat_add_static_mapping, "local_addr <ip> external_addr <ip> " \ + "[local_port <n>] [external_port <n>] [vrf <table-id>] [del]") \ +_(snat_static_mapping_dump, "") \ +_(snat_show_config, "") void vat_api_hookup (vat_main_t *vam) { |