// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * pim_autorp.c: PIM AutoRP handling routines
 *
 * Copyright (C) 2024 ATCorp
 * Nathan Bahr
 */

#include <zebra.h>

#include <sys/types.h>
#include <sys/socket.h>

#include "lib/plist.h"
#include "lib/plist_int.h"
#include "lib/sockopt.h"
#include "lib/network.h"
#include "lib/termtable.h"
#include "lib/json.h"

#include "pimd.h"
#include "pim_util.h"
#include "pim_iface.h"
#include "pim_rp.h"
#include "pim_sock.h"
#include "pim_instance.h"
#include "pim_autorp.h"

DEFINE_MTYPE_STATIC(PIMD, PIM_AUTORP, "PIM AutoRP info");
DEFINE_MTYPE_STATIC(PIMD, PIM_AUTORP_RP, "PIM AutoRP discovered RP info");
DEFINE_MTYPE_STATIC(PIMD, PIM_AUTORP_ANNOUNCE, "PIM AutoRP announcement packet");
DEFINE_MTYPE_STATIC(PIMD, PIM_AUTORP_GRPPFIX, "PIM AutoRP group prefix list");

static const char *PIM_AUTORP_ANNOUNCEMENT_GRP = "224.0.1.39";
static const char *PIM_AUTORP_DISCOVERY_GRP = "224.0.1.40";
static const in_port_t PIM_AUTORP_PORT = 496;

static int pim_autorp_rp_cmp(const struct pim_autorp_rp *l, const struct pim_autorp_rp *r)
{
	return pim_addr_cmp(l->addr, r->addr);
}

DECLARE_SORTLIST_UNIQ(pim_autorp_rp, struct pim_autorp_rp, item, pim_autorp_rp_cmp);

static int pim_autorp_grppfix_cmp(const struct pim_autorp_grppfix *l,
				  const struct pim_autorp_grppfix *r)
{
	return prefix_cmp(&l->grp, &r->grp);
}

DECLARE_SORTLIST_UNIQ(pim_autorp_grppfix, struct pim_autorp_grppfix, item, pim_autorp_grppfix_cmp);

static void pim_autorp_grppfix_free(struct pim_autorp_grppfix_head *head)
{
	struct pim_autorp_grppfix *grp;

	while ((grp = pim_autorp_grppfix_pop(head)))
		XFREE(MTYPE_PIM_AUTORP_GRPPFIX, grp);
}

static void pim_autorp_rp_free(struct pim_autorp_rp *rp, bool installed)
{
	event_cancel(&rp->hold_timer);

	/* Clean up installed RP info */
	if (installed) {
		if (pim_rp_del(rp->autorp->pim, rp->addr, rp->grp,
			       (strlen(rp->grplist) ? rp->grplist : NULL), RP_SRC_AUTORP)) {
			zlog_warn("%s: Failed to delete RP %pI4", __func__, &rp->addr);
		}

		if (strlen(rp->grplist)) {
			struct prefix_list *pl;

			pl = prefix_list_lookup(AFI_IP, rp->grplist);
			if (pl)
				prefix_list_delete(pl);
		}
	}

	pim_autorp_grppfix_free(&rp->grp_pfix_list);
	pim_autorp_grppfix_fini(&rp->grp_pfix_list);

	XFREE(MTYPE_PIM_AUTORP_RP, rp);
}

static void pim_autorp_rplist_free(struct pim_autorp_rp_head *head, bool installed)
{
	struct pim_autorp_rp *rp;

	while ((rp = pim_autorp_rp_pop(head)))
		pim_autorp_rp_free(rp, installed);
}

static void pim_autorp_free(struct pim_autorp *autorp)
{
	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: Freeing PIM AutoRP", __func__);

	pim_autorp_rplist_free(&(autorp->discovery_rp_list), true);
	pim_autorp_rp_fini(&(autorp->discovery_rp_list));

	pim_autorp_rplist_free(&(autorp->candidate_rp_list), false);
	pim_autorp_rp_fini(&(autorp->candidate_rp_list));

	pim_autorp_rplist_free(&(autorp->mapping_rp_list), false);
	pim_autorp_rp_fini(&(autorp->mapping_rp_list));

	pim_autorp_rplist_free(&(autorp->advertised_rp_list), false);
	pim_autorp_rp_fini(&(autorp->advertised_rp_list));

	if (autorp->announce_pkt)
		XFREE(MTYPE_PIM_AUTORP_ANNOUNCE, autorp->announce_pkt);
}

static bool autorp_is_pim_interface(struct interface *ifp)
{
	struct pim_interface *pim_ifp = ifp->info;

	return (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE) && pim_ifp && pim_ifp->pim_enable &&
		(!pim_ifp->pim_passive_enable));
}

static bool pim_autorp_should_enable_socket(struct pim_autorp *autorp)
{
	struct interface *ifp;

	/* Only enable the socket if there are any PIM enabled interfaces */
	FOR_ALL_INTERFACES (autorp->pim->vrf, ifp) {
		if (autorp_is_pim_interface(ifp))
			return true;
	}
	return false;
}

static bool pim_autorp_should_close(struct pim_autorp *autorp)
{
	/* If discovery or mapping agent is active, then we need the socket open. We also want to leave
	 * the socket open if there are any pim interfaces and we have an announcement packet to send.
	 */
	return !autorp->do_discovery && !autorp->send_rp_discovery &&
	       !(pim_autorp_should_enable_socket(autorp) && autorp->announce_timer != NULL);
}

static bool pim_autorp_join_groups(struct interface *ifp)
{
	struct pim_interface *pim_ifp;
	struct pim_instance *pim;
	struct pim_autorp *autorp;
	pim_addr grp;

	pim_ifp = ifp->info;
	pim = pim_ifp->pim;
	autorp = pim->autorp;

	inet_pton(PIM_AF, PIM_AUTORP_DISCOVERY_GRP, &grp);
	if (pim_socket_join(autorp->sock, grp, pim_ifp->primary_address,
			    ifp->ifindex, pim_ifp)) {
		zlog_warn("Failed to join group %pI4 on interface %s", &grp, ifp->name);
		return false;
	}

	zlog_info("%s: Joined AutoRP discovery group %pPA on interface %s", __func__, &grp,
		  ifp->name);

	inet_pton(PIM_AF, PIM_AUTORP_ANNOUNCEMENT_GRP, &grp);
	if (pim_socket_join(pim->autorp->sock, grp, pim_ifp->primary_address, ifp->ifindex,
			    pim_ifp)) {
		zlog_warn("Failed to join group %pI4 on interface %s", &grp, ifp->name);
		return errno;
	}

	zlog_info("%s: Joined AutoRP announcement group %pPA on interface %s", __func__, &grp,
		  ifp->name);

	return true;
}

static bool pim_autorp_leave_groups(struct interface *ifp)
{
	struct pim_interface *pim_ifp;
	struct pim_instance *pim;
	struct pim_autorp *autorp;
	pim_addr grp;

	pim_ifp = ifp->info;
	pim = pim_ifp->pim;
	autorp = pim->autorp;

	inet_pton(PIM_AF, PIM_AUTORP_DISCOVERY_GRP, &grp);
	if (pim_socket_leave(autorp->sock, grp, pim_ifp->primary_address,
			     ifp->ifindex, pim_ifp)) {
		zlog_warn("Failed to leave group %pI4 on interface %s", &grp, ifp->name);
		return false;
	}

	zlog_info("%s: Left AutoRP discovery group %pPA on interface %s", __func__, &grp, ifp->name);

	inet_pton(PIM_AF, PIM_AUTORP_ANNOUNCEMENT_GRP, &grp);
	if (pim_socket_leave(pim->autorp->sock, grp, pim_ifp->primary_address, ifp->ifindex,
			     pim_ifp)) {
		zlog_warn("Failed to leave group %pI4 on interface %s", &grp, ifp->name);
		return errno;
	}

	zlog_info("%s: Left AutoRP announcement group %pPA on interface %s", __func__, &grp,
		  ifp->name);

	return true;
}

static bool pim_autorp_setup(int fd)
{
#if defined(HAVE_IP_PKTINFO)
	int data;
	socklen_t data_len = sizeof(data);
#endif

	struct sockaddr_in autorp_addr = { .sin_family = AF_INET,
					   .sin_addr = { .s_addr = INADDR_ANY },
					   .sin_port = htons(PIM_AUTORP_PORT) };

	setsockopt_so_recvbuf(fd, 1024 * 1024 * 8);

#if defined(HAVE_IP_PKTINFO)
	/* Linux and Solaris IP_PKTINFO */
	data = 1;
	if (setsockopt(fd, PIM_IPPROTO, IP_PKTINFO, &data, data_len)) {
		zlog_warn("%s: Could not set IP_PKTINFO on socket fd=%d: errno=%d: %s", __func__,
			  fd, errno, safe_strerror(errno));
		return false;
	}
#endif

	if (set_nonblocking(fd) < 0) {
		zlog_warn("%s: Could not set non blocking on socket fd=%d: errno=%d: %s", __func__,
			  fd, errno, safe_strerror(errno));
		return false;
	}

	if (sockopt_reuseaddr(fd)) {
		zlog_warn("%s: Could not set reuse addr on socket fd=%d: errno=%d: %s", __func__,
			  fd, errno, safe_strerror(errno));
		return false;
	}

	if (setsockopt_ipv4_multicast_loop(fd, 1) < 0) {
		zlog_warn("%s: Could not enable multicast loopback on socket fd=%d: errno=%d: %s",
			  __func__, fd, errno, safe_strerror(errno));
		return false;
	}

	if (bind(fd, (const struct sockaddr *)&autorp_addr, sizeof(autorp_addr)) < 0) {
		zlog_warn("%s: Could not bind socket: %pSUp, fd=%d, errno=%d, %s", __func__,
			  (union sockunion *)&autorp_addr, fd, errno, safe_strerror(errno));
		return false;
	}

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP finished setup", __func__);

	return true;
}

static void autorp_ma_rp_holdtime(struct event *evt)
{
	/* Mapping agent RP hold time expired, remove the RP */
	struct pim_autorp_rp *rp = EVENT_ARG(evt);

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP hold time expired, RP removed from mapping agent: addr=%pI4, grp=%pFX, grplist=%s",
			   __func__, &rp->addr, &rp->grp,
			   (strlen(rp->grplist) ? rp->grplist : "NONE"));

	pim_autorp_rp_del(&(rp->autorp->mapping_rp_list), rp);
	pim_autorp_rp_free(rp, false);
}

static bool autorp_recv_announcement(struct pim_autorp *autorp, uint8_t rpcnt, uint16_t holdtime,
				     char *buf, size_t buf_size)
{
	int i, j;
	struct autorp_pkt_rp *rp;
	struct autorp_pkt_grp *grp;
	size_t offset = 0;
	pim_addr rp_addr;
	struct pim_autorp_rp *ma_rp;
	struct pim_autorp_rp *trp;

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: Processing AutoRP Announcement (rpcnt=%u, holdtime=%u)", __func__,
			   rpcnt, holdtime);

	for (i = 0; i < rpcnt; ++i) {
		if ((buf_size - offset) < AUTORP_RPLEN) {
			zlog_warn("%s: Failed to parse AutoRP Announcement RP, invalid buffer size (%u < %u)",
				  __func__, (uint32_t)(buf_size - offset), AUTORP_RPLEN);
			return false;
		}

		rp = (struct autorp_pkt_rp *)(buf + offset);
		offset += AUTORP_RPLEN;

		rp_addr.s_addr = rp->addr;

		/* Ignore RP's limited to PIM version 1 or with an unknown version */
		if (rp->pimver == AUTORP_PIM_V1 || rp->pimver == AUTORP_PIM_VUNKNOWN) {
			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: Ignoring unsupported PIM version (%u) in AutoRP Announcement for RP %pPA",
					   __func__, rp->pimver, &rp_addr);
			/* Update the offset to skip past the groups advertised for this RP */
			offset += (AUTORP_GRPLEN * rp->grpcnt);
			continue;
		}

		if (rp->grpcnt == 0) {
			/* No groups?? */
			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: Announcement message has no groups for RP %pPA",
					   __func__, &rp_addr);
			continue;
		}

		if ((buf_size - offset) < AUTORP_GRPLEN) {
			zlog_warn("%s: Buffer underrun parsing groups for RP %pPA", __func__,
				  &rp_addr);
			return false;
		}

		/* Store all announced RP's, calculate what to send in discovery when discovery is sent. */
		ma_rp = XCALLOC(MTYPE_PIM_AUTORP_RP, sizeof(struct pim_autorp_rp));
		memcpy(&(ma_rp->addr), &rp_addr, sizeof(pim_addr));
		trp = pim_autorp_rp_add(&(autorp->mapping_rp_list), ma_rp);
		if (trp == NULL) {
			/* RP was brand new, finish initializing */
			ma_rp->autorp = autorp;
			ma_rp->holdtime = holdtime;
			ma_rp->hold_timer = NULL;
			ma_rp->grplist[0] = '\0';
			memset(&(ma_rp->grp), 0, sizeof(ma_rp->grp));
			pim_autorp_grppfix_init(&ma_rp->grp_pfix_list);
			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: New candidate RP learned (%pPA)", __func__,
					   &rp_addr);
		} else {
			/* Returned an existing entry, free allocated RP */
			XFREE(MTYPE_PIM_AUTORP_RP, ma_rp);
			ma_rp = trp;
			/* Free the existing group prefix list, in case the advertised groups changed */
			pim_autorp_grppfix_free(&ma_rp->grp_pfix_list);
		}

		/* Cancel any existing timer and restart it */
		event_cancel(&ma_rp->hold_timer);
		if (holdtime > 0)
			event_add_timer(router->master, autorp_ma_rp_holdtime, ma_rp,
					ma_rp->holdtime, &(ma_rp->hold_timer));

		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: Parsing %u group(s) for candidate RP %pPA", __func__,
				   rp->grpcnt, &rp_addr);

		for (j = 0; j < rp->grpcnt; ++j) {
			/* grp is already pointing at the first group in the buffer */
			struct pim_autorp_grppfix *lgrp;
			struct pim_autorp_grppfix *tgrp;

			if ((buf_size - offset) < AUTORP_GRPLEN) {
				zlog_warn("%s: Failed parsing AutoRP announcement, RP(%pI4), invalid buffer size (%u < %u)",
					  __func__, &rp_addr, (uint32_t)(buf_size - offset),
					  AUTORP_GRPLEN);
				return false;
			}

			grp = (struct autorp_pkt_grp *)(buf + offset);
			offset += AUTORP_GRPLEN;

			lgrp = XCALLOC(MTYPE_PIM_AUTORP_GRPPFIX, sizeof(struct pim_autorp_grppfix));
			lgrp->grp.family = AF_INET;
			lgrp->grp.prefixlen = grp->masklen;
			lgrp->grp.u.prefix4.s_addr = grp->addr;
			lgrp->negative = grp->negprefix;

			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: %s%pFX added to candidate RP %pPA", __func__,
					   (lgrp->negative ? "!" : ""), &lgrp->grp, &rp_addr);

			tgrp = pim_autorp_grppfix_add(&ma_rp->grp_pfix_list, lgrp);
			if (tgrp != NULL) {
				/* This should never happen but if there was an existing entry just free the
				 * allocated group prefix
				 */
				if (PIM_DEBUG_AUTORP)
					zlog_debug("%s: %pFX was duplicated in AutoRP announcement",
						   __func__, &lgrp->grp);
				XFREE(MTYPE_PIM_AUTORP_GRPPFIX, lgrp);
			}
		}
	}

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP processed announcement message", __func__);
	return true;
}

static void autorp_cand_rp_holdtime(struct event *evt)
{
	/* RP hold time expired, remove the RP */
	struct pim_autorp_rp *rp = EVENT_ARG(evt);

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP hold time expired, RP removed: addr=%pI4, grp=%pFX, grplist=%s",
			   __func__, &rp->addr, &rp->grp,
			   (strlen(rp->grplist) ? rp->grplist : "NONE"));

	pim_autorp_rp_del(&(rp->autorp->discovery_rp_list), rp);
	pim_autorp_rp_free(rp, true);
}

static bool pim_autorp_add_rp(struct pim_autorp *autorp, pim_addr rpaddr, struct prefix grp,
			      char *listname, uint16_t holdtime)
{
	struct pim_autorp_rp *rp;
	struct pim_autorp_rp *trp = NULL;
	int ret;

	ret = pim_rp_new(autorp->pim, rpaddr, grp, listname, RP_SRC_AUTORP);

	/* There may not be a path to the RP right now, but that doesn't mean it failed to add the RP */
	if (ret != PIM_SUCCESS && ret != PIM_RP_NO_PATH) {
		zlog_warn("%s: Failed to add active RP addr=%pI4, grp=%pFX, grplist=%s", __func__,
			  &rpaddr, &grp, (listname ? listname : "NONE"));
		return false;
	}

	rp = XCALLOC(MTYPE_PIM_AUTORP_RP, sizeof(*rp));
	rp->autorp = autorp;
	memcpy(&(rp->addr), &rpaddr, sizeof(pim_addr));
	trp = pim_autorp_rp_add(&(autorp->discovery_rp_list), rp);
	if (trp == NULL) {
		/* RP was brand new */
		trp = pim_autorp_rp_find(&(autorp->discovery_rp_list),
					 (const struct pim_autorp_rp *)rp);
		/* Make sure the timer is NULL so the cancel below doesn't mess up */
		trp->hold_timer = NULL;
		zlog_info("%s: Added new AutoRP learned RP addr=%pI4, grp=%pFX, grplist=%s",
			  __func__, &rpaddr, &grp, (listname ? listname : "NONE"));
	} else {
		/* RP already existed, free the temp one */
		XFREE(MTYPE_PIM_AUTORP_RP, rp);
	}

	/* Cancel any existing timer before restarting it */
	event_cancel(&trp->hold_timer);
	trp->holdtime = holdtime;
	prefix_copy(&(trp->grp), &grp);
	if (listname)
		snprintf(trp->grplist, sizeof(trp->grplist), "%s", listname);
	else
		trp->grplist[0] = '\0';

	if (holdtime > 0) {
		event_add_timer(router->master, autorp_cand_rp_holdtime, trp, holdtime,
				&(trp->hold_timer));
		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: Started %u second hold timer for RP %pI4", __func__,
				   holdtime, &trp->addr);
	}

	return true;
}

static size_t autorp_build_disc_rps(struct pim_autorp *autorp, uint8_t *buf, size_t buf_sz,
				    size_t *sz)
{
	/* Header has already been added, fill in starting with the address of RP1
	 *  buf_sz is the max size of the buf
	 *  sz is the current size of the packet, update as buf is filled
	 *  return the total number of RP's added
	 *
	 *
	 * We need to resolve the announced RP's following these rules:
	 *  1) Co-existence of longer and shorter group prefixes, from different RPs. E.g. when RP1
	 *     announces 224.2.*.*, and RP2 announces 224.2.2.*, both are accepted;
	 *  2) For announcements for identical group prefixes from two different RPs, the one from the
	 *     RP with the higher IP address is accepted;
	 *  3) No duplicates are sent to the AUTORP-DISCOVERY address. E.g. if an RP announces both
	 *     224.2.2.* and 224.2.*.*, the former group-prefix is not sent and only 224.2.*.* is sent
	 *     to the AUTORP-DISCOVERY address.
	 *
	 *
	 * The approach to resolution, first loop the stored RP's and extract the group prefixes, stored
	 * in a sorted list, sorted from least specific to most 0.0.0.0/0 -> 239.255.255.255/32. Each
	 * group prefix will then store the RP advertising that group prefix, this will resolve 2.
	 * The next step is to then loop the group prefix list and store them back into a list sorted by
	 * RP address, where the least specific group address will be stored, resolving 3. 1 is more
	 * about what is allowed, and in the example above the different prefixes will be unique in the
	 * list of group prefixes, and when they go back into RP's, they are also from different RP's
	 * and will therefore be sent.
	 */

	struct pim_autorp_rp *rp;
	struct pim_autorp_rp *trp;
	struct pim_autorp_grppfix *grp;
	struct pim_autorp_grppfix *grp2;
	struct pim_autorp_grppfix *tgrp;
	struct pim_autorp_grppfix_head grplist;
	bool skip = false;
	size_t rpcnt = 0;
	size_t bsz = 0;

	/* Initialize the lists, grplist is temporary, disc rp list is stored long term for
	 * show output, so make sure it's empty
	 */
	pim_autorp_grppfix_init(&grplist);
	pim_autorp_rplist_free(&autorp->advertised_rp_list, false);

	/* Loop the advertised RP's and their group prefixes and make a unique list of group prefixes,
	 * keeping just the highest IP RP for each group prefix
	 */
	frr_each (pim_autorp_rp, &autorp->mapping_rp_list, rp) {
		frr_each (pim_autorp_grppfix, &rp->grp_pfix_list, grp) {
			grp2 = XCALLOC(MTYPE_PIM_AUTORP_GRPPFIX, sizeof(struct pim_autorp_grppfix));
			prefix_copy(&grp2->grp, &grp->grp);
			grp2->negative = grp->negative;
			grp2->rp = rp->addr;
			tgrp = pim_autorp_grppfix_add(&grplist, grp2);
			if (tgrp != NULL) {
				/* Returned an existing entry. Use the highest RP addr and free allocated object */
				if (IPV4_ADDR_CMP(&tgrp->rp, &grp2->rp))
					tgrp->rp = grp2->rp;
				XFREE(MTYPE_PIM_AUTORP_GRPPFIX, grp2);
			}
		}
	}

	/* Now loop the unique group prefixes and put it back into an RP list */
	frr_each (pim_autorp_grppfix, &grplist, grp) {
		rp = XCALLOC(MTYPE_PIM_AUTORP_RP, sizeof(struct pim_autorp_rp));
		rp->addr = grp->rp;
		trp = pim_autorp_rp_add(&autorp->advertised_rp_list, rp);
		if (trp == NULL) {
			/* RP was brand new, finish initializing */
			rp->autorp = NULL;
			rp->holdtime = 0;
			rp->hold_timer = NULL;
			rp->grplist[0] = '\0';
			memset(&(rp->grp), 0, sizeof(rp->grp));
			pim_autorp_grppfix_init(&rp->grp_pfix_list);
		} else {
			/* Returned an existing entry, free allocated RP */
			XFREE(MTYPE_PIM_AUTORP_RP, rp);
			rp = trp;
		}

		/* Groups are in order from least specific to most, so go through the existing
		 * groups for this RP and see if the current group is within the prefix of one that
		 * is already in the list, if so, skip it, if not, add it
		 * If one is a positive match and the other is negative, then still include it.
		 */
		skip = false;
		frr_each (pim_autorp_grppfix, &rp->grp_pfix_list, grp2) {
			if (prefix_match(&grp2->grp, &grp->grp) && grp->negative == grp2->negative) {
				skip = true;
				break;
			}
		}

		if (skip)
			continue;

		/* add the group to the RP's group list */
		grp2 = XCALLOC(MTYPE_PIM_AUTORP_GRPPFIX, sizeof(struct pim_autorp_grppfix));
		prefix_copy(&grp2->grp, &grp->grp);
		grp2->negative = grp->negative;
		tgrp = pim_autorp_grppfix_add(&rp->grp_pfix_list, grp2);
		assert(tgrp == NULL);
	}

	/* Done with temporary group prefix list, so free and finish */
	pim_autorp_grppfix_free(&grplist);
	pim_autorp_grppfix_fini(&grplist);

	/* Now finally we can loop the disc rp list and build the packet */
	frr_each (pim_autorp_rp, &autorp->advertised_rp_list, rp) {
		struct autorp_pkt_rp *brp;
		struct autorp_pkt_grp *bgrp;
		size_t rp_sz;
		size_t grpcnt;

		grpcnt = pim_autorp_grppfix_count(&rp->grp_pfix_list);
		rp_sz = sizeof(struct autorp_pkt_rp) + (grpcnt * sizeof(struct autorp_pkt_grp));
		if (buf_sz < *sz + rp_sz) {
			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: Failed to pack AutoRP discovery packet, buffer overrun, (%u < %u)",
					   __func__, (uint32_t)buf_sz, (uint32_t)(*sz + rp_sz));
			break;
		}

		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: Add RP %pI4 (grpcnt=%u) to discovery message", __func__,
				   &rp->addr, (uint32_t)grpcnt);

		rpcnt++;

		brp = (struct autorp_pkt_rp *)(buf + bsz);
		bsz += sizeof(struct autorp_pkt_rp);

		/* Since this is an in_addr, assume it's already the right byte order */
		brp->addr = rp->addr.s_addr;
		brp->pimver = AUTORP_PIM_V2;
		brp->reserved = 0;
		brp->grpcnt = grpcnt;

		frr_each (pim_autorp_grppfix, &rp->grp_pfix_list, grp) {
			bgrp = (struct autorp_pkt_grp *)(buf + bsz);
			bsz += sizeof(struct autorp_pkt_grp);

			bgrp->addr = grp->grp.u.prefix4.s_addr;
			bgrp->masklen = grp->grp.prefixlen;
			bgrp->negprefix = grp->negative;

			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: Add group %s%pFX for RP %pI4 to discovery message",
					   __func__, (grp->negative ? "!" : ""), &grp->grp,
					   &rp->addr);
		}

		/* Update the size with this RP now that it is packed */
		*sz += bsz;
	}

	return rpcnt;
}

static size_t autorp_build_disc_packet(struct pim_autorp *autorp, uint8_t *buf, size_t buf_sz)
{
	size_t sz = 0;
	struct autorp_pkt_hdr *hdr;

	if (buf_sz >= AUTORP_HDRLEN) {
		hdr = (struct autorp_pkt_hdr *)buf;
		hdr->version = AUTORP_VERSION;
		hdr->type = AUTORP_DISCOVERY_TYPE;
		hdr->holdtime = htons(autorp->discovery_holdtime);
		hdr->reserved = 0;
		sz += AUTORP_HDRLEN;
		hdr->rpcnt = autorp_build_disc_rps(autorp, buf + sizeof(struct autorp_pkt_hdr),
						   (buf_sz - AUTORP_HDRLEN), &sz);
		if (hdr->rpcnt == 0)
			sz = 0;
	}
	return sz;
}

static void autorp_send_discovery(struct event *evt)
{
	struct pim_autorp *autorp = EVENT_ARG(evt);
	struct sockaddr_in discGrp;
	size_t disc_sz;
	size_t buf_sz = 65535;
	uint8_t buf[65535] = { 0 };

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP sending discovery info", __func__);

	/* Mark true, even if nothing is sent */
	autorp->mapping_agent_active = true;
	disc_sz = autorp_build_disc_packet(autorp, buf, buf_sz);

	if (disc_sz > 0) {
		discGrp.sin_family = AF_INET;
		discGrp.sin_port = htons(PIM_AUTORP_PORT);
		inet_pton(PIM_AF, PIM_AUTORP_DISCOVERY_GRP, &discGrp.sin_addr);

		if (setsockopt(autorp->sock, IPPROTO_IP, IP_MULTICAST_TTL,
			       &(autorp->discovery_scope), sizeof(autorp->discovery_scope)) == 0) {
			if (setsockopt(autorp->sock, IPPROTO_IP, IP_MULTICAST_IF,
				       &(autorp->mapping_agent_addrsel.run_addr),
				       sizeof(autorp->mapping_agent_addrsel.run_addr)) == 0) {
				if (sendto(autorp->sock, buf, disc_sz, 0,
					   (struct sockaddr *)&discGrp, sizeof(discGrp)) > 0) {
					if (PIM_DEBUG_AUTORP)
						zlog_debug("%s: AutoRP discovery message sent",
							   __func__);
				} else if (PIM_DEBUG_AUTORP)
					zlog_warn("%s: Failed to send AutoRP discovery message, errno=%d, %s",
						  __func__, errno, safe_strerror(errno));
			} else if (PIM_DEBUG_AUTORP)
				zlog_warn("%s: Failed to set Multicast Interface for sending AutoRP discovery message, errno=%d, %s",
					  __func__, errno, safe_strerror(errno));
		} else if (PIM_DEBUG_AUTORP)
			zlog_warn("%s: Failed to set Multicast TTL for sending AutoRP discovery message, errno=%d, %s",
				  __func__, errno, safe_strerror(errno));
	}

	/* Start the new timer for the entire send discovery interval */
	event_add_timer(router->master, autorp_send_discovery, autorp, autorp->discovery_interval,
			&(autorp->send_discovery_timer));
}

static bool pim_autorp_socket_enable(struct pim_autorp *autorp);
static bool pim_autorp_socket_disable(struct pim_autorp *autorp);

static void autorp_send_discovery_on(struct pim_autorp *autorp)
{
	int interval = 5;

	/* Make sure the socket is open and ready */
	if (pim_autorp_should_enable_socket(autorp)) {
		if (!pim_autorp_socket_enable(autorp)) {
			zlog_err("%s: AutoRP failed to open socket", __func__);
			return;
		}
	} else {
		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: No PIM interfaces, not enabling socket", __func__);
		return;
	}

	/* Send the first discovery shortly after being enabled.
	 * If the configured interval is less than 5 seconds, then just use that.
	 */
	if (interval > autorp->discovery_interval)
		interval = autorp->discovery_interval;

	if (autorp->send_discovery_timer)
		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: AutoRP discovery sending enabled in %u seconds", __func__,
				   interval);

	event_add_timer(router->master, autorp_send_discovery, autorp, interval,
			&(autorp->send_discovery_timer));
}

static void autorp_send_discovery_off(struct pim_autorp *autorp)
{
	if (autorp->send_discovery_timer)
		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: AutoRP discovery sending disabled", __func__);
	event_cancel(&(autorp->send_discovery_timer));

	/* Close the socket if we need to */
	if (pim_autorp_should_close(autorp) && !pim_autorp_socket_disable(autorp))
		zlog_warn("%s: AutoRP failed to close socket", __func__);
}

static bool autorp_recv_discovery(struct pim_autorp *autorp, uint8_t rpcnt, uint16_t holdtime,
				  char *buf, size_t buf_size, pim_addr src)
{
	int i, j;
	struct autorp_pkt_rp *rp;
	struct autorp_pkt_grp *grp;
	size_t offset = 0;
	pim_addr rp_addr;
	struct prefix grppfix = {};
	char plname[32];
	struct prefix_list *pl;
	struct prefix_list_entry *ple;
	int64_t seq = 1;
	bool success = true;

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: Received AutoRP discovery message (src=%pI4, rpcnt=%u, holdtime=%u)",
			   __func__, &src, rpcnt, holdtime);

	if (autorp->send_rp_discovery &&
	    (pim_addr_cmp(autorp->mapping_agent_addrsel.run_addr, src) < 0)) {
		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: AutoRP send discovery suppressed -- Discovery received with higher IP address",
				   __func__);

		/* Cancel the existing send timer and restart for 3X the send discovery interval */
		event_cancel(&(autorp->send_discovery_timer));
		event_add_timer(router->master, autorp_send_discovery, autorp,
				(autorp->discovery_interval * 3), &(autorp->send_discovery_timer));

		/* Clear the last sent discovery RP's, since it is no longer valid */
		pim_autorp_rplist_free(&autorp->advertised_rp_list, false);
		/* Unset flag indicating we are active */
		autorp->mapping_agent_active = false;
	}

	for (i = 0; i < rpcnt; ++i) {
		if ((buf_size - offset) < AUTORP_RPLEN) {
			zlog_warn("%s: Failed to parse AutoRP discovery message, invalid buffer size (%u < %u)",
				  __func__, (uint32_t)(buf_size - offset), AUTORP_RPLEN);
			return false;
		}

		rp = (struct autorp_pkt_rp *)(buf + offset);
		offset += AUTORP_RPLEN;

		rp_addr.s_addr = rp->addr;

		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: Parsing RP %pPA (grpcnt=%u)", __func__, &rp_addr,
				   rp->grpcnt);

		/* Ignore RP's limited to PIM version 1 or with an unknown version */
		if (rp->pimver == AUTORP_PIM_V1 || rp->pimver == AUTORP_PIM_VUNKNOWN) {
			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: Ignoring unsupported PIM version in AutoRP Discovery for RP %pI4",
					   __func__, &rp_addr);
			/* Update the offset to skip past the groups advertised for this RP */
			offset += (AUTORP_GRPLEN * rp->grpcnt);
			continue;
		}

		if (rp->grpcnt == 0) {
			/* No groups?? */
			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: Discovery message has no groups for RP %pPA",
					   __func__, &rp_addr);
			continue;
		}

		/* Make sure there is enough buffer to parse all the groups */
		if ((buf_size - offset) < (AUTORP_GRPLEN * rp->grpcnt)) {
			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: Buffer underrun parsing groups for RP %pPA (%u < %u)",
					   __func__, &rp_addr, (uint32_t)(buf_size - offset),
					   (uint32_t)(AUTORP_GRPLEN * rp->grpcnt));
			return false;
		}

		/* Get the first group so we can check for a negative prefix */
		/* Don't add to offset yet to make the multiple group loop easier */
		grp = (struct autorp_pkt_grp *)(buf + offset);

		if (rp->grpcnt == 1 && grp->negprefix == 0) {
			/* Only one group with positive prefix, we can use the standard RP API */
			offset += AUTORP_GRPLEN;
			grppfix.family = AF_INET;
			grppfix.prefixlen = grp->masklen;
			grppfix.u.prefix4.s_addr = grp->addr;

			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: Parsing group %s%pFX for RP %pI4", __func__,
					   (grp->negprefix ? "!" : ""), &grppfix, &rp_addr);

			if (!pim_autorp_add_rp(autorp, rp_addr, grppfix, NULL, holdtime))
				success = false;
		} else {
			/* More than one grp, or the only group is a negative prefix.
			 * Need to make a prefix list for this RP
			 */
			snprintfrr(plname, sizeof(plname), "__AUTORP_%pI4__", &rp_addr);
			pl = prefix_list_lookup(AFI_IP, plname);

			if (pl) {
				/* Existing prefix list found, delete it first */
				/* TODO: Instead of deleting completely, maybe we can just clear it and re-add entries */
				if (PIM_DEBUG_AUTORP)
					zlog_debug("%s: Found existing prefix list %s, replacing it",
						   __func__, plname);
				prefix_list_delete(pl);
			}

			/* Now get a new prefix list */
			pl = prefix_list_get(AFI_IP, 0, plname);

			for (j = 0; j < rp->grpcnt; ++j) {
				/* This will just set grp to the same pointer on the first loop, but offset will
				 * be updated correctly while parsing
				 */
				grp = (struct autorp_pkt_grp *)(buf + offset);
				offset += AUTORP_GRPLEN;

				ple = prefix_list_entry_new();
				ple->pl = pl;
				ple->seq = seq;
				seq += 5;
				memset(&ple->prefix, 0, sizeof(ple->prefix));
				prefix_list_entry_update_start(ple);
				ple->type = (grp->negprefix ? PREFIX_DENY : PREFIX_PERMIT);
				ple->prefix.family = AF_INET;
				ple->prefix.prefixlen = grp->masklen;
				ple->prefix.u.prefix4.s_addr = grp->addr;
				ple->any = false;
				ple->ge = 0;
				ple->le = 32;
				prefix_list_entry_update_finish(ple);

				if (PIM_DEBUG_AUTORP)
					zlog_debug("%s: Parsing group %s%pFX for RP %pPA", __func__,
						   (grp->negprefix ? "!" : ""), &ple->prefix,
						   &rp_addr);
			}

			if (!pim_autorp_add_rp(autorp, rp_addr, grppfix, plname, holdtime))
				success = false;
		}
	}

	return success;
}

static bool autorp_recv_msg(struct pim_autorp *autorp, char *buf, size_t buf_size, pim_addr src)
{
	struct autorp_pkt_hdr *h;

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: Received AutoRP message", __func__);

	if (buf_size < AUTORP_HDRLEN) {
		zlog_warn("%s: Invalid AutoRP Header size (%u < %u)", __func__, (uint32_t)buf_size,
			  AUTORP_HDRLEN);
		return false;
	}

	h = (struct autorp_pkt_hdr *)buf;

	if (h->version != AUTORP_VERSION) {
		zlog_warn("%s: Unsupported AutoRP version (%u != %u)", __func__, h->version,
			  AUTORP_VERSION);
		return false;
	}

	if (h->type == AUTORP_ANNOUNCEMENT_TYPE)
		return autorp_recv_announcement(autorp, h->rpcnt, htons(h->holdtime),
						buf + AUTORP_HDRLEN, buf_size - AUTORP_HDRLEN);

	if (h->type == AUTORP_DISCOVERY_TYPE)
		return autorp_recv_discovery(autorp, h->rpcnt, htons(h->holdtime),
					     buf + AUTORP_HDRLEN, buf_size - AUTORP_HDRLEN, src);

	zlog_warn("%s: Unknown AutoRP message type (%u)", __func__, h->type);

	return false;
}

static void autorp_read(struct event *t);

static void autorp_read_on(struct pim_autorp *autorp)
{
	event_add_read(router->master, autorp_read, autorp, autorp->sock, &(autorp->read_event));
	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP socket read enabled", __func__);
}

static void autorp_read_off(struct pim_autorp *autorp)
{
	event_cancel(&(autorp->read_event));
	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP socket read disabled", __func__);
}

static void autorp_read(struct event *evt)
{
	struct pim_autorp *autorp = evt->arg;
	int fd = evt->u.fd;
	char buf[10000];
	int rd;
	struct sockaddr_storage from;
	socklen_t fromlen = sizeof(from);
	pim_addr src;

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: Reading from AutoRP socket", __func__);

	while (1) {
		rd = pim_socket_recvfromto(fd, (uint8_t *)buf, sizeof(buf), &from, &fromlen, NULL,
					   NULL, NULL);
		if (rd <= 0) {
			if (errno == EINTR)
				continue;
			if (errno == EWOULDBLOCK || errno == EAGAIN)
				break;
			zlog_warn("%s: Failure reading rd=%d: fd=%d: errno=%d: %s", __func__, rd,
				  fd, errno, safe_strerror(errno));
			goto err;
		}

		if (from.ss_family == AF_INET)
			src.s_addr = ((struct sockaddr_in *)&from)->sin_addr.s_addr;
		else {
			zlog_warn("%s: AutoRP message is not IPV4", __func__);
			goto err;
		}

		if (!autorp_recv_msg(autorp, buf, rd, src))
			zlog_warn("%s: Failure parsing AutoRP message", __func__);
		/* Keep reading until would block */
	}

	/* No error, enable read again */
	autorp_read_on(autorp);

err:
	return;
}

static void pim_autorp_new_announcement(struct pim_instance *pim);
static bool pim_autorp_socket_enable(struct pim_autorp *autorp)
{
	int fd;
	struct interface *ifp;

	/* Return early if socket is already enabled */
	if (autorp->sock != -1)
		return true;

	frr_with_privs (&pimd_privs) {
		fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
		if (fd < 0) {
			zlog_warn("Could not create autorp socket: errno=%d: %s", errno,
				  safe_strerror(errno));
			return false;
		}

		if (vrf_bind(autorp->pim->vrf->vrf_id, fd, NULL)) {
			zlog_warn("Could not bind autorp socket to vrf fd=%d: vrf_id=%d: errno=%d: %s",
				  fd, autorp->pim->vrf->vrf_id, errno, safe_strerror(errno));
			close(fd);
			return false;
		}

		if (!pim_autorp_setup(fd)) {
			zlog_warn("Could not setup autorp socket fd=%d: errno=%d: %s", fd, errno,
				  safe_strerror(errno));
			close(fd);
			return false;
		}
	}

	autorp->sock = fd;

	/* Join autorp groups on all pim enabled interfaces in the VRF */
	FOR_ALL_INTERFACES (autorp->pim->vrf, ifp) {
		pim_autorp_add_ifp(ifp);
	}

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP socket enabled (fd=%u)", __func__, fd);

	if (autorp->do_discovery)
		autorp_read_on(autorp);

	if (autorp->send_rp_discovery)
		autorp_send_discovery_on(autorp);

	/* Try to build a new announcement to make sure the send timer is enabled */
	pim_autorp_new_announcement(autorp->pim);

	return true;
}

static void autorp_announcement_off(struct pim_autorp *autorp);
static bool pim_autorp_socket_disable(struct pim_autorp *autorp)
{
	/* Return early if socket is already disabled */
	if (autorp->sock == -1)
		return true;

	/* No need to leave the autorp groups explicitly, they are left when the socket is closed */
	if (close(autorp->sock)) {
		zlog_warn("Failure closing autorp socket: fd=%d errno=%d: %s", autorp->sock, errno,
			  safe_strerror(errno));
		return false;
	}

	autorp_send_discovery_off(autorp);
	autorp_announcement_off(autorp);
	autorp_read_off(autorp);
	autorp->sock = -1;

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP socket disabled", __func__);

	return true;
}

static void autorp_send_announcement(struct event *evt)
{
	struct pim_autorp *autorp = EVENT_ARG(evt);
	struct interface *ifp;
	struct pim_interface *pim_ifp;
	struct sockaddr_in announceGrp;

	announceGrp.sin_family = AF_INET;
	announceGrp.sin_port = htons(PIM_AUTORP_PORT);
	inet_pton(PIM_AF, PIM_AUTORP_ANNOUNCEMENT_GRP, &announceGrp.sin_addr);

	if (autorp->announce_pkt_sz >= MIN_AUTORP_PKT_SZ) {
		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: Sending AutoRP announcement", __func__);

		if (setsockopt(autorp->sock, IPPROTO_IP, IP_MULTICAST_TTL,
			       &(autorp->announce_scope), sizeof(autorp->announce_scope)) < 0) {
			zlog_warn("%s: Failed to set Multicast TTL for sending AutoRP announcement message, errno=%d, %s",
				  __func__, errno, safe_strerror(errno));
			return;
		}

		FOR_ALL_INTERFACES (autorp->pim->vrf, ifp) {
			pim_ifp = ifp->info;
			/* Only send on active interfaces with full pim enabled, non-passive
			 * and have a primary address set.
			 */
			if (autorp_is_pim_interface(ifp) &&
			    !pim_addr_is_any(pim_ifp->primary_address)) {
				if (setsockopt(autorp->sock, IPPROTO_IP, IP_MULTICAST_IF,
					       &(pim_ifp->primary_address),
					       sizeof(pim_ifp->primary_address)) < 0) {
					zlog_warn("%s: Failed to set Multicast Interface for sending AutoRP announcement message, errno=%d, %s",
						  __func__, errno, safe_strerror(errno));
					continue;
				}

				if (sendto(autorp->sock, autorp->announce_pkt,
					   autorp->announce_pkt_sz, 0,
					   (struct sockaddr *)&announceGrp,
					   sizeof(announceGrp)) <= 0)
					zlog_warn("%s: Failed to send AutoRP announcement message, errno=%d, %s",
						  __func__, errno, safe_strerror(errno));
			}
		}
	}

	/* Start the new timer for the entire announce interval */
	event_add_timer(router->master, autorp_send_announcement, autorp, autorp->announce_interval,
			&(autorp->announce_timer));
}

static void autorp_announcement_on(struct pim_autorp *autorp)
{
	int interval = 5;

	/* Send the first announcement shortly after being enabled.
	 * If the configured interval is less than 5 seconds, then just use that.
	 */
	if (interval > autorp->announce_interval)
		interval = autorp->announce_interval;

	if (autorp->announce_timer == NULL)
		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: AutoRP announcement sending enabled", __func__);

	event_add_timer(router->master, autorp_send_announcement, autorp, interval,
			&(autorp->announce_timer));
}

static void autorp_announcement_off(struct pim_autorp *autorp)
{
	if (autorp->announce_timer != NULL)
		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: AutoRP announcement sending disabled", __func__);
	event_cancel(&(autorp->announce_timer));

	/* Close the socket if we need to */
	if (pim_autorp_should_close(autorp) && !pim_autorp_socket_disable(autorp))
		zlog_warn("%s: AutoRP failed to close socket", __func__);
}

/* Pack the groups of the RP
 *   rp - Pointer to the RP
 *   buf - Pointer to the buffer where to start packing groups
 *   returns - Total group count packed
 */
static uint8_t pim_autorp_new_announcement_rp_grps(struct pim_autorp_rp *rp, uint8_t *buf)
{
	struct autorp_pkt_grp *grpp = (struct autorp_pkt_grp *)buf;
	uint8_t cnt = 0;

	if (is_default_prefix(&(rp->grp))) {
		/* No group so pack from the prefix list
		 * The grplist should be set and the prefix list exist with at least one group address
		 */
		struct prefix_list *plist;
		struct prefix_list_entry *ple;

		plist = prefix_list_lookup(AFI_IP, rp->grplist);
		for (ple = plist->head; ple; ple = ple->next) {
			if (pim_addr_is_multicast(ple->prefix.u.prefix4) &&
			    ple->prefix.prefixlen >= 4) {
				grpp->addr = ple->prefix.u.prefix4.s_addr;
				grpp->masklen = ple->prefix.prefixlen;
				grpp->negprefix = (ple->type == PREFIX_PERMIT ? 0 : 1);
				grpp->reserved = 0;

				++cnt;
				grpp = (struct autorp_pkt_grp *)(buf +
								 (sizeof(struct autorp_pkt_grp) *
								  cnt));
			}
		}

		return cnt;
	}

	/* Only one of group or prefix list should be defined */
	grpp->addr = rp->grp.u.prefix4.s_addr;
	grpp->masklen = rp->grp.prefixlen;
	grpp->negprefix = 0;
	grpp->reserved = 0;
	return 1;
}

/* Pack a single candidate RP
 *   rp - Pointer to the RP to pack
 *   buf - Pointer to the buffer where to start packing the RP
 *   returns - Buffer pointer pointing to the start of the next RP
 */
static uint8_t *pim_autorp_new_announcement_rp(struct pim_autorp_rp *rp, uint8_t *buf)
{
	struct autorp_pkt_rp *brp = (struct autorp_pkt_rp *)buf;

	/* Since this is an in_addr, assume it's already the right byte order */
	brp->addr = rp->addr.s_addr;
	brp->pimver = AUTORP_PIM_V2;
	brp->reserved = 0;
	brp->grpcnt = pim_autorp_new_announcement_rp_grps(rp, buf + sizeof(struct autorp_pkt_rp));
	return buf + sizeof(struct autorp_pkt_rp) + (brp->grpcnt * sizeof(struct autorp_pkt_grp));
}

/* Pack the candidate RP's on the announcement packet
 *   autorp - Pointer to the AutoRP instance
 *   buf - Pointer to the buffer where to start packing the first RP
 *   bufsz - Output parameter to track size of packed bytes
 *   returns - Total count of RP's packed
 */
static int pim_autorp_new_announcement_rps(struct pim_autorp *autorp, uint8_t *buf, uint16_t *bufsz)
{
	int cnt = 0;
	struct pim_autorp_rp *rp;
	/* Keep the original buffer pointer to calculate final size after packing */
	uint8_t *obuf = buf;

	frr_each_safe (pim_autorp_rp, &(autorp->candidate_rp_list), rp) {
		/* We must have an rp address and either group or list in order to pack this RP,
		 * so skip this one
		 */
		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: Evaluating AutoRP candidate %pI4, group range %pFX, group list %s",
				   __func__, &rp->addr, &rp->grp, rp->grplist);

		if (pim_addr_is_any(rp->addr) ||
		    (is_default_prefix(&rp->grp) && strlen(rp->grplist) == 0))
			continue;

		/* Make sure that either group prefix is set, or that the prefix list exists and has at
		 * least one valid multicast prefix in it. Only multicast prefixes will be used.
		 */
		if (is_default_prefix(&rp->grp)) {
			struct prefix_list *plist;
			struct prefix_list_entry *ple;

			plist = prefix_list_lookup(AFI_IP, rp->grplist);
			if (plist == NULL)
				continue;
			plist = prefix_list_lookup(AFI_IP, rp->grplist);
			for (ple = plist->head; ple; ple = ple->next) {
				if (pim_addr_is_multicast(ple->prefix.u.prefix4) &&
				    ple->prefix.prefixlen >= 4)
					break;
			}

			/* If we went through the entire list without finding a multicast prefix,
			 * then skip this RP
			 */
			if (ple == NULL)
				continue;
		}

		/* Now we know for sure we will pack this RP, so count it */
		++cnt;
		/* This will return the buffer pointer at the location to start packing the next RP */
		buf = pim_autorp_new_announcement_rp(rp, buf);

		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: AutoRP candidate %pI4 added to announcement", __func__,
				   &rp->addr);
	}

	if (cnt > 0)
		*bufsz = buf - obuf;

	return cnt;
}

/* Build the new announcement packet. If there is a packet to send, restart the send timer
 * with a short wait
 */
static void pim_autorp_new_announcement(struct pim_instance *pim)
{
	struct pim_autorp *autorp = pim->autorp;
	struct autorp_pkt_hdr *hdr;
	int32_t holdtime;

	/* First disable any existing send timer */
	autorp_announcement_off(autorp);

	/*
	 * First time building, allocate the space
	 * Allocate the max packet size of 65536 so we don't need to resize later.
	 * This should be ok since we are only allocating the memory once for a single packet
	 * (potentially per vrf)
	 */
	if (!autorp->announce_pkt)
		autorp->announce_pkt = XCALLOC(MTYPE_PIM_AUTORP_ANNOUNCE, 65536);

	autorp->announce_pkt_sz = 0;

	holdtime = autorp->announce_holdtime;
	if (holdtime == DEFAULT_AUTORP_ANNOUNCE_HOLDTIME)
		holdtime = autorp->announce_interval * 3;
	if (holdtime > UINT16_MAX)
		holdtime = UINT16_MAX;

	hdr = (struct autorp_pkt_hdr *)autorp->announce_pkt;
	hdr->version = AUTORP_VERSION;
	hdr->type = AUTORP_ANNOUNCEMENT_TYPE;
	hdr->holdtime = htons((uint16_t)holdtime);
	hdr->reserved = 0;
	hdr->rpcnt = pim_autorp_new_announcement_rps(autorp,
						     autorp->announce_pkt +
							     sizeof(struct autorp_pkt_hdr),
						     &(autorp->announce_pkt_sz));

	/* Still need to add on the size of the header */
	autorp->announce_pkt_sz += sizeof(struct autorp_pkt_hdr);

	/* Only turn on the announcement timer if we have a packet to send */
	if (autorp->announce_pkt_sz >= MIN_AUTORP_PKT_SZ) {
		/* We are sending an announcement, but discovery could be off, so make sure the socket is open */
		if (pim_autorp_should_enable_socket(autorp)) {
			if (!pim_autorp_socket_enable(autorp)) {
				zlog_err("%s: AutoRP failed to open socket", __func__);
				return;
			}
		} else {
			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: No PIM interfaces, not enabling socket", __func__);
			return;
		}
		autorp_announcement_on(autorp);
	}
}

void pim_autorp_prefix_list_update(struct pim_instance *pim, struct prefix_list *plist)
{
	struct pim_autorp_rp *rp = NULL;
	struct pim_autorp *autorp = NULL;

	autorp = pim->autorp;
	if (autorp == NULL)
		return;

	/* Search for a candidate RP using this prefix list */
	frr_each_safe (pim_autorp_rp, &(autorp->candidate_rp_list), rp) {
		if (strmatch(rp->grplist, plist->name))
			break;
	}

	/* If we broke out of the loop early because we found a match, then rebuild the announcement */
	if (rp != NULL)
		pim_autorp_new_announcement(pim);
}

bool pim_autorp_rm_candidate_rp(struct pim_instance *pim, pim_addr rpaddr)
{
	struct pim_autorp *autorp = pim->autorp;
	struct pim_autorp_rp *rp;
	struct pim_autorp_rp find = { .addr = rpaddr };

	rp = pim_autorp_rp_find(&(autorp->candidate_rp_list), (const struct pim_autorp_rp *)&find);
	if (!rp)
		return false;

	pim_autorp_rp_del(&(autorp->candidate_rp_list), rp);
	pim_autorp_rp_free(rp, false);
	pim_autorp_new_announcement(pim);
	return true;
}

void pim_autorp_add_candidate_rp_group(struct pim_instance *pim, pim_addr rpaddr,
				       struct prefix group)
{
	struct pim_autorp *autorp = pim->autorp;
	struct pim_autorp_rp *rp;
	struct pim_autorp_rp find = { .addr = rpaddr };

	rp = pim_autorp_rp_find(&(autorp->candidate_rp_list), (const struct pim_autorp_rp *)&find);
	if (!rp) {
		rp = XCALLOC(MTYPE_PIM_AUTORP_RP, sizeof(*rp));
		memset(rp, 0, sizeof(struct pim_autorp_rp));
		rp->autorp = autorp;
		memcpy(&(rp->addr), &rpaddr, sizeof(pim_addr));
		pim_autorp_rp_add(&(autorp->candidate_rp_list), rp);
	}

	apply_mask(&group);
	prefix_copy(&(rp->grp), &group);
	/* A new group prefix implies that any previous prefix list is now invalid */
	rp->grplist[0] = '\0';

	pim_autorp_new_announcement(pim);
}

bool pim_autorp_rm_candidate_rp_group(struct pim_instance *pim, pim_addr rpaddr, struct prefix group)
{
	struct pim_autorp *autorp = pim->autorp;
	struct pim_autorp_rp *rp;
	struct pim_autorp_rp find = { .addr = rpaddr };

	rp = pim_autorp_rp_find(&(autorp->candidate_rp_list), (const struct pim_autorp_rp *)&find);
	if (!rp)
		return false;

	memset(&(rp->grp), 0, sizeof(rp->grp));
	pim_autorp_new_announcement(pim);
	return true;
}

void pim_autorp_add_candidate_rp_plist(struct pim_instance *pim, pim_addr rpaddr, const char *plist)
{
	struct pim_autorp *autorp = pim->autorp;
	struct pim_autorp_rp *rp;
	struct pim_autorp_rp find = { .addr = rpaddr };

	rp = pim_autorp_rp_find(&(autorp->candidate_rp_list), (const struct pim_autorp_rp *)&find);
	if (!rp) {
		rp = XCALLOC(MTYPE_PIM_AUTORP_RP, sizeof(*rp));
		memset(rp, 0, sizeof(struct pim_autorp_rp));
		rp->autorp = autorp;
		memcpy(&(rp->addr), &rpaddr, sizeof(pim_addr));
		pim_autorp_rp_add(&(autorp->candidate_rp_list), rp);
	}

	snprintf(rp->grplist, sizeof(rp->grplist), "%s", plist);
	/* A new group prefix list implies that any previous group prefix is now invalid */
	memset(&(rp->grp), 0, sizeof(rp->grp));
	rp->grp.family = AF_INET;

	pim_autorp_new_announcement(pim);
}

bool pim_autorp_rm_candidate_rp_plist(struct pim_instance *pim, pim_addr rpaddr, const char *plist)
{
	struct pim_autorp *autorp = pim->autorp;
	struct pim_autorp_rp *rp;
	struct pim_autorp_rp find = { .addr = rpaddr };

	rp = pim_autorp_rp_find(&(autorp->candidate_rp_list), (const struct pim_autorp_rp *)&find);
	if (!rp)
		return false;

	rp->grplist[0] = '\0';
	pim_autorp_new_announcement(pim);
	return true;
}

void pim_autorp_announce_scope(struct pim_instance *pim, uint8_t scope)
{
	struct pim_autorp *autorp = pim->autorp;

	scope = (scope == 0 ? DEFAULT_AUTORP_ANNOUNCE_SCOPE : scope);
	if (autorp->announce_scope != scope) {
		autorp->announce_scope = scope;
		pim_autorp_new_announcement(pim);
	}
}

void pim_autorp_announce_interval(struct pim_instance *pim, uint16_t interval)
{
	struct pim_autorp *autorp = pim->autorp;

	interval = (interval == 0 ? DEFAULT_AUTORP_ANNOUNCE_INTERVAL : interval);
	if (autorp->announce_interval != interval) {
		autorp->announce_interval = interval;
		pim_autorp_new_announcement(pim);
	}
}

void pim_autorp_announce_holdtime(struct pim_instance *pim, int32_t holdtime)
{
	struct pim_autorp *autorp = pim->autorp;

	if (autorp->announce_holdtime != holdtime) {
		autorp->announce_holdtime = holdtime;
		pim_autorp_new_announcement(pim);
	}
}

void pim_autorp_send_discovery_apply(struct pim_autorp *autorp)
{
	if (!autorp->mapping_agent_addrsel.run || !autorp->send_rp_discovery) {
		autorp_send_discovery_off(autorp);
		return;
	}

	autorp_send_discovery_on(autorp);
}

void pim_autorp_add_ifp(struct interface *ifp)
{
	/* Add a new interface for autorp
	 *   When autorp is enabled, we must join the autorp groups on all
	 *   pim/multicast interfaces. When autorp becomes enabled, it finds all
	 *   current pim enabled interfaces and joins the autorp groups on them.
	 *   Any new interfaces added after autorp is enabled will use this function
	 *   to join the autorp groups
	 * This is called even when adding a new pim interface that is not yet
	 * active, so make sure the check, it'll call in again once the interface is up.
	 */
	struct pim_instance *pim;
	struct pim_interface *pim_ifp;

	pim_ifp = ifp->info;
	if (autorp_is_pim_interface(ifp)) {
		pim = pim_ifp->pim;
		if (pim && pim->autorp && !pim_autorp_should_close(pim->autorp)) {
			/* Make sure the socket is open and ready */
			if (!pim_autorp_socket_enable(pim->autorp)) {
				zlog_err("%s: AutoRP failed to open socket", __func__);
				return;
			}

			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: Adding interface %s to AutoRP, joining AutoRP groups",
					   __func__, ifp->name);
			if (!pim_autorp_join_groups(ifp))
				zlog_warn("Could not join AutoRP groups, errno=%d, %s", errno,
					  safe_strerror(errno));
		}
	}
}

void pim_autorp_rm_ifp(struct interface *ifp)
{
	/* Remove interface for autorp
	 *   When an interface is no longer enabled for multicast, or at all, then
	 *   we should leave the AutoRP groups on this interface.
	 */
	struct pim_instance *pim;
	struct pim_interface *pim_ifp;
	struct pim_autorp *autorp = NULL;

	pim_ifp = ifp->info;
	if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE) && pim_ifp) {
		pim = pim_ifp->pim;
		if (pim && pim->autorp) {
			autorp = pim->autorp;
			if (PIM_DEBUG_AUTORP)
				zlog_debug("%s: Removing interface %s from AutoRP, leaving AutoRP groups",
					   __func__, ifp->name);
			if (!pim_autorp_leave_groups(ifp))
				zlog_warn("Could not leave AutoRP groups, errno=%d, %s", errno,
					  safe_strerror(errno));
		}
	}

	if (autorp != NULL && !pim_autorp_should_enable_socket(autorp)) {
		/* Removed the last pim enabled interface, close the socket */
		pim_autorp_socket_disable(autorp);
	}
}

void pim_autorp_start_discovery(struct pim_instance *pim)
{
	struct pim_autorp *autorp = pim->autorp;

	if (autorp->do_discovery)
		return;

	autorp->do_discovery = true;

	/* Make sure the socket is open and ready */
	if (pim_autorp_should_enable_socket(autorp)) {
		if (!pim_autorp_socket_enable(autorp)) {
			zlog_err("%s: AutoRP failed to open socket", __func__);
			return;
		}
	} else {
		if (PIM_DEBUG_AUTORP)
			zlog_debug("%s: No PIM interfaces, not enabling socket", __func__);
		return;
	}

	autorp_read_on(autorp);

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP Discovery started", __func__);
}

void pim_autorp_stop_discovery(struct pim_instance *pim)
{
	struct pim_autorp *autorp = pim->autorp;

	if (!autorp->do_discovery)
		return;

	autorp->do_discovery = false;
	autorp_read_off(autorp);

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP Discovery stopped", __func__);

	/* Close the socket if we need to */
	if (pim_autorp_should_close(autorp) && !pim_autorp_socket_disable(autorp))
		zlog_warn("%s: AutoRP failed to close socket", __func__);
}

void pim_autorp_init(struct pim_instance *pim)
{
	struct pim_autorp *autorp;

	autorp = XCALLOC(MTYPE_PIM_AUTORP, sizeof(*autorp));
	autorp->pim = pim;
	autorp->sock = -1;
	autorp->read_event = NULL;
	autorp->announce_timer = NULL;
	autorp->do_discovery = false;
	autorp->send_discovery_timer = NULL;
	autorp->send_rp_discovery = false;
	pim_autorp_rp_init(&(autorp->discovery_rp_list));
	pim_autorp_rp_init(&(autorp->candidate_rp_list));
	pim_autorp_rp_init(&(autorp->mapping_rp_list));
	pim_autorp_rp_init(&autorp->advertised_rp_list);
	autorp->announce_scope = DEFAULT_AUTORP_ANNOUNCE_SCOPE;
	autorp->announce_interval = DEFAULT_AUTORP_ANNOUNCE_INTERVAL;
	autorp->announce_holdtime = DEFAULT_AUTORP_ANNOUNCE_HOLDTIME;
	autorp->discovery_scope = DEFAULT_AUTORP_DISCOVERY_SCOPE;
	autorp->discovery_interval = DEFAULT_AUTORP_DISCOVERY_INTERVAL;
	autorp->discovery_holdtime = DEFAULT_AUTORP_DISCOVERY_HOLDTIME;
	cand_addrsel_clear(&(autorp->mapping_agent_addrsel));

	pim->autorp = autorp;

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP Initialized", __func__);
}

void pim_autorp_enable(struct pim_instance *pim)
{
	/* Start AutoRP discovery by default on startup */
	pim_autorp_start_discovery(pim);
}

void pim_autorp_finish(struct pim_instance *pim)
{
	struct pim_autorp *autorp = pim->autorp;

	autorp_read_off(autorp);
	autorp_announcement_off(autorp);
	autorp_send_discovery_off(autorp);
	pim_autorp_free(autorp);
	pim_autorp_socket_disable(autorp);
	XFREE(MTYPE_PIM_AUTORP, pim->autorp);

	if (PIM_DEBUG_AUTORP)
		zlog_debug("%s: AutoRP Finished", __func__);
}

int pim_autorp_config_write(struct pim_instance *pim, struct vty *vty)
{
	struct pim_autorp_rp *rp;
	struct pim_autorp *autorp = pim->autorp;
	int writes = 0;

	if (!autorp->do_discovery) {
		vty_out(vty, " no autorp discovery\n");
		++writes;
	}

	if (autorp->announce_interval != DEFAULT_AUTORP_ANNOUNCE_INTERVAL ||
	    autorp->announce_scope != DEFAULT_AUTORP_ANNOUNCE_SCOPE ||
	    autorp->announce_holdtime != DEFAULT_AUTORP_ANNOUNCE_HOLDTIME) {
		vty_out(vty, " autorp announce");
		if (autorp->announce_interval != DEFAULT_AUTORP_ANNOUNCE_INTERVAL)
			vty_out(vty, " interval %u", autorp->announce_interval);
		if (autorp->announce_scope != DEFAULT_AUTORP_ANNOUNCE_SCOPE)
			vty_out(vty, " scope %u", autorp->announce_scope);
		if (autorp->announce_holdtime != DEFAULT_AUTORP_ANNOUNCE_HOLDTIME)
			vty_out(vty, " holdtime %u", autorp->announce_holdtime);
		vty_out(vty, "\n");
		++writes;
	}

	frr_each_safe (pim_autorp_rp, &(autorp->candidate_rp_list), rp) {
		/* Only print candidate RP's that have all the information needed to be announced */
		if (pim_addr_is_any(rp->addr) ||
		    (is_default_prefix(&(rp->grp)) && strlen(rp->grplist) == 0))
			continue;

		vty_out(vty, " autorp announce %pI4", &(rp->addr));
		if (!is_default_prefix(&(rp->grp)))
			vty_out(vty, " %pFX", &(rp->grp));
		else
			vty_out(vty, " group-list %s", rp->grplist);
		vty_out(vty, "\n");
		++writes;
	}

	if (autorp->send_rp_discovery) {
		if (autorp->mapping_agent_addrsel.cfg_enable) {
			vty_out(vty, " autorp send-rp-discovery");
			switch (autorp->mapping_agent_addrsel.cfg_mode) {
			case CAND_ADDR_LO:
				break;
			case CAND_ADDR_ANY:
				vty_out(vty, " source any");
				break;
			case CAND_ADDR_IFACE:
				vty_out(vty, " source interface %s",
					autorp->mapping_agent_addrsel.cfg_ifname);
				break;
			case CAND_ADDR_EXPLICIT:
				vty_out(vty, " source address %pPA",
					&autorp->mapping_agent_addrsel.cfg_addr);
				break;
			}
			vty_out(vty, "\n");
			++writes;
		}

		if (autorp->discovery_interval != DEFAULT_AUTORP_DISCOVERY_INTERVAL ||
		    autorp->discovery_scope != DEFAULT_AUTORP_DISCOVERY_SCOPE ||
		    autorp->discovery_holdtime != DEFAULT_AUTORP_DISCOVERY_HOLDTIME) {
			vty_out(vty, " autorp send-rp-discovery");
			if (autorp->discovery_interval != DEFAULT_AUTORP_DISCOVERY_INTERVAL)
				vty_out(vty, " interval %u", autorp->discovery_interval);
			if (autorp->discovery_scope != DEFAULT_AUTORP_DISCOVERY_SCOPE)
				vty_out(vty, " scope %u", autorp->discovery_scope);
			if (autorp->discovery_holdtime != DEFAULT_AUTORP_DISCOVERY_HOLDTIME)
				vty_out(vty, " holdtime %u", autorp->discovery_holdtime);
			vty_out(vty, "\n");
			++writes;
		}
	}

	return writes;
}

static void pim_autorp_show_autorp_json(struct pim_autorp *autorp, const char *component,
					json_object *json, struct ttable *cand_table)
{
	struct pim_autorp_rp *rp;

	if (!component || strmatch(component, "discovery")) {
		json_object *disc_obj;

		disc_obj = json_object_new_object();
		json_object_boolean_add(disc_obj, "enabled", autorp->do_discovery);
		if (autorp->do_discovery) {
			json_object *rplist_obj;

			rplist_obj = json_object_new_object();
			frr_each (pim_autorp_rp, &(autorp->discovery_rp_list), rp) {
				json_object *rp_obj;
				json_object *grp_arr;

				rp_obj = json_object_new_object();
				json_object_string_addf(rp_obj, "rpAddress", "%pI4", &rp->addr);
				json_object_int_add(rp_obj, "holdtime", rp->holdtime);
				grp_arr = json_object_new_array();

				if (strlen(rp->grplist)) {
					struct prefix_list *pl;
					struct prefix_list_entry *ple;

					pl = prefix_list_lookup(AFI_IP, rp->grplist);
					if (pl == NULL)
						continue;

					for (ple = pl->head; ple != NULL; ple = ple->next) {
						json_object *grp_obj;

						grp_obj = json_object_new_object();
						json_object_boolean_add(grp_obj, "negative",
									ple->type == PREFIX_DENY);
						json_object_string_addf(grp_obj, "prefix", "%pFX",
									&ple->prefix);
						json_object_array_add(grp_arr, grp_obj);
					}
				} else {
					json_object *grp_obj;

					grp_obj = json_object_new_object();
					json_object_boolean_add(grp_obj, "negative", false);
					json_object_string_addf(grp_obj, "prefix", "%pFX", &rp->grp);
					json_object_array_add(grp_arr, grp_obj);
				}

				json_object_object_add(rp_obj, "groupRanges", grp_arr);
				json_object_object_addf(rplist_obj, rp_obj, "%pI4", &rp->addr);
			}
			json_object_object_add(disc_obj, "rpList", rplist_obj);
		}
		json_object_object_add(json, "discovery", disc_obj);
	}

	if (!component || strmatch(component, "candidate")) {
		json_object *announce_obj;

		announce_obj = json_object_new_object();
		json_object_boolean_add(announce_obj, "enabled",
					pim_autorp_rp_count(&autorp->candidate_rp_list) > 0);
		if (pim_autorp_rp_count(&autorp->candidate_rp_list) > 0) {
			json_object_int_add(announce_obj, "scope", autorp->announce_scope);
			json_object_int_add(announce_obj, "interval", autorp->announce_interval);
			json_object_int_add(announce_obj, "holdtime",
					    (autorp->announce_holdtime ==
							     DEFAULT_AUTORP_ANNOUNCE_HOLDTIME
						     ? (autorp->announce_interval * 3)
						     : autorp->announce_holdtime));
			json_object_object_add(announce_obj, "rpList",
					       ttable_json_with_json_text(cand_table, "sss",
									  "rpAddress|groupRange|prefixList"));
		}
		json_object_object_add(json, "announce", announce_obj);
	}

	if (!component || strmatch(component, "mapping-agent")) {
		json_object *adv_obj;

		adv_obj = json_object_new_object();
		json_object_boolean_add(adv_obj, "enabled", autorp->send_rp_discovery);
		if (autorp->send_rp_discovery) {
			json_object *rplist_obj;

			json_object_boolean_add(adv_obj, "active", autorp->mapping_agent_active);
			json_object_int_add(adv_obj, "scope", autorp->discovery_scope);
			json_object_int_add(adv_obj, "interval", autorp->discovery_interval);
			json_object_int_add(adv_obj, "holdtime", autorp->discovery_holdtime);
			switch (autorp->mapping_agent_addrsel.cfg_mode) {
			case CAND_ADDR_LO:
				json_object_string_add(adv_obj, "source", "loopback");
				break;
			case CAND_ADDR_ANY:
				json_object_string_add(adv_obj, "source", "any");
				break;
			case CAND_ADDR_IFACE:
				json_object_string_add(adv_obj, "source", "interface");
				json_object_string_add(adv_obj, "interface",
						       autorp->mapping_agent_addrsel.cfg_ifname);
				break;
			case CAND_ADDR_EXPLICIT:
				json_object_string_add(adv_obj, "source", "address");
				break;
			}
			json_object_string_addf(adv_obj, "address", "%pPA",
						&autorp->mapping_agent_addrsel.run_addr);

			rplist_obj = json_object_new_object();
			frr_each (pim_autorp_rp, &(autorp->advertised_rp_list), rp) {
				json_object *rp_obj;
				json_object *grp_arr;
				struct pim_autorp_grppfix *grppfix;

				rp_obj = json_object_new_object();
				json_object_string_addf(rp_obj, "rpAddress", "%pI4", &rp->addr);
				grp_arr = json_object_new_array();
				frr_each (pim_autorp_grppfix, &rp->grp_pfix_list, grppfix) {
					json_object *grp_obj;

					grp_obj = json_object_new_object();
					json_object_boolean_add(grp_obj, "negative",
								grppfix->negative);
					json_object_string_addf(grp_obj, "prefix", "%pFX",
								&grppfix->grp);
					json_object_array_add(grp_arr, grp_obj);
				}
				json_object_object_add(rp_obj, "groupRanges", grp_arr);
				json_object_object_addf(rplist_obj, rp_obj, "%pI4", &rp->addr);
			}
			json_object_object_add(adv_obj, "rpList", rplist_obj);
		}
		json_object_object_add(json, "mapping-agent", adv_obj);
	}
}

void pim_autorp_show_autorp(struct vty *vty, struct pim_instance *pim, const char *component,
			    json_object *json)
{
	struct pim_autorp *autorp = pim->autorp;
	struct pim_autorp_rp *rp;
	struct ttable *cand_table = NULL;
	struct ttable *adv_table = NULL;
	struct ttable *disc_table = NULL;
	char *tmp;

	if (autorp == NULL)
		return;

	/* We may use the candidate table in the json output, so prepare it first. */
	if (!component || strmatch(component, "candidate")) {
		cand_table = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
		ttable_add_row(cand_table, "RP address|Group Range|Prefix-List");
		cand_table->style.cell.rpad = 2;
		cand_table->style.corner = '+';
		ttable_restyle(cand_table);

		frr_each (pim_autorp_rp, &(autorp->candidate_rp_list), rp) {
			if (strlen(rp->grplist))
				ttable_add_row(cand_table, "%pI4|%s|%s", &(rp->addr), "-",
					       rp->grplist);
			else
				ttable_add_row(cand_table, "%pI4|%pFX|%s", &(rp->addr), &(rp->grp),
					       "-");
		}
	}

	if (json) {
		pim_autorp_show_autorp_json(autorp, component, json, cand_table);
		if (cand_table)
			ttable_del(cand_table);
		return;
	}

	/* Prepare discovered RP's table. */
	if (!component || strmatch(component, "discovery")) {
		disc_table = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
		ttable_add_row(disc_table, "RP address|Group Range");
		disc_table->style.cell.rpad = 2;
		disc_table->style.corner = '+';
		ttable_restyle(disc_table);

		frr_each (pim_autorp_rp, &(autorp->discovery_rp_list), rp) {
			if (strlen(rp->grplist)) {
				struct prefix_list *pl;
				struct prefix_list_entry *ple;
				bool first = true;

				pl = prefix_list_lookup(AFI_IP, rp->grplist);

				if (pl == NULL) {
					ttable_add_row(disc_table,
						       "%pI4|failed to find prefix list %s",
						       &(rp->addr), rp->grplist);
					continue;
				}

				for (ple = pl->head; ple != NULL; ple = ple->next) {
					if (first)
						ttable_add_row(disc_table, "%pI4|%s%pFX",
							       &(rp->addr),
							       (ple->type == PREFIX_DENY ? "!"
											 : " "),
							       &ple->prefix);
					else
						ttable_add_row(disc_table, "%s|%s%pFX", " ",
							       (ple->type == PREFIX_DENY ? "!"
											 : " "),
							       &ple->prefix);
					first = false;
				}
			} else
				ttable_add_row(disc_table, "%pI4| %pFX", &(rp->addr), &(rp->grp));
		}
	}

	/* Prepare discovery RP's table (mapping-agent). */
	if (!component || strmatch(component, "mapping-agent")) {
		adv_table = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
		ttable_add_row(adv_table, "RP address|Group Range");
		adv_table->style.cell.rpad = 2;
		adv_table->style.corner = '+';
		ttable_restyle(adv_table);

		frr_each (pim_autorp_rp, &(autorp->advertised_rp_list), rp) {
			struct pim_autorp_grppfix *grppfix;
			bool first = true;

			frr_each (pim_autorp_grppfix, &rp->grp_pfix_list, grppfix) {
				if (first)
					ttable_add_row(adv_table, "%pI4|%s%pFX", &rp->addr,
						       grppfix->negative ? "!" : " ", &grppfix->grp);
				else
					ttable_add_row(adv_table, "%s|%s%pFX", " ",
						       grppfix->negative ? "!" : " ", &grppfix->grp);
				first = false;
			}
		}
	}

	if (!component || strmatch(component, "discovery")) {
		vty_out(vty, "AutoRP Discovery is %sabled\n", (autorp->do_discovery ? "en" : "dis"));
		if (autorp->do_discovery) {
			tmp = ttable_dump(disc_table, "\n");
			vty_out(vty, "\n");
			vty_out(vty, "Discovered RP's (count=%u)\n",
				(uint32_t)pim_autorp_rp_count(&autorp->discovery_rp_list));
			vty_out(vty, "%s\n", tmp);
			XFREE(MTYPE_TMP_TTABLE, tmp);
		} else
			vty_out(vty, "\n");
	}

	if (!component || strmatch(component, "candidate")) {
		vty_out(vty, "AutoRP Announcement is %sabled\n",
			(pim_autorp_rp_count(&autorp->candidate_rp_list) > 0 ? "en" : "dis"));
		if (pim_autorp_rp_count(&autorp->candidate_rp_list) > 0) {
			tmp = ttable_dump(cand_table, "\n");
			vty_out(vty, "  interval %us scope %u holdtime %us\n",
				autorp->announce_interval, autorp->announce_scope,
				(autorp->announce_holdtime == DEFAULT_AUTORP_ANNOUNCE_HOLDTIME
					 ? (autorp->announce_interval * 3)
					 : autorp->announce_holdtime));
			vty_out(vty, "\n");
			vty_out(vty, "Candidate RP's (count=%u)\n",
				(uint32_t)pim_autorp_rp_count(&autorp->candidate_rp_list));
			vty_out(vty, "%s\n", tmp);
			XFREE(MTYPE_TMP_TTABLE, tmp);
		} else
			vty_out(vty, "\n");
	}

	if (!component || strmatch(component, "mapping-agent")) {
		vty_out(vty, "AutoRP Mapping-Agent is %sabled\n",
			(autorp->send_rp_discovery ? "en" : "dis"));
		if (autorp->send_rp_discovery) {
			vty_out(vty, "  interval %us scope %u holdtime %us\n",
				autorp->discovery_interval, autorp->discovery_scope,
				autorp->discovery_holdtime);
			vty_out(vty, "  source %pPA", &autorp->mapping_agent_addrsel.run_addr);
			switch (autorp->mapping_agent_addrsel.cfg_mode) {
			case CAND_ADDR_LO:
				vty_out(vty, " (loopback)");
				break;
			case CAND_ADDR_ANY:
				vty_out(vty, " (any)");
				break;
			case CAND_ADDR_IFACE:
				vty_out(vty, " (interface %s)",
					autorp->mapping_agent_addrsel.cfg_ifname);
				break;
			case CAND_ADDR_EXPLICIT:
				vty_out(vty, " (explicit address)");
				break;
			}
			vty_out(vty, "\n");

			if (autorp->mapping_agent_active) {
				tmp = ttable_dump(adv_table, "\n");
				vty_out(vty, "\n");
				vty_out(vty, "Advertised RP's (count=%u)\n",
					(uint32_t)pim_autorp_rp_count(&autorp->advertised_rp_list));
				vty_out(vty, "%s\n", tmp);
				XFREE(MTYPE_TMP_TTABLE, tmp);
			} else
				vty_out(vty, "  Mapping agent is inactive\n");
		} else
			vty_out(vty, "\n");
	}

	if (cand_table)
		ttable_del(cand_table);
	if (adv_table)
		ttable_del(adv_table);
	if (disc_table)
		ttable_del(disc_table);
}
