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

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

#include "log.h"
#include "privs.h"
#include "if.h"
#include "prefix.h"
#include "vty.h"
#include "plist.h"
#include "sockopt.h"
#include "lib_errors.h"
#include "lib/network.h"

#include "pimd.h"
#include "pim_rpf.h"
#include "pim_zebra.h"
#include "pim_mroute.h"
#include "pim_oil.h"
#include "pim_str.h"
#include "pim_time.h"
#include "pim_iface.h"
#include "pim_macro.h"
#include "pim_rp.h"
#include "pim_oil.h"
#include "pim_register.h"
#include "pim_ifchannel.h"
#include "pim_zlookup.h"
#include "pim_ssm.h"
#include "pim_dm.h"
#include "pim_sock.h"
#include "pim_vxlan.h"
#include "pim_msg.h"
#include "pim_pim.h"
#include "pim_join.h"
#include "pim_state_refresh.h"
#include "pim_util.h"
#include "pim_nht.h"
#include "pim_static.h"
#include "pim_upstream.h"

static void mroute_read_on(struct pim_instance *pim);

/*
 * Set when the first IGMPMSG_WRVIFWHOLE upcall is received on this host.
 * Kernel-wide capability shared by all pim_instance / VRFs in this process.
 *
 * On Linux, starts false after every daemon restart; the first WRVIFWHOLE
 * upcall sets this true and disables compensation.  Until then WRONGVIF may
 * briefly trigger the compat path on capable kernels (harmless overlap).
 *
 * On non-Linux, WRVIFWHOLE is unavailable so initialize true to keep the
 * original WRONGVIF behavior (no compensation).
 */
static bool mroute_wrvifwhole_supported =
#if defined linux
	false;
#else
	true;
#endif

int pim_mroute_set(struct pim_instance *pim, int enable)
{
	int err;
	int opt, data;
	socklen_t data_len = sizeof(data);

	/*
	 * We need to create the VRF table for the pim mroute_socket
	 */
	if (enable && pim->vrf->vrf_id != VRF_DEFAULT) {
		frr_with_privs (&pimd_privs) {

			data = pim->vrf->data.l.table_id;
			err = setsockopt(pim->mroute_socket, PIM_IPPROTO,
					 MRT_TABLE, &data, data_len);
			if (err) {
				if (err == ENOPROTOOPT)
					zlog_err("%s Kernel is not compiled with CONFIG_IP_MROUTE_MULTIPLE_TABLES and vrf's will not work",
						 __func__);
				else
					zlog_warn("%s %s: failure: setsockopt(fd=%d,PIM_IPPROTO, MRT_TABLE=%d): errno=%d: %s",
						  __FILE__, __func__,
						  pim->mroute_socket, data,
						  errno, safe_strerror(errno));
				return -1;
			}
		}
	}

	frr_with_privs (&pimd_privs) {
		opt = enable ? MRT_INIT : MRT_DONE;
		/*
		 * *BSD *cares* about what value we pass down
		 * here
		 */
		data = 1;
		err = setsockopt(pim->mroute_socket, PIM_IPPROTO, opt, &data,
				 data_len);
		if (err) {
			zlog_warn(
				"%s %s: failure: setsockopt(fd=%d,PIM_IPPROTO,%s=%d): errno=%d: %s",
				__FILE__, __func__, pim->mroute_socket,
				enable ? "MRT_INIT" : "MRT_DONE", data, errno,
				safe_strerror(errno));
			return -1;
		}
	}

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

#if PIM_IPV == 6
	if (enable) {
		/* Linux and Solaris IPV6_PKTINFO */
		data = 1;
		if (setsockopt(pim->mroute_socket, PIM_IPPROTO,
			       IPV6_RECVPKTINFO, &data, data_len)) {
			zlog_warn(
				"Could not set IPV6_RECVPKTINFO on socket fd=%d: errno=%d: %s",
				pim->mroute_socket, errno,
				safe_strerror(errno));
		}
	}
#endif
	setsockopt_so_recvbuf(pim->mroute_socket, 1024 * 1024 * 8);

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

	if (enable) {
#if defined linux
		/*
		 * MRT_PIM value selects the highest PIM upcall type delivered.
		 * GMMSG_WRVIFWHOLE (4) enables WRONGVIF and WRVIFWHOLE; kernels
		 * before 4.19 only deliver WRONGVIF.  To test the WRONGVIF
		 * compensation path on a modern kernel, temporarily use
		 * GMMSG_WRONGVIF here instead (PIM + assert, no WRVIFWHOLE).
		 */
		int upcalls = GMMSG_WRVIFWHOLE;
		opt = MRT_PIM;

		err = setsockopt(pim->mroute_socket, PIM_IPPROTO, opt, &upcalls,
				 sizeof(upcalls));
		if (err) {
			zlog_warn(
				"Failure to register for VIFWHOLE and WRONGVIF upcalls %d %s",
				errno, safe_strerror(errno));
			return -1;
		}
#else
		zlog_warn(
			"PIM-SM will not work properly on this platform, until the ability to receive the WRVIFWHOLE upcall");
#endif
	}

	return 0;
}

static const char *const gmmsgtype2str[GMMSG_WRVIFWHOLE + 1] = {
	"<unknown_upcall?>", "NOCACHE", "WRONGVIF", "WHOLEPKT", "WRVIFWHOLE"};

/*
 * RFC 4601 section 4.2: on NOCACHE, forward using existing (S,G) state when
 * iif == RPF_interface(S) and local receivers exist.  Register encapsulation
 * requires DirectlyConnected(S) at the DR and is handled elsewhere.
 */
static void pim_mroute_nocache_forward_existing(struct interface *ifp, pim_sgaddr *sg)
{
	struct pim_interface *pim_ifp = ifp->info;
	struct pim_instance *pim = pim_ifp->pim;
	struct pim_upstream *up;
	struct pim_nexthop rpf_nh;
	struct prefix grp;
	struct pim_rpf old;

	up = pim_upstream_find(pim, sg);
	if (!up)
		return;

	memset(&rpf_nh, 0, sizeof(rpf_nh));
	pim_addr_to_prefix(&grp, sg->grp);

	/*
	 * Live RPF lookup honors MRIB static routes (ip mroute PREFIX NEXTHOP)
	 * instead of stale upstream RPF cached from URIB-only resolution.
	 * Prefer the kernel ingress interface when it is a valid nexthop (e.g.
	 * tunnel vs underlying WAN with duplicate addressing).
	 */
	if (!pim_nht_lookup_ecmp(pim, &rpf_nh, sg->src, &grp, false, ifp)) {
		if (PIM_DEBUG_MROUTE_DETAIL)
			zlog_debug("%s: %pSG NOCACHE on %s, no RPF route to source", __func__, sg,
				   ifp->name);
		return;
	}

	if (!rpf_nh.interface || rpf_nh.interface->ifindex != ifp->ifindex) {
		if (PIM_DEBUG_MROUTE_DETAIL)
			zlog_debug("%s: %pSG NOCACHE on %s, RPF interface is %s", __func__, sg,
				   ifp->name, rpf_nh.interface ? rpf_nh.interface->name : "(none)");
		return;
	}

	if (up->rpf.source_nexthop.interface != rpf_nh.interface ||
	    pim_addr_cmp(up->rpf.source_nexthop.mrib_nexthop_addr, rpf_nh.mrib_nexthop_addr)) {
		enum pim_rpf_result rpf_result;

		memset(&old, 0, sizeof(old));
		rpf_result = pim_rpf_update(pim, up, &old, ifp, __func__);
		if (rpf_result == PIM_RPF_CHANGED ||
		    (rpf_result == PIM_RPF_FAILURE && old.source_nexthop.interface))
			pim_zebra_upstream_rpf_changed(pim, up, &old);
	}

	pim_upstream_inherited_olist_decide(pim, up);
	if (pim_upstream_empty_inherited_olist(up)) {
		if (PIM_DEBUG_MROUTE_DETAIL)
			zlog_debug("%s: %pSG NOCACHE on %s, no local receivers", __func__, sg,
				   ifp->name);
		return;
	}

	if (up->sptbit != PIM_UPSTREAM_SPTBIT_TRUE)
		pim_upstream_set_sptbit(up, ifp);

	PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags);
	up->channel_oil->cc.pktcnt++;

	pim_upstream_update_join_desired(pim, up);

	if (pim_upstream_kat_start_ok(up))
		pim_upstream_keep_alive_timer_start(up, pim->keep_alive_time);

	pim_upstream_mroute_add(up->channel_oil, __func__);
}

