/* Router advertisement
 * Copyright (C) 2016 Cumulus Networks
 * Copyright (C) 2005 6WIND <jean-mickael.guerin@6wind.com>
 * Copyright (C) 1999 Kunihiro Ishiguro
 *
 * This file is part of GNU Zebra.
 *
 * GNU Zebra is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * GNU Zebra is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; see the file COPYING; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <zebra.h>

#include "memory.h"
#include "sockopt.h"
#include "thread.h"
#include "if.h"
#include "stream.h"
#include "log.h"
#include "prefix.h"
#include "linklist.h"
#include "command.h"
#include "privs.h"
#include "vrf.h"
#include "ns.h"
#include "lib_errors.h"

#include "zebra/interface.h"
#include "zebra/rtadv.h"
#include "zebra/debug.h"
#include "zebra/rib.h"
#include "zebra/zapi_msg.h"
#include "zebra/zebra_vrf.h"
#include "zebra/zebra_errors.h"
#include "zebra/zebra_router.h"

extern struct zebra_privs_t zserv_privs;

static uint32_t interfaces_configured_for_ra_from_bgp;

#if defined(HAVE_RTADV)

#ifndef VTYSH_EXTRACT_PL
#include "zebra/rtadv_clippy.c"
#endif

DEFINE_MTYPE_STATIC(ZEBRA, RTADV_PREFIX, "Router Advertisement Prefix");
DEFINE_MTYPE_STATIC(ZEBRA, ADV_IF, "Advertised Interface");

#ifdef OPEN_BSD
#include <netinet/icmp6.h>
#endif

/* If RFC2133 definition is used. */
#ifndef IPV6_JOIN_GROUP
#define IPV6_JOIN_GROUP  IPV6_ADD_MEMBERSHIP
#endif
#ifndef IPV6_LEAVE_GROUP
#define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP
#endif

#define ALLNODE   "ff02::1"
#define ALLROUTER "ff02::2"

/* adv list node */
struct adv_if {
	char name[INTERFACE_NAMSIZ];
	struct adv_if_list_item list_item;
};

static int adv_if_cmp(const struct adv_if *a, const struct adv_if *b)
{
	return if_cmp_name_func(a->name, b->name);
}

DECLARE_SORTLIST_UNIQ(adv_if_list, struct adv_if, list_item, adv_if_cmp);

static int rtadv_prefix_cmp(const struct rtadv_prefix *a,
			    const struct rtadv_prefix *b)
{
	return prefix_cmp(&a->prefix, &b->prefix);
}

DECLARE_RBTREE_UNIQ(rtadv_prefixes, struct rtadv_prefix, item,
		    rtadv_prefix_cmp);

DEFINE_MTYPE_STATIC(ZEBRA, RTADV_RDNSS, "Router Advertisement RDNSS");
DEFINE_MTYPE_STATIC(ZEBRA, RTADV_DNSSL, "Router Advertisement DNSSL");

/* Order is intentional.  Matches RFC4191.  This array is also used for
   command matching, so only modify with care. */
static const char *const rtadv_pref_strs[] = {
	"medium", "high", "INVALID", "low", 0
};

enum rtadv_event {
	RTADV_START,
	RTADV_STOP,
	RTADV_TIMER,
	RTADV_TIMER_MSEC,
	RTADV_READ
};

static void rtadv_event(struct zebra_vrf *, enum rtadv_event, int);

static int if_join_all_router(int, struct interface *);
static int if_leave_all_router(int, struct interface *);

static struct zebra_vrf *rtadv_interface_get_zvrf(const struct interface *ifp)
{
	/* We use the default vrf for rtadv handling except in netns */
	if (!vrf_is_backend_netns())
		return vrf_info_lookup(VRF_DEFAULT);

	return ifp->vrf->info;
}

static int rtadv_increment_received(struct zebra_vrf *zvrf, ifindex_t *ifindex)
{
	int ret = -1;
	struct interface *iface;
	struct zebra_if *zif;

	iface = if_lookup_by_index(*ifindex, zvrf->vrf->vrf_id);
	if (iface && iface->info) {
		zif = iface->info;
		zif->ra_rcvd++;
		ret = 0;
	}
	return ret;
}

static int rtadv_recv_packet(struct zebra_vrf *zvrf, int sock, uint8_t *buf,
			     int buflen, struct sockaddr_in6 *from,
			     ifindex_t *ifindex, int *hoplimit)
{
	int ret;
	struct msghdr msg;
	struct iovec iov;
	struct cmsghdr *cmsgptr;
	struct in6_addr dst;

	char adata[1024];

	/* Fill in message and iovec. */
	memset(&msg, 0, sizeof(msg));
	msg.msg_name = (void *)from;
	msg.msg_namelen = sizeof(struct sockaddr_in6);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = (void *)adata;
	msg.msg_controllen = sizeof(adata);
	iov.iov_base = buf;
	iov.iov_len = buflen;

	/* If recvmsg fail return minus value. */
	ret = recvmsg(sock, &msg, 0);
	if (ret < 0)
		return ret;

	for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL;
	     cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) {
		/* I want interface index which this packet comes from. */
		if (cmsgptr->cmsg_level == IPPROTO_IPV6
		    && cmsgptr->cmsg_type == IPV6_PKTINFO) {
			struct in6_pktinfo *ptr;

			ptr = (struct in6_pktinfo *)CMSG_DATA(cmsgptr);
			*ifindex = ptr->ipi6_ifindex;
			memcpy(&dst, &ptr->ipi6_addr, sizeof(ptr->ipi6_addr));
		}

		/* Incoming packet's hop limit. */
		if (cmsgptr->cmsg_level == IPPROTO_IPV6
		    && cmsgptr->cmsg_type == IPV6_HOPLIMIT) {
			int *hoptr = (int *)CMSG_DATA(cmsgptr);
			*hoplimit = *hoptr;
		}
	}

	rtadv_increment_received(zvrf, ifindex);
	return ret;
}

#define RTADV_MSG_SIZE 4096

/* Send router advertisement packet. */
static void rtadv_send_packet(int sock, struct interface *ifp,
			      enum ipv6_nd_suppress_ra_status stop)
{
	struct msghdr msg;
	struct iovec iov;
	struct cmsghdr *cmsgptr;
	struct in6_pktinfo *pkt;
	struct sockaddr_in6 addr;
	static void *adata = NULL;
	unsigned char buf[RTADV_MSG_SIZE];
	struct nd_router_advert *rtadv;
	int ret;
	int len = 0;
	struct zebra_if *zif;
	struct rtadv_prefix *rprefix;
	uint8_t all_nodes_addr[] = {0xff, 0x02, 0, 0, 0, 0, 0, 0,
				    0,    0,    0, 0, 0, 0, 0, 1};
	struct listnode *node;
	uint16_t pkt_RouterLifetime;

	/*
	 * Allocate control message bufffer.  This is dynamic because
	 * CMSG_SPACE is not guaranteed not to call a function.  Note that
	 * the size will be different on different architectures due to
	 * differing alignment rules.
	 */
	if (adata == NULL) {
		/* XXX Free on shutdown. */
		adata = calloc(1, CMSG_SPACE(sizeof(struct in6_pktinfo)));

		if (adata == NULL) {
			zlog_debug("%s: can't malloc control data", __func__);
			exit(-1);
		}
	}

	/* Logging of packet. */
	if (IS_ZEBRA_DEBUG_PACKET)
		zlog_debug("%s(%s:%u): Tx RA, socket %u", ifp->name,
			   ifp->vrf->name, ifp->ifindex, sock);

	/* Fill in sockaddr_in6. */
	memset(&addr, 0, sizeof(struct sockaddr_in6));
	addr.sin6_family = AF_INET6;
#ifdef SIN6_LEN
	addr.sin6_len = sizeof(struct sockaddr_in6);
#endif /* SIN6_LEN */
	addr.sin6_port = htons(IPPROTO_ICMPV6);
	IPV6_ADDR_COPY(&addr.sin6_addr, all_nodes_addr);

	/* Fetch interface information. */
	zif = ifp->info;

	/* Make router advertisement message. */
	rtadv = (struct nd_router_advert *)buf;

	rtadv->nd_ra_type = ND_ROUTER_ADVERT;
	rtadv->nd_ra_code = 0;
	rtadv->nd_ra_cksum = 0;

	rtadv->nd_ra_curhoplimit = zif->rtadv.AdvCurHopLimit;

	/* RFC4191: Default Router Preference is 0 if Router Lifetime is 0. */
	rtadv->nd_ra_flags_reserved = zif->rtadv.AdvDefaultLifetime == 0
					      ? 0
					      : zif->rtadv.DefaultPreference;
	rtadv->nd_ra_flags_reserved <<= 3;

	if (zif->rtadv.AdvManagedFlag)
		rtadv->nd_ra_flags_reserved |= ND_RA_FLAG_MANAGED;
	if (zif->rtadv.AdvOtherConfigFlag)
		rtadv->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER;
	if (zif->rtadv.AdvHomeAgentFlag)
		rtadv->nd_ra_flags_reserved |= ND_RA_FLAG_HOME_AGENT;
	/* Note that according to Neighbor Discovery (RFC 4861 [18]),
	 * AdvDefaultLifetime is by default based on the value of
	 * MaxRtrAdvInterval.  AdvDefaultLifetime is used in the Router Lifetime
	 * field of Router Advertisements.  Given that this field is expressed
	 * in seconds, a small MaxRtrAdvInterval value can result in a zero
	 * value for this field.  To prevent this, routers SHOULD keep
	 * AdvDefaultLifetime in at least one second, even if the use of
	 * MaxRtrAdvInterval would result in a smaller value. -- RFC6275, 7.5 */
	pkt_RouterLifetime =
		zif->rtadv.AdvDefaultLifetime != -1
			? zif->rtadv.AdvDefaultLifetime
			: MAX(1, 0.003 * zif->rtadv.MaxRtrAdvInterval);

	/* send RA lifetime of 0 before stopping. rfc4861/6.2.5 */
	rtadv->nd_ra_router_lifetime =
		(stop == RA_SUPPRESS) ? htons(0) : htons(pkt_RouterLifetime);
	rtadv->nd_ra_reachable = htonl(zif->rtadv.AdvReachableTime);
	rtadv->nd_ra_retransmit = htonl(zif->rtadv.AdvRetransTimer);

	len = sizeof(struct nd_router_advert);

	/* If both the Home Agent Preference and Home Agent Lifetime are set to
	 * their default values specified above, this option SHOULD NOT be
	 * included in the Router Advertisement messages sent by this home
	 * agent. -- RFC6275, 7.4 */
	if (zif->rtadv.AdvHomeAgentFlag
	    && (zif->rtadv.HomeAgentPreference
		|| zif->rtadv.HomeAgentLifetime != -1)) {
		struct nd_opt_homeagent_info *ndopt_hai =
			(struct nd_opt_homeagent_info *)(buf + len);
		ndopt_hai->nd_opt_hai_type = ND_OPT_HA_INFORMATION;
		ndopt_hai->nd_opt_hai_len = 1;
		ndopt_hai->nd_opt_hai_reserved = 0;
		ndopt_hai->nd_opt_hai_preference =
			htons(zif->rtadv.HomeAgentPreference);
		/* 16-bit unsigned integer.  The lifetime associated with the
		 * home
		 * agent in units of seconds.  The default value is the same as
		 * the
		 * Router Lifetime, as specified in the main body of the Router
		 * Advertisement.  The maximum value corresponds to 18.2 hours.
		 * A
		 * value of 0 MUST NOT be used. -- RFC6275, 7.5 */
		ndopt_hai->nd_opt_hai_lifetime =
			htons(zif->rtadv.HomeAgentLifetime != -1
				      ? zif->rtadv.HomeAgentLifetime
				      : MAX(1, pkt_RouterLifetime) /* 0 is OK
								      for RL,
								      but not
								      for HAL*/
			      );
		len += sizeof(struct nd_opt_homeagent_info);
	}

	if (zif->rtadv.AdvIntervalOption) {
		struct nd_opt_adv_interval *ndopt_adv =
			(struct nd_opt_adv_interval *)(buf + len);
		ndopt_adv->nd_opt_ai_type = ND_OPT_ADV_INTERVAL;
		ndopt_adv->nd_opt_ai_len = 1;
		ndopt_adv->nd_opt_ai_reserved = 0;
		ndopt_adv->nd_opt_ai_interval =
			htonl(zif->rtadv.MaxRtrAdvInterval);
		len += sizeof(struct nd_opt_adv_interval);
	}

