#define __APPLE_USE_RFC_3542
#include "udppktinfo.h"
#include <netinet/in.h>
#include <sys/socket.h>
#if defined(IP_PKTINFO) && defined(HAVE_STRUCT_IN_PKTINFO)
#define HAVE_IP_PKTINFO
#endif
#if defined(IPV6_PKTINFO) && defined(HAVE_STRUCT_IN6_PKTINFO)
#define HAVE_IPV6_PKTINFO
#endif
#if defined(HAVE_IP_PKTINFO) || defined(IP_SENDSRCADDR) || \
defined(HAVE_IPV6_PKTINFO)
#define HAVE_PKTINFO_SUPPORT
#endif
#if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO)
#define IPV6_RECVPKTINFO IPV6_PKTINFO
#endif
#if !defined(IP_RECVPKTINFO) && defined(IP_PKTINFO)
#define IP_RECVPKTINFO IP_PKTINFO
#endif
#if defined(CMSG_SPACE) && defined(HAVE_STRUCT_CMSGHDR) && \
defined(HAVE_PKTINFO_SUPPORT)
union pktinfo {
#ifdef HAVE_STRUCT_IN6_PKTINFO
struct in6_pktinfo pi6;
#endif
#ifdef HAVE_STRUCT_IN_PKTINFO
struct in_pktinfo pi4;
#endif
#ifdef IP_RECVDSTADDR
struct in_addr iaddr;
#endif
char c;
};
#endif
#ifdef HAVE_IP_PKTINFO
#define set_ipv4_pktinfo set_ipv4_recvpktinfo
static inline krb5_error_code
set_ipv4_recvpktinfo(int sock)
{
int sockopt = 1;
return setsockopt(sock, IPPROTO_IP, IP_RECVPKTINFO, &sockopt,
sizeof(sockopt));
}
#elif defined(IP_RECVDSTADDR)
#define set_ipv4_pktinfo set_ipv4_recvdstaddr
static inline krb5_error_code
set_ipv4_recvdstaddr(int sock)
{
int sockopt = 1;
return setsockopt(sock, IPPROTO_IP, IP_RECVDSTADDR, &sockopt,
sizeof(sockopt));
}
#else
#define set_ipv4_pktinfo(s) EINVAL
#endif
#ifdef HAVE_IPV6_PKTINFO
#define set_ipv6_pktinfo set_ipv6_recvpktinfo
static inline krb5_error_code
set_ipv6_recvpktinfo(int sock)
{
int sockopt = 1;
return setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &sockopt,
sizeof(sockopt));
}
#else
#define set_ipv6_pktinfo(s) EINVAL
#endif
krb5_error_code
set_pktinfo(int sock, int family)
{
switch (family) {
case AF_INET:
return set_ipv4_pktinfo(sock);
case AF_INET6:
return set_ipv6_pktinfo(sock);
default:
return EINVAL;
}
}
#if defined(HAVE_PKTINFO_SUPPORT) && defined(CMSG_SPACE)
static int
is_socket_bound_to_wildcard(int sock)
{
struct sockaddr_storage bound_addr;
socklen_t bound_addr_len = sizeof(bound_addr);
struct sockaddr *sa = ss2sa(&bound_addr);
if (getsockname(sock, sa, &bound_addr_len) < 0)
return -1;
if (!sa_is_inet(sa)) {
errno = EINVAL;
return -1;
}
return sa_is_wildcard(sa);
}
#ifdef HAVE_IP_PKTINFO
static inline struct in_pktinfo *
cmsg2pktinfo(struct cmsghdr *cmsgptr)
{
return (struct in_pktinfo *)(void *)CMSG_DATA(cmsgptr);
}
#define check_cmsg_v4_pktinfo check_cmsg_ip_pktinfo
static int
check_cmsg_ip_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr_in *to,
aux_addressing_info *auxaddr)
{
struct in_pktinfo *pktinfo;
if (cmsgptr->cmsg_level == IPPROTO_IP &&
cmsgptr->cmsg_type == IP_PKTINFO) {
memset(to, 0, sizeof(*to));
pktinfo = cmsg2pktinfo(cmsgptr);
to->sin_addr = pktinfo->ipi_addr;
to->sin_family = AF_INET;
return 1;
}
return 0;
}
#elif defined(IP_RECVDSTADDR)
static inline struct in_addr *
cmsg2sin(struct cmsghdr *cmsgptr)
{
return (struct in_addr *)(void *)CMSG_DATA(cmsgptr);
}
#define check_cmsg_v4_pktinfo check_cmsg_ip_recvdstaddr
static int
check_cmsg_ip_recvdstaddr(struct cmsghdr *cmsgptr, struct sockaddr_in *to,
aux_addressing_info *auxaddr)
{
struct in_addr *sin_addr;
if (cmsgptr->cmsg_level == IPPROTO_IP &&
cmsgptr->cmsg_type == IP_RECVDSTADDR) {
memset(to, 0, sizeof(*to));
sin_addr = cmsg2sin(cmsgptr);
to->sin_addr = *sin_addr;
to->sin_family = AF_INET;
return 1;
}
return 0;
}
#else
#define check_cmsg_v4_pktinfo(c, t, l, a) 0
#endif
#ifdef HAVE_IPV6_PKTINFO
static inline struct in6_pktinfo *
cmsg2pktinfo6(struct cmsghdr *cmsgptr)
{
return (struct in6_pktinfo *)(void *)CMSG_DATA(cmsgptr);
}
#define check_cmsg_v6_pktinfo check_cmsg_ipv6_pktinfo
static int
check_cmsg_ipv6_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr_in6 *to,
aux_addressing_info *auxaddr)
{
struct in6_pktinfo *pktinfo;
if (cmsgptr->cmsg_level == IPPROTO_IPV6 &&
cmsgptr->cmsg_type == IPV6_PKTINFO) {
memset(to, 0, sizeof(*to));
pktinfo = cmsg2pktinfo6(cmsgptr);
to->sin6_addr = pktinfo->ipi6_addr;
to->sin6_family = AF_INET6;
auxaddr->ipv6_ifindex = pktinfo->ipi6_ifindex;
return 1;
}
return 0;
}
#else
#define check_cmsg_v6_pktinfo(c, t, l, a) 0
#endif
static int
check_cmsg_pktinfo(struct cmsghdr *cmsgptr, struct sockaddr_storage *to,
aux_addressing_info *auxaddr)
{
return check_cmsg_v4_pktinfo(cmsgptr, ss2sin(to), auxaddr) ||
check_cmsg_v6_pktinfo(cmsgptr, ss2sin6(to), auxaddr);
}
krb5_error_code
recv_from_to(int sock, void *buf, size_t len, int flags,
struct sockaddr_storage *from, struct sockaddr_storage *to,
aux_addressing_info *auxaddr)
{
int r;
struct iovec iov;
char cmsg[CMSG_SPACE(sizeof(union pktinfo))];
struct cmsghdr *cmsgptr;
struct msghdr msg;
socklen_t fromlen = sizeof(*from);
r = is_socket_bound_to_wildcard(sock);
if (r < 0)
return errno;
if (to == NULL || !r)
return recvfrom(sock, buf, len, flags, ss2sa(from), &fromlen);
memset(to, 0x40, sizeof(*to));
to->ss_family = AF_UNSPEC;
iov.iov_base = buf;
iov.iov_len = len;
memset(&msg, 0, sizeof(msg));
msg.msg_name = ss2sa(from);
msg.msg_namelen = sizeof(*from);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg;
msg.msg_controllen = sizeof(cmsg);
r = recvmsg(sock, &msg, flags);
if (r < 0)
return r;
if (msg.msg_controllen) {
cmsgptr = CMSG_FIRSTHDR(&msg);
while (cmsgptr) {
if (check_cmsg_pktinfo(cmsgptr, to, auxaddr))
return r;
cmsgptr = CMSG_NXTHDR(&msg, cmsgptr);
}
}
return r;
}
#ifdef HAVE_IP_PKTINFO
#define set_msg_from_ipv4 set_msg_from_ip_pktinfo
static krb5_error_code
set_msg_from_ip_pktinfo(struct msghdr *msg, struct cmsghdr *cmsgptr,
const struct sockaddr_in *from,
aux_addressing_info *auxaddr)
{
struct in_pktinfo *p = cmsg2pktinfo(cmsgptr);
cmsgptr->cmsg_level = IPPROTO_IP;
cmsgptr->cmsg_type = IP_PKTINFO;
cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
p->ipi_spec_dst = from->sin_addr;
msg->msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
return 0;
}
#elif defined(IP_SENDSRCADDR)
#define set_msg_from_ipv4 set_msg_from_ip_sendsrcaddr
static krb5_error_code
set_msg_from_ip_sendsrcaddr(struct msghdr *msg, struct cmsghdr *cmsgptr,
const struct sockaddr_in *from,
aux_addressing_info *auxaddr)
{
struct in_addr *sin_addr = cmsg2sin(cmsgptr);
cmsgptr->cmsg_level = IPPROTO_IP;
cmsgptr->cmsg_type = IP_SENDSRCADDR;
cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
msg->msg_controllen = CMSG_SPACE(sizeof(struct in_addr));
*sin_addr = from->sin_addr;
return 0;
}
#else
#define set_msg_from_ipv4(m, c, f, l, a) EINVAL
#endif
#ifdef HAVE_IPV6_PKTINFO
#define set_msg_from_ipv6 set_msg_from_ipv6_pktinfo
static krb5_error_code
set_msg_from_ipv6_pktinfo(struct msghdr *msg, struct cmsghdr *cmsgptr,
const struct sockaddr_in6 *from,
aux_addressing_info *auxaddr)
{
struct in6_pktinfo *p = cmsg2pktinfo6(cmsgptr);
cmsgptr->cmsg_level = IPPROTO_IPV6;
cmsgptr->cmsg_type = IPV6_PKTINFO;
cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
p->ipi6_addr = from->sin6_addr;
if (IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr))
p->ipi6_ifindex = auxaddr->ipv6_ifindex;
msg->msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
return 0;
}
#else
#define set_msg_from_ipv6(m, c, f, l, a) EINVAL
#endif
static krb5_error_code
set_msg_from(struct msghdr *msg, struct cmsghdr *cmsgptr,
const struct sockaddr *from, aux_addressing_info *auxaddr)
{
switch (from->sa_family) {
case AF_INET:
return set_msg_from_ipv4(msg, cmsgptr, sa2sin(from), auxaddr);
case AF_INET6:
return set_msg_from_ipv6(msg, cmsgptr, sa2sin6(from), auxaddr);
}
return EINVAL;
}
krb5_error_code
send_to_from(int sock, void *buf, size_t len, int flags,
const struct sockaddr *to, const struct sockaddr *from,
aux_addressing_info *auxaddr)
{
int r;
struct iovec iov;
struct msghdr msg;
struct cmsghdr *cmsgptr;
char cbuf[CMSG_SPACE(sizeof(union pktinfo))];
r = is_socket_bound_to_wildcard(sock);
if (r < 0)
return errno;
if (from == NULL || from->sa_family != to->sa_family || !r)
goto use_sendto;
iov.iov_base = buf;
iov.iov_len = len;
if (iov.iov_len != len)
return EINVAL;
memset(cbuf, 0, sizeof(cbuf));
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)to;
msg.msg_namelen = sa_socklen(to);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cbuf;
msg.msg_controllen = sizeof(cbuf);
cmsgptr = CMSG_FIRSTHDR(&msg);
msg.msg_controllen = 0;
if (set_msg_from(&msg, cmsgptr, from, auxaddr))
goto use_sendto;
return sendmsg(sock, &msg, flags);
use_sendto:
return sendto(sock, buf, len, flags, to, sa_socklen(to));
}
#else
krb5_error_code
recv_from_to(int sock, void *buf, size_t len, int flags,
struct sockaddr_storage *from, struct sockaddr_storage *to,
aux_addressing_info *auxaddr)
{
socklen_t fromlen = sizeof(*from);
if (to != NULL) {
memset(to, 0x40, sizeof(*to));
to->ss_family = AF_UNSPEC;
}
return recvfrom(sock, buf, len, flags, ss2sa(from), &fromlen);
}
krb5_error_code
send_to_from(int sock, void *buf, size_t len, int flags,
const struct sockaddr *to, const struct sockaddr *from,
aux_addressing_info *auxaddr)
{
return sendto(sock, buf, len, flags, to, sa_socklen(to));
}
#endif