mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-11-04 10:07:04 +00:00 
			
		
		
		
	Practically no-one uses this and ioctls are pretty much wrappered. Further wrappering could make this even better. Signed-off-by: Donald Sharp <sharpd@nvidia.com>
		
			
				
	
	
		
			1370 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1370 lines
		
	
	
		
			34 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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_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_sock.h"
 | 
						|
#include "pim_vxlan.h"
 | 
						|
#include "pim_msg.h"
 | 
						|
 | 
						|
static void mroute_read_on(struct pim_instance *pim);
 | 
						|
static int pim_upstream_mroute_update(struct channel_oil *c_oil,
 | 
						|
				      const char *name);
 | 
						|
 | 
						|
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) {
 | 
						|
				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
 | 
						|
		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"};
 | 
						|
 | 
						|
 | 
						|
int pim_mroute_msg_nocache(int fd, struct interface *ifp, const kernmsg *msg)
 | 
						|
{
 | 
						|
	struct pim_interface *pim_ifp = ifp->info;
 | 
						|
	struct pim_upstream *up;
 | 
						|
	pim_sgaddr sg;
 | 
						|
	bool desync = false;
 | 
						|
 | 
						|
	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;
 | 
						|
	}
 | 
						|
 | 
						|
	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;
 | 
						|
 | 
						|
		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);
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
		if (pim_rpf_addr_is_inaddr_any(rpg)) {
 | 
						|
			if (PIM_DEBUG_MROUTE)
 | 
						|
				zlog_debug("%s: null RPF for packet to %pSG",
 | 
						|
					   ifp->name, &sg);
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * If we've received a multicast packet that isn't connected to
 | 
						|
	 * us
 | 
						|
	 */
 | 
						|
	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);
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
 | 
						|
	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.
 | 
						|
		 *
 | 
						|
		 * This *will* create a dangling channel_oil
 | 
						|
		 * that I see no way to get rid of.  Just noting
 | 
						|
		 * this for future reference.
 | 
						|
		 */
 | 
						|
		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;
 | 
						|
	}
 | 
						|
 | 
						|
	up = pim_upstream_find_or_add(&sg, ifp, PIM_UPSTREAM_FLAG_MASK_FHR,
 | 
						|
				      __func__);
 | 
						|
	if (up->channel_oil->installed) {
 | 
						|
		zlog_warn(
 | 
						|
			"%s: NOCACHE for %pSG, MFC entry disappeared - reinstalling",
 | 
						|
			ifp->name, &sg);
 | 
						|
		desync = true;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * 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);
 | 
						|
	}
 | 
						|
 | 
						|
	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__);
 | 
						|
	}
 | 
						|
	pim_register_join(up);
 | 
						|
	/* if we have receiver, inherit from parent */
 | 
						|
	pim_upstream_inherited_olist_decide(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)) {
 | 
						|
			up = pim_upstream_add(pim_ifp->pim, &sg, ifp,
 | 
						|
					      PIM_UPSTREAM_FLAG_MASK_SRC_LHR,
 | 
						|
					      __func__, NULL);
 | 
						|
			if (!up) {
 | 
						|
				if (PIM_DEBUG_MROUTE)
 | 
						|
					zlog_debug(
 | 
						|
						"%s: Unable to create upstream information for %pSG",
 | 
						|
						__func__, &sg);
 | 
						|
				return 0;
 | 
						|
			}
 | 
						|
			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;
 | 
						|
	}
 | 
						|
 | 
						|
	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 (!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;
 | 
						|
}
 | 
						|
 | 
						|