	/* Fill in prefix. */
	frr_each (rtadv_prefixes, zif->rtadv.prefixes, rprefix) {
		struct nd_opt_prefix_info *pinfo;

		pinfo = (struct nd_opt_prefix_info *)(buf + len);

		pinfo->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION;
		pinfo->nd_opt_pi_len = 4;
		pinfo->nd_opt_pi_prefix_len = rprefix->prefix.prefixlen;

		pinfo->nd_opt_pi_flags_reserved = 0;
		if (rprefix->AdvOnLinkFlag)
			pinfo->nd_opt_pi_flags_reserved |=
				ND_OPT_PI_FLAG_ONLINK;
		if (rprefix->AdvAutonomousFlag)
			pinfo->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO;
		if (rprefix->AdvRouterAddressFlag)
			pinfo->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_RADDR;

		pinfo->nd_opt_pi_valid_time = htonl(rprefix->AdvValidLifetime);
		pinfo->nd_opt_pi_preferred_time =
			htonl(rprefix->AdvPreferredLifetime);
		pinfo->nd_opt_pi_reserved2 = 0;

		IPV6_ADDR_COPY(&pinfo->nd_opt_pi_prefix,
			       &rprefix->prefix.prefix);

		len += sizeof(struct nd_opt_prefix_info);
	}

	/* Hardware address. */
	if (ifp->hw_addr_len != 0) {
		buf[len++] = ND_OPT_SOURCE_LINKADDR;

		/* Option length should be rounded up to next octet if
		   the link address does not end on an octet boundary. */
		buf[len++] = (ifp->hw_addr_len + 9) >> 3;

		memcpy(buf + len, ifp->hw_addr, ifp->hw_addr_len);
		len += ifp->hw_addr_len;

		/* Pad option to end on an octet boundary. */
		memset(buf + len, 0, -(ifp->hw_addr_len + 2) & 0x7);
		len += -(ifp->hw_addr_len + 2) & 0x7;
	}

	/* MTU */
	if (zif->rtadv.AdvLinkMTU) {
		struct nd_opt_mtu *opt = (struct nd_opt_mtu *)(buf + len);
		opt->nd_opt_mtu_type = ND_OPT_MTU;
		opt->nd_opt_mtu_len = 1;
		opt->nd_opt_mtu_reserved = 0;
		opt->nd_opt_mtu_mtu = htonl(zif->rtadv.AdvLinkMTU);
		len += sizeof(struct nd_opt_mtu);
	}

	/*
	 * There is no limit on the number of configurable recursive DNS
	 * servers or search list entries. We don't want the RA message
	 * to exceed the link's MTU (risking fragmentation) or even
	 * blow the stack buffer allocated for it.
	 */
	size_t max_len = MIN(ifp->mtu6 - 40, sizeof(buf));

	/* Recursive DNS servers */
	struct rtadv_rdnss *rdnss;

	for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvRDNSSList, node, rdnss)) {
		size_t opt_len =
			sizeof(struct nd_opt_rdnss) + sizeof(struct in6_addr);

		if (len + opt_len > max_len) {
			zlog_warn(
				"%s(%s:%u): Tx RA: RDNSS option would exceed MTU, omitting it",
				ifp->name, ifp->vrf->name, ifp->ifindex);
			goto no_more_opts;
		}
		struct nd_opt_rdnss *opt = (struct nd_opt_rdnss *)(buf + len);

		opt->nd_opt_rdnss_type = ND_OPT_RDNSS;
		opt->nd_opt_rdnss_len = opt_len / 8;
		opt->nd_opt_rdnss_reserved = 0;
		opt->nd_opt_rdnss_lifetime = htonl(
			rdnss->lifetime_set
				? rdnss->lifetime
				: MAX(1, 0.003 * zif->rtadv.MaxRtrAdvInterval));

		len += sizeof(struct nd_opt_rdnss);

		IPV6_ADDR_COPY(buf + len, &rdnss->addr);
		len += sizeof(struct in6_addr);
	}

	/* DNS search list */
	struct rtadv_dnssl *dnssl;

	for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvDNSSLList, node, dnssl)) {
		size_t opt_len = sizeof(struct nd_opt_dnssl)
				 + ((dnssl->encoded_len + 7) & ~7);

		if (len + opt_len > max_len) {
			zlog_warn(
				"%s(%u): Tx RA: DNSSL option would exceed MTU, omitting it",
				ifp->name, ifp->ifindex);
			goto no_more_opts;
		}
		struct nd_opt_dnssl *opt = (struct nd_opt_dnssl *)(buf + len);

		opt->nd_opt_dnssl_type = ND_OPT_DNSSL;
		opt->nd_opt_dnssl_len = opt_len / 8;
		opt->nd_opt_dnssl_reserved = 0;
		opt->nd_opt_dnssl_lifetime = htonl(
			dnssl->lifetime_set
				? dnssl->lifetime
				: MAX(1, 0.003 * zif->rtadv.MaxRtrAdvInterval));

		len += sizeof(struct nd_opt_dnssl);

		memcpy(buf + len, dnssl->encoded_name, dnssl->encoded_len);
		len += dnssl->encoded_len;

		/* Zero-pad to 8-octet boundary */
		while (len % 8)
			buf[len++] = '\0';
	}

no_more_opts:

	msg.msg_name = (void *)&addr;
	msg.msg_namelen = sizeof(struct sockaddr_in6);
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = (void *)adata;
	msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
	msg.msg_flags = 0;
	iov.iov_base = buf;
	iov.iov_len = len;

	cmsgptr = CMSG_FIRSTHDR(&msg);
	cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
	cmsgptr->cmsg_level = IPPROTO_IPV6;
	cmsgptr->cmsg_type = IPV6_PKTINFO;

	pkt = (struct in6_pktinfo *)CMSG_DATA(cmsgptr);
	memset(&pkt->ipi6_addr, 0, sizeof(struct in6_addr));
	pkt->ipi6_ifindex = ifp->ifindex;

	ret = sendmsg(sock, &msg, 0);
	if (ret < 0) {
		flog_err_sys(EC_LIB_SOCKET,
			     "%s(%u): Tx RA failed, socket %u error %d (%s)",
			     ifp->name, ifp->ifindex, sock, errno,
			     safe_strerror(errno));
	} else
		zif->ra_sent++;
}

static void rtadv_timer(struct thread *thread)
{
	struct zebra_vrf *zvrf = THREAD_ARG(thread);
	struct vrf *vrf;
	struct interface *ifp;
	struct zebra_if *zif;
	int period;

	zvrf->rtadv.ra_timer = NULL;
	if (adv_if_list_count(&zvrf->rtadv.adv_msec_if) == 0) {
		period = 1000; /* 1 s */
		rtadv_event(zvrf, RTADV_TIMER, 1 /* 1 s */);
	} else {
		period = 10; /* 10 ms */
		rtadv_event(zvrf, RTADV_TIMER_MSEC, 10 /* 10 ms */);
	}

	RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id)
		FOR_ALL_INTERFACES (vrf, ifp) {
			if (if_is_loopback(ifp) || !if_is_operative(ifp) ||
			    IS_ZEBRA_IF_BRIDGE_SLAVE(ifp) ||
			    !connected_get_linklocal(ifp) ||
			    (vrf_is_backend_netns() &&
			     ifp->vrf->vrf_id != zvrf->vrf->vrf_id))
				continue;

			zif = ifp->info;

			if (zif->rtadv.AdvSendAdvertisements) {
				if (zif->rtadv.inFastRexmit
				    && zif->rtadv.UseFastRexmit) {
					/* We assume we fast rexmit every sec so
					 * no
					 * additional vars */
					if (--zif->rtadv.NumFastReXmitsRemain
					    <= 0)
						zif->rtadv.inFastRexmit = 0;

					if (IS_ZEBRA_DEBUG_SEND)
						zlog_debug(
							"Fast RA Rexmit on interface %s(%s:%u)",
							ifp->name,
							ifp->vrf->name,
							ifp->ifindex);

					rtadv_send_packet(zvrf->rtadv.sock, ifp,
							  RA_ENABLE);
				} else {
					zif->rtadv.AdvIntervalTimer -= period;
					if (zif->rtadv.AdvIntervalTimer <= 0) {
						/* FIXME: using
						   MaxRtrAdvInterval each
						   time isn't what section
						   6.2.4 of RFC4861 tells to do.
						   */
						zif->rtadv.AdvIntervalTimer =
							zif->rtadv
								.MaxRtrAdvInterval;
						rtadv_send_packet(
							zvrf->rtadv.sock, ifp,
							RA_ENABLE);
					}
				}
			}
		}
}

static void rtadv_process_solicit(struct interface *ifp)
{
	struct zebra_vrf *zvrf;
	struct zebra_if *zif;

	zvrf = rtadv_interface_get_zvrf(ifp);
	assert(zvrf);
	zif = ifp->info;

	/*
	 * If FastRetransmit is enabled, send the RA immediately.
	 * If not enabled but it has been more than MIN_DELAY_BETWEEN_RAS
	 * (3 seconds) since the last RA was sent, send it now and reset
	 * the timer to start at the max (configured) again.
	 * If not enabled and it is less than 3 seconds since the last
	 * RA packet was sent, set the timer for 3 seconds so the next
	 * one will be sent with a minimum of 3 seconds between RAs.
	 * RFC4861 sec 6.2.6
	 */
	if ((zif->rtadv.UseFastRexmit)
	    || (zif->rtadv.AdvIntervalTimer <=
		(zif->rtadv.MaxRtrAdvInterval - MIN_DELAY_BETWEEN_RAS))) {
		rtadv_send_packet(zvrf->rtadv.sock, ifp, RA_ENABLE);
		zif->rtadv.AdvIntervalTimer = zif->rtadv.MaxRtrAdvInterval;
	} else
		zif->rtadv.AdvIntervalTimer = MIN_DELAY_BETWEEN_RAS;
}

/*
 * This function processes optional attributes off of
 * end of a RA packet received.  At this point in
 * time we only care about this in one situation
 * which is when a interface does not have a LL
 * v6 address.  We still need to be able to install
 * the mac address for v4 to v6 resolution
 */
static void rtadv_process_optional(uint8_t *optional, unsigned int len,
				   struct interface *ifp,
				   struct sockaddr_in6 *addr)
{
	char *mac;

	while (len > 0) {
		struct nd_opt_hdr *opt_hdr = (struct nd_opt_hdr *)optional;

		switch(opt_hdr->nd_opt_type) {
		case ND_OPT_SOURCE_LINKADDR:
			mac = (char *)(optional+2);
			if_nbr_mac_to_ipv4ll_neigh_update(ifp, mac,
							  &addr->sin6_addr, 1);
			break;
		default:
			break;
		}

		len -= 8 * opt_hdr->nd_opt_len;
		optional += 8 * opt_hdr->nd_opt_len;
	}
}

static void rtadv_process_advert(uint8_t *msg, unsigned int len,
				 struct interface *ifp,
				 struct sockaddr_in6 *addr)
{
	struct nd_router_advert *radvert;
	char addr_str[INET6_ADDRSTRLEN];
	struct zebra_if *zif;
	struct prefix p;

	zif = ifp->info;

	inet_ntop(AF_INET6, &addr->sin6_addr, addr_str, INET6_ADDRSTRLEN);

	if (len < sizeof(struct nd_router_advert)) {
		if (IS_ZEBRA_DEBUG_PACKET)
			zlog_debug(
				"%s(%s:%u): Rx RA with invalid length %d from %s",
				ifp->name, ifp->vrf->name, ifp->ifindex, len,
				addr_str);
		return;
	}

	if (!IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr)) {
		rtadv_process_optional(msg + sizeof(struct nd_router_advert),
				       len - sizeof(struct nd_router_advert),
				       ifp, addr);
		if (IS_ZEBRA_DEBUG_PACKET)
			zlog_debug(
				"%s(%s:%u): Rx RA with non-linklocal source address from %s",
				ifp->name, ifp->vrf->name, ifp->ifindex,
				addr_str);
		return;
	}

	radvert = (struct nd_router_advert *)msg;

