Path: blob/main/crypto/krb5/src/lib/apputils/udppktinfo.c
39563 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1/*2* Copyright 2016 by the Massachusetts Institute of Technology.3* All Rights Reserved.4*5* Export of this software from the United States of America may6* require a specific license from the United States Government.7* It is the responsibility of any person or organization contemplating8* export to obtain such a license before exporting.9*10* WITHIN THAT CONSTRAINT, permission to use, copy, modify, and11* distribute this software and its documentation for any purpose and12* without fee is hereby granted, provided that the above copyright13* notice appear in all copies and that both that copyright notice and14* this permission notice appear in supporting documentation, and that15* the name of M.I.T. not be used in advertising or publicity pertaining16* to distribution of the software without specific, written prior17* permission. Furthermore if you modify this software you must label18* your software as modified software and not distribute it in such a19* fashion that it might be confused with the original M.I.T. software.20* M.I.T. makes no representations about the suitability of21* this software for any purpose. It is provided "as is" without express22* or implied warranty.23*/2425/* macOS requires this define for IPV6_PKTINFO. */26#define __APPLE_USE_RFC_35422728#include "udppktinfo.h"2930#include <netinet/in.h>31#include <sys/socket.h>3233#if defined(IP_PKTINFO) && defined(HAVE_STRUCT_IN_PKTINFO)34#define HAVE_IP_PKTINFO35#endif3637#if defined(IPV6_PKTINFO) && defined(HAVE_STRUCT_IN6_PKTINFO)38#define HAVE_IPV6_PKTINFO39#endif4041#if defined(HAVE_IP_PKTINFO) || defined(IP_SENDSRCADDR) || \42defined(HAVE_IPV6_PKTINFO)43#define HAVE_PKTINFO_SUPPORT44#endif4546/* Use RFC 3542 API below, but fall back from IPV6_RECVPKTINFO to IPV6_PKTINFO47* for RFC 2292 implementations. */48#if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO)49#define IPV6_RECVPKTINFO IPV6_PKTINFO50#endif5152/* Parallel, though not standardized. */53#if !defined(IP_RECVPKTINFO) && defined(IP_PKTINFO)54#define IP_RECVPKTINFO IP_PKTINFO55#endif /* IP_RECVPKTINFO */5657#if defined(CMSG_SPACE) && defined(HAVE_STRUCT_CMSGHDR) && \58defined(HAVE_PKTINFO_SUPPORT)59union pktinfo {60#ifdef HAVE_STRUCT_IN6_PKTINFO61struct in6_pktinfo pi6;62#endif63#ifdef HAVE_STRUCT_IN_PKTINFO64struct in_pktinfo pi4;65#endif66#ifdef IP_RECVDSTADDR67struct in_addr iaddr;68#endif69char c;70};71#endif /* HAVE_IPV6_PKTINFO && HAVE_STRUCT_CMSGHDR && HAVE_PKTINFO_SUPPORT */7273#ifdef HAVE_IP_PKTINFO7475#define set_ipv4_pktinfo set_ipv4_recvpktinfo76static inline krb5_error_code77set_ipv4_recvpktinfo(int sock)78{79int sockopt = 1;80return setsockopt(sock, IPPROTO_IP, IP_RECVPKTINFO, &sockopt,81sizeof(sockopt));82}8384#elif defined(IP_RECVDSTADDR) /* HAVE_IP_PKTINFO */8586#define set_ipv4_pktinfo set_ipv4_recvdstaddr87static inline krb5_error_code88set_ipv4_recvdstaddr(int sock)89{90int sockopt = 1;91return setsockopt(sock, IPPROTO_IP, IP_RECVDSTADDR, &sockopt,92sizeof(sockopt));93}9495#else /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */96#define set_ipv4_pktinfo(s) EINVAL97#endif /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */9899#ifdef HAVE_IPV6_PKTINFO100101#define set_ipv6_pktinfo set_ipv6_recvpktinfo102static inline krb5_error_code103set_ipv6_recvpktinfo(int sock)104{105int sockopt = 1;106return setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &sockopt,107sizeof(sockopt));108}109110#else /* HAVE_IPV6_PKTINFO */111#define set_ipv6_pktinfo(s) EINVAL112#endif /* HAVE_IPV6_PKTINFO */113114/*115* Set pktinfo option on a socket. Takes a socket and the socket address family116* as arguments.117*118* Returns 0 on success, EINVAL if pktinfo is not supported for the address119* family.120*/121krb5_error_code122set_pktinfo(int sock, int family)123{124switch (family) {125case AF_INET:126return set_ipv4_pktinfo(sock);127case AF_INET6:128return set_ipv6_pktinfo(sock);129default:130return EINVAL;131}132}133134#if defined(HAVE_PKTINFO_SUPPORT) && defined(CMSG_SPACE)135136/*137* Check if a socket is bound to a wildcard address.138* Returns 1 if it is, 0 if it's bound to a specific address, or -1 on error139* with errno set to the error.140*/141static int142is_socket_bound_to_wildcard(int sock)143{144struct sockaddr_storage bound_addr;145socklen_t bound_addr_len = sizeof(bound_addr);146struct sockaddr *sa = ss2sa(&bound_addr);147148if (getsockname(sock, sa, &bound_addr_len) < 0)149return -1;150151if (!sa_is_inet(sa)) {152errno = EINVAL;153return -1;154}155156return sa_is_wildcard(sa);157}158159#ifdef HAVE_IP_PKTINFO160161static inline struct in_pktinfo *162cmsg2pktinfo(struct cmsghdr *cmsgptr)163{164return (struct in_pktinfo *)(void *)CMSG_DATA(cmsgptr);165}166167#define check_cmsg_v4_pktinfo check_cmsg_ip_pktinfo168static int169check_cmsg_ip_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr_in *to,170aux_addressing_info *auxaddr)171{172struct in_pktinfo *pktinfo;173174if (cmsgptr->cmsg_level == IPPROTO_IP &&175cmsgptr->cmsg_type == IP_PKTINFO) {176memset(to, 0, sizeof(*to));177pktinfo = cmsg2pktinfo(cmsgptr);178to->sin_addr = pktinfo->ipi_addr;179to->sin_family = AF_INET;180return 1;181}182return 0;183}184185#elif defined(IP_RECVDSTADDR) /* HAVE_IP_PKTINFO */186187static inline struct in_addr *188cmsg2sin(struct cmsghdr *cmsgptr)189{190return (struct in_addr *)(void *)CMSG_DATA(cmsgptr);191}192193#define check_cmsg_v4_pktinfo check_cmsg_ip_recvdstaddr194static int195check_cmsg_ip_recvdstaddr(struct cmsghdr *cmsgptr, struct sockaddr_in *to,196aux_addressing_info *auxaddr)197{198struct in_addr *sin_addr;199200if (cmsgptr->cmsg_level == IPPROTO_IP &&201cmsgptr->cmsg_type == IP_RECVDSTADDR) {202memset(to, 0, sizeof(*to));203sin_addr = cmsg2sin(cmsgptr);204to->sin_addr = *sin_addr;205to->sin_family = AF_INET;206return 1;207}208return 0;209}210211#else /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */212#define check_cmsg_v4_pktinfo(c, t, l, a) 0213#endif /* HAVE_IP_PKTINFO || IP_RECVDSTADDR */214215#ifdef HAVE_IPV6_PKTINFO216217static inline struct in6_pktinfo *218cmsg2pktinfo6(struct cmsghdr *cmsgptr)219{220return (struct in6_pktinfo *)(void *)CMSG_DATA(cmsgptr);221}222223#define check_cmsg_v6_pktinfo check_cmsg_ipv6_pktinfo224static int225check_cmsg_ipv6_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr_in6 *to,226aux_addressing_info *auxaddr)227{228struct in6_pktinfo *pktinfo;229230if (cmsgptr->cmsg_level == IPPROTO_IPV6 &&231cmsgptr->cmsg_type == IPV6_PKTINFO) {232memset(to, 0, sizeof(*to));233pktinfo = cmsg2pktinfo6(cmsgptr);234to->sin6_addr = pktinfo->ipi6_addr;235to->sin6_family = AF_INET6;236auxaddr->ipv6_ifindex = pktinfo->ipi6_ifindex;237return 1;238}239return 0;240}241#else /* HAVE_IPV6_PKTINFO */242#define check_cmsg_v6_pktinfo(c, t, l, a) 0243#endif /* HAVE_IPV6_PKTINFO */244245static int246check_cmsg_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr_storage *to,247aux_addressing_info *auxaddr)248{249return check_cmsg_v4_pktinfo(cmsgptr, ss2sin(to), auxaddr) ||250check_cmsg_v6_pktinfo(cmsgptr, ss2sin6(to), auxaddr);251}252253/*254* Receive a message from a socket.255*256* Arguments:257* sock258* buf - The buffer to store the message in.259* len - buf length260* flags261* from - Set to the address that sent the message262* to - Set to the address that the message was sent to if possible.263* May not be set in certain cases such as if pktinfo support is264* missing. May be NULL.265* auxaddr - Miscellaneous address information.266*267* Returns 0 on success, otherwise an error code.268*/269krb5_error_code270recv_from_to(int sock, void *buf, size_t len, int flags,271struct sockaddr_storage *from, struct sockaddr_storage *to,272aux_addressing_info *auxaddr)273274{275int r;276struct iovec iov;277char cmsg[CMSG_SPACE(sizeof(union pktinfo))];278struct cmsghdr *cmsgptr;279struct msghdr msg;280socklen_t fromlen = sizeof(*from);281282/* Don't use pktinfo if the socket isn't bound to a wildcard address. */283r = is_socket_bound_to_wildcard(sock);284if (r < 0)285return errno;286287if (to == NULL || !r)288return recvfrom(sock, buf, len, flags, ss2sa(from), &fromlen);289290/* Clobber with something recognizable in case we can't extract the address291* but try to use it anyways. */292memset(to, 0x40, sizeof(*to));293to->ss_family = AF_UNSPEC;294295iov.iov_base = buf;296iov.iov_len = len;297memset(&msg, 0, sizeof(msg));298msg.msg_name = ss2sa(from);299msg.msg_namelen = sizeof(*from);300msg.msg_iov = &iov;301msg.msg_iovlen = 1;302msg.msg_control = cmsg;303msg.msg_controllen = sizeof(cmsg);304305r = recvmsg(sock, &msg, flags);306if (r < 0)307return r;308309/*310* On Darwin (and presumably all *BSD with KAME stacks), CMSG_FIRSTHDR311* doesn't check for a non-zero controllen. RFC 3542 recommends making312* this check, even though the (new) spec for CMSG_FIRSTHDR says it's313* supposed to do the check.314*/315if (msg.msg_controllen) {316cmsgptr = CMSG_FIRSTHDR(&msg);317while (cmsgptr) {318if (check_cmsg_pktinfo(cmsgptr, to, auxaddr))319return r;320cmsgptr = CMSG_NXTHDR(&msg, cmsgptr);321}322}323/* No info about destination addr was available. */324return r;325}326327#ifdef HAVE_IP_PKTINFO328329#define set_msg_from_ipv4 set_msg_from_ip_pktinfo330static krb5_error_code331set_msg_from_ip_pktinfo(struct msghdr *msg, struct cmsghdr *cmsgptr,332const struct sockaddr_in *from,333aux_addressing_info *auxaddr)334{335struct in_pktinfo *p = cmsg2pktinfo(cmsgptr);336337cmsgptr->cmsg_level = IPPROTO_IP;338cmsgptr->cmsg_type = IP_PKTINFO;339cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));340p->ipi_spec_dst = from->sin_addr;341342msg->msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));343return 0;344}345346#elif defined(IP_SENDSRCADDR) /* HAVE_IP_PKTINFO */347348#define set_msg_from_ipv4 set_msg_from_ip_sendsrcaddr349static krb5_error_code350set_msg_from_ip_sendsrcaddr(struct msghdr *msg, struct cmsghdr *cmsgptr,351const struct sockaddr_in *from,352aux_addressing_info *auxaddr)353{354struct in_addr *sin_addr = cmsg2sin(cmsgptr);355356cmsgptr->cmsg_level = IPPROTO_IP;357cmsgptr->cmsg_type = IP_SENDSRCADDR;358cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_addr));359msg->msg_controllen = CMSG_SPACE(sizeof(struct in_addr));360*sin_addr = from->sin_addr;361return 0;362}363364#else /* HAVE_IP_PKTINFO || IP_SENDSRCADDR */365#define set_msg_from_ipv4(m, c, f, l, a) EINVAL366#endif /* HAVE_IP_PKTINFO || IP_SENDSRCADDR */367368#ifdef HAVE_IPV6_PKTINFO369370#define set_msg_from_ipv6 set_msg_from_ipv6_pktinfo371static krb5_error_code372set_msg_from_ipv6_pktinfo(struct msghdr *msg, struct cmsghdr *cmsgptr,373const struct sockaddr_in6 *from,374aux_addressing_info *auxaddr)375{376struct in6_pktinfo *p = cmsg2pktinfo6(cmsgptr);377378cmsgptr->cmsg_level = IPPROTO_IPV6;379cmsgptr->cmsg_type = IPV6_PKTINFO;380cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));381382p->ipi6_addr = from->sin6_addr;383/*384* Because of the possibility of asymmetric routing, we385* normally don't want to specify an interface. However,386* macOS doesn't like sending from a link-local address387* (which can come up in testing at least, if you wind up388* with a "foo.local" name) unless we do specify the389* interface.390*/391if (IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr))392p->ipi6_ifindex = auxaddr->ipv6_ifindex;393/* otherwise, already zero */394395msg->msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));396return 0;397}398399#else /* HAVE_IPV6_PKTINFO */400#define set_msg_from_ipv6(m, c, f, l, a) EINVAL401#endif /* HAVE_IPV6_PKTINFO */402403static krb5_error_code404set_msg_from(struct msghdr *msg, struct cmsghdr *cmsgptr,405const struct sockaddr *from, aux_addressing_info *auxaddr)406{407switch (from->sa_family) {408case AF_INET:409return set_msg_from_ipv4(msg, cmsgptr, sa2sin(from), auxaddr);410case AF_INET6:411return set_msg_from_ipv6(msg, cmsgptr, sa2sin6(from), auxaddr);412}413414return EINVAL;415}416417/*418* Send a message to an address.419*420* Arguments:421* sock422* buf - The message to send.423* len - buf length424* flags425* to - The address to send the message to.426* from - The address to attempt to send the message from. May be NULL.427* auxaddr - Miscellaneous address information.428*429* Returns 0 on success, otherwise an error code.430*/431krb5_error_code432send_to_from(int sock, void *buf, size_t len, int flags,433const struct sockaddr *to, const struct sockaddr *from,434aux_addressing_info *auxaddr)435{436int r;437struct iovec iov;438struct msghdr msg;439struct cmsghdr *cmsgptr;440char cbuf[CMSG_SPACE(sizeof(union pktinfo))];441442/* Don't use pktinfo if the socket isn't bound to a wildcard address. */443r = is_socket_bound_to_wildcard(sock);444if (r < 0)445return errno;446447if (from == NULL || from->sa_family != to->sa_family || !r)448goto use_sendto;449450iov.iov_base = buf;451iov.iov_len = len;452/* Truncation? */453if (iov.iov_len != len)454return EINVAL;455memset(cbuf, 0, sizeof(cbuf));456memset(&msg, 0, sizeof(msg));457msg.msg_name = (void *)to;458msg.msg_namelen = sa_socklen(to);459msg.msg_iov = &iov;460msg.msg_iovlen = 1;461msg.msg_control = cbuf;462/* CMSG_FIRSTHDR needs a non-zero controllen, or it'll return NULL on463* Linux. */464msg.msg_controllen = sizeof(cbuf);465cmsgptr = CMSG_FIRSTHDR(&msg);466msg.msg_controllen = 0;467468if (set_msg_from(&msg, cmsgptr, from, auxaddr))469goto use_sendto;470return sendmsg(sock, &msg, flags);471472use_sendto:473return sendto(sock, buf, len, flags, to, sa_socklen(to));474}475476#else /* HAVE_PKTINFO_SUPPORT && CMSG_SPACE */477478krb5_error_code479recv_from_to(int sock, void *buf, size_t len, int flags,480struct sockaddr_storage *from, struct sockaddr_storage *to,481aux_addressing_info *auxaddr)482{483socklen_t fromlen = sizeof(*from);484485if (to != NULL) {486/* Clobber with something recognizable in case we try to use the487* address. */488memset(to, 0x40, sizeof(*to));489to->ss_family = AF_UNSPEC;490}491492return recvfrom(sock, buf, len, flags, ss2sa(from), &fromlen);493}494495krb5_error_code496send_to_from(int sock, void *buf, size_t len, int flags,497const struct sockaddr *to, const struct sockaddr *from,498aux_addressing_info *auxaddr)499{500return sendto(sock, buf, len, flags, to, sa_socklen(to));501}502503#endif /* HAVE_PKTINFO_SUPPORT && CMSG_SPACE */504505506