SNAT: Add outbound addresses to FIB (VPP-613)

Add the external NAT address to the FIB as receive entries.
This ensures that VPP will reply to ARP for these addresses and we don't need
to enable proxy ARP on the outside interface.

Change-Id: I1db153373c43fec4808845449a17085509ca588c
Signed-off-by: Matus Fabian <matfabia@cisco.com>
diff --git a/src/plugins/snat/snat.c b/src/plugins/snat/snat.c
index fb56659..6149411 100644
--- a/src/plugins/snat/snat.c
+++ b/src/plugins/snat/snat.c
@@ -22,6 +22,8 @@
 #include <vlibapi/api.h>
 #include <snat/snat.h>
 #include <snat/snat_ipfix_logging.h>
+#include <vnet/fib/fib_table.h>
+#include <vnet/fib/ip4_fib.h>
 
 #include <vlibapi/api.h>
 #include <vlibmemory/api.h>
@@ -211,9 +213,66 @@
 
 #endif  /* CLIB_DEBUG > 0 */
 
+/**
+ * @brief Add/del NAT address to FIB.
+ *
+ * Add the external NAT address to the FIB as receive entries. This ensures
+ * that VPP will reply to ARP for this address and we don't need to enable
+ * proxy ARP on the outside interface.
+ *
+ * @param sm SNAT main
+ * @param addr IPv4 address.
+ * @param sw_if_index Interface.
+ * @param is_add If 0 delete, otherwise add.
+ */
+static void
+snat_add_del_addr_to_fib (snat_main_t *sm,
+                          ip4_address_t * addr,
+                          u32 sw_if_index,
+                          int is_add)
+{
+  ip4_main_t * ip4_main = sm->ip4_main;
+  ip4_address_t * first_int_addr;
+  fib_prefix_t prefix = {
+    .fp_len = 32,
+    .fp_proto = FIB_PROTOCOL_IP4,
+    .fp_addr = {
+        .ip4.as_u32 = addr->as_u32,
+    },
+  };
+  u32 fib_index = ip4_fib_table_get_index_for_sw_if_index(sw_if_index);
+
+  first_int_addr = ip4_interface_first_address (ip4_main, sw_if_index, 0);
+  if (first_int_addr)
+    {
+      if (first_int_addr->as_u32 == addr->as_u32)
+        return;
+    }
+
+  if (is_add)
+    fib_table_entry_update_one_path(fib_index,
+                                    &prefix,
+                                    FIB_SOURCE_INTERFACE,
+                                    (FIB_ENTRY_FLAG_CONNECTED |
+                                     FIB_ENTRY_FLAG_LOCAL |
+                                     FIB_ENTRY_FLAG_EXCLUSIVE),
+                                    FIB_PROTOCOL_IP4,
+                                    NULL,
+                                    sw_if_index,
+                                    ~0,
+                                    1,
+                                    NULL,
+                                    FIB_ROUTE_PATH_FLAG_NONE);
+  else
+    fib_table_entry_delete(fib_index,
+                           &prefix,
+                           FIB_SOURCE_INTERFACE);
+}
+
 void snat_add_address (snat_main_t *sm, ip4_address_t *addr)
 {
   snat_address_t * ap;
+  snat_interface_t *i;
 
   /* Check if address already exists */
   vec_foreach (ap, sm->addresses)
@@ -225,6 +284,15 @@
   vec_add2 (sm->addresses, ap, 1);
   ap->addr = *addr;
   clib_bitmap_alloc (ap->busy_port_bitmap, 65535);
+
+  /* Add external address to FIB */
+  pool_foreach (i, sm->interfaces,
+  ({
+    if (i->is_inside)
+      continue;
+
+    snat_add_del_addr_to_fib(sm, addr, i->sw_if_index, 1);
+  }));
 }
 
 static int is_snat_address_used_in_static_mapping (snat_main_t *sm,
@@ -297,6 +365,7 @@
   snat_address_t *a = 0;
   u32 fib_index = ~0;
   uword * p;
+  snat_interface_t *interface;
   int i;
 
   /* If outside FIB index is not resolved yet */
@@ -561,6 +630,18 @@
       pool_put (sm->static_mappings, m);
     }
 
+  if (!addr_only)
+    return 0;
+
+  /* Add/delete external address to FIB */
+  pool_foreach (interface, sm->interfaces,
+  ({
+    if (interface->is_inside)
+      continue;
+
+    snat_add_del_addr_to_fib(sm, &e_addr, interface->sw_if_index, is_add);
+  }));
+
   return 0;
 }
 
@@ -574,6 +655,7 @@
   snat_user_t *u;
   snat_main_per_thread_data_t *tsm;
   snat_static_mapping_t *m;
+  snat_interface_t *interface;
   int i;
 
   /* Find SNAT address */
@@ -649,6 +731,15 @@
 
   vec_del1 (sm->addresses, i);
 
+  /* Delete external address from FIB */
+  pool_foreach (interface, sm->interfaces,
+  ({
+    if (interface->is_inside)
+      continue;
+
+    snat_add_del_addr_to_fib(sm, &addr, interface->sw_if_index, 0);
+  }));
+
   return 0;
 }
 