#define SIXHOUR2USEC (int64_t)6 * 60 * 60 * 1000000

	if ((radvert->nd_ra_curhoplimit && zif->rtadv.AdvCurHopLimit) &&
	    (radvert->nd_ra_curhoplimit != zif->rtadv.AdvCurHopLimit) &&
	    (monotime_since(&zif->rtadv.lastadvcurhoplimit, NULL) >
		     SIXHOUR2USEC ||
	     zif->rtadv.lastadvcurhoplimit.tv_sec == 0)) {
		flog_warn(
			EC_ZEBRA_RA_PARAM_MISMATCH,
			"%s(%u): Rx RA - our AdvCurHopLimit doesn't agree with %s",
			ifp->name, ifp->ifindex, addr_str);
		monotime(&zif->rtadv.lastadvcurhoplimit);
	}

	if ((radvert->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) &&
	    !zif->rtadv.AdvManagedFlag &&
	    (monotime_since(&zif->rtadv.lastadvmanagedflag, NULL) >
		     SIXHOUR2USEC ||
	     zif->rtadv.lastadvmanagedflag.tv_sec == 0)) {
		flog_warn(
			EC_ZEBRA_RA_PARAM_MISMATCH,
			"%s(%u): Rx RA - our AdvManagedFlag doesn't agree with %s",
			ifp->name, ifp->ifindex, addr_str);
		monotime(&zif->rtadv.lastadvmanagedflag);
	}

	if ((radvert->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) &&
	    !zif->rtadv.AdvOtherConfigFlag &&
	    (monotime_since(&zif->rtadv.lastadvotherconfigflag, NULL) >
		     SIXHOUR2USEC ||
	     zif->rtadv.lastadvotherconfigflag.tv_sec == 0)) {
		flog_warn(
			EC_ZEBRA_RA_PARAM_MISMATCH,
			"%s(%u): Rx RA - our AdvOtherConfigFlag doesn't agree with %s",
			ifp->name, ifp->ifindex, addr_str);
		monotime(&zif->rtadv.lastadvotherconfigflag);
	}

	if ((radvert->nd_ra_reachable && zif->rtadv.AdvReachableTime) &&
	    (ntohl(radvert->nd_ra_reachable) != zif->rtadv.AdvReachableTime) &&
	    (monotime_since(&zif->rtadv.lastadvreachabletime, NULL) >
		     SIXHOUR2USEC ||
	     zif->rtadv.lastadvreachabletime.tv_sec == 0)) {
		flog_warn(
			EC_ZEBRA_RA_PARAM_MISMATCH,
			"%s(%u): Rx RA - our AdvReachableTime doesn't agree with %s",
			ifp->name, ifp->ifindex, addr_str);
		monotime(&zif->rtadv.lastadvreachabletime);
	}

	if ((ntohl(radvert->nd_ra_retransmit) !=
	     (unsigned int)zif->rtadv.AdvRetransTimer) &&
	    (monotime_since(&zif->rtadv.lastadvretranstimer, NULL) >
		     SIXHOUR2USEC ||
	     zif->rtadv.lastadvretranstimer.tv_sec == 0)) {
		flog_warn(
			EC_ZEBRA_RA_PARAM_MISMATCH,
			"%s(%u): Rx RA - our AdvRetransTimer doesn't agree with %s",
			ifp->name, ifp->ifindex, addr_str);
		monotime(&zif->rtadv.lastadvretranstimer);
	}

	/* Create entry for neighbor if not known. */
	p.family = AF_INET6;
	IPV6_ADDR_COPY(&p.u.prefix6, &addr->sin6_addr);
	p.prefixlen = IPV6_MAX_BITLEN;

	if (!nbr_connected_check(ifp, &p))
		nbr_connected_add_ipv6(ifp, &addr->sin6_addr);
}


static void rtadv_process_packet(uint8_t *buf, unsigned int len,
				 ifindex_t ifindex, int hoplimit,
				 struct sockaddr_in6 *from,
				 struct zebra_vrf *zvrf)
{
	struct icmp6_hdr *icmph;
	struct interface *ifp;
	struct zebra_if *zif;
	char addr_str[INET6_ADDRSTRLEN];

	inet_ntop(AF_INET6, &from->sin6_addr, addr_str, INET6_ADDRSTRLEN);

	/* Interface search. */
	ifp = if_lookup_by_index(ifindex, zvrf->vrf->vrf_id);
	if (ifp == NULL) {
		flog_warn(EC_ZEBRA_UNKNOWN_INTERFACE,
			  "RA/RS received on unknown IF %u from %s", ifindex,
			  addr_str);
		return;
	}

	if (IS_ZEBRA_DEBUG_PACKET)
		zlog_debug("%s(%s:%u): Rx RA/RS len %d from %s", ifp->name,
			   ifp->vrf->name, ifp->ifindex, len, addr_str);

	if (if_is_loopback(ifp))
		return;

	/* Check interface configuration. */
	zif = ifp->info;
	if (!zif->rtadv.AdvSendAdvertisements)
		return;

	/* ICMP message length check. */
	if (len < sizeof(struct icmp6_hdr)) {
		zlog_debug(
			"%s(%s:%u): Rx RA with Invalid ICMPV6 packet length %d",
			ifp->name, ifp->vrf->name, ifp->ifindex, len);
		return;
	}

	icmph = (struct icmp6_hdr *)buf;

	/* ICMP message type check. */
	if (icmph->icmp6_type != ND_ROUTER_SOLICIT
	    && icmph->icmp6_type != ND_ROUTER_ADVERT) {
		zlog_debug("%s(%s:%u): Rx RA - Unwanted ICMPV6 message type %d",
			   ifp->name, ifp->vrf->name, ifp->ifindex,
			   icmph->icmp6_type);
		return;
	}

	/* Hoplimit check. */
	if (hoplimit >= 0 && hoplimit != 255) {
		zlog_debug("%s(%s:%u): Rx RA - Invalid hoplimit %d", ifp->name,
			   ifp->vrf->name, ifp->ifindex, hoplimit);
		return;
	}

	/* Check ICMP message type. */
	if (icmph->icmp6_type == ND_ROUTER_SOLICIT)
		rtadv_process_solicit(ifp);
	else if (icmph->icmp6_type == ND_ROUTER_ADVERT)
		rtadv_process_advert(buf, len, ifp, from);

	return;
}

static void rtadv_read(struct thread *thread)
{
	int sock;
	int len;
	uint8_t buf[RTADV_MSG_SIZE];
	struct sockaddr_in6 from;
	ifindex_t ifindex = 0;
	int hoplimit = -1;
	struct zebra_vrf *zvrf = THREAD_ARG(thread);

	sock = THREAD_FD(thread);
	zvrf->rtadv.ra_read = NULL;

	/* Register myself. */
	rtadv_event(zvrf, RTADV_READ, 0);

	len = rtadv_recv_packet(zvrf, sock, buf, sizeof(buf), &from, &ifindex,
				&hoplimit);

	if (len < 0) {
		flog_err_sys(EC_LIB_SOCKET,
			     "RA/RS recv failed, socket %u error %s", sock,
			     safe_strerror(errno));
		return;
	}

	rtadv_process_packet(buf, (unsigned)len, ifindex, hoplimit, &from, zvrf);
}

static int rtadv_make_socket(ns_id_t ns_id)
{
	int sock = -1;
	int ret = 0;
	struct icmp6_filter filter;
	int error;

	frr_with_privs(&zserv_privs) {

		sock = ns_socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6, ns_id);
		/*
		 * with privs might set errno too if it fails save
		 * to the side
		 */
		error = errno;
	}

	if (sock < 0) {
		zlog_warn("RTADV socket for ns: %u failure to create: %s(%u)",
			  ns_id, safe_strerror(error), error);
		return -1;
	}

	ret = setsockopt_ipv6_pktinfo(sock, 1);
	if (ret < 0) {
		zlog_warn("RTADV failure to set Packet Information");
		close(sock);
		return ret;
	}
	ret = setsockopt_ipv6_multicast_loop(sock, 0);
	if (ret < 0) {
		zlog_warn("RTADV failure to set multicast Loop detection");
		close(sock);
		return ret;
	}
	ret = setsockopt_ipv6_unicast_hops(sock, 255);
	if (ret < 0) {
		zlog_warn("RTADV failure to set maximum unicast hops");
		close(sock);
		return ret;
	}
	ret = setsockopt_ipv6_multicast_hops(sock, 255);
	if (ret < 0) {
		zlog_warn("RTADV failure to set maximum multicast hops");
		close(sock);
		return ret;
	}
	ret = setsockopt_ipv6_hoplimit(sock, 1);
	if (ret < 0) {
		zlog_warn("RTADV failure to set maximum incoming hop limit");
		close(sock);
		return ret;
	}

	ICMP6_FILTER_SETBLOCKALL(&filter);
	ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
	ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);

	ret = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter,
			 sizeof(struct icmp6_filter));
	if (ret < 0) {
		zlog_info("ICMP6_FILTER set fail: %s", safe_strerror(errno));
		close(sock);
		return ret;
	}

	return sock;
}

static struct adv_if *adv_if_new(const char *name)
{
	struct adv_if *new;

	new = XCALLOC(MTYPE_ADV_IF, sizeof(struct adv_if));

	strlcpy(new->name, name, sizeof(new->name));

	return new;
}

static void adv_if_free(struct adv_if *adv_if)
{
	XFREE(MTYPE_ADV_IF, adv_if);
}

static bool adv_if_is_empty_internal(const struct adv_if_list_head *adv_if_head)
{
	return adv_if_list_count(adv_if_head) ? false : true;
}

static struct adv_if *adv_if_add_internal(struct adv_if_list_head *adv_if_head,
					  const char *name)
{
	struct adv_if adv_if_lookup = {};
	struct adv_if *adv_if = NULL;

	strlcpy(adv_if_lookup.name, name, sizeof(adv_if_lookup.name));
	adv_if = adv_if_list_find(adv_if_head, &adv_if_lookup);

	if (adv_if != NULL)
		return adv_if;

	adv_if = adv_if_new(adv_if_lookup.name);
	adv_if_list_add(adv_if_head, adv_if);

	return NULL;
}

static struct adv_if *adv_if_del_internal(struct adv_if_list_head *adv_if_head,
					  const char *name)
{
	struct adv_if adv_if_lookup = {};
	struct adv_if *adv_if = NULL;

	strlcpy(adv_if_lookup.name, name, sizeof(adv_if_lookup.name));
	adv_if = adv_if_list_find(adv_if_head, &adv_if_lookup);

	if (adv_if == NULL)
		return NULL;

	adv_if_list_del(adv_if_head, adv_if);

	return adv_if;
}

static void adv_if_clean_internal(struct adv_if_list_head *adv_if_head)
{
	struct adv_if *node = NULL;

	if (!adv_if_is_empty_internal(adv_if_head)) {
		frr_each_safe (adv_if_list, adv_if_head, node) {
			adv_if_list_del(adv_if_head, node);
			adv_if_free(node);
		}
	}

	adv_if_list_fini(adv_if_head);
}


/*
 * Add to list. On Success, return NULL, otherwise return already existing
 * adv_if.
 */
static struct adv_if *adv_if_add(struct zebra_vrf *zvrf, const char *name)
{
	struct adv_if *adv_if = NULL;

	adv_if = adv_if_add_internal(&zvrf->rtadv.adv_if, name);

	if (adv_if != NULL)
		return adv_if;

	if (IS_ZEBRA_DEBUG_EVENT) {
		struct vrf *vrf = zvrf->vrf;

		zlog_debug("%s: %s:%u IF %s count: %zu", __func__,
			   VRF_LOGNAME(vrf), zvrf_id(zvrf), name,
			   adv_if_list_count(&zvrf->rtadv.adv_if));
	}

	return NULL;
}

/*
 * Del from list. On Success, return the adv_if, otherwise return NULL. Caller
 * frees.
 */
static struct adv_if *adv_if_del(struct zebra_vrf *zvrf, const char *name)
{
	struct adv_if *adv_if = NULL;

	adv_if = adv_if_del_internal(&zvrf->rtadv.adv_if, name);

	if (adv_if == NULL)
		return NULL;

	if (IS_ZEBRA_DEBUG_EVENT) {
		struct vrf *vrf = zvrf->vrf;

		zlog_debug("%s: %s:%u IF %s count: %zu", __func__,
			   VRF_LOGNAME(vrf), zvrf_id(zvrf), name,
			   adv_if_list_count(&zvrf->rtadv.adv_if));
	}

	return adv_if;
}

/*
 * Add to list. On Success, return NULL, otherwise return already existing
 * adv_if.
 */
static struct adv_if *adv_msec_if_add(struct zebra_vrf *zvrf, const char *name)
{
	struct adv_if *adv_if = NULL;

	adv_if = adv_if_add_internal(&zvrf->rtadv.adv_msec_if, name);

	if (adv_if != NULL)
		return adv_if;

	if (IS_ZEBRA_DEBUG_EVENT) {
		struct vrf *vrf = zvrf->vrf;

		zlog_debug("%s: %s:%u IF %s count: %zu", __func__,
			   VRF_LOGNAME(vrf), zvrf_id(zvrf), name,
			   adv_if_list_count(&zvrf->rtadv.adv_msec_if));
	}

	return NULL;
}

/*
 * Del from list. On Success, return the adv_if, otherwise return NULL. Caller
 * frees.
 */
static struct adv_if *adv_msec_if_del(struct zebra_vrf *zvrf, const char *name)
{
	struct adv_if *adv_if = NULL;