int pim_mroute_msg_nocache(int fd, struct interface *ifp, const kernmsg *msg)
{
	struct pim_interface *pim_ifp = ifp->info;
	struct pim_upstream *up = NULL;
	pim_sgaddr sg;
	bool desync = false;
	bool update_oil = false;
	bool dodense = false;
	int flags = PIM_UPSTREAM_FLAG_MASK_SRC_STREAM;
	struct interface *ifp2 = NULL;
	struct pim_interface *pim_ifp2 = NULL;

	memset(&sg, 0, sizeof(sg));
	sg.src = msg->msg_im_src;
	sg.grp = msg->msg_im_dst;

	if (!pim_ifp || !pim_ifp->pim_enable) {
		if (PIM_DEBUG_MROUTE)
			zlog_debug(
				"%s: %s on interface, dropping packet to %pSG",
				ifp->name,
				!pim_ifp ? "Multicast not enabled"
					 : "PIM not enabled",
				&sg);
		return 0;
	}

	/*
	 * "ip mroute OIF GROUP [SOURCE]" on this interface (ingress/IIF).
	 * Static MFC overrides PIM dense/sparse NOCACHE handling.
	 */
	if (pim_static_nocache_resolve(pim_ifp->pim, ifp, &sg) != 1)
		return 0;

	if (!pim_is_grp_ssm(pim_ifp->pim, sg.grp)) {
		/* for ASM, check that we have enough information (i.e. path
		 * to RP) to make a decision on what to do with this packet.
		 *
		 * for SSM, this is meaningless, everything is join-driven,
		 * and for NOCACHE we need to install an empty OIL MFC entry
		 * so the kernel doesn't keep nagging us.
		 */
		struct pim_rpf *rpg = NULL;

		/* Not an SSM group and configured for SSM only, just return */
		if (pim_ifp->pim_mode == PIM_MODE_SSM)
			return 0;

#if PIM_IPV == 6
		pim_addr embedded_rp;

		if (pim_ifp->pim->embedded_rp.enable &&
		    pim_embedded_rp_extract(&sg.grp, &embedded_rp) &&
		    !pim_embedded_rp_filter_match(pim_ifp->pim, &sg.grp))
			pim_embedded_rp_new(pim_ifp->pim, &sg.grp, &embedded_rp);
#endif /* PIM_IPV == 6 */

		/* We only care to find an RP if in a sparse mode */
		if (HAVE_SPARSE_MODE(pim_ifp->pim_mode)) {
			rpg = RP(pim_ifp->pim, msg->msg_im_dst);
			if (!rpg) {
				if (PIM_DEBUG_MROUTE)
					zlog_debug("%s: no RPF for packet to %pSG", ifp->name, &sg);
			} else if (pim_rpf_addr_is_inaddr_any(rpg)) {
				if (PIM_DEBUG_MROUTE)
					zlog_debug("%s: null RPF for packet to %pSG", ifp->name,
						   &sg);
				rpg = NULL;
			}
		}

		if (HAVE_DENSE_MODE(pim_ifp->pim_mode)) {
			/* If in a dense mode, we may need to set up flooding routes */
			if (pim_ifp->pim_mode == PIM_MODE_DENSE) {
				/* Dense only, set up flooding */
				dodense = true;
			} else if (pim_ifp->pim_mode == PIM_MODE_SPARSE_DENSE) {
				if (rpg) {
					/* Sparse-dense but we have an RP, no flooding */
					dodense = false;
				} else {
					/* Sparse-dense and no RP, set up flooding */
					dodense = true;
				}
			}
		}

		if (!dodense && !rpg)
			return 0;
	}


	if (dodense) {
		/*
		For proper dense handling....

		Non-ssm group packet, coming in on any interface, that has no route i.e. what we hare handling here

		Find or add upstream
			and set up forwarding to all dense interfaces
			and forward to all sparse-dense interfaces, if no RP
			and all passive interfaces, if joined and no RP
		*/

		/* Make sure the group is allowed in dense mode */
		if (!pim_is_dm_prefix_filter(pim_ifp->pim, sg.grp))
			return 0;

		/* Only handle groups not filtered */
		/* TODO: Should we still install a "blackhole" mroute to stop this upcall then?? */
		if (pim_is_group_filtered(pim_ifp, &sg.grp, &sg.src))
			return 0;

		if (pim_if_connected_to_source(ifp, msg->msg_im_src))
			flags |= PIM_UPSTREAM_FLAG_MASK_FHR;
		{
			struct pim_upstream *pre_up;
			bool mfc_was_installed;

			pre_up = pim_upstream_find(pim_ifp->pim, &sg);
			mfc_was_installed = pre_up && pre_up->channel_oil->installed;
			if (pre_up)
				pim_upstream_ref(pre_up, flags, __func__);
			else
				pre_up = pim_upstream_add(pim_ifp->pim, &sg, ifp, flags, __func__,
							  NULL);
			up = pre_up;

			/*
			 * Avoid false "resync" on first NOCACHE: find_or_add may
			 * install MFC in the same call, so installed is expected.
			 * Only resync when MFC was already present before this upcall
			 * (duplicate NOCACHE or kernel/user mismatch).
			 */
			if (up->channel_oil->installed && mfc_was_installed) {
				zlog_warn("%s: NOCACHE [%s] %s: %pSG MFC already installed before this upcall; resyncing (duplicate kernel upcall or transient MFC drop)",
					  __func__, pim_ifp->pim->vrf->name, ifp->name, &sg);
				desync = true;
			}
		}

		pim_upstream_keep_alive_timer_start(up, pim_ifp->pim->keep_alive_time);

		up->channel_oil->cc.pktcnt++;

		/* Detect if the rpf for the source is different from the interface the packet was received on.
		 * In this case, we need to prune immediately
		 */
		if (!up->rpf.source_nexthop.interface ||
		    up->rpf.source_nexthop.interface->ifindex != ifp->ifindex) {
			if (PIM_DEBUG_PIM_J_P)
				zlog_debug("%s: Dense Mode WRONGVIF on %s for (S,G)=%pSG",
					   __func__, ifp->name, &sg);
			pim_dm_wrongif(ifp, sg, up);
			return 0;
		}

		/* resolve mfcc_parent prior to mroute_add in channel_add_oif */
		if (up->rpf.source_nexthop.interface &&
		    *oil_incoming_vif(up->channel_oil) >= MAXVIFS) {
			pim_upstream_mroute_iif_update(up->channel_oil, __func__);
		}

		FOR_ALL_INTERFACES (pim_ifp->pim->vrf, ifp2) {
			struct pim_ifchannel *ch_flood, *ch_flood_rpt;

			pim_ifp2 = ifp2->info;

			/* Make sure the interface has PIM enabled and is not the current interface and is a dense type mode */
			if (!pim_ifp2 || !pim_ifp2->pim_enable || ifp2->ifindex == ifp->ifindex ||
			    !HAVE_DENSE_MODE(pim_ifp2->pim_mode))
				continue;

			/* Flood if has neighbors or IGMP join */
			if (pim_ifp2->pim_neighbor_list->count ||
			    pim_gm_has_igmp_join(ifp2, sg.grp)) {
				pim_ifchannel_find(ifp2, &sg, &ch_flood, &ch_flood_rpt);
				if (ch_flood) {
					if (PIM_UPSTREAM_DM_TEST_PRUNE(ch_flood->flags))
						continue;
					if (pim_macro_ch_lost_assert(ch_flood))
						continue;
				}
				oil_if_set(up->channel_oil, pim_ifp2->mroute_vif_index, 1);
				update_oil = true;
			}
		}

		if (update_oil || desync) {
			PIM_UPSTREAM_DM_SET_INTERFACE(up->flags);
			PIM_UPSTREAM_FLAG_UNSET_USE_RPT(up->flags);
			PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED(up->flags);
			pim_upstream_mroute_update(up->channel_oil, __func__);
		}

		/* If the router is an originator send state refresh */
		if (update_oil && PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) {
			up->pim->staterefresh_counter = 0;
			pim_send_staterefresh(up);
			staterefresh_timer_start(up);
		}

		/* We have no where to flood this traffic, prune it */
		if (!update_oil) {
			PIM_UPSTREAM_DM_SET_PRUNE(up->flags);
			pim_dm_prune_send(up->rpf, up, 0);
			prune_timer_start(up);
		}

		return 0;
	}

	/* All further processing is for SSM or SM with an RP only */

	if (!pim_if_connected_to_source(ifp, msg->msg_im_src)) {
		if (PIM_DEBUG_MROUTE)
			zlog_debug(
				"%s: incoming packet to %pSG from non-connected source",
				ifp->name, &sg);
		pim_mroute_nocache_forward_existing(ifp, &sg);
		return 0;
	}

	/* TODO we can optimize to not do this if we set up a dense mode route */
	if (!(PIM_I_am_DR(pim_ifp))) {
		/* unlike the other debug messages, this one is further in the
		 * "normal operation" category and thus under _DETAIL
		 */
		if (PIM_DEBUG_MROUTE_DETAIL)
			zlog_debug(
				"%s: not DR on interface, not forwarding traffic for %pSG",
				ifp->name, &sg);

		/*
		 * We are not the DR, but we are still receiving packets
		 * Let's blackhole those packets for the moment
		 * As that they will be coming up to the cpu
		 * and causing us to consider them.
		 *
		 * These will be cleared if/when we are elected DR
		 */

		up = pim_upstream_find_or_add(
			&sg, ifp, PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE, __func__);
		pim_upstream_mroute_add(up->channel_oil, __func__);

		return 0;
	}

	/* We may have already found the upstream as part of dense mode processing */
	{
		struct pim_upstream *pre_up;
		bool mfc_was_installed;

		pre_up = pim_upstream_find(pim_ifp->pim, &sg);
		mfc_was_installed = pre_up && pre_up->channel_oil->installed;
		if (pre_up)
			pim_upstream_ref(pre_up, PIM_UPSTREAM_FLAG_MASK_FHR, __func__);
		else
			pre_up = pim_upstream_add(pim_ifp->pim, &sg, ifp,
						  PIM_UPSTREAM_FLAG_MASK_FHR, __func__, NULL);
		up = pre_up;

		if (up->channel_oil->installed && mfc_was_installed) {
			zlog_warn("%s: NOCACHE [%s] %s: %pSG MFC already installed before this upcall; resyncing (duplicate kernel upcall or transient MFC drop)",
				  __func__, pim_ifp->pim->vrf->name, ifp->name, &sg);
			desync = true;
		}
	}

	PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags);
	pim_upstream_keep_alive_timer_start(up, pim_ifp->pim->keep_alive_time);

	up->channel_oil->cc.pktcnt++;

	/* resolve mfcc_parent prior to mroute_add in channel_add_oif */
	if (up->rpf.source_nexthop.interface && *oil_incoming_vif(up->channel_oil) >= MAXVIFS) {
		pim_upstream_mroute_iif_update(up->channel_oil, __func__);
	}

	/*
	 * I moved this debug till after the actual add because
	 * I want to take advantage of the up->sg_str being filled in.
	 */
	if (PIM_DEBUG_MROUTE)
		zlog_debug("%s: Adding a Route %s for WHOLEPKT consumption", __func__, up->sg_str);

	if (!pim_is_group_filtered(pim_ifp, &sg.grp, &sg.src))
		pim_register_join(up);

	/*
	 * if we have receiver, inherit from parent.
	 * install routes as needed for all cases (sm/dm)
	 */
	pim_upstream_inherited_olist_decide(pim_ifp->pim, up);
	pim_upstream_update_join_desired(pim_ifp->pim, up);

	/* we just got NOCACHE from the kernel, so...  MFC is not in the
	 * kernel for some reason or another.  Try installing again.
	 */
	if (desync)
		pim_upstream_mroute_update(up->channel_oil, __func__);

	return 0;
}

