IPv6 ND Router discovery data plane (VPP-1095)

Add API call to send Router Solicitation messages.
Save info from incoming Router Advertisement messages and notify listeners.

Change-Id: Ie518b5492231e03291bd4c4280be4727bfecab46
Signed-off-by: Juraj Sloboda <jsloboda@cisco.com>
diff --git a/src/vlibapi/api_helper_macros.h b/src/vlibapi/api_helper_macros.h
index 2334042..de3c09b 100644
--- a/src/vlibapi/api_helper_macros.h
+++ b/src/vlibapi/api_helper_macros.h
@@ -218,7 +218,8 @@
 _(oam_events)                                   \
 _(bfd_events)                                   \
 _(wc_ip6_nd_events)                             \
-_(wc_ip4_arp_events)
+_(wc_ip4_arp_events)                            \
+_(ip6_ra_events)
 
 typedef struct
 {
diff --git a/src/vnet/ip/ip.api b/src/vnet/ip/ip.api
index 282f531..d6c5f6b 100644
--- a/src/vnet/ip/ip.api
+++ b/src/vnet/ip/ip.api
@@ -19,7 +19,7 @@
     called through a shared memory interface. 
 */
 
-option version = "1.1.0";
+option version = "1.2.0";
 import "vnet/fib/fib_types.api";
 
 /** \brief Add / del table request
@@ -304,6 +304,30 @@
   u32 context;
 };
 
+/** \brief Start / stop sending router solicitation
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param irt - initial retransmission time
+    @param mrt - maximum retransmission time
+    @param mrc - maximum retransmission count
+    @param mrd - maximum retransmission duration
+    @param sw_if_index - software interface index of interface
+                         for sending router solicitation
+    @param stop - if non-zero then stop sending router solicitation,
+                  otherwise start sending router solicitation
+*/
+autoreply define ip6nd_send_router_solicitation
+{
+  u32 client_index;
+  u32 context;
+  u32 irt;
+  u32 mrt;
+  u32 mrc;
+  u32 mrd;
+  u32 sw_if_index;
+  u8 stop;
+};
+
 /** \brief IPv6 interface enable / disable request
     @param client_index - opaque cookie to identify the sender
     @param context - sender context, to match reply w/ request
@@ -700,6 +724,68 @@
 };
 
 service {
+  rpc want_ip6_ra_events returns want_ip6_ra_events_reply
+    events ip6_ra_event;
+};
+
+/** \brief Register for ip6 router advertisement events
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param enable_disable - 1 => register for events, 0 => cancel registration
+    @param pid - sender's pid
+*/
+autoreply define want_ip6_ra_events
+{
+  u32 client_index;
+  u32 context;
+  u8 enable_disable;
+  u32 pid;
+};
+
+/** \brief Struct representing RA prefix info
+    @param dst_address - RA prefix info destination address
+    @param dst_address_length - RA prefix info destination address length
+    @param flags - RA prefix info flags
+    @param valid_time - RA prefix info valid time
+    @param preferred_time - RA prefix info preferred time
+*/
+typeonly define ip6_ra_prefix_info
+{
+  u8 dst_address[16];
+  u8 dst_address_length;
+  u8 flags;
+  u32 valid_time;
+  u32 preferred_time;
+};
+
+/** \brief Tell client about a router advertisement event
+    @param client_index - opaque cookie to identify the sender
+    @param pid - client pid registered to receive notification
+    @param current_hop_limit - RA current hop limit
+    @param flags - RA flags
+    @param router_lifetime_in_sec - RA lifetime in seconds
+    @param neighbor_reachable_time_in_msec - RA neighbor reachable time in msec
+    @param time_in_msec_between_retransmitted_neighbor_solicitations -
+               time in msec between retransmitted neighbor solicitations
+    @param n_prefixes -
+    @param prefixes -
+*/
+define ip6_ra_event
+{
+  u32 client_index;
+  u32 pid;
+  u32 sw_if_index;
+  u8 router_address[16];
+  u8 current_hop_limit;
+  u8 flags;
+  u16 router_lifetime_in_sec;
+  u32 neighbor_reachable_time_in_msec;
+  u32 time_in_msec_between_retransmitted_neighbor_solicitations;
+  u32 n_prefixes;
+  vl_api_ip6_ra_prefix_info_t prefixes[n_prefixes];
+};
+
+service {
   rpc want_ip6_nd_events returns want_ip6_nd_events_reply
     events ip6_nd_event;
 };
diff --git a/src/vnet/ip/ip6_neighbor.c b/src/vnet/ip/ip6_neighbor.c
index c011ec5..0df29c6 100644
--- a/src/vnet/ip/ip6_neighbor.c
+++ b/src/vnet/ip/ip6_neighbor.c
@@ -154,6 +154,15 @@
 
   /* Link local address to use (defaults to underlying physical for logical interfaces */
   ip6_address_t link_local_address;
+
+  /* router solicitations sending state */
+  u8 keep_sending_rs;		/* when true then next fields are valid */
+  icmp6_send_router_solicitation_params_t params;
+  f64 sleep_interval;
+  f64 due_time;
+  u32 n_left;
+  f64 start_time;
+  vlib_buffer_t *buffer;
 } ip6_radv_t;
 
 typedef struct
