diff options
Diffstat (limited to 'src/lib/udpfromto.c')
-rw-r--r-- | src/lib/udpfromto.c | 579 |
1 files changed, 579 insertions, 0 deletions
diff --git a/src/lib/udpfromto.c b/src/lib/udpfromto.c new file mode 100644 index 0000000..d54d8a6 --- /dev/null +++ b/src/lib/udpfromto.c @@ -0,0 +1,579 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * @file udpfromto.c + * @brief Like recvfrom, but also stores the destination IP address. Useful on multihomed hosts. + * + * @copyright 2007 Alan DeKok <aland@deployingradius.com> + * @copyright 2002 Miquel van Smoorenburg + */ +RCSID("$Id$") + +#include <freeradius-devel/udpfromto.h> + +#ifdef WITH_UDPFROMTO + +#ifdef HAVE_SYS_UIO_H +# include <sys/uio.h> +#endif + +#include <fcntl.h> + +/* + * More portability idiocy + * Mac OSX Lion doesn't define SOL_IP. But IPPROTO_IP works. + */ +#ifndef SOL_IP +# define SOL_IP IPPROTO_IP +#endif + +/* + * glibc 2.4 and uClibc 0.9.29 introduce IPV6_RECVPKTINFO etc. and + * change IPV6_PKTINFO This is only supported in Linux kernel >= + * 2.6.14 + * + * This is only an approximation because the kernel version that libc + * was compiled against could be older or newer than the one being + * run. But this should not be a problem -- we just keep using the + * old kernel interface. + */ +#ifdef __linux__ +# ifdef IPV6_RECVPKTINFO +# include <linux/version.h> +# if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14) +# ifdef IPV6_2292PKTINFO +# undef IPV6_RECVPKTINFO +# undef IPV6_PKTINFO +# define IPV6_RECVPKTINFO IPV6_2292PKTINFO +# define IPV6_PKTINFO IPV6_2292PKTINFO +# endif +# endif +/* Fall back to the legacy socket option if IPV6_RECVPKTINFO isn't defined */ +# elif defined(IPV6_2292PKTINFO) +# define IPV6_RECVPKTINFO IPV6_2292PKTINFO +# endif +#else + +/* + * For everything that's not Linux we assume RFC 3542 compliance + * - setsockopt() takes IPV6_RECVPKTINFO + * - cmsg_type is IPV6_PKTINFO (in sendmsg, recvmsg) + * + * If we don't have IPV6_RECVPKTINFO defined but do have IPV6_PKTINFO + * defined, chances are the API is RFC2292 compliant and we need to use + * IPV6_PKTINFO for both. + */ +# if !defined(IPV6_RECVPKTINFO) && defined(IPV6_PKTINFO) +# define IPV6_RECVPKTINFO IPV6_PKTINFO + +/* + * Ensure IPV6_RECVPKTINFO is not defined somehow if we have we + * don't have IPV6_PKTINFO. + */ +# elif !defined(IPV6_PKTINFO) +# undef IPV6_RECVPKTINFO +# endif +#endif + +int udpfromto_init(int s) +{ + int proto, flag = 0, opt = 1; + struct sockaddr_storage si; + socklen_t si_len = sizeof(si); + + errno = ENOSYS; + + /* + * Clang analyzer doesn't see that getsockname initialises + * the memory passed to it. + */ +#ifdef __clang_analyzer__ + memset(&si, 0, sizeof(si)); +#endif + + if (getsockname(s, (struct sockaddr *) &si, &si_len) < 0) { + return -1; + } + + if (si.ss_family == AF_INET) { +#ifdef HAVE_IP_PKTINFO + /* + * Linux + */ + proto = SOL_IP; + flag = IP_PKTINFO; +#else +# ifdef IP_RECVDSTADDR + + /* + * Set the IP_RECVDSTADDR option (BSD). Note: + * IP_RECVDSTADDR == IP_SENDSRCADDR + */ + proto = IPPROTO_IP; + flag = IP_RECVDSTADDR; +# else + return -1; +# endif +#endif + +#if defined(AF_INET6) && defined(IPV6_PKTINFO) + } else if (si.ss_family == AF_INET6) { + /* + * This should actually be standard IPv6 + */ + proto = IPPROTO_IPV6; + + /* + * Work around Linux-specific hackery. + */ + flag = IPV6_RECVPKTINFO; + } else { +#endif + + /* + * Unknown AF. Return an error if possible. + */ +# ifdef EPROTONOSUPPORT + errno = EPROTONOSUPPORT; +# endif + return -1; + } + + return setsockopt(s, proto, flag, &opt, sizeof(opt)); +} + +int recvfromto(int s, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromlen, + struct sockaddr *to, socklen_t *tolen) +{ + struct msghdr msgh; + struct cmsghdr *cmsg; + struct iovec iov; + char cbuf[256]; + int err; + struct sockaddr_storage si; + socklen_t si_len = sizeof(si); + +#if !defined(IP_PKTINFO) && !defined(IP_RECVDSTADDR) && !defined(IPV6_PKTINFO) + /* + * If the recvmsg() flags aren't defined, fall back to + * using recvfrom(). + */ + to = NULL: +#endif + + /* + * Catch the case where the caller passes invalid arguments. + */ + if (!to || !tolen) return recvfrom(s, buf, len, flags, from, fromlen); + + /* + * Clang analyzer doesn't see that getsockname initialises + * the memory passed to it. + */ +#ifdef __clang_analyzer__ + memset(&si, 0, sizeof(si)); +#endif + + /* + * recvmsg doesn't provide sin_port so we have to + * retrieve it using getsockname(). + */ + if (getsockname(s, (struct sockaddr *)&si, &si_len) < 0) { + return -1; + } + + /* + * Initialize the 'to' address. It may be INADDR_ANY here, + * with a more specific address given by recvmsg(), below. + */ + if (si.ss_family == AF_INET) { +#if !defined(IP_PKTINFO) && !defined(IP_RECVDSTADDR) + return recvfrom(s, buf, len, flags, from, fromlen); +#else + struct sockaddr_in *dst = (struct sockaddr_in *) to; + struct sockaddr_in *src = (struct sockaddr_in *) &si; + + if (*tolen < sizeof(*dst)) { + errno = EINVAL; + return -1; + } + *tolen = sizeof(*dst); + *dst = *src; +#endif + } + +#ifdef AF_INET6 + else if (si.ss_family == AF_INET6) { +#if !defined(IPV6_PKTINFO) + return recvfrom(s, buf, len, flags, from, fromlen); +#else + struct sockaddr_in6 *dst = (struct sockaddr_in6 *) to; + struct sockaddr_in6 *src = (struct sockaddr_in6 *) &si; + + if (*tolen < sizeof(*dst)) { + errno = EINVAL; + return -1; + } + *tolen = sizeof(*dst); + *dst = *src; +#endif + } +#endif + /* + * Unknown address family. + */ + else { + errno = EINVAL; + return -1; + } + + /* Set up iov and msgh structures. */ + memset(&cbuf, 0, sizeof(cbuf)); + memset(&msgh, 0, sizeof(struct msghdr)); + 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; + + /* Receive one packet. */ + if ((err = recvmsg(s, &msgh, flags)) < 0) { + return err; + } + + if (fromlen) *fromlen = msgh.msg_namelen; + + /* Process auxiliary received data in msgh */ + for (cmsg = CMSG_FIRSTHDR(&msgh); + cmsg != NULL; + cmsg = CMSG_NXTHDR(&msgh,cmsg)) { + +#ifdef IP_PKTINFO + if ((cmsg->cmsg_level == SOL_IP) && + (cmsg->cmsg_type == IP_PKTINFO)) { + struct in_pktinfo *i = + (struct in_pktinfo *) CMSG_DATA(cmsg); + ((struct sockaddr_in *)to)->sin_addr = i->ipi_addr; + *tolen = sizeof(struct sockaddr_in); + break; + } +#endif + +#ifdef IP_RECVDSTADDR + if ((cmsg->cmsg_level == IPPROTO_IP) && + (cmsg->cmsg_type == IP_RECVDSTADDR)) { + struct in_addr *i = (struct in_addr *) CMSG_DATA(cmsg); + ((struct sockaddr_in *)to)->sin_addr = *i; + *tolen = sizeof(struct sockaddr_in); + break; + } +#endif + +#ifdef IPV6_PKTINFO + if ((cmsg->cmsg_level == IPPROTO_IPV6) && + (cmsg->cmsg_type == IPV6_PKTINFO)) { + struct in6_pktinfo *i = + (struct in6_pktinfo *) CMSG_DATA(cmsg); + ((struct sockaddr_in6 *)to)->sin6_addr = i->ipi6_addr; + *tolen = sizeof(struct sockaddr_in6); + break; + } +#endif + } + + return err; +} + +int sendfromto(int s, void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t fromlen, + struct sockaddr *to, socklen_t tolen) +{ + struct msghdr msgh; + struct iovec iov; + char cbuf[256]; + + /* + * Unknown address family, die. + */ + if (from && (from->sa_family != AF_INET) && (from->sa_family != AF_INET6)) { + errno = EINVAL; + return -1; + } + +#ifdef __FreeBSD__ + /* + * FreeBSD is extra pedantic about the use of IP_SENDSRCADDR, + * and sendmsg will fail with EINVAL if IP_SENDSRCADDR is used + * with a socket which is bound to something other than + * INADDR_ANY + */ + struct sockaddr bound; + socklen_t bound_len = sizeof(bound); + + if (getsockname(s, &bound, &bound_len) < 0) { + return -1; + } + + switch (bound.sa_family) { + case AF_INET: + if (((struct sockaddr_in *) &bound)->sin_addr.s_addr != INADDR_ANY) { + from = NULL; + } + break; + + case AF_INET6: + if (!IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *) &bound)->sin6_addr)) { + from = NULL; + } + break; + } +#endif /* !__FreeBSD__ */ + + /* + * If the sendmsg() flags aren't defined, fall back to + * using sendto(). These flags are defined on FreeBSD, + * but laying it out this way simplifies the look of the + * code. + */ +# if !defined(IP_PKTINFO) && !defined(IP_SENDSRCADDR) + if (from && from->sa_family == AF_INET) { + from = NULL; + } +# endif + +# if !defined(IPV6_PKTINFO) + if (from && from->sa_family == AF_INET6) { + from = NULL; + } +# endif + + /* + * No "from", just use regular sendto. + */ + if (!from || (fromlen == 0)) { + return sendto(s, buf, len, flags, to, tolen); + } + + /* Set up control buffer iov and msgh structures. */ + memset(&cbuf, 0, sizeof(cbuf)); + memset(&msgh, 0, sizeof(msgh)); + memset(&iov, 0, sizeof(iov)); + iov.iov_base = buf; + iov.iov_len = len; + + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_name = to; + msgh.msg_namelen = tolen; + +# if defined(IP_PKTINFO) || defined(IP_SENDSRCADDR) + if (from->sa_family == AF_INET) { + struct sockaddr_in *s4 = (struct sockaddr_in *) from; + +# ifdef IP_PKTINFO + struct cmsghdr *cmsg; + struct in_pktinfo *pkt; + + msgh.msg_control = cbuf; + msgh.msg_controllen = CMSG_SPACE(sizeof(*pkt)); + + cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = SOL_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(*pkt)); + + pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); + memset(pkt, 0, sizeof(*pkt)); + pkt->ipi_spec_dst = s4->sin_addr; + +# elif defined(IP_SENDSRCADDR) + struct cmsghdr *cmsg; + struct in_addr *in; + + msgh.msg_control = cbuf; + msgh.msg_controllen = CMSG_SPACE(sizeof(*in)); + + cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_SENDSRCADDR; + cmsg->cmsg_len = CMSG_LEN(sizeof(*in)); + + in = (struct in_addr *) CMSG_DATA(cmsg); + *in = s4->sin_addr; +# endif + } +#endif + +# if defined(IPV6_PKTINFO) + if (from->sa_family == AF_INET6) { + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) from; + + struct cmsghdr *cmsg; + struct in6_pktinfo *pkt; + + msgh.msg_control = cbuf; + msgh.msg_controllen = CMSG_SPACE(sizeof(*pkt)); + + cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(*pkt)); + + pkt = (struct in6_pktinfo *) CMSG_DATA(cmsg); + memset(pkt, 0, sizeof(*pkt)); + pkt->ipi6_addr = s6->sin6_addr; + } +# endif /* IPV6_PKTINFO */ + + return sendmsg(s, &msgh, flags); +} + + +#ifdef TESTING +/* + * Small test program to test recvfromto/sendfromto + * + * use a virtual IP address as first argument to test + * + * reply packet should originate from virtual IP and not + * from the default interface the alias is bound to + */ +# include <sys/wait.h> + +# define DEF_PORT 20000 /* default port to listen on */ +# define DESTIP "127.0.0.1" /* send packet to localhost per default */ +# define TESTSTRING "foo" /* what to send */ +# define TESTLEN 4 /* 4 bytes */ + +int main(int argc, char **argv) +{ + struct sockaddr_in from, to, in; + char buf[TESTLEN]; + char *destip = DESTIP; + uint16_t port = DEF_PORT; + int n, server_socket, client_socket, fl, tl, pid; + + if (argc > 1) destip = argv[1]; + if (argc > 2) port = atoi(argv[2]); + + in.sin_family = AF_INET; + in.sin_addr.s_addr = INADDR_ANY; + in.sin_port = htons(port); + fl = tl = sizeof(struct sockaddr_in); + memset(&from, 0, sizeof(from)); + memset(&to, 0, sizeof(to)); + + switch (pid = fork()) { + case -1: + perror("fork"); + return 0; + case 0: + /* child */ + usleep(100000); + goto client; + } + + /* parent: server */ + server_socket = socket(PF_INET, SOCK_DGRAM, 0); + if (udpfromto_init(server_socket) != 0) { + perror("udpfromto_init\n"); + waitpid(pid, NULL, WNOHANG); + return 0; + } + + if (bind(server_socket, (struct sockaddr *)&in, sizeof(in)) < 0) { + perror("server: bind"); + waitpid(pid, NULL, WNOHANG); + return 0; + } + + printf("server: waiting for packets on INADDR_ANY:%d\n", port); + if ((n = recvfromto(server_socket, buf, sizeof(buf), 0, + (struct sockaddr *)&from, &fl, + (struct sockaddr *)&to, &tl)) < 0) { + perror("server: recvfromto"); + waitpid(pid, NULL, WNOHANG); + return 0; + } + + printf("server: received a packet of %d bytes [%s] ", n, buf); + printf("(src ip:port %s:%d ", + inet_ntoa(from.sin_addr), ntohs(from.sin_port)); + printf(" dst ip:port %s:%d)\n", + inet_ntoa(to.sin_addr), ntohs(to.sin_port)); + + printf("server: replying from address packet was received on to source address\n"); + + if ((n = sendfromto(server_socket, buf, n, 0, + (struct sockaddr *)&to, tl, + (struct sockaddr *)&from, fl)) < 0) { + perror("server: sendfromto"); + } + + waitpid(pid, NULL, 0); + return 0; + +client: + close(server_socket); + client_socket = socket(PF_INET, SOCK_DGRAM, 0); + if (udpfromto_init(client_socket) != 0) { + perror("udpfromto_init"); + fr_exit_now(0); + } + /* bind client on different port */ + in.sin_port = htons(port+1); + if (bind(client_socket, (struct sockaddr *)&in, sizeof(in)) < 0) { + perror("client: bind"); + fr_exit_now(0); + } + + in.sin_port = htons(port); + in.sin_addr.s_addr = inet_addr(destip); + + printf("client: sending packet to %s:%d\n", destip, port); + if (sendto(client_socket, TESTSTRING, TESTLEN, 0, + (struct sockaddr *)&in, sizeof(in)) < 0) { + perror("client: sendto"); + fr_exit_now(0); + } + + printf("client: waiting for reply from server on INADDR_ANY:%d\n", port+1); + + if ((n = recvfromto(client_socket, buf, sizeof(buf), 0, + (struct sockaddr *)&from, &fl, + (struct sockaddr *)&to, &tl)) < 0) { + perror("client: recvfromto"); + fr_exit_now(0); + } + + printf("client: received a packet of %d bytes [%s] ", n, buf); + printf("(src ip:port %s:%d", + inet_ntoa(from.sin_addr), ntohs(from.sin_port)); + printf(" dst ip:port %s:%d)\n", + inet_ntoa(to.sin_addr), ntohs(to.sin_port)); + + fr_exit_now(0); +} + +#endif /* TESTING */ +#endif /* WITH_UDPFROMTO */ |