nat: per vrf session limits 75/26775/5
authorFilip Varga <fivarga@cisco.com>
Thu, 16 Apr 2020 11:20:25 +0000 (13:20 +0200)
committerFilip Varga <fivarga@cisco.com>
Mon, 4 May 2020 10:15:02 +0000 (12:15 +0200)
Type: improvement

Change-Id: I170256ab47978db34fb0ff6808d9cd54ab872410
Signed-off-by: Filip Varga <fivarga@cisco.com>
src/plugins/nat/in2out_ed.c
src/plugins/nat/nat.api
src/plugins/nat/nat.c
src/plugins/nat/nat.h
src/plugins/nat/nat44/inlines.h
src/plugins/nat/nat44_cli.c
src/plugins/nat/nat_api.c
src/plugins/nat/out2in_ed.c
src/plugins/nat/test/test_nat.py

index d78e552..f0bbe0b 100644 (file)
@@ -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);
index 10e7036..134f3e0 100644 (file)
@@ -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
index ba682f9..7cb0b53 100755 (executable)
@@ -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;
 
index 59a1243..9331901 100644 (file)
@@ -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)
  *
index 3c88e15..5cd4164 100644 (file)
@@ -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)
index 333b385..fe08832 100644 (file)
@@ -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 <limit> [vrf <table-id>]",
+  .function = nat44_set_session_limit_command_fn,
+};
+
 /*?
  * @cliexpar
  * @cliexstart{nat44 del user}
index a54a479..c58e88b 100644 (file)
@@ -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)                                   \
index 1382125..26a2e87 100644 (file)
@@ -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);
index 19e4c50..2c0fa10 100644 (file)
@@ -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 """