@@ -198,6 +207,10 @@
   /* Wildcard nd report publisher */
   uword wc_ip6_nd_publisher_node;
   uword wc_ip6_nd_publisher_et;
+
+  /* Router advertisement report publisher */
+  uword ip6_ra_publisher_node;
+  uword ip6_ra_publisher_et;
 } ip6_neighbor_main_t;
 
 /* ipv6 neighbor discovery - timer/event types */
@@ -220,6 +233,7 @@
 static ip6_address_t ip6a_zero;	/* ip6 address 0 */
 
 static void wc_nd_signal_report (wc_nd_report_t * r);
+static void ra_signal_report (ra_report_t * r);
 
 /**
  * @brief publish wildcard arp event
@@ -263,6 +277,37 @@
   nm->wc_ip6_nd_publisher_et = event_type;
 }
 
+static int
+ra_publish (ra_report_t * r)
+{
+  void vl_api_rpc_call_main_thread (void *fp, u8 * data, u32 data_length);
+  vl_api_rpc_call_main_thread (ra_signal_report, (u8 *) r, sizeof *r);
+  return 0;
+}
+
+static void
+ra_signal_report (ra_report_t * r)
+{
+  vlib_main_t *vm = vlib_get_main ();
+  ip6_neighbor_main_t *nm = &ip6_neighbor_main;
+  uword ni = nm->ip6_ra_publisher_node;
+  uword et = nm->ip6_ra_publisher_et;
+
+  if (ni == (uword) ~ 0)
+    return;
+  ra_report_t *q = vlib_process_signal_event_data (vm, ni, et, 1, sizeof *q);
+
+  *q = *r;
+}
+
+void
+ra_set_publisher_node (uword node_index, uword event_type)
+{
+  ip6_neighbor_main_t *nm = &ip6_neighbor_main;
+  nm->ip6_ra_publisher_node = node_index;
+  nm->ip6_ra_publisher_et = event_type;
+}
+
 static u8 *
 format_ip6_neighbor_ip6_entry (u8 * s, va_list * va)
 {
@@ -1877,6 +1922,22 @@
 
 		  if (error0 == ICMP6_ERROR_NONE)
 		    {
+		      radv_info->keep_sending_rs = 0;
+
+		      ra_report_t r;
+
+		      r.sw_if_index = sw_if_index0;
+		      memcpy (r.router_address, &ip0->src_address, 16);
+		      r.current_hop_limit = h0->current_hop_limit;
+		      r.flags = h0->flags;
+		      r.router_lifetime_in_sec =
+			clib_net_to_host_u16 (h0->router_lifetime_in_sec);
+		      r.neighbor_reachable_time_in_msec =
+			clib_net_to_host_u32
+			(h0->neighbor_reachable_time_in_msec);
+		      r.time_in_msec_between_retransmitted_neighbor_solicitations = clib_net_to_host_u32 (h0->time_in_msec_between_retransmitted_neighbor_solicitations);
+		      r.prefixes = 0;
+
 		      /* validate advertised information */
 		      if ((h0->current_hop_limit && radv_info->curr_hop_limit)
 			  && (h0->current_hop_limit !=
@@ -1994,6 +2055,20 @@
 
 			  switch (option_type)
 			    {
+			    case ICMP6_NEIGHBOR_DISCOVERY_OPTION_source_link_layer_address:
+			      {
+				icmp6_neighbor_discovery_ethernet_link_layer_address_option_t
+				  * h =
+				  (icmp6_neighbor_discovery_ethernet_link_layer_address_option_t
+				   *) (o0);
+
+				if (opt_len < sizeof (*h))
+				  break;
+
+				memcpy (r.slla, h->ethernet_address, 6);
+			      }
+			      break;
+
 			    case ICMP6_NEIGHBOR_DISCOVERY_OPTION_mtu:
 			      {
 				icmp6_neighbor_discovery_mtu_option_t *h =
@@ -2003,6 +2078,8 @@
 				if (opt_len < sizeof (*h))
 				  break;
 
+				r.mtu = clib_net_to_host_u32 (h->mtu);
+
 				if ((h->mtu && radv_info->adv_link_mtu) &&
 				    (h->mtu !=
 				     clib_host_to_net_u32
@@ -2032,10 +2109,23 @@
 				if (opt_len < sizeof (*h))
 				  break;
 
+				vec_validate (r.prefixes,
+					      vec_len (r.prefixes));
+				ra_report_prefix_info_t *prefix =
+				  vec_elt_at_index (r.prefixes,
+						    vec_len (r.prefixes) - 1);
+
 				preferred =
 				  clib_net_to_host_u32 (h->preferred_time);
 				valid = clib_net_to_host_u32 (h->valid_time);
 
+				prefix->preferred_time = preferred;
+				prefix->valid_time = valid;
+				prefix->flags = h->flags & 0xc0;
+				prefix->dst_address_length =
+				  h->dst_address_length;
+				prefix->dst_address = h->dst_address;
+
 				/* look for matching prefix - if we our advertising it, it better be consistant */
 				/* *INDENT-OFF* */
 				pool_foreach (pr_info, radv_info->adv_prefixes_pool,
@@ -2076,6 +2166,7 @@
 			      break;
 			    }
 			}
+		      ra_publish (&r);
 		    }
 		}
 	    }
@@ -2093,7 +2184,7 @@
       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
     }
 
-  /* Account for router advertisements sent. */
+  /* Account for router advertisements received. */
   vlib_error_count (vm, error_node->node_index,
 		    ICMP6_ERROR_ROUTER_ADVERTISEMENTS_RX,
 		    n_advertisements_rcvd);
@@ -2101,6 +2192,252 @@
   return frame->n_vectors;
 }
 