int pim_mroute_msg_wrongvif(int fd, struct interface *ifp, const kernmsg *msg)
 | 
						|
{
 | 
						|
	struct pim_ifchannel *ch;
 | 
						|
	struct pim_interface *pim_ifp;
 | 
						|
	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;
 | 
						|
	}
 | 
						|
 | 
						|
	ch = pim_ifchannel_find(ifp, &sg);
 | 
						|
	if (!ch) {
 | 
						|
		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;
 | 
						|
		ch = pim_ifchannel_find(ifp, &star_g);
 | 
						|
		if (!ch) {
 | 
						|
			if (PIM_DEBUG_MROUTE)
 | 
						|
				zlog_debug(
 | 
						|
					"%s: WRONGVIF (*,G)=%pSG could not find channel on interface %s",
 | 
						|
					__func__, &star_g, ifp->name);
 | 
						|
			return -3;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	  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;
 | 
						|
	struct pim_upstream *up;
 | 
						|
	pim_sgaddr star_g;
 | 
						|
	pim_sgaddr sg;
 | 
						|
 | 
						|
	pim_ifp = ifp->info;
 | 
						|
 | 
						|
	memset(&sg, 0, sizeof(sg));
 | 
						|
	sg.src = IPV_SRC(ip_hdr);
 | 
						|
	sg.grp = IPV_DST(ip_hdr);
 | 
						|
 | 
						|
	ch = pim_ifchannel_find(ifp, &sg);
 | 
						|
	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;
 | 
						|
 | 
						|
	pim = pim_ifp->pim;
 | 
						|
	/*
 | 
						|
	 * 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_nexthop_lookup(pim_ifp->pim, &source,
 | 
						|
					       up->upstream_register, 0)) {
 | 
						|
				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_nexthop_lookup(pim_ifp->pim, &source,
 | 
						|
						       up->upstream_register,
 | 
						|
						       0))
 | 
						|
					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)) {
 | 
						|
		up = pim_upstream_add(pim_ifp->pim, &sg, ifp,
 | 
						|
				      PIM_UPSTREAM_FLAG_MASK_FHR, __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++;
 | 
						|
		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;
 | 
						|
 | 
						|
	/* 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, &ip_hdr->ip_src);
 | 
						|
		}
 | 
						|
		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,
 | 
						|
			&ip_hdr->ip_src, &ip_hdr->ip_dst);
 | 
						|
	}
 | 
						|
	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
 | 
						|
	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, &ip_hdr->ip_src,
 | 
						|
				&ip_hdr->ip_dst, (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: pim kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%pI4,%pI4) on %s vifi=%d  size=%ld",
 | 
						|
				__func__, 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: pim kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%pI6,%pI6) on %s vifi=%d  size=%ld",
 | 
						|
				__func__, 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->thread);
 | 
						|
}
 | 
						|
 | 
						|
static void mroute_read_off(struct pim_instance *pim)
 | 
						|
{
 | 
						|
	EVENT_OFF(pim->thread);
 | 
						|
}
 | 
						|
 | 
						|
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
 | 
						|
 | 
						|
#ifdef SO_BINDTODEVICE
 | 
						|
		if (pim->vrf->vrf_id != VRF_DEFAULT
 | 
						|
		    && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
 | 
						|
				  pim->vrf->name, strlen(pim->vrf->name))) {
 | 
						|
			zlog_warn("Could not setsockopt SO_BINDTODEVICE: %s",
 | 
						|
				  safe_strerror(errno));
 | 
						|
			close(fd);
 | 
						|
			return -3;
 | 
						|
		}
 | 
						|
#endif
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
	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);
 | 
						|
		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
 | 
						|
 | 
						|
	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;
 | 
						|
 | 
						|
	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 implicitely 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.
 | 
						|
 * We shall allow igmp to create upstream when it is DR for the intf.
 | 
						|
 * Assume RP reachable via non DR.
 | 
						|
 */
 | 
						|
bool pim_mroute_allow_iif_in_oil(struct channel_oil *c_oil,
 | 
						|
		int oif_index)
 | 
						|
{
 | 
						|
#ifdef PIM_ENFORCE_LOOPFREE_MFC
 | 
						|
	struct interface *ifp_out;
 | 
						|
	struct pim_interface *pim_ifp;
 | 
						|
 | 
						|
	if (c_oil->up &&
 | 
						|
		PIM_UPSTREAM_FLAG_TEST_ALLOW_IIF_IN_OIL(c_oil->up->flags))
 | 
						|
		return true;
 | 
						|
 | 
						|
	ifp_out = pim_if_find_by_vif_index(c_oil->pim, oif_index);
 | 
						|
	if (!ifp_out)
 | 
						|
		return false;
 | 
						|
	pim_ifp = ifp_out->info;
 | 
						|
	if (!pim_ifp)
 | 
						|
		return false;
 | 
						|
	if ((c_oil->oif_flags[oif_index] & PIM_OIF_FLAG_PROTO_GM) &&
 | 
						|
	    PIM_I_am_DR(pim_ifp))
 | 
						|
		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 afterwords.
 | 
						|
	 */
 | 
						|
	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 */
 | 
						|
	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);
 | 
						|
		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_ifp = (struct pim_interface *)ifp->info;
 | 
						|
			if (pim_ifp)
 | 
						|
				iif = pim_ifp->mroute_vif_index;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return iif;
 | 
						|
}
 | 
						|
 | 
						|
static 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 dateplane 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];
 | 
						|
 | 
						|
	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 = 100 * 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;
 | 
						|
}
 |