mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-11-04 15:30:26 +00:00 
			
		
		
		
	IPv4 and IPv6 behaves a little bit differently with the socket options. IPPROTO_RAW socket option is only for IPv4. Therefore the register packet was not properly getting encapculated for PIMv6 and was working fine for PIMv4. So have used IPPROTO_PIM for PIMv6. Fixes: #11846 Signed-off-by: Mobashshera Rasool <mrasool@vmware.com>
		
			
				
	
	
		
			434 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			434 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * PIM for Quagga
 | 
						|
 * Copyright (C) 2008  Everton da Silva Marques
 | 
						|
 *
 | 
						|
 * This program is free software; you can redistribute it and/or modify
 | 
						|
 * it under the terms of the GNU General Public License as published by
 | 
						|
 * the Free Software Foundation; either version 2 of the License, or
 | 
						|
 * (at your option) any later version.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful, but
 | 
						|
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
						|
 * General Public License for more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU General Public License along
 | 
						|
 * with this program; see the file COPYING; if not, write to the Free Software
 | 
						|
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 | 
						|
 */
 | 
						|
 | 
						|
#include <zebra.h>
 | 
						|
 | 
						|
#include <sys/types.h>
 | 
						|
#include <sys/socket.h>
 | 
						|
#include <netinet/in.h>
 | 
						|
#include <netinet/igmp.h>
 | 
						|
#include <arpa/inet.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <netdb.h>
 | 
						|
#include <errno.h>
 | 
						|
 | 
						|
#include "log.h"
 | 
						|
#include "privs.h"
 | 
						|
#include "if.h"
 | 
						|
#include "vrf.h"
 | 
						|
#include "sockopt.h"
 | 
						|
#include "lib_errors.h"
 | 
						|
#include "network.h"
 | 
						|
 | 
						|
#include "pimd.h"
 | 
						|
#include "pim_instance.h"
 | 
						|
#include "pim_mroute.h"
 | 
						|
#include "pim_iface.h"
 | 
						|
#include "pim_sock.h"
 | 
						|
#include "pim_str.h"
 | 
						|
 | 
						|
#if PIM_IPV == 4
 | 
						|
#define setsockopt_iptos setsockopt_ipv4_tos
 | 
						|
#define setsockopt_multicast_loop setsockopt_ipv4_multicast_loop
 | 
						|
#else
 | 
						|
#define setsockopt_iptos setsockopt_ipv6_tclass
 | 
						|
#define setsockopt_multicast_loop setsockopt_ipv6_multicast_loop
 | 
						|
#endif
 | 
						|
 | 
						|
int pim_socket_raw(int protocol)
 | 
						|
{
 | 
						|
	int fd;
 | 
						|
 | 
						|
	frr_with_privs(&pimd_privs) {
 | 
						|
		fd = socket(PIM_AF, SOCK_RAW, protocol);
 | 
						|
	}
 | 
						|
 | 
						|
	if (fd < 0) {
 | 
						|
		zlog_warn("Could not create raw socket: errno=%d: %s", errno,
 | 
						|
			  safe_strerror(errno));
 | 
						|
		return PIM_SOCK_ERR_SOCKET;
 | 
						|
	}
 | 
						|
 | 
						|
	return fd;
 | 
						|
}
 | 
						|
 | 
						|
void pim_socket_ip_hdr(int fd)
 | 
						|
{
 | 
						|
	frr_with_privs(&pimd_privs) {
 | 
						|
#if PIM_IPV == 4
 | 
						|
		const int on = 1;
 | 
						|
 | 
						|
		if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)))
 | 
						|
			zlog_err("%s: Could not turn on IP_HDRINCL option: %m",
 | 
						|
				 __func__);
 | 
						|
#endif
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Given a socket and a interface,
 | 
						|
 * Bind that socket to that interface
 | 
						|
 */
 | 
						|
