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