ip6-nd: support dump/details for IPv6 RA 58/38158/2
authorAlexander Chernavin <achernavin@netgate.com>
Thu, 2 Feb 2023 14:22:56 +0000 (14:22 +0000)
committerNeale Ranns <neale@graphiant.com>
Wed, 8 Feb 2023 03:20:32 +0000 (03:20 +0000)
Type: improvement

With this change, add support for dumping IPv6 Router Advertisements
details on a per-interface basis (or all). Also, cover that with a test.

Signed-off-by: Alexander Chernavin <achernavin@netgate.com>
Change-Id: I89fa93439d33cc36252377f27187b18b3d30a1d4

src/vnet/ip6-nd/ip6_nd.api
src/vnet/ip6-nd/ip6_nd_api.c
src/vnet/ip6-nd/ip6_nd_test.c
src/vnet/ip6-nd/ip6_ra.c
src/vnet/ip6-nd/ip6_ra.h
test/test_ip6.py

index 0a519c1..3ddf251 100644 (file)
@@ -20,7 +20,7 @@
     called through a shared memory interface.
 */
 
-option version = "1.0.0";
+option version = "1.1.0";
 
 import "vnet/ip/ip_types.api";
 import "vnet/interface_types.api";
@@ -106,6 +106,134 @@ autoreply define sw_interface_ip6nd_ra_prefix
   u32 pref_lifetime;
 };
 