+static inline f64
+random_f64_from_to (f64 from, f64 to)
+{
+  static u32 seed = 0;
+  static u8 seed_set = 0;
+  if (!seed_set)
+    {
+      seed = random_default_seed ();
+      seed_set = 1;
+    }
+  return random_f64 (&seed) * (to - from) + from;
+}
+
+static inline u8
+get_mac_address (u32 sw_if_index, u8 * address)
+{
+  vnet_main_t *vnm = vnet_get_main ();
+  vnet_hw_interface_t *hw_if = vnet_get_sup_hw_interface (vnm, sw_if_index);
+  if (!hw_if->hw_address)
+    return 1;
+  clib_memcpy (address, hw_if->hw_address, 6);
+  return 0;
+}
+
+static inline vlib_buffer_t *
+create_buffer_for_rs (vlib_main_t * vm, ip6_radv_t * radv_info)
+{
+  u32 bi0;
+  vlib_buffer_t *p0;
+  vlib_buffer_free_list_t *fl;
+  icmp6_router_solicitation_header_t *rh;
+  u16 payload_length;
+  int bogus_length;
+  u32 sw_if_index;
+
+  sw_if_index = radv_info->sw_if_index;
+
+  if (vlib_buffer_alloc (vm, &bi0, 1) != 1)
+    {
+      clib_warning ("buffer allocation failure");
+      return 0;
+    }
+
+  p0 = vlib_get_buffer (vm, bi0);
+  fl = vlib_buffer_get_free_list (vm, VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX);
+  vlib_buffer_init_for_free_list (p0, fl);
+  VLIB_BUFFER_TRACE_TRAJECTORY_INIT (p0);
+  p0->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
+
+  vnet_buffer (p0)->sw_if_index[VLIB_RX] = sw_if_index;
+  vnet_buffer (p0)->sw_if_index[VLIB_TX] = sw_if_index;
+
+  vnet_buffer (p0)->ip.adj_index[VLIB_TX] = radv_info->mcast_adj_index;
+
+  rh = vlib_buffer_get_current (p0);
+  p0->current_length = sizeof (*rh);
+
+  rh->neighbor.icmp.type = ICMP6_router_solicitation;
+  rh->neighbor.icmp.code = 0;
+  rh->neighbor.icmp.checksum = 0;
+  rh->neighbor.reserved_must_be_zero = 0;
+
+  rh->link_layer_option.header.type =
+    ICMP6_NEIGHBOR_DISCOVERY_OPTION_source_link_layer_address;
+  if (0 != get_mac_address (sw_if_index,
+			    rh->link_layer_option.ethernet_address))
+    {
+      clib_warning ("interface with sw_if_index %u has no mac address",
+		    sw_if_index);
+      vlib_buffer_free (vm, &bi0, 1);
+      return 0;
+    }
+  rh->link_layer_option.header.n_data_u64s = 1;
+
+  payload_length = sizeof (rh->neighbor) + sizeof (u64);
+
+  rh->ip.ip_version_traffic_class_and_flow_label =
+    clib_host_to_net_u32 (0x6 << 28);
+  rh->ip.payload_length = clib_host_to_net_u16 (payload_length);
+  rh->ip.protocol = IP_PROTOCOL_ICMP6;
+  rh->ip.hop_limit = 255;
+  rh->ip.src_address = radv_info->link_local_address;
+  /* set address ff02::2 */
+  rh->ip.dst_address.as_u64[0] = clib_host_to_net_u64 (0xff02L << 48);
+  rh->ip.dst_address.as_u64[1] = clib_host_to_net_u64 (2);
+
+  rh->neighbor.icmp.checksum = ip6_tcp_udp_icmp_compute_checksum (vm, p0,
+								  &rh->ip,
+								  &bogus_length);
+
+  return p0;
+}
+
+static inline void
+stop_sending_rs (vlib_main_t * vm, ip6_radv_t * ra)
+{
+  u32 bi0;
+
+  ra->keep_sending_rs = 0;
+  if (ra->buffer)
+    {
+      bi0 = vlib_get_buffer_index (vm, ra->buffer);
+      vlib_buffer_free (vm, &bi0, 1);
+      ra->buffer = 0;
+    }
+}
+
+static inline bool
+check_send_rs (vlib_main_t * vm, ip6_radv_t * radv_info, f64 current_time,
+	       f64 * due_time)
+{
+  vlib_buffer_t *p0;
+  vlib_frame_t *f;
+  u32 *to_next;
+  u32 next_index;
+  vlib_buffer_t *c0;
+  u32 ci0;
+
+  icmp6_send_router_solicitation_params_t *params;
+
+  if (!radv_info->keep_sending_rs)
+    return false;
+
+  params = &radv_info->params;
+
+  if (radv_info->due_time > current_time)
+    {
+      *due_time = radv_info->due_time;
+      return true;
+    }
+
+  p0 = radv_info->buffer;
+
+  next_index = ip6_rewrite_mcast_node.index;
+
+  c0 = vlib_buffer_copy (vm, p0);
+  ci0 = vlib_get_buffer_index (vm, c0);
+
+  f = vlib_get_frame_to_node (vm, next_index);
+  to_next = vlib_frame_vector_args (f);
+  to_next[0] = ci0;
+  f->n_vectors = 1;
+  vlib_put_frame_to_node (vm, next_index, f);
+
+  if (params->mrc != 0 && --radv_info->n_left == 0)
+    stop_sending_rs (vm, radv_info);
+  else
+    {
+      radv_info->sleep_interval =
+	(2 + random_f64_from_to (-0.1, 0.1)) * radv_info->sleep_interval;
+      if (radv_info->sleep_interval > params->mrt)
+	radv_info->sleep_interval =
+	  (1 + random_f64_from_to (-0.1, 0.1)) * params->mrt;
+
+      radv_info->due_time = current_time + radv_info->sleep_interval;
+
+      if (params->mrd != 0
+	  && current_time > radv_info->start_time + params->mrd)
+	stop_sending_rs (vm, radv_info);
+      else
+	*due_time = radv_info->due_time;
+    }
+
+  return radv_info->keep_sending_rs;
+}
+
+static uword
+send_rs_process (vlib_main_t * vm, vlib_node_runtime_t * rt,
+		 vlib_frame_t * f0)
+{
+  ip6_neighbor_main_t *nm = &ip6_neighbor_main;
+  ip6_radv_t *radv_info;
+  uword *event_data = 0;
+  f64 sleep_time = 1e9;
+  f64 current_time;
+  f64 due_time;
+  f64 dt = 0;
+
+  while (true)
+    {
+      vlib_process_wait_for_event_or_clock (vm, sleep_time);
+      vlib_process_get_events (vm, &event_data);
+      vec_reset_length (event_data);
+
+      current_time = vlib_time_now (vm);
+      do
+	{
+	  due_time = current_time + 1e9;
+        /* *INDENT-OFF* */
+        pool_foreach (radv_info, nm->if_radv_pool,
+        ({
+	    if (check_send_rs (vm, radv_info, current_time, &dt)
+		&& (dt < due_time))
+	      due_time = dt;
+        }));
+        /* *INDENT-ON* */
+	  current_time = vlib_time_now (vm);
+	}
+      while (due_time < current_time);
+
+      sleep_time = due_time - current_time;
+    }
+
+  return 0;
+}
+
+/* *INDENT-OFF* */
+VLIB_REGISTER_NODE (send_rs_process_node) = {
+    .function = send_rs_process,
+    .type = VLIB_NODE_TYPE_PROCESS,
+    .name = "send-rs-process",
+};
+/* *INDENT-ON* */
+
+void
+icmp6_send_router_solicitation (vlib_main_t * vm, u32 sw_if_index, u8 stop,
+				icmp6_send_router_solicitation_params_t *
+				params)
+{
+  ip6_neighbor_main_t *nm = &ip6_neighbor_main;
+  u32 rai;
+  ip6_radv_t *ra = 0;
+
+  ASSERT (~0 != sw_if_index);
+
+  rai = nm->if_radv_pool_index_by_sw_if_index[sw_if_index];
+  ra = pool_elt_at_index (nm->if_radv_pool, rai);
+
+  if (stop)
+    stop_sending_rs (vm, ra);
+  else
+    {
+      ra->keep_sending_rs = 1;
+      ra->params = *params;
+      ra->n_left = params->mrc;
+      ra->start_time = vlib_time_now (vm);
+      ra->sleep_interval = (1 + random_f64_from_to (-0.1, 0.1)) * params->irt;
+      ra->due_time = 0;		/* send first packet ASAP */
+      ra->buffer = create_buffer_for_rs (vm, ra);
+      if (!ra->buffer)
+	ra->keep_sending_rs = 0;
+      else
+	vlib_process_signal_event (vm, send_rs_process_node.index, 1, 0);
+    }
+}
+
 /**
  * @brief Add a multicast Address to the advertised MLD set
  */
