From 6bb080f1e54391b161cf211a9cfa3f488f2fd331 Mon Sep 17 00:00:00 2001 From: Filip Varga Date: Thu, 16 Apr 2020 13:20:25 +0200 Subject: [PATCH] nat: per vrf session limits Type: improvement Change-Id: I170256ab47978db34fb0ff6808d9cd54ab872410 Signed-off-by: Filip Varga --- src/plugins/nat/in2out_ed.c | 8 +- src/plugins/nat/nat.api | 13 +++ src/plugins/nat/nat.c | 23 +++++- src/plugins/nat/nat.h | 13 +++ src/plugins/nat/nat44/inlines.h | 11 +++ src/plugins/nat/nat44_cli.c | 55 +++++++++++++ src/plugins/nat/nat_api.c | 28 +++++++ src/plugins/nat/out2in_ed.c | 16 +++- src/plugins/nat/test/test_nat.py | 172 ++++++++++++++++++++++++++++++++++----- 9 files changed, 310 insertions(+), 29 deletions(-) diff --git a/src/plugins/nat/in2out_ed.c b/src/plugins/nat/in2out_ed.c index d78e552d3ba..f0bbe0b656e 100644 --- a/src/plugins/nat/in2out_ed.c +++ b/src/plugins/nat/in2out_ed.c @@ -318,7 +318,9 @@ slow_path_ed (snat_main_t * sm, } } - if (PREDICT_FALSE (nat44_maximum_sessions_exceeded (sm, thread_index))) + // TODO: based on fib index do a lookup + if (PREDICT_FALSE + (nat44_ed_maximum_sessions_exceeded (sm, rx_fib_index, thread_index))) { if (!nat_global_lru_free_one (sm, thread_index, now)) { @@ -812,7 +814,9 @@ nat44_ed_in2out_unknown_proto (snat_main_t * sm, } else { - if (PREDICT_FALSE (nat44_maximum_sessions_exceeded (sm, thread_index))) + if (PREDICT_FALSE + (nat44_ed_maximum_sessions_exceeded + (sm, rx_fib_index, thread_index))) { b->error = node->errors[NAT_IN2OUT_ED_ERROR_MAX_SESSIONS_EXCEEDED]; nat_ipfix_logging_max_sessions (thread_index, sm->max_translations); diff --git a/src/plugins/nat/nat.api b/src/plugins/nat/nat.api index 10e7036608c..134f3e06d7a 100644 --- a/src/plugins/nat/nat.api +++ b/src/plugins/nat/nat.api @@ -139,6 +139,19 @@ autoreply define nat44_session_cleanup { u32 context; }; +/** \brief NAT44 set session limit + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param session_limit - session limit + @param vrf_id - vrf id +*/ +autoreply define nat44_set_session_limit { + u32 client_index; + u32 context; + u32 session_limit; + u32 vrf_id; +}; + /** \brief Set NAT logging level @param client_index - opaque cookie to identify the sender @param context - sender context, to match reply w/ request diff --git a/src/plugins/nat/nat.c b/src/plugins/nat/nat.c index ba682f9325b..7cb0b53368c 100755 --- a/src/plugins/nat/nat.c +++ b/src/plugins/nat/nat.c @@ -329,6 +329,26 @@ nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index, &s->out2in); } +int +nat44_set_session_limit (u32 session_limit, u32 vrf_id) +{ + snat_main_t *sm = &snat_main; + u32 fib_index = fib_table_find (FIB_PROTOCOL_IP4, vrf_id); + u32 len = vec_len (sm->max_translations_per_fib); + + if (len <= fib_index) + { + vec_validate (sm->max_translations_per_fib, fib_index + 1); + + for (; len < vec_len (sm->max_translations_per_fib); len++) + sm->max_translations_per_fib[len] = sm->max_translations; + } + + sm->max_translations_per_fib[fib_index] = session_limit; + return 0; +} + + void nat44_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index, u8 is_ha) @@ -4025,9 +4045,10 @@ snat_config (vlib_main_t * vm, unformat_input_t * input) sm->translation_buckets = translation_buckets; sm->translation_memory_size = translation_memory_size; - /* do not exceed load factor 10 */ sm->max_translations = 10 * translation_buckets; + vec_add1 (sm->max_translations_per_fib, sm->max_translations); + sm->max_translations_per_user = max_translations_per_user == ~0 ? sm->max_translations : max_translations_per_user; diff --git a/src/plugins/nat/nat.h b/src/plugins/nat/nat.h index 59a12437f77..9331901a3bc 100644 --- a/src/plugins/nat/nat.h +++ b/src/plugins/nat/nat.h @@ -634,12 +634,16 @@ typedef struct snat_main_s u8 deterministic; u8 out2in_dpo; u8 endpoint_dependent; + u32 translation_buckets; uword translation_memory_size; u32 max_translations; + u32 *max_translations_per_fib; + u32 user_buckets; uword user_memory_size; u32 max_translations_per_user; + u32 outside_vrf_id; u32 outside_fib_index; u32 inside_vrf_id; @@ -1253,6 +1257,15 @@ int nat44_del_ed_session (snat_main_t * sm, ip4_address_t * addr, u16 port, void nat_free_session_data (snat_main_t * sm, snat_session_t * s, u32 thread_index, u8 is_ha); +/** + * @brief Set NAT44 session limit (session limit, vrf id) + * + * @param session_limit Session limit + * @param vrf_id VRF id + * @return 0 on success, non-zero value otherwise + */ +int nat44_set_session_limit (u32 session_limit, u32 vrf_id); + /** * @brief Free NAT44 ED session data (lookup keys, external addrres port) * diff --git a/src/plugins/nat/nat44/inlines.h b/src/plugins/nat/nat44/inlines.h index 3c88e15c05e..5cd4164acc8 100644 --- a/src/plugins/nat/nat44/inlines.h +++ b/src/plugins/nat/nat44/inlines.h @@ -31,6 +31,17 @@ nat44_maximum_sessions_exceeded (snat_main_t * sm, u32 thread_index) return 0; } +static_always_inline u8 +nat44_ed_maximum_sessions_exceeded (snat_main_t * sm, + u32 fib_index, u32 thread_index) +{ + u32 translations; + translations = pool_elts (sm->per_thread_data[thread_index].sessions); + if (vec_len (sm->max_translations_per_fib) <= fib_index) + fib_index = 0; + return translations >= sm->max_translations_per_fib[fib_index]; +} + static_always_inline snat_session_t * nat44_session_reuse_old (snat_main_t * sm, snat_user_t * u, snat_session_t * s, u32 thread_index, f64 now) diff --git a/src/plugins/nat/nat44_cli.c b/src/plugins/nat/nat44_cli.c index 333b3854845..fe08832c641 100644 --- a/src/plugins/nat/nat44_cli.c +++ b/src/plugins/nat/nat44_cli.c @@ -1532,6 +1532,49 @@ print: return error; } +static clib_error_t * +nat44_set_session_limit_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + snat_main_t *sm = &snat_main; + unformat_input_t _line_input, *line_input = &_line_input; + clib_error_t *error = 0; + + u32 session_limit = 0, vrf_id = 0; + + if (sm->deterministic) + return clib_error_return (0, UNSUPPORTED_IN_DET_MODE_STR); + + /* Get a line of input. */ + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (line_input, "%u", &session_limit)) + ; + else if (unformat (line_input, "vrf %u", &vrf_id)) + ; + else + { + error = clib_error_return (0, "unknown input '%U'", + format_unformat_error, line_input); + goto done; + } + } + + if (!session_limit) + error = clib_error_return (0, "missing value of session limit"); + else if (nat44_set_session_limit (session_limit, vrf_id)) + error = clib_error_return (0, "nat44_set_session_limit failed"); + +done: + unformat_free (line_input); + + return error; +} + static clib_error_t * nat44_del_user_command_fn (vlib_main_t * vm, unformat_input_t * input, vlib_cli_command_t * cmd) @@ -2585,6 +2628,18 @@ VLIB_CLI_COMMAND (nat44_show_sessions_command, static) = { .function = nat44_show_sessions_command_fn, }; +/*? + * @cliexpar + * @cliexstart{set nat44 session limit} + * Set NAT44 session limit. + * @cliexend +?*/ +VLIB_CLI_COMMAND (nat44_set_session_limit_command, static) = { + .path = "set nat44 session limit", + .short_help = "set nat44 session limit [vrf ]", + .function = nat44_set_session_limit_command_fn, +}; + /*? * @cliexpar * @cliexstart{nat44 del user} diff --git a/src/plugins/nat/nat_api.c b/src/plugins/nat/nat_api.c index a54a4790a20..c58e88bd7f0 100644 --- a/src/plugins/nat/nat_api.c +++ b/src/plugins/nat/nat_api.c @@ -241,6 +241,33 @@ vl_api_nat_worker_dump_t_print (vl_api_nat_worker_dump_t * mp, void *handle) FINISH; } +static void +vl_api_nat44_set_session_limit_t_handler (vl_api_nat44_set_session_limit_t * + mp) +{ + snat_main_t *sm = &snat_main; + vl_api_nat44_set_session_limit_reply_t *rmp; + int rv = 0; + + rv = nat44_set_session_limit + (ntohl (mp->session_limit), ntohl (mp->vrf_id)); + + REPLY_MACRO (VL_API_NAT_SET_WORKERS_REPLY); +} + +static void * +vl_api_nat44_set_session_limit_t_print (vl_api_nat44_set_session_limit_t * + mp, void *handle) +{ + u8 *s; + + s = format (0, "SCRIPT: nat44_set_session_limit "); + s = format (s, "session_limit %d", ntohl (mp->session_limit)); + s = format (s, "vrf_id %d", ntohl (mp->vrf_id)); + + FINISH; +} + static void vl_api_nat_set_log_level_t_handler (vl_api_nat_set_log_level_t * mp) { @@ -3141,6 +3168,7 @@ _(NAT_SHOW_CONFIG, nat_show_config) \ _(NAT_SET_WORKERS, nat_set_workers) \ _(NAT_WORKER_DUMP, nat_worker_dump) \ _(NAT44_DEL_USER, nat44_del_user) \ +_(NAT44_SET_SESSION_LIMIT, nat44_set_session_limit) \ _(NAT_SET_LOG_LEVEL, nat_set_log_level) \ _(NAT_IPFIX_ENABLE_DISABLE, nat_ipfix_enable_disable) \ _(NAT_SET_TIMEOUTS, nat_set_timeouts) \ diff --git a/src/plugins/nat/out2in_ed.c b/src/plugins/nat/out2in_ed.c index 1382125dcf7..26a2e877a54 100644 --- a/src/plugins/nat/out2in_ed.c +++ b/src/plugins/nat/out2in_ed.c @@ -193,6 +193,7 @@ create_session_for_static_mapping_ed (snat_main_t * sm, snat_session_key_t l_key, snat_session_key_t e_key, vlib_node_runtime_t * node, + u32 rx_fib_index, u32 thread_index, twice_nat_type_t twice_nat, lb_nat_type_t lb_nat, f64 now) @@ -205,7 +206,8 @@ create_session_for_static_mapping_ed (snat_main_t * sm, snat_session_key_t eh_key; nat44_is_idle_session_ctx_t ctx; - if (PREDICT_FALSE (nat44_maximum_sessions_exceeded (sm, thread_index))) + if (PREDICT_FALSE + (nat44_ed_maximum_sessions_exceeded (sm, rx_fib_index, thread_index))) { b->error = node->errors[NAT_OUT2IN_ED_ERROR_MAX_SESSIONS_EXCEEDED]; nat_elog_notice ("maximum sessions exceeded"); @@ -359,7 +361,9 @@ create_bypass_for_fwd (snat_main_t * sm, vlib_buffer_t * b, ip4_header_t * ip, { u32 proto; - if (PREDICT_FALSE (nat44_maximum_sessions_exceeded (sm, thread_index))) + if (PREDICT_FALSE + (nat44_ed_maximum_sessions_exceeded + (sm, rx_fib_index, thread_index))) return; s = nat_ed_session_alloc (sm, thread_index, now); @@ -502,7 +506,8 @@ icmp_match_out2in_ed (snat_main_t * sm, vlib_node_runtime_t * node, /* Create session initiated by host from external network */ s = create_session_for_static_mapping_ed (sm, b, l_key, e_key, node, - thread_index, 0, 0, + rx_fib_index, thread_index, 0, + 0, vlib_time_now (sm->vlib_main)); @@ -568,7 +573,9 @@ nat44_ed_out2in_unknown_proto (snat_main_t * sm, } else { - if (PREDICT_FALSE (nat44_maximum_sessions_exceeded (sm, thread_index))) + if (PREDICT_FALSE + (nat44_ed_maximum_sessions_exceeded + (sm, rx_fib_index, thread_index))) { b->error = node->errors[NAT_OUT2IN_ED_ERROR_MAX_SESSIONS_EXCEEDED]; nat_elog_notice ("maximum sessions exceeded"); @@ -1089,6 +1096,7 @@ nat44_ed_out2in_slow_path_node_fn_inline (vlib_main_t * vm, /* Create session initiated by host from external network */ s0 = create_session_for_static_mapping_ed (sm, b0, l_key0, e_key0, node, + rx_fib_index0, thread_index, twice_nat0, lb_nat0, now); diff --git a/src/plugins/nat/test/test_nat.py b/src/plugins/nat/test/test_nat.py index 19e4c50e8f0..2c0fa1017d6 100644 --- a/src/plugins/nat/test/test_nat.py +++ b/src/plugins/nat/test/test_nat.py @@ -1,40 +1,35 @@ #!/usr/bin/env python3 import ipaddress +import random import socket -import unittest import struct -import random - -from framework import VppTestCase, VppTestRunner, running_extended_tests +import unittest +from io import BytesIO +from time import sleep import scapy.compat +from framework import VppTestCase, VppTestRunner, running_extended_tests +from ipfix import IPFIX, Set, Template, Data, IPFIXDecoder +from scapy.all import bind_layers, Packet, ByteEnumField, ShortField, \ + IPField, IntField, LongField, XByteField, FlagsField, FieldLenField, \ + PacketListField +from scapy.data import IP_PROTOS from scapy.layers.inet import IP, TCP, UDP, ICMP from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror +from scapy.layers.inet6 import ICMPv6DestUnreach, IPerror6, IPv6ExtHdrFragment from scapy.layers.inet6 import IPv6, ICMPv6EchoRequest, ICMPv6EchoReply, \ ICMPv6ND_NS, ICMPv6ND_NA, ICMPv6NDOptDstLLAddr, fragment6 -from scapy.layers.inet6 import ICMPv6DestUnreach, IPerror6, IPv6ExtHdrFragment from scapy.layers.l2 import Ether, ARP, GRE -from scapy.data import IP_PROTOS -from scapy.packet import bind_layers, Raw -from util import ppp -from ipfix import IPFIX, Set, Template, Data, IPFIXDecoder -from time import sleep -from util import ip4_range -from vpp_papi import mac_pton +from scapy.packet import Raw from syslog_rfc5424_parser import SyslogMessage, ParseError -from syslog_rfc5424_parser.constants import SyslogFacility, SyslogSeverity -from io import BytesIO -from vpp_papi import VppEnum -from vpp_ip_route import VppIpRoute, VppRoutePath, FibPathType -from vpp_neighbor import VppNeighbor -from scapy.all import bind_layers, Packet, ByteEnumField, ShortField, \ - IPField, IntField, LongField, XByteField, FlagsField, FieldLenField, \ - PacketListField -from ipaddress import IPv6Network +from syslog_rfc5424_parser.constants import SyslogSeverity +from util import ip4_range from util import ppc, ppp -from socket import inet_pton, AF_INET from vpp_acl import AclRule, VppAcl, VppAclInterface +from vpp_ip_route import VppIpRoute, VppRoutePath +from vpp_neighbor import VppNeighbor +from vpp_papi import VppEnum # NAT HA protocol event data @@ -4151,6 +4146,139 @@ class TestNAT44(MethodHolder): self.logger.info(self.vapi.cli("show nat ha")) +class TestNAT44EndpointDependent2(MethodHolder): + """ Endpoint-Dependent mapping and filtering test cases """ + + @classmethod + def setUpConstants(cls): + super(TestNAT44EndpointDependent2, cls).setUpConstants() + cls.vpp_cmdline.extend(["nat", "{", "endpoint-dependent", "}"]) + + @classmethod + def tearDownClass(cls): + super(TestNAT44EndpointDependent2, cls).tearDownClass() + + def tearDown(self): + super(TestNAT44EndpointDependent2, self).tearDown() + + @classmethod + def create_and_add_ip4_table(cls, i, table_id): + cls.vapi.ip_table_add_del(is_add=1, table={'table_id': table_id}) + i.set_table_ip4(table_id) + + @classmethod + def setUpClass(cls): + super(TestNAT44EndpointDependent2, cls).setUpClass() + + cls.create_pg_interfaces(range(3)) + cls.interfaces = list(cls.pg_interfaces) + + cls.create_and_add_ip4_table(cls.pg1, 10) + + for i in cls.interfaces: + i.admin_up() + i.config_ip4() + i.resolve_arp() + + i.generate_remote_hosts(1) + i.configure_ipv4_neighbors() + + def setUp(self): + super(TestNAT44EndpointDependent2, self).setUp() + + nat_config = self.vapi.nat_show_config() + self.assertEqual(1, nat_config.endpoint_dependent) + + def nat_add_inside_interface(self, i): + self.vapi.nat44_interface_add_del_feature( + flags=self.config_flags.NAT_IS_INSIDE, + sw_if_index=i.sw_if_index, is_add=1) + + def nat_add_outside_interface(self, i): + self.vapi.nat44_interface_add_del_feature( + flags=self.config_flags.NAT_IS_OUTSIDE, + sw_if_index=i.sw_if_index, is_add=1) + + def nat_add_interface_address(self, i): + self.nat_addr = i.local_ip4 + self.vapi.nat44_add_del_interface_addr( + sw_if_index=i.sw_if_index, is_add=1) + + def nat_add_address(self, address, vrf_id=0xFFFFFFFF): + self.nat_addr = address + self.nat44_add_address(address, vrf_id=vrf_id) + + def cli(self, command): + result = self.vapi.cli(command) + self.logger.info(result) + # print(result) + + def show_configuration(self): + self.cli("show interface") + self.cli("show interface address") + self.cli("show nat44 addresses") + self.cli("show nat44 interfaces") + + def create_tcp_stream(self, in_if, out_if, count): + """ + Create tcp packet stream + + :param in_if: Inside interface + :param out_if: Outside interface + :param count: count of packets to generate + """ + pkts = [] + port = 6303 + + 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) / + TCP(sport=port + i, dport=20)) + pkts.append(p) + + return pkts + + def test_session_limit_per_vrf(self): + + inside = self.pg0 + inside_vrf10 = self.pg1 + outside = self.pg2 + + limit = 5 + + # 2 interfaces pg0, pg1 (vrf10, limit 1 tcp session) + # non existing vrf_id makes process core dump + self.vapi.nat44_set_session_limit(session_limit=limit, vrf_id=10) + + self.nat_add_inside_interface(inside) + self.nat_add_inside_interface(inside_vrf10) + self.nat_add_outside_interface(outside) + + # vrf independent + self.nat_add_interface_address(outside) + + # BUG: causing core dump - when bad vrf_id is specified + # self.nat44_add_address(outside.local_ip4, vrf_id=20) + + self.show_configuration() + + stream = self.create_tcp_stream(inside_vrf10, outside, limit * 2) + inside_vrf10.add_stream(stream) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + capture = outside.get_capture(limit) + + stream = self.create_tcp_stream(inside, outside, limit * 2) + inside.add_stream(stream) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + capture = outside.get_capture(len(stream)) + + class TestNAT44EndpointDependent(MethodHolder): """ Endpoint-Dependent mapping and filtering test cases """ -- 2.16.6