Make hash table have comparison callback, it's needed.
[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
30 /*
31  *      Take the key fields of a request packet, and convert it to a
32  *      hash.
33  */
34 uint32_t lrad_request_packet_hash(const RADIUS_PACKET *packet)
35 {
36         uint32_t hash;
37         
38         hash = lrad_hash(&packet->src_port, sizeof(packet->src_port));
39         hash = lrad_hash_update(&packet->dst_port,
40                                 sizeof(packet->dst_port), hash);
41
42         /*
43          *      The caller ensures that src & dst AF are the same.
44          */
45         switch (packet->src_ipaddr.af) {
46         case AF_INET:
47                 hash = lrad_hash_update(&packet->src_ipaddr.ipaddr.ip4addr,
48                                         sizeof(packet->src_ipaddr.ipaddr.ip4addr),
49                                         hash);
50                 hash = lrad_hash_update(&packet->dst_ipaddr.ipaddr.ip4addr,
51                                         sizeof(packet->dst_ipaddr.ipaddr.ip4addr),
52                                         hash);
53                 break;
54         case AF_INET6:
55                 hash = lrad_hash_update(&packet->src_ipaddr.ipaddr.ip6addr,
56                                         sizeof(packet->src_ipaddr.ipaddr.ip6addr),
57                                         hash);
58                 hash = lrad_hash_update(&packet->dst_ipaddr.ipaddr.ip6addr,
59                                         sizeof(packet->dst_ipaddr.ipaddr.ip6addr),
60                                         hash);
61                 break;
62         default:
63                 /* FIXME: die! */
64                 break;
65         }
66
67         return lrad_hash_update(&packet->id, sizeof(packet->id), hash);
68 }
69
70
71 /*
72  *      Take the key fields of a reply packet, and convert it to a
73  *      hash.
74  *
75  *      i.e. take a reply packet, and find the hash of the request packet
76  *      that asked for the reply.  To do this, we hash the reverse fields
77  *      of the request.  e.g. where the request does (src, dst), we do
78  *      (dst, src)
79  */
80 uint32_t lrad_reply_packet_hash(const RADIUS_PACKET *packet)
81 {
82         uint32_t hash;
83         
84         hash = lrad_hash(&packet->src_port, sizeof(packet->src_port));
85         hash = lrad_hash_update(&packet->dst_port,
86                                 sizeof(packet->dst_port), hash);
87
88         /*
89          *      The caller ensures that src & dst AF are the same.
90          */
91         switch (packet->src_ipaddr.af) {
92         case AF_INET:
93                 hash = lrad_hash_update(&packet->dst_ipaddr.ipaddr.ip4addr,
94                                         sizeof(packet->dst_ipaddr.ipaddr.ip4addr),
95                                         hash);
96                 hash = lrad_hash_update(&packet->src_ipaddr.ipaddr.ip4addr,
97                                         sizeof(packet->src_ipaddr.ipaddr.ip4addr),
98                                         hash);
99                 break;
100         case AF_INET6:
101                 hash = lrad_hash_update(&packet->dst_ipaddr.ipaddr.ip6addr,
102                                         sizeof(packet->dst_ipaddr.ipaddr.ip6addr),
103                                         hash);
104                 hash = lrad_hash_update(&packet->src_ipaddr.ipaddr.ip6addr,
105                                         sizeof(packet->src_ipaddr.ipaddr.ip6addr),
106                                         hash);
107                 break;
108         default:
109                 /* FIXME: die! */
110                 break;
111         }
112
113         return lrad_hash_update(&packet->id, sizeof(packet->id), hash);
114 }
115
116
117 /*
118  *      See if two packets are identical.
119  *
120  *      Note that we do NOT compare the authentication vectors.
121  *      That's because if the authentication vector is different,
122  *      it means that the NAS has given up on the earlier request.
123  */
124 int lrad_packet_cmp(const RADIUS_PACKET *a, const RADIUS_PACKET *b)
125 {
126         int rcode;
127
128         if (a->sockfd < b->sockfd) return -1;
129         if (a->sockfd > b->sockfd) return +1;
130
131         if (a->src_ipaddr.af < b->dst_ipaddr.af) return -1;
132         if (a->src_ipaddr.af > b->dst_ipaddr.af) return +1;
133
134         if (a->id < b->id) return -1;
135         if (a->id > b->id) return +1;
136
137         if (a->src_port < b->src_port) return -1;
138         if (a->src_port > b->src_port) return +1;
139
140         if (a->dst_port < b->dst_port) return -1;
141         if (a->dst_port > b->dst_port) return +1;
142
143         switch (a->dst_ipaddr.af) {
144         case AF_INET:
145                 rcode = memcmp(&a->dst_ipaddr.ipaddr.ip4addr,
146                                &b->dst_ipaddr.ipaddr.ip4addr,
147                                sizeof(a->dst_ipaddr.ipaddr.ip4addr));
148                 if (rcode != 0) return rcode;
149                 rcode = memcmp(&a->src_ipaddr.ipaddr.ip4addr,
150                                &b->src_ipaddr.ipaddr.ip4addr,
151                                sizeof(a->src_ipaddr.ipaddr.ip4addr));
152                 if (rcode != 0) return rcode;
153                 break;
154         case AF_INET6:
155                 rcode = memcmp(&a->dst_ipaddr.ipaddr.ip6addr,
156                                &b->dst_ipaddr.ipaddr.ip6addr,
157                                sizeof(a->dst_ipaddr.ipaddr.ip6addr));
158                 if (rcode != 0) return rcode;
159                 rcode = memcmp(&a->src_ipaddr.ipaddr.ip6addr,
160                                &b->src_ipaddr.ipaddr.ip6addr,
161                                sizeof(a->src_ipaddr.ipaddr.ip6addr));
162                 if (rcode != 0) return rcode;
163                 break;
164         default:
165                 return -1;
166                 break;
167         }
168
169         /*
170          *      Everything's equal.  Say so.
171          */
172         return 0;
173 }
174
175
176 /*
177  *      Create a fake "request" from a reply, for later lookup.
178  */
179 void lrad_request_from_reply(RADIUS_PACKET *request,
180                              const RADIUS_PACKET *reply)
181 {
182         request->sockfd = reply->sockfd;
183         request->id = reply->id;
184         request->src_port = reply->dst_port;
185         request->dst_port = reply->src_port;
186         request->src_ipaddr = reply->dst_ipaddr;
187         request->dst_ipaddr = reply->src_ipaddr;
188 }
189
190
191 /*
192  *      Open a socket on the given IP and port.
193  */
194 int lrad_socket(lrad_ipaddr_t *ipaddr, int port)
195 {
196         int sockfd;
197         struct sockaddr salocal;
198         socklen_t       salen;
199
200         if ((port < 0) || (port > 65535)) {
201                 librad_log("Port %d is out of allowed bounds", port);
202                 return -1;
203         }
204
205         sockfd = socket(ipaddr->af, SOCK_DGRAM, 0);
206         if (sockfd < 0) {
207                 librad_log("Failed opening socket: %s", strerror(errno));
208                 return sockfd;
209         }
210
211 #ifdef WITH_UDPFROMTO
212         /*
213          *      Initialize udpfromto for all sockets.
214          */
215         if (udpfromto_init(sockfd) != 0) {
216                 close(sockfd);
217                 return -1;
218         }
219 #endif
220         
221         if (ipaddr->af == AF_INET) {
222                 struct sockaddr_in *sa;
223                 
224                 sa = (struct sockaddr_in *) &salocal;
225                 memset(sa, 0, sizeof(salocal));
226                 sa->sin_family = AF_INET;
227                 sa->sin_addr = ipaddr->ipaddr.ip4addr;
228                 sa->sin_port = htons((uint16_t) port);
229                 salen = sizeof(*sa);
230                 
231 #ifdef HAVE_STRUCT_SOCKADDR_IN6
232         } else if (ipaddr->af == AF_INET6) {
233                 struct sockaddr_in6 *sa;
234                 
235                 sa = (struct sockaddr_in6 *) &salocal;
236                 memset(sa, 0, sizeof(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                 /*
243                  *      Listening on '::' does NOT get you IPv4 to
244                  *      IPv6 mapping.  You've got to listen on an IPv4
245                  *      address, too.  This makes the rest of the server
246                  *      design a little simpler.
247                  */
248 #ifdef IPV6_V6ONLY
249                 if (IN6_IS_ADDR_UNSPECIFIED(&ipaddr->ipaddr.ip6addr)) {
250                         int on = 1;
251                         
252                         setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
253                                    (char *)&on, sizeof(on));
254                 }
255 #endif /* IPV6_V6ONLY */
256 #endif /* HAVE_STRUCT_SOCKADDR_IN6 */
257         } else {
258                 return sockfd;  /* don't bind it */
259         }
260
261         if (bind(sockfd, &salocal, salen) < 0) {
262                 librad_log("Bind to address failed: %s", strerror(errno));
263                 close(sockfd);
264                 return -1;
265         }
266
267         return sockfd;
268 }