+/** \brief IPv6 Router Advertisements prefix entry
+    @param prefix - prefix to advertise
+    @param onlink_flag - if true, the prefix can be used for on-link
+                         determination
+    @param autonomous_flag - if true, the prefix can be used for stateless
+                             address configuration
+    @param val_lifetime - valid lifetime in seconds (0xffffffff represents
+                          infinity)
+    @param pref_lifetime - preferred lifetime in seconds (0xffffffff represents
+                           infinity)
+    @param valid_lifetime_expires - number of seconds in which valid lifetime
+                                    expires (zero means never, negative value
+                                    means expired this number of seconds ago)
+    @param pref_lifetime_expires - number of seconds in which preferred
+                                   lifetime expires (zero means never, negative
+                                   value means expired this number of seconds
+                                   ago)
+    @param decrement_lifetime_flag - if true, decrement valid lifetime and
+                                     preferred lifetime
+    @param no_advertise - if true, the prefix will not be advertised
+*/
+typedef ip6nd_ra_prefix
+{
+  vl_api_prefix_t prefix;
+  bool onlink_flag;
+  bool autonomous_flag;
+  u32 val_lifetime;
+  u32 pref_lifetime;
+  f64 valid_lifetime_expires;
+  f64 pref_lifetime_expires;
+  bool decrement_lifetime_flag;
+  bool no_advertise;
+};
+
+/** \brief Dump IPv6 Router Advertisements details on a per-interface basis
+    @param client_index - opaque cookie to identify the sender
+    @param context - sender context, to match reply w/ request
+    @param sw_if_index - interface index to use as a filter (0xffffffff
+                         represents all interfaces)
+*/
+define sw_interface_ip6nd_ra_dump
+{
+  option in_progress;
+  u32 client_index;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+  option vat_help = "[(<if-name>|sw_if_index <if-idx>)]";
+};
+
+/** \brief Details on IPv6 Router Advertisements for a single interface
+    @param context - returned sender context, to match reply w/ request
+    @param sw_if_index - interface index the details are belong to
+    @param cur_hop_limit - current hop limit
+    @param adv_managed_flag - if true, enable DHCP for address
+    @param adv_other_flag - if true, Enable DHCP for other information
+    @param adv_router_lifetime - lifetime associated with the default router in
+                                 seconds (zero indicates that the router is not
+                                a default router)
+    @param adv_neighbor_reachable_time - number of milliseconds within which a
+                                         neighbor is assumed to be reachable
+                                         (zero means unspecified)
+    @param adv_retransmit_interval - number of milliseconds between
+                                     retransmitted Neighbor Solicitation
+                                     messages (zero means unspecified)
+    @param adv_link_mtu - MTU that all the nodes on a link use
+    @param send_radv - if true, send periodic Router Advertisements
+    @param cease_radv - if true, cease to send periodic Router Advertisements
+    @param send_unicast - if true, destination address of a Router
+                          Advertisement message will use the source address of
+                          the Router Solicitation message (when available).
+                          Otherwise, multicast address will be used
+    @param adv_link_layer_address - if true, add link layer address option
+    @param max_radv_interval - maximum time in seconds allowed between sending
+                               unsolicited multicast Router Advertisements
+    @param min_radv_interval - minimum time in seconds allowed between sending
+                               unsolicited multicast Router Advertisements
+    @param last_radv_time - number of seconds since the last time a solicited
+                            Router Advertisement message was sent (zero means
+                            never)
+    @param last_multicast_time - number of seconds since the last time a
+                                 multicast Router Advertisements message was
+                                 sent (zero means never)
+    @param next_multicast_time - number of seconds within which next time a
+                                 multicast Router Advertisement message will be
+                                 sent (zero means never)
+    @param initial_adverts_count - number of initial Router Advertisement
+                                   messages to send
+    @param initial_adverts_interval - number of seconds between initial Router
+                                      Advertisement messages
+    @param initial_adverts_sent - if true, all initial Router Advertisement
+                                  messages were sent
+    @param n_advertisements_sent - number of Router Advertisements sent
+    @param n_solicitations_rcvd - number of Router Solicitations received
+    @param n_solicitations_dropped - number of Router Solicitations dropped
+    @param n_prefixes - number of prefix entries
+    @param prefixes - array of prefix entries
+*/
+define sw_interface_ip6nd_ra_details
+{
+  option in_progress;
+  u32 context;
+  vl_api_interface_index_t sw_if_index;
+  u8 cur_hop_limit;
+  bool adv_managed_flag;
+  bool adv_other_flag;
+  u16 adv_router_lifetime;
+  u32 adv_neighbor_reachable_time;
+  u32 adv_retransmit_interval;
+  u32 adv_link_mtu;
+  bool send_radv;
+  bool cease_radv;
+  bool send_unicast;
+  bool adv_link_layer_address;
+  f64 max_radv_interval;
+  f64 min_radv_interval;
+  f64 last_radv_time;
+  f64 last_multicast_time;
+  f64 next_multicast_time;
+  u32 initial_adverts_count;
+  f64 initial_adverts_interval;
+  bool initial_adverts_sent;
+  u32 n_advertisements_sent;
+  u32 n_solicitations_rcvd;
+  u32 n_solicitations_dropped;
+  u32 n_prefixes;
+  vl_api_ip6nd_ra_prefix_t prefixes[n_prefixes];
+};
+
 /** \brief IPv6 ND (mirror) proxy
     @param client_index - opaque cookie to identify the sender
     @param context - sender context, to match reply w/ request
index 6520a61..c11c3cb 100644 (file)
@@ -221,6 +221,175 @@ static void
   REPLY_MACRO (VL_API_SW_INTERFACE_IP6ND_RA_PREFIX_REPLY);
 }
 
+static void
+ip6_radv_prefix_encode (f64 now, const ip6_radv_prefix_t *in,
+                       vl_api_ip6nd_ra_prefix_t *out)
+{
+  fib_prefix_t in_ip6_pfx = {
+    .fp_addr = {
+      .ip6 = in->prefix,
+    },
+    .fp_len = in->prefix_len,
+    .fp_proto = FIB_PROTOCOL_IP6,
+  };
+
+  ip_prefix_encode (&in_ip6_pfx, &out->prefix);
+
+  out->onlink_flag = in->adv_on_link_flag;
+  out->autonomous_flag = in->adv_autonomous_flag;
+  out->val_lifetime = htonl (in->adv_valid_lifetime_in_secs);
+  out->pref_lifetime = htonl (in->adv_pref_lifetime_in_secs);
+
+  if (in->adv_valid_lifetime_in_secs != ~0)
+    {
+      out->valid_lifetime_expires =
+       clib_host_to_net_f64 (in->valid_lifetime_expires - now);
+    }
+
+  if (in->adv_pref_lifetime_in_secs != ~0)
+    {
+      out->pref_lifetime_expires =
+       clib_host_to_net_f64 (in->pref_lifetime_expires - now);
+    }
+
+  out->decrement_lifetime_flag = in->decrement_lifetime_flag;
+  out->no_advertise = (in->enabled == 0);
+}
+
+static void
+send_sw_interface_ip6nd_ra_details (vl_api_registration_t *reg, u32 context,
+                                   ip6_ra_t *radv_info)
+{
+  vl_api_sw_interface_ip6nd_ra_details_t *rmp = 0;
+  vl_api_ip6nd_ra_prefix_t *api_radv_pfx;
+  u32 n_prefixes = pool_elts (radv_info->adv_prefixes_pool);
+  ip6_radv_prefix_t *radv_pfx;
+  u32 msg_size = sizeof (*rmp) + n_prefixes * sizeof (*api_radv_pfx);
+  vlib_main_t *vm = vlib_get_main ();
+  f64 now = vlib_time_now (vm);
+
+  rmp = vl_msg_api_alloc (msg_size);
+  if (!rmp)
+    return;
+  clib_memset (rmp, 0, msg_size);
+  rmp->_vl_msg_id =
+    ntohs (VL_API_SW_INTERFACE_IP6ND_RA_DETAILS + REPLY_MSG_ID_BASE);
+  rmp->context = context;
+
+  rmp->sw_if_index = htonl (radv_info->sw_if_index);
+  rmp->cur_hop_limit = radv_info->curr_hop_limit;
+  rmp->adv_managed_flag = radv_info->adv_managed_flag;
+  rmp->adv_other_flag = radv_info->adv_other_flag;
+  rmp->adv_router_lifetime = htons (radv_info->adv_router_lifetime_in_sec);
+  rmp->adv_neighbor_reachable_time =
+    htonl (radv_info->adv_neighbor_reachable_time_in_msec);
+  rmp->adv_retransmit_interval = htonl (
+    radv_info->adv_time_in_msec_between_retransmitted_neighbor_solicitations);
+  rmp->adv_link_mtu = htonl (radv_info->adv_link_mtu);
+  rmp->send_radv = radv_info->send_radv;
+  rmp->cease_radv = radv_info->cease_radv;
+  rmp->send_unicast = radv_info->send_unicast;
+  rmp->adv_link_layer_address = radv_info->adv_link_layer_address;
+  rmp->max_radv_interval = clib_host_to_net_f64 (radv_info->max_radv_interval);
+  rmp->min_radv_interval = clib_host_to_net_f64 (radv_info->min_radv_interval);
+
+  if (radv_info->last_radv_time > 0.0)
+    {
+      rmp->last_radv_time =
+       clib_host_to_net_f64 (now - radv_info->last_radv_time);
+    }
+
+  if ((radv_info->next_multicast_time - radv_info->last_multicast_time) > 0.0)
+    {
+      rmp->last_multicast_time =
+       clib_host_to_net_f64 (now - radv_info->last_multicast_time);
+      rmp->next_multicast_time =
+       clib_host_to_net_f64 (radv_info->next_multicast_time - now);
+    }
+
+  rmp->initial_adverts_count = htonl (radv_info->initial_adverts_count);
+  rmp->initial_adverts_interval =
+    clib_host_to_net_f64 (radv_info->initial_adverts_interval);
+  rmp->initial_adverts_sent = (radv_info->initial_adverts_sent == 0);
+  rmp->n_advertisements_sent = htonl (radv_info->n_advertisements_sent);
+  rmp->n_solicitations_rcvd = htonl (radv_info->n_solicitations_rcvd);
+  rmp->n_solicitations_dropped = htonl (radv_info->n_solicitations_dropped);
+  rmp->n_prefixes = htonl (n_prefixes);
+
+  api_radv_pfx = rmp->prefixes;
+  pool_foreach (radv_pfx, radv_info->adv_prefixes_pool)
+    {
+      ip6_radv_prefix_encode (now, radv_pfx, api_radv_pfx);
+
+      api_radv_pfx++;
+    }
+
+  vl_api_send_msg (reg, (u8 *) rmp);
+}
+
+typedef struct
+{
+  u32 *sw_if_indices;
+} api_dump_ip6_ra_itf_walk_ctx_t;
+
+static walk_rc_t
+api_dump_ip6_ra_itf_walk_fn (u32 sw_if_index, void *arg)
+{
+  api_dump_ip6_ra_itf_walk_ctx_t *ctx = arg;
+
+  vec_add1 (ctx->sw_if_indices, sw_if_index);
+
+  return (WALK_CONTINUE);
+}
+
+static void
+vl_api_sw_interface_ip6nd_ra_dump_t_handler (
+  vl_api_sw_interface_ip6nd_ra_dump_t *mp)
+{
+  vl_api_registration_t *reg;
+  u32 sw_if_index;
+  ip6_ra_t *radv_info;
+
+  reg = vl_api_client_index_to_registration (mp->client_index);
+  if (!reg)
+    return;
+
+  sw_if_index = ntohl (mp->sw_if_index);
+
+  if (sw_if_index == INDEX_INVALID)
+    {
+      /* dump all interfaces */
+
+      api_dump_ip6_ra_itf_walk_ctx_t ctx = {
+       .sw_if_indices = NULL,
+      };
+      u32 *sw_if_i;
+
+      ip6_ra_itf_walk (api_dump_ip6_ra_itf_walk_fn, &ctx);
+
+      vec_foreach (sw_if_i, ctx.sw_if_indices)
+       {
+         radv_info = ip6_ra_get_itf (*sw_if_i);
+         if (radv_info != NULL)
+           {
+             send_sw_interface_ip6nd_ra_details (reg, mp->context, radv_info);
+           }
+       }
+
+      vec_free (ctx.sw_if_indices);
+    }
+  else
+    {
+      /* dump a single interface */
+
+      radv_info = ip6_ra_get_itf (sw_if_index);
+      if (radv_info != NULL)
+       {
+         send_sw_interface_ip6nd_ra_details (reg, mp->context, radv_info);
+       }
+    }
+}
+
 static void
   vl_api_ip6nd_send_router_solicitation_t_handler
   (vl_api_ip6nd_send_router_solicitation_t * mp)