	adv_if = adv_if_del_internal(&zvrf->rtadv.adv_msec_if, name);

	if (adv_if == NULL)
		return NULL;

	if (IS_ZEBRA_DEBUG_EVENT) {
		struct vrf *vrf = zvrf->vrf;

		zlog_debug("%s: %s:%u IF %s count: %zu", __func__,
			   VRF_LOGNAME(vrf), zvrf_id(zvrf), name,
			   adv_if_list_count(&zvrf->rtadv.adv_msec_if));
	}

	return adv_if;
}

/* Clean adv_if list, called on vrf terminate */
static void adv_if_clean(struct zebra_vrf *zvrf)
{
	if (IS_ZEBRA_DEBUG_EVENT) {
		struct vrf *vrf = zvrf->vrf;

		zlog_debug("%s: %s:%u count: %zu -> 0", __func__,
			   VRF_LOGNAME(vrf), zvrf_id(zvrf),
			   adv_if_list_count(&zvrf->rtadv.adv_if));
	}

	adv_if_clean_internal(&zvrf->rtadv.adv_if);
}

/* Clean adv_msec_if list, called on vrf terminate */
static void adv_msec_if_clean(struct zebra_vrf *zvrf)
{
	if (IS_ZEBRA_DEBUG_EVENT) {
		struct vrf *vrf = zvrf->vrf;

		zlog_debug("%s: %s:%u count: %zu -> 0", __func__,
			   VRF_LOGNAME(vrf), zvrf_id(zvrf),
			   adv_if_list_count(&zvrf->rtadv.adv_msec_if));
	}

	adv_if_clean_internal(&zvrf->rtadv.adv_msec_if);
}

static struct rtadv_prefix *rtadv_prefix_new(void)
{
	return XCALLOC(MTYPE_RTADV_PREFIX, sizeof(struct rtadv_prefix));
}

static void rtadv_prefix_free(struct rtadv_prefix *rtadv_prefix)
{
	XFREE(MTYPE_RTADV_PREFIX, rtadv_prefix);
}

static struct rtadv_prefix *rtadv_prefix_get(struct rtadv_prefixes_head *list,
					     struct prefix_ipv6 *p)
{
	struct rtadv_prefix *rprefix, ref;

	ref.prefix = *p;

	rprefix = rtadv_prefixes_find(list, &ref);
	if (rprefix)
		return rprefix;

	rprefix = rtadv_prefix_new();
	memcpy(&rprefix->prefix, p, sizeof(struct prefix_ipv6));
	rtadv_prefixes_add(list, rprefix);

	return rprefix;
}

static void rtadv_prefix_set_defaults(struct rtadv_prefix *rp)
{
	rp->AdvAutonomousFlag = 1;
	rp->AdvOnLinkFlag = 1;
	rp->AdvRouterAddressFlag = 0;
	rp->AdvPreferredLifetime = RTADV_PREFERRED_LIFETIME;
	rp->AdvValidLifetime = RTADV_VALID_LIFETIME;
}

static void rtadv_prefix_set(struct zebra_if *zif, struct rtadv_prefix *rp)
{
	struct rtadv_prefix *rprefix;

	rprefix = rtadv_prefix_get(zif->rtadv.prefixes, &rp->prefix);

	/*
	 * Set parameters based on where the prefix is created.
	 * If auto-created based on kernel address addition, set the
	 * default values.  If created from a manual "ipv6 nd prefix"
	 * command, take the parameters from the manual command. Note
	 * that if the manual command exists, the default values will
	 * not overwrite the manual values.
	 */
	if (rp->AdvPrefixCreate == PREFIX_SRC_MANUAL) {
		if (rprefix->AdvPrefixCreate == PREFIX_SRC_AUTO)
			rprefix->AdvPrefixCreate = PREFIX_SRC_BOTH;
		else
			rprefix->AdvPrefixCreate = PREFIX_SRC_MANUAL;

		rprefix->AdvAutonomousFlag = rp->AdvAutonomousFlag;
		rprefix->AdvOnLinkFlag = rp->AdvOnLinkFlag;
		rprefix->AdvRouterAddressFlag = rp->AdvRouterAddressFlag;
		rprefix->AdvPreferredLifetime = rp->AdvPreferredLifetime;
		rprefix->AdvValidLifetime = rp->AdvValidLifetime;
	} else if (rp->AdvPrefixCreate == PREFIX_SRC_AUTO) {
		if (rprefix->AdvPrefixCreate == PREFIX_SRC_MANUAL)
			rprefix->AdvPrefixCreate = PREFIX_SRC_BOTH;
		else {
			rprefix->AdvPrefixCreate = PREFIX_SRC_AUTO;
			rtadv_prefix_set_defaults(rprefix);
		}
	}
}

static int rtadv_prefix_reset(struct zebra_if *zif, struct rtadv_prefix *rp)
{
	struct rtadv_prefix *rprefix;

	rprefix = rtadv_prefixes_find(zif->rtadv.prefixes, rp);
	if (rprefix != NULL) {

		/*
		 * When deleting an address from the list, need to take care
		 * it wasn't defined both automatically via kernel
		 * address addition as well as manually by vtysh cli. If both,
		 * we don't actually delete but may change the parameters
		 * back to default if a manually defined entry is deleted.
		 */
		if (rp->AdvPrefixCreate == PREFIX_SRC_MANUAL) {
			if (rprefix->AdvPrefixCreate == PREFIX_SRC_BOTH) {
				rprefix->AdvPrefixCreate = PREFIX_SRC_AUTO;
				rtadv_prefix_set_defaults(rprefix);
				return 1;
			}
		} else if (rp->AdvPrefixCreate == PREFIX_SRC_AUTO) {
			if (rprefix->AdvPrefixCreate == PREFIX_SRC_BOTH) {
				rprefix->AdvPrefixCreate = PREFIX_SRC_MANUAL;
				return 1;
			}
		}

		rtadv_prefixes_del(zif->rtadv.prefixes, rprefix);
		rtadv_prefix_free(rprefix);
		return 1;
	} else
		return 0;
}

/* Add IPv6 prefixes learned from the kernel to the RA prefix list */
void rtadv_add_prefix(struct zebra_if *zif, const struct prefix_ipv6 *p)
{
	struct rtadv_prefix rp;

	rp.prefix = *p;
	apply_mask_ipv6(&rp.prefix);
	rp.AdvPrefixCreate = PREFIX_SRC_AUTO;
	rtadv_prefix_set(zif, &rp);
}

/* Delete IPv6 prefixes removed by the kernel from the RA prefix list */
void rtadv_delete_prefix(struct zebra_if *zif, const struct prefix *p)
{
	struct rtadv_prefix rp;

	rp.prefix = *((struct prefix_ipv6 *)p);
	apply_mask_ipv6(&rp.prefix);
	rp.AdvPrefixCreate = PREFIX_SRC_AUTO;
	rtadv_prefix_reset(zif, &rp);
}

static void rtadv_start_interface_events(struct zebra_vrf *zvrf,
					 struct zebra_if *zif)
{
	struct adv_if *adv_if = NULL;

	if (zif->ifp->ifindex == IFINDEX_INTERNAL) {
		if (IS_ZEBRA_DEBUG_EVENT)
			zlog_debug(
				"%s(%s) has not configured an ifindex yet, delaying until we have one",
				zif->ifp->name, zvrf->vrf->name);
		return;
	}

	adv_if = adv_if_add(zvrf, zif->ifp->name);
	if (adv_if != NULL)
		return; /* Already added */

	if_join_all_router(zvrf->rtadv.sock, zif->ifp);

	if (adv_if_list_count(&zvrf->rtadv.adv_if) == 1)
		rtadv_event(zvrf, RTADV_START, 0);
}

static void ipv6_nd_suppress_ra_set(struct interface *ifp,
				    enum ipv6_nd_suppress_ra_status status)
{
	struct zebra_if *zif;
	struct zebra_vrf *zvrf;
	struct adv_if *adv_if = NULL;

	zif = ifp->info;

	zvrf = rtadv_interface_get_zvrf(ifp);

	if (status == RA_SUPPRESS) {
		/* RA is currently enabled */
		if (zif->rtadv.AdvSendAdvertisements) {
			rtadv_send_packet(zvrf->rtadv.sock, ifp, RA_SUPPRESS);
			zif->rtadv.AdvSendAdvertisements = 0;
			zif->rtadv.AdvIntervalTimer = 0;

			adv_if = adv_if_del(zvrf, ifp->name);
			if (adv_if == NULL)
				return; /* Nothing to delete */

			adv_if_free(adv_if);

			if_leave_all_router(zvrf->rtadv.sock, ifp);

			if (adv_if_list_count(&zvrf->rtadv.adv_if) == 0)
				rtadv_event(zvrf, RTADV_STOP, 0);
		}
	} else {
		if (!zif->rtadv.AdvSendAdvertisements) {
			zif->rtadv.AdvSendAdvertisements = 1;
			zif->rtadv.AdvIntervalTimer = 0;
			if ((zif->rtadv.MaxRtrAdvInterval >= 1000)
			    && zif->rtadv.UseFastRexmit) {
				/*
				 * Enable Fast RA only when RA interval is in
				 * secs and Fast RA retransmit is enabled
				 */
				zif->rtadv.inFastRexmit = 1;
				zif->rtadv.NumFastReXmitsRemain =
					RTADV_NUM_FAST_REXMITS;
			}

			rtadv_start_interface_events(zvrf, zif);
		}
	}
}

/*
 * Handle client (BGP) message to enable or disable IPv6 RA on an interface.
 * Note that while the client could request RA on an interface on which the
 * operator has not enabled RA, RA won't be disabled upon client request
 * if the operator has explicitly enabled RA. The enable request can also
 * specify a RA interval (in seconds).
 */
static void zebra_interface_radv_set(ZAPI_HANDLER_ARGS, int enable)
{
	struct stream *s;
	ifindex_t ifindex;
	struct interface *ifp;
	struct zebra_if *zif;
	uint32_t ra_interval;

	s = msg;

	/* Get interface index and RA interval. */
	STREAM_GETL(s, ifindex);
	STREAM_GETL(s, ra_interval);

	if (IS_ZEBRA_DEBUG_EVENT) {
		struct vrf *vrf = zvrf->vrf;

		zlog_debug("%s:%u: IF %u RA %s from client %s, interval %ums",
			   VRF_LOGNAME(vrf), zvrf_id(zvrf), ifindex,
			   enable ? "enable" : "disable",
			   zebra_route_string(client->proto), ra_interval);
	}

	/* Locate interface and check VRF match. */
	ifp = if_lookup_by_index(ifindex, zvrf->vrf->vrf_id);
	if (!ifp) {
		struct vrf *vrf = zvrf->vrf;

		flog_warn(EC_ZEBRA_UNKNOWN_INTERFACE,
			  "%s:%u: IF %u RA %s client %s - interface unknown",
			  VRF_LOGNAME(vrf), zvrf_id(zvrf), ifindex,
			  enable ? "enable" : "disable",
			  zebra_route_string(client->proto));
		return;
	}
	if (vrf_is_backend_netns() && ifp->vrf->vrf_id != zvrf_id(zvrf)) {
		zlog_debug(
			"%s:%u: IF %u RA %s client %s - VRF mismatch, IF VRF %u",
			ifp->vrf->name, zvrf_id(zvrf), ifindex,
			enable ? "enable" : "disable",
			zebra_route_string(client->proto), ifp->vrf->vrf_id);
		return;
	}

	zif = ifp->info;
	if (enable) {
		if (!CHECK_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED))
			interfaces_configured_for_ra_from_bgp++;

		SET_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED);
		ipv6_nd_suppress_ra_set(ifp, RA_ENABLE);
		if (ra_interval
		    && (ra_interval * 1000) < (unsigned int) zif->rtadv.MaxRtrAdvInterval
		    && !CHECK_FLAG(zif->rtadv.ra_configured,
				   VTY_RA_INTERVAL_CONFIGURED))
			zif->rtadv.MaxRtrAdvInterval = ra_interval * 1000;
	} else {
		if (CHECK_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED))
			interfaces_configured_for_ra_from_bgp--;

		UNSET_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED);
		if (!CHECK_FLAG(zif->rtadv.ra_configured,
				VTY_RA_INTERVAL_CONFIGURED))
			zif->rtadv.MaxRtrAdvInterval =
				RTADV_MAX_RTR_ADV_INTERVAL;
		if (!CHECK_FLAG(zif->rtadv.ra_configured, VTY_RA_CONFIGURED))
			ipv6_nd_suppress_ra_set(ifp, RA_SUPPRESS);
	}
stream_failure:
	return;
}

