Set errno appropriately if we're building without IPv6 support and an IPv6 socket...
[freeradius.git] / src / lib / udpfromto.c
index a3186d4..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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 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.
- *     Copyright (C) 2007 Alan DeKok <aland@deployingradius.com>
- *
- * sendfromto  added 18/08/2003, Jan Berkel <jan@sitadelle.com>
- *             Works on Linux and FreeBSD (5.x)
- *
- * Version: $Id$
  */
 
-#include <freeradius-devel/ident.h>
+/**
+ * $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>
@@ -43,7 +29,7 @@ RCSID("$Id$")
 #ifdef WITH_UDPFROMTO
 
 #ifdef HAVE_SYS_UIO_H
-#include <sys/uio.h>
+#  include <sys/uio.h>
 #endif
 
 #include <fcntl.h>
@@ -53,7 +39,7 @@ RCSID("$Id$")
  *     Mac OSX Lion doesn't define SOL_IP.  But IPPROTO_IP works.
  */
 #ifndef SOL_IP
-#define SOL_IP IPPROTO_IP
+#  define SOL_IP IPPROTO_IP
 #endif
 
 /*
@@ -67,16 +53,19 @@ RCSID("$Id$")
  * old kernel interface.
  */
 #ifdef __linux__
-#  if defined IPV6_RECVPKTINFO
+#  ifdef IPV6_RECVPKTINFO
 #    include <linux/version.h>
 #    if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14)
-#      if defined IPV6_2292PKTINFO
+#      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
 
@@ -88,22 +77,34 @@ RCSID("$Id$")
  *     for all three calls.
  */
 #ifdef IPV6_PKTINFO
-#ifdef __linux__
-#define FR_IPV6_RECVPKTINFO IPV6_RECVPKTINFO
-#else
-#define FR_IPV6_RECVPKTINFO IPV6_PKTINFO
-#endif
+#  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 proto, flag, opt = 1;
+       int proto, flag = 0, opt = 1;
        struct sockaddr_storage si;
        socklen_t si_len = sizeof(si);
 
        errno = ENOSYS;
 
-       proto = -1;
+       /*
+        *      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;
@@ -116,20 +117,23 @@ int udpfromto_init(int s)
                 */
                proto = SOL_IP;
                flag = IP_PKTINFO;
-#endif
-               
-#ifdef IP_RECVDSTADDR
+#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
+#  ifdef IPV6_PKTINFO
                /*
                 *      This should actually be standard IPv6
                 */
@@ -139,7 +143,12 @@ int udpfromto_init(int s)
                 *      Work around Linux-specific hackery.
                 */
                flag = FR_IPV6_RECVPKTINFO;
-#endif
+#else
+#  ifdef EPROTONOSUPPORT
+               errno = EPROTONOSUPPORT;
+#  endif
+               return -1;
+#  endif
 #endif
        } else {
                /*
@@ -147,11 +156,6 @@ int udpfromto_init(int s)
                 */
                return -1;
        }
-               
-       /*
-        *      Unsupported.  Don't worry about it.
-        */
-       if (proto < 0) return 0;
 
        return setsockopt(s, proto, flag, &opt, sizeof(opt));
 }
@@ -168,7 +172,7 @@ int recvfromto(int s, void *buf, size_t len, int flags,
        struct sockaddr_storage si;
        socklen_t si_len = sizeof(si);
 
-#if !defined(IP_PKTINFO) && !defined(IP_RECVDSTADDR) && !defined (IPV6_PKTINFO)
+#if !defined(IP_PKTINFO) && !defined(IP_RECVDSTADDR) && !defined(IPV6_PKTINFO)
        /*
         *      If the recvmsg() flags aren't defined, fall back to
         *      using recvfrom().
@@ -182,6 +186,14 @@ int recvfromto(int s, void *buf, size_t len, int flags,
        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().
         */
@@ -199,7 +211,7 @@ int recvfromto(int s, void *buf, size_t len, int flags,
 #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;
@@ -216,7 +228,7 @@ int recvfromto(int s, void *buf, size_t len, int flags,
 #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;
@@ -228,13 +240,14 @@ int recvfromto(int s, void *buf, size_t len, int flags,
 #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;
@@ -303,12 +316,41 @@ int sendfromto(int s, void *buf, size_t len, int flags,
        struct iovec iov;
        char cbuf[256];
 
-#if !defined(IP_PKTINFO) && !defined(IP_SENDSRCADDR) && !defined(IPV6_PKTINFO)
+#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;
+       }
+#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
 
        /*
@@ -318,8 +360,10 @@ int sendfromto(int s, void *buf, size_t len, int flags,
                return sendto(s, buf, len, flags, to, tolen);
        }
 
-       /* Set up iov and msgh structures. */
-       memset(&msgh, 0, sizeof(struct msghdr));
+       /* 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;
@@ -333,7 +377,7 @@ int sendfromto(int s, void *buf, size_t len, int flags,
 #else
                struct sockaddr_in *s4 = (struct sockaddr_in *) from;
 
-#ifdef IP_PKTINFO
+#  ifdef IP_PKTINFO
                struct in_pktinfo *pkt;
 
                msgh.msg_control = cbuf;
@@ -347,9 +391,9 @@ int sendfromto(int s, void *buf, size_t len, int flags,
                pkt = (struct in_pktinfo *) CMSG_DATA(cmsg);
                memset(pkt, 0, sizeof(*pkt));
                pkt->ipi_spec_dst = s4->sin_addr;
-#endif
+#  endif
 
-#ifdef IP_SENDSRCADDR
+#  ifdef IP_SENDSRCADDR
                struct in_addr *in;
 
                msgh.msg_control = cbuf;
@@ -362,15 +406,15 @@ int sendfromto(int s, void *buf, size_t len, int flags,
 
                in = (struct in_addr *) CMSG_DATA(cmsg);
                *in = s4->sin_addr;
-#endif
+#  endif
 #endif /* IP_PKTINFO or IP_SENDSRCADDR */
        }
 
 #ifdef AF_INET6
        else if (from->sa_family == AF_INET6) {
-#if !defined(IPV6_PKTINFO)
+#  if !defined(IPV6_PKTINFO)
                return sendto(s, buf, len, flags, to, tolen);
-#else          
+#  else
                struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) from;
 
                struct in6_pktinfo *pkt;
@@ -386,13 +430,13 @@ int sendfromto(int s, void *buf, size_t len, int flags,
                pkt = (struct in6_pktinfo *) CMSG_DATA(cmsg);
                memset(pkt, 0, sizeof(*pkt));
                pkt->ipi6_addr = s6->sin6_addr;
-#endif /* IPV6_PKTINFO */
+#  endif       /* IPV6_PKTINFO */
        }
 #endif
 
        /*
         *      Unknown address family.
-        */             
+        */
        else {
                errno = EINVAL;
                return -1;
@@ -411,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];
@@ -496,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);
@@ -512,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);
@@ -521,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);
@@ -530,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 */