index 933029d..488ca59 100644 (file)
@@ -325,6 +325,63 @@ api_ip6nd_proxy_enable_disable (vat_main_t *vam)
   return -1;
 }
 
+static int
+api_sw_interface_ip6nd_ra_dump (vat_main_t *vam)
+{
+  unformat_input_t *i = vam->input;
+  vl_api_sw_interface_ip6nd_ra_dump_t *mp;
+  vl_api_control_ping_t *mp_ping;
+  u32 sw_if_index = ~0;
+  int ret;
+
+  /* Parse args required to build the message */
+  while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT)
+    {
+      if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index))
+       ;
+      else if (unformat (i, "sw_if_index %u", &sw_if_index))
+       ;
+      else
+       {
+         clib_warning ("parse error '%U'", format_unformat_error, i);
+         return -99;
+       }
+    }
+
+  /* Construct the API message */
+  M (SW_INTERFACE_IP6ND_RA_DUMP, mp);
+  mp->sw_if_index = ntohl (sw_if_index);
+
+  /* Send it */
+  S (mp);
+
+  /* Use control ping for synchronization */
+  PING (&ip6_nd_test_main, mp_ping);
+  S (mp_ping);
+
+  /* Wait for a reply... */
+  W (ret);
+
+  return ret;
+}
+
+static void
+vl_api_sw_interface_ip6nd_ra_details_t_handler (
+  vl_api_sw_interface_ip6nd_ra_details_t *mp)
+{
+  vat_main_t *vam = ip6_nd_test_main.vat_main;
+  u32 sw_if_index;
+  u8 send_radv;
+
+  /* Read the message */
+  sw_if_index = ntohl (mp->sw_if_index);
+  send_radv = mp->send_radv;
+
+  /* Print it */
+  print (vam->ofp, "sw_if_index: %u, send_radv: %s", sw_if_index,
+        (send_radv ? "on" : "off"));
+}
+
 #include <ip6-nd/ip6_nd.api_test.c>
 
 /*
index 65b7cf0..3781ea3 100644 (file)
@@ -65,95 +65,6 @@ typedef CLIB_PACKED (struct
 #define MAX_DELAY_BETWEEN_RAS                    1800  /* seconds */
 #define MAX_RA_DELAY_TIME                                          .5  /* seconds */
 
