L2BD,ARP-TERM:fix arp query report mechanism+test 27/8427/8
authorEyal Bari <ebari@cisco.com>
Wed, 13 Sep 2017 09:29:08 +0000 (12:29 +0300)
committerJohn Lo <loj@cisco.com>
Mon, 18 Sep 2017 13:51:52 +0000 (13:51 +0000)
previous mechanism was emitting duplicates of last event, when handling multiple arp queries.
tests:
* arp events sent for graps
* duplicate suppression
* verify no events when disabled

Change-Id: I84adc23980d43b819261eccf02ec056b5cec61df
Signed-off-by: Eyal Bari <ebari@cisco.com>
src/vlibapi/api_helper_macros.h
src/vnet/ethernet/arp.c
src/vnet/ethernet/ethernet.h
src/vpp/api/api.c
test/test_l2bd_arp_term.py
test/vpp_papi_provider.py

index b8a9978..1373489 100644 (file)
@@ -215,7 +215,8 @@ _(from_netconf_server)                          \
 _(to_netconf_client)                            \
 _(from_netconf_client)                          \
 _(oam_events)                                   \
-_(bfd_events)
+_(bfd_events)                                   \
+_(wc_ip4_arp_events)
 
 typedef struct
 {
index 957706c..2f3aa6c 100644 (file)
@@ -92,6 +92,8 @@ typedef struct
 
   /* Proxy arp vector */
   ethernet_proxy_arp_t *proxy_arps;
+
+  uword wc_ip4_arp_publisher_node;
 } ethernet_arp_main_t;
 
 static ethernet_arp_main_t ethernet_arp_main;
@@ -106,6 +108,7 @@ typedef struct
 #define ETHERNET_ARP_ARGS_REMOVE (1<<0)
 #define ETHERNET_ARP_ARGS_FLUSH  (1<<1)
 #define ETHERNET_ARP_ARGS_POPULATE  (1<<2)
+#define ETHERNET_ARP_ARGS_WC_PUB  (1<<3)
 } vnet_arp_set_ip4_over_ethernet_rpc_args_t;
 
 static const u8 vrrp_prefix[] = { 0x00, 0x00, 0x5E, 0x00, 0x01 };
@@ -540,7 +543,7 @@ arp_adj_fib_add (ethernet_arp_ip4_entry_t * e, u32 fib_index)
   fib_table_lock (fib_index, FIB_PROTOCOL_IP4, FIB_SOURCE_ADJ);
 }
 
-int
+static int
 vnet_arp_set_ip4_over_ethernet_internal (vnet_main_t * vnm,
                                         vnet_arp_set_ip4_over_ethernet_rpc_args_t
                                         * args)
@@ -1507,6 +1510,49 @@ vnet_arp_populate_ip4_over_ethernet (vnet_main_t * vnm,
   return 0;
 }
 
