diff options
Diffstat (limited to 'pimd/pim_sock.c')
-rw-r--r-- | pimd/pim_sock.c | 433 |
1 files changed, 433 insertions, 0 deletions
diff --git a/pimd/pim_sock.c b/pimd/pim_sock.c new file mode 100644 index 0000000..4b91bf0 --- /dev/null +++ b/pimd/pim_sock.c @@ -0,0 +1,433 @@ +/* + * 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; +} |