From e6eaa24f156e908dcbb92209c4f50f8da8066d79 Mon Sep 17 00:00:00 2001 From: Filip Varga Date: Wed, 27 Nov 2019 17:40:29 +0100 Subject: [PATCH] nat: session cleanup fix Ticket: VPP-1795 Type: fix Change-Id: Ib3b5742119d7013c293a11eb3dd1aadf46b422dd Signed-off-by: Filip Varga --- src/plugins/nat/in2out.c | 4 ++ src/plugins/nat/in2out_ed.c | 3 + src/plugins/nat/nat.c | 68 ++++++++++++------ src/plugins/nat/nat.h | 4 +- src/plugins/nat/nat44_inlines.h | 109 ++++++++++++++++++++++++++++ src/plugins/nat/out2in.c | 4 ++ src/plugins/nat/out2in_ed.c | 3 + src/plugins/nat/test/test_nat.py | 150 +++++++++++++++++++++++++-------------- 8 files changed, 266 insertions(+), 79 deletions(-) create mode 100644 src/plugins/nat/nat44_inlines.h diff --git a/src/plugins/nat/in2out.c b/src/plugins/nat/in2out.c index f3878dbbe30..6cb111c9b3e 100755 --- a/src/plugins/nat/in2out.c +++ b/src/plugins/nat/in2out.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -268,6 +269,9 @@ slow_path (snat_main_t * sm, vlib_buffer_t * b0, }; nat44_is_idle_session_ctx_t ctx0; + nat44_session_try_cleanup (&ip0->src_address, rx_fib_index0, thread_index, + now); + if (PREDICT_FALSE (maximum_sessions_exceeded (sm, thread_index))) { b0->error = node->errors[SNAT_IN2OUT_ERROR_MAX_SESSIONS_EXCEEDED]; diff --git a/src/plugins/nat/in2out_ed.c b/src/plugins/nat/in2out_ed.c index 2d2be5c3812..f8cd89fb8de 100644 --- a/src/plugins/nat/in2out_ed.c +++ b/src/plugins/nat/in2out_ed.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -210,6 +211,8 @@ slow_path_ed (snat_main_t * sm, }; nat44_is_idle_session_ctx_t ctx; + nat44_session_try_cleanup (&key->l_addr, rx_fib_index, thread_index, now); + if (PREDICT_FALSE (maximum_sessions_exceeded (sm, thread_index))) { b->error = node->errors[NAT_IN2OUT_ED_ERROR_MAX_SESSIONS_EXCEEDED]; diff --git a/src/plugins/nat/nat.c b/src/plugins/nat/nat.c index ee70b0f00ab..d85fb267bed 100755 --- a/src/plugins/nat/nat.c +++ b/src/plugins/nat/nat.c @@ -462,6 +462,7 @@ nat_ed_session_alloc (snat_main_t * sm, snat_user_t * u, u32 thread_index, u->sessions_per_user_list_head_index); oldest_elt = pool_elt_at_index (tsm->list_pool, oldest_index); s = pool_elt_at_index (tsm->sessions, oldest_elt->value); + sess_timeout_time = s->last_heard + (f64) nat44_session_get_timeout (sm, s); if (now >= sess_timeout_time) { @@ -2318,10 +2319,8 @@ snat_init (vlib_main_t * vm) sm->fq_in2out_index = ~0; sm->fq_in2out_output_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; + + sm->alloc_addr_and_port = nat_alloc_addr_and_port_default; sm->addr_and_port_alloc_alg = NAT_ADDR_AND_PORT_ALLOC_ALG_DEFAULT; sm->forwarding_enabled = 0; @@ -2343,8 +2342,6 @@ snat_init (vlib_main_t * vm) node = vlib_get_node_by_name (vm, (u8 *) "nat-pre-out2in"); sm->pre_out2in_node_index = node->index; - // TODO: output ?? (special node) - node = vlib_get_node_by_name (vm, (u8 *) "nat44-in2out"); sm->in2out_node_index = node->index; node = vlib_get_node_by_name (vm, (u8 *) "nat44-in2out-output"); @@ -3749,24 +3746,36 @@ snat_config (vlib_main_t * vm, unformat_input_t * input) { snat_main_t *sm = &snat_main; nat66_main_t *nm = &nat66_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 outside_ip6_vrf_id = 0; - u32 inside_vrf_id = 0; + dslite_main_t *dm = &dslite_main; + snat_main_per_thread_data_t *tsm; + u32 static_mapping_buckets = 1024; u32 static_mapping_memory_size = 64 << 20; + u32 nat64_bib_buckets = 1024; u32 nat64_bib_memory_size = 128 << 20; + u32 nat64_st_buckets = 2048; u32 nat64_st_memory_size = 256 << 20; + + u32 user_buckets = 128; + u32 user_memory_size = 64 << 20; + u32 translation_buckets = 1024; + u32 translation_memory_size = 128 << 20; + + u32 max_translations_per_user = ~0; + + u32 outside_vrf_id = 0; + u32 outside_ip6_vrf_id = 0; + u32 inside_vrf_id = 0; u8 static_mapping_only = 0; u8 static_mapping_connection_tracking = 0; - snat_main_per_thread_data_t *tsm; - dslite_main_t *dm = &dslite_main; + + u32 udp_timeout = SNAT_UDP_TIMEOUT; + u32 icmp_timeout = SNAT_ICMP_TIMEOUT; + + u32 tcp_transitory_timeout = SNAT_TCP_TRANSITORY_TIMEOUT; + u32 tcp_established_timeout = SNAT_TCP_ESTABLISHED_TIMEOUT; sm->deterministic = 0; sm->out2in_dpo = 0; @@ -3777,6 +3786,14 @@ snat_config (vlib_main_t * vm, unformat_input_t * input) if (unformat (input, "translation hash buckets %d", &translation_buckets)) ; + else if (unformat (input, "udp timeout %d", &udp_timeout)) + ; + else if (unformat (input, "icmp timeout %d", &icmp_timeout)) + ; + else if (unformat (input, "tcp transitory timeout %d", + &tcp_transitory_timeout)); + else if (unformat (input, "tcp established timeout %d", + &tcp_established_timeout)); else if (unformat (input, "translation hash memory %d", &translation_memory_size)); else if (unformat (input, "user hash buckets %d", &user_buckets)) @@ -3835,14 +3852,23 @@ snat_config (vlib_main_t * vm, unformat_input_t * input) return clib_error_return (0, "out2in dpo mode available only for simple nat"); - /* for show commands, etc. */ + /* optionally configurable timeouts for testing purposes */ + sm->udp_timeout = udp_timeout; + sm->icmp_timeout = icmp_timeout; + sm->tcp_transitory_timeout = tcp_transitory_timeout; + sm->tcp_established_timeout = tcp_established_timeout; + + sm->user_buckets = user_buckets; + sm->user_memory_size = user_memory_size; + sm->translation_buckets = translation_buckets; sm->translation_memory_size = translation_memory_size; + /* do not exceed load factor 10 */ sm->max_translations = 10 * translation_buckets; - sm->user_buckets = user_buckets; - sm->user_memory_size = user_memory_size; - sm->max_translations_per_user = max_translations_per_user; + sm->max_translations_per_user = max_translations_per_user == ~0 ? + sm->max_translations : 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, @@ -3878,7 +3904,6 @@ snat_config (vlib_main_t * vm, unformat_input_t * input) sm->handoff_out2in_index = nat_pre_out2in_node.index; sm->handoff_in2out_index = nat_pre_in2out_node.index; - // TODO: test sm->handoff_in2out_output_index = nat44_ed_in2out_output_node.index; sm->in2out_node_index = nat44_ed_in2out_node.index; @@ -3898,7 +3923,6 @@ snat_config (vlib_main_t * vm, unformat_input_t * input) sm->handoff_out2in_index = snat_in2out_node.index; sm->handoff_in2out_index = snat_out2in_node.index; - // TODO: test sm->handoff_in2out_output_index = snat_in2out_output_node.index; sm->in2out_node_index = snat_in2out_node.index; diff --git a/src/plugins/nat/nat.h b/src/plugins/nat/nat.h index ee712f4748f..38f5a99bfbb 100644 --- a/src/plugins/nat/nat.h +++ b/src/plugins/nat/nat.h @@ -675,9 +675,9 @@ typedef struct snat_main_s /* values of various timeouts */ u32 udp_timeout; - u32 tcp_established_timeout; - u32 tcp_transitory_timeout; u32 icmp_timeout; + u32 tcp_transitory_timeout; + u32 tcp_established_timeout; /* TCP MSS clamping */ u16 mss_clamping; diff --git a/src/plugins/nat/nat44_inlines.h b/src/plugins/nat/nat44_inlines.h new file mode 100644 index 00000000000..eb1077cdf34 --- /dev/null +++ b/src/plugins/nat/nat44_inlines.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2019 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. + */ +/** + * @brief The NAT44 inline functions + */ +#ifndef __included_nat44_inlines_h__ +#define __included_nat44_inlines_h__ + +#include +#include + +static_always_inline void +nat44_session_cleanup (snat_session_t * s, u32 thread_index) +{ + snat_main_t *sm = &snat_main; + + nat_free_session_data (sm, s, thread_index, 0); + nat44_delete_session (sm, s, thread_index); +} + +static_always_inline void +nat44_user_try_cleanup (snat_user_t * u, u32 thread_index, f64 now) +{ + dlist_elt_t *elt; + snat_session_t *s; + u64 sess_timeout_time; + + snat_main_t *sm = &snat_main; + snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index]; + + // get head + elt = pool_elt_at_index (tsm->list_pool, + u->sessions_per_user_list_head_index); + // get first element + elt = pool_elt_at_index (tsm->list_pool, elt->next); + + while (elt->value != ~0) + { + s = pool_elt_at_index (tsm->sessions, elt->value); + elt = pool_elt_at_index (tsm->list_pool, elt->next); + + sess_timeout_time = s->last_heard + + (f64) nat44_session_get_timeout (sm, s); + + if (now < sess_timeout_time) + continue; + + nat44_session_cleanup (s, thread_index); + } +} + +static_always_inline void +nat44_session_try_cleanup (ip4_address_t * addr, + u32 fib_index, u32 thread_index, f64 now) +{ + snat_user_t *u = 0; + snat_user_key_t user_key; + clib_bihash_kv_8_8_t kv, value; + + snat_main_t *sm = &snat_main; + snat_main_per_thread_data_t *tsm = &sm->per_thread_data[thread_index]; + + user_key.addr.as_u32 = addr->as_u32; + user_key.fib_index = fib_index; + kv.key = user_key.as_u64; + + // lookup user for this traffic + if (PREDICT_FALSE (clib_bihash_search_8_8 (&tsm->user_hash, &kv, &value))) + { + // there is still place and a new user can be created + if (PREDICT_TRUE (pool_elts (tsm->sessions) < sm->max_translations)) + return; + + // there is no place so we try to cleanup all users in this thread + /* *INDENT-OFF* */ + pool_foreach (u, tsm->users, + ({ + nat44_user_try_cleanup (u, thread_index, now); + })); + /* *INDENT-ON* */ + return; + } + + // each time user creates a new session we try to cleanup expired sessions + nat44_user_try_cleanup (pool_elt_at_index (tsm->users, value.value), + thread_index, now); +} + +#endif /* __included_nat44_inlines_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/nat/out2in.c b/src/plugins/nat/out2in.c index 7c6fae6ec91..6ee126658c8 100755 --- a/src/plugins/nat/out2in.c +++ b/src/plugins/nat/out2in.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -189,6 +190,9 @@ create_session_for_static_mapping (snat_main_t * sm, udp_header_t *udp0; nat44_is_idle_session_ctx_t ctx0; + nat44_session_try_cleanup (&in2out.addr, in2out.fib_index, thread_index, + now); + if (PREDICT_FALSE (maximum_sessions_exceeded (sm, thread_index))) { b0->error = node->errors[SNAT_OUT2IN_ERROR_MAX_SESSIONS_EXCEEDED]; diff --git a/src/plugins/nat/out2in_ed.c b/src/plugins/nat/out2in_ed.c index a81d58e2fa8..cb1cbdad375 100644 --- a/src/plugins/nat/out2in_ed.c +++ b/src/plugins/nat/out2in_ed.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -203,6 +204,8 @@ create_session_for_static_mapping_ed (snat_main_t * sm, snat_session_key_t eh_key; nat44_is_idle_session_ctx_t ctx; + nat44_session_try_cleanup (&l_key.addr, l_key.fib_index, thread_index, now); + if (PREDICT_FALSE (maximum_sessions_exceeded (sm, thread_index))) { b->error = node->errors[NAT_OUT2IN_ED_ERROR_MAX_SESSIONS_EXCEEDED]; diff --git a/src/plugins/nat/test/test_nat.py b/src/plugins/nat/test/test_nat.py index 29fd5ca23c5..0daa61042c1 100644 --- a/src/plugins/nat/test/test_nat.py +++ b/src/plugins/nat/test/test_nat.py @@ -2606,61 +2606,6 @@ class TestNAT44(MethodHolder): self.logger.error(ppp("Unexpected or invalid packet:", packet)) raise - def test_max_translations_per_user(self): - """ MAX translations per user - recycle the least recently used """ - - self.nat44_add_address(self.nat_addr) - flags = self.config_flags.NAT_IS_INSIDE - self.vapi.nat44_interface_add_del_feature( - sw_if_index=self.pg0.sw_if_index, - flags=flags, is_add=1) - self.vapi.nat44_interface_add_del_feature( - sw_if_index=self.pg1.sw_if_index, - is_add=1) - - # get maximum number of translations per user - nat44_config = self.vapi.nat_show_config() - - # send more than maximum number of translations per user packets - pkts_num = nat44_config.max_translations_per_user + 5 - pkts = [] - for port in range(0, pkts_num): - p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / - IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) / - TCP(sport=1025 + port)) - pkts.append(p) - self.pg0.add_stream(pkts) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - - # verify number of translated packet - self.pg1.get_capture(pkts_num) - - users = self.vapi.nat44_user_dump() - for user in users: - if user.ip_address == self.pg0.remote_ip4: - self.assertEqual(user.nsessions, - nat44_config.max_translations_per_user) - self.assertEqual(user.nstaticsessions, 0) - - tcp_port = 22 - self.nat44_add_static_mapping(self.pg0.remote_ip4, self.nat_addr, - tcp_port, tcp_port, - proto=IP_PROTOS.tcp) - p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / - IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) / - TCP(sport=tcp_port)) - self.pg0.add_stream(p) - self.pg_enable_capture(self.pg_interfaces) - self.pg_start() - self.pg1.get_capture(1) - users = self.vapi.nat44_user_dump() - for user in users: - if user.ip_address == self.pg0.remote_ip4: - self.assertEqual(user.nsessions, - nat44_config.max_translations_per_user - 1) - self.assertEqual(user.nstaticsessions, 1) - def test_interface_addr(self): """ Acquire NAT44 addresses from interface """ self.vapi.nat44_add_del_interface_addr( @@ -4386,6 +4331,101 @@ class TestNAT44(MethodHolder): self.logger.info(self.vapi.cli("show nat ha")) +class TestNAT44EndpointDependent2(MethodHolder): + """ Endpoint-Dependent session test cases """ + + icmp_timeout = 2 + + @classmethod + def setUpConstants(cls): + super(TestNAT44EndpointDependent2, cls).setUpConstants() + cls.vpp_cmdline.extend(["nat", "{", "endpoint-dependent", + "translation", "hash", "buckets", "1", + "icmp", "timeout", str(cls.icmp_timeout), "}"]) + + @classmethod + def setUpClass(cls): + super(TestNAT44EndpointDependent2, cls).setUpClass() + try: + translation_buckets = 1 + cls.max_translations = 10 * translation_buckets + + cls.create_pg_interfaces(range(2)) + cls.interfaces = list(cls.pg_interfaces[0:2]) + + for i in cls.interfaces: + i.admin_up() + i.config_ip4() + i.resolve_arp() + + cls.pg0.generate_remote_hosts(1) + cls.pg0.configure_ipv4_neighbors() + + cls.pg1.generate_remote_hosts(1) + cls.pg1.configure_ipv4_neighbors() + + except Exception: + super(TestNAT44EndpointDependent2, cls).tearDownClass() + raise + + def create_icmp_stream(self, in_if, out_if, count): + """ + Create ICMP packet stream for inside network + + :param in_if: Inside interface + :param out_if: Outside interface + :param count: Number of packets + """ + + self.assertTrue(count > 0) + icmp_id = random.randint(0, 65535 - (count - 1)) + + pkts = list() + for i in range(count): + p = (Ether(dst=in_if.local_mac, src=in_if.remote_mac) / + IP(src=in_if.remote_ip4, dst=out_if.remote_ip4, ttl=64) / + ICMP(id=icmp_id + i, type='echo-request')) + pkts.append(p) + return pkts + + def send_pkts(self, pkts, expected=None): + self.pg0.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + return self.pg1.get_capture( + len(pkts) if expected is None else expected) + + def test_session_cleanup(self): + """ NAT44 session cleanup test """ + + self.nat44_add_address(self.pg1.local_ip4) + flags = self.config_flags.NAT_IS_INSIDE + self.vapi.nat44_interface_add_del_feature( + sw_if_index=self.pg0.sw_if_index, + flags=flags, is_add=1) + self.vapi.nat44_interface_add_del_feature( + sw_if_index=self.pg1.sw_if_index, + is_add=1) + + nat_config = self.vapi.nat_show_config() + self.assertEqual(1, nat_config.endpoint_dependent) + + pkts = self.create_icmp_stream(self.pg0, self.pg1, + self.max_translations + 2) + sz = len(pkts) + + # positive test + self.send_pkts(pkts[0:self.max_translations]) + + # false positive test + self.send_pkts(pkts[self.max_translations:sz - 1], 0) + + sleep(self.icmp_timeout) + + # positive test + self.send_pkts(pkts[self.max_translations + 1:sz]) + + class TestNAT44EndpointDependent(MethodHolder): """ Endpoint-Dependent mapping and filtering test cases """ -- 2.16.6