Part 2.
[freeradius.git] / src / lib / udpfromto.c
1 /*
2  *   This library is free software; you can redistribute it and/or
3  *   modify it under the terms of the GNU Lesser General Public
4  *   License as published by the Free Software Foundation; either
5  *   version 2.1 of the License, or (at your option) any later version.
6  *
7  *   This library is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10  *   Lesser General Public License for more details.
11  *
12  *   You should have received a copy of the GNU Lesser General Public
13  *   License along with this library; if not, write to the Free Software
14  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
15  *
16  *  Helper functions to get/set addresses of UDP packets 
17  *  based on recvfromto by Miquel van Smoorenburg
18  *
19  * recvfromto   Like recvfrom, but also stores the destination
20  *              IP address. Useful on multihomed hosts.
21  *
22  *              Should work on Linux and BSD.
23  *
24  *              Copyright (C) 2002 Miquel van Smoorenburg.
25  *
26  *              This program is free software; you can redistribute it and/or
27  *              modify it under the terms of the GNU Lesser General Public
28  *              License as published by the Free Software Foundation; either
29  *              version 2 of the License, or (at your option) any later version.
30  *
31  * sendfromto   added 18/08/2003, Jan Berkel <jan@sitadelle.com>
32  *              Works on Linux and FreeBSD (5.x)                        
33  * 
34  * Version: $Id$
35  */
36
37 #include "autoconf.h"
38 static const char rcsid[] = "$Id$";
39
40 #include <sys/types.h>
41
42 #ifdef HAVE_SYS_UIO_H
43 #include <sys/uio.h>
44 #endif
45
46 #include <netinet/in.h>
47 #include <errno.h>
48 #include <unistd.h>
49 #include <fcntl.h>
50 #include <string.h>
51
52 #include "udpfromto.h"
53
54 /* Remove this when autoconf can detect this. */
55 #if defined(IP_RECVDSTADDR) && !defined(HAVE_IP_RECVDSTADDR)
56 #  define HAVE_IP_RECVDSTADDR
57 #endif
58
59 #if defined(IP_SENDSRCADDR) && !defined(HAVE_IP_SENDSRCADDR)
60 #  define HAVE_IP_SENDSRCADDR
61 #endif
62
63 int udpfromto_init(int s)
64 {
65         int err = -1, opt = 1;
66         errno = ENOSYS;
67 #ifdef HAVE_IP_PKTINFO
68         /* Set the IP_PKTINFO option (Linux). */
69         err = setsockopt(s, SOL_IP, IP_PKTINFO, &opt, sizeof(opt));
70 #endif
71
72 #ifdef HAVE_IP_RECVDSTADDR
73         /*
74          * Set the IP_RECVDSTADDR option (BSD). 
75          * Note: IP_RECVDSTADDR == IP_SENDSRCADDR 
76          */
77         err = setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt));
78 #endif
79         return err;
80 }
81         
82 int recvfromto(int s, void *buf, size_t len, int flags,
83         struct sockaddr *from, socklen_t *fromlen,
84         struct sockaddr *to, socklen_t *tolen)
85 {
86 #if defined(HAVE_IP_PKTINFO) || defined(HAVE_IP_RECVDSTADDR)
87         struct msghdr msgh;
88         struct cmsghdr *cmsg;
89         struct iovec iov;
90         char cbuf[256];
91         int err;
92
93         /*
94          *      If from or to are set, they must be big enough
95          *      to store a struct sockaddr_in.
96          */
97         if ((from && (!fromlen || *fromlen < sizeof(struct sockaddr_in))) ||
98             (to   && (!tolen   || *tolen   < sizeof(struct sockaddr_in)))) {
99                 errno = EINVAL;
100                 return -1;
101         }
102
103         /*
104          *      IP_PKTINFO / IP_RECVDSTADDR don't provide sin_port so we have to
105          *      retrieve it using getsockname().
106          */
107         if (to) {
108                 struct sockaddr_in si;
109                 socklen_t l = sizeof(si);
110
111                 ((struct sockaddr_in *)to)->sin_family = AF_INET;
112                 ((struct sockaddr_in *)to)->sin_port = 0;
113                 l = sizeof(si);
114                 if (getsockname(s, (struct sockaddr *)&si, &l) == 0) {
115                         ((struct sockaddr_in *)to)->sin_port = si.sin_port;
116                         ((struct sockaddr_in *)to)->sin_addr = si.sin_addr; 
117                 }
118                 if (tolen) *tolen = sizeof(struct sockaddr_in);
119         }
120
121         /* Set up iov and msgh structures. */
122         memset(&msgh, 0, sizeof(struct msghdr));
123         iov.iov_base = buf;
124         iov.iov_len  = len;
125         msgh.msg_control = cbuf;
126         msgh.msg_controllen = sizeof(cbuf);
127         msgh.msg_name = from;
128         msgh.msg_namelen = fromlen ? *fromlen : 0;
129         msgh.msg_iov  = &iov;
130         msgh.msg_iovlen = 1;
131         msgh.msg_flags = 0;
132
133         /* Receive one packet. */
134         if ((err = recvmsg(s, &msgh, flags)) < 0) {
135                 return err;
136         }
137         if (fromlen) *fromlen = msgh.msg_namelen;
138
139         /* Process auxiliary received data in msgh */
140         for (cmsg = CMSG_FIRSTHDR(&msgh);
141              cmsg != NULL;
142              cmsg = CMSG_NXTHDR(&msgh,cmsg)) {
143
144 # ifdef HAVE_IP_PKTINFO
145                 if (cmsg->cmsg_level == SOL_IP
146                     && cmsg->cmsg_type == IP_PKTINFO) {
147                         struct in_pktinfo *i =
148                                 (struct in_pktinfo *)CMSG_DATA(cmsg);
149                         if (to) {
150                                 ((struct sockaddr_in *)to)->sin_addr =
151                                         i->ipi_addr;
152                                 if (tolen) *tolen = sizeof(struct sockaddr_in);
153                         }
154                         break;
155                 }
156 # endif
157
158 # ifdef HAVE_IP_RECVDSTADDR
159                 if (cmsg->cmsg_level == IPPROTO_IP
160                     && cmsg->cmsg_type == IP_RECVDSTADDR) {
161                         struct in_addr *i = (struct in_addr *)CMSG_DATA(cmsg);
162                         if (to) {
163                                 ((struct sockaddr_in *)to)->sin_addr = *i;
164                                 if (tolen) *tolen = sizeof(struct sockaddr_in);
165                         }
166                         break;
167                 }
168 # endif
169         }
170         return err;
171 #else 
172         /* fallback: call recvfrom */
173         return recvfrom(s, buf, len, flags, from, fromlen);
174 #endif /* defined(HAVE_IP_PKTINFO) || defined(HAVE_IP_RECVDSTADDR) */
175 }
176
177 int sendfromto(int s, void *buf, size_t len, int flags,
178                           struct sockaddr *from, socklen_t fromlen,
179                           struct sockaddr *to, socklen_t tolen)
180 {
181 #if defined(HAVE_IP_PKTINFO) || defined(HAVE_IP_SENDSRCADDR)
182         struct msghdr msgh;
183         struct cmsghdr *cmsg;
184         struct iovec iov;
185 # ifdef HAVE_IP_PKTINFO
186         char cmsgbuf[CMSG_SPACE(sizeof(struct in_pktinfo))];
187         struct in_pktinfo pktinfo, *pktinfo_ptr;
188         memset(&pktinfo, 0, sizeof(struct in_pktinfo));
189 # endif
190
191 # ifdef HAVE_IP_SENDSRCADDR
192         char cmsgbuf[CMSG_SPACE(sizeof(struct in_addr))];
193 # endif
194
195         /* Set up iov and msgh structures. */
196         memset(&msgh, 0, sizeof(struct msghdr));
197         iov.iov_base = buf;
198         iov.iov_len = len;
199         msgh.msg_iov = &iov;
200         msgh.msg_iovlen = 1;
201         msgh.msg_control = cmsgbuf;
202         msgh.msg_controllen = sizeof(cmsgbuf);
203         msgh.msg_name = to;
204         msgh.msg_namelen = tolen;
205         
206         cmsg = CMSG_FIRSTHDR(&msgh);
207
208 # ifdef HAVE_IP_PKTINFO
209         cmsg->cmsg_level = SOL_IP;
210         cmsg->cmsg_type = IP_PKTINFO;
211         cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
212         pktinfo.ipi_spec_dst = ((struct sockaddr_in *)from)->sin_addr;
213         pktinfo_ptr = (struct in_pktinfo *)CMSG_DATA(cmsg);
214         memcpy(pktinfo_ptr, &pktinfo, sizeof(struct in_pktinfo));
215 # endif
216 # ifdef HAVE_IP_SENDSRCADDR
217         cmsg->cmsg_level = IPPROTO_IP;
218         cmsg->cmsg_type = IP_SENDSRCADDR;
219         cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
220         memcpy((struct in_addr *)CMSG_DATA(cmsg), 
221                         &((struct sockaddr_in *)from)->sin_addr, sizeof(struct in_addr));
222 # endif
223
224         return sendmsg(s, &msgh, flags);
225 #else
226         /* fallback: call sendto() */
227         return sendto(s, buf, len, flags, to, tolen);
228 #endif  /* defined(HAVE_IP_PKTINFO) || defined (HAVE_IP_SENDSRCADDR) */
229 }
230
231
232 #ifdef TESTING
233 /*
234  *      Small test program to test recvfromto/sendfromto
235  *
236  *      use a virtual IP address as first argument to test 
237  *
238  *      reply packet should originate from virtual IP and not
239  *      from the default interface the alias is bound to
240  */
241
242 #include <stdio.h>
243 #include <stdlib.h>
244 #include <arpa/inet.h>
245 #include <sys/types.h>
246 #include <sys/wait.h>
247
248 #define DEF_PORT 20000          /* default port to listen on */
249 #define DESTIP "127.0.0.1"      /* send packet to localhost per default */
250 #define TESTSTRING "foo"        /* what to send */
251 #define TESTLEN 4                       /* 4 bytes */
252
253 int main(int argc, char **argv)
254 {
255         struct sockaddr_in from, to, in;
256         char buf[TESTLEN];
257         char *destip = DESTIP;
258         int port = DEF_PORT;
259         int n, server_socket, client_socket, fl, tl, pid;
260
261         if (argc > 1) destip = argv[1];
262         if (argc > 2) port = atoi(argv[2]);
263
264         in.sin_family = AF_INET;
265         in.sin_addr.s_addr = INADDR_ANY;
266         in.sin_port = htons(port);
267         fl = tl = sizeof(struct sockaddr_in);
268         memset(&from, 0, sizeof(from));
269         memset(&to,   0, sizeof(to));
270
271         switch(pid = fork()) {
272                 case -1:
273                         perror("fork");
274                         return 0;
275                 case 0:
276                         /* child */
277                         usleep(100000); 
278                         goto client;
279         }
280
281         /* parent: server */
282         server_socket = socket(PF_INET, SOCK_DGRAM, 0);
283         if (udpfromto_init(server_socket) != 0) {
284                 perror("udpfromto_init\n");
285                 waitpid(pid, NULL, WNOHANG);
286                 return 0;
287         }
288
289         if (bind(server_socket, (struct sockaddr *)&in, sizeof(in)) < 0) {
290                 perror("server: bind");
291                 waitpid(pid, NULL, WNOHANG);
292                 return 0;
293         }
294
295         printf("server: waiting for packets on INADDR_ANY:%d\n", port);
296         if ((n = recvfromto(server_socket, buf, sizeof(buf), 0,
297             (struct sockaddr *)&from, &fl,
298             (struct sockaddr *)&to, &tl)) < 0) {
299                 perror("server: recvfromto");
300                 waitpid(pid, NULL, WNOHANG);
301                 return 0;
302         }
303
304         printf("server: received a packet of %d bytes [%s] ", n, buf);
305         printf("(src ip:port %s:%d ",
306                 inet_ntoa(from.sin_addr), ntohs(from.sin_port));
307         printf(" dst ip:port %s:%d)\n",
308                 inet_ntoa(to.sin_addr), ntohs(to.sin_port));
309
310         printf("server: replying from address packet was received on to source address\n");
311                 
312         if ((n = sendfromto(server_socket, buf, n, 0,
313                 (struct sockaddr *)&to, tl,
314                 (struct sockaddr *)&from, fl)) < 0) {
315                 perror("server: sendfromto");
316         }
317
318         waitpid(pid, NULL, 0);
319         return 0;
320
321 client:
322         close(server_socket);
323         client_socket = socket(PF_INET, SOCK_DGRAM, 0);
324         if (udpfromto_init(client_socket) != 0) {
325                 perror("udpfromto_init");
326                 _exit(0);
327         }
328         /* bind client on different port */
329         in.sin_port = htons(port+1);
330         if (bind(client_socket, (struct sockaddr *)&in, sizeof(in)) < 0) {
331                 perror("client: bind");
332                 _exit(0);
333         }
334
335         in.sin_port = htons(port);
336         in.sin_addr.s_addr = inet_addr(destip);
337
338         printf("client: sending packet to %s:%d\n", destip, port);
339         if (sendto(client_socket, TESTSTRING, TESTLEN, 0, 
340                         (struct sockaddr *)&in, sizeof(in)) < 0) {
341                 perror("client: sendto");
342                 _exit(0);
343         }
344                         
345         printf("client: waiting for reply from server on INADDR_ANY:%d\n", port+1);
346
347         if ((n = recvfromto(client_socket, buf, sizeof(buf), 0,
348             (struct sockaddr *)&from, &fl,
349             (struct sockaddr *)&to, &tl)) < 0) {
350                 perror("client: recvfromto");
351                 _exit(0);
352         }
353
354         printf("client: received a packet of %d bytes [%s] ", n, buf);
355         printf("(src ip:port %s:%d",
356                 inet_ntoa(from.sin_addr), ntohs(from.sin_port));
357         printf(" dst ip:port %s:%d)\n",
358                 inet_ntoa(to.sin_addr), ntohs(to.sin_port));
359
360         _exit(0);
361 }
362
363 #endif /* TESTING */