@@ -657,6 +748,8 @@
   snat_main_t *sm = &snat_main;
   snat_interface_t *i;
   const char * feature_name;
+  snat_address_t * ap;
+  snat_static_mapping_t * m;
 
   if (sm->static_mapping_only && !(sm->static_mapping_connection_tracking))
     feature_name = is_inside ?  "snat-in2out-fast" : "snat-out2in-fast";
@@ -686,7 +779,7 @@
         else
           return VNET_API_ERROR_VALUE_EXIST;
 
-        return 0;
+        goto fib;
       }
   }));
 
@@ -697,6 +790,22 @@
   i->sw_if_index = sw_if_index;
   i->is_inside = is_inside;
 
+  /* Add/delete external addresses to FIB */
+fib:
+  if (is_inside)
+    return 0;
+
+  vec_foreach (ap, sm->addresses)
+    snat_add_del_addr_to_fib(sm, &ap->addr, sw_if_index, !is_del);
+
+  pool_foreach (m, sm->static_mappings,
+  ({
+    if (!(m->addr_only))
+      continue;
+
+    snat_add_del_addr_to_fib(sm, &m->external_addr, sw_if_index, !is_del);
+  }));
+
   return 0;
 }
 
diff --git a/test/test_snat.py b/test/test_snat.py
index b6cc1c9..a67deed 100644
--- a/test/test_snat.py
+++ b/test/test_snat.py
@@ -6,7 +6,7 @@
 
 from framework import VppTestCase, VppTestRunner
 from scapy.layers.inet import IP, TCP, UDP, ICMP
-from scapy.layers.l2 import Ether
+from scapy.layers.l2 import Ether, ARP
 from scapy.data import IP_PROTOS
 from util import ppp
 from ipfix import IPFIX, Set, Template, Data, IPFIXDecoder
@@ -524,9 +524,7 @@
         self.pg3.assert_nothing_captured()
 
     def test_multiple_inside_interfaces(self):
-        """
-        SNAT multiple inside interfaces with non-overlapping address space
-        """
+        """ SNAT multiple inside interfaces (non-overlapping address space) """
 
         self.snat_add_address(self.snat_addr)
         self.vapi.snat_interface_add_del_feature(self.pg0.sw_if_index)
@@ -862,6 +860,67 @@
                 data = ipfix.decode_data_set(p.getlayer(Set))
                 self.verify_ipfix_addr_exhausted(data)
 
+    def test_pool_addr_fib(self):
+        """ S-NAT add pool addresses to FIB """
+        static_addr = '10.0.0.10'
+        self.snat_add_address(self.snat_addr)
+        self.vapi.snat_interface_add_del_feature(self.pg0.sw_if_index)
+        self.vapi.snat_interface_add_del_feature(self.pg1.sw_if_index,
+                                                 is_inside=0)
+        self.snat_add_static_mapping(self.pg0.remote_ip4, static_addr)
+
+        # SNAT address
+        p = (Ether(src=self.pg1.remote_mac, dst='ff:ff:ff:ff:ff:ff') /
+             ARP(op=ARP.who_has, pdst=self.snat_addr,
+                 psrc=self.pg1.remote_ip4, hwsrc=self.pg1.remote_mac))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        self.assertTrue(capture[0].haslayer(ARP))
+        self.assertTrue(capture[0][ARP].op, ARP.is_at)
+
+        # 1:1 NAT address
+        p = (Ether(src=self.pg1.remote_mac, dst='ff:ff:ff:ff:ff:ff') /
+             ARP(op=ARP.who_has, pdst=static_addr,
+                 psrc=self.pg1.remote_ip4, hwsrc=self.pg1.remote_mac))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(1)
+        self.assertTrue(capture[0].haslayer(ARP))
+        self.assertTrue(capture[0][ARP].op, ARP.is_at)
+
+        # send ARP to non-SNAT interface
+        p = (Ether(src=self.pg2.remote_mac, dst='ff:ff:ff:ff:ff:ff') /
+             ARP(op=ARP.who_has, pdst=self.snat_addr,
+                 psrc=self.pg2.remote_ip4, hwsrc=self.pg2.remote_mac))
+        self.pg2.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(0)
+
+        # remove addresses and verify
+        self.snat_add_address(self.snat_addr, is_add=0)
+        self.snat_add_static_mapping(self.pg0.remote_ip4, static_addr,
+                                     is_add=0)
+
+        p = (Ether(src=self.pg1.remote_mac, dst='ff:ff:ff:ff:ff:ff') /
+             ARP(op=ARP.who_has, pdst=self.snat_addr,
+                 psrc=self.pg1.remote_ip4, hwsrc=self.pg1.remote_mac))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(0)
+
+        p = (Ether(src=self.pg1.remote_mac, dst='ff:ff:ff:ff:ff:ff') /
+             ARP(op=ARP.who_has, pdst=static_addr,
+                 psrc=self.pg1.remote_ip4, hwsrc=self.pg1.remote_mac))
+        self.pg1.add_stream(p)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg1.get_capture(0)
+
     def tearDown(self):
         super(TestSNAT, self).tearDown()
         if not self.vpp_dead: