#include <vppinfra/mhash.h>
#include <vppinfra/md5.h>
#include <vnet/adj/adj.h>
+#include <vnet/adj/adj_mcast.h>
#include <vnet/fib/fib_table.h>
#include <vnet/fib/ip6_fib.h>
+#include <vnet/mfib/ip6_mfib.h>
/**
* @file
/* local information */
u32 sw_if_index;
- u32 fib_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;
u32 seed;
u64 randomizer;
int ref_count;
- adj_index_t all_nodes_adj_index;
- adj_index_t all_routers_adj_index;
- adj_index_t all_mldv2_routers_adj_index;
+ adj_index_t mcast_adj_index;
/* timing information */
#define DEF_MAX_RADV_INTERVAL 200
/* Link local address to use (defaults to underlying physical for logical interfaces */
ip6_address_t link_local_address;
- u8 link_local_prefix_len;
-
} ip6_radv_t;
typedef struct
nbr = ip6_nd_find (sw_if_index, &adj->sub_type.nbr.next_hop.ip6);
- if (NULL != nbr)
- {
- adj_nbr_walk_nh6 (sw_if_index, &nbr->key.ip6_address,
- ip6_nd_mk_complete_walk, nbr);
- }
- else
+ switch (adj->lookup_next_index)
{
+ case IP_LOOKUP_NEXT_ARP:
+ case IP_LOOKUP_NEXT_GLEAN:
+ if (NULL != nbr)
+ {
+ adj_nbr_walk_nh6 (sw_if_index, &nbr->key.ip6_address,
+ ip6_nd_mk_complete_walk, nbr);
+ }
+ else
+ {
+ /*
+ * no matching ND entry.
+ * construct the rewrite required to for an ND packet, and stick
+ * that in the adj's pipe to smoke.
+ */
+ adj_nbr_update_rewrite (ai,
+ ADJ_NBR_REWRITE_FLAG_INCOMPLETE,
+ ethernet_build_rewrite (vnm,
+ sw_if_index,
+ VNET_LINK_IP6,
+ VNET_REWRITE_FOR_SW_INTERFACE_ADDRESS_BROADCAST));
+
+ /*
+ * since the FIB has added this adj for a route, it makes sense it may
+ * want to forward traffic sometime soon. Let's send a speculative ND.
+ * just one. If we were to do periodically that wouldn't be bad either,
+ * but that's more code than i'm prepared to write at this time for
+ * relatively little reward.
+ */
+ ip6_nbr_probe (adj);
+ }
+ break;
+ case IP_LOOKUP_NEXT_MCAST:
/*
- * no matching ND entry.
- * construct the rewrite required to for an ND packet, and stick
- * that in the adj's pipe to smoke.
+ * Construct a partial rewrite from the known ethernet mcast dest MAC
*/
- adj_nbr_update_rewrite (ai,
- ADJ_NBR_REWRITE_FLAG_INCOMPLETE,
- ethernet_build_rewrite (vnm,
- sw_if_index,
- VNET_LINK_IP6,
- VNET_REWRITE_FOR_SW_INTERFACE_ADDRESS_BROADCAST));
+ adj_mcast_update_rewrite
+ (ai,
+ ethernet_build_rewrite (vnm,
+ sw_if_index,
+ adj->ia_link,
+ ethernet_ip6_mcast_dst_addr ()));
/*
- * since the FIB has added this adj for a route, it makes sense it may
- * want to forward traffic sometime soon. Let's send a speculative ND.
- * just one. If we were to do periodically that wouldn't be bad either,
- * but that's more code than i'm prepared to write at this time for
- * relatively little reward.
+ * Complete the remaining fields of the adj's rewrite to direct the
+ * complete of the rewrite at switch time by copying in the IP
+ * dst address's bytes.
+ * Ofset is 12 bytes from the end of the MAC header - which is 2
+ * bytes into the desintation address. And we write 4 bytes.
*/
- ip6_nbr_probe (adj);
+ adj->rewrite_header.dst_mcast_offset = 12;
+ adj->rewrite_header.dst_mcast_n_bytes = 4;
+
+ break;
+
+ case IP_LOOKUP_NEXT_DROP:
+ case IP_LOOKUP_NEXT_PUNT:
+ case IP_LOOKUP_NEXT_LOCAL:
+ case IP_LOOKUP_NEXT_REWRITE:
+ case IP_LOOKUP_NEXT_LOAD_BALANCE:
+ case IP_LOOKUP_NEXT_MIDCHAIN:
+ case IP_LOOKUP_NEXT_ICMP_ERROR:
+ case IP_LOOKUP_N_NEXT:
+ ASSERT (0);
+ break;
}
}
/* for solicited adverts - need to rate limit */
if (is_solicitation)
{
- if ((now - radv_info->last_radv_time) <
+ if (0 != radv_info->last_radv_time &&
+ (now - radv_info->last_radv_time) <
MIN_DELAY_BETWEEN_RAS)
is_dropped = 1;
else
}
else
{
- adj_index0 = radv_info->all_nodes_adj_index;
+ adj_index0 = radv_info->mcast_adj_index;
if (adj_index0 == 0)
error0 = ICMP6_ERROR_DST_LOOKUP_MISS;
else
{
- ip_adjacency_t *adj0 =
- ip_get_adjacency (&im->lookup_main,
- adj_index0);
- error0 =
- ((adj0->rewrite_header.sw_if_index !=
- sw_if_index0
- || adj0->lookup_next_index !=
- IP_LOOKUP_NEXT_REWRITE) ?
- ICMP6_ERROR_ROUTER_SOLICITATION_DEST_UNKNOWN
- : error0);
next0 =
is_dropped ? next0 :
ICMP6_ROUTER_SOLICITATION_NEXT_REPLY_RW;
return frame->n_vectors;
}
-/* create and initialize router advertisement parameters with default values for this intfc */
+/**
+ * @brief Add a multicast Address to the advertised MLD set
+ */
+static void
+ip6_neighbor_add_mld_prefix (ip6_radv_t * radv_info, ip6_address_t * addr)
+{
+ ip6_mldp_group_t *mcast_group_info;
+ uword *p;
+
+ /* lookup mldp info for this interface */
+ p = mhash_get (&radv_info->address_to_mldp_index, &addr);
+ mcast_group_info =
+ p ? pool_elt_at_index (radv_info->mldp_group_pool, p[0]) : 0;
+
+ /* add address */
+ if (!mcast_group_info)
+ {
+ /* add */
+ u32 mi;
+ pool_get (radv_info->mldp_group_pool, mcast_group_info);
+
+ mi = mcast_group_info - radv_info->mldp_group_pool;
+ mhash_set (&radv_info->address_to_mldp_index, &addr, mi, /* old_value */
+ 0);
+
+ mcast_group_info->type = 4;
+ mcast_group_info->mcast_source_address_pool = 0;
+ mcast_group_info->num_sources = 0;
+ clib_memcpy (&mcast_group_info->mcast_address, &addr,
+ sizeof (ip6_address_t));
+ }
+}
+
+/**
+ * @brief Delete a multicast Address from the advertised MLD set
+ */
+static void
+ip6_neighbor_del_mld_prefix (ip6_radv_t * radv_info, ip6_address_t * addr)
+{
+ ip6_mldp_group_t *mcast_group_info;
+ uword *p;
+
+ p = mhash_get (&radv_info->address_to_mldp_index, &addr);
+ mcast_group_info =
+ p ? pool_elt_at_index (radv_info->mldp_group_pool, p[0]) : 0;
+
+ if (mcast_group_info)
+ {
+ mhash_unset (&radv_info->address_to_mldp_index, &addr,
+ /* old_value */ 0);
+ pool_put (radv_info->mldp_group_pool, mcast_group_info);
+ }
+}
+
+/**
+ * @brief Add a multicast Address to the advertised MLD set
+ */
+static void
+ip6_neighbor_add_mld_grp (ip6_radv_t * a,
+ ip6_multicast_address_scope_t scope,
+ ip6_multicast_link_local_group_id_t group)
+{
+ ip6_address_t addr;
+
+ ip6_set_reserved_multicast_address (&addr, scope, group);
+
+ ip6_neighbor_add_mld_prefix (a, &addr);
+}
+
+/**
+ * @brief create and initialize router advertisement parameters with default
+ * values for this intfc
+ */
static u32
ip6_neighbor_sw_interface_add_del (vnet_main_t * vnm,
u32 sw_if_index, u32 is_add)
if (!is_add)
{
- u32 i, *to_delete = 0;
ip6_radv_prefix_t *p;
ip6_mldp_group_t *m;
- /* remove adjacencies */
- adj_unlock (a->all_nodes_adj_index);
- adj_unlock (a->all_routers_adj_index);
- adj_unlock (a->all_mldv2_routers_adj_index);
+ /* release the lock on the interface's mcast adj */
+ adj_unlock (a->mcast_adj_index);
- /* clean up prefix_pool */
+ /* clean up prefix and MDP pools */
/* *INDENT-OFF* */
- pool_foreach (p, a->adv_prefixes_pool,
+ pool_flush(p, a->adv_prefixes_pool,
({
- vec_add1 (to_delete, p - a->adv_prefixes_pool);
- }));
- /* *INDENT-ON* */
-
- for (i = 0; i < vec_len (to_delete); i++)
- {
- p = pool_elt_at_index (a->adv_prefixes_pool, to_delete[i]);
mhash_unset (&a->address_to_prefix_index, &p->prefix, 0);
- pool_put (a->adv_prefixes_pool, p);
- }
-
- vec_free (to_delete);
- to_delete = 0;
-
- /* clean up mldp group pool */
- /* *INDENT-OFF* */
- pool_foreach (m, a->mldp_group_pool,
+ }));
+ pool_flush (m, a->mldp_group_pool,
({
- vec_add1 (to_delete, m - a->mldp_group_pool);
+ mhash_unset (&a->address_to_mldp_index, &m->mcast_address, 0);
}));
/* *INDENT-ON* */
- for (i = 0; i < vec_len (to_delete); i++)
- {
- m = pool_elt_at_index (a->mldp_group_pool, to_delete[i]);
- mhash_unset (&a->address_to_mldp_index, &m->mcast_address, 0);
- pool_put (a->mldp_group_pool, m);
- }
+ pool_free (a->mldp_group_pool);
+ pool_free (a->adv_prefixes_pool);
- vec_free (to_delete);
+ mhash_free (&a->address_to_prefix_index);
+ mhash_free (&a->address_to_mldp_index);
pool_put (nm->if_radv_pool, a);
nm->if_radv_pool_index_by_sw_if_index[sw_if_index] = ~0;
memset (a, 0, sizeof (a[0]));
a->sw_if_index = sw_if_index;
- a->fib_index = ~0;
a->max_radv_interval = DEF_MAX_RADV_INTERVAL;
a->min_radv_interval = DEF_MIN_RADV_INTERVAL;
a->curr_hop_limit = DEF_CURR_HOP_LIMIT;
a->adv_router_lifetime_in_sec = DEF_DEF_RTR_LIFETIME;
- a->adv_link_layer_address = 1; /* send ll address source address option */
+ /* send ll address source address option */
+ a->adv_link_layer_address = 1;
a->min_delay_between_radv = MIN_DELAY_BETWEEN_RAS;
a->max_delay_between_radv = MAX_DELAY_BETWEEN_RAS;
/* fill in default link-local address (this may be overridden) */
ip6_link_local_address_from_ethernet_address
(&a->link_local_address, eth_if0->address);
- a->link_local_prefix_len = 64;
mhash_init (&a->address_to_prefix_index, sizeof (uword),
sizeof (ip6_address_t));
mhash_init (&a->address_to_mldp_index, sizeof (uword),
sizeof (ip6_address_t));
- {
- u8 link_layer_address[6] = { 0x33, 0x33, 0x00, 0x00, 0x00,
- IP6_MULTICAST_GROUP_ID_all_hosts
- };
-
- a->all_nodes_adj_index =
- adj_rewrite_add_and_lock (FIB_PROTOCOL_IP6, VNET_LINK_IP6,
- sw_if_index, link_layer_address);
- }
-
- {
- u8 link_layer_address[6] = { 0x33, 0x33, 0x00, 0x00, 0x00,
- IP6_MULTICAST_GROUP_ID_all_routers
- };
-
- a->all_routers_adj_index =
- adj_rewrite_add_and_lock (FIB_PROTOCOL_IP6, VNET_LINK_IP6,
- sw_if_index, link_layer_address);
- }
-
- {
- u8 link_layer_address[6] = { 0x33, 0x33, 0x00, 0x00, 0x00,
- IP6_MULTICAST_GROUP_ID_mldv2_routers
- };
-
- a->all_mldv2_routers_adj_index =
- adj_rewrite_add_and_lock (FIB_PROTOCOL_IP6,
- VNET_LINK_IP6,
- sw_if_index, link_layer_address);
- }
+ a->mcast_adj_index = adj_mcast_add_or_lock (FIB_PROTOCOL_IP6,
+ VNET_LINK_IP6,
+ sw_if_index);
/* add multicast groups we will always be reporting */
- ip6_address_t addr;
- ip6_mldp_group_t *mcast_group_info;
-
- ip6_set_reserved_multicast_address (&addr,
- IP6_MULTICAST_SCOPE_link_local,
- IP6_MULTICAST_GROUP_ID_all_hosts);
-
- /* lookup mldp info for this interface */
-
- uword *p = mhash_get (&a->address_to_mldp_index, &addr);
- mcast_group_info =
- p ? pool_elt_at_index (a->mldp_group_pool, p[0]) : 0;
-
- /* add address */
- if (!mcast_group_info)
- {
- /* add */
- u32 mi;
- pool_get (a->mldp_group_pool, mcast_group_info);
-
- mi = mcast_group_info - a->mldp_group_pool;
- mhash_set (&a->address_to_mldp_index, &addr, mi, /* old_value */
- 0);
-
- mcast_group_info->type = 4;
- mcast_group_info->mcast_source_address_pool = 0;
- mcast_group_info->num_sources = 0;
- clib_memcpy (&mcast_group_info->mcast_address, &addr,
- sizeof (ip6_address_t));
- }
-
- ip6_set_reserved_multicast_address (&addr,
- IP6_MULTICAST_SCOPE_link_local,
- IP6_MULTICAST_GROUP_ID_all_routers);
-
- p = mhash_get (&a->address_to_mldp_index, &addr);
- mcast_group_info =
- p ? pool_elt_at_index (a->mldp_group_pool, p[0]) : 0;
-
- if (!mcast_group_info)
- {
- /* add */
- u32 mi;
- pool_get (a->mldp_group_pool, mcast_group_info);
-
- mi = mcast_group_info - a->mldp_group_pool;
- mhash_set (&a->address_to_mldp_index, &addr, mi, /* old_value */
- 0);
-
- mcast_group_info->type = 4;
- mcast_group_info->mcast_source_address_pool = 0;
- mcast_group_info->num_sources = 0;
- clib_memcpy (&mcast_group_info->mcast_address, &addr,
- sizeof (ip6_address_t));
- }
-
- ip6_set_reserved_multicast_address (&addr,
- IP6_MULTICAST_SCOPE_link_local,
- IP6_MULTICAST_GROUP_ID_mldv2_routers);
-
- p = mhash_get (&a->address_to_mldp_index, &addr);
- mcast_group_info =
- p ? pool_elt_at_index (a->mldp_group_pool, p[0]) : 0;
-
- if (!mcast_group_info)
- {
- /* add */
- u32 mi;
- pool_get (a->mldp_group_pool, mcast_group_info);
-
- mi = mcast_group_info - a->mldp_group_pool;
- mhash_set (&a->address_to_mldp_index, &addr, mi, /* old_value */
- 0);
-
- mcast_group_info->type = 4;
- mcast_group_info->mcast_source_address_pool = 0;
- mcast_group_info->num_sources = 0;
- clib_memcpy (&mcast_group_info->mcast_address, &addr,
- sizeof (ip6_address_t));
- }
+ ip6_neighbor_add_mld_grp (a,
+ IP6_MULTICAST_SCOPE_link_local,
+ IP6_MULTICAST_GROUP_ID_all_hosts);
+ ip6_neighbor_add_mld_grp (a,
+ IP6_MULTICAST_SCOPE_link_local,
+ IP6_MULTICAST_GROUP_ID_all_routers);
+ ip6_neighbor_add_mld_grp (a,
+ IP6_MULTICAST_SCOPE_link_local,
+ IP6_MULTICAST_GROUP_ID_mldv2_routers);
}
}
return ri;
vnet_buffer (b0)->sw_if_index[VLIB_RX] =
vnet_main.local_interface_sw_if_index;
- vnet_buffer (b0)->ip.adj_index[VLIB_TX] =
- radv_info->all_mldv2_routers_adj_index;
+ vnet_buffer (b0)->ip.adj_index[VLIB_TX] = radv_info->mcast_adj_index;
b0->flags |= VNET_BUFFER_LOCALLY_ORIGINATED;
- vlib_node_t *node = vlib_get_node_by_name (vm, (u8 *) "ip6-rewrite");
+ vlib_node_t *node = vlib_get_node_by_name (vm, (u8 *) "ip6-rewrite-mcast");
f = vlib_get_frame_to_node (vm, node->index);
to_next = vlib_frame_vector_args (f);
.n_next_nodes = ICMP6_ROUTER_SOLICITATION_N_NEXT,
.next_nodes = {
[ICMP6_ROUTER_SOLICITATION_NEXT_DROP] = "error-drop",
- [ICMP6_ROUTER_SOLICITATION_NEXT_REPLY_RW] = "ip6-rewrite",
+ [ICMP6_ROUTER_SOLICITATION_NEXT_REPLY_RW] = "ip6-rewrite-mcast",
[ICMP6_ROUTER_SOLICITATION_NEXT_REPLY_TX] = "interface-output",
},
};
else if (unformat (line_input, "ra-lifetime"))
{
if (!unformat (line_input, "%d", &ra_lifetime))
- return (error = unformat_parse_error (line_input));
+ {
+ error = unformat_parse_error (line_input);
+ goto done;
+ }
use_lifetime = 1;
break;
}
{
if (!unformat
(line_input, "%d %d", &ra_initial_count, &ra_initial_interval))
- return (error = unformat_parse_error (line_input));
+ {
+ error = unformat_parse_error (line_input);
+ goto done;
+ }
break;
}
else if (unformat (line_input, "ra-interval"))
{
if (!unformat (line_input, "%d", &ra_max_interval))
- return (error = unformat_parse_error (line_input));
+ {
+ error = unformat_parse_error (line_input);
+ goto done;
+ }
if (!unformat (line_input, "%d", &ra_min_interval))
ra_min_interval = 0;
break;
}
else
- return (unformat_parse_error (line_input));
+ {
+ error = unformat_parse_error (line_input);
+ goto done;
+ }
}
if (add_radv_info)
else if (unformat (line_input, "no-onlink"))
no_onlink = 1;
else
- return (unformat_parse_error (line_input));
+ {
+ error = unformat_parse_error (line_input);
+ goto done;
+ }
}
ip6_neighbor_ra_prefix (vm, sw_if_index,
off_link, no_autoconfig, no_onlink, is_no);
}
+done:
unformat_free (line_input);
-done:
return error;
}
radv_info = pool_elt_at_index (nm->if_radv_pool, ri);
/* check radv_info ref count for other ip6 addresses on this interface */
+ /* This implicitly excludes the link local address */
if (radv_info->ref_count == 0)
{
/* essentially "disables" ipv6 on this interface */
error = ip6_add_del_interface_address (vm, sw_if_index,
&radv_info->
- link_local_address,
- radv_info->
- link_local_prefix_len,
+ link_local_address, 128,
1 /* is_del */ );
ip6_neighbor_sw_interface_add_del (vnm, sw_if_index,
0 /* is_add */ );
+ ip6_mfib_interface_enable_disable (sw_if_index, 0);
}
}
return error;
link_local_address.as_u8[8] &= 0xfd;
}
+ ip6_mfib_interface_enable_disable (sw_if_index, 1);
+
/* essentially "enables" ipv6 on this interface */
error = ip6_add_del_interface_address (vm, sw_if_index,
&link_local_address,
else
{
radv_info->link_local_address = link_local_address;
- radv_info->link_local_prefix_len = 64;
}
}
}
clib_error_t *
set_ip6_link_local_address (vlib_main_t * vm,
- u32 sw_if_index,
- ip6_address_t * address, u8 address_length)
+ u32 sw_if_index, ip6_address_t * address)
{
clib_error_t *error = 0;
ip6_neighbor_main_t *nm = &ip6_neighbor_main;
/* delete the old one */
error = ip6_add_del_interface_address (vm, sw_if_index,
&radv_info->link_local_address,
- radv_info->link_local_prefix_len
- /* address width */ ,
- 1 /* is_del */ );
+ 128, 1 /* is_del */ );
if (!error)
{
/* add the new one */
error = ip6_add_del_interface_address (vm, sw_if_index,
- address, address_length
- /* address width */ ,
+ address, 128,
0 /* is_del */ );
if (!error)
{
radv_info->link_local_address = *address;
- radv_info->link_local_prefix_len = address_length;
}
}
}
clib_error_t *error = 0;
u32 sw_if_index;
ip6_address_t ip6_addr;
- u32 addr_len = 0;
if (unformat_user (input, unformat_vnet_sw_interface, vnm, &sw_if_index))
{
/* get the rest of the command */
while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
{
- if (unformat (input, "%U/%d",
- unformat_ip6_address, &ip6_addr, &addr_len))
+ if (unformat (input, "%U", unformat_ip6_address, &ip6_addr))
break;
else
return (unformat_parse_error (input));
}
}
- error = set_ip6_link_local_address (vm, sw_if_index, &ip6_addr, addr_len);
+ error = set_ip6_link_local_address (vm, sw_if_index, &ip6_addr);
return error;
}
*
* @cliexpar
* Example of how to assign an IPv6 Link-local address to an interface:
- * @cliexcmd{set ip6 link-local address GigabitEthernet2/0/0 FE80::AB8/64}
+ * @cliexcmd{set ip6 link-local address GigabitEthernet2/0/0 FE80::AB8}
?*/
/* *INDENT-OFF* */
VLIB_CLI_COMMAND (set_ip6_link_local_address_command, static) =
{
.path = "set ip6 link-local address",
- .short_help = "set ip6 link-local address <interface> <ip6-address>/<width>",
+ .short_help = "set ip6 link-local address <interface> <ip6-address>",
.function = set_ip6_link_local_address_cmd,
};
/* *INDENT-ON* */
-/* callback when an interface address is added or deleted */
+/**
+ * @brief callback when an interface address is added or deleted
+ */
static void
ip6_neighbor_add_del_interface_address (ip6_main_t * im,
uword opaque,
vlib_main_t *vm = vnm->vlib_main;
ip6_radv_t *radv_info;
ip6_address_t a;
- ip6_mldp_group_t *mcast_group_info;
/* create solicited node multicast address for this interface adddress */
ip6_set_solicited_node_multicast_address (&a, 0);
if (!ip6_address_is_link_local_unicast (address))
radv_info->ref_count++;
- /* lookup prefix info for this address on this interface */
- uword *p = mhash_get (&radv_info->address_to_mldp_index, &a);
- mcast_group_info =
- p ? pool_elt_at_index (radv_info->mldp_group_pool, p[0]) : 0;
-
- /* add -solicted node multicast address */
- if (!mcast_group_info)
- {
- /* add */
- u32 mi;
- pool_get (radv_info->mldp_group_pool, mcast_group_info);
-
- mi = mcast_group_info - radv_info->mldp_group_pool;
- mhash_set (&radv_info->address_to_mldp_index, &a, mi,
- /* old_value */ 0);
-
- mcast_group_info->type = 4;
- mcast_group_info->mcast_source_address_pool = 0;
- mcast_group_info->num_sources = 0;
- clib_memcpy (&mcast_group_info->mcast_address, &a,
- sizeof (ip6_address_t));
- }
+ ip6_neighbor_add_mld_prefix (radv_info, &a);
}
}
else
vec_validate_init_empty (nm->if_radv_pool_index_by_sw_if_index,
sw_if_index, ~0);
ri = nm->if_radv_pool_index_by_sw_if_index[sw_if_index];
+
if (ri != ~0)
{
/* get radv_info */
radv_info = pool_elt_at_index (nm->if_radv_pool, ri);
- /* lookup prefix info for this address on this interface */
- uword *p = mhash_get (&radv_info->address_to_mldp_index, &a);
- mcast_group_info =
- p ? pool_elt_at_index (radv_info->mldp_group_pool, p[0]) : 0;
-
- if (mcast_group_info)
- {
- mhash_unset (&radv_info->address_to_mldp_index, &a,
- /* old_value */ 0);
- pool_put (radv_info->mldp_group_pool, mcast_group_info);
- }
+ ip6_neighbor_del_mld_prefix (radv_info, &a);
/* if interface up send MLDP "report" */
radv_info->all_routers_mcast = 0;
if (!ip6_address_is_link_local_unicast (address))
radv_info->ref_count--;
}
+ /* Ensure that IPv6 is disabled, and LL removed after ref_count reaches 0 */
+ disable_ip6_interface (vm, sw_if_index);
}
}