Set errno appropriately if we're building without IPv6 support and an IPv6 socket...
[freeradius.git] / src / lib / udpfromto.c
index 8dac01f..beda5cf 100644 (file)
  *
  *   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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
- *
- *  Helper functions to get/set addresses of UDP packets
- *  based on recvfromto by Miquel van Smoorenburg
- *
- * recvfromto  Like recvfrom, but also stores the destination
- *             IP address. Useful on multihomed hosts.
- *
- *             Should work on Linux and BSD.
- *
- *             Copyright (C) 2002 Miquel van Smoorenburg.
- *
- *             This program 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 of the License, or (at your option) any later version.
- *
- * sendfromto  added 18/08/2003, Jan Berkel <jan@sitadelle.com>
- *             Works on Linux and FreeBSD (5.x)
+ *   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.
  *
- * Version: $Id$
+ * @copyright 2007 Alan DeKok <aland@deployingradius.com>
+ * @copyright 2002 Miquel van Smoorenburg
  */
+RCSID("$Id$")
 
-#include "autoconf.h"
+#include <freeradius-devel/udpfromto.h>
 
 #ifdef WITH_UDPFROMTO
-static const char rcsid[] = "$Id$";
-
-#include <sys/types.h>
 
 #ifdef HAVE_SYS_UIO_H
-#include <sys/uio.h>
+#  include <sys/uio.h>
 #endif
 
-#include <netinet/in.h>
-#include <errno.h>
-#include <unistd.h>
 #include <fcntl.h>
-#include <string.h>
 
-#include "udpfromto.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
+#endif
+
+/*
+ *     Linux requires IPV6_RECVPKTINFO for the setsockopt() call,
+ *     but sendmsg() and recvmsg() require IPV6_PKTINFO. <sigh>
+ *
+ *     We want all *other* (i.e. sane) systems to use IPV6_PKTINFO
+ *     for all three calls.
+ */
+#ifdef IPV6_PKTINFO
+#  ifdef __linux__
+#    ifdef IPV6_RECVPKTINFO
+#      define FR_IPV6_RECVPKTINFO IPV6_RECVPKTINFO
+/* Fallback to to using recvfrom */
+#    else
+#      undef IPV6_RECVPKTINFO
+#      undef IPV6_PKTINFO
+#    endif
+#  else
+#    define FR_IPV6_RECVPKTINFO IPV6_PKTINFO
+#  endif
+#endif
 
 int udpfromto_init(int s)
 {
-       int err = -1, opt = 1;
+       int proto, flag = 0, opt = 1;
+       struct sockaddr_storage si;
+       socklen_t si_len = sizeof(si);
+
        errno = ENOSYS;
-#ifdef HAVE_IP_PKTINFO
-       /* Set the IP_PKTINFO option (Linux). */
-       err = setsockopt(s, SOL_IP, IP_PKTINFO, &opt, sizeof(opt));
-#endif
 
-#ifdef HAVE_IP_RECVDSTADDR
        /*
-        * Set the IP_RECVDSTADDR option (BSD).
-        * Note: IP_RECVDSTADDR == IP_SENDSRCADDR
+        *      Clang analyzer doesn't see that getsockname initialises
+        *      the memory passed to it.
         */
-       err = setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt));
+#ifdef __clang_analyzer__
+       memset(&si, 0, sizeof(si));
 #endif
