* Copyright 2000-2006 The FreeRADIUS server project
*/
-static const char rcsid[] = "$Id$";
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
-#include <freeradius-devel/autoconf.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);
/*
*/
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;
break;
}
- return lrad_hash_update(&packet->id, sizeof(packet->id), hash);
+ return fr_hash_update(&packet->id, sizeof(packet->id), hash);
}
* 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);
/*
*/
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;
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);
}
* 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;
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;
}
+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;
}
*/
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
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;
}
/*
* 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)
* 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;
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)
{
- lrad_packet_socket_t *ps;
+ 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)
+{
+ 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;
/*
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);
do {
if (pl->sockets[i].sockfd == -1) {
ps = &pl->sockets[i];
- start = i;
break;
}
} 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
* 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)
const RADIUS_PACKET * const *a = one;
const RADIUS_PACKET * const *b = two;
- return lrad_packet_cmp(*a, *b);
-}
-
-/*
- * 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;
+ if (!a || !*a || !b || !*b) return -1; /* work-around for bug #35 */
-
-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)
-{
- lrad_packet_dst2id_t *pd = data;
-
- free(pd->id);
- 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);
}
/*
* 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;
}
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);
}
* 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;
/*
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);
}
* 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;
-
- memcpy(pd, &my_pd, sizeof(*pd) + 255 * sizeof(pd->id[0]));
- memset(pd->id, 0, 256 * sizeof(pd->id[0]));
+ int i, j, k, fd, id, start_i, start_j, start_k;
+ int src_any = 0;
+ fr_packet_socket_t *ps;
- if (!lrad_hash_table_insert(pl->dst2id_ht, pd)) {
- free(pd);
- return 0;
- }
+ 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++;
* 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;
}
* 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--;
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;
* 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;
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.
*/
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;