int pim_mroute_msg_wholepkt(int fd, struct interface *ifp, const char *buf,
			    size_t len)
{
	struct pim_interface *pim_ifp;
	pim_sgaddr sg;
	struct pim_rpf *rpg;
	const ipv_hdr *ip_hdr;
	struct pim_upstream *up;

	pim_ifp = ifp->info;

	ip_hdr = (const ipv_hdr *)buf;

	memset(&sg, 0, sizeof(sg));
	sg.src = IPV_SRC(ip_hdr);
	sg.grp = IPV_DST(ip_hdr);

	up = pim_upstream_find(pim_ifp->pim, &sg);
	if (!up) {
		pim_sgaddr star = sg;

		star.src = PIMADDR_ANY;

		up = pim_upstream_find(pim_ifp->pim, &star);

		if (up && PIM_UPSTREAM_FLAG_TEST_CAN_BE_LHR(up->flags)) {
			struct connected *src_conn = NULL;
			struct pim_interface *src_pim_ifp = NULL;

			/*
			 * Now let's test to see if this also can be
			 * a FHR as well, As that this is a possibility
			 * and if it is we must treat it as such.
			 */
			src_conn = if_lookup_address(&sg.src, PIM_AF, pim_ifp->pim->vrf->vrf_id);

			if (src_conn && src_conn->ifp->info)
				src_pim_ifp = src_conn->ifp->info;

			if (src_conn != NULL && src_pim_ifp && src_pim_ifp->pim_enable) {
				/*
				 * Same rationale as pim_mroute_msg_wrvifwhole():
				 * only the DR on the source-connected interface is
				 * the FHR. On a non-DR, install as SRC_NOCACHE to
				 * avoid setting the FHR flag, which would later
				 * cause pim_upstream_switch() to silently attach
				 * pimreg to the OIF and CPU-punt every packet.
				 */
				int up_flags = PIM_UPSTREAM_FLAG_MASK_FHR;

				if (!PIM_I_am_DR(src_pim_ifp))
					up_flags = PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE;

				up = pim_upstream_add(pim_ifp->pim, &sg, src_conn->ifp, up_flags,
						      __func__, NULL);
				PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags);
				pim_upstream_keep_alive_timer_start(up,
								    pim_ifp->pim->keep_alive_time);
				pim_upstream_inherited_olist(pim_ifp->pim, up);
				pim_upstream_update_join_desired(pim_ifp->pim, up);
				if (!pim_is_group_filtered(pim_ifp, &sg.grp, &sg.src) &&
				    pim_upstream_could_register(up))
					pim_register_join(up);
				if (PIM_DEBUG_MROUTE)
					zlog_debug("%s: Treat WHOLEPKT on pimreg as FHR for %pSG",
						   __func__, &sg);
				goto have_up;
			} else {
				up = pim_upstream_add(pim_ifp->pim, &sg, ifp,
						      PIM_UPSTREAM_FLAG_MASK_SRC_LHR, __func__,
						      NULL);
				pim_upstream_keep_alive_timer_start(up,
								    pim_ifp->pim->keep_alive_time);
				pim_upstream_inherited_olist(pim_ifp->pim, up);
				pim_upstream_update_join_desired(pim_ifp->pim, up);

				if (PIM_DEBUG_MROUTE)
					zlog_debug("%s: Creating %s upstream on LHR", __func__,
						   up->sg_str);
				return 0;
			}
		}
		if (PIM_DEBUG_MROUTE_DETAIL) {
			zlog_debug(
				"%s: Unable to find upstream channel WHOLEPKT%pSG",
				__func__, &sg);
		}
		return 0;
	}

have_up:

	if (!up->rpf.source_nexthop.interface) {
		if (PIM_DEBUG_PIM_TRACE)
			zlog_debug("%s: up %s RPF is not present", __func__,
				   up->sg_str);
		return 0;
	}

	pim_ifp = up->rpf.source_nexthop.interface->info;

	rpg = pim_ifp ? RP(pim_ifp->pim, sg.grp) : NULL;

	if ((pim_rpf_addr_is_inaddr_any(rpg)) || (!pim_ifp) ||
	    (!(PIM_I_am_DR(pim_ifp)))) {
		if (PIM_DEBUG_MROUTE) {
			zlog_debug("%s: Failed Check send packet", __func__);
		}
		return 0;
	}

	/*
	 * If we've received a register suppress
	 */
	if (!event_is_scheduled(up->t_rs_timer)) {
		if (pim_is_grp_ssm(pim_ifp->pim, sg.grp)) {
			if (PIM_DEBUG_PIM_REG)
				zlog_debug(
					"%pSG register forward skipped as group is SSM",
					&sg);
			return 0;
		}

		if (!PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) {
			if (PIM_DEBUG_PIM_REG)
				zlog_debug(
					"%s register forward skipped, not FHR",
					up->sg_str);
			return 0;
		}

		pim_register_send((uint8_t *)buf + sizeof(ipv_hdr),
				  len - sizeof(ipv_hdr),
				  pim_ifp->primary_address, rpg, 0, up);
	}
	return 0;
}

/*
 * pim_upstream_activate_stream - drive FHR upstream state for a newly-seen
 * (S,G) data flow when the kernel has not delivered IGMPMSG_WRVIFWHOLE.
 *
 * Mirrors the FHR activation block in pim_mroute_msg_wrvifwhole() (without
 * register encapsulation, which requires the packet buffer).  Only called
 * from pim_mroute_msg_wrongvif() when !mroute_wrvifwhole_supported and
 * there is no (S,G) ifchannel on the ingress interface (including the
 * bidirectional case where only a (*,G) ifchannel exists).
 *
 * The caller guarantees:
 *   - ifp and ifp->info are non-NULL and PIM-enabled
 *   - sg is the (S,G) extracted from the upcall message
 *   - ifp is source-connected for sg->src
 */
static int pim_upstream_activate_stream(struct interface *ifp, pim_sgaddr *sg)
{
	struct pim_interface *pim_ifp = ifp->info;
	struct pim_upstream *up;

	up = pim_upstream_find(pim_ifp->pim, sg);
	if (!up) {
		up = pim_upstream_add(pim_ifp->pim, sg, ifp, PIM_UPSTREAM_FLAG_MASK_FHR, __func__,
				      NULL);
		if (!up)
			return -1;
	} else if (!PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) {
		/*
		 * Upstream may already exist from join-before-data without
		 * FHR (e.g. partial MFC).  Promote once; do not call ref on
		 * every WRONGVIF as that would inflate ref_count.
		 */
		pim_upstream_ref(up, PIM_UPSTREAM_FLAG_MASK_FHR, __func__);
		PIM_UPSTREAM_FLAG_UNSET_USE_RPT(up->flags);
	}

	PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags);
	pim_upstream_keep_alive_timer_start(up, pim_ifp->pim->keep_alive_time);
	up->channel_oil->cc.pktcnt++;

	/* resolve mfcc_parent prior to mroute_add in channel_add_oif */
	if (up->rpf.source_nexthop.interface && *oil_incoming_vif(up->channel_oil) >= MAXVIFS)
		pim_upstream_mroute_iif_update(up->channel_oil, __func__);

	if (!pim_is_group_filtered(pim_ifp, &sg->grp, &sg->src) && pim_upstream_could_register(up))
		pim_register_join(up);

	pim_upstream_inherited_olist(pim_ifp->pim, up);

	if (!up->channel_oil->installed)
		pim_upstream_mroute_add(up->channel_oil, __func__);
	else
		pim_upstream_mroute_iif_update(up->channel_oil, __func__);

	return 0;
}