-       return err;
+
+       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
+
+#ifdef AF_INET6
+       } else if (si.ss_family == AF_INET6) {
+#  ifdef IPV6_PKTINFO
+               /*
+                *      This should actually be standard IPv6
+                */
+               proto = IPPROTO_IPV6;
+
+               /*
+                *      Work around Linux-specific hackery.
+                */
+               flag = FR_IPV6_RECVPKTINFO;
+#else
+#  ifdef EPROTONOSUPPORT
+               errno = EPROTONOSUPPORT;
+#  endif
+               return -1;
+#  endif
+#endif
+       } else {
+               /*
+                *      Unknown AF.
+                */
+               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 sockaddr *from, socklen_t *fromlen,
+              struct sockaddr *to, socklen_t *tolen)
 {
-#if defined(HAVE_IP_PKTINFO) || defined(HAVE_IP_RECVDSTADDR)
        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 from or to are set, they must be big enough
-        *      to store a struct sockaddr_in.
+        *      If the recvmsg() flags aren't defined, fall back to
+        *      using recvfrom().
         */
-       if ((from && (!fromlen || *fromlen < sizeof(struct sockaddr_in))) ||
-           (to   && (!tolen   || *tolen   < sizeof(struct sockaddr_in)))) {
-               errno = EINVAL;
+       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;
        }
 
        /*
-        *      IP_PKTINFO / IP_RECVDSTADDR don't provide sin_port so we have to
-        *      retrieve it using getsockname().
+        *      Initialize the 'to' address.  It may be INADDR_ANY here,
+        *      with a more specific address given by recvmsg(), below.
         */
-       if (to) {
-               struct sockaddr_in si;
-               socklen_t l = sizeof(si);
-
-               ((struct sockaddr_in *)to)->sin_family = AF_INET;
-               ((struct sockaddr_in *)to)->sin_port = 0;
-               l = sizeof(si);
-               if (getsockname(s, (struct sockaddr *)&si, &l) == 0) {
-                       ((struct sockaddr_in *)to)->sin_port = si.sin_port;
-                       ((struct sockaddr_in *)to)->sin_addr = si.sin_addr;
+       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;
                }
-               if (tolen) *tolen = sizeof(struct sockaddr_in);
+               *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;
@@ -127,6 +263,7 @@ int recvfromto(int s, void *buf, size_t len, int flags,
        if ((err = recvmsg(s, &msgh, flags)) < 0) {
                return err;
        }
+
        if (fromlen) *fromlen = msgh.msg_namelen;
 
        /* Process auxiliary received data in msgh */
@@ -134,91 +271,178 @@ int recvfromto(int s, void *buf, size_t len, int flags,
             cmsg != NULL;
             cmsg = CMSG_NXTHDR(&msgh,cmsg)) {
 
-# ifdef HAVE_IP_PKTINFO
-               if (cmsg->cmsg_level == SOL_IP
-                   && cmsg->cmsg_type == IP_PKTINFO) {
+#ifdef IP_PKTINFO
+               if ((cmsg->cmsg_level == SOL_IP) &&
+                   (cmsg->cmsg_type == IP_PKTINFO)) {
                        struct in_pktinfo *i =
-                               (struct in_pktinfo *)CMSG_DATA(cmsg);
-                       if (to) {
-                               ((struct sockaddr_in *)to)->sin_addr =
-                                       i->ipi_addr;
-                               if (tolen) *tolen = sizeof(struct sockaddr_in);
-                       }
+                               (struct in_pktinfo *) CMSG_DATA(cmsg);
+                       ((struct sockaddr_in *)to)->sin_addr = i->ipi_addr;
+                       *tolen = sizeof(struct sockaddr_in);
                        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 (to) {
-                               ((struct sockaddr_in *)to)->sin_addr = *i;
-                               if (tolen) *tolen = sizeof(struct sockaddr_in);
-                       }
+#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
+#endif
        }
+
        return err;
-#else
-       /* fallback: call recvfrom */
-       return recvfrom(s, buf, len, flags, from, fromlen);
-#endif /* defined(HAVE_IP_PKTINFO) || defined(HAVE_IP_RECVDSTADDR) */
 }
 
 int sendfromto(int s, void *buf, size_t len, int flags,
-                         struct sockaddr *from, socklen_t fromlen,
-                         struct sockaddr *to, socklen_t tolen)
+              struct sockaddr *from, socklen_t fromlen,
+              struct sockaddr *to, socklen_t tolen)
 {
-#if defined(HAVE_IP_PKTINFO) || defined(HAVE_IP_SENDSRCADDR)
        struct msghdr msgh;
        struct cmsghdr *cmsg;
        struct iovec iov;
-# ifdef HAVE_IP_PKTINFO
-       char cmsgbuf[CMSG_SPACE(sizeof(struct in_pktinfo))];
-       struct in_pktinfo pktinfo, *pktinfo_ptr;
-       memset(&pktinfo, 0, sizeof(struct in_pktinfo));
-# endif
+       char cbuf[256];
 
-# ifdef HAVE_IP_SENDSRCADDR
-       char cmsgbuf[CMSG_SPACE(sizeof(struct in_addr))];
-# endif
+#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);
 
-       /* Set up iov and msgh structures. */
-       memset(&msgh, 0, sizeof(struct msghdr));
+       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;
+       }
+#else
+#  if !defined(IP_PKTINFO) && !defined(IP_SENDSRCADDR) && !defined(IPV6_PKTINFO)
+       /*
+        *      If the sendmsg() flags aren't defined, fall back to
+        *      using sendto().
+        */
+       from = NULL;
+#  endif
+#endif
+
+       /*
+        *      Catch the case where the caller passes invalid arguments.
+        */
+       if (!from || (fromlen == 0) || (from->sa_family == AF_UNSPEC)) {
+               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_control = cmsgbuf;
-       msgh.msg_controllen = sizeof(cmsgbuf);
        msgh.msg_name = to;
        msgh.msg_namelen = tolen;
 
-       cmsg = CMSG_FIRSTHDR(&msgh);
-
-# ifdef HAVE_IP_PKTINFO
-       cmsg->cmsg_level = SOL_IP;
-       cmsg->cmsg_type = IP_PKTINFO;
-       cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
-       pktinfo.ipi_spec_dst = ((struct sockaddr_in *)from)->sin_addr;
-       pktinfo_ptr = (struct in_pktinfo *)CMSG_DATA(cmsg);
-       memcpy(pktinfo_ptr, &pktinfo, sizeof(struct in_pktinfo));
-# endif
-# ifdef HAVE_IP_SENDSRCADDR
-       cmsg->cmsg_level = IPPROTO_IP;
-       cmsg->cmsg_type = IP_SENDSRCADDR;
-       cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
-       memcpy((struct in_addr *)CMSG_DATA(cmsg),
-              &((struct sockaddr_in *)from)->sin_addr, sizeof(struct in_addr));
-# endif
+       if (from->sa_family == AF_INET) {
+#if !defined(IP_PKTINFO) && !defined(IP_SENDSRCADDR)
+               return sendto(s, buf, len, flags, to, tolen);
+#else
+               struct sockaddr_in *s4 = (struct sockaddr_in *) from;
+
+#  ifdef IP_PKTINFO
+               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;
+#  endif
+
+#  ifdef IP_SENDSRCADDR
+               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 /* IP_PKTINFO or IP_SENDSRCADDR */
+       }
+
+#ifdef AF_INET6
+       else if (from->sa_family == AF_INET6) {
+#  if !defined(IPV6_PKTINFO)
+               return sendto(s, buf, len, flags, to, tolen);
+#  else
+               struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) from;
+
+               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 */
+       }
+#endif
+
+       /*
+        *      Unknown address family.
+        */
+       else {
+               errno = EINVAL;
+               return -1;
+       }
 
        return sendmsg(s, &msgh, flags);
-#else
-       /* fallback: call sendto() */
-       return sendto(s, buf, len, flags, to, tolen);
-#endif /* defined(HAVE_IP_PKTINFO) || defined (HAVE_IP_SENDSRCADDR) */
 }
 
 
@@ -231,24 +455,19 @@ int sendfromto(int s, void *buf, size_t len, int flags,
  *     reply packet should originate from virtual IP and not
  *     from the default interface the alias is bound to
  */
+#  include <sys/wait.h>
 
-#include <stdio.h>
-#include <stdlib.h>
-#include <arpa/inet.h>
-#include <sys/types.h>
-#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 */
+#  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;
-       int port = DEF_PORT;
+       uint16_t port = DEF_PORT;
        int n, server_socket, client_socket, fl, tl, pid;
 
        if (argc > 1) destip = argv[1];
@@ -316,13 +535,13 @@ client:
        client_socket = socket(PF_INET, SOCK_DGRAM, 0);
        if (udpfromto_init(client_socket) != 0) {
                perror("udpfromto_init");
-               _exit(0);
+               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");
-               _exit(0);
+               fr_exit_now(0);
        }
 
        in.sin_port = htons(port);
@@ -332,7 +551,7 @@ client:
        if (sendto(client_socket, TESTSTRING, TESTLEN, 0,
                        (struct sockaddr *)&in, sizeof(in)) < 0) {
                perror("client: sendto");
-               _exit(0);
+               fr_exit_now(0);
        }
 
        printf("client: waiting for reply from server on INADDR_ANY:%d\n", port+1);
@@ -341,7 +560,7 @@ client:
            (struct sockaddr *)&from, &fl,
            (struct sockaddr *)&to, &tl)) < 0) {
                perror("client: recvfromto");
-               _exit(0);
+               fr_exit_now(0);
        }
 
        printf("client: received a packet of %d bytes [%s] ", n, buf);
@@ -350,7 +569,7 @@ client:
        printf(" dst ip:port %s:%d)\n",
                inet_ntoa(to.sin_addr), ntohs(to.sin_port));
 
-       _exit(0);
+       fr_exit_now(0);
 }
 
 #endif /* TESTING */