Translate matching packets using NAT (VPP-1069) 59/9559/8
authorJuraj Sloboda <jsloboda@cisco.com>
Thu, 23 Nov 2017 12:20:48 +0000 (13:20 +0100)
committerOle Trøan <otroan@employees.org>
Wed, 20 Dec 2017 11:12:24 +0000 (11:12 +0000)
Add API function which enables forwarding of packets not matching
existing translation or static mapping instead of dropping them.

When forwarding is enabled matching packets will be translated
while non-matching packets will be forwarded without translation.

Change-Id: Ic13040cbad16d3a1ecdc3e02a497171bef6aa413
Signed-off-by: Juraj Sloboda <jsloboda@cisco.com>
src/plugins/nat/in2out.c
src/plugins/nat/nat.api
src/plugins/nat/nat.c
src/plugins/nat/nat.h
src/plugins/nat/nat_api.c
src/plugins/nat/out2in.c
test/test_nat.py
test/vpp_papi_provider.py

index 603abb8..517011e 100755 (executable)
@@ -242,6 +242,9 @@ snat_not_translate (snat_main_t * sm, vlib_node_runtime_t *node,
   else
     return 0;
 
+  if (sm->forwarding_enabled)
+    return 1;
+
   return snat_not_translate_fast(sm, node, sw_if_index0, ip0, proto0,
                                  rx_fib_index0);
 }
index 2a8eeb9..d6a912b 100644 (file)
@@ -606,6 +606,37 @@ autoreply define nat44_del_session {
   u32 vrf_id;
 };
 