/*
 * pim_upstream_wrongvif_wrvifwhole_compat - mirror pim_mroute_msg_wrvifwhole()
 * when IGMPMSG_WRVIFWHOLE is unavailable.  Handles LHR / SPT (S,G) state and
 * MFC reinstall on WRONGVIF where there is no (S,G) ifchannel on ingress.
 * Register encapsulation is omitted (no packet buffer on WRONGVIF).
 */
static int pim_upstream_wrongvif_wrvifwhole_compat(struct interface *ifp, pim_sgaddr *sg)
{
	struct pim_interface *pim_ifp = ifp->info;
	struct pim_instance *pim = pim_ifp->pim;
	struct pim_upstream *up;
	struct pim_upstream *parent;
	pim_sgaddr star_g;

	up = pim_upstream_find(pim, sg);
	if (up) {
		struct pim_nexthop source;
		struct pim_rpf *rpf = RP(pim, sg->grp);
		struct pim_interface *rpf_pim_ifp;

		if (!rpf || !rpf->source_nexthop.interface || !rpf->source_nexthop.interface->info)
			return 0;

		star_g = *sg;
		star_g.src = PIMADDR_ANY;
		parent = pim_upstream_find(pim, &star_g);
		if (parent && parent->rpf.source_nexthop.interface == ifp)
			return 0;

		rpf_pim_ifp = rpf->source_nexthop.interface->info;
		memset(&source, 0, sizeof(source));

		if (!PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) {
			if (!pim_addr_is_any(up->upstream_register) &&
			    pim_nht_lookup(rpf_pim_ifp->pim, &source, up->upstream_register,
					   up->sg.grp, false)) {
				pim_register_stop_send(source.interface, sg,
						       rpf_pim_ifp->primary_address,
						       up->upstream_register);
				up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE;
			}

			pim_upstream_inherited_olist(pim, up);
		} else {
			if (I_am_RP(rpf_pim_ifp->pim, up->sg.grp)) {
				if (pim_nht_lookup(rpf_pim_ifp->pim, &source,
						   up->upstream_register, up->sg.grp, false))
					pim_register_stop_send(source.interface, sg,
							       rpf_pim_ifp->primary_address,
							       up->upstream_register);
				up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE;
			} else {
				if (up->sptbit != PIM_UPSTREAM_SPTBIT_TRUE && up->parent &&
				    up->rpf.source_nexthop.interface !=
					    up->parent->rpf.source_nexthop.interface) {
					up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE;
					pim_jp_agg_single_upstream_send(&up->parent->rpf,
									up->parent, true);
				}
			}
			pim_upstream_keep_alive_timer_start(up, pim->keep_alive_time);
			pim_upstream_inherited_olist(pim, up);
		}

		if (!up->channel_oil->installed)
			pim_upstream_mroute_add(up->channel_oil, __func__);
		else
			pim_upstream_mroute_iif_update(up->channel_oil, __func__);

		return 0;
	}

	if (pim_if_connected_to_source(ifp, sg->src) && PIM_I_am_DR(pim_ifp))
		return pim_upstream_activate_stream(ifp, sg);

	up = pim_upstream_add(pim, sg, ifp, PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE, __func__, NULL);
	if (!up)
		return -1;
	if (!up->channel_oil->installed)
		pim_upstream_mroute_add(up->channel_oil, __func__);

	return 0;
}

/*
 * On WRONGVIF, realign upstream RPF and MFC iif with the kernel ingress
 * interface when it is a valid nexthop (e.g. passive GRE tunnel vs parent
 * with duplicate addressing).  Mirrors pim_mroute_nocache_forward_existing().
 *
 * Returns 0 when (S,G) state was updated and MFC iif was reconciled, -1
 * when nothing was done (no upstream, ingress not valid RPF, etc.).
 */
static int pim_mroute_wrongvif_prefer_ingress(struct interface *ifp, pim_sgaddr *sg)
{
	struct pim_interface *pim_ifp = ifp->info;
	struct pim_instance *pim = pim_ifp->pim;
	struct pim_upstream *up;
	struct pim_nexthop rpf_nh;
	struct prefix grp;
	struct pim_rpf old;
	vifi_t ingress_vif;
	bool force_connected_ingress = false;

	up = pim_upstream_find(pim, sg);
	if (!up)
		return -1;

	memset(&rpf_nh, 0, sizeof(rpf_nh));
	pim_addr_to_prefix(&grp, sg->grp);

	/*
	 * Live RPF lookup with ingress preference (neighbor_needed=false so
	 * passive receive interfaces are accepted when routing selects them).
	 */
	if (!pim_nht_lookup_ecmp(pim, &rpf_nh, sg->src, &grp, false, ifp)) {
		if (PIM_DEBUG_MROUTE_DETAIL)
			zlog_debug("%s: %pSG WRONGVIF on %s, no RPF route to source", __func__, sg,
				   ifp->name);
		return -1;
	}

	if (!rpf_nh.interface || rpf_nh.interface->ifindex != ifp->ifindex) {
		/*
		 * Overlapping connected prefixes (e.g. parent secondary vs
		 * PIM-passive tunnel covering the same source): zebra may
		 * still select the parent as RPF while data arrives on the
		 * tunnel.  If the kernel ingress is itself connected to S,
		 * force RPF/MFC iif onto that ingress so WRONGVIF can clear.
		 */
		if (pim_if_connected_to_source(ifp, sg->src)) {
			force_connected_ingress = true;
			if (PIM_DEBUG_MROUTE_DETAIL)
				zlog_debug("%s: %pSG WRONGVIF on %s, forcing connected ingress (RPF is %s)",
					   __func__, sg, ifp->name,
					   rpf_nh.interface ? rpf_nh.interface->name : "(none)");
		} else {
			if (PIM_DEBUG_MROUTE_DETAIL)
				zlog_debug("%s: %pSG WRONGVIF on %s, ingress is not RPF interface (RPF is %s)",
					   __func__, sg, ifp->name,
					   rpf_nh.interface ? rpf_nh.interface->name : "(none)");
			return -1;
		}
	}

	ingress_vif = pim_ifp->mroute_vif_index;
	if (up->channel_oil->installed && up->rpf.source_nexthop.interface == ifp &&
	    *oil_incoming_vif(up->channel_oil) == ingress_vif)
		/*
		 * Already aligned with kernel ingress.  Treat as handled so
		 * the caller does not fall through to Assert on a (*,G)
		 * ifchannel that cannot win CouldAssert.
		 */
		return 0;

	if (force_connected_ingress) {
		memset(&old, 0, sizeof(old));
		old = up->rpf;
		pim_upstream_fill_static_iif(up, ifp);
		if (old.source_nexthop.interface != ifp)
			pim_zebra_upstream_rpf_changed(pim, up, &old);
	} else if (up->rpf.source_nexthop.interface != rpf_nh.interface ||
		   pim_addr_cmp(up->rpf.source_nexthop.mrib_nexthop_addr,
				rpf_nh.mrib_nexthop_addr)) {
		enum pim_rpf_result rpf_result;

		memset(&old, 0, sizeof(old));
		rpf_result = pim_rpf_update(pim, up, &old, ifp, __func__);
		if (rpf_result == PIM_RPF_CHANGED ||
		    (rpf_result == PIM_RPF_FAILURE && old.source_nexthop.interface))
			pim_zebra_upstream_rpf_changed(pim, up, &old);
	}

	pim_upstream_inherited_olist_decide(pim, up);
	if (pim_upstream_empty_inherited_olist(up)) {
		if (PIM_DEBUG_MROUTE_DETAIL)
			zlog_debug("%s: %pSG WRONGVIF on %s, no local receivers", __func__, sg,
				   ifp->name);
		/*
		 * Handled: do not fall through to WRVIFWHOLE compat, which can
		 * install/update MFC without this empty-OIL guard.
		 */
		return 0;
	}

	if (up->sptbit != PIM_UPSTREAM_SPTBIT_TRUE)
		pim_upstream_set_sptbit(up, ifp);

	PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags);
	up->channel_oil->cc.pktcnt++;

	pim_upstream_update_join_desired(pim, up);

	if (pim_upstream_kat_start_ok(up))
		pim_upstream_keep_alive_timer_start(up, pim->keep_alive_time);

	if (!up->channel_oil->installed)
		pim_upstream_mroute_add(up->channel_oil, __func__);
	else
		pim_upstream_mroute_iif_update(up->channel_oil, __func__);

	if (PIM_DEBUG_MROUTE)
		zlog_debug("%s: %pSG WRONGVIF on %s, realigned MFC iif to ingress%s", __func__, sg,
			   ifp->name, force_connected_ingress ? " (connected)" : "");

	return 0;
}

