Fixes from clang / scan-build
[freeradius.git] / src / lib / packet.c
index abb19cd..0091192 100644 (file)
 #include       <freeradius-devel/ident.h>
 RCSID("$Id$")
 
-#include       <freeradius-devel/autoconf.h>
-#include       <freeradius-devel/missing.h>
 #include       <freeradius-devel/libradius.h>
 
-#include <unistd.h>
-#include <stdlib.h>
+#ifdef WITH_UDPFROMTO
+#include       <freeradius-devel/udpfromto.h>
+#endif
+
+#include <fcntl.h>
 
 /*
  *     Take the key fields of a request packet, and convert it to a
  *     hash.
  */
-uint32_t lrad_request_packet_hash(const RADIUS_PACKET *packet)
+uint32_t fr_request_packet_hash(const RADIUS_PACKET *packet)
 {
        uint32_t hash;
 
        if (packet->hash) return packet->hash;
 
-       hash = lrad_hash(&packet->sockfd, sizeof(packet->sockfd));
-       hash = lrad_hash_update(&packet->id, sizeof(packet->id), hash);
-       hash = lrad_hash_update(&packet->src_port, sizeof(packet->src_port),
+       hash = fr_hash(&packet->sockfd, sizeof(packet->sockfd));
+       hash = fr_hash_update(&packet->src_port, sizeof(packet->src_port),
                                hash);
-       hash = lrad_hash_update(&packet->dst_port,
+       hash = fr_hash_update(&packet->dst_port,
                                sizeof(packet->dst_port), hash);
-       hash = lrad_hash_update(&packet->src_ipaddr.af,
+       hash = fr_hash_update(&packet->src_ipaddr.af,
                                sizeof(packet->src_ipaddr.af), hash);
 
        /*
@@ -54,18 +54,18 @@ uint32_t lrad_request_packet_hash(const RADIUS_PACKET *packet)
         */
        switch (packet->src_ipaddr.af) {
        case AF_INET:
-               hash = lrad_hash_update(&packet->src_ipaddr.ipaddr.ip4addr,
+               hash = fr_hash_update(&packet->src_ipaddr.ipaddr.ip4addr,
                                        sizeof(packet->src_ipaddr.ipaddr.ip4addr),
                                        hash);
-               hash = lrad_hash_update(&packet->dst_ipaddr.ipaddr.ip4addr,
+               hash = fr_hash_update(&packet->dst_ipaddr.ipaddr.ip4addr,
                                        sizeof(packet->dst_ipaddr.ipaddr.ip4addr),
                                        hash);
                break;
        case AF_INET6:
-               hash = lrad_hash_update(&packet->src_ipaddr.ipaddr.ip6addr,
+               hash = fr_hash_update(&packet->src_ipaddr.ipaddr.ip6addr,
                                        sizeof(packet->src_ipaddr.ipaddr.ip6addr),
                                        hash);
-               hash = lrad_hash_update(&packet->dst_ipaddr.ipaddr.ip6addr,
+               hash = fr_hash_update(&packet->dst_ipaddr.ipaddr.ip6addr,
                                        sizeof(packet->dst_ipaddr.ipaddr.ip6addr),
                                        hash);
                break;
@@ -73,7 +73,7 @@ uint32_t lrad_request_packet_hash(const RADIUS_PACKET *packet)
                break;
        }
 
-       return lrad_hash_update(&packet->id, sizeof(packet->id), hash);
+       return fr_hash_update(&packet->id, sizeof(packet->id), hash);
 }
 
 
@@ -86,17 +86,17 @@ uint32_t lrad_request_packet_hash(const RADIUS_PACKET *packet)
  *     of the request.  e.g. where the request does (src, dst), we do
  *     (dst, src)
  */
-uint32_t lrad_reply_packet_hash(const RADIUS_PACKET *packet)
+uint32_t fr_reply_packet_hash(const RADIUS_PACKET *packet)
 {
        uint32_t hash;
-       
-       hash = lrad_hash(&packet->sockfd, sizeof(packet->sockfd));
-       hash = lrad_hash_update(&packet->id, sizeof(packet->id), hash);
-       hash = lrad_hash_update(&packet->src_port, sizeof(packet->src_port),
+
+       hash = fr_hash(&packet->sockfd, sizeof(packet->sockfd));
+       hash = fr_hash_update(&packet->id, sizeof(packet->id), hash);
+       hash = fr_hash_update(&packet->src_port, sizeof(packet->src_port),
                                hash);
-       hash = lrad_hash_update(&packet->dst_port,
+       hash = fr_hash_update(&packet->dst_port,
                                sizeof(packet->dst_port), hash);
-       hash = lrad_hash_update(&packet->src_ipaddr.af,
+       hash = fr_hash_update(&packet->src_ipaddr.af,
                                sizeof(packet->src_ipaddr.af), hash);
 
        /*
@@ -104,18 +104,18 @@ uint32_t lrad_reply_packet_hash(const RADIUS_PACKET *packet)
         */
        switch (packet->src_ipaddr.af) {
        case AF_INET:
-               hash = lrad_hash_update(&packet->dst_ipaddr.ipaddr.ip4addr,
+               hash = fr_hash_update(&packet->dst_ipaddr.ipaddr.ip4addr,
                                        sizeof(packet->dst_ipaddr.ipaddr.ip4addr),
                                        hash);
-               hash = lrad_hash_update(&packet->src_ipaddr.ipaddr.ip4addr,
+               hash = fr_hash_update(&packet->src_ipaddr.ipaddr.ip4addr,
                                        sizeof(packet->src_ipaddr.ipaddr.ip4addr),
                                        hash);
                break;
        case AF_INET6:
-               hash = lrad_hash_update(&packet->dst_ipaddr.ipaddr.ip6addr,
+               hash = fr_hash_update(&packet->dst_ipaddr.ipaddr.ip6addr,
                                        sizeof(packet->dst_ipaddr.ipaddr.ip6addr),
                                        hash);
-               hash = lrad_hash_update(&packet->src_ipaddr.ipaddr.ip6addr,
+               hash = fr_hash_update(&packet->src_ipaddr.ipaddr.ip6addr,
                                        sizeof(packet->src_ipaddr.ipaddr.ip6addr),
                                        hash);
                break;
@@ -123,31 +123,7 @@ uint32_t lrad_reply_packet_hash(const RADIUS_PACKET *packet)
                break;
        }
 
-       return lrad_hash_update(&packet->id, sizeof(packet->id), hash);
-}
-
-
-static int lrad_ipaddr_cmp(const lrad_ipaddr_t *a, const lrad_ipaddr_t *b)
-{
-       if (a->af < b->af) return -1;
-       if (a->af > b->af) return +1;
-
-       switch (a->af) {
-       case AF_INET:
-               return memcmp(&a->ipaddr.ip4addr,
-                             &b->ipaddr.ip4addr,
-                             sizeof(a->ipaddr.ip4addr));
-               break;
-       case AF_INET6:
-               return memcmp(&a->ipaddr.ip6addr,
-                             &b->ipaddr.ip6addr,
-                             sizeof(a->ipaddr.ip6addr));
-               break;
-       default:
-               break;
-       }
-       
-       return -1;
+       return fr_hash_update(&packet->id, sizeof(packet->id), hash);
 }
 
 
@@ -158,7 +134,7 @@ static int lrad_ipaddr_cmp(const lrad_ipaddr_t *a, const lrad_ipaddr_t *b)
  *     That's because if the authentication vector is different,
  *     it means that the NAS has given up on the earlier request.
  */
-int lrad_packet_cmp(const RADIUS_PACKET *a, const RADIUS_PACKET *b)
+int fr_packet_cmp(const RADIUS_PACKET *a, const RADIUS_PACKET *b)
 {
        int rcode;
 
@@ -174,16 +150,39 @@ int lrad_packet_cmp(const RADIUS_PACKET *a, const RADIUS_PACKET *b)
        if (a->dst_port < b->dst_port) return -1;
        if (a->dst_port > b->dst_port) return +1;
 
-       rcode = lrad_ipaddr_cmp(&a->dst_ipaddr, &b->dst_ipaddr);
+       rcode = fr_ipaddr_cmp(&a->dst_ipaddr, &b->dst_ipaddr);
        if (rcode != 0) return rcode;
-       return lrad_ipaddr_cmp(&a->src_ipaddr, &b->src_ipaddr);
+       return fr_ipaddr_cmp(&a->src_ipaddr, &b->src_ipaddr);
+}
+
+int fr_inaddr_any(fr_ipaddr_t *ipaddr)
+{
+
+       if (ipaddr->af == AF_INET) {
+               if (ipaddr->ipaddr.ip4addr.s_addr == INADDR_ANY) {
+                       return 1;
+               }
+               
+#ifdef HAVE_STRUCT_SOCKADDR_IN6
+       } else if (ipaddr->af == AF_INET6) {
+               if (IN6_IS_ADDR_UNSPECIFIED(&(ipaddr->ipaddr.ip6addr))) {
+                       return 1;
+               }
+#endif
+               
+       } else {
+               fr_strerror_printf("Unknown address family");
+               return -1;
+       }
+
+       return 0;
 }
 
 
 /*
  *     Create a fake "request" from a reply, for later lookup.
  */
-void lrad_request_from_reply(RADIUS_PACKET *request,
+void fr_request_from_reply(RADIUS_PACKET *request,
                             const RADIUS_PACKET *reply)
 {
        request->sockfd = reply->sockfd;
@@ -195,22 +194,38 @@ void lrad_request_from_reply(RADIUS_PACKET *request,
 }
 
 
+int fr_nonblock(UNUSED int fd)
+{
+       int flags = 0;
+
+#ifdef O_NONBLOCK
+
+       flags = fcntl(fd, F_GETFL, NULL);
+       if (flags >= 0) {
+               flags |= O_NONBLOCK;
+               return fcntl(fd, F_SETFL, flags);
+       }
+#endif
+       return flags;
+}
+
 /*
  *     Open a socket on the given IP and port.
  */
-int lrad_socket(lrad_ipaddr_t *ipaddr, int port)
+int fr_socket(fr_ipaddr_t *ipaddr, int port)
 {
        int sockfd;
        struct sockaddr_storage salocal;
        socklen_t       salen;
 
        if ((port < 0) || (port > 65535)) {
-               librad_log("Port %d is out of allowed bounds", port);
+               fr_strerror_printf("Port %d is out of allowed bounds", port);
                return -1;
        }
 
        sockfd = socket(ipaddr->af, SOCK_DGRAM, 0);
        if (sockfd < 0) {
+               fr_strerror_printf("cannot open socket: %s", strerror(errno));
                return sockfd;
        }
 
@@ -220,31 +235,22 @@ int lrad_socket(lrad_ipaddr_t *ipaddr, int port)
         */
        if (udpfromto_init(sockfd) != 0) {
                close(sockfd);
+               fr_strerror_printf("cannot initialize udpfromto: %s", strerror(errno));
                return -1;
        }
 #endif
 
-       memset(&salocal, 0, sizeof(salocal));
-       if (ipaddr->af == AF_INET) {
-               struct sockaddr_in *sa;
-               
-               sa = (struct sockaddr_in *) &salocal;
-               sa->sin_family = AF_INET;
-               sa->sin_addr = ipaddr->ipaddr.ip4addr;
-               sa->sin_port = htons((uint16_t) port);
-               salen = sizeof(*sa);
-               
-#ifdef HAVE_STRUCT_SOCKADDR_IN6
-       } else if (ipaddr->af == AF_INET6) {
-               struct sockaddr_in6 *sa;
-               
-               sa = (struct sockaddr_in6 *) &salocal;
-               sa->sin6_family = AF_INET6;
-               sa->sin6_addr = ipaddr->ipaddr.ip6addr;
-               sa->sin6_port = htons((uint16_t) port);
-               salen = sizeof(*sa);
+       if (fr_nonblock(sockfd) < 0) {
+               close(sockfd);
+               return -1;
+       }
+
+       if (!fr_ipaddr2sockaddr(ipaddr, port, &salocal, &salen)) {
+               return sockfd;
+       }
 
-#if 1
+#ifdef HAVE_STRUCT_SOCKADDR_IN6
+       if (ipaddr->af == AF_INET6) {
                /*
                 *      Listening on '::' does NOT get you IPv4 to
                 *      IPv6 mapping.  You've got to listen on an IPv4
@@ -255,19 +261,41 @@ int lrad_socket(lrad_ipaddr_t *ipaddr, int port)
 
                if (IN6_IS_ADDR_UNSPECIFIED(&ipaddr->ipaddr.ip6addr)) {
                        int on = 1;
-                       
+
                        setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
                                   (char *)&on, sizeof(on));
                }
 #endif /* IPV6_V6ONLY */
-#endif
+       }
 #endif /* HAVE_STRUCT_SOCKADDR_IN6 */
-       } else {
-               return sockfd;  /* don't bind it */
+
+       if (ipaddr->af == AF_INET) {
+               UNUSED int flag;
+               
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
+               /*
+                *      Disable PMTU discovery.  On Linux, this
+                *      also makes sure that the "don't fragment"
+                *      flag is zero.
+                */
+               flag = IP_PMTUDISC_DONT;
+               setsockopt(sockfd, IPPROTO_IP, IP_MTU_DISCOVER,
+                          &flag, sizeof(flag));
+#endif
+
+#if defined(IP_DONTFRAG)
+               /*
+                *      Ensure that the "don't fragment" flag is zero.
+                */
+               flag = 0;
+               setsockopt(sockfd, IPPROTO_IP, IP_DONTFRAG,
+                          &flag, sizeof(flag));
+#endif
        }
 
        if (bind(sockfd, (struct sockaddr *) &salocal, salen) < 0) {
                close(sockfd);
+               fr_strerror_printf("cannot bind socket: %s", strerror(errno));
                return -1;
        }
 
@@ -278,20 +306,32 @@ int lrad_socket(lrad_ipaddr_t *ipaddr, int port)
 /*
  *     We need to keep track of the socket & it's IP/port.
  */
-typedef struct lrad_packet_socket_t {
+typedef struct fr_packet_socket_t {
        int             sockfd;
+       void            *ctx;
 
        int             num_outgoing;
 
-       int             offset; /* 0..31 */
-       int             inaddr_any;
-       lrad_ipaddr_t   ipaddr;
-       int             port;
-} lrad_packet_socket_t;
+       int             src_any;
+       fr_ipaddr_t     src_ipaddr;
+       int             src_port;
+
+       int             dst_any;
+       fr_ipaddr_t     dst_ipaddr;
+       int             dst_port;
+
+       int             dont_use;
+
+#ifdef WITH_TCP
+       int             proto;
+#endif
+
+       uint8_t         id[32];
+} fr_packet_socket_t;
 
 
 #define FNV_MAGIC_PRIME (0x01000193)
-#define MAX_SOCKETS (32)
+#define MAX_SOCKETS (256)
 #define SOCKOFFSET_MASK (MAX_SOCKETS - 1)
 #define SOCK2OFFSET(sockfd) ((sockfd * FNV_MAGIC_PRIME) & SOCKOFFSET_MASK)
 
@@ -301,25 +341,23 @@ typedef struct lrad_packet_socket_t {
  *     Structure defining a list of packets (incoming or outgoing)
  *     that should be managed.
  */
-struct lrad_packet_list_t {
-       lrad_hash_table_t *ht;
-
-       lrad_hash_table_t *dst2id_ht;
+struct fr_packet_list_t {
+       fr_hash_table_t *ht;
 
        int             alloc_id;
        int             num_outgoing;
+       int             last_recv;
+       int             num_sockets;
 
-       uint32_t mask;
-       int last_recv;
-       lrad_packet_socket_t sockets[MAX_SOCKETS];
+       fr_packet_socket_t sockets[MAX_SOCKETS];
 };
 
 
 /*
  *     Ugh.  Doing this on every sent/received packet is not nice.
  */
-static lrad_packet_socket_t *lrad_socket_find(lrad_packet_list_t *pl,
-                                            int sockfd)
+static fr_packet_socket_t *fr_socket_find(fr_packet_list_t *pl,
+                                         int sockfd)
 {
        int i, start;
 
@@ -334,13 +372,33 @@ static lrad_packet_socket_t *lrad_socket_find(lrad_packet_list_t *pl,
        return NULL;
 }
 
-int lrad_packet_list_socket_remove(lrad_packet_list_t *pl, int sockfd)
+int fr_packet_list_socket_freeze(fr_packet_list_t *pl, int sockfd)
+{
+       fr_packet_socket_t *ps;
+
+       if (!pl) {
+               fr_strerror_printf("Invalid argument");
+               return 0;
+       }
+
+       ps = fr_socket_find(pl, sockfd);
+       if (!ps) {
+               fr_strerror_printf("No such socket");
+               return 0;
+       }
+
+       ps->dont_use = 1;
+       return 1;
+}
+
+int fr_packet_list_socket_remove(fr_packet_list_t *pl, int sockfd,
+                                void **pctx)
 {
-       lrad_packet_socket_t *ps;
+       fr_packet_socket_t *ps;
 
        if (!pl) return 0;
 
-       ps = lrad_socket_find(pl, sockfd);
+       ps = fr_socket_find(pl, sockfd);
        if (!ps) return 0;
 
        /*
@@ -349,20 +407,37 @@ int lrad_packet_list_socket_remove(lrad_packet_list_t *pl, int sockfd)
        if (ps->num_outgoing != 0) return 0;
 
        ps->sockfd = -1;
-       pl->mask &= ~(1 << ps->offset);
-
+       pl->num_sockets--;
+       if (pctx) *pctx = ps->ctx;
 
        return 1;
 }
 
-int lrad_packet_list_socket_add(lrad_packet_list_t *pl, int sockfd)
+int fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd, int proto,
+                             fr_ipaddr_t *dst_ipaddr, int dst_port,
+                             void *ctx)
 {
        int i, start;
        struct sockaddr_storage src;
-       socklen_t               sizeof_src = sizeof(src);
-       lrad_packet_socket_t    *ps;
+       socklen_t               sizeof_src;
+       fr_packet_socket_t      *ps;
 
-       if (!pl) return 0;
+       if (!pl || !dst_ipaddr || (dst_ipaddr->af == AF_UNSPEC)) {
+               fr_strerror_printf("Invalid argument");
+               return 0;
+       }
+
+       if (pl->num_sockets >= MAX_SOCKETS) {
+               fr_strerror_printf("Too many open sockets");
+               return 0;
+       }
+
+#ifndef WITH_TCP
+       if (proto != IPPROTO_UDP) {
+               fr_strerror_printf("only UDP is supported");
+               return 0;
+       }
+#endif
 
        ps = NULL;
        i = start = SOCK2OFFSET(sockfd);
@@ -370,7 +445,6 @@ int lrad_packet_list_socket_add(lrad_packet_list_t *pl, int sockfd)
        do {
                if (pl->sockets[i].sockfd == -1) {
                        ps =  &pl->sockets[i];
-                       start = i;
                        break;
                }
 
@@ -378,12 +452,15 @@ int lrad_packet_list_socket_add(lrad_packet_list_t *pl, int sockfd)
        } while (i != start);
 
        if (!ps) {
+               fr_strerror_printf("All socket entries are full");
                return 0;
        }
 
        memset(ps, 0, sizeof(*ps));
-       ps->sockfd = sockfd;
-       ps->offset = start;
+       ps->ctx = ctx;
+#ifdef WITH_TCP
+       ps->proto = proto;
+#endif
 
        /*
         *      Get address family, etc. first, so we know if we
@@ -392,50 +469,41 @@ int lrad_packet_list_socket_add(lrad_packet_list_t *pl, int sockfd)
         *      FIXME: udpfromto also does this, but it's not
         *      a critical problem.
         */
+       sizeof_src = sizeof(src);
        memset(&src, 0, sizeof_src);
        if (getsockname(sockfd, (struct sockaddr *) &src,
                        &sizeof_src) < 0) {
+               fr_strerror_printf("%s", strerror(errno));
                return 0;
        }
 
-       /*
-        *      Grab IP addresses & ports from the sockaddr.
-        */
-       ps->ipaddr.af = src.ss_family;
-       if (src.ss_family == AF_INET) {
-               struct sockaddr_in      *s4;
-
-               s4 = (struct sockaddr_in *)&src;
-               ps->ipaddr.ipaddr.ip4addr = s4->sin_addr;
-               ps->port = ntohs(s4->sin_port);
+       if (!fr_sockaddr2ipaddr(&src, sizeof_src, &ps->src_ipaddr,
+                               &ps->src_port)) {
+               fr_strerror_printf("Failed to get IP");
+               return 0;
+       }
 
-               if (ps->ipaddr.ipaddr.ip4addr.s_addr == INADDR_ANY) {
-                       ps->inaddr_any = 1;
-               }
+       ps->dst_ipaddr = *dst_ipaddr;
+       ps->dst_port = dst_port;
 
-#ifdef HAVE_STRUCT_SOCKADDR_IN6
-       } else if (src.ss_family == AF_INET6) {
-               struct sockaddr_in6     *s6;
+       ps->src_any = fr_inaddr_any(&ps->src_ipaddr);
+       if (ps->src_any < 0) return 0;
 
-               s6 = (struct sockaddr_in6 *)&src;
-               ps->ipaddr.ipaddr.ip6addr = s6->sin6_addr;
-               ps->port = ntohs(s6->sin6_port);
+       ps->dst_any = fr_inaddr_any(&ps->dst_ipaddr);
+       if (ps->dst_any < 0) return 0;
 
-               if (IN6_IS_ADDR_UNSPECIFIED(&ps->ipaddr.ipaddr.ip6addr)) {
-                       ps->inaddr_any = 1;
-               }
-#endif
-       } else {
-               return 0;
-       }
+       /*
+        *      As the last step before returning.
+        */
+       ps->sockfd = sockfd;
+       pl->num_sockets++;
 
-       pl->mask |= (1 << ps->offset);
        return 1;
 }
 
 static uint32_t packet_entry_hash(const void *data)
 {
-       return lrad_request_packet_hash(*(const RADIUS_PACKET * const *) data);
+       return fr_request_packet_hash(*(const RADIUS_PACKET * const *) data);
 }
 
 static int packet_entry_cmp(const void *one, const void *two)
@@ -443,79 +511,16 @@ static int packet_entry_cmp(const void *one, const void *two)
        const RADIUS_PACKET * const *a = one;
        const RADIUS_PACKET * const *b = two;
 
-       return lrad_packet_cmp(*a, *b);
-}
+       if (!a || !*a || !b || !*b) return -1; /* work-around for bug #35 */
 
-/*
- *     A particular socket can have 256 RADIUS ID's outstanding to
- *     any one destination IP/port.  So we have a structure that
- *     manages destination IP & port, and has an array of 256 ID's.
- *
- *     The only magic here is that we map the socket number (0..256)
- *     into an "internal" socket number 0..31, that we use to set
- *     bits in the ID array.  If a bit is 1, then that ID is in use
- *     for that socket, and the request MUST be in the packet hash!
- *
- *     Note that as a minor memory leak, we don't have an API to free
- *     this structure, except when we discard the whole packet list.
- *     This means that if destinations are added and removed, they
- *     won't be removed from this tree.
- */
-typedef struct lrad_packet_dst2id_t {
-       lrad_ipaddr_t   dst_ipaddr;
-       int             dst_port;
-       uint32_t        id[1];  /* really id[256] */
-} lrad_packet_dst2id_t;
-
-
-static uint32_t packet_dst2id_hash(const void *data)
-{
-       uint32_t hash;
-       const lrad_packet_dst2id_t *pd = data;
-
-       hash = lrad_hash(&pd->dst_port, sizeof(pd->dst_port));
-       
-       switch (pd->dst_ipaddr.af) {
-       case AF_INET:
-               hash = lrad_hash_update(&pd->dst_ipaddr.ipaddr.ip4addr,
-                                       sizeof(pd->dst_ipaddr.ipaddr.ip4addr),
-                                       hash);
-               break;
-       case AF_INET6:
-               hash = lrad_hash_update(&pd->dst_ipaddr.ipaddr.ip6addr,
-                                       sizeof(pd->dst_ipaddr.ipaddr.ip6addr),
-                                       hash);
-               break;
-       default:
-               break;
-       }
-
-       return hash;
+       return fr_packet_cmp(*a, *b);
 }
 
-static int packet_dst2id_cmp(const void *one, const void *two)
-{
-       const lrad_packet_dst2id_t *a = one;
-       const lrad_packet_dst2id_t *b = two;
-
-       if (a->dst_port < b->dst_port) return -1;
-       if (a->dst_port > b->dst_port) return +1;
-
-       return lrad_ipaddr_cmp(&a->dst_ipaddr, &b->dst_ipaddr);
-}
-
-static void packet_dst2id_free(void *data)
-{
-       free(data);
-}
-
-
-void lrad_packet_list_free(lrad_packet_list_t *pl)
+void fr_packet_list_free(fr_packet_list_t *pl)
 {
        if (!pl) return;
 
-       if (pl->ht) lrad_hash_table_free(pl->ht);
-       if (pl->dst2id_ht) lrad_hash_table_free(pl->dst2id_ht);
+       fr_hash_table_free(pl->ht);
        free(pl);
 }
 
@@ -523,20 +528,20 @@ void lrad_packet_list_free(lrad_packet_list_t *pl)
 /*
  *     Caller is responsible for managing the packet entries.
  */
-lrad_packet_list_t *lrad_packet_list_create(int alloc_id)
+fr_packet_list_t *fr_packet_list_create(int alloc_id)
 {
        int i;
-       lrad_packet_list_t      *pl;
+       fr_packet_list_t        *pl;
 
        pl = malloc(sizeof(*pl));
        if (!pl) return NULL;
        memset(pl, 0, sizeof(*pl));
 
-       pl->ht = lrad_hash_table_create(packet_entry_hash,
+       pl->ht = fr_hash_table_create(packet_entry_hash,
                                        packet_entry_cmp,
                                        NULL);
        if (!pl->ht) {
-               lrad_packet_list_free(pl);
+               fr_packet_list_free(pl);
                return NULL;
        }
 
@@ -544,42 +549,32 @@ lrad_packet_list_t *lrad_packet_list_create(int alloc_id)
                pl->sockets[i].sockfd = -1;
        }
 
-       if (alloc_id) {
-               pl->alloc_id = 1;
-
-               pl->dst2id_ht = lrad_hash_table_create(packet_dst2id_hash,
-                                                      packet_dst2id_cmp,
-                                                      packet_dst2id_free);
-               if (!pl->dst2id_ht) {
-                       lrad_packet_list_free(pl);
-                       return NULL;
-               }
-       }
+       pl->alloc_id = alloc_id;
 
        return pl;
 }
 
 
 /*
- *     If pl->alloc_id is set, then lrad_packet_list_id_alloc() MUST
+ *     If pl->alloc_id is set, then fr_packet_list_id_alloc() MUST
  *     be called before inserting the packet into the list!
  */
-int lrad_packet_list_insert(lrad_packet_list_t *pl,
+int fr_packet_list_insert(fr_packet_list_t *pl,
                            RADIUS_PACKET **request_p)
 {
        if (!pl || !request_p || !*request_p) return 0;
 
-       (*request_p)->hash = lrad_request_packet_hash(*request_p);
+       (*request_p)->hash = fr_request_packet_hash(*request_p);
 
-       return lrad_hash_table_insert(pl->ht, request_p);
+       return fr_hash_table_insert(pl->ht, request_p);
 }
 
-RADIUS_PACKET **lrad_packet_list_find(lrad_packet_list_t *pl,
+RADIUS_PACKET **fr_packet_list_find(fr_packet_list_t *pl,
                                      RADIUS_PACKET *request)
 {
        if (!pl || !request) return 0;
 
-       return lrad_hash_table_finddata(pl->ht, &request);
+       return fr_hash_table_finddata(pl->ht, &request);
 }
 
 
@@ -587,15 +582,15 @@ RADIUS_PACKET **lrad_packet_list_find(lrad_packet_list_t *pl,
  *     This presumes that the reply has dst_ipaddr && dst_port set up
  *     correctly (i.e. real IP, or "*").
  */
-RADIUS_PACKET **lrad_packet_list_find_byreply(lrad_packet_list_t *pl,
+RADIUS_PACKET **fr_packet_list_find_byreply(fr_packet_list_t *pl,
                                              RADIUS_PACKET *reply)
 {
        RADIUS_PACKET my_request, *request;
-       lrad_packet_socket_t *ps;
+       fr_packet_socket_t *ps;
 
        if (!pl || !reply) return NULL;
 
-       ps = lrad_socket_find(pl, reply->sockfd);
+       ps = fr_socket_find(pl, reply->sockfd);
        if (!ps) return NULL;
 
        /*
@@ -607,36 +602,36 @@ RADIUS_PACKET **lrad_packet_list_find_byreply(lrad_packet_list_t *pl,
        my_request.sockfd = reply->sockfd;
        my_request.id = reply->id;
 
-       if (ps->inaddr_any) {
-               my_request.src_ipaddr = ps->ipaddr;
+       if (ps->src_any) {
+               my_request.src_ipaddr = ps->src_ipaddr;
        } else {
                my_request.src_ipaddr = reply->dst_ipaddr;
        }
-       my_request.src_port = ps->port;;
+       my_request.src_port = ps->src_port;
 
        my_request.dst_ipaddr = reply->src_ipaddr;
        my_request.dst_port = reply->src_port;
        my_request.hash = 0;
-       
+
        request = &my_request;
 
-       return lrad_hash_table_finddata(pl->ht, &request);
+       return fr_hash_table_finddata(pl->ht, &request);
 }
 
 
-RADIUS_PACKET **lrad_packet_list_yank(lrad_packet_list_t *pl,
+RADIUS_PACKET **fr_packet_list_yank(fr_packet_list_t *pl,
                                      RADIUS_PACKET *request)
 {
        if (!pl || !request) return NULL;
 
-       return lrad_hash_table_yank(pl->ht, &request);
+       return fr_hash_table_yank(pl->ht, &request);
 }
 
-int lrad_packet_list_num_elements(lrad_packet_list_t *pl)
+int fr_packet_list_num_elements(fr_packet_list_t *pl)
 {
        if (!pl) return 0;
 
-       return lrad_hash_table_num_elements(pl->ht);
+       return fr_hash_table_num_elements(pl->ht);
 }
 
 
@@ -652,64 +647,172 @@ int lrad_packet_list_num_elements(lrad_packet_list_t *pl)
  *     should be protected by a mutex.  This does NOT have to be
  *     the same mutex as the one protecting the insert/find/yank
  *     calls!
+ *
+ *     We assume that the packet has dst_ipaddr && dst_port
+ *     already initialized.  We will use those to find an
+ *     outgoing socket.  The request MAY also have src_ipaddr set.
+ *
+ *     We also assume that the sender doesn't care which protocol
+ *     should be used.
  */
-int lrad_packet_list_id_alloc(lrad_packet_list_t *pl,
-                             RADIUS_PACKET *request)
+int fr_packet_list_id_alloc(fr_packet_list_t *pl, int proto,
+                           RADIUS_PACKET *request, void **pctx)
 {
-       int i, id, start;
-       uint32_t free_mask;
-       lrad_packet_dst2id_t my_pd, *pd;
-       lrad_packet_socket_t *ps;
-       
-       if (!pl || !pl->alloc_id || !request) return 0;
-
-       my_pd.dst_ipaddr = request->dst_ipaddr;
-       my_pd.dst_port = request->dst_port;
-       
-       pd = lrad_hash_table_finddata(pl->dst2id_ht, &my_pd);
-       if (!pd) {
-               pd = malloc(sizeof(*pd) + 255 * sizeof(pd->id[0]));
-               if (!pd) return 0;
-
-               memset(pd, 0, sizeof(*pd) + 255 * sizeof(pd->id[0]));
-
-               if (!lrad_hash_table_insert(pl->dst2id_ht, pd)) {
-                       free(pd);
-                       return 0;
-               }
+       int i, j, k, fd, id, start_i, start_j, start_k;
+       int src_any = 0;
+       fr_packet_socket_t *ps;
+
+       if ((request->dst_ipaddr.af == AF_UNSPEC) ||
+           (request->dst_port == 0)) {
+               fr_strerror_printf("No destination address/port specified");
+               return 0;
+       }
+
+#ifndef WITH_TCP
+       if ((proto != 0) && (proto != IPPROTO_UDP)) {
+               fr_strerror_printf("Invalid destination protocol");
+               return 0;
        }
-       
+#endif
+
+       /*
+        *      Special case: unspec == "don't care"
+        */
+       if (request->src_ipaddr.af == AF_UNSPEC) {
+               memset(&request->src_ipaddr, 0, sizeof(request->src_ipaddr));
+               request->src_ipaddr.af = request->dst_ipaddr.af;
+       }
+
+       src_any = fr_inaddr_any(&request->src_ipaddr);
+       if (src_any < 0) return 0;
+
+       /*
+        *      MUST specify a destination address.
+        */
+       if (fr_inaddr_any(&request->dst_ipaddr) != 0) return 0;
+
        /*
         *      FIXME: Go to an LRU system.  This prevents ID re-use
         *      for as long as possible.  The main problem with that
         *      approach is that it requires us to populate the
         *      LRU/FIFO when we add a new socket, or a new destination,
         *      which can be expensive.
+        *
+        *      The LRU can be avoided if the caller takes care to free
+        *      Id's only when all responses have been received, OR after
+        *      a timeout.
+        *
+        *      Right now, the random approach is almost OK... it's
+        *      brute-force over all of the available ID's, BUT using
+        *      random numbers for everything spreads the load a bit.
+        *
+        *      The old method had a hash lookup on allocation AND
+        *      on free.  The new method has brute-force on allocation,
+        *      and near-zero cost on free.
         */
-       id = start = (int) lrad_rand() & 0xff;
-       
-       while (pd->id[id] == pl->mask) { /* all sockets are using this ID */
-               id++;
-               id &= 0xff;
-               if (id == start) return -1;
-       }
 
-       free_mask = ~((~pd->id[id]) & pl->mask);
+       id = fd = -1;
+       start_i = fr_rand() & SOCKOFFSET_MASK;
 
-       start = -1;
+#define ID_i ((i + start_i) & SOCKOFFSET_MASK)
        for (i = 0; i < MAX_SOCKETS; i++) {
-               if (pl->sockets[i].sockfd == -1) continue; /* paranoia */
+               if (pl->sockets[ID_i].sockfd == -1) continue; /* paranoia */
 
-               if ((free_mask & (1 << i)) == 0) {
-                       start = i;
-                       break;
+               ps = &(pl->sockets[ID_i]);
+
+               /*
+                *      This socket is marked as "don't use for new
+                *      packets".  But we can still receive packets
+                *      that are outstanding.
+                */
+               if (ps->dont_use) continue;
+
+               /*
+                *      All IDs are allocated: ignore it.
+                */
+               if (ps->num_outgoing == 256) continue;
+
+#ifdef WITH_TCP
+               if (ps->proto != proto) continue;
+#endif
+
+               /*
+                *      MUST match dst port, if we have one.
+                */
+               if ((ps->dst_port != 0) && 
+                   (ps->dst_port != request->dst_port)) continue;
+
+               /*
+                *      MUST match requested src port, if one has been given.
+                */
+               if ((request->src_port != 0) && 
+                   (ps->src_port != request->src_port)) continue;
+
+               /*
+                *      We're sourcing from *, and they asked for a
+                *      specific source address: ignore it.
+                */
+               if (ps->src_any && !src_any) continue;
+
+               /*
+                *      We're sourcing from a specific IP, and they
+                *      asked for a source IP that isn't us: ignore
+                *      it.
+                */
+               if (!ps->src_any && !src_any &&
+                   (fr_ipaddr_cmp(&request->src_ipaddr,
+                                  &ps->src_ipaddr) != 0)) continue;
+
+               /*
+                *      UDP sockets are allowed to match
+                *      destination IPs exactly, OR a socket
+                *      with destination * is allowed to match
+                *      any requested destination.
+                *
+                *      TCP sockets must match the destination
+                *      exactly.  They *always* have dst_any=0,
+                *      so the first check always matches.
+                */
+               if (!ps->dst_any &&
+                   (fr_ipaddr_cmp(&request->dst_ipaddr,
+                                  &ps->dst_ipaddr) != 0)) continue;
+               
+               /*
+                *      Otherwise, this socket is OK to use.
+                */
+
+               /*
+                *      Look for a free Id, starting from a random number.
+                */
+               start_j = fr_rand() & 0x1f;
+#define ID_j ((j + start_j) & 0x1f)
+               for (j = 0; j < 32; j++) {
+                       if (ps->id[ID_j] == 0xff) continue;
+
+
+                       start_k = fr_rand() & 0x07;
+#define ID_k ((k + start_k) & 0x07)
+                       for (k = 0; k < 8; k++) {
+                               if ((ps->id[ID_j] & (1 << ID_k)) != 0) continue;
+
+                               ps->id[ID_j] |= (1 << ID_k);
+                               id = (ID_j * 8) + ID_k;
+                               fd = i;
+                               break;
+                       }
+                       if (fd >= 0) break;
                }
+#undef ID_i
+#undef ID_j
+#undef ID_k
+               if (fd >= 0) break;
+               break;
        }
 
-       if (start < 0) return 0; /* bad error */
-
-       pd->id[id] |= (1 << start);
-       ps = &pl->sockets[start];
+       /*
+        *      Ask the caller to allocate a new ID.
+        */
+       if (fd < 0) return 0;
 
        ps->num_outgoing++;
        pl->num_outgoing++;
@@ -718,9 +821,12 @@ int lrad_packet_list_id_alloc(lrad_packet_list_t *pl,
         *      Set the ID, source IP, and source port.
         */
        request->id = id;
+
        request->sockfd = ps->sockfd;
-       request->src_ipaddr = ps->ipaddr;
-       request->src_port = ps->port;
+       request->src_ipaddr = ps->src_ipaddr;
+       request->src_port = ps->src_port;
+
+       if (pctx) *pctx = ps->ctx;
 
        return 1;
 }
@@ -729,24 +835,23 @@ int lrad_packet_list_id_alloc(lrad_packet_list_t *pl,
  *     Should be called AFTER yanking it from the list, so that
  *     any newly inserted entries don't collide with this one.
  */
-int lrad_packet_list_id_free(lrad_packet_list_t *pl,
+int fr_packet_list_id_free(fr_packet_list_t *pl,
                             RADIUS_PACKET *request)
 {
-       lrad_packet_socket_t *ps;
-       lrad_packet_dst2id_t my_pd, *pd;
+       fr_packet_socket_t *ps;
 
        if (!pl || !request) return 0;
 
-       ps = lrad_socket_find(pl, request->sockfd);
+       ps = fr_socket_find(pl, request->sockfd);
        if (!ps) return 0;
 
-       my_pd.dst_ipaddr = request->dst_ipaddr;
-       my_pd.dst_port = request->dst_port;
-       
-       pd = lrad_hash_table_finddata(pl->dst2id_ht, &my_pd);
-       if (!pd) return 0;
+#if 0
+       if (!ps->id[(request->id >> 3) & 0x1f] & (1 << (request->id & 0x07))) {
+               exit(1);
+       }
+#endif
 
-       pd->id[request->id] &= ~(1 << ps->offset);
+       ps->id[(request->id >> 3) & 0x1f] &= ~(1 << (request->id & 0x07));
        request->hash = 0;      /* invalidate the cached hash */
 
        ps->num_outgoing--;
@@ -755,15 +860,15 @@ int lrad_packet_list_id_free(lrad_packet_list_t *pl,
        return 1;
 }
 
-int lrad_packet_list_walk(lrad_packet_list_t *pl, void *ctx,
-                         lrad_hash_table_walk_t callback)
+int fr_packet_list_walk(fr_packet_list_t *pl, void *ctx,
+                         fr_hash_table_walk_t callback)
 {
        if (!pl || !callback) return 0;
 
-       return lrad_hash_table_walk(pl->ht, callback, ctx);
+       return fr_hash_table_walk(pl->ht, callback, ctx);
 }
 
-int lrad_packet_list_fd_set(lrad_packet_list_t *pl, fd_set *set)
+int fr_packet_list_fd_set(fr_packet_list_t *pl, fd_set *set)
 {
        int i, maxfd;
 
@@ -790,7 +895,7 @@ int lrad_packet_list_fd_set(lrad_packet_list_t *pl, fd_set *set)
  *     FIXME: Add sockfd, if -1, do round-robin, else do sockfd
  *             IF in fdset.
  */
-RADIUS_PACKET *lrad_packet_list_recv(lrad_packet_list_t *pl, fd_set *set)
+RADIUS_PACKET *fr_packet_list_recv(fr_packet_list_t *pl, fd_set *set)
 {
        int start;
        RADIUS_PACKET *packet;
@@ -806,11 +911,16 @@ RADIUS_PACKET *lrad_packet_list_recv(lrad_packet_list_t *pl, fd_set *set)
 
                if (!FD_ISSET(pl->sockets[start].sockfd, set)) continue;
 
-               packet = rad_recv(pl->sockets[start].sockfd);
+#ifdef WITH_TCP
+               if (pl->sockets[start].proto == IPPROTO_TCP) {
+                       packet = fr_tcp_recv(pl->sockets[start].sockfd, 0);
+               } else
+#endif
+               packet = rad_recv(pl->sockets[start].sockfd, 0);
                if (!packet) continue;
 
                /*
-                *      Call lrad_packet_list_find_byreply().  If it
+                *      Call fr_packet_list_find_byreply().  If it
                 *      doesn't find anything, discard the reply.
                 */
 
@@ -821,19 +931,19 @@ RADIUS_PACKET *lrad_packet_list_recv(lrad_packet_list_t *pl, fd_set *set)
        return NULL;
 }
 
-int lrad_packet_list_num_incoming(lrad_packet_list_t *pl)
+int fr_packet_list_num_incoming(fr_packet_list_t *pl)
 {
        int num_elements;
 
        if (!pl) return 0;
 
-       num_elements = lrad_hash_table_num_elements(pl->ht);
+       num_elements = fr_hash_table_num_elements(pl->ht);
        if (num_elements < pl->num_outgoing) return 0; /* panic! */
 
        return num_elements - pl->num_outgoing;
 }
 
-int lrad_packet_list_num_outgoing(lrad_packet_list_t *pl)
+int fr_packet_list_num_outgoing(fr_packet_list_t *pl)
 {
        if (!pl) return 0;