// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * PIM for Quagga
 * Copyright (C) 2008  Everton da Silva Marques
 */

#include <zebra.h>

#include "if.h"
#include "log.h"
#include "vty.h"
#include "memory.h"
#include "prefix.h"
#include "vrf.h"
#include "linklist.h"
#include "plist.h"
#include "hash.h"
#include "ferr.h"
#include "network.h"

#include "pimd.h"
#include "pim_instance.h"
#include "pim_zebra.h"
#include "pim_iface.h"
#include "pim_igmp.h"
#include "pim_mroute.h"
#include "pim_oil.h"
#include "pim_str.h"
#include "pim_pim.h"
#include "pim_neighbor.h"
#include "pim_ifchannel.h"
#include "pim_sock.h"
#include "pim_time.h"
#include "pim_ssmpingd.h"
#include "pim_rp.h"
#include "pim_nht.h"
#include "pim_jp_agg.h"
#include "pim_igmp_join.h"
#include "pim_vxlan.h"
#include "pim_tib.h"

#include "pim6_mld.h"

static void pim_if_gm_join_del_all(struct interface *ifp);
static void pim_if_static_group_del_all(struct interface *ifp);

static int gm_join_sock(const char *ifname, ifindex_t ifindex,
			pim_addr group_addr, pim_addr source_addr,
			struct pim_interface *pim_ifp);

void pim_if_init(struct pim_instance *pim)
{
	int i;

	for (i = 0; i < MAXVIFS; i++)
		pim->iface_vif_index[i] = 0;
}

void pim_if_terminate(struct pim_instance *pim)
{
	struct interface *ifp;

	FOR_ALL_INTERFACES (pim->vrf, ifp) {
		struct pim_interface *pim_ifp = ifp->info;

		if (!pim_ifp)
			continue;

		pim_if_delete(ifp);
	}
	return;
}

static void pim_sec_addr_free(struct pim_secondary_addr *sec_addr)
{
	XFREE(MTYPE_PIM_SEC_ADDR, sec_addr);
}

__attribute__((unused))
static int pim_sec_addr_comp(const void *p1, const void *p2)
{
	const struct pim_secondary_addr *sec1 = p1;
	const struct pim_secondary_addr *sec2 = p2;

	if (sec1->addr.family == AF_INET && sec2->addr.family == AF_INET6)
		return -1;

	if (sec1->addr.family == AF_INET6 && sec2->addr.family == AF_INET)
		return 1;

	if (sec1->addr.family == AF_INET) {
		if (ntohl(sec1->addr.u.prefix4.s_addr)
		    < ntohl(sec2->addr.u.prefix4.s_addr))
			return -1;

		if (ntohl(sec1->addr.u.prefix4.s_addr)
		    > ntohl(sec2->addr.u.prefix4.s_addr))
			return 1;
	} else {
		return memcmp(&sec1->addr.u.prefix6, &sec2->addr.u.prefix6,
			      sizeof(struct in6_addr));
	}

	return 0;
}

struct pim_interface *pim_if_new(struct interface *ifp, bool gm, bool pim,
				 bool ispimreg, bool is_vxlan_term)
{
	struct pim_interface *pim_ifp;

	assert(ifp);
	assert(!ifp->info);

	pim_ifp = XCALLOC(MTYPE_PIM_INTERFACE, sizeof(*pim_ifp));

	pim_ifp->pim = ifp->vrf->info;
	pim_ifp->mroute_vif_index = -1;

	pim_ifp->igmp_version = IGMP_DEFAULT_VERSION;
	pim_ifp->mld_version = MLD_DEFAULT_VERSION;
	pim_ifp->gm_default_robustness_variable =
		GM_DEFAULT_ROBUSTNESS_VARIABLE;
	pim_ifp->gm_default_query_interval = GM_GENERAL_QUERY_INTERVAL;
	pim_ifp->gm_query_max_response_time_dsec =
		GM_QUERY_MAX_RESPONSE_TIME_DSEC;
	pim_ifp->gm_specific_query_max_response_time_dsec =
		GM_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC;
	pim_ifp->gm_last_member_query_count = GM_DEFAULT_ROBUSTNESS_VARIABLE;

	/* BSM config on interface: true by default */
	pim_ifp->bsm_enable = true;
	pim_ifp->ucast_bsm_accept = true;
	pim_ifp->am_i_dr = false;

	/*
	  RFC 3376: 8.3. Query Response Interval
	  The number of seconds represented by the [Query Response Interval]
	  must be less than the [Query Interval].
	 */
	assert(pim_ifp->gm_query_max_response_time_dsec <
	       pim_ifp->gm_default_query_interval);

	pim_ifp->pim_enable = pim;
	pim_ifp->pim_passive_enable = false;
	pim_ifp->gm_enable = gm;
	pim_ifp->gm_proxy = false;

	pim_ifp->gm_join_list = NULL;
	pim_ifp->static_group_list = NULL;
	pim_ifp->pim_neighbor_list = NULL;
	pim_ifp->upstream_switch_list = NULL;
	pim_ifp->pim_generation_id = 0;

	/* list of struct gm_sock */
	pim_igmp_if_init(pim_ifp, ifp);

	/* list of struct pim_neighbor */
	pim_ifp->pim_neighbor_list = list_new();
	pim_ifp->pim_neighbor_list->del = (void (*)(void *))pim_neighbor_free;

	pim_ifp->upstream_switch_list = list_new();
	pim_ifp->upstream_switch_list->del =
		(void (*)(void *))pim_jp_agg_group_list_free;
	pim_ifp->upstream_switch_list->cmp = pim_jp_agg_group_list_cmp;

	pim_ifp->sec_addr_list = list_new();
	pim_ifp->sec_addr_list->del = (void (*)(void *))pim_sec_addr_free;
	pim_ifp->sec_addr_list->cmp =
		(int (*)(void *, void *))pim_sec_addr_comp;

	pim_ifp->activeactive = false;

	RB_INIT(pim_ifchannel_rb, &pim_ifp->ifchannel_rb);

	ifp->info = pim_ifp;

	pim_sock_reset(ifp);

	pim_if_add_vif(ifp, ispimreg, is_vxlan_term);
	pim_ifp->pim->mcast_if_count++;

	return pim_ifp;
}

void pim_if_delete(struct interface *ifp)
{
	struct pim_interface *pim_ifp;

	assert(ifp);
	pim_ifp = ifp->info;
	assert(pim_ifp);

	pim_ifp->pim->mcast_if_count--;
	if (pim_ifp->gm_join_list) {
		pim_if_gm_join_del_all(ifp);
		/*
		 * Sometimes gm_join_del_all does not delete them all
		 * and as such it's not actually freed.  Let's
		 * just clean this up if it wasn't to prevent
		 * the problem.
		 */
		if (pim_ifp->gm_join_list)
			list_delete(&pim_ifp->gm_join_list);
	}

	if (pim_ifp->static_group_list)
		pim_if_static_group_del_all(ifp);

	pim_ifchannel_delete_all(ifp);
#if PIM_IPV == 4
	igmp_sock_delete_all(ifp);
#endif
	if (pim_ifp->pim_sock_fd >= 0)
		pim_sock_delete(ifp, "Interface removed from configuration");

	pim_if_del_vif(ifp);

	pim_igmp_if_fini(pim_ifp);

	list_delete(&pim_ifp->pim_neighbor_list);
	list_delete(&pim_ifp->upstream_switch_list);
	list_delete(&pim_ifp->sec_addr_list);

	if (pim_ifp->bfd_config.profile)
		XFREE(MTYPE_TMP, pim_ifp->bfd_config.profile);

	XFREE(MTYPE_PIM_INTERFACE, pim_ifp->boundary_oil_plist);
	XFREE(MTYPE_PIM_INTERFACE, pim_ifp);

	ifp->info = NULL;
}

void pim_if_update_could_assert(struct interface *ifp)
{
	struct pim_interface *pim_ifp;
	struct pim_ifchannel *ch;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) {
		pim_ifchannel_update_could_assert(ch);
	}
}

