Gratuitous ARP packet handling 56/12956/3
authorNeale Ranns <neale.ranns@cisco.com>
Fri, 8 Jun 2018 01:09:49 +0000 (18:09 -0700)
committerOle Trøan <otroan@employees.org>
Fri, 8 Jun 2018 08:52:29 +0000 (08:52 +0000)
only learn from a GARP packet if it is an update to an existing entry.

Change-Id: I4c1b59cfedb911466e5e4c9756cf53a6676e1909
Signed-off-by: Neale Ranns <neale.ranns@cisco.com>
src/vnet/ethernet/arp.c
test/test_neighbor.py
test/vpp_neighbor.py

index 682bc2a..37e3e88 100644 (file)
@@ -864,6 +864,7 @@ typedef enum
   _ (l3_type_not_ip4, "L3 type not IP4")                               \
   _ (l3_src_address_not_local, "IP4 source address not local to subnet") \
   _ (l3_dst_address_not_local, "IP4 destination address not local to subnet") \
+  _ (l3_dst_address_unset, "IP4 destination address is unset")          \
   _ (l3_src_address_is_local, "IP4 source address matches local interface") \
   _ (l3_src_address_learned, "ARP request IP4 source address learned")  \
   _ (replies_received, "ARP replies received")                         \
@@ -980,6 +981,9 @@ arp_input (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame)
            (arp0->l3_type !=
             clib_net_to_host_u16 (ETHERNET_TYPE_IP4) ?
             ETHERNET_ARP_ERROR_l3_type_not_ip4 : error0);
+         error0 =
+           (0 == arp0->ip4_over_ethernet[0].ip4.as_u32 ?
+            ETHERNET_ARP_ERROR_l3_dst_address_unset : error0);
 
          sw_if_index0 = vnet_buffer (p0)->sw_if_index[VLIB_RX];
 
@@ -1110,7 +1114,23 @@ arp_input (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame)
              }
          }
 
