vxlan: Protect against tunnel config where source is not local

Type: fix

If a tunnel's source is not local then post encap VPP will attempt to
receive (via ip4-local) that packet, things go wrong from there.
The fix is when stacking the encap forwarding don't accept a receive
DPO. This approach is taken, rather than rejecting bad tunnels, because
the 'local-ness' of the tunnel's source can change and we can't reject
tunnels that were once correctly configured but are no longer.
the user will quickly discover their mistake as traffic won't pass.

Signed-off-by: Neale Ranns <nranns@cisco.com>
Change-Id: I46198422e321606e8baba003112e978a526b4c2f
diff --git a/src/vnet/vxlan/vxlan.c b/src/vnet/vxlan/vxlan.c
index bf205ad..71d03f6 100644
--- a/src/vnet/vxlan/vxlan.c
+++ b/src/vnet/vxlan/vxlan.c
@@ -20,6 +20,7 @@
 #include <vnet/mfib/mfib_table.h>
 #include <vnet/adj/adj_mcast.h>
 #include <vnet/adj/rewrite.h>
+#include <vnet/dpo/drop_dpo.h>
 #include <vnet/interface.h>
 #include <vnet/flow/flow.h>
 #include <vnet/udp/udp_local.h>
@@ -151,11 +152,19 @@
    * skip single bucket load balance dpo's */
   while (DPO_LOAD_BALANCE == dpo.dpoi_type)
     {
-      load_balance_t *lb = load_balance_get (dpo.dpoi_index);
+      const load_balance_t *lb;
+      const dpo_id_t *choice;
+
+      lb = load_balance_get (dpo.dpoi_index);
       if (lb->lb_n_buckets > 1)
 	break;
 
-      dpo_copy (&dpo, load_balance_get_bucket_i (lb, 0));
+      choice = load_balance_get_bucket_i (lb, 0);
+
+      if (DPO_RECEIVE == choice->dpoi_type)
+	dpo_copy (&dpo, drop_dpo_get (choice->dpoi_proto));
+      else
+	dpo_copy (&dpo, choice);
     }
 
   u32 encap_index = is_ip4 ?
diff --git a/test/test_vxlan.py b/test/test_vxlan.py
index 54d0c2d..dc48f53 100644
--- a/test/test_vxlan.py
+++ b/test/test_vxlan.py
@@ -282,5 +282,53 @@
         self.logger.info(self.vapi.cli("show vxlan tunnel"))
 
 
+class TestVxlan2(VppTestCase):
+    """ VXLAN Test Case """
+    def setUp(self):
+        super(TestVxlan2, self).setUp()
+
+        # Create 2 pg interfaces.
+        self.create_pg_interfaces(range(4))
+        for pg in self.pg_interfaces:
+            pg.admin_up()
+
+        # Configure IPv4 addresses on VPP pg0.
+        self.pg0.config_ip4()
+        self.pg0.resolve_arp()
+
+    def tearDown(self):
+        super(TestVxlan2, self).tearDown()
+
+    def test_xconnect(self):
+        """ VXLAN source address not local """
+
+        #
+        # test the broken configuration of a VXLAN tunnel whose
+        # source address is not local ot the box. packets sent
+        # through the tunnel should be dropped
+        #
+        t = VppVxlanTunnel(self,
+                           src="10.0.0.5",
+                           dst=self.pg0.local_ip4,
+                           vni=1000)
+        t.add_vpp_config()
+        t.admin_up()
+
+        self.vapi.sw_interface_set_l2_xconnect(t.sw_if_index,
+                                               self.pg1.sw_if_index,
+                                               enable=1)
+        self.vapi.sw_interface_set_l2_xconnect(self.pg1.sw_if_index,
+                                               t.sw_if_index,
+                                               enable=1)
+
+        p = (Ether(src="00:11:22:33:44:55",
+                   dst="00:00:00:11:22:33") /
+             IP(src="4.3.2.1", dst="1.2.3.4") /
+             UDP(sport=20000, dport=10000) /
+             Raw(b'\xa5' * 1450))
+
+        rx = self.send_and_assert_no_replies(self.pg1, [p])
+
+
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)