2 * packet.c Generic packet manipulation functions.
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 * Copyright 2000-2006 The FreeRADIUS server project
23 #include <freeradius-devel/ident.h>
26 #include <freeradius-devel/libradius.h>
29 #include <freeradius-devel/udpfromto.h>
33 * Take the key fields of a request packet, and convert it to a
36 uint32_t fr_request_packet_hash(const RADIUS_PACKET *packet)
40 if (packet->hash) return packet->hash;
42 hash = fr_hash(&packet->sockfd, sizeof(packet->sockfd));
43 hash = fr_hash_update(&packet->src_port, sizeof(packet->src_port),
45 hash = fr_hash_update(&packet->dst_port,
46 sizeof(packet->dst_port), hash);
47 hash = fr_hash_update(&packet->src_ipaddr.af,
48 sizeof(packet->src_ipaddr.af), hash);
51 * The caller ensures that src & dst AF are the same.
53 switch (packet->src_ipaddr.af) {
55 hash = fr_hash_update(&packet->src_ipaddr.ipaddr.ip4addr,
56 sizeof(packet->src_ipaddr.ipaddr.ip4addr),
58 hash = fr_hash_update(&packet->dst_ipaddr.ipaddr.ip4addr,
59 sizeof(packet->dst_ipaddr.ipaddr.ip4addr),
63 hash = fr_hash_update(&packet->src_ipaddr.ipaddr.ip6addr,
64 sizeof(packet->src_ipaddr.ipaddr.ip6addr),
66 hash = fr_hash_update(&packet->dst_ipaddr.ipaddr.ip6addr,
67 sizeof(packet->dst_ipaddr.ipaddr.ip6addr),
74 return fr_hash_update(&packet->id, sizeof(packet->id), hash);
79 * Take the key fields of a reply packet, and convert it to a
82 * i.e. take a reply packet, and find the hash of the request packet
83 * that asked for the reply. To do this, we hash the reverse fields
84 * of the request. e.g. where the request does (src, dst), we do
87 uint32_t fr_reply_packet_hash(const RADIUS_PACKET *packet)
91 hash = fr_hash(&packet->sockfd, sizeof(packet->sockfd));
92 hash = fr_hash_update(&packet->id, sizeof(packet->id), hash);
93 hash = fr_hash_update(&packet->src_port, sizeof(packet->src_port),
95 hash = fr_hash_update(&packet->dst_port,
96 sizeof(packet->dst_port), hash);
97 hash = fr_hash_update(&packet->src_ipaddr.af,
98 sizeof(packet->src_ipaddr.af), hash);
101 * The caller ensures that src & dst AF are the same.
103 switch (packet->src_ipaddr.af) {
105 hash = fr_hash_update(&packet->dst_ipaddr.ipaddr.ip4addr,
106 sizeof(packet->dst_ipaddr.ipaddr.ip4addr),
108 hash = fr_hash_update(&packet->src_ipaddr.ipaddr.ip4addr,
109 sizeof(packet->src_ipaddr.ipaddr.ip4addr),
113 hash = fr_hash_update(&packet->dst_ipaddr.ipaddr.ip6addr,
114 sizeof(packet->dst_ipaddr.ipaddr.ip6addr),
116 hash = fr_hash_update(&packet->src_ipaddr.ipaddr.ip6addr,
117 sizeof(packet->src_ipaddr.ipaddr.ip6addr),
124 return fr_hash_update(&packet->id, sizeof(packet->id), hash);
129 * See if two packets are identical.
131 * Note that we do NOT compare the authentication vectors.
132 * That's because if the authentication vector is different,
133 * it means that the NAS has given up on the earlier request.
135 int fr_packet_cmp(const RADIUS_PACKET *a, const RADIUS_PACKET *b)
139 if (a->sockfd < b->sockfd) return -1;
140 if (a->sockfd > b->sockfd) return +1;
142 if (a->id < b->id) return -1;
143 if (a->id > b->id) return +1;
145 if (a->src_port < b->src_port) return -1;
146 if (a->src_port > b->src_port) return +1;
148 if (a->dst_port < b->dst_port) return -1;
149 if (a->dst_port > b->dst_port) return +1;
151 rcode = fr_ipaddr_cmp(&a->dst_ipaddr, &b->dst_ipaddr);
152 if (rcode != 0) return rcode;
153 return fr_ipaddr_cmp(&a->src_ipaddr, &b->src_ipaddr);
158 * Create a fake "request" from a reply, for later lookup.
160 void fr_request_from_reply(RADIUS_PACKET *request,
161 const RADIUS_PACKET *reply)
163 request->sockfd = reply->sockfd;
164 request->id = reply->id;
165 request->src_port = reply->dst_port;
166 request->dst_port = reply->src_port;
167 request->src_ipaddr = reply->dst_ipaddr;
168 request->dst_ipaddr = reply->src_ipaddr;
173 * Open a socket on the given IP and port.
175 int fr_socket(fr_ipaddr_t *ipaddr, int port)
178 struct sockaddr_storage salocal;
181 if ((port < 0) || (port > 65535)) {
182 fr_strerror_printf("Port %d is out of allowed bounds", port);
186 sockfd = socket(ipaddr->af, SOCK_DGRAM, 0);
188 fr_strerror_printf("cannot open socket: %s", strerror(errno));
192 #ifdef WITH_UDPFROMTO
194 * Initialize udpfromto for all sockets.
196 if (udpfromto_init(sockfd) != 0) {
198 fr_strerror_printf("cannot initialize udpfromto: %s", strerror(errno));
204 if (!fr_ipaddr2sockaddr(ipaddr, port, &salocal, &salen)) {
208 #ifdef HAVE_STRUCT_SOCKADDR_IN6
209 if (ipaddr->af == AF_INET6) {
211 * Listening on '::' does NOT get you IPv4 to
212 * IPv6 mapping. You've got to listen on an IPv4
213 * address, too. This makes the rest of the server
214 * design a little simpler.
218 if (IN6_IS_ADDR_UNSPECIFIED(&ipaddr->ipaddr.ip6addr)) {
221 setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
222 (char *)&on, sizeof(on));
224 #endif /* IPV6_V6ONLY */
226 #endif /* HAVE_STRUCT_SOCKADDR_IN6 */
228 if (ipaddr->af == AF_INET) {
231 #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
233 * Disable PMTU discovery. On Linux, this
234 * also makes sure that the "don't fragment"
237 flag = IP_PMTUDISC_DONT;
238 setsockopt(sock->fd, IPPROTO_IP, IP_MTU_DISCOVER,
239 &action, sizeof(action));
242 #if defined(IP_DONTFRAG)
244 * Ensure that the "don't fragment" flag is zero.
247 setsockopt(sock->fd, IPPROTO_IP, IP_DONTFRAG,
252 if (bind(sockfd, (struct sockaddr *) &salocal, salen) < 0) {
254 fr_strerror_printf("cannot bind socket: %s", strerror(errno));
263 * We need to keep track of the socket & it's IP/port.
265 typedef struct fr_packet_socket_t {
270 int offset; /* 0..31 */
274 } fr_packet_socket_t;
277 #define FNV_MAGIC_PRIME (0x01000193)
278 #define MAX_SOCKETS (32)
279 #define SOCKOFFSET_MASK (MAX_SOCKETS - 1)
280 #define SOCK2OFFSET(sockfd) ((sockfd * FNV_MAGIC_PRIME) & SOCKOFFSET_MASK)
282 #define MAX_QUEUES (8)
285 * Structure defining a list of packets (incoming or outgoing)
286 * that should be managed.
288 struct fr_packet_list_t {
291 fr_hash_table_t *dst2id_ht;
298 fr_packet_socket_t sockets[MAX_SOCKETS];
303 * Ugh. Doing this on every sent/received packet is not nice.
305 static fr_packet_socket_t *fr_socket_find(fr_packet_list_t *pl,
310 i = start = SOCK2OFFSET(sockfd);
312 do { /* make this hack slightly more efficient */
313 if (pl->sockets[i].sockfd == sockfd) return &pl->sockets[i];
315 i = (i + 1) & SOCKOFFSET_MASK;
316 } while (i != start);
321 int fr_packet_list_socket_remove(fr_packet_list_t *pl, int sockfd)
323 fr_packet_socket_t *ps;
327 ps = fr_socket_find(pl, sockfd);
331 * FIXME: Allow the caller forcibly discard these?
333 if (ps->num_outgoing != 0) return 0;
336 pl->mask &= ~(1 << ps->offset);
342 int fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd)
345 struct sockaddr_storage src;
346 socklen_t sizeof_src = sizeof(src);
347 fr_packet_socket_t *ps;
352 i = start = SOCK2OFFSET(sockfd);
355 if (pl->sockets[i].sockfd == -1) {
356 ps = &pl->sockets[i];
361 i = (i + 1) & SOCKOFFSET_MASK;
362 } while (i != start);
368 memset(ps, 0, sizeof(*ps));
373 * Get address family, etc. first, so we know if we
374 * need to do udpfromto.
376 * FIXME: udpfromto also does this, but it's not
377 * a critical problem.
379 memset(&src, 0, sizeof_src);
380 if (getsockname(sockfd, (struct sockaddr *) &src,
385 if (!fr_sockaddr2ipaddr(&src, sizeof_src, &ps->ipaddr, &ps->port)) {
390 * Grab IP addresses & ports from the sockaddr.
392 if (src.ss_family == AF_INET) {
393 if (ps->ipaddr.ipaddr.ip4addr.s_addr == INADDR_ANY) {
397 #ifdef HAVE_STRUCT_SOCKADDR_IN6
398 } else if (src.ss_family == AF_INET6) {
399 if (IN6_IS_ADDR_UNSPECIFIED(&ps->ipaddr.ipaddr.ip6addr)) {
407 pl->mask |= (1 << ps->offset);
411 static uint32_t packet_entry_hash(const void *data)
413 return fr_request_packet_hash(*(const RADIUS_PACKET * const *) data);
416 static int packet_entry_cmp(const void *one, const void *two)
418 const RADIUS_PACKET * const *a = one;
419 const RADIUS_PACKET * const *b = two;
421 return fr_packet_cmp(*a, *b);
425 * A particular socket can have 256 RADIUS ID's outstanding to
426 * any one destination IP/port. So we have a structure that
427 * manages destination IP & port, and has an array of 256 ID's.
429 * The only magic here is that we map the socket number (0..256)
430 * into an "internal" socket number 0..31, that we use to set
431 * bits in the ID array. If a bit is 1, then that ID is in use
432 * for that socket, and the request MUST be in the packet hash!
434 * Note that as a minor memory leak, we don't have an API to free
435 * this structure, except when we discard the whole packet list.
436 * This means that if destinations are added and removed, they
437 * won't be removed from this tree.
439 typedef struct fr_packet_dst2id_t {
440 fr_ipaddr_t dst_ipaddr;
442 uint32_t id[1]; /* really id[256] */
443 } fr_packet_dst2id_t;
446 static uint32_t packet_dst2id_hash(const void *data)
449 const fr_packet_dst2id_t *pd = data;
451 hash = fr_hash(&pd->dst_port, sizeof(pd->dst_port));
453 switch (pd->dst_ipaddr.af) {
455 hash = fr_hash_update(&pd->dst_ipaddr.ipaddr.ip4addr,
456 sizeof(pd->dst_ipaddr.ipaddr.ip4addr),
460 hash = fr_hash_update(&pd->dst_ipaddr.ipaddr.ip6addr,
461 sizeof(pd->dst_ipaddr.ipaddr.ip6addr),
471 static int packet_dst2id_cmp(const void *one, const void *two)
473 const fr_packet_dst2id_t *a = one;
474 const fr_packet_dst2id_t *b = two;
476 if (a->dst_port < b->dst_port) return -1;
477 if (a->dst_port > b->dst_port) return +1;
479 return fr_ipaddr_cmp(&a->dst_ipaddr, &b->dst_ipaddr);
482 static void packet_dst2id_free(void *data)
488 void fr_packet_list_free(fr_packet_list_t *pl)
492 fr_hash_table_free(pl->ht);
493 fr_hash_table_free(pl->dst2id_ht);
499 * Caller is responsible for managing the packet entries.
501 fr_packet_list_t *fr_packet_list_create(int alloc_id)
504 fr_packet_list_t *pl;
506 pl = malloc(sizeof(*pl));
507 if (!pl) return NULL;
508 memset(pl, 0, sizeof(*pl));
510 pl->ht = fr_hash_table_create(packet_entry_hash,
514 fr_packet_list_free(pl);
518 for (i = 0; i < MAX_SOCKETS; i++) {
519 pl->sockets[i].sockfd = -1;
525 pl->dst2id_ht = fr_hash_table_create(packet_dst2id_hash,
528 if (!pl->dst2id_ht) {
529 fr_packet_list_free(pl);
539 * If pl->alloc_id is set, then fr_packet_list_id_alloc() MUST
540 * be called before inserting the packet into the list!
542 int fr_packet_list_insert(fr_packet_list_t *pl,
543 RADIUS_PACKET **request_p)
545 if (!pl || !request_p || !*request_p) return 0;
547 (*request_p)->hash = fr_request_packet_hash(*request_p);
549 return fr_hash_table_insert(pl->ht, request_p);
552 RADIUS_PACKET **fr_packet_list_find(fr_packet_list_t *pl,
553 RADIUS_PACKET *request)
555 if (!pl || !request) return 0;
557 return fr_hash_table_finddata(pl->ht, &request);
562 * This presumes that the reply has dst_ipaddr && dst_port set up
563 * correctly (i.e. real IP, or "*").
565 RADIUS_PACKET **fr_packet_list_find_byreply(fr_packet_list_t *pl,
566 RADIUS_PACKET *reply)
568 RADIUS_PACKET my_request, *request;
569 fr_packet_socket_t *ps;
571 if (!pl || !reply) return NULL;
573 ps = fr_socket_find(pl, reply->sockfd);
574 if (!ps) return NULL;
577 * Initialize request from reply, AND from the source
578 * IP & port of this socket. The client may have bound
579 * the socket to 0, in which case it's some random port,
580 * that is NOT in the original request->src_port.
582 my_request.sockfd = reply->sockfd;
583 my_request.id = reply->id;
585 if (ps->inaddr_any) {
586 my_request.src_ipaddr = ps->ipaddr;
588 my_request.src_ipaddr = reply->dst_ipaddr;
590 my_request.src_port = ps->port;;
592 my_request.dst_ipaddr = reply->src_ipaddr;
593 my_request.dst_port = reply->src_port;
596 request = &my_request;
598 return fr_hash_table_finddata(pl->ht, &request);
602 RADIUS_PACKET **fr_packet_list_yank(fr_packet_list_t *pl,
603 RADIUS_PACKET *request)
605 if (!pl || !request) return NULL;
607 return fr_hash_table_yank(pl->ht, &request);
610 int fr_packet_list_num_elements(fr_packet_list_t *pl)
614 return fr_hash_table_num_elements(pl->ht);
619 * 1 == ID was allocated & assigned
620 * 0 == error allocating memory
621 * -1 == all ID's are used, caller should open a new socket.
623 * Note that this ALSO assigns a socket to use, and updates
624 * packet->request->src_ipaddr && packet->request->src_port
626 * In multi-threaded systems, the calls to id_alloc && id_free
627 * should be protected by a mutex. This does NOT have to be
628 * the same mutex as the one protecting the insert/find/yank
631 int fr_packet_list_id_alloc(fr_packet_list_t *pl,
632 RADIUS_PACKET *request)
634 int i, id, start, fd;
636 fr_packet_dst2id_t my_pd, *pd;
637 fr_packet_socket_t *ps;
639 if (!pl || !pl->alloc_id || !request) return 0;
641 my_pd.dst_ipaddr = request->dst_ipaddr;
642 my_pd.dst_port = request->dst_port;
644 pd = fr_hash_table_finddata(pl->dst2id_ht, &my_pd);
646 pd = malloc(sizeof(*pd) + 255 * sizeof(pd->id[0]));
649 memset(pd, 0, sizeof(*pd) + 255 * sizeof(pd->id[0]));
651 pd->dst_ipaddr = request->dst_ipaddr;
652 pd->dst_port = request->dst_port;
654 if (!fr_hash_table_insert(pl->dst2id_ht, pd)) {
661 * FIXME: Go to an LRU system. This prevents ID re-use
662 * for as long as possible. The main problem with that
663 * approach is that it requires us to populate the
664 * LRU/FIFO when we add a new socket, or a new destination,
665 * which can be expensive.
667 * The LRU can be avoided if the caller takes care to free
668 * Id's only when all responses have been received, OR after
671 id = start = (int) fr_rand() & 0xff;
673 while (pd->id[id] == pl->mask) { /* all sockets are using this ID */
677 if (id == start) return 0;
680 free_mask = ~((~pd->id[id]) & pl->mask);
683 * This ID has at least one socket free. Check the sockets
684 * to see if they are satisfactory for the caller.
687 for (i = 0; i < MAX_SOCKETS; i++) {
688 if (pl->sockets[i].sockfd == -1) continue; /* paranoia */
691 * This ID is allocated.
693 if ((free_mask & (1 << i)) != 0) continue;
696 * If the caller cares about the source address,
697 * try to re-use that. This means that the
698 * requested source address is set, AND this
699 * socket wasn't bound to "*", AND the requested
700 * source address is the same as this socket
703 if ((request->src_ipaddr.af != AF_UNSPEC) &&
704 !pl->sockets[i].inaddr_any &&
705 (fr_ipaddr_cmp(&request->src_ipaddr, &pl->sockets[i].ipaddr) != 0)) continue;
708 * They asked for a specific address, and this socket
709 * is bound to a wildcard address. Ignore this one, too.
711 if ((request->src_ipaddr.af != AF_UNSPEC) &&
712 pl->sockets[i].inaddr_any) continue;
719 goto redo; /* keep searching IDs */
722 pd->id[id] |= (1 << fd);
723 ps = &pl->sockets[fd];
729 * Set the ID, source IP, and source port.
733 request->sockfd = ps->sockfd;
734 request->src_ipaddr = ps->ipaddr;
735 request->src_port = ps->port;
741 * Should be called AFTER yanking it from the list, so that
742 * any newly inserted entries don't collide with this one.
744 int fr_packet_list_id_free(fr_packet_list_t *pl,
745 RADIUS_PACKET *request)
747 fr_packet_socket_t *ps;
748 fr_packet_dst2id_t my_pd, *pd;
750 if (!pl || !request) return 0;
752 ps = fr_socket_find(pl, request->sockfd);
755 my_pd.dst_ipaddr = request->dst_ipaddr;
756 my_pd.dst_port = request->dst_port;
758 pd = fr_hash_table_finddata(pl->dst2id_ht, &my_pd);
761 pd->id[request->id] &= ~(1 << ps->offset);
762 request->hash = 0; /* invalidate the cached hash */
770 int fr_packet_list_walk(fr_packet_list_t *pl, void *ctx,
771 fr_hash_table_walk_t callback)
773 if (!pl || !callback) return 0;
775 return fr_hash_table_walk(pl->ht, callback, ctx);
778 int fr_packet_list_fd_set(fr_packet_list_t *pl, fd_set *set)
782 if (!pl || !set) return 0;
786 for (i = 0; i < MAX_SOCKETS; i++) {
787 if (pl->sockets[i].sockfd == -1) continue;
788 FD_SET(pl->sockets[i].sockfd, set);
789 if (pl->sockets[i].sockfd > maxfd) {
790 maxfd = pl->sockets[i].sockfd;
794 if (maxfd < 0) return -1;
800 * Round-robins the receivers, without priority.
802 * FIXME: Add sockfd, if -1, do round-robin, else do sockfd
805 RADIUS_PACKET *fr_packet_list_recv(fr_packet_list_t *pl, fd_set *set)
808 RADIUS_PACKET *packet;
810 if (!pl || !set) return NULL;
812 start = pl->last_recv;
815 start &= SOCKOFFSET_MASK;
817 if (pl->sockets[start].sockfd == -1) continue;
819 if (!FD_ISSET(pl->sockets[start].sockfd, set)) continue;
821 packet = rad_recv(pl->sockets[start].sockfd, 0);
822 if (!packet) continue;
825 * Call fr_packet_list_find_byreply(). If it
826 * doesn't find anything, discard the reply.
829 pl->last_recv = start;
831 } while (start != pl->last_recv);
836 int fr_packet_list_num_incoming(fr_packet_list_t *pl)
842 num_elements = fr_hash_table_num_elements(pl->ht);
843 if (num_elements < pl->num_outgoing) return 0; /* panic! */
845 return num_elements - pl->num_outgoing;
848 int fr_packet_list_num_outgoing(fr_packet_list_t *pl)
852 return pl->num_outgoing;