+/**
+ * @brief publish wildcard arp event
+ * @param sw_if_index The interface on which the ARP entires are acted
+ */
+static int
+vnet_arp_wc_publish (u32 sw_if_index, void *a_arg)
+{
+  ethernet_arp_ip4_over_ethernet_address_t *a = a_arg;
+  vnet_arp_set_ip4_over_ethernet_rpc_args_t args = {
+    .flags = ETHERNET_ARP_ARGS_WC_PUB,
+    .sw_if_index = sw_if_index,
+    .a = *a
+  };
+
+  vl_api_rpc_call_main_thread (set_ip4_over_ethernet_rpc_callback,
+                              (u8 *) & args, sizeof (args));
+  return 0;
+}
+
+static void
+vnet_arp_wc_publish_internal (vnet_main_t * vnm,
+                             vnet_arp_set_ip4_over_ethernet_rpc_args_t *
+                             args)
+{
+  vlib_main_t *vm = vlib_get_main ();
+  ethernet_arp_main_t *am = &ethernet_arp_main;
+  if (am->wc_ip4_arp_publisher_node == (uword) ~ 0)
+    return;
+  wc_arp_report_t *r =
+    vlib_process_signal_event_data (vm, am->wc_ip4_arp_publisher_node, 1, 1,
+                                   sizeof *r);
+  r->ip4 = args->a.ip4.as_u32;
+  r->sw_if_index = args->sw_if_index;
+  memcpy (r->mac, args->a.ethernet, sizeof r->mac);
+}
+
+void
+wc_arp_set_publisher_node (uword node_index)
+{
+  ethernet_arp_main_t *am = &ethernet_arp_main;
+  am->wc_ip4_arp_publisher_node = node_index;
+}
+
 /*
  * arp_add_del_interface_address
  *
@@ -1652,6 +1698,7 @@ ethernet_arp_init (vlib_main_t * vm)
 
   am->pending_resolutions_by_address = hash_create (0, sizeof (uword));
   am->mac_changes_by_address = hash_create (0, sizeof (uword));
+  am->wc_ip4_arp_publisher_node = (uword) ~ 0;
 
   /* don't trace ARP error packets */
   {
@@ -1795,6 +1842,8 @@ set_ip4_over_ethernet_rpc_callback (vnet_arp_set_ip4_over_ethernet_rpc_args_t
     vnet_arp_flush_ip4_over_ethernet_internal (vm, a);
   else if (a->flags & ETHERNET_ARP_ARGS_POPULATE)
     vnet_arp_populate_ip4_over_ethernet_internal (vm, a);
+  else if (a->flags & ETHERNET_ARP_ARGS_WC_PUB)
+    vnet_arp_wc_publish_internal (vm, a);
   else
     vnet_arp_set_ip4_over_ethernet_internal (vm, a);
 }
@@ -2277,31 +2326,9 @@ arp_term_l2bd (vlib_main_t * vm,
 
          /* Check if anyone want ARP request events for L2 BDs */
          {
-           pending_resolution_t *mc;
            ethernet_arp_main_t *am = &ethernet_arp_main;
-           uword *p = hash_get (am->mac_changes_by_address, 0);
-           if (p)
-             {
-               u32 next_index = p[0];
-               while (next_index != (u32) ~ 0)
-                 {
-                   int (*fp) (u32, u8 *, u32, u32);
-                   int rv = 1;
-                   mc = pool_elt_at_index (am->mac_changes, next_index);
-                   fp = mc->data_callback;
-                   /* Call the callback, return 1 to suppress dup events */
-                   if (fp)
-                     rv = (*fp) (mc->data,
-                                 arp0->ip4_over_ethernet[0].ethernet,
-                                 sw_if_index0,
-                                 arp0->ip4_over_ethernet[0].ip4.as_u32);
-                   /* Signal the resolver process */
-                   if (rv == 0)
-                     vlib_process_signal_event (vm, mc->node_index,
-                                                mc->type_opaque, mc->data);
-                   next_index = mc->next_index;
-                 }
-             }
+           if (am->wc_ip4_arp_publisher_node != (uword) ~ 0)
+             vnet_arp_wc_publish (sw_if_index0, &arp0->ip4_over_ethernet[0]);
          }
 
          /* lookup BD mac_by_ip4 hash table for MAC entry */
index 9ca256c..1a9e979 100644 (file)
@@ -543,6 +543,8 @@ int vnet_add_del_ip4_arp_change_event (vnet_main_t * vnm,
                                       uword type_opaque,
                                       uword data, int is_add);
 
+void wc_arp_set_publisher_node (uword inode_index);
+
 void ethernet_arp_change_mac (u32 sw_if_index);
 void ethernet_ndp_change_mac (u32 sw_if_index);
 
@@ -557,6 +559,13 @@ const u8 *ethernet_ip6_mcast_dst_addr (void);
 
 extern vlib_node_registration_t ethernet_input_node;
 
+typedef struct
+{
+  u32 sw_if_index;
+  u32 ip4;
+  u8 mac[6];
+} wc_arp_report_t;
+
 #endif /* included_ethernet_h */
 
 /*
index 044ddb5..e229a5e 100644 (file)
@@ -299,17 +299,17 @@ static uword
 resolver_process (vlib_main_t * vm,
                  vlib_node_runtime_t * rt, vlib_frame_t * f)
 {
-  uword event_type;
-  uword *event_data = 0;
-  f64 timeout = 100.0;
-  int i;
+  volatile f64 timeout = 100.0;
+  volatile uword *event_data = 0;
 
   while (1)
     {
       vlib_process_wait_for_event_or_clock (vm, timeout);
 
-      event_type = vlib_process_get_events (vm, &event_data);
+      uword event_type =
+       vlib_process_get_events (vm, (uword **) & event_data);
 
+      int i;
       switch (event_type)
        {
        case RESOLUTION_PENDING_EVENT:
@@ -1352,26 +1352,15 @@ arp_change_data_callback (u32 pool_index, u8 * new_mac,
     return 1;
 
   event = pool_elt_at_index (am->arp_events, pool_index);
-  /* *INDENT-OFF* */
-  if (memcmp (&event->new_mac, new_mac, sizeof (event->new_mac)))
+  if (eth_mac_equal (event->new_mac, new_mac) &&
+      sw_if_index == ntohl (event->sw_if_index))
     {
-      clib_memcpy (event->new_mac, new_mac, sizeof (event->new_mac));
+      return 1;
     }
-  else
-    {                          /* same mac */
-      if (sw_if_index == ntohl(event->sw_if_index) &&
-         (!event->mac_ip ||
-          /* for BD case, also check IP address with 10 sec timeout */
-          (address == event->address &&
-           (now - arp_event_last_time) < 10.0)))
-       return 1;
-    }
-  /* *INDENT-ON* */
 
-  arp_event_last_time = now;
+  clib_memcpy (event->new_mac, new_mac, sizeof (event->new_mac));
   event->sw_if_index = htonl (sw_if_index);
-  if (event->mac_ip)
-    event->address = address;
+  arp_event_last_time = now;
   return 0;
 }
 
@@ -1391,7 +1380,7 @@ nd_change_data_callback (u32 pool_index, u8 * new_mac,
   event = pool_elt_at_index (am->nd_events, pool_index);
 
   /* *INDENT-OFF* */
-  if (memcmp (&event->new_mac, new_mac, sizeof (event->new_mac)))
+  if (memcmp (event->new_mac, new_mac, sizeof (event->new_mac)))
     {
       clib_memcpy (event->new_mac, new_mac, sizeof (event->new_mac));
     }
@@ -1438,13 +1427,123 @@ nd_change_delete_callback (u32 pool_index, u8 * notused)
   return 0;
 }
 
+static vlib_node_registration_t wc_ip4_arp_process_node;
+
+static uword
+wc_ip4_arp_process (vlib_main_t * vm,
+                   vlib_node_runtime_t * rt, vlib_frame_t * f)
+{
+  /* These cross the longjmp  boundry (vlib_process_wait_for_event)
+   * and need to be volatile - to prevent them from being optimized into
+   * a register - which could change during suspension */
+
+  volatile wc_arp_report_t prev = { 0 };
+  volatile f64 last = vlib_time_now (vm);
+
+  while (1)
+    {
+      vlib_process_wait_for_event (vm);
+      uword event_type;
+      wc_arp_report_t *event_data =
+       vlib_process_get_event_data (vm, &event_type);
+
+      f64 now = vlib_time_now (vm);
+      int i;
+      for (i = 0; i < vec_len (event_data); i++)
+       {
+         /* discard dup event */
+         if (prev.ip4 == event_data[i].ip4 &&
+             eth_mac_equal ((u8 *) prev.mac, event_data[i].mac) &&
+             prev.sw_if_index == event_data[i].sw_if_index &&
+             (now - last) < 10.0)
+           {
+             continue;
+           }
+         prev = event_data[i];
+         last = now;
+         vpe_client_registration_t *reg;
+          /* *INDENT-OFF* */
+          pool_foreach(reg, vpe_api_main.wc_ip4_arp_events_registrations,
+          ({
+           unix_shared_memory_queue_t *q;
+            q = vl_api_client_index_to_input_queue (reg->client_index);
+           if (q && q->cursize < q->maxsize)
+             {
+               vl_api_ip4_arp_event_t * event = vl_msg_api_alloc (sizeof *event);
+               memset (event, 0, sizeof *event);
+               event->_vl_msg_id = htons (VL_API_IP4_ARP_EVENT);
+               event->client_index = reg->client_index;
+               event->pid = reg->client_pid;
+               event->mac_ip = 1;
+               event->address = event_data[i].ip4;
+               event->sw_if_index = htonl(event_data[i].sw_if_index);
+               memcpy(event->new_mac, event_data[i].mac, sizeof event->new_mac);
+               vl_msg_api_send_shmem (q, (u8 *) &event);
+             }
+          }));
+          /* *INDENT-ON* */
+       }
+      vlib_process_put_event_data (vm, event_data);
+    }
+
+  return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (wc_ip4_arp_process_node,static) = {
+  .function = wc_ip4_arp_process,
+  .type = VLIB_NODE_TYPE_PROCESS,
+  .name = "wildcard-ip4-arp-publisher-process",
+};
+/* *INDENT-ON* */
+
 static void
 vl_api_want_ip4_arp_events_t_handler (vl_api_want_ip4_arp_events_t * mp)
 {
   vpe_api_main_t *am = &vpe_api_main;
   vnet_main_t *vnm = vnet_get_main ();
   vl_api_want_ip4_arp_events_reply_t *rmp;
-  int rv;
+  int rv = 0;
+
+  if (mp->address == 0)
+    {
+      uword *p =
+       hash_get (am->wc_ip4_arp_events_registration_hash, mp->client_index);
+      vpe_client_registration_t *rp;
+      if (p)
+       {
+         if (mp->enable_disable)
+           {
+             clib_warning ("pid %d: already enabled...", mp->pid);
+             rv = VNET_API_ERROR_INVALID_REGISTRATION;
+             goto reply;
+           }
+         else
+           {
+             rp =
+               pool_elt_at_index (am->wc_ip4_arp_events_registrations, p[0]);
+             pool_put (am->wc_ip4_arp_events_registrations, rp);
+             hash_unset (am->wc_ip4_arp_events_registration_hash,
+                         mp->client_index);
+             if (pool_elts (am->wc_ip4_arp_events_registrations) == 0)
+               wc_arp_set_publisher_node (~0);
+             goto reply;
+           }
+       }
+      if (mp->enable_disable == 0)
+       {
+         clib_warning ("pid %d: already disabled...", mp->pid);
+         rv = VNET_API_ERROR_INVALID_REGISTRATION;
+         goto reply;
+       }
+      pool_get (am->wc_ip4_arp_events_registrations, rp);
+      rp->client_index = mp->client_index;
+      rp->client_pid = mp->pid;
+      hash_set (am->wc_ip4_arp_events_registration_hash, rp->client_index,
+               rp - am->wc_ip4_arp_events_registrations);
+      wc_arp_set_publisher_node (wc_ip4_arp_process_node.index);
+      goto reply;
+    }
 
   if (mp->enable_disable)
     {
@@ -1459,12 +1558,12 @@ vl_api_want_ip4_arp_events_t_handler (vl_api_want_ip4_arp_events_t * mp)
       if (rv)
        {
          pool_put (am->arp_events, event);
-         goto out;
+         goto reply;
        }
       memset (event, 0, sizeof (*event));
 
       /* Python API expects events to have no context */
-      event->_vl_msg_id = ntohs (VL_API_IP4_ARP_EVENT);
+      event->_vl_msg_id = htons (VL_API_IP4_ARP_EVENT);
       event->client_index = mp->client_index;
       event->address = mp->address;
       event->pid = mp->pid;
@@ -1479,7 +1578,7 @@ vl_api_want_ip4_arp_events_t_handler (vl_api_want_ip4_arp_events_t * mp)
         vpe_resolver_process_node.index,
         IP4_ARP_EVENT, ~0 /* pool index */ , 0 /* is_add */ );
     }
-out:
+reply:
   REPLY_MACRO (VL_API_WANT_IP4_ARP_EVENTS_REPLY);
 }
 
@@ -2072,13 +2171,10 @@ vpe_api_init (vlib_main_t * vm)
 
   am->vlib_main = vm;
   am->vnet_main = vnet_get_main ();
-  am->interface_events_registration_hash = hash_create (0, sizeof (uword));
-  am->to_netconf_server_registration_hash = hash_create (0, sizeof (uword));
-  am->from_netconf_server_registration_hash = hash_create (0, sizeof (uword));
-  am->to_netconf_client_registration_hash = hash_create (0, sizeof (uword));
-  am->from_netconf_client_registration_hash = hash_create (0, sizeof (uword));
-  am->oam_events_registration_hash = hash_create (0, sizeof (uword));
-  am->bfd_events_registration_hash = hash_create (0, sizeof (uword));
+#define _(a)                                                    \
+  am->a##_registration_hash = hash_create (0, sizeof (uword));
+  foreach_registration_hash;
+#undef _
 
   vl_set_memory_region_name ("/vpe-api");
   vl_enable_disable_memory_api (vm, 1 /* enable it */ );
@@ -2219,10 +2315,7 @@ format_arp_event (u8 * s, va_list * args)
   vl_api_ip4_arp_event_t *event = va_arg (*args, vl_api_ip4_arp_event_t *);
 
   s = format (s, "pid %d: ", ntohl (event->pid));
-  if (event->mac_ip)
-    s = format (s, "bd mac/ip4 binding events");
-  else
-    s = format (s, "resolution for %U", format_ip4_address, &event->address);
+  s = format (s, "resolution for %U", format_ip4_address, &event->address);
   return s;
 }
 
