Clean up API for packet list stuff, and a minor bug.
[freeradius.git] / src / lib / packet.c
1 /*
2  * packet.c     Generic packet manipulation functions.
3  *
4  * Version:     $Id$
5  *
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.
10  *
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.
15  *
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
19  *
20  * Copyright 2000-2006  The FreeRADIUS server project
21  */
22
23 static const char rcsid[] = "$Id$";
24
25 #include        <freeradius-devel/autoconf.h>
26 #include        <freeradius-devel/libradius.h>
27
28 #include <unistd.h>
29 #include <stdlib.h>
30
31 /*
32  *      Take the key fields of a request packet, and convert it to a
33  *      hash.
34  */
35 uint32_t lrad_request_packet_hash(const RADIUS_PACKET *packet)
36 {
37         uint32_t hash;
38
39         if (packet->hash) return packet->hash;
40
41         hash = lrad_hash(&packet->sockfd, sizeof(packet->sockfd));
42         hash = lrad_hash_update(&packet->id, sizeof(packet->id), hash);
43         hash = lrad_hash_update(&packet->src_port, sizeof(packet->src_port),
44                                 hash);
45         hash = lrad_hash_update(&packet->dst_port,
46                                 sizeof(packet->dst_port), hash);
47         hash = lrad_hash_update(&packet->src_ipaddr.af,
48                                 sizeof(packet->src_ipaddr.af), hash);
49
50         /*
51          *      The caller ensures that src & dst AF are the same.
52          */
53         switch (packet->src_ipaddr.af) {
54         case AF_INET:
55                 hash = lrad_hash_update(&packet->src_ipaddr.ipaddr.ip4addr,
56                                         sizeof(packet->src_ipaddr.ipaddr.ip4addr),
57                                         hash);
58                 hash = lrad_hash_update(&packet->dst_ipaddr.ipaddr.ip4addr,
59                                         sizeof(packet->dst_ipaddr.ipaddr.ip4addr),
60                                         hash);
61                 break;
62         case AF_INET6:
63                 hash = lrad_hash_update(&packet->src_ipaddr.ipaddr.ip6addr,
64                                         sizeof(packet->src_ipaddr.ipaddr.ip6addr),
65                                         hash);
66                 hash = lrad_hash_update(&packet->dst_ipaddr.ipaddr.ip6addr,
67                                         sizeof(packet->dst_ipaddr.ipaddr.ip6addr),
68                                         hash);
69                 break;
70         default:
71                 break;
72         }
73
74         return lrad_hash_update(&packet->id, sizeof(packet->id), hash);
75 }
76
77
78 /*
79  *      Take the key fields of a reply packet, and convert it to a
80  *      hash.
81  *
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
85  *      (dst, src)
86  */
87 uint32_t lrad_reply_packet_hash(const RADIUS_PACKET *packet)
88 {
89         uint32_t hash;
90         
91         hash = lrad_hash(&packet->sockfd, sizeof(packet->sockfd));
92         hash = lrad_hash_update(&packet->id, sizeof(packet->id), hash);
93         hash = lrad_hash_update(&packet->src_port, sizeof(packet->src_port),
94                                 hash);
95         hash = lrad_hash_update(&packet->dst_port,
96                                 sizeof(packet->dst_port), hash);
97         hash = lrad_hash_update(&packet->src_ipaddr.af,
98                                 sizeof(packet->src_ipaddr.af), hash);
99
100         /*
101          *      The caller ensures that src & dst AF are the same.
102          */
103         switch (packet->src_ipaddr.af) {
104         case AF_INET:
105                 hash = lrad_hash_update(&packet->dst_ipaddr.ipaddr.ip4addr,
106                                         sizeof(packet->dst_ipaddr.ipaddr.ip4addr),
107                                         hash);
108                 hash = lrad_hash_update(&packet->src_ipaddr.ipaddr.ip4addr,
109                                         sizeof(packet->src_ipaddr.ipaddr.ip4addr),
110                                         hash);
111                 break;
112         case AF_INET6:
113                 hash = lrad_hash_update(&packet->dst_ipaddr.ipaddr.ip6addr,
114                                         sizeof(packet->dst_ipaddr.ipaddr.ip6addr),
115                                         hash);
116                 hash = lrad_hash_update(&packet->src_ipaddr.ipaddr.ip6addr,
117                                         sizeof(packet->src_ipaddr.ipaddr.ip6addr),
118                                         hash);
119                 break;
120         default:
121                 break;
122         }
123
124         return lrad_hash_update(&packet->id, sizeof(packet->id), hash);
125 }
126
127
128 /*
129  *      See if two packets are identical.
130  *
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.
134  */
135 int lrad_packet_cmp(const RADIUS_PACKET *a, const RADIUS_PACKET *b)
136 {
137         int rcode;
138
139         if (a->sockfd < b->sockfd) return -1;
140         if (a->sockfd > b->sockfd) return +1;
141
142         if (a->id < b->id) return -1;
143         if (a->id > b->id) return +1;
144
145         if (a->src_ipaddr.af < b->dst_ipaddr.af) return -1;
146         if (a->src_ipaddr.af > b->dst_ipaddr.af) return +1;
147
148         if (a->src_port < b->src_port) return -1;
149         if (a->src_port > b->src_port) return +1;
150
151         if (a->dst_port < b->dst_port) return -1;
152         if (a->dst_port > b->dst_port) return +1;
153
154         switch (a->dst_ipaddr.af) {
155         case AF_INET:
156                 rcode = memcmp(&a->dst_ipaddr.ipaddr.ip4addr,
157                                &b->dst_ipaddr.ipaddr.ip4addr,
158                                sizeof(a->dst_ipaddr.ipaddr.ip4addr));
159                 if (rcode != 0) return rcode;
160                 rcode = memcmp(&a->src_ipaddr.ipaddr.ip4addr,
161                                &b->src_ipaddr.ipaddr.ip4addr,
162                                sizeof(a->src_ipaddr.ipaddr.ip4addr));
163                 if (rcode != 0) return rcode;
164                 break;
165         case AF_INET6:
166                 rcode = memcmp(&a->dst_ipaddr.ipaddr.ip6addr,
167                                &b->dst_ipaddr.ipaddr.ip6addr,
168                                sizeof(a->dst_ipaddr.ipaddr.ip6addr));
169                 if (rcode != 0) return rcode;
170                 rcode = memcmp(&a->src_ipaddr.ipaddr.ip6addr,
171                                &b->src_ipaddr.ipaddr.ip6addr,
172                                sizeof(a->src_ipaddr.ipaddr.ip6addr));
173                 if (rcode != 0) return rcode;
174                 break;
175         default:
176                 return -1;
177                 break;
178         }
179
180         /*
181          *      Everything's equal.  Say so.
182          */
183         return 0;
184 }
185
186
187 /*
188  *      Create a fake "request" from a reply, for later lookup.
189  */
190 void lrad_request_from_reply(RADIUS_PACKET *request,
191                              const RADIUS_PACKET *reply)
192 {
193         request->sockfd = reply->sockfd;
194         request->id = reply->id;
195         request->src_port = reply->dst_port;
196         request->dst_port = reply->src_port;
197         request->src_ipaddr = reply->dst_ipaddr;
198         request->dst_ipaddr = reply->src_ipaddr;
199 }
200
201
202 /*
203  *      Open a socket on the given IP and port.
204  */
205 int lrad_socket(lrad_ipaddr_t *ipaddr, int port)
206 {
207         int sockfd;
208         struct sockaddr salocal;
209         socklen_t       salen;
210
211         if ((port < 0) || (port > 65535)) {
212                 librad_log("Port %d is out of allowed bounds", port);
213                 return -1;
214         }
215
216         sockfd = socket(ipaddr->af, SOCK_DGRAM, 0);
217         if (sockfd < 0) {
218                 librad_log("Failed opening socket: %s", strerror(errno));
219                 return sockfd;
220         }
221
222 #ifdef WITH_UDPFROMTO
223         /*
224          *      Initialize udpfromto for all sockets.
225          */
226         if (udpfromto_init(sockfd) != 0) {
227                 close(sockfd);
228                 return -1;
229         }
230 #endif
231         
232         if (ipaddr->af == AF_INET) {
233                 struct sockaddr_in *sa;
234                 
235                 sa = (struct sockaddr_in *) &salocal;
236                 memset(sa, 0, sizeof(salocal));
237                 sa->sin_family = AF_INET;
238                 sa->sin_addr = ipaddr->ipaddr.ip4addr;
239                 sa->sin_port = htons((uint16_t) port);
240                 salen = sizeof(*sa);
241                 
242 #ifdef HAVE_STRUCT_SOCKADDR_IN6
243         } else if (ipaddr->af == AF_INET6) {
244                 struct sockaddr_in6 *sa;
245                 
246                 sa = (struct sockaddr_in6 *) &salocal;
247                 memset(sa, 0, sizeof(salocal));
248                 sa->sin6_family = AF_INET6;
249                 sa->sin6_addr = ipaddr->ipaddr.ip6addr;
250                 sa->sin6_port = htons((uint16_t) port);
251                 salen = sizeof(*sa);
252                 
253                 /*
254                  *      Listening on '::' does NOT get you IPv4 to
255                  *      IPv6 mapping.  You've got to listen on an IPv4
256                  *      address, too.  This makes the rest of the server
257                  *      design a little simpler.
258                  */
259 #ifdef IPV6_V6ONLY
260                 if (IN6_IS_ADDR_UNSPECIFIED(&ipaddr->ipaddr.ip6addr)) {
261                         int on = 1;
262                         
263                         setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
264                                    (char *)&on, sizeof(on));
265                 }
266 #endif /* IPV6_V6ONLY */
267 #endif /* HAVE_STRUCT_SOCKADDR_IN6 */
268         } else {
269                 return sockfd;  /* don't bind it */
270         }
271
272         if (bind(sockfd, &salocal, salen) < 0) {
273                 librad_log("Bind to address failed: %s", strerror(errno));
274                 close(sockfd);
275                 return -1;
276         }
277
278         return sockfd;
279 }
280
281
282 /*
283  *      We need to keep track of the socket & it's IP/port.
284  */
285 typedef struct lrad_packet_socket_t {
286         int             sockfd;
287
288         int             num_outgoing;
289
290         int             offset; /* 0..31 */
291         int             inaddr_any;
292         lrad_ipaddr_t   ipaddr;
293         int             port;
294 } lrad_packet_socket_t;
295
296
297 #define FNV_MAGIC_PRIME (0x01000193)
298 #define MAX_SOCKETS (32)
299 #define SOCKOFFSET_MASK (MAX_SOCKETS - 1)
300 #define SOCK2OFFSET(sockfd) ((sockfd * FNV_MAGIC_PRIME) & SOCKOFFSET_MASK)
301
302 #define MAX_QUEUES (8)
303
304 /*
305  *      Structure defining a list of packets (incoming or outgoing)
306  *      that should be managed.
307  */
308 struct lrad_packet_list_t {
309         lrad_hash_table_t *ht;
310
311         lrad_hash_table_t *dst2id_ht;
312
313         int             alloc_id;
314         int             num_outgoing;
315
316         uint32_t mask;
317         int last_recv;
318         lrad_packet_socket_t sockets[MAX_SOCKETS];
319 };
320
321
322 /*
323  *      Ugh.  Doing this on every sent/received packet is not nice.
324  */
325 static lrad_packet_socket_t *lrad_socket_find(lrad_packet_list_t *pl,
326                                              int sockfd)
327 {
328         int i, start;
329
330         i = start = SOCK2OFFSET(sockfd);
331
332         do {                    /* make this hack slightly more efficient */
333                 if (pl->sockets[i].sockfd == sockfd) return &pl->sockets[i];
334
335                 i = (i + 1) & SOCKOFFSET_MASK;
336         } while (i != start);
337
338         return NULL;
339 }
340
341 int lrad_packet_list_socket_remove(lrad_packet_list_t *pl, int sockfd)
342 {
343         lrad_packet_socket_t *ps;
344
345         if (!pl) return 0;
346
347         ps = lrad_socket_find(pl, sockfd);
348         if (!ps) return 0;
349
350         /*
351          *      FIXME: Allow the caller forcibly discard these?
352          */
353         if (ps->num_outgoing != 0) return 0;
354
355         ps->sockfd = -1;
356         pl->mask &= ~(1 << ps->offset);
357
358
359         return 1;
360 }
361
362 int lrad_packet_list_socket_add(lrad_packet_list_t *pl, int sockfd)
363 {
364         int i, start;
365         struct sockaddr_storage src;
366         socklen_t               sizeof_src = sizeof(src);
367         lrad_packet_socket_t    *ps;
368
369         if (!pl) return 0;
370
371         ps = NULL;
372         i = start = SOCK2OFFSET(sockfd);
373
374         do {
375                 if (pl->sockets[i].sockfd == -1) {
376                         ps =  &pl->sockets[i];
377                         start = i;
378                         break;
379                 }
380
381                 i = (i + 1) & SOCKOFFSET_MASK;
382         } while (i != start);
383
384         if (!ps) {
385                 return 0;
386         }
387
388         memset(ps, 0, sizeof(*ps));
389         ps->sockfd = sockfd;
390         ps->offset = start;
391
392         /*
393          *      Get address family, etc. first, so we know if we
394          *      need to do udpfromto.
395          *
396          *      FIXME: udpfromto also does this, but it's not
397          *      a critical problem.
398          */
399         memset(&src, 0, sizeof_src);
400         if (getsockname(sockfd, (struct sockaddr *) &src,
401                         &sizeof_src) < 0) {
402                 return 0;
403         }
404
405         /*
406          *      Grab IP addresses & ports from the sockaddr.
407          */
408         ps->ipaddr.af = src.ss_family;
409         if (src.ss_family == AF_INET) {
410                 struct sockaddr_in      *s4;
411
412                 s4 = (struct sockaddr_in *)&src;
413                 ps->ipaddr.ipaddr.ip4addr = s4->sin_addr;
414                 ps->port = ntohs(s4->sin_port);
415
416                 if (ps->ipaddr.ipaddr.ip4addr.s_addr == INADDR_ANY) {
417                         ps->inaddr_any = 1;
418                 }
419
420 #ifdef HAVE_STRUCT_SOCKADDR_IN6
421         } else if (src.ss_family == AF_INET6) {
422                 struct sockaddr_in6     *s6;
423
424                 s6 = (struct sockaddr_in6 *)&src;
425                 ps->ipaddr.ipaddr.ip6addr = s6->sin6_addr;
426                 ps->port = ntohs(s6->sin6_port);
427
428                 if (IN6_IS_ADDR_UNSPECIFIED(&ps->ipaddr.ipaddr.ip6addr)) {
429                         ps->inaddr_any = 1;
430                 }
431 #endif
432         } else {
433                 return 0;
434         }
435
436         pl->mask |= (1 << ps->offset);
437         return 1;
438 }
439
440
441 static uint32_t packet_entry_hash(const void *data)
442 {
443         return lrad_request_packet_hash(*(const RADIUS_PACKET * const *) data);
444 }
445
446 static int packet_entry_cmp(const void *one, const void *two)
447 {
448         const RADIUS_PACKET * const *a = one;
449         const RADIUS_PACKET * const *b = two;
450
451         return lrad_packet_cmp(*a, *b);
452 }
453
454 /*
455  *      A particular socket can have 256 RADIUS ID's outstanding to
456  *      any one destination IP/port.  So we have a structure that
457  *      manages destination IP & port, and has an array of 256 ID's.
458  *
459  *      The only magic here is that we map the socket number (0..256)
460  *      into an "internal" socket number 0..31, that we use to set
461  *      bits in the ID array.  If a bit is 1, then that ID is in use
462  *      for that socket, and the request MUST be in the packet hash!
463  *
464  *      Note that as a minor memory leak, we don't have an API to free
465  *      this structure, except when we discard the whole packet list.
466  *      This means that if destinations are added and removed, they
467  *      won't be removed from this tree.
468  */
469 typedef struct lrad_packet_dst2id_t {
470         lrad_ipaddr_t   dst_ipaddr;
471         int             dst_port;
472         uint32_t        id[1];  /* really id[256] */
473 } lrad_packet_dst2id_t;
474
475
476 static uint32_t packet_dst2id_hash(const void *data)
477 {
478         uint32_t hash;
479         const lrad_packet_dst2id_t *pd = data;
480
481         hash = lrad_hash(&pd->dst_port, sizeof(pd->dst_port));
482         
483         switch (pd->dst_ipaddr.af) {
484         case AF_INET:
485                 hash = lrad_hash_update(&pd->dst_ipaddr.ipaddr.ip4addr,
486                                         sizeof(pd->dst_ipaddr.ipaddr.ip4addr),
487                                         hash);
488                 break;
489         case AF_INET6:
490                 hash = lrad_hash_update(&pd->dst_ipaddr.ipaddr.ip6addr,
491                                         sizeof(pd->dst_ipaddr.ipaddr.ip6addr),
492                                         hash);
493                 break;
494         default:
495                 break;
496         }
497
498         return hash;
499 }
500
501 static int packet_dst2id_cmp(const void *one, const void *two)
502 {
503         const lrad_packet_dst2id_t *a = one;
504         const lrad_packet_dst2id_t *b = two;
505
506         if (a->dst_port < b->dst_port) return -1;
507         if (a->dst_port > b->dst_port) return +1;
508
509         switch (a->dst_ipaddr.af) {
510         case AF_INET:
511                 return memcmp(&a->dst_ipaddr.ipaddr.ip4addr,
512                               &b->dst_ipaddr.ipaddr.ip4addr,
513                               sizeof(a->dst_ipaddr.ipaddr.ip4addr));
514                 break;
515         case AF_INET6:
516                 return memcmp(&a->dst_ipaddr.ipaddr.ip6addr,
517                               &b->dst_ipaddr.ipaddr.ip6addr,
518                               sizeof(a->dst_ipaddr.ipaddr.ip6addr));
519                 break;
520         default:
521                 break;
522         }
523         
524         return 0;
525 }
526
527 static void packet_ds2id_free(void *data)
528 {
529         lrad_packet_dst2id_t *pd = data;
530
531         free(pd->id);
532         free(data);
533 }
534
535
536 void lrad_packet_list_free(lrad_packet_list_t *pl)
537 {
538         if (!pl) return;
539
540         if (pl->ht) lrad_hash_table_free(pl->ht);
541         if (pl->dst2id_ht) lrad_hash_table_free(pl->dst2id_ht);
542 }
543
544
545 /*
546  *      Caller is responsible for managing the packet entries.
547  */
548 lrad_packet_list_t *lrad_packet_list_create(int alloc_id)
549 {
550         int i;
551         lrad_packet_list_t      *pl;
552
553         pl = malloc(sizeof(*pl));
554         if (!pl) return NULL;
555         memset(pl, 0, sizeof(*pl));
556
557         pl->ht = lrad_hash_table_create(packet_entry_hash,
558                                         packet_entry_cmp,
559                                         NULL);
560         if (!pl->ht) {
561                 lrad_packet_list_free(pl);
562                 return NULL;
563         }
564
565         for (i = 0; i < MAX_SOCKETS; i++) {
566                 pl->sockets[i].sockfd = -1;
567         }
568
569         if (alloc_id) {
570                 pl->alloc_id = 1;
571
572                 pl->dst2id_ht = lrad_hash_table_create(packet_dst2id_hash,
573                                                        packet_dst2id_cmp,
574                                                        packet_ds2id_free);
575                 if (!pl->dst2id_ht) {
576                         lrad_packet_list_free(pl);
577                         return NULL;
578                 }
579         }
580
581         return pl;
582 }
583
584
585 /*
586  *      If pl->alloc_id is set, then lrad_packet_list_id_alloc() MUST
587  *      be called before inserting the packet into the list!
588  */
589 int lrad_packet_list_insert(lrad_packet_list_t *pl,
590                             RADIUS_PACKET **request_p)
591 {
592         if (!pl || !request_p || !*request_p) return 0;
593
594         (*request_p)->hash = lrad_request_packet_hash(*request_p);
595
596         return lrad_hash_table_insert(pl->ht, request_p);
597 }
598
599 RADIUS_PACKET **lrad_packet_list_find(lrad_packet_list_t *pl,
600                                       RADIUS_PACKET *request)
601 {
602         if (!pl || !request) return 0;
603
604         return lrad_hash_table_finddata(pl->ht, &request);
605 }
606
607
608 /*
609  *      This presumes that the reply has dst_ipaddr && dst_port set up
610  *      correctly (i.e. real IP, or "*").
611  */
612 RADIUS_PACKET **lrad_packet_list_find_byreply(lrad_packet_list_t *pl,
613                                               RADIUS_PACKET *reply)
614 {
615         RADIUS_PACKET my_request, *request;
616         lrad_packet_socket_t *ps;
617
618         if (!pl || !reply) return NULL;
619
620         ps = lrad_socket_find(pl, reply->sockfd);
621         if (!ps) return NULL;
622
623         /*
624          *      Initialize request from reply, AND from the source
625          *      IP & port of this socket.  The client may have bound
626          *      the socket to 0, in which case it's some random port,
627          *      that is NOT in the original request->src_port.
628          */
629         my_request.sockfd = reply->sockfd;
630         my_request.id = reply->id;
631
632         if (ps->inaddr_any) {
633                 my_request.src_ipaddr = ps->ipaddr;
634         } else {
635                 my_request.src_ipaddr = reply->dst_ipaddr;
636         }
637         my_request.src_port = ps->port;;
638
639         my_request.dst_ipaddr = reply->src_ipaddr;
640         my_request.dst_port = reply->src_port;
641         my_request.hash = 0;
642         
643         request = &my_request;
644
645         return lrad_hash_table_finddata(pl->ht, &request);
646 }
647
648
649 RADIUS_PACKET **lrad_packet_list_yank(lrad_packet_list_t *pl,
650                                       RADIUS_PACKET *request)
651 {
652         if (!pl || !request) return NULL;
653
654         return lrad_hash_table_yank(pl->ht, &request);
655 }
656
657 int lrad_packet_list_num_elements(lrad_packet_list_t *pl)
658 {
659         if (!pl) return 0;
660
661         return lrad_hash_table_num_elements(pl->ht);
662 }
663
664
665 /*
666  *      1 == ID was allocated & assigned
667  *      0 == error allocating memory
668  *      -1 == all ID's are used, caller should open a new socket.
669  *
670  *      Note that this ALSO assigns a socket to use, and updates
671  *      packet->request->src_ipaddr && packet->request->src_port
672  *
673  *      In multi-threaded systems, the calls to id_alloc && id_free
674  *      should be protected by a mutex.  This does NOT have to be
675  *      the same mutex as the one protecting the insert/find/yank
676  *      calls!
677  */
678 int lrad_packet_list_id_alloc(lrad_packet_list_t *pl,
679                               RADIUS_PACKET *request)
680 {
681         int i, id, start;
682         uint32_t free_mask;
683         lrad_packet_dst2id_t my_pd, *pd;
684         lrad_packet_socket_t *ps;
685         
686         if (!pl || !pl->alloc_id || !request) return 0;
687
688         my_pd.dst_ipaddr = request->dst_ipaddr;
689         my_pd.dst_port = request->dst_port;
690         
691         pd = lrad_hash_table_finddata(pl->dst2id_ht, &my_pd);
692         if (!pd) {
693                 pd = malloc(sizeof(*pd) + 255 * sizeof(pd->id[0]));
694                 if (!pd) return 0;
695                 
696                 memcpy(pd, &my_pd, sizeof(*pd) + 255 * sizeof(pd->id[0]));
697                 memset(pd->id, 0, 256 * sizeof(pd->id[0]));
698
699                 if (!lrad_hash_table_insert(pl->dst2id_ht, pd)) {
700                         free(pd);
701                         return 0;
702                 }
703         }
704         
705         /*
706          *      FIXME: Go to an LRU system.  This prevents ID re-use
707          *      for as long as possible.  The main problem with that
708          *      approach is that it requires us to populate the
709          *      LRU/FIFO when we add a new socket, or a new destination,
710          *      which can be expensive.
711          */
712         id = start = (int) lrad_rand() & 0xff;
713         
714         while (pd->id[id] == pl->mask) { /* all sockets are using this ID */
715                 id++;
716                 id &= 0xff;
717                 if (id == start) return -1;
718         }
719
720         free_mask = ~((~pd->id[id]) & pl->mask);
721
722         start = -1;
723         for (i = 0; i < MAX_SOCKETS; i++) {
724                 if (pl->sockets[i].sockfd == -1) continue; /* paranoia */
725
726                 if ((free_mask & (1 << i)) == 0) {
727                         start = i;
728                         break;
729                 }
730         }
731
732         if (start < 0) return 0; /* bad error */
733
734         pd->id[id] |= (1 << start);
735         ps = &pl->sockets[start];
736
737         ps->num_outgoing++;
738         pl->num_outgoing++;
739
740         /*
741          *      Set the ID, source IP, and source port.
742          */
743         request->id = id;
744         request->sockfd = ps->sockfd;
745         request->src_ipaddr = ps->ipaddr;
746         request->src_port = ps->port;
747
748         return 1;
749 }
750
751 /*
752  *      Should be called AFTER yanking it from the list, so that
753  *      any newly inserted entries don't collide with this one.
754  */
755 int lrad_packet_list_id_free(lrad_packet_list_t *pl,
756                              RADIUS_PACKET *request)
757 {
758         lrad_packet_socket_t *ps;
759         lrad_packet_dst2id_t my_pd, *pd;
760
761         if (!pl || !request) return 0;
762
763         ps = lrad_socket_find(pl, request->sockfd);
764         if (!ps) return 0;
765
766         my_pd.dst_ipaddr = request->dst_ipaddr;
767         my_pd.dst_port = request->dst_port;
768         
769         pd = lrad_hash_table_finddata(pl->dst2id_ht, &my_pd);
770         if (!pd) return 0;
771
772         pd->id[request->id] &= ~(1 << ps->offset);
773         request->hash = 0;      /* invalidate the cached hash */
774
775         ps->num_outgoing--;
776         pl->num_outgoing--;
777
778         return 1;
779 }
780
781 int lrad_packet_list_walk(lrad_packet_list_t *pl, void *ctx,
782                           lrad_hash_table_walk_t callback)
783 {
784         if (!pl || !callback) return 0;
785
786         return lrad_hash_table_walk(pl->ht, callback, ctx);
787 }
788
789 int lrad_packet_list_fd_set(lrad_packet_list_t *pl, fd_set *set)
790 {
791         int i, maxfd;
792
793         if (!pl || !set) return 0;
794
795         maxfd = -1;
796
797         for (i = 0; i < MAX_SOCKETS; i++) {
798                 if (pl->sockets[i].sockfd == -1) continue;
799                 FD_SET(pl->sockets[i].sockfd, set);
800                 if (pl->sockets[i].sockfd > maxfd) {
801                         maxfd = pl->sockets[i].sockfd;
802                 }
803         }
804
805         if (maxfd < 0) return -1;
806
807         return maxfd + 1;
808 }
809
810 /*
811  *      Round-robins the receivers, without priority.
812  *
813  *      FIXME: Add sockfd, if -1, do round-robin, else do sockfd
814  *              IF in fdset.
815  */
816 RADIUS_PACKET *lrad_packet_list_recv(lrad_packet_list_t *pl, fd_set *set)
817 {
818         int start;
819         RADIUS_PACKET *packet;
820
821         if (!pl || !set) return NULL;
822
823         start = pl->last_recv;
824         do {
825                 start++;
826                 start &= SOCKOFFSET_MASK;
827
828                 if (pl->sockets[start].sockfd == -1) continue;
829
830                 if (!FD_ISSET(pl->sockets[start].sockfd, set)) continue;
831
832                 packet = rad_recv(pl->sockets[start].sockfd);
833                 if (!packet) continue;
834
835                 /*
836                  *      Call lrad_packet_list_find_byreply().  If it
837                  *      doesn't find anything, discard the reply.
838                  */
839
840                 pl->last_recv = start;
841                 return packet;
842         } while (start != pl->last_recv);
843
844         return NULL;
845 }
846
847 int lrad_packet_list_num_incoming(lrad_packet_list_t *pl)
848 {
849         int num_elements;
850
851         if (!pl) return 0;
852
853         num_elements = lrad_hash_table_num_elements(pl->ht);
854         if (num_elements < pl->num_outgoing) return 0; /* panic! */
855
856         return num_elements - pl->num_outgoing;
857 }
858
859 int lrad_packet_list_num_outgoing(lrad_packet_list_t *pl)
860 {
861         if (!pl) return 0;
862
863         return pl->num_outgoing;
864 }