/*
 * send router lifetime value of zero in RAs on this interface since we're
 * ceasing to advertise and want to let our neighbors know.
 * RFC 4861 secion 6.2.5
 */
void rtadv_stop_ra(struct interface *ifp)
{
	struct zebra_if *zif;
	struct zebra_vrf *zvrf;

	zif = ifp->info;
	zvrf = rtadv_interface_get_zvrf(ifp);

	if (zif->rtadv.AdvSendAdvertisements)
		rtadv_send_packet(zvrf->rtadv.sock, ifp, RA_SUPPRESS);
}

/*
 * Send router lifetime value of zero in RAs on all interfaces since we're
 * ceasing to advertise globally and want to let all of our neighbors know
 * RFC 4861 secion 6.2.5
 *
 * Delete all ipv6 global prefixes added to the router advertisement prefix
 * lists prior to ceasing.
 */
void rtadv_stop_ra_all(void)
{
	struct vrf *vrf;
	struct interface *ifp;
	struct zebra_if *zif;
	struct rtadv_prefix *rprefix;

	RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name)
		FOR_ALL_INTERFACES (vrf, ifp) {
			zif = ifp->info;

			frr_each_safe (rtadv_prefixes, zif->rtadv.prefixes,
				       rprefix)
				rtadv_prefix_reset(zif, rprefix);

			rtadv_stop_ra(ifp);
		}
}

void zebra_interface_radv_disable(ZAPI_HANDLER_ARGS)
{
	zebra_interface_radv_set(client, hdr, msg, zvrf, 0);
}
void zebra_interface_radv_enable(ZAPI_HANDLER_ARGS)
{
	zebra_interface_radv_set(client, hdr, msg, zvrf, 1);
}

static void show_zvrf_rtadv_adv_if_helper(struct vty *vty,
					  struct adv_if_list_head *adv_if_head)
{
	struct adv_if *node = NULL;

	if (!adv_if_is_empty_internal(adv_if_head)) {
		frr_each (adv_if_list, adv_if_head, node) {
			vty_out(vty, "    %s\n", node->name);
		}
	}

	vty_out(vty, "\n");
}

static void show_zvrf_rtadv_helper(struct vty *vty, struct zebra_vrf *zvrf)
{
	vty_out(vty, "VRF: %s\n", zvrf_name(zvrf));
	vty_out(vty, "  Interfaces:\n");
	show_zvrf_rtadv_adv_if_helper(vty, &zvrf->rtadv.adv_if);

	vty_out(vty, "  Interfaces(msec):\n");
	show_zvrf_rtadv_adv_if_helper(vty, &zvrf->rtadv.adv_msec_if);
}