@@ -2226,6 +2563,9 @@
 	  mhash_free (&a->address_to_prefix_index);
 	  mhash_free (&a->address_to_mldp_index);
 
+	  if (a->keep_sending_rs)
+	    a->keep_sending_rs = 0;
+
 	  pool_put (nm->if_radv_pool, a);
 	  nm->if_radv_pool_index_by_sw_if_index[sw_if_index] = ~0;
 	  ri = ~0;
@@ -2289,6 +2629,8 @@
 						      VNET_LINK_IP6,
 						      sw_if_index);
 
+	  a->keep_sending_rs = 0;
+
 	  /* add multicast groups we will always be reporting  */
 	  ip6_neighbor_add_mld_grp (a,
 				    IP6_MULTICAST_SCOPE_link_local,
@@ -4030,6 +4372,8 @@
 
   nm->wc_ip6_nd_publisher_node = (uword) ~ 0;
 
+  nm->ip6_ra_publisher_node = (uword) ~ 0;
+
 #if 0
   /* $$$$ Hack fix for today */
   vec_validate_init_empty
diff --git a/src/vnet/ip/ip6_neighbor.h b/src/vnet/ip/ip6_neighbor.h
index ed80381..e46a6b1 100644
--- a/src/vnet/ip/ip6_neighbor.h
+++ b/src/vnet/ip/ip6_neighbor.h
@@ -98,6 +98,44 @@
 
 void wc_nd_set_publisher_node (uword node_index, uword event_type);
 
+typedef struct
+{
+  u32 irt;
+  u32 mrt;
+  u32 mrc;
+  u32 mrd;
+} icmp6_send_router_solicitation_params_t;
+
+void icmp6_send_router_solicitation (vlib_main_t * vm, u32 sw_if_index,
+				     u8 stop,
+				     icmp6_send_router_solicitation_params_t *
+				     params);
+
+typedef struct
+{
+  ip6_address_t dst_address;
+  u8 dst_address_length;
+  u8 flags;
+  u32 valid_time;
+  u32 preferred_time;
+} ra_report_prefix_info_t;
+
+typedef struct
+{
+  u32 sw_if_index;
+  u8 router_address[16];
+  u8 current_hop_limit;
+  u8 flags;
+  u16 router_lifetime_in_sec;
+  u32 neighbor_reachable_time_in_msec;
+  u32 time_in_msec_between_retransmitted_neighbor_solicitations;
+  u8 slla[6];
+  u32 mtu;
+  ra_report_prefix_info_t *prefixes;
+} ra_report_t;
+
+void ra_set_publisher_node (uword node_index, uword event_type);
+
 #endif /* included_ip6_neighbor_h */
 
 /*
diff --git a/src/vnet/ip/ip_api.c b/src/vnet/ip/ip_api.c
index 9cd6257..0a05511 100644
--- a/src/vnet/ip/ip_api.c
+++ b/src/vnet/ip/ip_api.c
@@ -78,6 +78,7 @@
 _(SET_ARP_NEIGHBOR_LIMIT, set_arp_neighbor_limit)			\
 _(WANT_IP4_ARP_EVENTS, want_ip4_arp_events)                             \
 _(WANT_IP6_ND_EVENTS, want_ip6_nd_events)                               \
+_(WANT_IP6_RA_EVENTS, want_ip6_ra_events)                               \
 _(PROXY_ARP_ADD_DEL, proxy_arp_add_del)                                 \
 _(PROXY_ARP_INTFC_ENABLE_DISABLE, proxy_arp_intfc_enable_disable)       \
 _(RESET_FIB, reset_fib)							\
@@ -90,6 +91,7 @@
 _(SW_INTERFACE_IP6ND_RA_PREFIX, sw_interface_ip6nd_ra_prefix)           \
 _(IP6ND_PROXY_ADD_DEL, ip6nd_proxy_add_del)                             \
 _(IP6ND_PROXY_DUMP, ip6nd_proxy_dump)                                   \
+_(IP6ND_SEND_ROUTER_SOLICITATION, ip6nd_send_router_solicitation)       \
 _(SW_INTERFACE_IP6_ENABLE_DISABLE, sw_interface_ip6_enable_disable )    \
 _(SW_INTERFACE_IP6_SET_LINK_LOCAL_ADDRESS, 				\
   sw_interface_ip6_set_link_local_address)				\
@@ -1651,6 +1653,32 @@
 }
 
 static void
+  vl_api_ip6nd_send_router_solicitation_t_handler
+  (vl_api_ip6nd_send_router_solicitation_t * mp)
+{
+  vl_api_ip6nd_send_router_solicitation_reply_t *rmp;
+  icmp6_send_router_solicitation_params_t params;
+  vlib_main_t *vm = vlib_get_main ();
+  int rv = 0;
+
+  VALIDATE_SW_IF_INDEX (mp);
+
+  BAD_SW_IF_INDEX_LABEL;
+  REPLY_MACRO (VL_API_IP6ND_SEND_ROUTER_SOLICITATION_REPLY);
+
+  if (rv != 0)
+    return;
+
+  params.irt = ntohl (mp->irt);
+  params.mrt = ntohl (mp->mrt);
+  params.mrc = ntohl (mp->mrc);
+  params.mrd = ntohl (mp->mrd);
+
+  icmp6_send_router_solicitation (vm, ntohl (mp->sw_if_index), mp->stop,
+				  &params);
+}
+
+static void
   vl_api_sw_interface_ip6_enable_disable_t_handler
   (vl_api_sw_interface_ip6_enable_disable_t * mp)
 {
@@ -2181,7 +2209,7 @@
 static vlib_node_registration_t wc_arp_process_node;
 
 enum
-{ WC_ARP_REPORT, WC_ND_REPORT };
+{ WC_ARP_REPORT, WC_ND_REPORT, RA_REPORT, REPORT_MAX };
 
 static uword
 wc_arp_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f)
@@ -2280,6 +2308,74 @@
             /* *INDENT-ON* */
 	    }
 	}