static void pim_if_update_my_assert_metric(struct interface *ifp)
{
	struct pim_interface *pim_ifp;
	struct pim_ifchannel *ch;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) {
		pim_ifchannel_update_my_assert_metric(ch);
	}
}

static void pim_addr_change(struct interface *ifp)
{
	struct pim_interface *pim_ifp;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	pim_if_dr_election(ifp); /* router's own DR Priority (addr) changes --
				    Done TODO T30 */
	pim_if_update_join_desired(pim_ifp); /* depends on DR */
	pim_if_update_could_assert(ifp);     /* depends on DR */
	pim_if_update_my_assert_metric(ifp); /* depends on could_assert */
	pim_if_update_assert_tracking_desired(
		ifp); /* depends on DR, join_desired */

	/*
	  RFC 4601: 4.3.1.  Sending Hello Messages

	  1) Before an interface goes down or changes primary IP address, a
	  Hello message with a zero HoldTime should be sent immediately
	  (with the old IP address if the IP address changed).
	  -- Done at the caller of the function as new ip already updated here

	  2) After an interface has changed its IP address, it MUST send a
	  Hello message with its new IP address.
	  -- DONE below

	  3) If an interface changes one of its secondary IP addresses, a
	  Hello message with an updated Address_List option and a non-zero
	  HoldTime should be sent immediately.
	  -- FIXME See TODO T31
	 */
	PIM_IF_FLAG_UNSET_HELLO_SENT(pim_ifp->flags);
	if (pim_ifp->pim_sock_fd < 0)
		return;
	pim_hello_restart_now(ifp); /* send hello and restart timer */
}

static int detect_primary_address_change(struct interface *ifp,
					 int force_prim_as_any,
					 const char *caller)
{
	struct pim_interface *pim_ifp = ifp->info;
	pim_addr new_prim_addr;
	int changed;

	if (force_prim_as_any)
		new_prim_addr = PIMADDR_ANY;
	else
		new_prim_addr = pim_find_primary_addr(ifp);

	changed = pim_addr_cmp(new_prim_addr, pim_ifp->primary_address);

	if (PIM_DEBUG_ZEBRA)
		zlog_debug("%s: old=%pPA new=%pPA on interface %s: %s",
			   __func__, &pim_ifp->primary_address, &new_prim_addr,
			   ifp->name, changed ? "changed" : "unchanged");

	if (changed) {
		/* Before updating pim_ifp send Hello time with 0 hold time */
		if (pim_ifp->pim_enable) {
			pim_hello_send(ifp, 0 /* zero-sec holdtime */);
		}
		pim_ifp->primary_address = new_prim_addr;
	}

	return changed;
}

static struct pim_secondary_addr *
pim_sec_addr_find(struct pim_interface *pim_ifp, struct prefix *addr)
{
	struct pim_secondary_addr *sec_addr;
	struct listnode *node;

	for (ALL_LIST_ELEMENTS_RO(pim_ifp->sec_addr_list, node, sec_addr)) {
		if (prefix_cmp(&sec_addr->addr, addr) == 0) {
			return sec_addr;
		}
	}

	return NULL;
}

static void pim_sec_addr_del(struct pim_interface *pim_ifp,
			     struct pim_secondary_addr *sec_addr)
{
	listnode_delete(pim_ifp->sec_addr_list, sec_addr);
	pim_sec_addr_free(sec_addr);
}

static int pim_sec_addr_add(struct pim_interface *pim_ifp, struct prefix *addr)
{
	int changed = 0;
	struct pim_secondary_addr *sec_addr;

	sec_addr = pim_sec_addr_find(pim_ifp, addr);
	if (sec_addr) {
		sec_addr->flags &= ~PIM_SEC_ADDRF_STALE;
		return changed;
	}

	sec_addr = XCALLOC(MTYPE_PIM_SEC_ADDR, sizeof(*sec_addr));

	changed = 1;
	sec_addr->addr = *addr;
	listnode_add_sort(pim_ifp->sec_addr_list, sec_addr);

	return changed;
}

static int pim_sec_addr_del_all(struct pim_interface *pim_ifp)
{
	int changed = 0;

	if (!list_isempty(pim_ifp->sec_addr_list)) {
		changed = 1;
		/* remove all nodes and free up the list itself */
		list_delete_all_node(pim_ifp->sec_addr_list);
	}

	return changed;
}

static int pim_sec_addr_update(struct interface *ifp)
{
	struct pim_interface *pim_ifp = ifp->info;
	struct connected *ifc;
	struct listnode *node;
	struct listnode *nextnode;
	struct pim_secondary_addr *sec_addr;
	int changed = 0;

	for (ALL_LIST_ELEMENTS_RO(pim_ifp->sec_addr_list, node, sec_addr)) {
		sec_addr->flags |= PIM_SEC_ADDRF_STALE;
	}

	frr_each (if_connected, ifp->connected, ifc) {
		pim_addr addr = pim_addr_from_prefix(ifc->address);

		if (pim_addr_is_any(addr))
			continue;

		if (!pim_addr_cmp(addr, pim_ifp->primary_address)) {
			/* don't add the primary address into the secondary
			 * address list */
			continue;
		}

		if (pim_sec_addr_add(pim_ifp, ifc->address)) {
			changed = 1;
		}
	}

	/* Drop stale entries */
	for (ALL_LIST_ELEMENTS(pim_ifp->sec_addr_list, node, nextnode,
			       sec_addr)) {
		if (sec_addr->flags & PIM_SEC_ADDRF_STALE) {
			pim_sec_addr_del(pim_ifp, sec_addr);
			changed = 1;
		}
	}

	return changed;
}

static int detect_secondary_address_change(struct interface *ifp,
					   int force_prim_as_any,
					   const char *caller)
{
	struct pim_interface *pim_ifp = ifp->info;
	int changed = 0;

	if (force_prim_as_any) {
		/* if primary address is being forced to zero just flush the
		 * secondary address list */
		changed = pim_sec_addr_del_all(pim_ifp);
	} else {
		/* re-evaluate the secondary address list */
		changed = pim_sec_addr_update(ifp);
	}

	return changed;
}

static void detect_address_change(struct interface *ifp, int force_prim_as_any,
				  const char *caller)
{
	int changed = 0;
	struct pim_interface *pim_ifp;

	pim_ifp = ifp->info;
	if (!pim_ifp)
		return;

	if (detect_primary_address_change(ifp, force_prim_as_any, caller)) {
		changed = 1;
	}

	if (detect_secondary_address_change(ifp, force_prim_as_any, caller)) {
		changed = 1;
	}


	if (changed) {
		if (!pim_ifp->pim_enable) {
			return;
		}

		pim_addr_change(ifp);
	}

	/* XXX: if we have unnumbered interfaces we need to run detect address
	 * address change on all of them when the lo address changes */
}

int pim_update_source_set(struct interface *ifp, pim_addr source)
{
	struct pim_interface *pim_ifp = ifp->info;

	if (!pim_ifp) {
		return PIM_IFACE_NOT_FOUND;
	}

	if (!pim_addr_cmp(pim_ifp->update_source, source)) {
		return PIM_UPDATE_SOURCE_DUP;
	}

	pim_ifp->update_source = source;
	detect_address_change(ifp, 0 /* force_prim_as_any */, __func__);

	return PIM_SUCCESS;
}

void pim_if_addr_add(struct connected *ifc)
{
	struct pim_interface *pim_ifp;
	struct interface *ifp;
	bool vxlan_term;

	assert(ifc);

	ifp = ifc->ifp;
	assert(ifp);
	pim_ifp = ifp->info;
	if (!pim_ifp)
		return;

	if (!if_is_operative(ifp))
		return;

	if (PIM_DEBUG_ZEBRA)
		zlog_debug("%s: %s ifindex=%d connected IP address %pFX %s",
			   __func__, ifp->name, ifp->ifindex, ifc->address,
			   CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY)
				   ? "secondary"
				   : "primary");