@@ -2259,6 +2352,13 @@ show_ip_arp_nd_events_fn (vlib_main_t * vm,
     vlib_cli_output (vm, "%U", format_arp_event, arp_event);
   }));
 
+  vpe_client_registration_t *reg;
+  pool_foreach(reg, am->wc_ip4_arp_events_registrations,
+  ({
+    vlib_cli_output (vm, "pid %d: bd mac/ip4 binding events",
+                     ntohl (reg->client_pid));
+  }));
+
   pool_foreach (nd_event, am->nd_events,
   ({
     vlib_cli_output (vm, "%U", format_nd_event, nd_event);
index 80a7ff8..20885f8 100644 (file)
@@ -5,7 +5,7 @@ import unittest
 import random
 import copy
 
-from socket import AF_INET6
+from socket import AF_INET, AF_INET6
 
 from scapy.packet import Raw
 from scapy.layers.l2 import Ether, ARP
@@ -117,7 +117,7 @@ class TestL2bdArpTerm(VppTestCase):
             self.vapi.bridge_domain_add_del(bd_id=bd_id, is_add=is_add)
 
     @classmethod
-    def arp(cls, src_host, host):
+    def arp_req(cls, src_host, host):
         return (Ether(dst="ff:ff:ff:ff:ff:ff", src=src_host.mac) /
                 ARP(op="who-has",
                     hwsrc=src_host.bin_mac,
@@ -126,7 +126,15 @@ class TestL2bdArpTerm(VppTestCase):
 
     @classmethod
     def arp_reqs(cls, src_host, entries):
-        return [cls.arp(src_host, e) for e in entries]
+        return [cls.arp_req(src_host, e) for e in entries]
+
+    @classmethod
+    def garp_req(cls, host):
+        return cls.arp_req(host, host)
+
+    @classmethod
+    def garp_reqs(cls, entries):
+        return [cls.garp_req(e) for e in entries]
 
     def arp_resp_host(self, src_host, arp_resp):
         ether = arp_resp[Ether]
@@ -146,6 +154,20 @@ class TestL2bdArpTerm(VppTestCase):
     def arp_resp_hosts(self, src_host, pkts):
         return {self.arp_resp_host(src_host, p) for p in pkts}
 
+    def inttoip4(self, ip):
+        o1 = int(ip / 16777216) % 256
+        o2 = int(ip / 65536) % 256
+        o3 = int(ip / 256) % 256
+        o4 = int(ip) % 256
+        return '%(o1)s.%(o2)s.%(o3)s.%(o4)s' % locals()
+
+    def arp_event_host(self, e):
+        return Host(mac=':'.join(['%02x' % ord(char) for char in e.new_mac]),
+                    ip4=self.inttoip4(e.address))
+
+    def arp_event_hosts(self, evs):
+        return {self.arp_event_host(e) for e in evs}
+
     @classmethod
     def ns_req(cls, src_host, host):
         nsma = in6_getnsma(inet_pton(AF_INET6, "fd10::ffff"))
@@ -347,6 +369,60 @@ class TestL2bdArpTerm(VppTestCase):
         self.verify_nd(src_host, hosts, updated)
         self.bd_add_del(1, is_add=0)
 
+    def test_l2bd_arp_term_09(self):
+        """ L2BD arp term - send garps, verify arp event reports
+        """
+        self.vapi.want_ip4_arp_events()
+        self.bd_add_del(1, is_add=1)
+        self.set_bd_flags(1, arp_term=True, flood=False,
+                          uu_flood=False, learn=False)
+        macs = self.mac_list(range(90, 95))
+        hosts = self.ip4_hosts(5, 1, macs)
+
+        garps = self.garp_reqs(hosts)
+        self.bd_swifs(1)[0].add_stream(garps)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        evs = [self.vapi.wait_for_event(1, "ip4_arp_event")
+               for i in range(len(hosts))]
+        ev_hosts = self.arp_event_hosts(evs)
+        self.assertEqual(len(ev_hosts ^ hosts), 0)
+
+    def test_l2bd_arp_term_10(self):
+        """ L2BD arp term - send duplicate garps, verify suppression
+        """
+        macs = self.mac_list(range(70, 71))
+        hosts = self.ip4_hosts(6, 1, macs)
+
+        """ send the packet 5 times expect one event
+        """
+        garps = self.garp_reqs(hosts) * 5
+        self.bd_swifs(1)[0].add_stream(garps)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        evs = [self.vapi.wait_for_event(1, "ip4_arp_event")
+               for i in range(len(hosts))]
+        ev_hosts = self.arp_event_hosts(evs)
+        self.assertEqual(len(ev_hosts ^ hosts), 0)
+
+    def test_l2bd_arp_term_11(self):
+        """ L2BD arp term - disable ip4 arp events,send garps, verify no events
+        """
+        self.vapi.want_ip4_arp_events(enable_disable=0)
+        macs = self.mac_list(range(90, 95))
+        hosts = self.ip4_hosts(5, 1, macs)
+
+        garps = self.garp_reqs(hosts)
+        self.bd_swifs(1)[0].add_stream(garps)
+
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.sleep(1)
+        self.assertEqual(len(self.vapi.collect_events()), 0)
+        self.bd_add_del(1, is_add=0)
+
 
 if __name__ == '__main__':
     unittest.main(testRunner=VppTestRunner)
index b63a265..fcc6784 100644 (file)
@@ -101,11 +101,11 @@ class VppPapiProvider(object):
     def wait_for_event(self, timeout, name=None):
         """ Wait for and return next event. """
         if name:
-            self.test_class.logger.debug("Expecting event within %ss",
-                                         timeout)
-        else:
             self.test_class.logger.debug("Expecting event '%s' within %ss",
                                          name, timeout)
+        else:
+            self.test_class.logger.debug("Expecting event within %ss",
+                                         timeout)
         if self._events:
             self.test_class.logger.debug("Not waiting, event already queued")
         limit = time.time() + timeout
@@ -419,6 +419,12 @@ class VppPapiProvider(object):
                          'ip_address': ip,
                          'mac_address': mac})
 
+    def want_ip4_arp_events(self, enable_disable=1, address=0):
+        return self.api(self.papi.want_ip4_arp_events,
+                        {'enable_disable': enable_disable,
+                         'address': address,
+                         'pid': os.getpid(), })
+
     def l2fib_add_del(self, mac, bd_id, sw_if_index, is_add=1, static_mac=0,
                       filter_mac=0, bvi_mac=0):
         """Create/delete L2 FIB entry.