-         if (!(FIB_ENTRY_FLAG_CONNECTED & dst_flags))
+         if (fib_entry_is_sourced (dst_fei, FIB_SOURCE_ADJ))
+           {
+             /*
+              * We matched an adj-fib on ths source subnet (a /32 previously
+              * added as a result of ARP). If this request is a gratuitous
+              * ARP, then learn from it.
+              * The check for matching an adj-fib, is to prevent hosts
+              * from spamming us with gratuitous ARPS that might otherwise
+              * blow our ARP cache
+              */
+             if (arp0->ip4_over_ethernet[0].ip4.as_u32 ==
+                 arp0->ip4_over_ethernet[1].ip4.as_u32)
+               error0 = arp_learn (vnm, am, sw_if_index0,
+                                   &arp0->ip4_over_ethernet[0]);
+             goto drop2;
+           }
+         else if (!(FIB_ENTRY_FLAG_CONNECTED & dst_flags))
            {
              error0 = ETHERNET_ARP_ERROR_l3_dst_address_not_local;
              goto drop1;
@@ -1152,11 +1172,17 @@ arp_input (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame)
          /* Learn or update sender's mapping only for replies to addresses
           * that are local to the subnet */
          if (arp0->opcode ==
-             clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_reply) &&
-             dst_is_local0)
+             clib_host_to_net_u16 (ETHERNET_ARP_OPCODE_reply))
            {
-             error0 = arp_learn (vnm, am, sw_if_index0,
-                                 &arp0->ip4_over_ethernet[0]);
+             if (dst_is_local0)
+               error0 = arp_learn (vnm, am, sw_if_index0,
+                                   &arp0->ip4_over_ethernet[0]);
+             else
+               /* a reply for a non-local destination could be a GARP.
+                * GARPs for hosts we know were handled above, so this one
+                * we drop */
+               error0 = ETHERNET_ARP_ERROR_l3_dst_address_not_local;
+
              goto drop1;
            }
          else if (arp0->opcode ==
@@ -1227,9 +1253,8 @@ arp_input (vlib_main_t * vm, vlib_node_runtime_t * node, vlib_frame_t * frame)
          continue;
 
        drop1:
-         if (0 == arp0->ip4_over_ethernet[0].ip4.as_u32 ||
-             (arp0->ip4_over_ethernet[0].ip4.as_u32 ==
-              arp0->ip4_over_ethernet[1].ip4.as_u32))
+         if (arp0->ip4_over_ethernet[0].ip4.as_u32 ==
+             arp0->ip4_over_ethernet[1].ip4.as_u32)
            {
              error0 = ETHERNET_ARP_ERROR_gratuitous_arp;
              goto drop2;
index 7798fdd..c161eb8 100644 (file)
@@ -1214,6 +1214,104 @@ class ARPTestCase(VppTestCase):
         self.vapi.sw_interface_set_mac_address(self.pg2.sw_if_index,
                                                mac_string)
 
+    def test_garp(self):
+        """ GARP """
+
+        #
+        # Generate some hosts on the LAN
+        #
+        self.pg1.generate_remote_hosts(4)
+
+        #
+        # And an ARP entry
+        #
+        arp = VppNeighbor(self,
+                          self.pg1.sw_if_index,
+                          self.pg1.remote_hosts[1].mac,
+                          self.pg1.remote_hosts[1].ip4)
+        arp.add_vpp_config()
+
+        self.assertTrue(find_nbr(self,
+                                 self.pg1.sw_if_index,
+                                 self.pg1.remote_hosts[1].ip4,
+                                 mac=self.pg1.remote_hosts[1].mac))
+
+        #
+        # Send a GARP (request) to swap the host 1's address to that of host 2
+        #
+        p1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
+                    src=self.pg1.remote_hosts[2].mac) /
+              ARP(op="who-has",
+                  hwdst=self.pg1.local_mac,
+                  hwsrc=self.pg1.remote_hosts[2].mac,
+                  pdst=self.pg1.remote_hosts[1].ip4,
+                  psrc=self.pg1.remote_hosts[1].ip4))
+
+        self.pg1.add_stream(p1)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        self.assertTrue(find_nbr(self,
+                                 self.pg1.sw_if_index,
+                                 self.pg1.remote_hosts[1].ip4,
+                                 mac=self.pg1.remote_hosts[2].mac))
+
+        #
+        # Send a GARP (reply) to swap the host 1's address to that of host 3
+        #
+        p1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
+                    src=self.pg1.remote_hosts[3].mac) /
+              ARP(op="is-at",
+                  hwdst=self.pg1.local_mac,
+                  hwsrc=self.pg1.remote_hosts[3].mac,
+                  pdst=self.pg1.remote_hosts[1].ip4,
+                  psrc=self.pg1.remote_hosts[1].ip4))
+
+        self.pg1.add_stream(p1)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        self.assertTrue(find_nbr(self,
+                                 self.pg1.sw_if_index,
+                                 self.pg1.remote_hosts[1].ip4,
+                                 mac=self.pg1.remote_hosts[3].mac))
+
+        #
+        # GARPs (requets nor replies) for host we don't know yet
+        # don't result in new neighbour entries
+        #
+        p1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
+                    src=self.pg1.remote_hosts[3].mac) /
+              ARP(op="who-has",
+                  hwdst=self.pg1.local_mac,
+                  hwsrc=self.pg1.remote_hosts[3].mac,
+                  pdst=self.pg1.remote_hosts[2].ip4,
+                  psrc=self.pg1.remote_hosts[2].ip4))
+
+        self.pg1.add_stream(p1)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        self.assertFalse(find_nbr(self,
+                                  self.pg1.sw_if_index,
+                                  self.pg1.remote_hosts[2].ip4))
+
+        p1 = (Ether(dst="ff:ff:ff:ff:ff:ff",
+                    src=self.pg1.remote_hosts[3].mac) /
+              ARP(op="is-at",
+                  hwdst=self.pg1.local_mac,
+                  hwsrc=self.pg1.remote_hosts[3].mac,
+                  pdst=self.pg1.remote_hosts[2].ip4,
+                  psrc=self.pg1.remote_hosts[2].ip4))
+
+        self.pg1.add_stream(p1)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+
+        self.assertFalse(find_nbr(self,
+                                  self.pg1.sw_if_index,
+                                  self.pg1.remote_hosts[2].ip4))
+
 
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)
index e8ba3b2..c08132d 100644 (file)
@@ -9,7 +9,7 @@ from vpp_object import *
 from util import mactobinary
 
 
-def find_nbr(test, sw_if_index, ip_addr, is_static=0, inet=AF_INET):
+def find_nbr(test, sw_if_index, ip_addr, is_static=0, inet=AF_INET, mac=None):
     nbrs = test.vapi.ip_neighbor_dump(sw_if_index,
                                       is_ipv6=1 if AF_INET6 == inet else 0)
     if inet == AF_INET:
@@ -21,7 +21,11 @@ def find_nbr(test, sw_if_index, ip_addr, is_static=0, inet=AF_INET):
     for n in nbrs:
         if nbr_addr == n.ip_address[:s] \
            and is_static == n.is_static:
-            return True
+            if mac:
+                if n.mac_address == mactobinary(mac):
+                    return True
+            else:
+                return True
     return False