int pim_socket_bind(int fd, struct interface *ifp)
 | 
						|
{
 | 
						|
	int ret = 0;
 | 
						|
 | 
						|
#ifdef SO_BINDTODEVICE
 | 
						|
	frr_with_privs(&pimd_privs) {
 | 
						|
		ret = setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifp->name,
 | 
						|
				 strlen(ifp->name));
 | 
						|
	}
 | 
						|
#endif
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
#if PIM_IPV == 4
 | 
						|
static inline int pim_setsockopt(int protocol, int fd, struct interface *ifp)
 | 
						|
{
 | 
						|
	int one = 1;
 | 
						|
	int ttl = 1;
 | 
						|
 | 
						|
#if defined(HAVE_IP_PKTINFO)
 | 
						|
	/* Linux and Solaris IP_PKTINFO */
 | 
						|
	if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)))
 | 
						|
		zlog_warn("Could not set PKTINFO on socket fd=%d: %m", fd);
 | 
						|
#elif defined(HAVE_IP_RECVDSTADDR)
 | 
						|
	/* BSD IP_RECVDSTADDR */
 | 
						|
	if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &one, sizeof(one)))
 | 
						|
		zlog_warn("Could not set IP_RECVDSTADDR on socket fd=%d: %m",
 | 
						|
			  fd);
 | 
						|
#else
 | 
						|
	flog_err(
 | 
						|
		EC_LIB_DEVELOPMENT,
 | 
						|
		"Missing IP_PKTINFO and IP_RECVDSTADDR: unable to get dst addr from recvmsg()");
 | 
						|
	close(fd);
 | 
						|
	return PIM_SOCK_ERR_DSTADDR;
 | 
						|