+      else if (event_type == RA_REPORT)
+	{
+	  ra_report_t *ra_events = event_data;
+	  for (i = 0; i < vec_len (ra_events); i++)
+	    {
+	      vpe_client_registration_t *reg;
+              /* *INDENT-OFF* */
+              pool_foreach(reg, vpe_api_main.ip6_ra_events_registrations,
+              ({
+		vl_api_registration_t *vl_reg;
+		vl_reg =
+		  vl_api_client_index_to_registration (reg->client_index);
+		if (vl_reg && vl_api_can_send_msg (vl_reg))
+		  {
+		    u32 event_size =
+		      sizeof (vl_api_ip6_ra_event_t) +
+		      vec_len (ra_events[i].prefixes) *
+		      sizeof (vl_api_ip6_ra_prefix_info_t);
+		    vl_api_ip6_ra_event_t *event =
+		      vl_msg_api_alloc (event_size);
+		    memset (event, 0, event_size);
+		    event->_vl_msg_id = htons (VL_API_IP6_RA_EVENT);
+		    event->client_index = reg->client_index;
+		    event->pid = reg->client_pid;
+
+		    event->sw_if_index = clib_host_to_net_u32 (ra_events[i].sw_if_index);
+
+		    memcpy (event->router_address, ra_events[i].router_address, 16);
+
+		    event->current_hop_limit = ra_events[i].current_hop_limit;
+		    event->flags = ra_events[i].flags;
+		    event->router_lifetime_in_sec =
+		      clib_host_to_net_u16 (ra_events
+					    [i].router_lifetime_in_sec);
+		    event->neighbor_reachable_time_in_msec =
+		      clib_host_to_net_u32 (ra_events
+					    [i].neighbor_reachable_time_in_msec);
+		    event->time_in_msec_between_retransmitted_neighbor_solicitations
+		      =
+		      clib_host_to_net_u32 (ra_events
+					    [i].time_in_msec_between_retransmitted_neighbor_solicitations);
+
+		    event->n_prefixes =
+		      clib_host_to_net_u32 (vec_len (ra_events[i].prefixes));
+		    vl_api_ip6_ra_prefix_info_t *prefix =
+		      (typeof (prefix)) event->prefixes;
+		    u32 j;
+		    for (j = 0; j < vec_len (ra_events[i].prefixes); j++)
+		      {
+			ra_report_prefix_info_t *info =
+			  &ra_events[i].prefixes[j];
+			memcpy (prefix->dst_address, info->dst_address.as_u8,
+				16);
+			prefix->dst_address_length = info->dst_address_length;
+			prefix->flags = info->flags;
+			prefix->valid_time =
+			  clib_host_to_net_u32 (info->valid_time);
+			prefix->preferred_time =
+			  clib_host_to_net_u32 (info->preferred_time);
+			prefix++;
+		      }
+
+		    vl_api_send_msg (vl_reg, (u8 *) event);
+		  }
+              }));
+            /* *INDENT-ON* */
+	    }
+	}
       vlib_process_put_event_data (vm, event_data);
     }
 