int pim_mroute_msg_wrongvif(int fd, struct interface *ifp, const kernmsg *msg)
{
	struct pim_ifchannel *ch, *throwaway;
	struct pim_interface *pim_ifp;
	bool sg_channel = false;
	bool any_channel = false;
	pim_sgaddr sg;

	memset(&sg, 0, sizeof(sg));
	sg.src = msg->msg_im_src;
	sg.grp = msg->msg_im_dst;

	/*
	  Send Assert(S,G) on iif as response to WRONGVIF kernel upcall.

	  RFC 4601 4.8.2.  PIM-SSM-Only Routers

	  iif is the incoming interface of the packet.
	  if (iif is in inherited_olist(S,G)) {
	  send Assert(S,G) on iif
	  }
	*/

	if (!ifp) {
		if (PIM_DEBUG_MROUTE)
			zlog_debug(
				"%s: WRONGVIF (S,G)=%pSG could not find input interface for input_vif_index=%d",
				__func__, &sg, msg->msg_im_vif);
		return -1;
	}

	pim_ifp = ifp->info;
	if (!pim_ifp) {
		if (PIM_DEBUG_MROUTE)
			zlog_debug(
				"%s: WRONGVIF (S,G)=%pSG multicast not enabled on interface %s",
				__func__, &sg, ifp->name);
		return -2;
	}

	if (pim_static_nocache_resolve(pim_ifp->pim, ifp, &sg) != 1)
		return 0;

	pim_ifchannel_find(ifp, &sg, &ch, &throwaway);
	if (ch) {
		sg_channel = true;
		any_channel = true;
	} else {
		pim_sgaddr star_g = sg;

		if (PIM_DEBUG_MROUTE)
			zlog_debug(
				"%s: WRONGVIF (S,G)=%pSG could not find channel on interface %s",
				__func__, &sg, ifp->name);

		star_g.src = PIMADDR_ANY;
		pim_ifchannel_find(ifp, &star_g, &ch, &throwaway);
		if (ch)
			any_channel = true;
		else if (PIM_DEBUG_MROUTE)
			zlog_debug("%s: WRONGVIF (*,G)=%pSG could not find channel on interface %s",
				   __func__, &star_g, ifp->name);
	}

	if (!sg_channel && !pim_iface_grp_dm(pim_ifp, sg.grp) && ifp != pim_ifp->pim->regiface) {
		/*
		 * Prefer kernel ingress when there is no (S,G) ifchannel.
		 * A (*,G)-only ifchannel must not block this: Assert on (*,G)
		 * never succeeds (SPTbit is false for (*,G)).  Local (*,G)
		 * membership on the ingress (IGMP/MLD or static-group) is
		 * common when interfaces share overlapping connected prefixes
		 * and data arrives on a different interface than the installed
		 * MFC iif; without prefer-ingress, WRONGVIF repeats until RPF
		 * is realigned to the kernel ingress.
		 *
		 * If prefer_ingress cannot reconcile (ingress not a valid RPF),
		 * fall through to WRVIFWHOLE compat and/or Assert below.
		 */
		if (pim_mroute_wrongvif_prefer_ingress(ifp, &sg) == 0)
			return 0;

		if (!mroute_wrvifwhole_supported) {
			/*
			 * Old kernels without WRVIFWHOLE: WRONGVIF may be the
			 * only upcall.  Mirror pim_mroute_msg_wrvifwhole()
			 * when there is no (S,G) ifchannel on ingress -- FHR on
			 * the source-connected interface (join-before-data,
			 * bidirectional) and LHR / SPT switch on upstream
			 * interfaces (e.g. (S,G) MFC reinstall after pimd
			 * restart).
			 */
			/*
			 * FHR activation requires DR: only the DR originates
			 * register state on a shared LAN.
			 * pim_mroute_msg_wrvifwhole() omits this check because
			 * WRVIFWHOLE carries the packet and the kernel already
			 * selected this router's VIF.
			 */
			if (pim_if_connected_to_source(ifp, sg.src) && PIM_I_am_DR(pim_ifp))
				return pim_upstream_activate_stream(ifp, &sg);
			return pim_upstream_wrongvif_wrvifwhole_compat(ifp, &sg);
		}
	}

	if (!any_channel)
		return -3;

	if (pim_iface_grp_dm(pim_ifp, sg.grp)) {
		struct pim_upstream *up = pim_upstream_find(pim_ifp->pim, &sg);

		if (!up) {
			if (PIM_DEBUG_MROUTE)
				zlog_debug("%s: Dense Mode WRONGVIF on %s for (S,G)=%pSG, no upstream",
					   __func__, ifp->name, &sg);
			return 0;
		}
		if (PIM_DEBUG_PIM_J_P)
			zlog_debug("%s: Dense Mode WRONGVIF on %s for (S,G)=%pSG", __func__,
				   ifp->name, &sg);
		pim_dm_wrongif(ifp, sg, up);
		return 0;
	}

	/*
	  RFC 4601: 4.6.1.  (S,G) Assert Message State Machine

	  Transitions from NoInfo State

	  An (S,G) data packet arrives on interface I, AND
	  CouldAssert(S,G,I)==TRUE An (S,G) data packet arrived on an
	  downstream interface that is in our (S,G) outgoing interface
	  list.  We optimistically assume that we will be the assert
	  winner for this (S,G), and so we transition to the "I am Assert
	  Winner" state and perform Actions A1 (below), which will
	  initiate the assert negotiation for (S,G).
	*/

	if (ch->ifassert_state != PIM_IFASSERT_NOINFO) {
		if (PIM_DEBUG_MROUTE) {
			zlog_debug(
				"%s: WRONGVIF (S,G)=%s channel is not on Assert NoInfo state for interface %s",
				__func__, ch->sg_str, ifp->name);
		}
		return -4;
	}

	if (!PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)) {
		if (PIM_DEBUG_MROUTE) {
			zlog_debug(
				"%s: WRONGVIF (S,G)=%s interface %s is not downstream for channel",
				__func__, ch->sg_str, ifp->name);
		}
		return -5;
	}

	if (assert_action_a1(ch)) {
		if (PIM_DEBUG_MROUTE) {
			zlog_debug(
				"%s: WRONGVIF (S,G)=%s assert_action_a1 failure on interface %s",
				__func__, ch->sg_str, ifp->name);
		}
		return -6;
	}

	return 0;
}

int pim_mroute_msg_wrvifwhole(int fd, struct interface *ifp, const char *buf,
			      size_t len)
{
	const ipv_hdr *ip_hdr = (const ipv_hdr *)buf;
	struct pim_interface *pim_ifp;
	struct pim_instance *pim;
	struct pim_ifchannel *ch, *throwaway;
	struct pim_upstream *up;
	pim_sgaddr star_g;
	pim_sgaddr sg;
	bool isdense = false;

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

	if (!mroute_wrvifwhole_supported) {
		mroute_wrvifwhole_supported = true;
		zlog_info("pimd: IGMPMSG_WRVIFWHOLE received, kernel supports WRVIFWHOLE upcalls");
	}

	memset(&sg, 0, sizeof(sg));
	sg.src = IPV_SRC(ip_hdr);
	sg.grp = IPV_DST(ip_hdr);

	isdense = pim_iface_grp_dm(pim_ifp, sg.grp);

	if (isdense) {
		up = pim_upstream_find(pim, &sg);
		if (!up) {
			if (PIM_DEBUG_MROUTE)
				zlog_debug("WRVIFWHOLE (S,G)=%pSG dense mode, no upstream", &sg);
			return 0;
		}

		if (PIM_DEBUG_PIM_J_P)
			zlog_debug("%s: Dense Mode WRVIFWHOLE on %s for (S,G)=%pSG", __func__,
				   ifp->name, &sg);
		pim_dm_wrongif(ifp, sg, up);
		return 0;
	}

	pim_ifchannel_find(ifp, &sg, &ch, &throwaway);
	if (ch) {
		if (PIM_DEBUG_MROUTE)
			zlog_debug(
				"WRVIFWHOLE (S,G)=%s found ifchannel on interface %s",
				ch->sg_str, ifp->name);
		return -1;
	}

	star_g = sg;
	star_g.src = PIMADDR_ANY;

	/*
	 * If the incoming interface is the pimreg, then
	 * we know the callback is associated with a pim register
	 * packet and there is nothing to do here as that
	 * normal pim processing will see the packet and allow
	 * us to do the right thing.
	 */
	if (ifp == pim->regiface) {
		return 0;
	}

	up = pim_upstream_find(pim_ifp->pim, &sg);
	if (up) {
		struct pim_upstream *parent;
		struct pim_nexthop source;
		struct pim_rpf *rpf = RP(pim_ifp->pim, sg.grp);

		/* No RPF or No RPF interface or No mcast on RPF interface */
		if (!rpf || !rpf->source_nexthop.interface ||
		    !rpf->source_nexthop.interface->info)
			return 0;

		/*
		 * If we have received a WRVIFWHOLE and are at this
		 * point, we could be receiving the packet on the *,G
		 * tree, let's check and if so we can safely drop
		 * it.
		 */
		parent = pim_upstream_find(pim_ifp->pim, &star_g);
		if (parent && parent->rpf.source_nexthop.interface == ifp)
			return 0;

		pim_ifp = rpf->source_nexthop.interface->info;

		memset(&source, 0, sizeof(source));
		/*
		 * If we are the fhr that means we are getting a callback during
		 * the pimreg period, so I believe we can ignore this packet
		 */
		if (!PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) {
			/*
			 * No if channel, but upstream we are at the RP.
			 *
			 * This could be a anycast RP too and we may
			 * not have received a register packet from
			 * the source here at all.  So gracefully
			 * bow out of doing a nexthop lookup and
			 * setting the SPTBIT to true
			 */
			if (!(pim_addr_is_any(up->upstream_register)) &&
			    pim_nht_lookup(pim_ifp->pim, &source, up->upstream_register, up->sg.grp,
					   false)) {
				pim_register_stop_send(source.interface, &sg,
						       pim_ifp->primary_address,
						       up->upstream_register);
				up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE;
			}

			pim_upstream_inherited_olist(pim_ifp->pim, up);
			if (!up->channel_oil->installed)
				pim_upstream_mroute_add(up->channel_oil,
							__func__);
		} else {
			if (I_am_RP(pim_ifp->pim, up->sg.grp)) {
				if (pim_nht_lookup(pim_ifp->pim, &source, up->upstream_register,
						   up->sg.grp, false))
					pim_register_stop_send(
						source.interface, &sg,
						pim_ifp->primary_address,
						up->upstream_register);
				up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE;
			} else {
				/*
				 * At this point pimd is connected to
				 * the source, it has a parent, we are not
				 * the RP  and the SPTBIT should be set
				 * since we know *the* S,G is on the SPT.
				 * The first time this happens, let's cause
				 * an immediate join to go out so that
				 * the RP can trim this guy immediately
				 * if necessary, instead of waiting
				 * one join/prune send cycle
				 */
				if (up->sptbit != PIM_UPSTREAM_SPTBIT_TRUE &&
				    up->parent &&
				    up->rpf.source_nexthop.interface !=
					    up->parent->rpf.source_nexthop
						    .interface) {
					up->sptbit = PIM_UPSTREAM_SPTBIT_TRUE;
					pim_jp_agg_single_upstream_send(
						&up->parent->rpf, up->parent,
						true);
				}
			}
			pim_upstream_keep_alive_timer_start(
				up, pim_ifp->pim->keep_alive_time);
			pim_upstream_inherited_olist(pim_ifp->pim, up);
			pim_mroute_msg_wholepkt(fd, ifp, buf, len);
		}
		return 0;
	}

	pim_ifp = ifp->info;
	if (pim_if_connected_to_source(ifp, sg.src)) {
		/*
		 * Per RFC 7761 only the DR on the incoming interface is the
		 * FHR for a directly-connected source. On a non-DR (e.g.
		 * MLAG active-active peer, or any shared-LAN neighbor), an
		 * FHR-flagged upstream would later trip pim_upstream_switch()
		 * into silently adding pimreg to the channel_oil OIF, causing
		 * every data packet on the (S,G) to be CPU-punted forever.
		 * Install as SRC_NOCACHE (self-aging blackhole) instead.
		 */
		int up_flags = PIM_UPSTREAM_FLAG_MASK_FHR;

		if (!PIM_I_am_DR(pim_ifp))
			up_flags = PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE;

		up = pim_upstream_add(pim_ifp->pim, &sg, ifp, up_flags, __func__, NULL);
		if (!up) {
			if (PIM_DEBUG_MROUTE)
				zlog_debug(
					"%pSG: WRONGVIF%s unable to create upstream on interface",
					&sg, ifp->name);
			return -2;
		}
		PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags);
		pim_upstream_keep_alive_timer_start(
			up, pim_ifp->pim->keep_alive_time);
		up->channel_oil->cc.pktcnt++;
		if (!pim_is_group_filtered(pim_ifp, &sg.grp, &sg.src) &&
		    pim_upstream_could_register(up))
			pim_register_join(up);
		pim_upstream_inherited_olist(pim_ifp->pim, up);
		if (!up->channel_oil->installed)
			pim_upstream_mroute_add(up->channel_oil, __func__);

		// Send the packet to the RP
		pim_mroute_msg_wholepkt(fd, ifp, buf, len);
	} else {
		up = pim_upstream_add(pim_ifp->pim, &sg, ifp,
				      PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE,
				      __func__, NULL);
		if (!up->channel_oil->installed)
			pim_upstream_mroute_add(up->channel_oil, __func__);
	}

	return 0;
}

