// SPDX-License-Identifier: GPL-2.0-or-later
/* Router advertisement
 * Copyright (C) 2016 Cumulus Networks
 * Copyright (C) 2005 6WIND <jean-mickael.guerin@6wind.com>
 * Copyright (C) 1999 Kunihiro Ishiguro
 */

#include <zebra.h>
#include <netinet/icmp6.h>

#include "memory.h"
#include "sockopt.h"
#include "frrevent.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;
#define RTADV_ADATA_SIZE 1024

#if defined(HAVE_RTADV)

#include "zebra/rtadv_clippy.c"

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[IFNAMSIZ];
	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 = { 0 };
	struct iovec iov = { 0 };
	struct cmsghdr *cmsgptr;
	struct in6_pktinfo *pkt;
	struct sockaddr_in6 addr = { 0 };
	unsigned char buf[RTADV_MSG_SIZE] = { 0 };
	char adata[RTADV_ADATA_SIZE] = { 0 };

	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;

	/* 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 event *thread)
{
	struct zebra_vrf *zvrf = EVENT_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;
}

static const char *rtadv_optionalhdr2str(uint8_t opt_type)
{
	switch (opt_type) {
	case ND_OPT_SOURCE_LINKADDR:
		return "Optional Source Link Address";
	case ND_OPT_TARGET_LINKADDR:
		return "Optional Target Link Address";
	case ND_OPT_PREFIX_INFORMATION:
		return "Optional Prefix Information";
	case ND_OPT_REDIRECTED_HEADER:
		return "Optional Redirected Header";
	case ND_OPT_MTU:
		return "Optional MTU";
	case ND_OPT_RTR_ADV_INTERVAL:
		return "Optional Advertisement Interval";
	case ND_OPT_HOME_AGENT_INFO:
		return "Optional Home Agent Information";
	}

	return "Unknown Optional Type";
}

/*
 * 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:
			if (IS_ZEBRA_DEBUG_PACKET)
				zlog_debug(
					"%s:Received Packet with optional Header type %s(%u) that is being ignored",
					__func__,
					rtadv_optionalhdr2str(
						opt_hdr->nd_opt_type),
					opt_hdr->nd_opt_type);
			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 (%u) doesn't agree with %s (%u)",
			ifp->name, ifp->ifindex, zif->rtadv.AdvCurHopLimit,
			addr_str, radvert->nd_ra_curhoplimit);
		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 (%u) doesn't agree with %s (%u)",
			ifp->name, ifp->ifindex, zif->rtadv.AdvManagedFlag,
			addr_str,
			!!CHECK_FLAG(radvert->nd_ra_flags_reserved,
				     ND_RA_FLAG_MANAGED));
		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 (%u) doesn't agree with %s (%u)",
			ifp->name, ifp->ifindex, zif->rtadv.AdvOtherConfigFlag,
			addr_str,
			!!CHECK_FLAG(radvert->nd_ra_flags_reserved,
				     ND_RA_FLAG_OTHER));
		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 (%u) doesn't agree with %s (%u)",
			ifp->name, ifp->ifindex, zif->rtadv.AdvReachableTime,
			addr_str, ntohl(radvert->nd_ra_reachable));
		monotime(&zif->rtadv.lastadvreachabletime);
	}

	if ((radvert->nd_ra_retransmit && zif->rtadv.AdvRetransTimer) &&
	    (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 (%u) doesn't agree with %s (%u)",
			ifp->name, ifp->ifindex, zif->rtadv.AdvRetransTimer,
			addr_str, ntohl(radvert->nd_ra_retransmit));
		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 event *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 = EVENT_ARG(thread);

	sock = EVENT_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 struct rtadv_prefix *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);
		}
	}

	return rprefix;
}

static void rtadv_prefix_reset(struct zebra_if *zif, struct rtadv_prefix *rp,
			       struct rtadv_prefix *rprefix)
{
	if (!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;
			}
		} else if (rp->AdvPrefixCreate == PREFIX_SRC_AUTO) {
			if (rprefix->AdvPrefixCreate == PREFIX_SRC_BOTH) {
				rprefix->AdvPrefixCreate = PREFIX_SRC_MANUAL;
				return;
			}
		}

		rtadv_prefixes_del(zif->rtadv.prefixes, rprefix);
		rtadv_prefix_free(rprefix);
	}
}

struct rtadv_prefix *rtadv_add_prefix_manual(struct zebra_if *zif,
					     struct rtadv_prefix *rp)
{
	rp->AdvPrefixCreate = PREFIX_SRC_MANUAL;
	return rtadv_prefix_set(zif, rp);
}

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

	rp.AdvPrefixCreate = PREFIX_SRC_MANUAL;

	rtadv_prefix_reset(zif, &rp, rprefix);
}

/* 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, NULL);
}

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);
}

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);
		}
	}
}

void ipv6_nd_interval_set(struct interface *ifp, uint32_t interval)
{
	struct zebra_if *zif = ifp->info;
	struct zebra_vrf *zvrf = rtadv_interface_get_zvrf(ifp);
	struct adv_if *adv_if;

	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);

	zif->rtadv.MaxRtrAdvInterval = interval;
	zif->rtadv.MinRtrAdvInterval = 0.33 * interval;

	if (interval != RTADV_MAX_RTR_ADV_INTERVAL) {
		SET_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED);
		zif->rtadv.AdvIntervalTimer = 0;
	} else {
		if (CHECK_FLAG(zif->rtadv.ra_configured, BGP_RA_CONFIGURED))
			zif->rtadv.MaxRtrAdvInterval = 10000;

		UNSET_FLAG(zif->rtadv.ra_configured, VTY_RA_INTERVAL_CONFIGURED);
		zif->rtadv.AdvIntervalTimer = zif->rtadv.MaxRtrAdvInterval;
	}
}

/*
 * 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, 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;
}

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);
}

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

	p = rtadv_rdnss_new();
	memcpy(p, rdnss, sizeof(struct rtadv_rdnss));
	listnode_add(zif->rtadv.AdvRDNSSList, p);

	return p;
}

void rtadv_rdnss_reset(struct zebra_if *zif, struct rtadv_rdnss *p)
{
	listnode_delete(zif->rtadv.AdvRDNSSList, p);
	rtadv_rdnss_free(p);
}

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);
}

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

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

	return p;
}

void rtadv_dnssl_reset(struct zebra_if *zif, struct rtadv_dnssl *p)
{
	listnode_delete(zif->rtadv.AdvDNSSLList, p);
	rtadv_dnssl_free(p);
}

/*
 * 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.
 */
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;
}

/* 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;
}

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:
		event_add_read(zrouter.master, rtadv_read, zvrf, rtadv->sock,
			       &rtadv->ra_read);
		event_add_event(zrouter.master, rtadv_timer, zvrf, 0,
				&rtadv->ra_timer);
		break;
	case RTADV_STOP:
		EVENT_OFF(rtadv->ra_timer);
		EVENT_OFF(rtadv->ra_read);
		break;
	case RTADV_TIMER:
		event_add_timer(zrouter.master, rtadv_timer, zvrf, val,
				&rtadv->ra_timer);
		break;
	case RTADV_TIMER_MSEC:
		event_add_timer_msec(zrouter.master, rtadv_timer, zvrf, val,
				     &rtadv->ra_timer);
		break;
	case RTADV_READ:
		event_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);

	install_element(VIEW_NODE, &show_ipv6_nd_ra_if_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;
}

void rtadv_init(void)
{
	if (CMSG_SPACE(sizeof(struct in6_pktinfo)) > RTADV_ADATA_SIZE) {
		zlog_debug("%s: RTADV_ADATA_SIZE chosen will not work on this platform, please use a larger size",
			   __func__);

		exit(-1);
	}
}
