summaryrefslogtreecommitdiffstats
path: root/src/lib/udpfromto.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib/udpfromto.c579
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 */