#if PIM_IPV == 4
static int process_igmp_packet(struct pim_instance *pim, const char *buf,
			       size_t buf_size, ifindex_t ifindex)
{
	struct interface *ifp;
	struct pim_interface *pim_ifp;
	struct in_addr ifaddr;
	struct gm_sock *igmp;
	const struct prefix *connected_src;
	const struct ip *ip_hdr = (const struct ip *)buf;
	const struct in_addr srcaddr = ip_hdr->ip_src;
	const struct in_addr dstaddr = ip_hdr->ip_dst;

	/* We have the IP packet but we do not know which interface this
	 * packet was
	 * received on. Find the interface that is on the same subnet as
	 * the source
	 * of the IP packet.
	 */
	ifp = if_lookup_by_index(ifindex, pim->vrf->vrf_id);

	if (!ifp || !ifp->info)
		return 0;

	connected_src = pim_if_connected_to_source(ifp, ip_hdr->ip_src);

	if (!connected_src && !pim_addr_is_any(ip_hdr->ip_src)) {
		if (PIM_DEBUG_GM_PACKETS) {
			zlog_debug("Recv IGMP packet on interface: %s from a non-connected source: %pI4",
				   ifp->name, &srcaddr);
		}
		return 0;
	}

	pim_ifp = ifp->info;
	ifaddr = connected_src ? connected_src->u.prefix4
			       : pim_ifp->primary_address;
	igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->gm_socket_list, ifaddr);

	if (PIM_DEBUG_GM_PACKETS) {
		zlog_debug("%s(%s): igmp kernel upcall on %s(%p) for %pI4 -> %pI4", __func__,
			   pim->vrf->name, ifp->name, igmp, &srcaddr, &dstaddr);
	}
	if (igmp)
		pim_igmp_packet(igmp, (char *)buf, buf_size);
	else if (PIM_DEBUG_GM_PACKETS)
		zlog_debug(
			"No IGMP socket on interface: %s with connected source: %pI4",
			ifp->name, &ifaddr);

	return 0;
}
#endif

int pim_mroute_msg(struct pim_instance *pim, const char *buf, size_t buf_size,
		   ifindex_t ifindex)
{
	struct interface *ifp;
	const ipv_hdr *ip_hdr;
	const kernmsg *msg;

	if (buf_size < (int)sizeof(ipv_hdr))
		return 0;

	ip_hdr = (const ipv_hdr *)buf;

#if PIM_IPV == 4
	struct in_addr srcaddr, dstaddr;

	if (ip_hdr->ip_p == IPPROTO_IGMP) {
		process_igmp_packet(pim, buf, buf_size, ifindex);
	} else if (ip_hdr->ip_p) {
		if (PIM_DEBUG_MROUTE_DETAIL) {
			zlog_debug("%s: no kernel upcall proto=%d src: %pI4 dst: %pI4 msg_size=%ld",
				   __func__, ip_hdr->ip_p, &srcaddr, &dstaddr, (long int)buf_size);
		}

	} else {
#else

	if ((ip_hdr->ip6_vfc & 0xf) == 0) {
#endif
		msg = (const kernmsg *)buf;

		ifp = pim_if_find_by_vif_index(pim, msg->msg_im_vif);

		if (!ifp)
			return 0;
		if (PIM_DEBUG_MROUTE) {
#if PIM_IPV == 4
			zlog_debug("%s: [%s] pim kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%pI4,%pI4) on %s vifi=%d size=%ld",
				   __func__, pim->vrf->name, gmmsgtype2str[msg->msg_im_msgtype],
				   msg->msg_im_msgtype, ip_hdr->ip_p, pim->mroute_socket,
				   &msg->msg_im_src, &msg->msg_im_dst, ifp->name, msg->msg_im_vif,
				   (long int)buf_size);
#else
			zlog_debug("%s: [%s] pim kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%pI6,%pI6) on %s vifi=%d size=%ld",
				   __func__, pim->vrf->name, gmmsgtype2str[msg->msg_im_msgtype],
				   msg->msg_im_msgtype, ip_hdr->ip6_nxt, pim->mroute_socket,
				   &msg->msg_im_src, &msg->msg_im_dst, ifp->name, msg->msg_im_vif,
				   (long int)buf_size);
#endif
		}

		switch (msg->msg_im_msgtype) {
		case GMMSG_WRONGVIF:
			return pim_mroute_msg_wrongvif(pim->mroute_socket, ifp,
						       msg);
		case GMMSG_NOCACHE:
			return pim_mroute_msg_nocache(pim->mroute_socket, ifp,
						      msg);
		case GMMSG_WHOLEPKT:
			return pim_mroute_msg_wholepkt(pim->mroute_socket, ifp,
						       (const char *)msg,
						       buf_size);
		case GMMSG_WRVIFWHOLE:
			return pim_mroute_msg_wrvifwhole(pim->mroute_socket,
							 ifp, (const char *)msg,
							 buf_size);
		default:
			break;
		}
	}

	return 0;
}

static void mroute_read(struct event *t)
{
	struct pim_instance *pim;
	static long long count;
	char buf[10000];
	int cont = 1;
	int rd;
	ifindex_t ifindex;
	pim = EVENT_ARG(t);

	while (cont) {
		rd = pim_socket_recvfromto(pim->mroute_socket, (uint8_t *)buf,
					   sizeof(buf), NULL, NULL, NULL, NULL,
					   &ifindex);
		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, pim->mroute_socket, errno,
				safe_strerror(errno));
			goto done;
		}

		pim_mroute_msg(pim, buf, rd, ifindex);

		count++;
		if (count % router->packet_process == 0)
			cont = 0;
	}
/* Keep reading */
done:
	mroute_read_on(pim);

	return;
}