DEFPY(show_ipv6_nd_ra_if, show_ipv6_nd_ra_if_cmd,
      "show ipv6 nd ra-interfaces [vrf<NAME$vrf_name|all$vrf_all>]",
      SHOW_STR IP6_STR
      "Neighbor discovery\n"
      "Route Advertisement Interfaces\n" VRF_FULL_CMD_HELP_STR)
{
	struct zebra_vrf *zvrf = NULL;

	if (!vrf_is_backend_netns() && (vrf_name || vrf_all)) {
		vty_out(vty,
			"%% VRF subcommand only applicable for netns-based vrfs.\n");
		return CMD_WARNING;
	}

	if (vrf_all) {
		struct vrf *vrf;

		RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
			struct zebra_vrf *zvrf;

			zvrf = vrf->info;
			if (!zvrf)
				continue;

			show_zvrf_rtadv_helper(vty, zvrf);
		}

		return CMD_SUCCESS;
	}

	if (vrf_name)
		zvrf = zebra_vrf_lookup_by_name(vrf_name);
	else
		zvrf = zebra_vrf_lookup_by_name(VRF_DEFAULT_NAME);

	if (!zvrf) {
		vty_out(vty, "%% VRF '%s' specified does not exist\n",
			vrf_name);
		return CMD_WARNING;
	}

	show_zvrf_rtadv_helper(vty, zvrf);

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_ra_fast_retrans,
	ipv6_nd_ra_fast_retrans_cmd,
	"ipv6 nd ra-fast-retrans",
	"Interface IPv6 config commands\n"
	"Neighbor discovery\n"
	"Fast retransmit of RA packets\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	if (if_is_loopback(ifp)) {
		vty_out(vty,
			"Cannot configure IPv6 Router Advertisements on this  interface\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	zif->rtadv.UseFastRexmit = true;

	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_ra_fast_retrans,
	no_ipv6_nd_ra_fast_retrans_cmd,
	"no ipv6 nd ra-fast-retrans",
	NO_STR
	"Interface IPv6 config commands\n"
	"Neighbor discovery\n"
	"Fast retransmit of RA packets\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	if (if_is_loopback(ifp)) {
		vty_out(vty,
			"Cannot configure IPv6 Router Advertisements on this  interface\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	zif->rtadv.UseFastRexmit = false;

	return CMD_SUCCESS;
}

DEFPY (ipv6_nd_ra_hop_limit,
       ipv6_nd_ra_hop_limit_cmd,
       "ipv6 nd ra-hop-limit (0-255)$hopcount",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Advertisement Hop Limit\n"
       "Advertisement Hop Limit in hops (default:64)\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	if (if_is_loopback(ifp)) {
		vty_out(vty,
			"Cannot configure IPv6 Router Advertisements on this interface\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	zif->rtadv.AdvCurHopLimit = hopcount;

	return CMD_SUCCESS;
}

DEFPY (no_ipv6_nd_ra_hop_limit,
       no_ipv6_nd_ra_hop_limit_cmd,
       "no ipv6 nd ra-hop-limit [(0-255)]",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Advertisement Hop Limit\n"
       "Advertisement Hop Limit in hops\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	if (if_is_loopback(ifp)) {
		vty_out(vty,
			"Cannot configure IPv6 Router Advertisements on this interface\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	zif->rtadv.AdvCurHopLimit = RTADV_DEFAULT_HOPLIMIT;

	return CMD_SUCCESS;
}

DEFPY (ipv6_nd_ra_retrans_interval,
       ipv6_nd_ra_retrans_interval_cmd,
       "ipv6 nd ra-retrans-interval (0-4294967295)$interval",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Advertisement Retransmit Interval\n"
       "Advertisement Retransmit Interval in msec\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	if (if_is_loopback(ifp)) {
		vty_out(vty,
			"Cannot configure IPv6 Router Advertisements on loopback interface\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	zif->rtadv.AdvRetransTimer = interval;

	return CMD_SUCCESS;
}

DEFPY (no_ipv6_nd_ra_retrans_interval,
       no_ipv6_nd_ra_retrans_interval_cmd,
       "no ipv6 nd ra-retrans-interval [(0-4294967295)]",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Advertisement Retransmit Interval\n"
       "Advertisement Retransmit Interval in msec\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	if (if_is_loopback(ifp)) {
		vty_out(vty,
			"Cannot remove IPv6 Router Advertisements on loopback interface\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	zif->rtadv.AdvRetransTimer = 0;

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_suppress_ra,
       ipv6_nd_suppress_ra_cmd,
       "ipv6 nd suppress-ra",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Suppress Router Advertisement\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	if (if_is_loopback(ifp)) {
		vty_out(vty,
			"Cannot configure IPv6 Router Advertisements on this  interface\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	if (!CHECK_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED))
		ipv6_nd_suppress_ra_set(ifp, RA_SUPPRESS);

	UNSET_FLAG(zif->rtadv.ra_configured, VTY_RA_CONFIGURED);
	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_suppress_ra,
       no_ipv6_nd_suppress_ra_cmd,
       "no ipv6 nd suppress-ra",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Suppress Router Advertisement\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	if (if_is_loopback(ifp)) {
		vty_out(vty,
			"Cannot configure IPv6 Router Advertisements on this interface\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	ipv6_nd_suppress_ra_set(ifp, RA_ENABLE);
	SET_FLAG(zif->rtadv.ra_configured, VTY_RA_CONFIGURED);
	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_ra_interval_msec,
       ipv6_nd_ra_interval_msec_cmd,
       "ipv6 nd ra-interval msec (70-1800000)",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Router Advertisement interval\n"
       "Router Advertisement interval in milliseconds\n"
       "Router Advertisement interval in milliseconds\n")
{
	int idx_number = 4;
	VTY_DECLVAR_CONTEXT(interface, ifp);
	unsigned interval;
	struct zebra_if *zif = ifp->info;
	struct zebra_vrf *zvrf;
	struct adv_if *adv_if;

	zvrf = rtadv_interface_get_zvrf(ifp);

	interval = strtoul(argv[idx_number]->arg, NULL, 10);
	if ((zif->rtadv.AdvDefaultLifetime != -1
	     && interval > (unsigned)zif->rtadv.AdvDefaultLifetime * 1000)) {
		vty_out(vty,
			"This ra-interval would conflict with configured ra-lifetime!\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	if (zif->rtadv.MaxRtrAdvInterval % 1000) {
		adv_if = adv_msec_if_del(zvrf, ifp->name);
		if (adv_if != NULL)
			adv_if_free(adv_if);
	}

	if (interval % 1000)
		(void)adv_msec_if_add(zvrf, ifp->name);

	SET_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED);
	zif->rtadv.MaxRtrAdvInterval = interval;
	zif->rtadv.MinRtrAdvInterval = 0.33 * interval;
	zif->rtadv.AdvIntervalTimer = 0;

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_ra_interval,
       ipv6_nd_ra_interval_cmd,
       "ipv6 nd ra-interval (1-1800)",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Router Advertisement interval\n"
       "Router Advertisement interval in seconds\n")
{
	int idx_number = 3;
	VTY_DECLVAR_CONTEXT(interface, ifp);
	unsigned interval;
	struct zebra_if *zif = ifp->info;
	struct zebra_vrf *zvrf;
	struct adv_if *adv_if;

	zvrf = rtadv_interface_get_zvrf(ifp);

	interval = strtoul(argv[idx_number]->arg, NULL, 10);
	if ((zif->rtadv.AdvDefaultLifetime != -1
	     && interval > (unsigned)zif->rtadv.AdvDefaultLifetime)) {
		vty_out(vty,
			"This ra-interval would conflict with configured ra-lifetime!\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	if (zif->rtadv.MaxRtrAdvInterval % 1000) {
		adv_if = adv_msec_if_del(zvrf, ifp->name);
		if (adv_if != NULL)
			adv_if_free(adv_if);
	}

	/* convert to milliseconds */
	interval = interval * 1000;

	SET_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED);
	zif->rtadv.MaxRtrAdvInterval = interval;
	zif->rtadv.MinRtrAdvInterval = 0.33 * interval;
	zif->rtadv.AdvIntervalTimer = 0;

	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_ra_interval,
       no_ipv6_nd_ra_interval_cmd,
       "no ipv6 nd ra-interval [<(1-1800)|msec (1-1800000)>]",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Router Advertisement interval\n"
       "Router Advertisement interval in seconds\n"
       "Specify millisecond router advertisement interval\n"
       "Router Advertisement interval in milliseconds\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;
	struct zebra_vrf *zvrf = NULL;
	struct adv_if *adv_if;

	zvrf = rtadv_interface_get_zvrf(ifp);

	if (zif->rtadv.MaxRtrAdvInterval % 1000) {
		adv_if = adv_msec_if_del(zvrf, ifp->name);
		if (adv_if != NULL)
			adv_if_free(adv_if);
	}

	UNSET_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED);

	if (CHECK_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED))
		zif->rtadv.MaxRtrAdvInterval = 10000;
	else
		zif->rtadv.MaxRtrAdvInterval = RTADV_MAX_RTR_ADV_INTERVAL;

	zif->rtadv.AdvIntervalTimer = zif->rtadv.MaxRtrAdvInterval;
	zif->rtadv.MinRtrAdvInterval = RTADV_MIN_RTR_ADV_INTERVAL;

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_ra_lifetime,
       ipv6_nd_ra_lifetime_cmd,
       "ipv6 nd ra-lifetime (0-9000)",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Router lifetime\n"
       "Router lifetime in seconds (0 stands for a non-default gw)\n")
{
	int idx_number = 3;
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;
	int lifetime;

	lifetime = strtoul(argv[idx_number]->arg, NULL, 10);

	/* The value to be placed in the Router Lifetime field
	 * of Router Advertisements sent from the interface,
	 * in seconds.  MUST be either zero or between
	 * MaxRtrAdvInterval and 9000 seconds. -- RFC4861, 6.2.1 */
	if ((lifetime != 0 && lifetime * 1000 < zif->rtadv.MaxRtrAdvInterval)) {
		vty_out(vty,
			"This ra-lifetime would conflict with configured ra-interval\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	zif->rtadv.AdvDefaultLifetime = lifetime;

	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_ra_lifetime,
       no_ipv6_nd_ra_lifetime_cmd,
       "no ipv6 nd ra-lifetime [(0-9000)]",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Router lifetime\n"
       "Router lifetime in seconds (0 stands for a non-default gw)\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.AdvDefaultLifetime = -1;

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_reachable_time,
       ipv6_nd_reachable_time_cmd,
       "ipv6 nd reachable-time (1-3600000)",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Reachable time\n"
       "Reachable time in milliseconds\n")
{
	int idx_number = 3;
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;
	zif->rtadv.AdvReachableTime = strtoul(argv[idx_number]->arg, NULL, 10);
	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_reachable_time,
       no_ipv6_nd_reachable_time_cmd,
       "no ipv6 nd reachable-time [(1-3600000)]",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Reachable time\n"
       "Reachable time in milliseconds\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.AdvReachableTime = 0;

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_homeagent_preference,
       ipv6_nd_homeagent_preference_cmd,
       "ipv6 nd home-agent-preference (0-65535)",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Home Agent preference\n"
       "preference value (default is 0, least preferred)\n")
{
	int idx_number = 3;
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;
	zif->rtadv.HomeAgentPreference =
		strtoul(argv[idx_number]->arg, NULL, 10);
	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_homeagent_preference,
       no_ipv6_nd_homeagent_preference_cmd,
       "no ipv6 nd home-agent-preference [(0-65535)]",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Home Agent preference\n"
       "preference value (default is 0, least preferred)\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.HomeAgentPreference = 0;

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_homeagent_lifetime,
       ipv6_nd_homeagent_lifetime_cmd,
       "ipv6 nd home-agent-lifetime (0-65520)",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Home Agent lifetime\n"
       "Home Agent lifetime in seconds (0 to track ra-lifetime)\n")
{
	int idx_number = 3;
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;
	zif->rtadv.HomeAgentLifetime = strtoul(argv[idx_number]->arg, NULL, 10);
	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_homeagent_lifetime,
       no_ipv6_nd_homeagent_lifetime_cmd,
       "no ipv6 nd home-agent-lifetime [(0-65520)]",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Home Agent lifetime\n"
       "Home Agent lifetime in seconds (0 to track ra-lifetime)\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.HomeAgentLifetime = -1;

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_managed_config_flag,
       ipv6_nd_managed_config_flag_cmd,
       "ipv6 nd managed-config-flag",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Managed address configuration flag\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.AdvManagedFlag = 1;

	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_managed_config_flag,
       no_ipv6_nd_managed_config_flag_cmd,
       "no ipv6 nd managed-config-flag",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Managed address configuration flag\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.AdvManagedFlag = 0;

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_homeagent_config_flag,
       ipv6_nd_homeagent_config_flag_cmd,
       "ipv6 nd home-agent-config-flag",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Home Agent configuration flag\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.AdvHomeAgentFlag = 1;

	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_homeagent_config_flag,
       no_ipv6_nd_homeagent_config_flag_cmd,
       "no ipv6 nd home-agent-config-flag",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Home Agent configuration flag\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.AdvHomeAgentFlag = 0;

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_adv_interval_config_option,
       ipv6_nd_adv_interval_config_option_cmd,
       "ipv6 nd adv-interval-option",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Advertisement Interval Option\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.AdvIntervalOption = 1;

	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_adv_interval_config_option,
       no_ipv6_nd_adv_interval_config_option_cmd,
       "no ipv6 nd adv-interval-option",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Advertisement Interval Option\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.AdvIntervalOption = 0;

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_other_config_flag,
       ipv6_nd_other_config_flag_cmd,
       "ipv6 nd other-config-flag",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Other statefull configuration flag\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.AdvOtherConfigFlag = 1;

	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_other_config_flag,
       no_ipv6_nd_other_config_flag_cmd,
       "no ipv6 nd other-config-flag",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Other statefull configuration flag\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.AdvOtherConfigFlag = 0;

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_prefix,
       ipv6_nd_prefix_cmd,
       "ipv6 nd prefix X:X::X:X/M [<(0-4294967295)|infinite> <(0-4294967295)|infinite>] [<router-address|off-link [no-autoconfig]|no-autoconfig [off-link]>]",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Prefix information\n"
       "IPv6 prefix\n"
       "Valid lifetime in seconds\n"
       "Infinite valid lifetime\n"
       "Preferred lifetime in seconds\n"
       "Infinite preferred lifetime\n"
       "Set Router Address flag\n"
       "Do not use prefix for onlink determination\n"
       "Do not use prefix for autoconfiguration\n"
       "Do not use prefix for autoconfiguration\n"
       "Do not use prefix for onlink determination\n")
{
	/* prelude */
	char *prefix = argv[3]->arg;
	int lifetimes = (argc > 4) && (argv[4]->type == RANGE_TKN
				       || strmatch(argv[4]->text, "infinite"));
	int routeropts = lifetimes ? argc > 6 : argc > 4;

	int idx_routeropts = routeropts ? (lifetimes ? 6 : 4) : 0;

	char *lifetime = NULL, *preflifetime = NULL;
	int routeraddr = 0, offlink = 0, noautoconf = 0;
	if (lifetimes) {
		lifetime = argv[4]->type == RANGE_TKN ? argv[4]->arg
						      : argv[4]->text;
		preflifetime = argv[5]->type == RANGE_TKN ? argv[5]->arg
							  : argv[5]->text;
	}
	if (routeropts) {
		routeraddr =
			strmatch(argv[idx_routeropts]->text, "router-address");
		if (!routeraddr) {
			offlink = (argc > idx_routeropts + 1
				   || strmatch(argv[idx_routeropts]->text,
					       "off-link"));
			noautoconf = (argc > idx_routeropts + 1
				      || strmatch(argv[idx_routeropts]->text,
						  "no-autoconfig"));
		}
	}

	/* business */
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zebra_if = ifp->info;
	int ret;
	struct rtadv_prefix rp;

	ret = str2prefix_ipv6(prefix, &rp.prefix);
	if (!ret) {
		vty_out(vty, "Malformed IPv6 prefix\n");
		return CMD_WARNING_CONFIG_FAILED;
	}
	apply_mask_ipv6(&rp.prefix); /* RFC4861 4.6.2 */
	rp.AdvOnLinkFlag = !offlink;
	rp.AdvAutonomousFlag = !noautoconf;
	rp.AdvRouterAddressFlag = routeraddr;
	rp.AdvValidLifetime = RTADV_VALID_LIFETIME;
	rp.AdvPreferredLifetime = RTADV_PREFERRED_LIFETIME;
	rp.AdvPrefixCreate = PREFIX_SRC_MANUAL;

	if (lifetimes) {
		rp.AdvValidLifetime = strmatch(lifetime, "infinite")
					      ? UINT32_MAX
					      : strtoll(lifetime, NULL, 10);
		rp.AdvPreferredLifetime =
			strmatch(preflifetime, "infinite")
				? UINT32_MAX
				: strtoll(preflifetime, NULL, 10);
		if (rp.AdvPreferredLifetime > rp.AdvValidLifetime) {
			vty_out(vty, "Invalid preferred lifetime\n");
			return CMD_WARNING_CONFIG_FAILED;
		}
	}

	rtadv_prefix_set(zebra_if, &rp);

	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_prefix,
       no_ipv6_nd_prefix_cmd,
       "no ipv6 nd prefix X:X::X:X/M [<(0-4294967295)|infinite> <(0-4294967295)|infinite>] [<router-address|off-link [no-autoconfig]|no-autoconfig [off-link]>]",
        NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Prefix information\n"
       "IPv6 prefix\n"
       "Valid lifetime in seconds\n"
       "Infinite valid lifetime\n"
       "Preferred lifetime in seconds\n"
       "Infinite preferred lifetime\n"
       "Set Router Address flag\n"
       "Do not use prefix for onlink determination\n"
       "Do not use prefix for autoconfiguration\n"
       "Do not use prefix for autoconfiguration\n"
       "Do not use prefix for onlink determination\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zebra_if = ifp->info;
	int ret;
	struct rtadv_prefix rp;
	char *prefix = argv[4]->arg;

	ret = str2prefix_ipv6(prefix, &rp.prefix);
	if (!ret) {
		vty_out(vty, "Malformed IPv6 prefix\n");
		return CMD_WARNING_CONFIG_FAILED;
	}
	apply_mask_ipv6(&rp.prefix); /* RFC4861 4.6.2 */
	rp.AdvPrefixCreate = PREFIX_SRC_MANUAL;

	ret = rtadv_prefix_reset(zebra_if, &rp);
	if (!ret) {
		vty_out(vty, "Non-existant IPv6 prefix\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_router_preference,
       ipv6_nd_router_preference_cmd,
       "ipv6 nd router-preference <high|medium|low>",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Default router preference\n"
       "High default router preference\n"
       "Medium default router preference (default)\n"
       "Low default router preference\n")
{
	int idx_high_medium_low = 3;
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;
	int i = 0;

	while (0 != rtadv_pref_strs[i]) {
		if (strncmp(argv[idx_high_medium_low]->arg, rtadv_pref_strs[i],
			    1)
		    == 0) {
			zif->rtadv.DefaultPreference = i;
			return CMD_SUCCESS;
		}
		i++;
	}

	return CMD_ERR_NO_MATCH;
}

DEFUN (no_ipv6_nd_router_preference,
       no_ipv6_nd_router_preference_cmd,
       "no ipv6 nd router-preference [<high|medium|low>]",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Default router preference\n"
       "High default router preference\n"
       "Medium default router preference (default)\n"
       "Low default router preference\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;

	zif->rtadv.DefaultPreference =
		RTADV_PREF_MEDIUM; /* Default per RFC4191. */

	return CMD_SUCCESS;
}

DEFUN (ipv6_nd_mtu,
       ipv6_nd_mtu_cmd,
       "ipv6 nd mtu (1-65535)",
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Advertised MTU\n"
       "MTU in bytes\n")
{
	int idx_number = 3;
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;
	zif->rtadv.AdvLinkMTU = strtoul(argv[idx_number]->arg, NULL, 10);
	return CMD_SUCCESS;
}

DEFUN (no_ipv6_nd_mtu,
       no_ipv6_nd_mtu_cmd,
       "no ipv6 nd mtu [(1-65535)]",
       NO_STR
       "Interface IPv6 config commands\n"
       "Neighbor discovery\n"
       "Advertised MTU\n"
       "MTU in bytes\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;
	zif->rtadv.AdvLinkMTU = 0;
	return CMD_SUCCESS;
}

static struct rtadv_rdnss *rtadv_rdnss_new(void)
{
	return XCALLOC(MTYPE_RTADV_RDNSS, sizeof(struct rtadv_rdnss));
}

static void rtadv_rdnss_free(struct rtadv_rdnss *rdnss)
{
	XFREE(MTYPE_RTADV_RDNSS, rdnss);
}

static struct rtadv_rdnss *rtadv_rdnss_lookup(struct list *list,
					      struct rtadv_rdnss *rdnss)
{
	struct listnode *node;
	struct rtadv_rdnss *p;

	for (ALL_LIST_ELEMENTS_RO(list, node, p))
		if (IPV6_ADDR_SAME(&p->addr, &rdnss->addr))
			return p;
	return NULL;
}

static struct rtadv_rdnss *rtadv_rdnss_get(struct list *list,
					   struct rtadv_rdnss *rdnss)
{
	struct rtadv_rdnss *p;

	p = rtadv_rdnss_lookup(list, rdnss);
	if (p)
		return p;

	p = rtadv_rdnss_new();
	memcpy(p, rdnss, sizeof(struct rtadv_rdnss));
	listnode_add(list, p);

	return p;
}

static void rtadv_rdnss_set(struct zebra_if *zif, struct rtadv_rdnss *rdnss)
{
	struct rtadv_rdnss *p;

	p = rtadv_rdnss_get(zif->rtadv.AdvRDNSSList, rdnss);
	p->lifetime = rdnss->lifetime;
	p->lifetime_set = rdnss->lifetime_set;
}

static int rtadv_rdnss_reset(struct zebra_if *zif, struct rtadv_rdnss *rdnss)
{
	struct rtadv_rdnss *p;

	p = rtadv_rdnss_lookup(zif->rtadv.AdvRDNSSList, rdnss);
	if (p) {
		listnode_delete(zif->rtadv.AdvRDNSSList, p);
		rtadv_rdnss_free(p);
		return 1;
	}

	return 0;
}

static struct rtadv_dnssl *rtadv_dnssl_new(void)
{
	return XCALLOC(MTYPE_RTADV_DNSSL, sizeof(struct rtadv_dnssl));
}

static void rtadv_dnssl_free(struct rtadv_dnssl *dnssl)
{
	XFREE(MTYPE_RTADV_DNSSL, dnssl);
}

static struct rtadv_dnssl *rtadv_dnssl_lookup(struct list *list,
					      struct rtadv_dnssl *dnssl)
{
	struct listnode *node;
	struct rtadv_dnssl *p;

	for (ALL_LIST_ELEMENTS_RO(list, node, p))
		if (!strcasecmp(p->name, dnssl->name))
			return p;
	return NULL;
}

static struct rtadv_dnssl *rtadv_dnssl_get(struct list *list,
					   struct rtadv_dnssl *dnssl)
{
	struct rtadv_dnssl *p;

	p = rtadv_dnssl_lookup(list, dnssl);
	if (p)
		return p;

	p = rtadv_dnssl_new();
	memcpy(p, dnssl, sizeof(struct rtadv_dnssl));
	listnode_add(list, p);

	return p;
}

static void rtadv_dnssl_set(struct zebra_if *zif, struct rtadv_dnssl *dnssl)
{
	struct rtadv_dnssl *p;

	p = rtadv_dnssl_get(zif->rtadv.AdvDNSSLList, dnssl);
	memcpy(p, dnssl, sizeof(struct rtadv_dnssl));
}

static int rtadv_dnssl_reset(struct zebra_if *zif, struct rtadv_dnssl *dnssl)
{
	struct rtadv_dnssl *p;

	p = rtadv_dnssl_lookup(zif->rtadv.AdvDNSSLList, dnssl);
	if (p) {
		listnode_delete(zif->rtadv.AdvDNSSLList, p);
		rtadv_dnssl_free(p);
		return 1;
	}

	return 0;
}

/*
 * Convert dotted domain name (with or without trailing root zone dot) to
 * sequence of length-prefixed labels, as described in [RFC1035 3.1]. Write up
 * to strlen(in) + 2 octets to out.
 *
 * Returns the number of octets written to out or -1 if in does not constitute
 * a valid domain name.
 */
static int rtadv_dnssl_encode(uint8_t *out, const char *in)
{
	const char *label_start, *label_end;
	size_t outp;

	outp = 0;
	label_start = in;

	while (*label_start) {
		size_t label_len;

		label_end = strchr(label_start, '.');
		if (label_end == NULL)
			label_end = label_start + strlen(label_start);

		label_len = label_end - label_start;
		if (label_len >= 64)
			return -1; /* labels must be 63 octets or less */

		out[outp++] = (uint8_t)label_len;
		memcpy(out + outp, label_start, label_len);
		outp += label_len;
		label_start += label_len;
		if (*label_start == '.')
			label_start++;
	}

	out[outp++] = '\0';
	return outp;
}

DEFUN(ipv6_nd_rdnss,
      ipv6_nd_rdnss_cmd,
      "ipv6 nd rdnss X:X::X:X [<(0-4294967295)|infinite>]",
      "Interface IPv6 config commands\n"
      "Neighbor discovery\n"
      "Recursive DNS server information\n"
      "IPv6 address\n"
      "Valid lifetime in seconds\n"
      "Infinite valid lifetime\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;
	struct rtadv_rdnss rdnss = {};

	if (inet_pton(AF_INET6, argv[3]->arg, &rdnss.addr) != 1) {
		vty_out(vty, "Malformed IPv6 address\n");
		return CMD_WARNING_CONFIG_FAILED;
	}
	if (argc > 4) {
		char *lifetime = argv[4]->type == RANGE_TKN ? argv[4]->arg
							    : argv[4]->text;
		rdnss.lifetime = strmatch(lifetime, "infinite")
					 ? UINT32_MAX
					 : strtoll(lifetime, NULL, 10);
		rdnss.lifetime_set = 1;
	}

	rtadv_rdnss_set(zif, &rdnss);

	return CMD_SUCCESS;
}

DEFUN(no_ipv6_nd_rdnss,
      no_ipv6_nd_rdnss_cmd,
      "no ipv6 nd rdnss X:X::X:X [<(0-4294967295)|infinite>]",
      NO_STR
      "Interface IPv6 config commands\n"
      "Neighbor discovery\n"
      "Recursive DNS server information\n"
      "IPv6 address\n"
      "Valid lifetime in seconds\n"
      "Infinite valid lifetime\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;
	struct rtadv_rdnss rdnss = {};

	if (inet_pton(AF_INET6, argv[4]->arg, &rdnss.addr) != 1) {
		vty_out(vty, "Malformed IPv6 address\n");
		return CMD_WARNING_CONFIG_FAILED;
	}
	if (rtadv_rdnss_reset(zif, &rdnss) != 1) {
		vty_out(vty, "Non-existant RDNSS address\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	return CMD_SUCCESS;
}

DEFUN(ipv6_nd_dnssl,
      ipv6_nd_dnssl_cmd,
      "ipv6 nd dnssl SUFFIX [<(0-4294967295)|infinite>]",
      "Interface IPv6 config commands\n"
      "Neighbor discovery\n"
      "DNS search list information\n"
      "Domain name suffix\n"
      "Valid lifetime in seconds\n"
      "Infinite valid lifetime\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;
	struct rtadv_dnssl dnssl = {};
	size_t len;
	int ret;

	len = strlcpy(dnssl.name, argv[3]->arg, sizeof(dnssl.name));
	if (len == 0 || len >= sizeof(dnssl.name)) {
		vty_out(vty, "Malformed DNS search domain\n");
		return CMD_WARNING_CONFIG_FAILED;
	}
	if (dnssl.name[len - 1] == '.') {
		/*
		 * Allow, but don't require, a trailing dot signifying the root
		 * zone. Canonicalize by cutting it off if present.
		 */
		dnssl.name[len - 1] = '\0';
		len--;
	}
	if (argc > 4) {
		char *lifetime = argv[4]->type == RANGE_TKN ? argv[4]->arg
							    : argv[4]->text;
		dnssl.lifetime = strmatch(lifetime, "infinite")
					 ? UINT32_MAX
					 : strtoll(lifetime, NULL, 10);
		dnssl.lifetime_set = 1;
	}

	ret = rtadv_dnssl_encode(dnssl.encoded_name, dnssl.name);
	if (ret < 0) {
		vty_out(vty, "Malformed DNS search domain\n");
		return CMD_WARNING_CONFIG_FAILED;
	}
	dnssl.encoded_len = ret;
	rtadv_dnssl_set(zif, &dnssl);

	return CMD_SUCCESS;
}

DEFUN(no_ipv6_nd_dnssl,
      no_ipv6_nd_dnssl_cmd,
      "no ipv6 nd dnssl SUFFIX [<(0-4294967295)|infinite>]",
      NO_STR
      "Interface IPv6 config commands\n"
      "Neighbor discovery\n"
      "DNS search list information\n"
      "Domain name suffix\n"
      "Valid lifetime in seconds\n"
      "Infinite valid lifetime\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct zebra_if *zif = ifp->info;
	struct rtadv_dnssl dnssl = {};
	size_t len;

	len = strlcpy(dnssl.name, argv[4]->arg, sizeof(dnssl.name));
	if (len == 0 || len >= sizeof(dnssl.name)) {
		vty_out(vty, "Malformed DNS search domain\n");
		return CMD_WARNING_CONFIG_FAILED;
	}
	if (dnssl.name[len - 1] == '.') {
		dnssl.name[len - 1] = '\0';
		len--;
	}
	if (rtadv_dnssl_reset(zif, &dnssl) != 1) {
		vty_out(vty, "Non-existant DNS search domain\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	return CMD_SUCCESS;
}


/* Dump interface ND information to vty. */
static int nd_dump_vty(struct vty *vty, struct interface *ifp)
{
	struct zebra_if *zif;
	struct rtadvconf *rtadv;
	int interval;

	zif = (struct zebra_if *)ifp->info;
	rtadv = &zif->rtadv;

	if (rtadv->AdvSendAdvertisements) {
		vty_out(vty,
			"  ND advertised reachable time is %d milliseconds\n",
			rtadv->AdvReachableTime);
		vty_out(vty,
			"  ND advertised retransmit interval is %u milliseconds\n",
			rtadv->AdvRetransTimer);
		vty_out(vty, "  ND advertised hop-count limit is %d hops\n",
			rtadv->AdvCurHopLimit);
		vty_out(vty, "  ND router advertisements sent: %d rcvd: %d\n",
			zif->ra_sent, zif->ra_rcvd);
		interval = rtadv->MaxRtrAdvInterval;
		if (interval % 1000)
			vty_out(vty,
				"  ND router advertisements are sent every %d milliseconds\n",
				interval);
		else
			vty_out(vty,
				"  ND router advertisements are sent every %d seconds\n",
				interval / 1000);
		if (!rtadv->UseFastRexmit)
			vty_out(vty,
				"  ND router advertisements do not use fast retransmit\n");

		if (rtadv->AdvDefaultLifetime != -1)
			vty_out(vty,
				"  ND router advertisements live for %d seconds\n",
				rtadv->AdvDefaultLifetime);
		else
			vty_out(vty,
				"  ND router advertisements lifetime tracks ra-interval\n");
		vty_out(vty,
			"  ND router advertisement default router preference is %s\n",
			rtadv_pref_strs[rtadv->DefaultPreference]);
		if (rtadv->AdvManagedFlag)
			vty_out(vty,
				"  Hosts use DHCP to obtain routable addresses.\n");
		else
			vty_out(vty,
				"  Hosts use stateless autoconfig for addresses.\n");
		if (rtadv->AdvHomeAgentFlag) {
			vty_out(vty,
				"  ND router advertisements with Home Agent flag bit set.\n");
			if (rtadv->HomeAgentLifetime != -1)
				vty_out(vty,
					"  Home Agent lifetime is %u seconds\n",
					rtadv->HomeAgentLifetime);
			else
				vty_out(vty,
					"  Home Agent lifetime tracks ra-lifetime\n");
			vty_out(vty, "  Home Agent preference is %u\n",
				rtadv->HomeAgentPreference);
		}
		if (rtadv->AdvIntervalOption)
			vty_out(vty,
				"  ND router advertisements with Adv. Interval option.\n");
	}
	return 0;
}


/* Write configuration about router advertisement. */
static int rtadv_config_write(struct vty *vty, struct interface *ifp)
{
	struct zebra_if *zif;
	struct listnode *node;
	struct rtadv_prefix *rprefix;
	struct rtadv_rdnss *rdnss;
	struct rtadv_dnssl *dnssl;
	int interval;

	zif = ifp->info;

	if (!if_is_loopback(ifp)) {
		if (zif->rtadv.AdvSendAdvertisements
		    && CHECK_FLAG(zif->rtadv.ra_configured, VTY_RA_CONFIGURED))
			vty_out(vty, " no ipv6 nd suppress-ra\n");
	}

	interval = zif->rtadv.MaxRtrAdvInterval;
	if (CHECK_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED)) {
		if (interval % 1000)
			vty_out(vty, " ipv6 nd ra-interval msec %d\n",
				interval);
		else if (interval != RTADV_MAX_RTR_ADV_INTERVAL)
			vty_out(vty, " ipv6 nd ra-interval %d\n",
				interval / 1000);
	}

	if (zif->rtadv.AdvIntervalOption)
		vty_out(vty, " ipv6 nd adv-interval-option\n");

	if (!zif->rtadv.UseFastRexmit)
		vty_out(vty, " no ipv6 nd ra-fast-retrans\n");

	if (zif->rtadv.AdvRetransTimer != 0)
		vty_out(vty, " ipv6 nd ra-retrans-interval %u\n",
			zif->rtadv.AdvRetransTimer);

	if (zif->rtadv.AdvCurHopLimit != RTADV_DEFAULT_HOPLIMIT)
		vty_out(vty, " ipv6 nd ra-hop-limit %d\n",
			zif->rtadv.AdvCurHopLimit);

	if (zif->rtadv.AdvDefaultLifetime != -1)
		vty_out(vty, " ipv6 nd ra-lifetime %d\n",
			zif->rtadv.AdvDefaultLifetime);

	if (zif->rtadv.HomeAgentPreference)
		vty_out(vty, " ipv6 nd home-agent-preference %u\n",
			zif->rtadv.HomeAgentPreference);

	if (zif->rtadv.HomeAgentLifetime != -1)
		vty_out(vty, " ipv6 nd home-agent-lifetime %u\n",
			zif->rtadv.HomeAgentLifetime);

	if (zif->rtadv.AdvHomeAgentFlag)
		vty_out(vty, " ipv6 nd home-agent-config-flag\n");

	if (zif->rtadv.AdvReachableTime)
		vty_out(vty, " ipv6 nd reachable-time %d\n",
			zif->rtadv.AdvReachableTime);

	if (zif->rtadv.AdvManagedFlag)
		vty_out(vty, " ipv6 nd managed-config-flag\n");

	if (zif->rtadv.AdvOtherConfigFlag)
		vty_out(vty, " ipv6 nd other-config-flag\n");

	if (zif->rtadv.DefaultPreference != RTADV_PREF_MEDIUM)
		vty_out(vty, " ipv6 nd router-preference %s\n",
			rtadv_pref_strs[zif->rtadv.DefaultPreference]);

	if (zif->rtadv.AdvLinkMTU)
		vty_out(vty, " ipv6 nd mtu %d\n", zif->rtadv.AdvLinkMTU);

	frr_each (rtadv_prefixes, zif->rtadv.prefixes, rprefix) {
		if ((rprefix->AdvPrefixCreate == PREFIX_SRC_MANUAL)
		    || (rprefix->AdvPrefixCreate == PREFIX_SRC_BOTH)) {
			vty_out(vty, " ipv6 nd prefix %pFX", &rprefix->prefix);
			if ((rprefix->AdvValidLifetime != RTADV_VALID_LIFETIME)
			    || (rprefix->AdvPreferredLifetime
				!= RTADV_PREFERRED_LIFETIME)) {
				if (rprefix->AdvValidLifetime == UINT32_MAX)
					vty_out(vty, " infinite");
				else
					vty_out(vty, " %u",
						rprefix->AdvValidLifetime);
				if (rprefix->AdvPreferredLifetime == UINT32_MAX)
					vty_out(vty, " infinite");
				else
					vty_out(vty, " %u",
						rprefix->AdvPreferredLifetime);
			}
			if (!rprefix->AdvOnLinkFlag)
				vty_out(vty, " off-link");
			if (!rprefix->AdvAutonomousFlag)
				vty_out(vty, " no-autoconfig");
			if (rprefix->AdvRouterAddressFlag)
				vty_out(vty, " router-address");
			vty_out(vty, "\n");
		}
	}

	for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvRDNSSList, node, rdnss)) {
		char buf[INET6_ADDRSTRLEN];

		vty_out(vty, " ipv6 nd rdnss %s",
			inet_ntop(AF_INET6, &rdnss->addr, buf, sizeof(buf)));
		if (rdnss->lifetime_set) {
			if (rdnss->lifetime == UINT32_MAX)
				vty_out(vty, " infinite");
			else
				vty_out(vty, " %u", rdnss->lifetime);
		}
		vty_out(vty, "\n");
	}
	for (ALL_LIST_ELEMENTS_RO(zif->rtadv.AdvDNSSLList, node, dnssl)) {
		vty_out(vty, " ipv6 nd dnssl %s", dnssl->name);
		if (dnssl->lifetime_set) {
			if (dnssl->lifetime == UINT32_MAX)
				vty_out(vty, " infinite");
			else
				vty_out(vty, " %u", dnssl->lifetime);
		}
		vty_out(vty, "\n");
	}
	return 0;
}


static void rtadv_event(struct zebra_vrf *zvrf, enum rtadv_event event, int val)
{
	struct rtadv *rtadv;

	if (IS_ZEBRA_DEBUG_EVENT) {
		struct vrf *vrf = zvrf->vrf;

		zlog_debug("%s(%s) with event: %d and val: %d", __func__,
			   VRF_LOGNAME(vrf), event, val);
	}

	rtadv = &zvrf->rtadv;

	switch (event) {
	case RTADV_START:
		thread_add_read(zrouter.master, rtadv_read, zvrf, rtadv->sock,
				&rtadv->ra_read);
		thread_add_event(zrouter.master, rtadv_timer, zvrf, 0,
				 &rtadv->ra_timer);
		break;
	case RTADV_STOP:
		THREAD_OFF(rtadv->ra_timer);
		THREAD_OFF(rtadv->ra_read);
		break;
	case RTADV_TIMER:
		thread_add_timer(zrouter.master, rtadv_timer, zvrf, val,
				 &rtadv->ra_timer);
		break;
	case RTADV_TIMER_MSEC:
		thread_add_timer_msec(zrouter.master, rtadv_timer, zvrf, val,
				      &rtadv->ra_timer);
		break;
	case RTADV_READ:
		thread_add_read(zrouter.master, rtadv_read, zvrf, rtadv->sock,
				&rtadv->ra_read);
		break;
	default:
		break;
	}
	return;
}

void rtadv_if_up(struct zebra_if *zif)
{
	struct zebra_vrf *zvrf = rtadv_interface_get_zvrf(zif->ifp);

	/* Enable fast tx of RA if enabled && RA interval is not in msecs */
	if (zif->rtadv.AdvSendAdvertisements &&
	    (zif->rtadv.MaxRtrAdvInterval >= 1000) &&
	    zif->rtadv.UseFastRexmit) {
		zif->rtadv.inFastRexmit = 1;
		zif->rtadv.NumFastReXmitsRemain = RTADV_NUM_FAST_REXMITS;
	}

	/*
	 * startup the state machine, if it hasn't been already
	 * due to a delayed ifindex on startup ordering
	 */
	if (zif->rtadv.AdvSendAdvertisements)
		rtadv_start_interface_events(zvrf, zif);
}

void rtadv_if_init(struct zebra_if *zif)
{
	/* Set default router advertise values. */
	struct rtadvconf *rtadv;

	rtadv = &zif->rtadv;

	rtadv->AdvSendAdvertisements = 0;
	rtadv->MaxRtrAdvInterval = RTADV_MAX_RTR_ADV_INTERVAL;
	rtadv->MinRtrAdvInterval = RTADV_MIN_RTR_ADV_INTERVAL;
	rtadv->AdvIntervalTimer = 0;
	rtadv->AdvManagedFlag = 0;
	rtadv->AdvOtherConfigFlag = 0;
	rtadv->AdvHomeAgentFlag = 0;
	rtadv->AdvLinkMTU = 0;
	rtadv->AdvReachableTime = 0;
	rtadv->AdvRetransTimer = 0;
	rtadv->AdvCurHopLimit = RTADV_DEFAULT_HOPLIMIT;
	memset(&rtadv->lastadvcurhoplimit, 0,
	       sizeof(rtadv->lastadvcurhoplimit));
	memset(&rtadv->lastadvmanagedflag, 0,
	       sizeof(rtadv->lastadvmanagedflag));
	memset(&rtadv->lastadvotherconfigflag, 0,
	       sizeof(rtadv->lastadvotherconfigflag));
	memset(&rtadv->lastadvreachabletime, 0,
	       sizeof(rtadv->lastadvreachabletime));
	memset(&rtadv->lastadvretranstimer, 0,
	       sizeof(rtadv->lastadvretranstimer));
	rtadv->AdvDefaultLifetime = -1; /* derive from MaxRtrAdvInterval */
	rtadv->HomeAgentPreference = 0;
	rtadv->HomeAgentLifetime = -1; /* derive from AdvDefaultLifetime */
	rtadv->AdvIntervalOption = 0;
	rtadv->UseFastRexmit = true;
	rtadv->DefaultPreference = RTADV_PREF_MEDIUM;

	rtadv_prefixes_init(rtadv->prefixes);

	rtadv->AdvRDNSSList = list_new();
	rtadv->AdvDNSSLList = list_new();
}

void rtadv_if_fini(struct zebra_if *zif)
{
	struct rtadvconf *rtadv;
	struct rtadv_prefix *rp;

	rtadv = &zif->rtadv;

	while ((rp = rtadv_prefixes_pop(rtadv->prefixes)))
		rtadv_prefix_free(rp);

	list_delete(&rtadv->AdvRDNSSList);
	list_delete(&rtadv->AdvDNSSLList);
}

void rtadv_vrf_init(struct zebra_vrf *zvrf)
{
	if (!vrf_is_backend_netns() && (zvrf_id(zvrf) != VRF_DEFAULT))
		return;

	zvrf->rtadv.sock = rtadv_make_socket(zvrf->zns->ns_id);
}

void rtadv_vrf_terminate(struct zebra_vrf *zvrf)
{
	if (!vrf_is_backend_netns() && (zvrf_id(zvrf) != VRF_DEFAULT))
		return;

	rtadv_event(zvrf, RTADV_STOP, 0);
	if (zvrf->rtadv.sock >= 0) {
		close(zvrf->rtadv.sock);
		zvrf->rtadv.sock = -1;
	}

	adv_if_clean(zvrf);
	adv_msec_if_clean(zvrf);
}

void rtadv_cmd_init(void)
{
	interfaces_configured_for_ra_from_bgp = 0;

	hook_register(zebra_if_extra_info, nd_dump_vty);
	hook_register(zebra_if_config_wr, rtadv_config_write);

	install_element(VIEW_NODE, &show_ipv6_nd_ra_if_cmd);

	install_element(INTERFACE_NODE, &ipv6_nd_ra_fast_retrans_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_ra_fast_retrans_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_ra_retrans_interval_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_ra_retrans_interval_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_ra_hop_limit_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_ra_hop_limit_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_suppress_ra_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_suppress_ra_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_ra_interval_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_ra_interval_msec_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_ra_interval_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_ra_lifetime_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_ra_lifetime_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_reachable_time_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_reachable_time_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_managed_config_flag_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_managed_config_flag_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_other_config_flag_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_other_config_flag_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_homeagent_config_flag_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_homeagent_config_flag_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_homeagent_preference_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_homeagent_preference_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_homeagent_lifetime_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_homeagent_lifetime_cmd);
	install_element(INTERFACE_NODE,
			&ipv6_nd_adv_interval_config_option_cmd);
	install_element(INTERFACE_NODE,
			&no_ipv6_nd_adv_interval_config_option_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_prefix_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_prefix_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_router_preference_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_router_preference_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_mtu_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_mtu_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_rdnss_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_rdnss_cmd);
	install_element(INTERFACE_NODE, &ipv6_nd_dnssl_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_nd_dnssl_cmd);
}

static int if_join_all_router(int sock, struct interface *ifp)
{
	int ret;

	struct ipv6_mreq mreq;

	memset(&mreq, 0, sizeof(mreq));
	inet_pton(AF_INET6, ALLROUTER, &mreq.ipv6mr_multiaddr);
	mreq.ipv6mr_interface = ifp->ifindex;

	ret = setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&mreq,
			 sizeof(mreq));
	if (ret < 0)
		flog_err_sys(EC_LIB_SOCKET,
			     "%s(%u): Failed to join group, socket %u error %s",
			     ifp->name, ifp->ifindex, sock,
			     safe_strerror(errno));

	if (IS_ZEBRA_DEBUG_EVENT)
		zlog_debug(
			"%s(%s:%u): Join All-Routers multicast group, socket %u",
			ifp->name, ifp->vrf->name, ifp->ifindex, sock);

	return 0;
}

static int if_leave_all_router(int sock, struct interface *ifp)
{
	int ret;

	struct ipv6_mreq mreq;

	memset(&mreq, 0, sizeof(mreq));
	inet_pton(AF_INET6, ALLROUTER, &mreq.ipv6mr_multiaddr);
	mreq.ipv6mr_interface = ifp->ifindex;

	ret = setsockopt(sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, (char *)&mreq,
			 sizeof(mreq));
	if (ret < 0)
		flog_err_sys(
			EC_LIB_SOCKET,
			"%s(%s:%u): Failed to leave group, socket %u error %s",
			ifp->name, ifp->vrf->name, ifp->ifindex, sock,
			safe_strerror(errno));

	if (IS_ZEBRA_DEBUG_EVENT)
		zlog_debug(
			"%s(%s:%u): Leave All-Routers multicast group, socket %u",
			ifp->name, ifp->vrf->name, ifp->ifindex, sock);

	return 0;
}

bool rtadv_compiled_in(void)
{
	return true;
}

#else /* !HAVE_RTADV */
/*
 * If the end user does not have RADV enabled we should
 * handle this better
 */
void zebra_interface_radv_disable(ZAPI_HANDLER_ARGS)
{
	if (IS_ZEBRA_DEBUG_PACKET)
		zlog_debug(
			"Received %s command, but ZEBRA is not compiled with Router Advertisements on",
			zserv_command_string(hdr->command));

	return;
}

void zebra_interface_radv_enable(ZAPI_HANDLER_ARGS)
{
	if (IS_ZEBRA_DEBUG_PACKET)
		zlog_debug(
			"Received %s command, but ZEBRA is not compiled with Router Advertisements on",
			zserv_command_string(hdr->command));

	return;
}

bool rtadv_compiled_in(void)
{
	return false;
}

#endif /* HAVE_RTADV */

uint32_t rtadv_get_interfaces_configured_from_bgp(void)
{
	return interfaces_configured_for_ra_from_bgp;
}
