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