@@ -2345,7 +2441,7 @@
 	      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, WC_ARP_REPORT);
+		wc_arp_set_publisher_node (~0, REPORT_MAX);
 	      goto reply;
 	    }
 	}
@@ -2430,7 +2526,7 @@
 	      hash_unset (am->wc_ip6_nd_events_registration_hash,
 			  mp->client_index);
 	      if (pool_elts (am->wc_ip6_nd_events_registrations) == 0)
-		wc_nd_set_publisher_node (~0, 2);
+		wc_nd_set_publisher_node (~0, REPORT_MAX);
 	      goto reply;
 	    }
 	}
@@ -2485,6 +2581,50 @@
 }
 
 static void
+vl_api_want_ip6_ra_events_t_handler (vl_api_want_ip6_ra_events_t * mp)
+{
+  vpe_api_main_t *am = &vpe_api_main;
+  vl_api_want_ip6_ra_events_reply_t *rmp;
+  int rv = 0;
+
+  uword *p = hash_get (am->ip6_ra_events_registration_hash, mp->client_index);
+  vpe_client_registration_t *rp;
+  if (p)
+    {
+      if (mp->enable_disable)
+	{
+	  clib_warning ("pid %d: already enabled...", ntohl (mp->pid));
+	  rv = VNET_API_ERROR_INVALID_REGISTRATION;
+	  goto reply;
+	}
+      else
+	{
+	  rp = pool_elt_at_index (am->ip6_ra_events_registrations, p[0]);
+	  pool_put (am->ip6_ra_events_registrations, rp);
+	  hash_unset (am->ip6_ra_events_registration_hash, mp->client_index);
+	  if (pool_elts (am->ip6_ra_events_registrations) == 0)
+	    ra_set_publisher_node (~0, REPORT_MAX);
+	  goto reply;
+	}
+    }
+  if (mp->enable_disable == 0)
+    {
+      clib_warning ("pid %d: already disabled...", ntohl (mp->pid));
+      rv = VNET_API_ERROR_INVALID_REGISTRATION;
+      goto reply;
+    }
+  pool_get (am->ip6_ra_events_registrations, rp);
+  rp->client_index = mp->client_index;
+  rp->client_pid = ntohl (mp->pid);
+  hash_set (am->ip6_ra_events_registration_hash, rp->client_index,
+	    rp - am->ip6_ra_events_registrations);
+  ra_set_publisher_node (wc_arp_process_node.index, RA_REPORT);
+
+reply:
+  REPLY_MACRO (VL_API_WANT_IP6_RA_EVENTS_REPLY);
+}
+
+static void
 vl_api_proxy_arp_add_del_t_handler (vl_api_proxy_arp_add_del_t * mp)
 {
   vl_api_proxy_arp_add_del_reply_t *rmp;
diff --git a/test/test_ip6.py b/test/test_ip6.py
index 68b077c..70ebc36 100644
--- a/test/test_ip6.py
+++ b/test/test_ip6.py
@@ -1,9 +1,10 @@
 #!/usr/bin/env python
 
 import unittest
-from socket import AF_INET6
+import socket
 
 from framework import VppTestCase, VppTestRunner
+from util import ppp, ip6_normalize
 from vpp_sub_interface import VppSubInterface, VppDot1QSubint
 from vpp_pg_interface import is_ipv6_misc
 from vpp_ip_route import VppIpRoute, VppRoutePath, find_route, VppIpMRoute, \
@@ -18,14 +19,15 @@
     ICMPv6NDOptMTU, ICMPv6NDOptSrcLLAddr, ICMPv6NDOptPrefixInfo, \
     ICMPv6ND_NA, ICMPv6NDOptDstLLAddr, ICMPv6DestUnreach, icmp6types, \
     ICMPv6TimeExceeded
-
-from util import ppp
 from scapy.utils6 import in6_getnsma, in6_getnsmac, in6_ptop, in6_islladdr, \
     in6_mactoifaceid, in6_ismaddr
 from scapy.utils import inet_pton, inet_ntop
 from scapy.contrib.mpls import MPLS
 
 
+AF_INET6 = socket.AF_INET6
+
+
 def mk_ll_addr(mac):
     euid = in6_mactoifaceid(mac)
     addr = "fe80::" + euid
@@ -895,6 +897,114 @@
         self.pg0.ip6_ra_config(no=1, suppress=1, send_unicast=0)
 
 
+class TestIPv6RD(TestIPv6ND):
+    """ IPv6 Router Discovery Test Case """
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestIPv6RD, cls).setUpClass()
+
+    def setUp(self):
+        super(TestIPv6RD, self).setUp()
+
+        # create 2 pg interfaces
+        self.create_pg_interfaces(range(2))
+
+        self.interfaces = list(self.pg_interfaces)
+
+        # setup all interfaces
+        for i in self.interfaces:
+            i.admin_up()
+            i.config_ip6()
+
+    def tearDown(self):
+        super(TestIPv6RD, self).tearDown()
+
+    def test_rd_send_router_solicitation(self):
+        """ Verify router solicitation packets """
+
+        count = 2
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        self.vapi.ip6nd_send_router_solicitation(self.pg1.sw_if_index,
+                                                 mrc=count)
+        rx_list = self.pg1.get_capture(count, timeout=3)
+        self.assertEqual(len(rx_list), count)
+        for packet in rx_list:
+            self.assertTrue(packet.haslayer(IPv6))
+            self.assertTrue(packet[IPv6].haslayer(ICMPv6ND_RS))
+            dst = ip6_normalize(packet[IPv6].dst)
+            dst2 = ip6_normalize("ff02::2")
+            self.assert_equal(dst, dst2)
+            src = ip6_normalize(packet[IPv6].src)
+            src2 = ip6_normalize(self.pg1.local_ip6_ll)
+            self.assert_equal(src, src2)
+            self.assertTrue(packet[ICMPv6ND_RS].haslayer(ICMPv6NDOptSrcLLAddr))
+            self.assert_equal(packet[ICMPv6NDOptSrcLLAddr].lladdr,
+                              self.pg1.local_mac)
+
+    def verify_prefix_info(self, reported_prefix, prefix_option):
+        prefix = socket.inet_pton(socket.AF_INET6,
+                                  prefix_option.getfieldval("prefix"))
+        self.assert_equal(reported_prefix.dst_address, prefix)
+        self.assert_equal(reported_prefix.dst_address_length,
+                          prefix_option.getfieldval("prefixlen"))
+        L = prefix_option.getfieldval("L")
+        A = prefix_option.getfieldval("A")
+        option_flags = (L << 7) | (A << 6)
+        self.assert_equal(reported_prefix.flags, option_flags)
+        self.assert_equal(reported_prefix.valid_time,
+                          prefix_option.getfieldval("validlifetime"))
+        self.assert_equal(reported_prefix.preferred_time,
+                          prefix_option.getfieldval("preferredlifetime"))
+
+    def test_rd_receive_router_advertisement(self):
+        """ Verify events triggered by received RA packets """
+
+        self.vapi.want_ip6_ra_events()
+
+        prefix_info_1 = ICMPv6NDOptPrefixInfo(
+            prefix="1::2",
+            prefixlen=50,
+            validlifetime=200,
+            preferredlifetime=500,
+            L=1,
+            A=1,
+        )
+
+        prefix_info_2 = ICMPv6NDOptPrefixInfo(
+            prefix="7::4",
+            prefixlen=20,
+            validlifetime=70,
+            preferredlifetime=1000,
+            L=1,
+            A=0,
+        )
+
+        p = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) /
+             IPv6(dst=self.pg1.local_ip6_ll,
+                  src=mk_ll_addr(self.pg1.remote_mac)) /
+             ICMPv6ND_RA() /
+             prefix_info_1 /
+             prefix_info_2)
+        self.pg1.add_stream([p])
+        self.pg_start()
+
+        ev = self.vapi.wait_for_event(10, "ip6_ra_event")
+
+        self.assert_equal(ev.current_hop_limit, 0)
+        self.assert_equal(ev.flags, 8)
+        self.assert_equal(ev.router_lifetime_in_sec, 1800)
+        self.assert_equal(ev.neighbor_reachable_time_in_msec, 0)
+        self.assert_equal(
+            ev.time_in_msec_between_retransmitted_neighbor_solicitations, 0)
+
+        self.assert_equal(ev.n_prefixes, 2)
+
+        self.verify_prefix_info(ev.prefixes[0], prefix_info_1)
+        self.verify_prefix_info(ev.prefixes[1], prefix_info_2)
+
+
 class IPv6NDProxyTest(TestIPv6ND):
     """ IPv6 ND ProxyTest Case """
 