static void mroute_read_on(struct pim_instance *pim)
{
	event_add_read(router->master, mroute_read, pim, pim->mroute_socket, &pim->event);
}

static void mroute_read_off(struct pim_instance *pim)
{
	event_cancel(&pim->event);
}

int pim_mroute_socket_enable(struct pim_instance *pim)
{
	int fd;

	frr_with_privs(&pimd_privs) {

#if PIM_IPV == 4
		fd = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
#else
		fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
#endif
		if (fd < 0) {
			zlog_warn("Could not create mroute socket: errno=%d: %s",
				  errno,
				  safe_strerror(errno));
			return -2;
		}

#if PIM_IPV == 6
		struct icmp6_filter filter[1];
		int ret;

		/* Unlike IPv4, this socket is not used for MLD, so just drop
		 * everything with an empty ICMP6 filter.  Otherwise we get
		 * all kinds of garbage here, possibly even non-multicast
		 * related ICMPv6 traffic (e.g. ping)
		 *
		 * (mroute kernel upcall "packets" are injected directly on the
		 * socket, this sockopt -or any other- has no effect on them)
		 */
		ICMP6_FILTER_SETBLOCKALL(filter);
		ret = setsockopt(fd, SOL_ICMPV6, ICMP6_FILTER, filter,
				 sizeof(filter));
		if (ret)
			zlog_err(
				"(VRF %s) failed to set mroute control filter: %m",
				pim->vrf->name);
#endif

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

	pim->mroute_socket = fd;
	if (pim_mroute_set(pim, 1)) {
		zlog_warn(
			"Could not enable mroute on socket fd=%d: errno=%d: %s",
			fd, errno, safe_strerror(errno));
		close(fd);
		mroute_read_off(pim);
		pim->mroute_socket = -1;
		return -3;
	}

	pim->mroute_socket_creation = pim_time_monotonic_sec();

	mroute_read_on(pim);

	return 0;
}

int pim_mroute_socket_disable(struct pim_instance *pim)
{
	if (pim_mroute_set(pim, 0)) {
		zlog_warn(
			"Could not disable mroute on socket fd=%d: errno=%d: %s",
			pim->mroute_socket, errno, safe_strerror(errno));
		return -2;
	}

	if (close(pim->mroute_socket)) {
		zlog_warn("Failure closing mroute socket: fd=%d errno=%d: %s",
			  pim->mroute_socket, errno, safe_strerror(errno));
		return -3;
	}

	mroute_read_off(pim);
	pim->mroute_socket = -1;

	return 0;
}

/*
  For each network interface (e.g., physical or a virtual tunnel) that
  would be used for multicast forwarding, a corresponding multicast
  interface must be added to the kernel.
 */
int pim_mroute_add_vif(struct interface *ifp, pim_addr ifaddr,
		       unsigned char flags)
{
	struct pim_interface *pim_ifp = ifp->info;
	pim_vifctl vc;
	int err;

	if (PIM_DEBUG_MROUTE)
		zlog_debug("%s: Add Vif %d (%s[%s])", __func__,
			   pim_ifp->mroute_vif_index, ifp->name,
			   pim_ifp->pim->vrf->name);

	memset(&vc, 0, sizeof(vc));
	vc.vc_vifi = pim_ifp->mroute_vif_index;
#if PIM_IPV == 4
#ifdef VIFF_USE_IFINDEX
	vc.vc_lcl_ifindex = ifp->ifindex;
#else
	if (ifaddr.s_addr == INADDR_ANY) {
		zlog_warn(
			"%s: unnumbered interfaces are not supported on this platform",
			__func__);
		return -1;
	}
	memcpy(&vc.vc_lcl_addr, &ifaddr, sizeof(vc.vc_lcl_addr));
#endif
#else
	vc.vc_pifi = ifp->ifindex;
#endif
	vc.vc_flags = flags;
	vc.vc_threshold = PIM_MROUTE_MIN_TTL;
	vc.vc_rate_limit = 0;

#if PIM_IPV == 4
#ifdef PIM_DVMRP_TUNNEL
	if (vc.vc_flags & VIFF_TUNNEL) {
		memcpy(&vc.vc_rmt_addr, &vif_remote_addr,
		       sizeof(vc.vc_rmt_addr));
	}
#endif
#endif

	frr_with_privs (&pimd_privs) {
		err = setsockopt(pim_ifp->pim->mroute_socket, PIM_IPPROTO, MRT_ADD_VIF,
				 (void *)&vc, sizeof(vc));
	}
	if (err) {
		zlog_warn(
			"%s: failure: setsockopt(fd=%d,PIM_IPPROTO,MRT_ADD_VIF,vif_index=%d,ifaddr=%pPAs,flag=%d): errno=%d: %s",
			__func__, pim_ifp->pim->mroute_socket, ifp->ifindex,
			&ifaddr, flags, errno, safe_strerror(errno));
		return -2;
	}

	return 0;
}

int pim_mroute_del_vif(struct interface *ifp)
{
	struct pim_interface *pim_ifp = ifp->info;
	pim_vifctl vc;
	int err;

	if (PIM_DEBUG_MROUTE)
		zlog_debug("%s: Del Vif %d (%s[%s])", __func__,
			   pim_ifp->mroute_vif_index, ifp->name,
			   pim_ifp->pim->vrf->name);

	memset(&vc, 0, sizeof(vc));
	vc.vc_vifi = pim_ifp->mroute_vif_index;

	frr_with_privs (&pimd_privs) {
		err = setsockopt(pim_ifp->pim->mroute_socket, PIM_IPPROTO, MRT_DEL_VIF,
				 (void *)&vc, sizeof(vc));
	}
	if (err) {
		zlog_warn(
			"%s %s: failure: setsockopt(fd=%d,PIM_IPPROTO,MRT_DEL_VIF,vif_index=%d): errno=%d: %s",
			__FILE__, __func__, pim_ifp->pim->mroute_socket,
			pim_ifp->mroute_vif_index, errno, safe_strerror(errno));
		return -2;
	}

	return 0;
}

/*
 * Prevent creating MFC entry with OIF=IIF.
 *
 * This is a protection against implementation mistakes.
 *
 * PIM protocol implicitly ensures loopfree multicast topology.
 *
 * IGMP must be protected against adding looped MFC entries created
 * by both source and receiver attached to the same interface. See
 * TODO T22.
 *
 * Local IGMP/MLD membership is delivered by the stack on the receiving
 * interface; the MFC must not list that interface as an OIF (split
 * horizon).  VXLAN is the only exception (ALLOW_IIF_IN_OIL).
 */
bool pim_mroute_allow_iif_in_oil(struct channel_oil *c_oil,
		int oif_index)
{
#ifdef PIM_ENFORCE_LOOPFREE_MFC
	if (c_oil->up && PIM_UPSTREAM_FLAG_TEST_ALLOW_IIF_IN_OIL(c_oil->up->flags))
		return true;

	return false;
#else
	return true;
#endif
}

static inline void pim_mroute_copy(struct channel_oil *out,
				   struct channel_oil *in)
{
	int i;

	*oil_origin(out) = *oil_origin(in);
	*oil_mcastgrp(out) = *oil_mcastgrp(in);
	*oil_incoming_vif(out) = *oil_incoming_vif(in);

	for (i = 0; i < MAXVIFS; ++i) {
		if (*oil_incoming_vif(out) == i &&
		    !pim_mroute_allow_iif_in_oil(in, i)) {
			oil_if_set(out, i, 0);
			continue;
		}

		if (in->oif_flags[i] & PIM_OIF_FLAG_MUTE)
			oil_if_set(out, i, 0);
		else
			oil_if_set(out, i, oil_if_has(in, i));
	}
}

/* This function must not be called directly 0
 * use pim_upstream_mroute_add or pim_static_mroute_add instead
 */
static int pim_mroute_add(struct channel_oil *c_oil, const char *name)
{
	struct pim_instance *pim = c_oil->pim;
	struct channel_oil tmp_oil[1] = { };
	int err;

	pim->mroute_add_last = pim_time_monotonic_sec();
	++pim->mroute_add_events;

	/* Copy the oil to a temporary structure to fixup (without need to
	 * later restore) before sending the mroute add to the dataplane
	 */
	pim_mroute_copy(tmp_oil, c_oil);

	/* The linux kernel *expects* the incoming
	 * vif to be part of the outgoing list
	 * in the case of a (*,G).
	 */
	if (pim_addr_is_any(*oil_origin(c_oil))) {
		oil_if_set(tmp_oil, *oil_incoming_vif(c_oil), 1);
	}

	/*
	 * If we have an unresolved cache entry for the S,G
	 * it is owned by the pimreg for the incoming IIF
	 * So set pimreg as the IIF temporarily to cause
	 * the packets to be forwarded.  Then set it
	 * to the correct IIF afterwards.
	 */
	if (!c_oil->installed && !pim_addr_is_any(*oil_origin(c_oil)) &&
	    *oil_incoming_vif(c_oil) != 0) {
		*oil_incoming_vif(tmp_oil) = 0;
	}
	/* For IPv6 MRT_ADD_MFC is defined to MRT6_ADD_MFC */
	frr_with_privs (&pimd_privs) {
		err = setsockopt(pim->mroute_socket, PIM_IPPROTO, MRT_ADD_MFC, &tmp_oil->oil,
				 sizeof(tmp_oil->oil));
	}

	if (!err && !c_oil->installed && !pim_addr_is_any(*oil_origin(c_oil)) &&
	    *oil_incoming_vif(c_oil) != 0) {
		*oil_incoming_vif(tmp_oil) = *oil_incoming_vif(c_oil);
		frr_with_privs (&pimd_privs) {
			err = setsockopt(pim->mroute_socket, PIM_IPPROTO, MRT_ADD_MFC,
					 &tmp_oil->oil, sizeof(tmp_oil->oil));
		}
	}

	if (err) {
		zlog_warn(
			"%s %s: failure: setsockopt(fd=%d,PIM_IPPROTO,MRT_ADD_MFC): errno=%d: %s",
			__FILE__, __func__, pim->mroute_socket, errno,
			safe_strerror(errno));
		return -2;
	}

	if (PIM_DEBUG_MROUTE) {
		char buf[1000];
		zlog_debug("%s(%s), vrf %s Added Route: %s", __func__, name,
			   pim->vrf->name,
			   pim_channel_oil_dump(c_oil, buf, sizeof(buf)));
	}

	if (!c_oil->installed) {
		c_oil->installed = 1;
		c_oil->mroute_creation = pim_time_monotonic_sec();
	}

	return 0;
}

static int pim_upstream_get_mroute_iif(struct channel_oil *c_oil,
		const char *name)
{
	vifi_t iif = MAXVIFS;
	struct interface *ifp = NULL;
	struct pim_interface *pim_ifp;
	struct pim_upstream *up = c_oil->up;

	if (up) {
		if (PIM_UPSTREAM_FLAG_TEST_USE_RPT(up->flags)) {
			if (up->parent)
				ifp = up->parent->rpf.source_nexthop.interface;
		} else {
			ifp = up->rpf.source_nexthop.interface;
		}
		if (ifp && !PIM_IF_IS_DELETED(ifp) && ifp->info) {
			pim_ifp = ifp->info;
			if (pim_ifp->pim == c_oil->pim)
				iif = pim_ifp->mroute_vif_index;
		}
	}
	return iif;
}

int pim_upstream_mroute_update(struct channel_oil *c_oil, const char *name)
{
	char buf[1000];

	if (*oil_incoming_vif(c_oil) >= MAXVIFS) {
		/* the c_oil cannot be installed as a mroute yet */
		if (PIM_DEBUG_MROUTE)
			zlog_debug(
					"%s(%s) %s mroute not ready to be installed; %s",
					__func__, name,
					pim_channel_oil_dump(c_oil, buf,
						sizeof(buf)),
					c_oil->installed ?
					"uninstall" : "skip");
		/* if already installed flush it out as we are going to stop
		 * updates to it leaving it in a stale state
		 */
		if (c_oil->installed)
			pim_mroute_del(c_oil, name);
		/* return success (skipped) */
		return 0;
	}

	return pim_mroute_add(c_oil, name);
}

/* IIF associated with SGrpt entries are re-evaluated when the parent
 * (*,G) entries IIF changes
 */
static void pim_upstream_all_sources_iif_update(struct pim_upstream *up)
{
	struct listnode *listnode;
	struct pim_upstream *child;

	for (ALL_LIST_ELEMENTS_RO(up->sources, listnode,
				child)) {
		if (PIM_UPSTREAM_FLAG_TEST_USE_RPT(child->flags))
			pim_upstream_mroute_iif_update(child->channel_oil,
					__func__);
	}
}

/* In the case of "PIM state machine" added mroutes an upstream entry
 * must be present to decide on the SPT-forwarding vs. RPT-forwarding.
 */
int pim_upstream_mroute_add(struct channel_oil *c_oil, const char *name)
{
	vifi_t iif;

	iif = pim_upstream_get_mroute_iif(c_oil, name);

	if (*oil_incoming_vif(c_oil) != iif) {
		*oil_incoming_vif(c_oil) = iif;
		if (pim_addr_is_any(*oil_origin(c_oil)) &&
				c_oil->up)
			pim_upstream_all_sources_iif_update(c_oil->up);
	} else {
		*oil_incoming_vif(c_oil) = iif;
	}

	return pim_upstream_mroute_update(c_oil, name);
}

/* Look for IIF changes and update the dataplane entry only if the IIF
 * has changed.
 */
int pim_upstream_mroute_iif_update(struct channel_oil *c_oil, const char *name)
{
	vifi_t iif;
	char buf[1000];

	if (!c_oil)
		return 0;

	iif = pim_upstream_get_mroute_iif(c_oil, name);
	if (*oil_incoming_vif(c_oil) == iif) {
		/* no change */
		return 0;
	}
	*oil_incoming_vif(c_oil) = iif;

	if (pim_addr_is_any(*oil_origin(c_oil)) &&
			c_oil->up)
		pim_upstream_all_sources_iif_update(c_oil->up);

	if (PIM_DEBUG_MROUTE_DETAIL)
		zlog_debug("%s(%s) %s mroute iif update %d",
				__func__, name,
				pim_channel_oil_dump(c_oil, buf,
					sizeof(buf)), iif);
	/* XXX: is this hack needed? */
	c_oil->oil_inherited_rescan = 1;
	return pim_upstream_mroute_update(c_oil, name);
}

int pim_static_mroute_add(struct channel_oil *c_oil, const char *name)
{
	return pim_mroute_add(c_oil, name);
}

void pim_static_mroute_iif_update(struct channel_oil *c_oil,
				int input_vif_index,
				const char *name)
{
	if (*oil_incoming_vif(c_oil) == input_vif_index)
		return;

	*oil_incoming_vif(c_oil) = input_vif_index;
	if (input_vif_index == MAXVIFS)
		pim_mroute_del(c_oil, name);
	else
		pim_static_mroute_add(c_oil, name);
}

int pim_mroute_del(struct channel_oil *c_oil, const char *name)
{
	struct pim_instance *pim = c_oil->pim;
	int err;

	pim->mroute_del_last = pim_time_monotonic_sec();
	++pim->mroute_del_events;

	if (!c_oil->installed) {
		if (PIM_DEBUG_MROUTE) {
			char buf[1000];
			struct interface *iifp =
				pim_if_find_by_vif_index(pim, *oil_incoming_vif(
								      c_oil));

			zlog_debug("%s %s: incoming interface %s for route is %s not installed, do not need to send del req. ",
				   __FILE__, __func__,
				   iifp ? iifp->name : "Unknown",
				   pim_channel_oil_dump(c_oil, buf,
							sizeof(buf)));
		}
		return -2;
	}

	err = setsockopt(pim->mroute_socket, PIM_IPPROTO, MRT_DEL_MFC,
			 &c_oil->oil, sizeof(c_oil->oil));
	if (err) {
		if (PIM_DEBUG_MROUTE)
			zlog_warn(
				"%s %s: failure: setsockopt(fd=%d,PIM_IPPROTO,MRT_DEL_MFC): errno=%d: %s",
				__FILE__, __func__, pim->mroute_socket, errno,
				safe_strerror(errno));
		return -2;
	}

	if (PIM_DEBUG_MROUTE) {
		char buf[1000];
		zlog_debug("%s(%s), vrf %s Deleted Route: %s", __func__, name,
			   pim->vrf->name,
			   pim_channel_oil_dump(c_oil, buf, sizeof(buf)));
	}

	// Reset kernel installed flag
	c_oil->installed = 0;

	return 0;
}

void pim_mroute_update_counters(struct channel_oil *c_oil)
{
	struct pim_instance *pim = c_oil->pim;
	pim_sioc_sg_req sgreq;

	c_oil->cc.oldpktcnt = c_oil->cc.pktcnt;
	c_oil->cc.oldbytecnt = c_oil->cc.bytecnt;
	c_oil->cc.oldwrong_if = c_oil->cc.wrong_if;

	if (!c_oil->installed) {
		c_oil->cc.lastused = 100ULL * pim->keep_alive_time;
		if (PIM_DEBUG_MROUTE) {
			pim_sgaddr sg;

			sg.src = *oil_origin(c_oil);
			sg.grp = *oil_mcastgrp(c_oil);
			zlog_debug("Channel%pSG is not installed no need to collect data from kernel",
				   &sg);
		}
		return;
	}


	memset(&sgreq, 0, sizeof(sgreq));

	pim_zlookup_sg_statistics(c_oil);

#if PIM_IPV == 4
	sgreq.src = *oil_origin(c_oil);
	sgreq.grp = *oil_mcastgrp(c_oil);
#else
	sgreq.src = c_oil->oil.mf6cc_origin;
	sgreq.grp = c_oil->oil.mf6cc_mcastgrp;
#endif
	if (ioctl(pim->mroute_socket, PIM_SIOCGETSGCNT, &sgreq)) {
		pim_sgaddr sg;

		sg.src = *oil_origin(c_oil);
		sg.grp = *oil_mcastgrp(c_oil);

		zlog_warn(
			"ioctl(PIM_SIOCGETSGCNT=%lu) failure for (S,G)=%pSG: errno=%d: %s",
			(unsigned long)PIM_SIOCGETSGCNT, &sg, errno,
			safe_strerror(errno));
		return;
	}

	c_oil->cc.pktcnt = sgreq.pktcnt;
	c_oil->cc.bytecnt = sgreq.bytecnt;
	c_oil->cc.wrong_if = sgreq.wrong_if;
	return;
}
