Turn off the DF bit.
[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 #ifdef WITH_UDPFROMTO
29 #include        <freeradius-devel/udpfromto.h>
30 #endif
31
32 /*
33  *      Take the key fields of a request packet, and convert it to a
34  *      hash.
35  */
36 uint32_t fr_request_packet_hash(const RADIUS_PACKET *packet)
37 {
38         uint32_t hash;
39
40         if (packet->hash) return packet->hash;
41
42         hash = fr_hash(&packet->sockfd, sizeof(packet->sockfd));
43         hash = fr_hash_update(&packet->src_port, sizeof(packet->src_port),
44                                 hash);
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);
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 = fr_hash_update(&packet->src_ipaddr.ipaddr.ip4addr,
56                                         sizeof(packet->src_ipaddr.ipaddr.ip4addr),
57                                         hash);
58                 hash = fr_hash_update(&packet->dst_ipaddr.ipaddr.ip4addr,
59                                         sizeof(packet->dst_ipaddr.ipaddr.ip4addr),
60                                         hash);
61                 break;
62         case AF_INET6:
63                 hash = fr_hash_update(&packet->src_ipaddr.ipaddr.ip6addr,
64                                         sizeof(packet->src_ipaddr.ipaddr.ip6addr),
65                                         hash);
66                 hash = fr_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 fr_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 fr_reply_packet_hash(const RADIUS_PACKET *packet)
88 {
89         uint32_t hash;
90
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),
94                                 hash);
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);
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 = fr_hash_update(&packet->dst_ipaddr.ipaddr.ip4addr,
106                                         sizeof(packet->dst_ipaddr.ipaddr.ip4addr),
107                                         hash);
108                 hash = fr_hash_update(&packet->src_ipaddr.ipaddr.ip4addr,
109                                         sizeof(packet->src_ipaddr.ipaddr.ip4addr),
110                                         hash);
111                 break;
112         case AF_INET6:
113                 hash = fr_hash_update(&packet->dst_ipaddr.ipaddr.ip6addr,
114                                         sizeof(packet->dst_ipaddr.ipaddr.ip6addr),
115                                         hash);
116                 hash = fr_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 fr_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 fr_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_port < b->src_port) return -1;
146         if (a->src_port > b->src_port) return +1;
147
148         if (a->dst_port < b->dst_port) return -1;
149         if (a->dst_port > b->dst_port) return +1;
150
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);
154 }
155
156
157 /*
158  *      Create a fake "request" from a reply, for later lookup.
159  */
160 void fr_request_from_reply(RADIUS_PACKET *request,
161                              const RADIUS_PACKET *reply)
162 {
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;
169 }
170
171
172 /*
173  *      Open a socket on the given IP and port.
174  */
175 int fr_socket(fr_ipaddr_t *ipaddr, int port)
176 {
177         int sockfd;
178         struct sockaddr_storage salocal;
179         socklen_t       salen;
180
181         if ((port < 0) || (port > 65535)) {
182                 fr_strerror_printf("Port %d is out of allowed bounds", port);
183                 return -1;
184         }
185
186         sockfd = socket(ipaddr->af, SOCK_DGRAM, 0);
187         if (sockfd < 0) {
188                 fr_strerror_printf("cannot open socket: %s", strerror(errno));
189                 return sockfd;
190         }
191
192 #ifdef WITH_UDPFROMTO
193         /*
194          *      Initialize udpfromto for all sockets.
195          */
196         if (udpfromto_init(sockfd) != 0) {
197                 close(sockfd);
198                 fr_strerror_printf("cannot initialize udpfromto: %s", strerror(errno));
199                 return -1;
200         }
201 #endif
202
203
204         if (!fr_ipaddr2sockaddr(ipaddr, port, &salocal, &salen)) {
205                 return sockfd;
206         }
207
208 #ifdef HAVE_STRUCT_SOCKADDR_IN6
209         if (ipaddr->af == AF_INET6) {
210                 /*
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.
215                  */
216 #ifdef IPV6_V6ONLY
217
218                 if (IN6_IS_ADDR_UNSPECIFIED(&ipaddr->ipaddr.ip6addr)) {
219                         int on = 1;
220
221                         setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
222                                    (char *)&on, sizeof(on));
223                 }
224 #endif /* IPV6_V6ONLY */
225         }
226 #endif /* HAVE_STRUCT_SOCKADDR_IN6 */
227
228         if (ipaddr->af == AF_INET) {
229                 UNUSED int flag;
230                 
231 #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
232                 /*
233                  *      Disable PMTU discovery.  On Linux, this
234                  *      also makes sure that the "don't fragment"
235                  *      flag is zero.
236                  */
237                 flag = IP_PMTUDISC_DONT;
238                 setsockopt(sock->fd, IPPROTO_IP, IP_MTU_DISCOVER,
239                            &action, sizeof(action));
240 #endif
241
242 #if defined(IP_DONTFRAG)
243                 /*
244                  *      Ensure that the "don't fragment" flag is zero.
245                  */
246                 flag = 0;
247                 setsockopt(sock->fd, IPPROTO_IP, IP_DONTFRAG,
248                            &off, sizeof(off));
249 #endif
250         }
251
252         if (bind(sockfd, (struct sockaddr *) &salocal, salen) < 0) {
253                 close(sockfd);
254                 fr_strerror_printf("cannot bind socket: %s", strerror(errno));
255                 return -1;
256         }
257
258         return sockfd;
259 }
260
261
262 /*
263  *      We need to keep track of the socket & it's IP/port.
264  */
265 typedef struct fr_packet_socket_t {
266         int             sockfd;
267
268         int             num_outgoing;
269
270         int             offset; /* 0..31 */
271         int             inaddr_any;
272         fr_ipaddr_t     ipaddr;
273         int             port;
274 } fr_packet_socket_t;
275
276
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)
281
282 #define MAX_QUEUES (8)
283
284 /*
285  *      Structure defining a list of packets (incoming or outgoing)
286  *      that should be managed.
287  */
288 struct fr_packet_list_t {
289         fr_hash_table_t *ht;
290
291         fr_hash_table_t *dst2id_ht;
292
293         int             alloc_id;
294         int             num_outgoing;
295
296         uint32_t mask;
297         int last_recv;
298         fr_packet_socket_t sockets[MAX_SOCKETS];
299 };
300
301
302 /*
303  *      Ugh.  Doing this on every sent/received packet is not nice.
304  */
305 static fr_packet_socket_t *fr_socket_find(fr_packet_list_t *pl,
306                                              int sockfd)
307 {
308         int i, start;
309
310         i = start = SOCK2OFFSET(sockfd);
311
312         do {                    /* make this hack slightly more efficient */
313                 if (pl->sockets[i].sockfd == sockfd) return &pl->sockets[i];
314
315                 i = (i + 1) & SOCKOFFSET_MASK;
316         } while (i != start);
317
318         return NULL;
319 }
320
321 int fr_packet_list_socket_remove(fr_packet_list_t *pl, int sockfd)
322 {
323         fr_packet_socket_t *ps;
324
325         if (!pl) return 0;
326
327         ps = fr_socket_find(pl, sockfd);
328         if (!ps) return 0;
329
330         /*
331          *      FIXME: Allow the caller forcibly discard these?
332          */
333         if (ps->num_outgoing != 0) return 0;
334
335         ps->sockfd = -1;
336         pl->mask &= ~(1 << ps->offset);
337
338
339         return 1;
340 }
341
342 int fr_packet_list_socket_add(fr_packet_list_t *pl, int sockfd)
343 {
344         int i, start;
345         struct sockaddr_storage src;
346         socklen_t               sizeof_src = sizeof(src);
347         fr_packet_socket_t      *ps;
348
349         if (!pl) return 0;
350
351         ps = NULL;
352         i = start = SOCK2OFFSET(sockfd);
353
354         do {
355                 if (pl->sockets[i].sockfd == -1) {
356                         ps =  &pl->sockets[i];
357                         start = i;
358                         break;
359                 }
360
361                 i = (i + 1) & SOCKOFFSET_MASK;
362         } while (i != start);
363
364         if (!ps) {
365                 return 0;
366         }
367
368         memset(ps, 0, sizeof(*ps));
369         ps->sockfd = sockfd;
370         ps->offset = start;
371
372         /*
373          *      Get address family, etc. first, so we know if we
374          *      need to do udpfromto.
375          *
376          *      FIXME: udpfromto also does this, but it's not
377          *      a critical problem.
378          */
379         memset(&src, 0, sizeof_src);
380         if (getsockname(sockfd, (struct sockaddr *) &src,
381                         &sizeof_src) < 0) {
382                 return 0;
383         }
384
385         if (!fr_sockaddr2ipaddr(&src, sizeof_src, &ps->ipaddr, &ps->port)) {
386                 return 0;
387         }
388
389         /*
390          *      Grab IP addresses & ports from the sockaddr.
391          */
392         if (src.ss_family == AF_INET) {
393                 if (ps->ipaddr.ipaddr.ip4addr.s_addr == INADDR_ANY) {
394                         ps->inaddr_any = 1;
395                 }
396
397 #ifdef HAVE_STRUCT_SOCKADDR_IN6
398         } else if (src.ss_family == AF_INET6) {
399                 if (IN6_IS_ADDR_UNSPECIFIED(&ps->ipaddr.ipaddr.ip6addr)) {
400                         ps->inaddr_any = 1;
401                 }
402 #endif
403         } else {
404                 return 0;
405         }
406
407         pl->mask |= (1 << ps->offset);
408         return 1;
409 }
410
411 static uint32_t packet_entry_hash(const void *data)
412 {
413         return fr_request_packet_hash(*(const RADIUS_PACKET * const *) data);
414 }
415
416 static int packet_entry_cmp(const void *one, const void *two)
417 {
418         const RADIUS_PACKET * const *a = one;
419         const RADIUS_PACKET * const *b = two;
420
421         return fr_packet_cmp(*a, *b);
422 }
423
424 /*
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.
428  *
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!
433  *
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.
438  */
439 typedef struct fr_packet_dst2id_t {
440         fr_ipaddr_t     dst_ipaddr;
441         int             dst_port;
442         uint32_t        id[1];  /* really id[256] */
443 } fr_packet_dst2id_t;
444
445
446 static uint32_t packet_dst2id_hash(const void *data)
447 {
448         uint32_t hash;
449         const fr_packet_dst2id_t *pd = data;
450
451         hash = fr_hash(&pd->dst_port, sizeof(pd->dst_port));
452
453         switch (pd->dst_ipaddr.af) {
454         case AF_INET:
455                 hash = fr_hash_update(&pd->dst_ipaddr.ipaddr.ip4addr,
456                                         sizeof(pd->dst_ipaddr.ipaddr.ip4addr),
457                                         hash);
458                 break;
459         case AF_INET6:
460                 hash = fr_hash_update(&pd->dst_ipaddr.ipaddr.ip6addr,
461                                         sizeof(pd->dst_ipaddr.ipaddr.ip6addr),
462                                         hash);
463                 break;
464         default:
465                 break;
466         }
467
468         return hash;
469 }
470
471 static int packet_dst2id_cmp(const void *one, const void *two)
472 {
473         const fr_packet_dst2id_t *a = one;
474         const fr_packet_dst2id_t *b = two;
475
476         if (a->dst_port < b->dst_port) return -1;
477         if (a->dst_port > b->dst_port) return +1;
478
479         return fr_ipaddr_cmp(&a->dst_ipaddr, &b->dst_ipaddr);
480 }
481
482 static void packet_dst2id_free(void *data)
483 {
484         free(data);
485 }
486
487
488 void fr_packet_list_free(fr_packet_list_t *pl)
489 {
490         if (!pl) return;
491
492         fr_hash_table_free(pl->ht);
493         fr_hash_table_free(pl->dst2id_ht);
494         free(pl);
495 }
496
497
498 /*
499  *      Caller is responsible for managing the packet entries.
500  */
501 fr_packet_list_t *fr_packet_list_create(int alloc_id)
502 {
503         int i;
504         fr_packet_list_t        *pl;
505
506         pl = malloc(sizeof(*pl));
507         if (!pl) return NULL;
508         memset(pl, 0, sizeof(*pl));
509
510         pl->ht = fr_hash_table_create(packet_entry_hash,
511                                         packet_entry_cmp,
512                                         NULL);
513         if (!pl->ht) {
514                 fr_packet_list_free(pl);
515                 return NULL;
516         }
517
518         for (i = 0; i < MAX_SOCKETS; i++) {
519                 pl->sockets[i].sockfd = -1;
520         }
521
522         if (alloc_id) {
523                 pl->alloc_id = 1;
524
525                 pl->dst2id_ht = fr_hash_table_create(packet_dst2id_hash,
526                                                        packet_dst2id_cmp,
527                                                        packet_dst2id_free);
528                 if (!pl->dst2id_ht) {
529                         fr_packet_list_free(pl);
530                         return NULL;
531                 }
532         }
533
534         return pl;
535 }
536
537
538 /*
539  *      If pl->alloc_id is set, then fr_packet_list_id_alloc() MUST
540  *      be called before inserting the packet into the list!
541  */
542 int fr_packet_list_insert(fr_packet_list_t *pl,
543                             RADIUS_PACKET **request_p)
544 {
545         if (!pl || !request_p || !*request_p) return 0;
546
547         (*request_p)->hash = fr_request_packet_hash(*request_p);
548
549         return fr_hash_table_insert(pl->ht, request_p);
550 }
551
552 RADIUS_PACKET **fr_packet_list_find(fr_packet_list_t *pl,
553                                       RADIUS_PACKET *request)
554 {
555         if (!pl || !request) return 0;
556
557         return fr_hash_table_finddata(pl->ht, &request);
558 }
559
560
561 /*
562  *      This presumes that the reply has dst_ipaddr && dst_port set up
563  *      correctly (i.e. real IP, or "*").
564  */
565 RADIUS_PACKET **fr_packet_list_find_byreply(fr_packet_list_t *pl,
566                                               RADIUS_PACKET *reply)
567 {
568         RADIUS_PACKET my_request, *request;
569         fr_packet_socket_t *ps;
570
571         if (!pl || !reply) return NULL;
572
573         ps = fr_socket_find(pl, reply->sockfd);
574         if (!ps) return NULL;
575
576         /*
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.
581          */
582         my_request.sockfd = reply->sockfd;
583         my_request.id = reply->id;
584
585         if (ps->inaddr_any) {
586                 my_request.src_ipaddr = ps->ipaddr;
587         } else {
588                 my_request.src_ipaddr = reply->dst_ipaddr;
589         }
590         my_request.src_port = ps->port;;
591
592         my_request.dst_ipaddr = reply->src_ipaddr;
593         my_request.dst_port = reply->src_port;
594         my_request.hash = 0;
595
596         request = &my_request;
597
598         return fr_hash_table_finddata(pl->ht, &request);
599 }
600
601
602 RADIUS_PACKET **fr_packet_list_yank(fr_packet_list_t *pl,
603                                       RADIUS_PACKET *request)
604 {
605         if (!pl || !request) return NULL;
606
607         return fr_hash_table_yank(pl->ht, &request);
608 }
609
610 int fr_packet_list_num_elements(fr_packet_list_t *pl)
611 {
612         if (!pl) return 0;
613
614         return fr_hash_table_num_elements(pl->ht);
615 }
616
617
618 /*
619  *      1 == ID was allocated & assigned
620  *      0 == error allocating memory
621  *      -1 == all ID's are used, caller should open a new socket.
622  *
623  *      Note that this ALSO assigns a socket to use, and updates
624  *      packet->request->src_ipaddr && packet->request->src_port
625  *
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
629  *      calls!
630  */
631 int fr_packet_list_id_alloc(fr_packet_list_t *pl,
632                               RADIUS_PACKET *request)
633 {
634         int i, id, start, fd;
635         uint32_t free_mask;
636         fr_packet_dst2id_t my_pd, *pd;
637         fr_packet_socket_t *ps;
638
639         if (!pl || !pl->alloc_id || !request) return 0;
640
641         my_pd.dst_ipaddr = request->dst_ipaddr;
642         my_pd.dst_port = request->dst_port;
643
644         pd = fr_hash_table_finddata(pl->dst2id_ht, &my_pd);
645         if (!pd) {
646                 pd = malloc(sizeof(*pd) + 255 * sizeof(pd->id[0]));
647                 if (!pd) return 0;
648
649                 memset(pd, 0, sizeof(*pd) + 255 * sizeof(pd->id[0]));
650
651                 pd->dst_ipaddr = request->dst_ipaddr;
652                 pd->dst_port = request->dst_port;
653
654                 if (!fr_hash_table_insert(pl->dst2id_ht, pd)) {
655                         free(pd);
656                         return 0;
657                 }
658         }
659
660         /*
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.
666          *
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
669          *      a timeout.
670          */
671         id = start = (int) fr_rand() & 0xff;
672
673         while (pd->id[id] == pl->mask) { /* all sockets are using this ID */
674         redo:
675                 id++;
676                 id &= 0xff;
677                 if (id == start) return 0;
678         }
679
680         free_mask = ~((~pd->id[id]) & pl->mask);
681
682         /*
683          *      This ID has at least one socket free.  Check the sockets
684          *      to see if they are satisfactory for the caller.
685          */
686         fd = -1;
687         for (i = 0; i < MAX_SOCKETS; i++) {
688                 if (pl->sockets[i].sockfd == -1) continue; /* paranoia */
689
690                 /*
691                  *      This ID is allocated.
692                  */
693                 if ((free_mask & (1 << i)) != 0) continue;
694                 
695                 /*
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
701                  *      address.
702                  */
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;
706
707                 /*
708                  *      They asked for a specific address, and this socket
709                  *      is bound to a wildcard address.  Ignore this one, too.
710                  */
711                 if ((request->src_ipaddr.af != AF_UNSPEC) &&
712                     pl->sockets[i].inaddr_any) continue;
713                 
714                 fd = i;
715                 break;
716         }
717
718         if (fd < 0) {
719                 goto redo; /* keep searching IDs */
720         }
721
722         pd->id[id] |= (1 << fd);
723         ps = &pl->sockets[fd];
724
725         ps->num_outgoing++;
726         pl->num_outgoing++;
727
728         /*
729          *      Set the ID, source IP, and source port.
730          */
731         request->id = id;
732
733         request->sockfd = ps->sockfd;
734         request->src_ipaddr = ps->ipaddr;
735         request->src_port = ps->port;
736
737         return 1;
738 }
739
740 /*
741  *      Should be called AFTER yanking it from the list, so that
742  *      any newly inserted entries don't collide with this one.
743  */
744 int fr_packet_list_id_free(fr_packet_list_t *pl,
745                              RADIUS_PACKET *request)
746 {
747         fr_packet_socket_t *ps;
748         fr_packet_dst2id_t my_pd, *pd;
749
750         if (!pl || !request) return 0;
751
752         ps = fr_socket_find(pl, request->sockfd);
753         if (!ps) return 0;
754
755         my_pd.dst_ipaddr = request->dst_ipaddr;
756         my_pd.dst_port = request->dst_port;
757
758         pd = fr_hash_table_finddata(pl->dst2id_ht, &my_pd);
759         if (!pd) return 0;
760
761         pd->id[request->id] &= ~(1 << ps->offset);
762         request->hash = 0;      /* invalidate the cached hash */
763
764         ps->num_outgoing--;
765         pl->num_outgoing--;
766
767         return 1;
768 }
769
770 int fr_packet_list_walk(fr_packet_list_t *pl, void *ctx,
771                           fr_hash_table_walk_t callback)
772 {
773         if (!pl || !callback) return 0;
774
775         return fr_hash_table_walk(pl->ht, callback, ctx);
776 }
777
778 int fr_packet_list_fd_set(fr_packet_list_t *pl, fd_set *set)
779 {
780         int i, maxfd;
781
782         if (!pl || !set) return 0;
783
784         maxfd = -1;
785
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;
791                 }
792         }
793
794         if (maxfd < 0) return -1;
795
796         return maxfd + 1;
797 }
798
799 /*
800  *      Round-robins the receivers, without priority.
801  *
802  *      FIXME: Add sockfd, if -1, do round-robin, else do sockfd
803  *              IF in fdset.
804  */
805 RADIUS_PACKET *fr_packet_list_recv(fr_packet_list_t *pl, fd_set *set)
806 {
807         int start;
808         RADIUS_PACKET *packet;
809
810         if (!pl || !set) return NULL;
811
812         start = pl->last_recv;
813         do {
814                 start++;
815                 start &= SOCKOFFSET_MASK;
816
817                 if (pl->sockets[start].sockfd == -1) continue;
818
819                 if (!FD_ISSET(pl->sockets[start].sockfd, set)) continue;
820
821                 packet = rad_recv(pl->sockets[start].sockfd, 0);
822                 if (!packet) continue;
823
824                 /*
825                  *      Call fr_packet_list_find_byreply().  If it
826                  *      doesn't find anything, discard the reply.
827                  */
828
829                 pl->last_recv = start;
830                 return packet;
831         } while (start != pl->last_recv);
832
833         return NULL;
834 }
835
836 int fr_packet_list_num_incoming(fr_packet_list_t *pl)
837 {
838         int num_elements;
839
840         if (!pl) return 0;
841
842         num_elements = fr_hash_table_num_elements(pl->ht);
843         if (num_elements < pl->num_outgoing) return 0; /* panic! */
844
845         return num_elements - pl->num_outgoing;
846 }
847
848 int fr_packet_list_num_outgoing(fr_packet_list_t *pl)
849 {
850         if (!pl) return 0;
851
852         return pl->num_outgoing;
853 }