-/* advertised prefix option */
-typedef struct
-{
-  /* basic advertised information */
-  ip6_address_t prefix;
-  u8 prefix_len;
-  int adv_on_link_flag;
-  int adv_autonomous_flag;
-  u32 adv_valid_lifetime_in_secs;
-  u32 adv_pref_lifetime_in_secs;
-
-  /* advertised values are computed from these times if decrementing */
-  f64 valid_lifetime_expires;
-  f64 pref_lifetime_expires;
-
-  /* local information */
-  int enabled;
-  int deprecated_prefix_flag;
-  int decrement_lifetime_flag;
-
-#define MIN_ADV_VALID_LIFETIME 7203    /* seconds */
-#define DEF_ADV_VALID_LIFETIME  2592000
-#define DEF_ADV_PREF_LIFETIME 604800
-
-  /* extensions are added here, mobile, DNS etc.. */
-} ip6_radv_prefix_t;
-
-typedef struct ip6_ra_t_
-{
-  /* advertised config information, zero means unspecified  */
-  u8 curr_hop_limit;
-  int adv_managed_flag;
-  int adv_other_flag;
-  u16 adv_router_lifetime_in_sec;
-  u32 adv_neighbor_reachable_time_in_msec;
-  u32 adv_time_in_msec_between_retransmitted_neighbor_solicitations;
-
-  /* mtu option */
-  u32 adv_link_mtu;
-
-  /* local information */
-  u32 sw_if_index;
-  int send_radv;               /* radv on/off on this interface -  set by config */
-  int cease_radv;              /* we are ceasing  to send  - set byf config */
-  int send_unicast;
-  int adv_link_layer_address;
-  int prefix_option;
-  int failed_device_check;
-  int ref_count;
-
-  /* prefix option */
-  ip6_radv_prefix_t *adv_prefixes_pool;
-
-  /* Hash table mapping address to index in interface advertised  prefix pool. */
-  mhash_t address_to_prefix_index;
-
-  f64 max_radv_interval;
-  f64 min_radv_interval;
-  f64 min_delay_between_radv;
-  f64 max_delay_between_radv;
-  f64 max_rtr_default_lifetime;
-
-  f64 last_radv_time;
-  f64 last_multicast_time;
-  f64 next_multicast_time;
-
-
-  u32 initial_adverts_count;
-  f64 initial_adverts_interval;
-  u32 initial_adverts_sent;
-
-  /* stats */
-  u32 n_advertisements_sent;
-  u32 n_solicitations_rcvd;
-  u32 n_solicitations_dropped;
-
-  /* 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;
-
-  u32 seed;
-
-} ip6_ra_t;
-
 static ip6_link_delegate_id_t ip6_ra_delegate_id;
 static ip6_ra_t *ip6_ra_pool;
 
@@ -191,7 +102,7 @@ ip6_ra_report_unregister (ip6_ra_report_notify_t fn)
   }
 }
 
-static inline ip6_ra_t *
+ip6_ra_t *
 ip6_ra_get_itf (u32 sw_if_index)
 {
   index_t rai;
@@ -214,6 +125,18 @@ ip6_ra_adv_enabled (u32 sw_if_index)
   return ((ra != NULL) && (ra->send_radv != 0));
 }
 
+void
+ip6_ra_itf_walk (ip6_ra_itf_walk_fn_t fn, void *ctx)
+{
+  ip6_ra_t *radv_info;
+
+  pool_foreach (radv_info, ip6_ra_pool)
+    {
+      if (WALK_STOP == fn (radv_info->sw_if_index, ctx))
+       break;
+    }
+}
+
 /* for "syslogging" - use elog for now */
 #define foreach_log_level           \
   _ (DEBUG, "DEBUG")                 \
index c501247..958845b 100644 (file)
 
 #include <vnet/fib/fib_types.h>
 
+/* advertised prefix option */
+typedef struct
+{
+  /* basic advertised information */
+  ip6_address_t prefix;
+  u8 prefix_len;
+  int adv_on_link_flag;
+  int adv_autonomous_flag;
+  u32 adv_valid_lifetime_in_secs;
+  u32 adv_pref_lifetime_in_secs;
+
+  /* advertised values are computed from these times if decrementing */
+  f64 valid_lifetime_expires;
+  f64 pref_lifetime_expires;
+
+  /* local information */
+  int enabled;
+  int deprecated_prefix_flag;
+  int decrement_lifetime_flag;
+
+#define MIN_ADV_VALID_LIFETIME 7203 /* seconds */
+#define DEF_ADV_VALID_LIFETIME 2592000
+#define DEF_ADV_PREF_LIFETIME  604800
+
+  /* extensions are added here, mobile, DNS etc.. */
+} ip6_radv_prefix_t;
+
+typedef struct
+{
+  u32 irt;
+  u32 mrt;
+  u32 mrc;
+  u32 mrd;
+} icmp6_send_router_solicitation_params_t;
+
+typedef struct ip6_ra_t_
+{
+  /* advertised config information, zero means unspecified  */
+  u8 curr_hop_limit;
+  int adv_managed_flag;
+  int adv_other_flag;
+  u16 adv_router_lifetime_in_sec;
+  u32 adv_neighbor_reachable_time_in_msec;
+  u32 adv_time_in_msec_between_retransmitted_neighbor_solicitations;
+
+  /* mtu option */
+  u32 adv_link_mtu;
+
+  /* local information */
+  u32 sw_if_index;
+  int send_radv;  /* radv on/off on this interface -  set by config */
+  int cease_radv; /* we are ceasing  to send  - set byf config */
+  int send_unicast;
+  int adv_link_layer_address;
+  int prefix_option;
+  int failed_device_check;
+  int ref_count;
+
+  /* prefix option */
+  ip6_radv_prefix_t *adv_prefixes_pool;
+
+  /* Hash table mapping address to index in interface advertised  prefix pool.
+   */
+  mhash_t address_to_prefix_index;
+
+  f64 max_radv_interval;
+  f64 min_radv_interval;
+  f64 min_delay_between_radv;
+  f64 max_delay_between_radv;
+  f64 max_rtr_default_lifetime;
+
+  f64 last_radv_time;
+  f64 last_multicast_time;
+  f64 next_multicast_time;
+
+  u32 initial_adverts_count;
+  f64 initial_adverts_interval;
+  u32 initial_adverts_sent;
+
+  /* stats */
+  u32 n_advertisements_sent;
+  u32 n_solicitations_rcvd;
+  u32 n_solicitations_dropped;
+
+  /* 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;
+
+  u32 seed;
+
+} ip6_ra_t;
+
+extern ip6_ra_t *ip6_ra_get_itf (u32 sw_if_index);
+
 extern int ip6_ra_config (vlib_main_t * vm, u32 sw_if_index,
                          u8 suppress, u8 managed, u8 other,
                          u8 ll_option, u8 send_unicast, u8 cease,
@@ -35,13 +134,9 @@ extern int ip6_ra_prefix (vlib_main_t * vm, u32 sw_if_index,
                          u8 off_link, u8 no_autoconfig,
                          u8 no_onlink, u8 is_no);
 
-typedef struct
-{
-  u32 irt;
-  u32 mrt;
-  u32 mrc;
-  u32 mrd;
-} icmp6_send_router_solicitation_params_t;
+typedef walk_rc_t (*ip6_ra_itf_walk_fn_t) (u32 sw_if_index, void *ctx);
+
+extern void ip6_ra_itf_walk (ip6_ra_itf_walk_fn_t fn, void *ctx);
 
 extern void icmp6_send_router_solicitation (vlib_main_t * vm,
                                            u32 sw_if_index,
index 38bce97..b46bef6 100644 (file)
@@ -779,6 +779,85 @@ class TestIPv6(TestIPv6ND):
         rx = rx[0]
         self.validate_ra(intf, rx, dst_ip, src_ip=src_ip, pi_opt=opt)
 
+    def test_ip6_ra_dump(self):
+        """IPv6 RA dump"""
+
+        # Dump IPv6 RA for all interfaces
+        ip6_ra_dump = self.vapi.sw_interface_ip6nd_ra_dump(sw_if_index=0xFFFFFFFF)
+        self.assertEqual(len(ip6_ra_dump), len(self.interfaces))
+
+        for ip6_ra in ip6_ra_dump:
+            self.assertFalse(ip6_ra.send_radv)
+            self.assertEqual(ip6_ra.n_prefixes, 0)
+            self.assertEqual(len(ip6_ra.prefixes), 0)
+            self.assertEqual(ip6_ra.last_radv_time, 0.0)
+            self.assertEqual(ip6_ra.last_multicast_time, 0.0)
+            self.assertEqual(ip6_ra.next_multicast_time, 0.0)
+            self.assertEqual(ip6_ra.n_advertisements_sent, 0)
+            self.assertEqual(ip6_ra.n_solicitations_rcvd, 0)
+            self.assertEqual(ip6_ra.n_solicitations_dropped, 0)
+
+        # Enable sending IPv6 RA for an interface
+        self.pg0.ip6_ra_config(no=1, suppress=1)
+
+        # Add IPv6 RA prefixes for the interface
+        pfx0 = IPv6Network(
+            "%s/%s" % (self.pg0.local_ip6, self.pg0.local_ip6_prefix_len), strict=False
+        )
+        pfx1 = IPv6Network("fafa::/96")
+        self.pg0.ip6_ra_prefix(pfx0, off_link=0, no_autoconfig=0)
+        self.pg0.ip6_ra_prefix(pfx1, off_link=1, no_autoconfig=1)
+
+        # Wait for multicast IPv6 RA
+        self.sleep(1)
+
+        # Dump IPv6 RA for the interface
+        ip6_ra_dump = self.vapi.sw_interface_ip6nd_ra_dump(
+            sw_if_index=self.pg0.sw_if_index
+        )
+        self.assertEqual(len(ip6_ra_dump), 1)
+        ip6_ra = ip6_ra_dump[0]
+
+        self.assertEqual(ip6_ra.sw_if_index, self.pg0.sw_if_index)
+        self.assertTrue(ip6_ra.send_radv)
+        self.assertEqual(ip6_ra.n_prefixes, 2)
+        self.assertEqual(len(ip6_ra.prefixes), 2)
+        self.assertEqual(ip6_ra.last_radv_time, 0.0)
+        self.assertGreater(ip6_ra.last_multicast_time, 0.0)
+        self.assertGreater(ip6_ra.next_multicast_time, 0.0)
+        self.assertGreater(ip6_ra.n_advertisements_sent, 0)
+        self.assertEqual(ip6_ra.n_solicitations_rcvd, 0)
+        self.assertEqual(ip6_ra.n_solicitations_dropped, 0)
+
+        self.assertEqual(ip6_ra.prefixes[0].prefix, pfx0)
+        self.assertTrue(ip6_ra.prefixes[0].onlink_flag)
+        self.assertTrue(ip6_ra.prefixes[0].autonomous_flag)
+        self.assertFalse(ip6_ra.prefixes[0].no_advertise)
+
+        self.assertEqual(ip6_ra.prefixes[1].prefix, pfx1)
+        self.assertFalse(ip6_ra.prefixes[1].onlink_flag)
+        self.assertFalse(ip6_ra.prefixes[1].autonomous_flag)
+        self.assertFalse(ip6_ra.prefixes[1].no_advertise)
+
+        # Reset sending IPv6 RA for the interface
+        self.pg0.ip6_ra_config(suppress=1)
+
+        # Remove IPv6 RA prefixes for the interface
+        self.pg0.ip6_ra_prefix(pfx0, is_no=1)
+        self.pg0.ip6_ra_prefix(pfx1, is_no=1)
+
+        # Dump IPv6 RA for the interface
+        ip6_ra_dump = self.vapi.sw_interface_ip6nd_ra_dump(
+            sw_if_index=self.pg0.sw_if_index
+        )
+        self.assertEqual(len(ip6_ra_dump), 1)
+        ip6_ra = ip6_ra_dump[0]
+
+        self.assertEqual(ip6_ra.sw_if_index, self.pg0.sw_if_index)
+        self.assertFalse(ip6_ra.send_radv)
+        self.assertEqual(ip6_ra.n_prefixes, 0)
+        self.assertEqual(len(ip6_ra.prefixes), 0)
+
     def test_rs(self):
         """IPv6 Router Solicitation Exceptions