+/** \brief Enable/disable forwarding for NAT44
+    Forward packets which don't match existing translation
+    or static mapping instead of dropping them.
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param enable - 0 for enable, 1 for disable
+*/
+autoreply define nat44_forwarding_enable_disable {
+  u32 client_index;
+  u32 context;
+  u8 enable;
+};
+
+/** \brief Check if forwarding is enabled or disabled
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+*/
+define nat44_forwarding_is_enabled {
+  u32 client_index;
+  u32 context;
+};
+
+/** \brief Response to check if forwarding is enabled or disabled
+    @param context - sender context, to match reply w/ request
+    @param enabled - 1 if enabled, 0 if disabled
+*/
+define nat44_forwarding_is_enabled_reply {
+  u32 context;
+  u8 enabled;
+};
+
 
 /*
  * Deterministic NAT (CGN) APIs
index 5294401..df00f5e 100644 (file)
@@ -1642,6 +1642,7 @@ static clib_error_t * snat_init (vlib_main_t * vm)
   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->forwarding_enabled = 0;
 
   p = hash_get_mem (tm->thread_registrations_by_name, "workers");
   if (p)
@@ -4208,3 +4209,70 @@ VLIB_CLI_COMMAND (snat_det_close_session_in_command, static) = {
                 "<in_addr>:<in_port> <ext_addr>:<ext_port>",
   .function = snat_det_close_session_in_fn,
 };
+
+static clib_error_t *
+snat_forwarding_set_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;
+  u8 forwarding_enable;
+  u8 forwarding_enable_set = 0;
+  clib_error_t *error = 0;
+
+  /* Get a line of input. */
+  if (!unformat_user (input, unformat_line_input, line_input))
+      return clib_error_return (0, "'enable' or 'disable' expected");
+
+  while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT)
+    {
+      if (!forwarding_enable_set && unformat (line_input, "enable"))
+        {
+          forwarding_enable = 1;
+          forwarding_enable_set = 1;
+        }
+      else if (!forwarding_enable_set && unformat (line_input, "disable"))
+        {
+          forwarding_enable = 0;
+          forwarding_enable_set = 1;
+        }
+      else
+        {
+          error = clib_error_return (0, "unknown input '%U'",
+                                     format_unformat_error, line_input);
+          goto done;
+        }
+    }
+
+  if (!forwarding_enable_set)
+    {
+      error = clib_error_return (0, "'enable' or 'disable' expected");
+      goto done;
+    }
+
+  sm->forwarding_enabled = forwarding_enable;
+
+done:
+  unformat_free(line_input);
+
+  return error;
+}
+
+/*?
+ * @cliexpar
+ * @cliexstart{nat44 forwarding}
+ * Enable or disable forwarding
+ * Forward packets which don't match existing translation
+ * or static mapping instead of dropping them.
+ * To enable forwarding, use:
+ *  vpp# nat44 forwarding enable
+ * To disable forwarding, use:
+ *  vpp# nat44 forwarding disable
+ * @cliexend
+?*/
+VLIB_CLI_COMMAND (snat_forwarding_set_command, static) = {
+  .path = "nat44 forwarding",
+  .short_help = "nat44 forwarding enable|disable",
+  .function = snat_forwarding_set_command_fn,
+};
index 5a2d085..1e8e3ca 100644 (file)
@@ -350,6 +350,9 @@ typedef struct snat_main_s {
   /* Deterministic NAT */
   snat_det_map_t * det_maps;
 
+  /* If forwarding is enabled */
+  u8 forwarding_enabled;
+
   /* Config parameters */
   u8 static_mapping_only;
   u8 static_mapping_connection_tracking;
index 5071609..2397663 100644 (file)
@@ -1366,6 +1366,63 @@ vl_api_nat44_del_session_t_print (vl_api_nat44_del_session_t * mp,
   FINISH;
 }
 
+static void
+  vl_api_nat44_forwarding_enable_disable_t_handler
+  (vl_api_nat44_forwarding_enable_disable_t * mp)
+{
+  snat_main_t *sm = &snat_main;
+  vl_api_nat44_forwarding_enable_disable_reply_t *rmp;
+  int rv = 0;
+
+  sm->forwarding_enabled = mp->enable != 0;
+
+  REPLY_MACRO (VL_API_NAT44_FORWARDING_ENABLE_DISABLE_REPLY);
+}
+
+static void *vl_api_nat44_forwarding_enable_disable_t_print
+  (vl_api_nat44_forwarding_enable_disable_t * mp, void *handle)
+{
+  u8 *s;
+
+  s = format (0, "SCRIPT: nat44_forwarding_enable_disable ");
+  s = format (s, "enable %d", mp->enable != 0);
+
+  FINISH;
+}
+
+static void
+  vl_api_nat44_forwarding_is_enabled_t_handler
+  (vl_api_nat44_forwarding_is_enabled_t * mp)
+{
+  unix_shared_memory_queue_t *q;
+  snat_main_t *sm = &snat_main;
+  vl_api_nat44_forwarding_is_enabled_reply_t *rmp;
+
+  q = vl_api_client_index_to_input_queue (mp->client_index);
+  if (q == 0)
+    return;
+
+  rmp = vl_msg_api_alloc (sizeof (*rmp));
+  memset (rmp, 0, sizeof (*rmp));
+  rmp->_vl_msg_id =
+    ntohs (VL_API_NAT44_FORWARDING_IS_ENABLED_REPLY + sm->msg_id_base);
+  rmp->context = mp->context;
+
+  rmp->enabled = sm->forwarding_enabled;
+
+  vl_msg_api_send_shmem (q, (u8 *) & rmp);
+}
+
+static void *vl_api_nat44_forwarding_is_enabled_t_print
+  (vl_api_nat44_forwarding_is_enabled_t * mp, void *handle)
+{
+  u8 *s;
+
+  s = format (0, "SCRIPT: nat44_forwarding_is_enabled ");
+
+  FINISH;
+}
+
 /*******************************/
 /*** Deterministic NAT (CGN) ***/
 /*******************************/
@@ -2434,6 +2491,8 @@ _(NAT44_INTERFACE_OUTPUT_FEATURE_DUMP,                                  \
 _(NAT44_ADD_DEL_LB_STATIC_MAPPING, nat44_add_del_lb_static_mapping)     \
 _(NAT44_LB_STATIC_MAPPING_DUMP, nat44_lb_static_mapping_dump)           \
 _(NAT44_DEL_SESSION, nat44_del_session)                                 \
+_(NAT44_FORWARDING_ENABLE_DISABLE, nat44_forwarding_enable_disable)     \
+_(NAT44_FORWARDING_IS_ENABLED, nat44_forwarding_is_enabled)             \
 _(NAT_DET_ADD_DEL_MAP, nat_det_add_del_map)                             \
 _(NAT_DET_FORWARD, nat_det_forward)                                     \
 _(NAT_DET_REVERSE, nat_det_reverse)                                     \
index b5464e0..d548ab3 100755 (executable)
@@ -320,16 +320,24 @@ u32 icmp_match_out2in_slow(snat_main_t *sm, vlib_node_runtime_t *node,
          destination address and port in packet */
       if (snat_static_mapping_match(sm, key0, &sm0, 1, &is_addr_only, 0))
         {
-          /* Don't NAT packet aimed at the intfc address */
-          if (PREDICT_FALSE(is_interface_addr(sm, node, sw_if_index0,
-                                              ip0->dst_address.as_u32)))
+          if (!sm->forwarding_enabled)
+            {
+              /* Don't NAT packet aimed at the intfc address */
+              if (PREDICT_FALSE(is_interface_addr(sm, node, sw_if_index0,
+                                                  ip0->dst_address.as_u32)))
+                {
+                  dont_translate = 1;
+                  goto out;
+                }
+              b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
+              next0 = SNAT_OUT2IN_NEXT_DROP;
+              goto out;
+            }
+          else
             {
               dont_translate = 1;
               goto out;
             }
-          b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
-          next0 = SNAT_OUT2IN_NEXT_DROP;
-          goto out;
         }
 
       if (PREDICT_FALSE(icmp0->type != ICMP4_echo_reply &&
@@ -1017,16 +1025,21 @@ snat_out2in_node_fn (vlib_main_t * vm,
                  destination address and port in packet */
               if (snat_static_mapping_match(sm, key0, &sm0, 1, 0, 0))
                 {
-                  b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
-                  /*
-                   * Send DHCP packets to the ipv4 stack, or we won't
-                   * be able to use dhcp client on the outside interface
-                   */
-                  if (proto0 != SNAT_PROTOCOL_UDP
-                      || (udp0->dst_port
-                          != clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client)))
-                    next0 = SNAT_OUT2IN_NEXT_DROP;
-                  goto trace0;
+                  if (!sm->forwarding_enabled)
+                    {
+                      b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
+                      /*
+                       * Send DHCP packets to the ipv4 stack, or we won't
+                       * be able to use dhcp client on the outside interface
+                       */
+                      if (proto0 != SNAT_PROTOCOL_UDP
+                          || (udp0->dst_port
+                              != clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client)))
+                        next0 = SNAT_OUT2IN_NEXT_DROP;
+                      goto trace0;
+                    }
+                  else
+                    goto trace0;
                 }
 
               /* Create session initiated by host from external network */
@@ -1175,16 +1188,21 @@ snat_out2in_node_fn (vlib_main_t * vm,
                  destination address and port in packet */
               if (snat_static_mapping_match(sm, key1, &sm1, 1, 0, 0))
                 {
-                  b1->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
-                  /*
-                   * Send DHCP packets to the ipv4 stack, or we won't
-                   * be able to use dhcp client on the outside interface
-                   */
-                  if (proto1 != SNAT_PROTOCOL_UDP
-                      || (udp1->dst_port
-                          != clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client)))
-                    next1 = SNAT_OUT2IN_NEXT_DROP;
-                  goto trace1;
+                  if (!sm->forwarding_enabled)
+                    {
+                      b1->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
+                      /*
+                       * Send DHCP packets to the ipv4 stack, or we won't
+                       * be able to use dhcp client on the outside interface
+                       */
+                      if (proto1 != SNAT_PROTOCOL_UDP
+                          || (udp1->dst_port
+                              != clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client)))
+                        next1 = SNAT_OUT2IN_NEXT_DROP;
+                      goto trace1;
+                    }
+                  else
+                    goto trace1;
                 }
 
               /* Create session initiated by host from external network */
@@ -1369,17 +1387,21 @@ snat_out2in_node_fn (vlib_main_t * vm,
                  destination address and port in packet */
               if (snat_static_mapping_match(sm, key0, &sm0, 1, 0, 0))
                 {
-                  b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
-                  /*
-                   * Send DHCP packets to the ipv4 stack, or we won't
-                   * be able to use dhcp client on the outside interface
-                   */
-                  if (proto0 != SNAT_PROTOCOL_UDP
-                      || (udp0->dst_port
-                          != clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client)))
-
-                    next0 = SNAT_OUT2IN_NEXT_DROP;
-                  goto trace00;
+                  if (!sm->forwarding_enabled)
+                    {
+                      b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
+                      /*
+                       * Send DHCP packets to the ipv4 stack, or we won't
+                       * be able to use dhcp client on the outside interface
+                       */
+                      if (proto0 != SNAT_PROTOCOL_UDP
+                          || (udp0->dst_port
+                              != clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client)))
+                        next0 = SNAT_OUT2IN_NEXT_DROP;
+                      goto trace00;
+                    }
+                  else
+                    goto trace00;
                 }
 
               /* Create session initiated by host from external network */
@@ -1605,17 +1627,21 @@ nat44_out2in_reass_node_fn (vlib_main_t * vm,
                      destination address and port in packet */
                   if (snat_static_mapping_match(sm, key0, &sm0, 1, 0, 0))
                     {
-                      b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
-                      /*
-                       * Send DHCP packets to the ipv4 stack, or we won't
-                       * be able to use dhcp client on the outside interface
-                       */
-                      if (proto0 != SNAT_PROTOCOL_UDP
-                          || (udp0->dst_port
-                              != clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client)))
-
-                        next0 = SNAT_OUT2IN_NEXT_DROP;
-                      goto trace0;
+                      if (!sm->forwarding_enabled)
+                        {
+                          b0->error = node->errors[SNAT_OUT2IN_ERROR_NO_TRANSLATION];
+                          /*
+                           * Send DHCP packets to the ipv4 stack, or we won't
+                           * be able to use dhcp client on the outside interface
+                           */
+                          if (proto0 != SNAT_PROTOCOL_UDP
+                              || (udp0->dst_port
+                                  != clib_host_to_net_u16(UDP_DST_PORT_dhcp_to_client)))
+                            next0 = SNAT_OUT2IN_NEXT_DROP;
+                          goto trace0;
+                        }
+                      else
+                        goto trace0;
                     }
 
                   /* Create session initiated by host from external network */
index 1f87bff..aeeb5aa 100644 (file)
@@ -242,33 +242,44 @@ class MethodHolder(VppTestCase):
 
         return pkts
 
-    def create_stream_out(self, out_if, dst_ip=None, ttl=64):
+    def create_stream_out(self, out_if, dst_ip=None, ttl=64,
+                          use_inside_ports=False):
         """
         Create packet stream for outside network
 
         :param out_if: Outside interface
         :param dst_ip: Destination IP address (Default use global NAT address)
         :param ttl: TTL of generated packets
+        :param use_inside_ports: Use inside NAT ports as destination ports
+               instead of outside ports
         """
         if dst_ip is None:
             dst_ip = self.nat_addr
+        if not use_inside_ports:
+            tcp_port = self.tcp_port_out
+            udp_port = self.udp_port_out
+            icmp_id = self.icmp_id_out
+        else:
+            tcp_port = self.tcp_port_in
+            udp_port = self.udp_port_in
+            icmp_id = self.icmp_id_in
         pkts = []
         # TCP
         p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) /
              IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) /