#endif
 | 
						|
 | 
						|
	/* Set router alert (RFC 2113) for all IGMP messages (RFC
 | 
						|
	 * 3376 4. Message Formats)*/
 | 
						|
	if (protocol == IPPROTO_IGMP) {
 | 
						|
		uint8_t ra[4];
 | 
						|
 | 
						|
		ra[0] = 148;
 | 
						|
		ra[1] = 4;
 | 
						|
		ra[2] = 0;
 | 
						|
		ra[3] = 0;
 | 
						|
		if (setsockopt(fd, IPPROTO_IP, IP_OPTIONS, ra, 4)) {
 | 
						|
			zlog_warn(
 | 
						|
				"Could not set Router Alert Option on socket fd=%d: %m",
 | 
						|
				fd);
 | 
						|
			close(fd);
 | 
						|
			return PIM_SOCK_ERR_RA;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl))) {
 | 
						|
		zlog_warn("Could not set multicast TTL=%d on socket fd=%d: %m",
 | 
						|
			  ttl, fd);
 | 
						|
		close(fd);
 | 
						|
		return PIM_SOCK_ERR_TTL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (setsockopt_ipv4_multicast_if(fd, PIMADDR_ANY, ifp->ifindex)) {
 | 
						|
		zlog_warn(
 | 
						|
			"Could not set Outgoing Interface Option on socket fd=%d: %m",
 | 
						|
			fd);
 | 
						|
		close(fd);
 | 
						|
		return PIM_SOCK_ERR_IFACE;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
#else /* PIM_IPV != 4 */
 | 
						|
static inline int pim_setsockopt(int protocol, int fd, struct interface *ifp)
 | 
						|
{
 | 
						|
	int ttl = 1;
 | 
						|
	struct ipv6_mreq mreq = {};
 | 
						|
 | 
						|
	setsockopt_ipv6_pktinfo(fd, 1);
 | 
						|
	setsockopt_ipv6_multicast_hops(fd, ttl);
 | 
						|
 | 
						|
	mreq.ipv6mr_interface = ifp->ifindex;
 | 
						|
	if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &mreq,
 | 
						|
		       sizeof(mreq))) {
 | 
						|
		zlog_warn(
 | 
						|
			"Could not set Outgoing Interface Option on socket fd=%d: %m",
 | 
						|
			fd);
 | 
						|
		close(fd);
 | 
						|
		return PIM_SOCK_ERR_IFACE;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
int pim_reg_sock(void)
 | 
						|
{
 | 
						|
	int fd;
 | 
						|
	long flags;
 | 
						|
 | 
						|
	frr_with_privs (&pimd_privs) {
 | 
						|
		fd = socket(PIM_AF, SOCK_RAW, PIM_PROTO_REG);
 | 
						|
	}
 | 
						|
 | 
						|
	if (fd < 0) {
 | 
						|
		zlog_warn("Could not create raw socket: errno=%d: %s", errno,
 | 
						|
			  safe_strerror(errno));
 | 
						|
		return PIM_SOCK_ERR_SOCKET;
 | 
						|
	}
 | 
						|
 | 
						|
	if (sockopt_reuseaddr(fd)) {
 | 
						|
		close(fd);
 | 
						|
		return PIM_SOCK_ERR_REUSE;
 | 
						|
	}
 | 
						|
 | 
						|
	flags = fcntl(fd, F_GETFL, 0);
 | 
						|
	if (flags < 0) {
 | 
						|
		zlog_warn(
 | 
						|
			"Could not get fcntl(F_GETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s",
 | 
						|
			fd, errno, safe_strerror(errno));
 | 
						|
		close(fd);
 | 
						|
		return PIM_SOCK_ERR_NONBLOCK_GETFL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
 | 
						|
		zlog_warn(
 | 
						|
			"Could not set fcntl(F_SETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s",
 | 
						|
			fd, errno, safe_strerror(errno));
 | 
						|
		close(fd);
 | 
						|
		return PIM_SOCK_ERR_NONBLOCK_SETFL;
 | 
						|
	}
 | 
						|
 | 
						|
	return fd;
 | 
						|
}
 | 
						|
 | 
						|
int pim_socket_mcast(int protocol, pim_addr ifaddr, struct interface *ifp,
 | 
						|
		     uint8_t loop)
 | 
						|
{
 | 
						|
	int fd;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	fd = pim_socket_raw(protocol);
 | 
						|
	if (fd < 0) {
 | 
						|
		zlog_warn("Could not create multicast socket: errno=%d: %s",
 | 
						|
			  errno, safe_strerror(errno));
 | 
						|
		return PIM_SOCK_ERR_SOCKET;
 | 
						|
	}
 | 
						|
 | 
						|
	/* XXX: if SO_BINDTODEVICE isn't available, use IP_PKTINFO / IP_RECVIF
 | 
						|
	 * to emulate behaviour?  Or change to only use 1 socket for all
 | 
						|
	 * interfaces? */
 | 
						|
	ret = pim_socket_bind(fd, ifp);
 | 
						|
	if (ret) {
 | 
						|
		close(fd);
 | 
						|
		zlog_warn("Could not set fd: %d for interface: %s to device",
 | 
						|
			  fd, ifp->name);
 | 
						|
		return PIM_SOCK_ERR_BIND;
 | 
						|
	}
 | 
						|
 | 
						|
	set_nonblocking(fd);
 | 
						|
	sockopt_reuseaddr(fd);
 | 
						|
	setsockopt_so_recvbuf(fd, 8 * 1024 * 1024);
 | 
						|
 | 
						|
	ret = pim_setsockopt(protocol, fd, ifp);
 | 
						|
	if (ret) {
 | 
						|
		zlog_warn("pim_setsockopt failed for interface: %s to device ",
 | 
						|
			  ifp->name);
 | 
						|
		return ret;
 | 
						|
	}
 | 
						|
 | 
						|
	/* leftover common sockopts */
 | 
						|
	if (setsockopt_multicast_loop(fd, loop)) {
 | 
						|
		zlog_warn(
 | 
						|
			"Could not %s Multicast Loopback Option on socket fd=%d: %m",
 | 
						|
			loop ? "enable" : "disable", fd);
 | 
						|
		close(fd);
 | 
						|
		return PIM_SOCK_ERR_LOOP;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Set Tx socket DSCP byte */
 | 
						|
	if (setsockopt_iptos(fd, IPTOS_PREC_INTERNETCONTROL))
 | 
						|
		zlog_warn("can't set sockopt IP[V6]_TOS to socket %d: %m", fd);
 | 
						|
 | 
						|
	return fd;
 | 
						|
}
 | 
						|
 | 
						|
int pim_socket_join(int fd, pim_addr group, pim_addr ifaddr, ifindex_t ifindex,
 | 
						|
		    struct pim_interface *pim_ifp)
 | 
						|
{
 | 
						|
	int ret;
 | 
						|
 | 
						|
#if PIM_IPV == 4
 | 
						|
	ret = setsockopt_ipv4_multicast(fd, IP_ADD_MEMBERSHIP, ifaddr,
 | 
						|
					group.s_addr, ifindex);
 | 
						|
#else
 | 
						|
	struct ipv6_mreq opt;
 | 
						|
 | 
						|
	memcpy(&opt.ipv6mr_multiaddr, &group, 16);
 | 
						|
	opt.ipv6mr_interface = ifindex;
 | 
						|
	ret = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &opt, sizeof(opt));
 | 
						|
#endif
 | 
						|
 | 
						|
	pim_ifp->igmp_ifstat_joins_sent++;
 | 
						|
 | 
						|
	if (ret) {
 | 
						|
		flog_err(
 | 
						|
			EC_LIB_SOCKET,
 | 
						|
			"Failure socket joining fd=%d group %pPAs on interface address %pPAs: %m",
 | 
						|
			fd, &group, &ifaddr);
 | 
						|
		pim_ifp->igmp_ifstat_joins_failed++;
 | 
						|
		return ret;
 | 
						|
	}
 | 
						|
 | 
						|
	if (PIM_DEBUG_TRACE)
 | 
						|
		zlog_debug(
 | 
						|
			"Socket fd=%d joined group %pPAs on interface address %pPAs",
 | 
						|
			fd, &group, &ifaddr);
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
#if PIM_IPV == 4
 | 
						|
static void cmsg_getdstaddr(struct msghdr *mh, struct sockaddr_storage *dst,
 | 
						|
			    ifindex_t *ifindex)
 | 
						|
{
 | 
						|
	struct cmsghdr *cmsg;
 | 
						|
	struct sockaddr_in *dst4 = (struct sockaddr_in *)dst;
 | 
						|
 | 
						|
	for (cmsg = CMSG_FIRSTHDR(mh); cmsg != NULL;
 | 
						|
	     cmsg = CMSG_NXTHDR(mh, cmsg)) {
 | 
						|
#ifdef HAVE_IP_PKTINFO
 | 
						|
		if ((cmsg->cmsg_level == IPPROTO_IP) &&
 | 
						|
		    (cmsg->cmsg_type == IP_PKTINFO)) {
 | 
						|
			struct in_pktinfo *i;
 | 
						|
 | 
						|
			i = (struct in_pktinfo *)CMSG_DATA(cmsg);
 | 
						|
			if (dst4)
 | 
						|
				dst4->sin_addr = i->ipi_addr;
 | 
						|
			if (ifindex)
 | 
						|
				*ifindex = i->ipi_ifindex;
 | 
						|
 | 
						|
			break;
 | 
						|
		}
 | 
						|
#endif
 | 
						|
 | 
						|
#ifdef HAVE_IP_RECVDSTADDR
 | 
						|
		if ((cmsg->cmsg_level == IPPROTO_IP) &&
 | 
						|
		    (cmsg->cmsg_type == IP_RECVDSTADDR)) {
 | 
						|
			struct in_addr *i = (struct in_addr *)CMSG_DATA(cmsg);
 | 
						|
 | 
						|
			if (dst4)
 | 
						|
				dst4->sin_addr = *i;
 | 
						|
 | 
						|
			break;
 | 
						|
		}
 | 
						|
#endif
 | 
						|
 | 
						|
#if defined(HAVE_IP_RECVIF) && defined(CMSG_IFINDEX)
 | 
						|
		if (cmsg->cmsg_type == IP_RECVIF)
 | 
						|
			if (ifindex)
 | 
						|
				*ifindex = CMSG_IFINDEX(cmsg);
 | 
						|
#endif
 | 
						|
	}
 | 
						|
}
 | 
						|
#else  /* PIM_IPV != 4 */
 | 
						|
static void cmsg_getdstaddr(struct msghdr *mh, struct sockaddr_storage *dst,
 | 
						|
			    ifindex_t *ifindex)
 | 
						|
{
 | 
						|
	struct cmsghdr *cmsg;
 | 
						|
	struct sockaddr_in6 *dst6 = (struct sockaddr_in6 *)dst;
 | 
						|
 | 
						|
	for (cmsg = CMSG_FIRSTHDR(mh); cmsg != NULL;
 | 
						|
	     cmsg = CMSG_NXTHDR(mh, cmsg)) {
 | 
						|
		if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
 | 
						|
		    (cmsg->cmsg_type == IPV6_PKTINFO)) {
 | 
						|
			struct in6_pktinfo *i;
 | 
						|
 | 
						|
			i = (struct in6_pktinfo *)CMSG_DATA(cmsg);
 | 
						|
 | 
						|
			if (dst6)
 | 
						|
				dst6->sin6_addr = i->ipi6_addr;
 | 
						|
			if (ifindex)
 | 
						|
				*ifindex = i->ipi6_ifindex;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
#endif /* PIM_IPV != 4 */
 | 
						|
 | 
						|
int pim_socket_recvfromto(int fd, uint8_t *buf, size_t len,
 | 
						|
			  struct sockaddr_storage *from, socklen_t *fromlen,
 | 
						|
			  struct sockaddr_storage *to, socklen_t *tolen,
 | 
						|
			  ifindex_t *ifindex)
 | 
						|
{
 | 
						|
	struct msghdr msgh;
 | 
						|
	struct iovec iov;
 | 
						|
	char cbuf[1000];
 | 
						|
	int err;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * IP_PKTINFO / IP_RECVDSTADDR don't yield sin_port.
 | 
						|
	 * Use getsockname() to get sin_port.
 | 
						|
	 */
 | 
						|
	if (to) {
 | 
						|
		socklen_t to_len = sizeof(*to);
 | 
						|
 | 
						|
		pim_socket_getsockname(fd, (struct sockaddr *)to, &to_len);
 | 
						|
 | 
						|
		if (tolen)
 | 
						|
			*tolen = sizeof(*to);
 | 
						|
	}
 | 
						|
 | 
						|
	memset(&msgh, 0, sizeof(msgh));
 | 
						|
	iov.iov_base = buf;
 | 
						|
	iov.iov_len = len;
 | 
						|
	msgh.msg_control = cbuf;
 | 
						|
	msgh.msg_controllen = sizeof(cbuf);
 | 
						|
	msgh.msg_name = from;
 | 
						|
	msgh.msg_namelen = fromlen ? *fromlen : 0;
 | 
						|
	msgh.msg_iov = &iov;
 | 
						|
	msgh.msg_iovlen = 1;
 | 
						|
	msgh.msg_flags = 0;
 | 
						|
 | 
						|
	err = recvmsg(fd, &msgh, 0);
 | 
						|
	if (err < 0)
 | 
						|
		return err;
 | 
						|
 | 
						|
	if (fromlen)
 | 
						|
		*fromlen = msgh.msg_namelen;
 | 
						|
 | 
						|
	cmsg_getdstaddr(&msgh, to, ifindex);
 | 
						|
 | 
						|
	return err; /* len */
 | 
						|
}
 | 
						|
 | 
						|
int pim_socket_getsockname(int fd, struct sockaddr *name, socklen_t *namelen)
 | 
						|
{
 | 
						|
	if (getsockname(fd, name, namelen)) {
 | 
						|
		int e = errno;
 | 
						|
		zlog_warn(
 | 
						|
			"Could not get Socket Name for socket fd=%d: errno=%d: %s",
 | 
						|
			fd, errno, safe_strerror(errno));
 | 
						|
		errno = e;
 | 
						|
		return PIM_SOCK_ERR_NAME;
 | 
						|
	}
 | 
						|
 | 
						|
	return PIM_SOCK_ERR_NONE;
 | 
						|
}
 |