#if PIM_IPV != 4
	if (IN6_IS_ADDR_LINKLOCAL(&ifc->address->u.prefix6) ||
	    IN6_IS_ADDR_LOOPBACK(&ifc->address->u.prefix6)) {
		if (IN6_IS_ADDR_UNSPECIFIED(&pim_ifp->ll_lowest))
			pim_ifp->ll_lowest = ifc->address->u.prefix6;
		else if (IPV6_ADDR_CMP(&ifc->address->u.prefix6,
				       &pim_ifp->ll_lowest) < 0)
			pim_ifp->ll_lowest = ifc->address->u.prefix6;

		if (IPV6_ADDR_CMP(&ifc->address->u.prefix6,
				  &pim_ifp->ll_highest) > 0)
			pim_ifp->ll_highest = ifc->address->u.prefix6;

		if (PIM_DEBUG_ZEBRA)
			zlog_debug(
				"%s: new link-local %pI6, lowest now %pI6, highest %pI6",
				ifc->ifp->name, &ifc->address->u.prefix6,
				&pim_ifp->ll_lowest, &pim_ifp->ll_highest);
	}
#endif

	detect_address_change(ifp, 0, __func__);

#if PIM_IPV == 4
	if (ifc->address->family == AF_INET) {
		struct in_addr ifaddr = ifc->address->u.prefix4;

		if (pim_ifp->gm_enable) {
			struct gm_sock *igmp;

			/* lookup IGMP socket */
			igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->gm_socket_list, ifaddr);
			if (!igmp) {
				/* if addr new, add IGMP socket */
				pim_igmp_sock_add(pim_ifp->gm_socket_list,
						  ifaddr, ifp, false);
			} else if (igmp->mtrace_only) {
				igmp_sock_delete(igmp);
				pim_igmp_sock_add(pim_ifp->gm_socket_list, ifaddr, ifp, false);
			}

			/* Replay Static IGMP groups */
			if (pim_ifp->gm_join_list) {
				struct listnode *node;
				struct listnode *nextnode;
				struct gm_join *ij;
				int join_fd;

				for (ALL_LIST_ELEMENTS(pim_ifp->gm_join_list, node, nextnode, ij)) {
					/* Close socket and reopen with Source and Group
					 */
					close(ij->sock_fd);
					join_fd = gm_join_sock(ifp->name, ifp->ifindex,
							       ij->group_addr, ij->source_addr,
							       pim_ifp);
					if (join_fd < 0) {
						char group_str[INET_ADDRSTRLEN];
						char source_str[INET_ADDRSTRLEN];
						pim_inet4_dump("<grp?>", ij->group_addr, group_str,
							       sizeof(group_str));
						pim_inet4_dump("<src?>", ij->source_addr,
							       source_str, sizeof(source_str));
						zlog_warn("%s: gm_join_sock() failure for IGMP group %s source %s on interface %s",
							  __func__, group_str, source_str,
							  ifp->name);
						/* warning only */
					} else
						ij->sock_fd = join_fd;
				}
			}
		} /* igmp */
		else {
			struct gm_sock *igmp;

			/* lookup IGMP socket */
			igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->gm_socket_list, ifaddr);
			if (igmp)
				igmp_sock_delete(igmp);
			/* if addr new, add IGMP socket */
			pim_igmp_sock_add(pim_ifp->gm_socket_list, ifaddr, ifp,
					  true);
		} /* igmp mtrace only */
	}
#endif

	if (pim_ifp->pim_enable) {

		if (!pim_addr_is_any(pim_ifp->primary_address)) {

			/* Interface has a valid socket ? */
			if (pim_ifp->pim_sock_fd < 0) {
				if (pim_sock_add(ifp)) {
					zlog_warn(
						"Failure creating PIM socket for interface %s",
						ifp->name);
				}
			}
			struct pim_nexthop_cache *pnc = NULL;
			struct pim_rpf rpf;
			struct zclient *zclient = NULL;

			zclient = pim_zebra_zclient_get();
			/* RP config might come prior to (local RP's interface)
			   IF UP event.
			   In this case, pnc would not have pim enabled
			   nexthops.
			   Once Interface is UP and pim info is available,
			   reregister
			   with RNH address to receive update and add the
			   interface as nexthop. */
			memset(&rpf, 0, sizeof(struct pim_rpf));
			rpf.rpf_addr = pim_addr_from_prefix(ifc->address);
			pnc = pim_nexthop_cache_find(pim_ifp->pim, &rpf);
			if (pnc)
				pim_sendmsg_zebra_rnh(pim_ifp->pim, zclient,
						      pnc,
						      ZEBRA_NEXTHOP_REGISTER);
		}
	} /* pim */

	/*
	  PIM or IGMP is enabled on interface, and there is at least one
	  address assigned, then try to create a vif_index.
	*/
	if (pim_ifp->mroute_vif_index < 0) {
		vxlan_term = pim_vxlan_is_term_dev_cfg(pim_ifp->pim, ifp);
		pim_if_add_vif(ifp, false, vxlan_term);
	}
	gm_ifp_update(ifp);
	pim_ifchannel_scan_forward_start(ifp);
}

static void pim_if_addr_del_igmp(struct connected *ifc)
{
#if PIM_IPV == 4
	struct pim_interface *pim_ifp = ifc->ifp->info;
	struct gm_sock *igmp;
	struct in_addr ifaddr;

	if (ifc->address->family != AF_INET) {
		/* non-IPv4 address */
		return;
	}

	if (!pim_ifp) {
		/* IGMP not enabled on interface */
		return;
	}

	ifaddr = ifc->address->u.prefix4;

	/* lookup IGMP socket */
	igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->gm_socket_list, ifaddr);
	if (igmp) {
		/* if addr found, del IGMP socket */
		igmp_sock_delete(igmp);
	}
#endif
}

static void pim_if_addr_del_pim(struct connected *ifc)
{
	struct pim_interface *pim_ifp = ifc->ifp->info;

	if (ifc->address->family != PIM_AF) {
		/* non-IPv4 address */
		return;
	}

	if (!pim_ifp) {
		/* PIM not enabled on interface */
		return;
	}

	if (!pim_addr_is_any(pim_ifp->primary_address)) {
		/* Interface keeps a valid primary address */
		return;
	}

	if (pim_ifp->pim_sock_fd < 0) {
		/* Interface does not hold a valid socket any longer */
		return;
	}

	/*
	  pim_sock_delete() closes the socket, stops read and timer threads,
	  and kills all neighbors.
	 */
	pim_sock_delete(ifc->ifp,
			"last address has been removed from interface");
}

void pim_if_addr_del(struct connected *ifc, int force_prim_as_any)
{
	struct interface *ifp;

	assert(ifc);
	ifp = ifc->ifp;
	assert(ifp);

	if (PIM_DEBUG_ZEBRA)
		zlog_debug("%s: %s ifindex=%d disconnected IP address %pFX %s",
			   __func__, ifp->name, ifp->ifindex, ifc->address,
			   CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY)
				   ? "secondary"
				   : "primary");

#if PIM_IPV == 6
	struct pim_interface *pim_ifp = ifc->ifp->info;

	if (pim_ifp &&
	    (!IPV6_ADDR_CMP(&ifc->address->u.prefix6, &pim_ifp->ll_lowest) ||
	     !IPV6_ADDR_CMP(&ifc->address->u.prefix6, &pim_ifp->ll_highest))) {
		struct connected *cc;

		memset(&pim_ifp->ll_lowest, 0xff, sizeof(pim_ifp->ll_lowest));
		memset(&pim_ifp->ll_highest, 0, sizeof(pim_ifp->ll_highest));

		frr_each (if_connected, ifc->ifp->connected, cc) {
			if (!IN6_IS_ADDR_LINKLOCAL(&cc->address->u.prefix6) &&
			    !IN6_IS_ADDR_LOOPBACK(&cc->address->u.prefix6))
				continue;

			if (IPV6_ADDR_CMP(&cc->address->u.prefix6,
					  &pim_ifp->ll_lowest) < 0)
				pim_ifp->ll_lowest = cc->address->u.prefix6;
			if (IPV6_ADDR_CMP(&cc->address->u.prefix6,
					  &pim_ifp->ll_highest) > 0)
				pim_ifp->ll_highest = cc->address->u.prefix6;
		}

		if (pim_ifp->ll_lowest.s6_addr[0] == 0xff)
			memset(&pim_ifp->ll_lowest, 0,
			       sizeof(pim_ifp->ll_lowest));

		if (PIM_DEBUG_ZEBRA)
			zlog_debug(
				"%s: removed link-local %pI6, lowest now %pI6, highest %pI6",
				ifc->ifp->name, &ifc->address->u.prefix6,
				&pim_ifp->ll_lowest, &pim_ifp->ll_highest);

		gm_ifp_update(ifp);
	}
#endif

	detect_address_change(ifp, force_prim_as_any, __func__);

	pim_if_addr_del_igmp(ifc);
	pim_if_addr_del_pim(ifc);
}

void pim_if_addr_add_all(struct interface *ifp)
{
	struct connected *ifc;
	int v4_addrs = 0;
	int v6_addrs = 0;
	struct pim_interface *pim_ifp = ifp->info;
	bool vxlan_term;


	/* PIM/IGMP enabled ? */
	if (!pim_ifp)
		return;

	frr_each (if_connected, ifp->connected, ifc) {
		struct prefix *p = ifc->address;

		if (p->family != AF_INET)
			v6_addrs++;
		else
			v4_addrs++;
		pim_if_addr_add(ifc);
	}

	if (!v4_addrs && v6_addrs && !if_is_loopback(ifp) &&
	    pim_ifp->pim_enable && !pim_addr_is_any(pim_ifp->primary_address) &&
	    pim_ifp->pim_sock_fd < 0 && pim_sock_add(ifp)) {
		/* Interface has a valid primary address ? */
		/* Interface has a valid socket ? */
		zlog_warn("Failure creating PIM socket for interface %s",
			  ifp->name);
	}
	/*
	 * PIM or IGMP/MLD is enabled on interface, and there is at least one
	 * address assigned, then try to create a vif_index.
	 */
	if (pim_ifp->mroute_vif_index < 0) {
		vxlan_term = pim_vxlan_is_term_dev_cfg(pim_ifp->pim, ifp);
		pim_if_add_vif(ifp, false, vxlan_term);
	}
	gm_ifp_update(ifp);
	pim_ifchannel_scan_forward_start(ifp);

	pim_rp_setup(pim_ifp->pim);
	pim_rp_check_on_if_add(pim_ifp);
}

void pim_if_addr_del_all(struct interface *ifp)
{
	struct connected *ifc;
	struct pim_instance *pim;

	pim = ifp->vrf->info;
	if (!pim)
		return;

	/* PIM/IGMP enabled ? */
	if (!ifp->info)
		return;

	frr_each_safe (if_connected, ifp->connected, ifc) {
		struct prefix *p = ifc->address;

		if (p->family != PIM_AF)
			continue;

		pim_if_addr_del(ifc, 1 /* force_prim_as_any=true */);
	}

	pim_rp_setup(pim);
	pim_i_am_rp_re_evaluate(pim);
}

void pim_if_addr_del_all_igmp(struct interface *ifp)
{
	struct connected *ifc;

	/* PIM/IGMP enabled ? */
	if (!ifp->info)
		return;

	frr_each_safe (if_connected, ifp->connected, ifc) {
		struct prefix *p = ifc->address;

		if (p->family != AF_INET)
			continue;

		pim_if_addr_del_igmp(ifc);
	}
}

pim_addr pim_find_primary_addr(struct interface *ifp)
{
	struct connected *ifc;
	struct pim_interface *pim_ifp = ifp->info;

	if (pim_ifp && !pim_addr_is_any(pim_ifp->update_source))
		return pim_ifp->update_source;

#if PIM_IPV == 6
	if (pim_ifp && !pim_addr_is_any(pim_ifp->ll_highest))
		return pim_ifp->ll_highest;

	pim_addr best_addr = PIMADDR_ANY;

	frr_each (if_connected, ifp->connected, ifc) {
		pim_addr addr;

		if (ifc->address->family != AF_INET6)
			continue;

		addr = pim_addr_from_prefix(ifc->address);
		if (!IN6_IS_ADDR_LINKLOCAL(&addr))
			continue;
		if (pim_addr_cmp(addr, best_addr) > 0)
			best_addr = addr;
	}

	return best_addr;
#else
	int v4_addrs = 0;
	int v6_addrs = 0;
	struct connected *promote_ifc = NULL;

	frr_each (if_connected, ifp->connected, ifc) {
		switch (ifc->address->family) {
		case AF_INET:
			v4_addrs++;
			break;
		case AF_INET6:
			v6_addrs++;
			break;
		default:
			continue;
		}

		if (ifc->address->family != PIM_AF)
			continue;

		if (CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY)) {
			promote_ifc = ifc;
			continue;
		}

		return pim_addr_from_prefix(ifc->address);
	}


	/* Promote the new primary address. */
	if (v4_addrs && promote_ifc) {
		UNSET_FLAG(promote_ifc->flags, ZEBRA_IFA_SECONDARY);
		return pim_addr_from_prefix(promote_ifc->address);
	}

	/*
	 * If we have no v4_addrs and v6 is configured
	 * We probably are using unnumbered
	 * So let's grab the loopbacks v4 address
	 * and use that as the primary address
	 */
	if (!v4_addrs && v6_addrs) {
		struct interface *lo_ifp;

		// DBS - Come back and check here
		if (ifp->vrf->vrf_id == VRF_DEFAULT)
			lo_ifp = if_lookup_by_name("lo", ifp->vrf->vrf_id);
		else
			lo_ifp = if_lookup_by_name(ifp->vrf->name,
						   ifp->vrf->vrf_id);

		if (lo_ifp && (lo_ifp != ifp))
			return pim_find_primary_addr(lo_ifp);
	}
	return PIMADDR_ANY;
#endif
}

static int pim_iface_next_vif_index(struct interface *ifp)
{
	struct pim_interface *pim_ifp = ifp->info;
	struct pim_instance *pim = pim_ifp->pim;
	int i;

	/*
	 * The pimreg vif is always going to be in index 0
	 * of the table.
	 */
	if (ifp->ifindex == PIM_OIF_PIM_REGISTER_VIF)
		return 0;

	for (i = 1; i < MAXVIFS; i++) {
		if (pim->iface_vif_index[i] == 0)
			return i;
	}
	return MAXVIFS;
}

/*
  pim_if_add_vif() uses ifindex as vif_index

  see also pim_if_find_vifindex_by_ifindex()
 */
int pim_if_add_vif(struct interface *ifp, bool ispimreg, bool is_vxlan_term)
{
	struct pim_interface *pim_ifp = ifp->info;
	pim_addr ifaddr;
	unsigned char flags = 0;

	assert(pim_ifp);

	if (pim_ifp->mroute_vif_index > 0) {
		zlog_warn("%s: vif_index=%d > 0 on interface %s ifindex=%d",
			  __func__, pim_ifp->mroute_vif_index, ifp->name,
			  ifp->ifindex);
		return -1;
	}

	if (ifp->ifindex < 0) {
		zlog_warn("%s: ifindex=%d < 0 on interface %s", __func__,
			  ifp->ifindex, ifp->name);
		return -2;
	} else if ((ifp->ifindex == PIM_OIF_PIM_REGISTER_VIF) &&
		   ((strncmp(ifp->name, "pimreg", 6)) &&
		    (strncmp(ifp->name, "pim6reg", 7)))) {
		zlog_warn("%s: ifindex=%d on interface %s", __func__,
			  ifp->ifindex, ifp->name);
		return -2;
	}

	ifaddr = pim_ifp->primary_address;
#if PIM_IPV != 6
	/* IPv6 API is always by interface index */
	if (!ispimreg && !is_vxlan_term && pim_addr_is_any(ifaddr)) {
		zlog_warn(
			"%s: could not get address for interface %s ifindex=%d",
			__func__, ifp->name, ifp->ifindex);
		return -4;
	}
#endif

	pim_ifp->mroute_vif_index = pim_iface_next_vif_index(ifp);

	if (pim_ifp->mroute_vif_index >= MAXVIFS) {
		zlog_warn(
			"%s: Attempting to configure more than MAXVIFS=%d on pim enabled interface %s",
			__func__, MAXVIFS, ifp->name);
		return -3;
	}

	if (ifp->ifindex == PIM_OIF_PIM_REGISTER_VIF)
		flags = VIFF_REGISTER;
#ifdef VIFF_USE_IFINDEX
	else
		flags = VIFF_USE_IFINDEX;
#endif

	if (pim_mroute_add_vif(ifp, ifaddr, flags)) {
		/* pim_mroute_add_vif reported error */
		return -5;
	}

	pim_ifp->pim->iface_vif_index[pim_ifp->mroute_vif_index] = 1;

	if (!ispimreg)
		gm_ifp_update(ifp);

	/* if the device qualifies as pim_vxlan iif/oif update vxlan entries */
	pim_vxlan_add_vif(ifp);
	return 0;
}

int pim_if_del_vif(struct interface *ifp)
{
	struct pim_interface *pim_ifp = ifp->info;

	if (pim_ifp->mroute_vif_index < 1) {
		zlog_warn("%s: vif_index=%d < 1 on interface %s ifindex=%d",
			  __func__, pim_ifp->mroute_vif_index, ifp->name,
			  ifp->ifindex);
		return -1;
	}

	/* if the device was a pim_vxlan iif/oif update vxlan mroute entries */
	pim_vxlan_del_vif(ifp);

	gm_ifp_teardown(ifp);

	pim_mroute_del_vif(ifp);

	/*
	  Update vif_index
	 */
	pim_ifp->pim->iface_vif_index[pim_ifp->mroute_vif_index] = 0;

	pim_ifp->mroute_vif_index = -1;
	return 0;
}

// DBS - VRF Revist
struct interface *pim_if_find_by_vif_index(struct pim_instance *pim,
					   ifindex_t vif_index)
{
	struct interface *ifp;

	FOR_ALL_INTERFACES (pim->vrf, ifp) {
		if (ifp->info) {
			struct pim_interface *pim_ifp;
			pim_ifp = ifp->info;

			if (vif_index == pim_ifp->mroute_vif_index)
				return ifp;
		}
	}

	return 0;
}

/*
  pim_if_add_vif() uses ifindex as vif_index
 */
int pim_if_find_vifindex_by_ifindex(struct pim_instance *pim, ifindex_t ifindex)
{
	struct pim_interface *pim_ifp;
	struct interface *ifp;

	ifp = if_lookup_by_index(ifindex, pim->vrf->vrf_id);
	if (!ifp || !ifp->info)
		return -1;
	pim_ifp = ifp->info;

	return pim_ifp->mroute_vif_index;
}

int pim_if_lan_delay_enabled(struct interface *ifp)
{
	struct pim_interface *pim_ifp;

	pim_ifp = ifp->info;
	assert(pim_ifp);
	assert(pim_ifp->pim_number_of_nonlandelay_neighbors >= 0);

	return pim_ifp->pim_number_of_nonlandelay_neighbors == 0;
}

uint16_t pim_if_effective_propagation_delay_msec(struct interface *ifp)
{
	if (pim_if_lan_delay_enabled(ifp)) {
		struct pim_interface *pim_ifp;
		pim_ifp = ifp->info;
		return pim_ifp->pim_neighbors_highest_propagation_delay_msec;
	} else {
		return PIM_DEFAULT_PROPAGATION_DELAY_MSEC;
	}
}

uint16_t pim_if_effective_override_interval_msec(struct interface *ifp)
{
	if (pim_if_lan_delay_enabled(ifp)) {
		struct pim_interface *pim_ifp;
		pim_ifp = ifp->info;
		return pim_ifp->pim_neighbors_highest_override_interval_msec;
	} else {
		return PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC;
	}
}

int pim_if_t_override_msec(struct interface *ifp)
{
	int effective_override_interval_msec;
	int t_override_msec;

	effective_override_interval_msec =
		pim_if_effective_override_interval_msec(ifp);

	t_override_msec =
		frr_weak_random() % (effective_override_interval_msec + 1);

	return t_override_msec;
}

uint16_t pim_if_jp_override_interval_msec(struct interface *ifp)
{
	return pim_if_effective_propagation_delay_msec(ifp)
	       + pim_if_effective_override_interval_msec(ifp);
}

/*
  RFC 4601: 4.1.6.  State Summarization Macros

  The function NBR( I, A ) uses information gathered through PIM Hello
  messages to map the IP address A of a directly connected PIM
  neighbor router on interface I to the primary IP address of the same
  router (Section 4.3.4).  The primary IP address of a neighbor is the
  address that it uses as the source of its PIM Hello messages.
*/
struct pim_neighbor *pim_if_find_neighbor(struct interface *ifp, pim_addr addr)
{
	struct listnode *neighnode;
	struct pim_neighbor *neigh;
	struct pim_interface *pim_ifp;
	struct prefix p;

	assert(ifp);

	pim_ifp = ifp->info;
	if (!pim_ifp) {
		zlog_warn("%s: multicast not enabled on interface %s", __func__,
			  ifp->name);
		return 0;
	}

	pim_addr_to_prefix(&p, addr);

	for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode,
				  neigh)) {

		/* primary address ? */
		if (!pim_addr_cmp(neigh->source_addr, addr))
			return neigh;

		/* secondary address ? */
		if (pim_neighbor_find_secondary(neigh, &p))
			return neigh;
	}

	if (PIM_DEBUG_PIM_TRACE)
		zlog_debug(
			"%s: neighbor not found for address %pPA on interface %s",
			__func__, &addr, ifp->name);

	return NULL;
}

long pim_if_t_suppressed_msec(struct interface *ifp)
{
	struct pim_interface *pim_ifp;
	long t_suppressed_msec;
	uint32_t ramount = 0;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	/* join suppression disabled ? */
	if (pim_ifp->pim_can_disable_join_suppression)
		return 0;

	/* t_suppressed = t_periodic * rand(1.1, 1.4) */
	ramount = 1100 + (frr_weak_random() % (1400 - 1100 + 1));
	t_suppressed_msec = router->t_periodic * ramount;

	return t_suppressed_msec;
}

static void gm_join_free(struct gm_join *ij)
{
	XFREE(MTYPE_PIM_IGMP_JOIN, ij);
}

static void static_group_free(struct static_group *stgrp)
{
	XFREE(MTYPE_PIM_STATIC_GROUP, stgrp);
}

static struct gm_join *gm_join_find(struct list *join_list, pim_addr group_addr,
				    pim_addr source_addr)
{
	struct listnode *node;
	struct gm_join *ij;

	assert(join_list);

	for (ALL_LIST_ELEMENTS_RO(join_list, node, ij)) {
		if ((!pim_addr_cmp(group_addr, ij->group_addr)) &&
		    (!pim_addr_cmp(source_addr, ij->source_addr)))
			return ij;
	}

	return NULL;
}

static struct static_group *static_group_find(struct list *static_group_list,
					      pim_addr group_addr,
					      pim_addr source_addr)
{
	struct listnode *node;
	struct static_group *stgrp;

	assert(static_group_list);

	for (ALL_LIST_ELEMENTS_RO(static_group_list, node, stgrp)) {
		if ((!pim_addr_cmp(group_addr, stgrp->group_addr)) &&
		    (!pim_addr_cmp(source_addr, stgrp->source_addr)))
			return stgrp;
	}

	return NULL;
}

static int gm_join_sock(const char *ifname, ifindex_t ifindex,
			pim_addr group_addr, pim_addr source_addr,
			struct pim_interface *pim_ifp)
{
	int join_fd;

	pim_ifp->igmp_ifstat_joins_sent++;

	join_fd = pim_socket_raw(IPPROTO_GM);
	if (join_fd < 0) {
		pim_ifp->igmp_ifstat_joins_failed++;
		return -1;
	}

	if (pim_gm_join_source(join_fd, ifindex, group_addr, source_addr)) {
		zlog_warn(
			"%s: setsockopt(fd=%d) failure for " GM
			" group %pPAs source %pPAs ifindex %d on interface %s: errno=%d: %s",
			__func__, join_fd, &group_addr, &source_addr, ifindex,
			ifname, errno, safe_strerror(errno));

		pim_ifp->igmp_ifstat_joins_failed++;

		close(join_fd);
		return -2;
	}

	return join_fd;
}

static struct gm_join *gm_join_new(struct interface *ifp, pim_addr group_addr,
				   pim_addr source_addr,
				   enum gm_join_type join_type)
{
	struct pim_interface *pim_ifp;
	struct gm_join *ij;
	int join_fd;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	join_fd = gm_join_sock(ifp->name, ifp->ifindex, group_addr, source_addr,
			       pim_ifp);
	if (join_fd < 0) {
		zlog_warn("%s: gm_join_sock() failure for " GM
			  " group %pPAs source %pPAs on interface %s",
			  __func__, &group_addr, &source_addr, ifp->name);
		return 0;
	}

	ij = XCALLOC(MTYPE_PIM_IGMP_JOIN, sizeof(*ij));

	ij->sock_fd = join_fd;
	ij->group_addr = group_addr;
	ij->source_addr = source_addr;
	ij->join_type = join_type;
	ij->sock_creation = pim_time_monotonic_sec();

	listnode_add(pim_ifp->gm_join_list, ij);

	return ij;
}

static struct static_group *static_group_new(struct interface *ifp,
					     pim_addr group_addr,
					     pim_addr source_addr)
{
	struct pim_interface *pim_ifp;
	struct static_group *stgrp;
	pim_sgaddr sg;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	stgrp = XCALLOC(MTYPE_PIM_STATIC_GROUP, sizeof(*stgrp));

	stgrp->group_addr = group_addr;
	stgrp->source_addr = source_addr;
	stgrp->oilp = NULL;

	memset(&sg, 0, sizeof(sg));
	sg.src = source_addr;
	sg.grp = group_addr;

	tib_sg_gm_join(pim_ifp->pim, sg, ifp, &(stgrp->oilp));

	listnode_add(pim_ifp->static_group_list, stgrp);

	return stgrp;
}

ferr_r pim_if_gm_join_add(struct interface *ifp, pim_addr group_addr,
			  pim_addr source_addr, enum gm_join_type join_type)
{
	struct pim_interface *pim_ifp;
	struct gm_join *ij;

	pim_ifp = ifp->info;
	if (!pim_ifp) {
		return ferr_cfg_invalid("multicast not enabled on interface %s",
					ifp->name);
	}

	if (!pim_ifp->gm_join_list) {
		pim_ifp->gm_join_list = list_new();
		pim_ifp->gm_join_list->del = (void (*)(void *))gm_join_free;
	}

	ij = gm_join_find(pim_ifp->gm_join_list, group_addr, source_addr);

	/* This interface has already been configured to join this IGMP/MLD
	 * group
	 */
	if (ij) {
		/* turn an existing join into a "both" join */
		if (ij->join_type != join_type)
			ij->join_type = GM_JOIN_BOTH;
		return ferr_ok();
	}

	if (!gm_join_new(ifp, group_addr, source_addr, join_type)) {
		return ferr_cfg_invalid("can't join (%pPA,%pPA) on interface %s",
					&source_addr, &group_addr, ifp->name);
	}

	if (PIM_DEBUG_GM_EVENTS) {
		zlog_debug(
			"%s: issued static " GM
			" join for channel (S,G)=(%pPA,%pPA) on interface %s",
			__func__, &source_addr, &group_addr, ifp->name);
	}

	return ferr_ok();
}

int pim_if_gm_join_del(struct interface *ifp, pim_addr group_addr,
		       pim_addr source_addr, enum gm_join_type join_type)
{
	struct pim_interface *pim_ifp;
	struct gm_join *ij;

	pim_ifp = ifp->info;
	if (!pim_ifp) {
		zlog_warn("%s: multicast not enabled on interface %s", __func__,
			  ifp->name);
		return -1;
	}

	if (!pim_ifp->gm_join_list) {
		zlog_warn("%s: no " GM " join on interface %s", __func__,
			  ifp->name);
		return -2;
	}

	ij = gm_join_find(pim_ifp->gm_join_list, group_addr, source_addr);
	if (!ij) {
		zlog_warn("%s: could not find " GM
			  " group %pPAs source %pPAs on interface %s",
			  __func__, &group_addr, &source_addr, ifp->name);
		return -3;
	}

	if (ij->join_type != join_type) {
		if (ij->join_type != GM_JOIN_BOTH) {
			zlog_warn("%s: wrong  " GM
				  " gm_join_type %pPAs source %pPAs on interface %s",
				  __func__, &group_addr, &source_addr,
				  ifp->name);
			return -4;
		}
		/* drop back to a single join type from current setting of GM_JOIN_BOTH */
		ij->join_type = (join_type == GM_JOIN_STATIC ? GM_JOIN_PROXY
							     : GM_JOIN_STATIC);
		return 0;
	}

	if (close(ij->sock_fd)) {
		zlog_warn(
			"%s: failure closing sock_fd=%d for " GM
			" group %pPAs source %pPAs on interface %s: errno=%d: %s",
			__func__, ij->sock_fd, &group_addr, &source_addr,
			ifp->name, errno, safe_strerror(errno));
		/* warning only */
	}
	listnode_delete(pim_ifp->gm_join_list, ij);
	gm_join_free(ij);
	if (listcount(pim_ifp->gm_join_list) < 1)
		list_delete(&pim_ifp->gm_join_list);

	return 0;
}

static void pim_if_gm_join_del_all(struct interface *ifp)
{
	struct pim_interface *pim_ifp;
	struct listnode *node;
	struct listnode *nextnode;
	struct gm_join *ij;

	pim_ifp = ifp->info;
	if (!pim_ifp) {
		zlog_warn("%s: multicast not enabled on interface %s", __func__,
			  ifp->name);
		return;
	}

	if (!pim_ifp->gm_join_list)
		return;

	for (ALL_LIST_ELEMENTS(pim_ifp->gm_join_list, node, nextnode, ij))
		pim_if_gm_join_del(ifp, ij->group_addr, ij->source_addr, ij->join_type);
}

ferr_r pim_if_static_group_add(struct interface *ifp, pim_addr group_addr,
			       pim_addr source_addr)
{
	struct pim_interface *pim_ifp;
	struct static_group *stgrp;

	pim_ifp = ifp->info;
	if (!pim_ifp) {
		return ferr_cfg_invalid("multicast not enabled on interface %s",
					ifp->name);
	}

	if (!pim_ifp->static_group_list) {
		pim_ifp->static_group_list = list_new();
		pim_ifp->static_group_list->del =
			(void (*)(void *))static_group_free;
	}

	stgrp = static_group_find(pim_ifp->static_group_list, group_addr,
				  source_addr);

	/* This interface has already been configured with this static group
	 */
	if (stgrp)
		return ferr_ok();

	(void)static_group_new(ifp, group_addr, source_addr);

	if (PIM_DEBUG_GM_EVENTS) {
		zlog_debug("%s: Added static group (S,G)=(%pPA,%pPA) on interface %s",
			   __func__, &source_addr, &group_addr, ifp->name);
	}

	return ferr_ok();
}

int pim_if_static_group_del(struct interface *ifp, pim_addr group_addr,
			    pim_addr source_addr)
{
	struct pim_interface *pim_ifp;
	struct static_group *stgrp;
	pim_sgaddr sg;

	pim_ifp = ifp->info;
	if (!pim_ifp) {
		zlog_warn("%s: multicast not enabled on interface %s", __func__,
			  ifp->name);
		return -1;
	}

	if (!pim_ifp->static_group_list) {
		zlog_warn("%s: no static groups on interface %s", __func__,
			  ifp->name);
		return -2;
	}

	stgrp = static_group_find(pim_ifp->static_group_list, group_addr,
				  source_addr);
	if (!stgrp) {
		zlog_warn("%s: could not find static group %pPAs source %pPAs on interface %s",
			  __func__, &group_addr, &source_addr, ifp->name);
		return -3;
	}

	memset(&sg, 0, sizeof(sg));
	sg.src = source_addr;
	sg.grp = group_addr;

	tib_sg_gm_prune(pim_ifp->pim, sg, ifp, &(stgrp->oilp));

	listnode_delete(pim_ifp->static_group_list, stgrp);
	static_group_free(stgrp);
	if (listcount(pim_ifp->static_group_list) < 1) {
		list_delete(&pim_ifp->static_group_list);
		pim_ifp->static_group_list = 0;
	}

	return 0;
}

static void pim_if_static_group_del_all(struct interface *ifp)
{
	struct pim_interface *pim_ifp;
	struct listnode *node;
	struct listnode *nextnode;
	struct static_group *stgrp;

	pim_ifp = ifp->info;
	if (!pim_ifp) {
		zlog_warn("%s: multicast not enabled on interface %s", __func__,
			  ifp->name);
		return;
	}

	if (!pim_ifp->static_group_list)
		return;

	for (ALL_LIST_ELEMENTS(pim_ifp->static_group_list, node, nextnode,
			       stgrp))
		pim_if_static_group_del(ifp, stgrp->group_addr,
					stgrp->source_addr);
}

void pim_if_gm_proxy_init(struct pim_instance *pim, struct interface *oif)
{
	struct interface *ifp;

	FOR_ALL_INTERFACES (pim->vrf, ifp) {
		struct pim_interface *pim_ifp = ifp->info;
		struct listnode *source_node, *group_node;
		struct gm_group *group;
		struct gm_source *src;

		if (!pim_ifp)
			continue;

		if (ifp == oif) /* skip the source interface */
			continue;

		for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, group_node,
					  group)) {
			for (ALL_LIST_ELEMENTS_RO(group->group_source_list,
						  source_node, src)) {
				pim_if_gm_join_add(oif, group->group_addr,
						   src->source_addr,
						   GM_JOIN_PROXY);
			}
		}
	} /* scan interfaces */
}

void pim_if_gm_proxy_finis(struct pim_instance *pim, struct interface *ifp)
{
	struct pim_interface *pim_ifp = ifp->info;
	struct listnode *join_node;
	struct listnode *next_join_node;
	struct gm_join *join;

	if (!pim_ifp) {
		zlog_warn("%s: multicast not enabled on interface %s", __func__,
			  ifp->name);
		return;
	}

	for (ALL_LIST_ELEMENTS(pim_ifp->gm_join_list, join_node, next_join_node,
			       join)) {
		if (join)
			pim_if_gm_join_del(ifp, join->group_addr,
					   join->source_addr, GM_JOIN_PROXY);
	}
}

/*
  RFC 4601

  Transitions from "I am Assert Loser" State

  Current Winner's GenID Changes or NLT Expires

  The Neighbor Liveness Timer associated with the current winner
  expires or we receive a Hello message from the current winner
  reporting a different GenID from the one it previously reported.
  This indicates that the current winner's interface or router has
  gone down (and may have come back up), and so we must assume it no
  longer knows it was the winner.
 */
void pim_if_assert_on_neighbor_down(struct interface *ifp, pim_addr neigh_addr)
{
	struct pim_interface *pim_ifp;
	struct pim_ifchannel *ch;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) {
		/* Is (S,G,I) assert loser ? */
		if (ch->ifassert_state != PIM_IFASSERT_I_AM_LOSER)
			continue;
		/* Dead neighbor was winner ? */
		if (pim_addr_cmp(ch->ifassert_winner, neigh_addr))
			continue;

		assert_action_a5(ch);
	}
}

void pim_if_update_join_desired(struct pim_interface *pim_ifp)
{
	struct pim_ifchannel *ch;

	/* clear off flag from interface's upstreams */
	RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) {
		PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED_UPDATED(
			ch->upstream->flags);
	}

	/* scan per-interface (S,G,I) state on this I interface */
	RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) {
		struct pim_upstream *up = ch->upstream;

		if (PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED_UPDATED(up->flags))
			continue;

		/* update join_desired for the global (S,G) state */
		pim_upstream_update_join_desired(pim_ifp->pim, up);
		PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED_UPDATED(up->flags);
	}
}

void pim_if_update_assert_tracking_desired(struct interface *ifp)
{
	struct pim_interface *pim_ifp;
	struct pim_ifchannel *ch;

	pim_ifp = ifp->info;
	if (!pim_ifp)
		return;

	RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) {
		pim_ifchannel_update_assert_tracking_desired(ch);
	}
}

/*
 * PIM wants to have an interface pointer for everything it does.
 * The pimreg is a special interface that we have that is not
 * quite an interface but a VIF is created for it.
 */
void pim_if_create_pimreg(struct pim_instance *pim)
{
	char pimreg_name[IFNAMSIZ];

	if (!pim->regiface) {
		if (pim->vrf->vrf_id == VRF_DEFAULT)
			strlcpy(pimreg_name, PIMREG, sizeof(pimreg_name));
		else
			snprintf(pimreg_name, sizeof(pimreg_name), PIMREG "%u",
				 pim->vrf->data.l.table_id);

		pim->regiface = if_get_by_name(pimreg_name, pim->vrf->vrf_id,
					       pim->vrf->name);
		pim->regiface->ifindex = PIM_OIF_PIM_REGISTER_VIF;

		/*
		 * The pimreg interface might has been removed from
		 * kerenl with the VRF's deletion.  It must be
		 * recreated, so delete the old one first.
		 */
		if (pim->regiface->info)
			pim_if_delete(pim->regiface);

		pim_if_new(pim->regiface, false, false, true,
			   false /*vxlan_term*/);

		/*
		 * On vrf moves we delete the interface if there
		 * is nothing going on with it.  We cannot have
		 * the pimregiface deleted.
		 */
		pim->regiface->configured = true;

	}
}

struct prefix *pim_if_connected_to_source(struct interface *ifp, pim_addr src)
{
	struct connected *c;
	struct prefix p;

	if (!ifp)
		return NULL;

	pim_addr_to_prefix(&p, src);

	frr_each (if_connected, ifp->connected, c) {
		if (c->address->family != PIM_AF)
			continue;
		if (prefix_match(c->address, &p))
			return c->address;
		if (CONNECTED_PEER(c) && prefix_match(c->destination, &p))
			/* this is not a typo, on PtP we need to return the
			 * *local* address that lines up with src.
			 */
			return c->address;
	}

	return NULL;
}

bool pim_if_is_vrf_device(struct interface *ifp)
{
	if (if_is_vrf(ifp))
		return true;

	return false;
}

int pim_if_ifchannel_count(struct pim_interface *pim_ifp)
{
	struct pim_ifchannel *ch;
	int count = 0;

	RB_FOREACH (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb) {
		count++;
	}

	return count;
}

static int pim_ifp_create(struct interface *ifp)
{
	struct pim_instance *pim;

	pim = ifp->vrf->info;
	if (PIM_DEBUG_ZEBRA) {
		zlog_debug(
			"%s: %s index %d vrf %s(%u) flags %ld metric %d mtu %d operative %d",
			__func__, ifp->name, ifp->ifindex, ifp->vrf->name,
			ifp->vrf->vrf_id, (long)ifp->flags, ifp->metric,
			ifp->mtu, if_is_operative(ifp));
	}

	if (if_is_operative(ifp)) {
		struct pim_interface *pim_ifp;

		pim_ifp = ifp->info;
		/*
		 * If we have a pim_ifp already and this is an if_add
		 * that means that we probably have a vrf move event
		 * If that is the case, set the proper vrfness.
		 */
		if (pim_ifp)
			pim_ifp->pim = pim;
		pim_if_addr_add_all(ifp);

		/*
		 * Due to ordering issues based upon when
		 * a command is entered we should ensure that
		 * the pim reg is created for this vrf if we
		 * have configuration for it already.
		 *
		 * this is a no-op if it's already been done.
		 */
		pim_if_create_pimreg(pim);
	}

#if PIM_IPV == 4
	/*
	 * If we are a vrf device that is up, open up the pim_socket for
	 * listening
	 * to incoming pim messages irrelevant if the user has configured us
	 * for pim or not.
	 */
	if (pim_if_is_vrf_device(ifp)) {
		struct pim_interface *pim_ifp;

		if (!ifp->info) {
			pim_ifp = pim_if_new(ifp, false, false, false,
					     false /*vxlan_term*/);
			ifp->info = pim_ifp;
		}

		pim_sock_add(ifp);
	}

	if (!strncmp(ifp->name, PIM_VXLAN_TERM_DEV_NAME,
		     sizeof(PIM_VXLAN_TERM_DEV_NAME))) {
		if (pim->mcast_if_count < MAXVIFS)
			pim_vxlan_add_term_dev(pim, ifp);
		else
			zlog_warn(
				"%s: Cannot enable pim on %s. MAXVIFS(%d) reached. Deleting and readding the vxlan termimation device after unconfiguring pim from other interfaces may succeed.",
				__func__, ifp->name, MAXVIFS);
	}
#endif

	return 0;
}

static int pim_ifp_up(struct interface *ifp)
{
	uint32_t table_id;
	struct pim_interface *pim_ifp;
	struct pim_instance *pim;

	if (PIM_DEBUG_ZEBRA) {
		zlog_debug(
			"%s: %s index %d vrf %s(%u) flags %ld metric %d mtu %d operative %d",
			__func__, ifp->name, ifp->ifindex, ifp->vrf->name,
			ifp->vrf->vrf_id, (long)ifp->flags, ifp->metric,
			ifp->mtu, if_is_operative(ifp));
	}

	pim = ifp->vrf->info;

	pim_ifp = ifp->info;
	/*
	 * If we have a pim_ifp already and this is an if_add
	 * that means that we probably have a vrf move event
	 * If that is the case, set the proper vrfness.
	 */
	if (pim_ifp)
		pim_ifp->pim = pim;

	/*
	  pim_if_addr_add_all() suffices for bringing up both IGMP and
	  PIM
	*/
	pim_if_addr_add_all(ifp);

	/*
	 * If we have a pimreg device callback and it's for a specific
	 * table set the master appropriately
	 */
	if (sscanf(ifp->name, "" PIMREG "%" SCNu32, &table_id) == 1) {
		struct vrf *vrf;
		RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
			if ((table_id == vrf->data.l.table_id)
			    && (ifp->vrf->vrf_id != vrf->vrf_id)) {
				struct interface *master = if_lookup_by_name(
					vrf->name, vrf->vrf_id);

				if (!master) {
					zlog_debug(
						"%s: Unable to find Master interface for %s",
						__func__, vrf->name);
					return 0;
				}

				pim_zebra_interface_set_master(master, ifp);
				break;
			}
		}
	}

#if PIM_IPV == 4
	pim_autorp_add_ifp(ifp);
#endif

	pim_cand_addrs_changed();
	return 0;
}

static int pim_ifp_down(struct interface *ifp)
{
	if (PIM_DEBUG_ZEBRA) {
		zlog_debug(
			"%s: %s index %d vrf %s(%u) flags %ld metric %d mtu %d operative %d",
			__func__, ifp->name, ifp->ifindex, ifp->vrf->name,
			ifp->vrf->vrf_id, (long)ifp->flags, ifp->metric,
			ifp->mtu, if_is_operative(ifp));
	}

	if (!if_is_operative(ifp)) {
		pim_ifchannel_delete_all(ifp);
		/*
		  pim_if_addr_del_all() suffices for shutting down IGMP,
		  but not for shutting down PIM
		*/
		pim_if_addr_del_all(ifp);

		/*
		  pim_sock_delete() closes the socket, stops read and timer
		  threads,
		  and kills all neighbors.
		*/
		if (ifp->info) {
			pim_sock_delete(ifp, "link down");
		}
	}

	if (ifp->info) {
		pim_if_del_vif(ifp);
		pim_ifstat_reset(ifp);
	}

#if PIM_IPV == 4
	pim_autorp_rm_ifp(ifp);
#endif

	pim_cand_addrs_changed();
	return 0;
}

static int pim_ifp_destroy(struct interface *ifp)
{
	if (PIM_DEBUG_ZEBRA) {
		zlog_debug(
			"%s: %s index %d vrf %s(%u) flags %ld metric %d mtu %d operative %d",
			__func__, ifp->name, ifp->ifindex, ifp->vrf->name,
			ifp->vrf->vrf_id, (long)ifp->flags, ifp->metric,
			ifp->mtu, if_is_operative(ifp));
	}

	if (!if_is_operative(ifp))
		pim_if_addr_del_all(ifp);

#if PIM_IPV == 4
	struct pim_instance *pim;

	pim = ifp->vrf->info;
	if (pim && pim->vxlan.term_if == ifp)
		pim_vxlan_del_term_dev(pim);
#endif

	return 0;
}

static int pim_if_new_hook(struct interface *ifp)
{
	return 0;
}

static int pim_if_delete_hook(struct interface *ifp)
{
	if (ifp->info)
		pim_if_delete(ifp);

	return 0;
}

void pim_iface_init(void)
{
	hook_register_prio(if_add, 0, pim_if_new_hook);
	hook_register_prio(if_del, 0, pim_if_delete_hook);

	hook_register_prio(if_real, 0, pim_ifp_create);
	hook_register_prio(if_up, 0, pim_ifp_up);
	hook_register_prio(if_down, 0, pim_ifp_down);
	hook_register_prio(if_unreal, 0, pim_ifp_destroy);
}

static void pim_if_membership_clear(struct interface *ifp)
{
	struct pim_interface *pim_ifp;

	pim_ifp = ifp->info;
	assert(pim_ifp);

	if (pim_ifp->pim_enable && pim_ifp->gm_enable)
		return;

	pim_ifchannel_membership_clear(ifp);
}

void pim_pim_interface_delete(struct interface *ifp)
{
	struct pim_interface *pim_ifp = ifp->info;

	if (!pim_ifp)
		return;

#if PIM_IPV == 4
	pim_autorp_rm_ifp(ifp);
#endif

	pim_ifp->pim_enable = false;

	pim_if_membership_clear(ifp);

	/*
	 * pim_sock_delete() removes all neighbors from
	 * pim_ifp->pim_neighbor_list.
	 */
	pim_sock_delete(ifp, "pim unconfigured on interface");
	pim_upstream_nh_if_update(pim_ifp->pim, ifp);

	if (!pim_ifp->gm_enable) {
		pim_if_addr_del_all(ifp);
		pim_if_delete(ifp);
	}
}

void pim_gm_interface_delete(struct interface *ifp)
{
	struct pim_interface *pim_ifp = ifp->info;

	if (!pim_ifp)
		return;

	pim_ifp->gm_enable = false;

	pim_if_membership_clear(ifp);

#if PIM_IPV == 4
	igmp_sock_delete_all(ifp);
#else
	gm_ifp_teardown(ifp);
#endif

	if (!pim_ifp->pim_enable)
		pim_if_delete(ifp);
}
