VPP-719: Accept ARP replies from VRRP hw addr 34/6734/3
authorMatthew Smith <mgsmith@netgate.com>
Wed, 17 May 2017 02:35:56 +0000 (21:35 -0500)
committerDamjan Marion <dmarion.lists@gmail.com>
Wed, 17 May 2017 13:43:17 +0000 (13:43 +0000)
Check whether an ARP src hw addr starts with 00:00:5e:00:01
before rejecting due to a mismatch between ARP src hw addr
and ethernet frame src addr.

Change-Id: Ia3ecd5d6dba34876aca8d90bc622a0a1397e48fb
Signed-off-by: Matthew Smith <mgsmith@netgate.com>
src/vnet/ethernet/arp.c
test/test_neighbor.py

index bfcd357..f44cb59 100644 (file)
@@ -107,6 +107,8 @@ typedef struct
 #define ETHERNET_ARP_ARGS_POPULATE  (1<<2)
 } vnet_arp_set_ip4_over_ethernet_rpc_args_t;
 
+static const u8 vrrp_prefix[] = { 0x00, 0x00, 0x5E, 0x00, 0x01 };
+
 static void
 set_ip4_over_ethernet_rpc_callback (vnet_arp_set_ip4_over_ethernet_rpc_args_t
                                    * a);
@@ -991,7 +993,7 @@ arp_input (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame)
          ethernet_header_t *eth0;
          ip4_address_t *if_addr0, proxy_src;
          u32 pi0, error0, next0, sw_if_index0, conn_sw_if_index0, fib_index0;
-         u8 is_request0, dst_is_local0, is_unnum0;
+         u8 is_request0, dst_is_local0, is_unnum0, is_vrrp_reply0;
          ethernet_proxy_arp_t *pa;
          fib_node_index_t dst_fei, src_fei;
          fib_prefix_t pfx0;
@@ -1097,10 +1099,19 @@ arp_input (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame)
          /* Fill in ethernet header. */
          eth0 = ethernet_buffer_get_header (p0);
 
+         is_vrrp_reply0 =
+           ((arp0->opcode ==
+             clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_reply))
+            &&
+            (!memcmp
+             (arp0->ip4_over_ethernet[0].ethernet, vrrp_prefix,
+              sizeof (vrrp_prefix))));
+
          /* Trash ARP packets whose ARP-level source addresses do not
-            match their L2-frame-level source addresses */
+            match their L2-frame-level source addresses, unless it's
+            a reply from a VRRP virtual router */
          if (memcmp (eth0->src_address, arp0->ip4_over_ethernet[0].ethernet,
-                     sizeof (eth0->src_address)))
+                     sizeof (eth0->src_address)) && !is_vrrp_reply0)
            {
              error0 = ETHERNET_ARP_ERROR_l2_address_mismatch;
              goto drop2;
@@ -2170,6 +2181,7 @@ arp_term_l2bd (vlib_main_t * vm,
          u16 bd_index0;
          u32 ip0;
          u8 *macp0;
+         u8 is_vrrp_reply0;
 
          pi0 = from[0];
          to_next[0] = pi0;
@@ -2218,12 +2230,20 @@ arp_term_l2bd (vlib_main_t * vm,
          if (error0)
            goto drop;
 
+         is_vrrp_reply0 =
+           ((arp0->opcode ==
+             clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_reply))
+            &&
+            (!memcmp
+             (arp0->ip4_over_ethernet[0].ethernet, vrrp_prefix,
+              sizeof (vrrp_prefix))));
+
          /* Trash ARP packets whose ARP-level source addresses do not
-            match their L2-frame-level source addresses  */
+            match their L2-frame-level source addresses, unless it's
+            a reply from a VRRP virtual router */
          if (PREDICT_FALSE
-             (memcmp
-              (eth0->src_address, arp0->ip4_over_ethernet[0].ethernet,
-               sizeof (eth0->src_address))))
+             (memcmp (eth0->src_address, arp0->ip4_over_ethernet[0].ethernet,
+                      sizeof (eth0->src_address)) && !is_vrrp_reply0))
            {
              error0 = ETHERNET_ARP_ERROR_l2_address_mismatch;
              goto drop;
index 48c6a29..67d64a2 100644 (file)
@@ -87,6 +87,24 @@ class ARPTestCase(VppTestCase):
         self.assertEqual(arp.psrc, sip)
         self.assertEqual(arp.pdst, dip)
 
+    def verify_arp_vrrp_resp(self, rx, smac, dmac, sip, dip):
+        ether = rx[Ether]
+        self.assertEqual(ether.dst, dmac)
+        self.assertEqual(ether.src, smac)
+
+        arp = rx[ARP]
+        self.assertEqual(arp.hwtype, 1)
+        self.assertEqual(arp.ptype, 0x800)
+        self.assertEqual(arp.hwlen, 6)
+        self.assertEqual(arp.plen, 4)
+        self.assertEqual(arp.op, arp_opts["is-at"])
+        self.assertNotEqual(arp.hwsrc, smac)
+        self.assertTrue("00:00:5e:00:01" in arp.hwsrc or
+                        "00:00:5E:00:01" in arp.hwsrc)
+        self.assertEqual(arp.hwdst, dmac)
+        self.assertEqual(arp.psrc, sip)
+        self.assertEqual(arp.pdst, dip)
+
     def verify_ip(self, rx, smac, dmac, sip, dip):
         ether = rx[Ether]
         self.assertEqual(ether.dst, dmac)
@@ -737,5 +755,62 @@ class ARPTestCase(VppTestCase):
                               "10.0.0.1")
         self.pg2.unconfig_ip4()
 
+    def test_arp_vrrp(self):
+        """ ARP reply with VRRP virtual src hw addr """
+
+        #
+        # IP packet destined for pg1 remote host arrives on pg0 resulting
+        # in an ARP request for the address of the remote host on pg1
+        #
+        p0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) /
+              IP(src=self.pg0.remote_ip4, dst=self.pg1.remote_ip4) /
+              UDP(sport=1234, dport=1234) /
+              Raw())
+
+        self.pg0.add_stream(p0)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx1 = self.pg1.get_capture(1)
+
+        self.verify_arp_req(rx1[0],
+                            self.pg1.local_mac,
+                            self.pg1.local_ip4,
+                            self.pg1.remote_ip4)
+
+        #
+        # ARP reply for address of pg1 remote host arrives on pg1 with
+        # the hw src addr set to a value in the VRRP IPv4 range of
+        # MAC addresses
+        #
+        p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+              ARP(op="is-at", hwdst=self.pg1.local_mac,
+                  hwsrc="00:00:5e:00:01:09", pdst=self.pg1.local_ip4,
+                  psrc=self.pg1.remote_ip4))
+
+        self.pg1.add_stream(p1)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        #
+        # IP packet destined for pg1 remote host arrives on pg0 again.
+        # VPP should have an ARP entry for that address now and the packet
+        # should be sent out pg1.
+        #
+        self.pg0.add_stream(p0)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        rx1 = self.pg1.get_capture(1)
+
+        self.verify_ip(rx1[0],
+                       self.pg1.local_mac,
+                       "00:00:5e:00:01:09",
+                       self.pg0.remote_ip4,
+                       self.pg1.remote_ip4)
+
+        self.pg1.admin_down()
+        self.pg1.admin_up()
+
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)