-             TCP(dport=self.tcp_port_out, sport=20))
+             TCP(dport=tcp_port, sport=20))
         pkts.append(p)
 
         # UDP
         p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) /
              IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) /
-             UDP(dport=self.udp_port_out, sport=20))
+             UDP(dport=udp_port, sport=20))
         pkts.append(p)
 
         # ICMP
         p = (Ether(dst=out_if.local_mac, src=out_if.remote_mac) /
              IP(src=out_if.remote_ip4, dst=dst_ip, ttl=ttl) /
-             ICMP(id=self.icmp_id_out, type='echo-reply'))
+             ICMP(id=icmp_id, type='echo-reply'))
         pkts.append(p)
 
         return pkts
@@ -654,6 +665,7 @@ class TestNAT44(MethodHolder):
             cls.icmp_id_in = 6305
             cls.icmp_id_out = 6305
             cls.nat_addr = '10.0.0.3'
+            cls.nat_addr_n = socket.inet_pton(socket.AF_INET, cls.nat_addr)
             cls.ipfix_src_port = 4739
             cls.ipfix_domain_id = 1
 
@@ -1044,6 +1056,66 @@ class TestNAT44(MethodHolder):
         self.verify_capture_out(capture, same_port=True, packet_num=1)
         self.assert_equal(capture[0][IP].proto, IP_PROTOS.icmp)
 