diff --git a/test/util.py b/test/util.py
index 512bf9e..1044aa8 100644
--- a/test/util.py
+++ b/test/util.py
@@ -65,6 +65,11 @@
     return addr
 
 
+def ip6_normalize(ip6):
+    return socket.inet_ntop(socket.AF_INET6,
+                            socket.inet_pton(socket.AF_INET6, ip6))
+
+
 class NumericConstant(object):
     __metaclass__ = ABCMeta
 
diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py
index 22c6a0d..8fc0bfd 100644
--- a/test/vpp_papi_provider.py
+++ b/test/vpp_papi_provider.py
@@ -466,6 +466,20 @@
                          'address': address,
                          'pid': os.getpid(), })
 
+    def want_ip6_ra_events(self, enable_disable=1):
+        return self.api(self.papi.want_ip6_ra_events,
+                        {'enable_disable': enable_disable,
+                         'pid': os.getpid(), })
+
+    def ip6nd_send_router_solicitation(self, sw_if_index, irt=1, mrt=120,
+                                       mrc=0, mrd=0):
+        return self.api(self.papi.ip6nd_send_router_solicitation,
+                        {'irt': irt,
+                         'mrt': mrt,
+                         'mrc': mrc,
+                         'mrd': mrd,
+                         'sw_if_index': sw_if_index})
+
     def want_macs_learn_events(self, enable_disable=1, scan_delay=0,
                                max_macs_in_event=0, learn_limit=0):
         return self.api(self.papi.want_l2_macs_events,