+    def test_forwarding(self):
+        """ NAT44 forwarding test """
+
+        self.vapi.nat44_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.nat44_interface_add_del_feature(self.pg1.sw_if_index,
+                                                  is_inside=0)
+        self.vapi.nat44_forwarding_enable_disable(1)
+
+        real_ip = self.pg0.remote_ip4n
+        alias_ip = self.nat_addr_n
+        self.vapi.nat44_add_del_static_mapping(local_ip=real_ip,
+                                               external_ip=alias_ip)
+
+        try:
+            # in2out - static mapping match
+
+            pkts = self.create_stream_out(self.pg1)
+            self.pg1.add_stream(pkts)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            capture = self.pg0.get_capture(len(pkts))
+            self.verify_capture_in(capture, self.pg0)
+
+            pkts = self.create_stream_in(self.pg0, self.pg1)
+            self.pg0.add_stream(pkts)
+            self.pg_enable_capture(self.pg_interfaces)
+            self.pg_start()
+            capture = self.pg1.get_capture(len(pkts))
+            self.verify_capture_out(capture, same_port=True)
+
+            # in2out - no static mapping match
+
+            host0 = self.pg0.remote_hosts[0]
+            self.pg0.remote_hosts[0] = self.pg0.remote_hosts[1]
+            try:
+                pkts = self.create_stream_out(self.pg1,
+                                              dst_ip=self.pg0.remote_ip4,
+                                              use_inside_ports=True)
+                self.pg1.add_stream(pkts)
+                self.pg_enable_capture(self.pg_interfaces)
+                self.pg_start()
+                capture = self.pg0.get_capture(len(pkts))
+                self.verify_capture_in(capture, self.pg0)
+
+                pkts = self.create_stream_in(self.pg0, self.pg1)
+                self.pg0.add_stream(pkts)
+                self.pg_enable_capture(self.pg_interfaces)
+                self.pg_start()
+                capture = self.pg1.get_capture(len(pkts))
+                self.verify_capture_out(capture, nat_ip=self.pg0.remote_ip4,
+                                        same_port=True)
+            finally:
+                self.pg0.remote_hosts[0] = host0
+
+        finally:
+            self.vapi.nat44_forwarding_enable_disable(0)
+            self.vapi.nat44_add_del_static_mapping(local_ip=real_ip,
+                                                   external_ip=alias_ip,
+                                                   is_add=0)
+
     def test_static_in(self):
         """ 1:1 NAT initialized from inside network """
 
index 3644d3c..aa06cfe 100644 (file)
@@ -1457,6 +1457,17 @@ class VppPapiProvider(object):
              'vrf_id': vrf_id,
              'is_in': is_in})
 
+    def nat44_forwarding_enable_disable(
+            self,
+            enable):
+        """Enable/disable forwarding for NAT44
+
+        :param enable: 1 for enable, 0 for disable
+        """
+        return self.api(
+            self.papi.nat44_forwarding_enable_disable,
+            {'enable': enable})
+
     def nat_set_reass(
             self,
             timeout=2,