import from HEAD:
[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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, 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
39 #ifdef WITH_UDPFROMTO
40 static const char rcsid[] = "$Id$";
41
42 #include <sys/types.h>
43
44 #ifdef HAVE_SYS_UIO_H
45 #include <sys/uio.h>
46 #endif
47
48 #include <netinet/in.h>
49 #include <errno.h>
50 #include <unistd.h>
51 #include <fcntl.h>
52 #include <string.h>
53
54 #include "udpfromto.h"
55
56 int udpfromto_init(int s)
57 {
58         int err = -1, opt = 1;
59         errno = ENOSYS;
60 #ifdef HAVE_IP_PKTINFO
61         /* Set the IP_PKTINFO option (Linux). */
62         err = setsockopt(s, SOL_IP, IP_PKTINFO, &opt, sizeof(opt));
63 #endif
64
65 #ifdef HAVE_IP_RECVDSTADDR
66         /*
67          * Set the IP_RECVDSTADDR option (BSD).
68          * Note: IP_RECVDSTADDR == IP_SENDSRCADDR
69          */
70         err = setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt));
71 #endif
72         return err;
73 }
74
75 int recvfromto(int s, void *buf, size_t len, int flags,
76         struct sockaddr *from, socklen_t *fromlen,
77         struct sockaddr *to, socklen_t *tolen)
78 {
79 #if defined(HAVE_IP_PKTINFO) || defined(HAVE_IP_RECVDSTADDR)
80         struct msghdr msgh;
81         struct cmsghdr *cmsg;
82         struct iovec iov;
83         char cbuf[256];
84         int err;
85
86         /*
87          *      If from or to are set, they must be big enough
88          *      to store a struct sockaddr_in.
89          */
90         if ((from && (!fromlen || *fromlen < sizeof(struct sockaddr_in))) ||
91             (to   && (!tolen   || *tolen   < sizeof(struct sockaddr_in)))) {
92                 errno = EINVAL;
93                 return -1;
94         }
95
96         /*
97          *      IP_PKTINFO / IP_RECVDSTADDR don't provide sin_port so we have to
98          *      retrieve it using getsockname().
99          */
100         if (to) {
101                 struct sockaddr_in si;
102                 socklen_t l = sizeof(si);
103
104                 ((struct sockaddr_in *)to)->sin_family = AF_INET;
105                 ((struct sockaddr_in *)to)->sin_port = 0;
106                 l = sizeof(si);
107                 if (getsockname(s, (struct sockaddr *)&si, &l) == 0) {
108                         ((struct sockaddr_in *)to)->sin_port = si.sin_port;
109                         ((struct sockaddr_in *)to)->sin_addr = si.sin_addr;
110                 }
111                 if (tolen) *tolen = sizeof(struct sockaddr_in);
112         }
113
114         /* Set up iov and msgh structures. */
115         memset(&msgh, 0, sizeof(struct msghdr));
116         iov.iov_base = buf;
117         iov.iov_len  = len;
118         msgh.msg_control = cbuf;
119         msgh.msg_controllen = sizeof(cbuf);
120         msgh.msg_name = from;
121         msgh.msg_namelen = fromlen ? *fromlen : 0;
122         msgh.msg_iov  = &iov;
123         msgh.msg_iovlen = 1;
124         msgh.msg_flags = 0;
125
126         /* Receive one packet. */
127         if ((err = recvmsg(s, &msgh, flags)) < 0) {
128                 return err;
129         }
130         if (fromlen) *fromlen = msgh.msg_namelen;
131
132         /* Process auxiliary received data in msgh */
133         for (cmsg = CMSG_FIRSTHDR(&msgh);
134              cmsg != NULL;
135              cmsg = CMSG_NXTHDR(&msgh,cmsg)) {
136
137 # ifdef HAVE_IP_PKTINFO
138                 if (cmsg->cmsg_level == SOL_IP
139                     && cmsg->cmsg_type == IP_PKTINFO) {
140                         struct in_pktinfo *i =
141                                 (struct in_pktinfo *)CMSG_DATA(cmsg);
142                         if (to) {
143                                 ((struct sockaddr_in *)to)->sin_addr =
144                                         i->ipi_addr;
145                                 if (tolen) *tolen = sizeof(struct sockaddr_in);
146                         }
147                         break;
148                 }
149 # endif
150
151 # ifdef HAVE_IP_RECVDSTADDR
152                 if (cmsg->cmsg_level == IPPROTO_IP
153                     && cmsg->cmsg_type == IP_RECVDSTADDR) {
154                         struct in_addr *i = (struct in_addr *)CMSG_DATA(cmsg);
155                         if (to) {
156                                 ((struct sockaddr_in *)to)->sin_addr = *i;
157                                 if (tolen) *tolen = sizeof(struct sockaddr_in);
158                         }
159                         break;
160                 }
161 # endif
162         }
163         return err;
164 #else
165         /* fallback: call recvfrom */
166         return recvfrom(s, buf, len, flags, from, fromlen);
167 #endif /* defined(HAVE_IP_PKTINFO) || defined(HAVE_IP_RECVDSTADDR) */
168 }
169
170 int sendfromto(int s, void *buf, size_t len, int flags,
171                           struct sockaddr *from, socklen_t fromlen,
172                           struct sockaddr *to, socklen_t tolen)
173 {
174 #if defined(HAVE_IP_PKTINFO) || defined(HAVE_IP_SENDSRCADDR)
175         struct msghdr msgh;
176         struct cmsghdr *cmsg;
177         struct iovec iov;
178 # ifdef HAVE_IP_PKTINFO
179         char cmsgbuf[CMSG_SPACE(sizeof(struct in_pktinfo))];
180         struct in_pktinfo pktinfo, *pktinfo_ptr;
181         memset(&pktinfo, 0, sizeof(struct in_pktinfo));
182 # endif
183
184 # ifdef HAVE_IP_SENDSRCADDR
185         char cmsgbuf[CMSG_SPACE(sizeof(struct in_addr))];
186 # endif
187
188         /* Set up iov and msgh structures. */
189         memset(&msgh, 0, sizeof(struct msghdr));
190         iov.iov_base = buf;
191         iov.iov_len = len;
192         msgh.msg_iov = &iov;
193         msgh.msg_iovlen = 1;
194         msgh.msg_control = cmsgbuf;
195         msgh.msg_controllen = sizeof(cmsgbuf);
196         msgh.msg_name = to;
197         msgh.msg_namelen = tolen;
198
199         cmsg = CMSG_FIRSTHDR(&msgh);
200
201 # ifdef HAVE_IP_PKTINFO
202         cmsg->cmsg_level = SOL_IP;
203         cmsg->cmsg_type = IP_PKTINFO;
204         cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
205         pktinfo.ipi_spec_dst = ((struct sockaddr_in *)from)->sin_addr;
206         pktinfo_ptr = (struct in_pktinfo *)CMSG_DATA(cmsg);
207         memcpy(pktinfo_ptr, &pktinfo, sizeof(struct in_pktinfo));
208 # endif
209 # ifdef HAVE_IP_SENDSRCADDR
210         cmsg->cmsg_level = IPPROTO_IP;
211         cmsg->cmsg_type = IP_SENDSRCADDR;
212         cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr));
213         memcpy((struct in_addr *)CMSG_DATA(cmsg),
214                &((struct sockaddr_in *)from)->sin_addr, sizeof(struct in_addr));
215 # endif
216
217         return sendmsg(s, &msgh, flags);
218 #else
219         /* fallback: call sendto() */
220         return sendto(s, buf, len, flags, to, tolen);
221 #endif  /* defined(HAVE_IP_PKTINFO) || defined (HAVE_IP_SENDSRCADDR) */
222 }
223
224
225 #ifdef TESTING
226 /*
227  *      Small test program to test recvfromto/sendfromto
228  *
229  *      use a virtual IP address as first argument to test
230  *
231  *      reply packet should originate from virtual IP and not
232  *      from the default interface the alias is bound to
233  */
234
235 #include <stdio.h>
236 #include <stdlib.h>
237 #include <arpa/inet.h>
238 #include <sys/types.h>
239 #include <sys/wait.h>
240
241 #define DEF_PORT 20000          /* default port to listen on */
242 #define DESTIP "127.0.0.1"      /* send packet to localhost per default */
243 #define TESTSTRING "foo"        /* what to send */
244 #define TESTLEN 4                       /* 4 bytes */
245
246 int main(int argc, char **argv)
247 {
248         struct sockaddr_in from, to, in;
249         char buf[TESTLEN];
250         char *destip = DESTIP;
251         int port = DEF_PORT;
252         int n, server_socket, client_socket, fl, tl, pid;
253
254         if (argc > 1) destip = argv[1];
255         if (argc > 2) port = atoi(argv[2]);
256
257         in.sin_family = AF_INET;
258         in.sin_addr.s_addr = INADDR_ANY;
259         in.sin_port = htons(port);
260         fl = tl = sizeof(struct sockaddr_in);
261         memset(&from, 0, sizeof(from));
262         memset(&to,   0, sizeof(to));
263
264         switch(pid = fork()) {
265                 case -1:
266                         perror("fork");
267                         return 0;
268                 case 0:
269                         /* child */
270                         usleep(100000);
271                         goto client;
272         }
273
274         /* parent: server */
275         server_socket = socket(PF_INET, SOCK_DGRAM, 0);
276         if (udpfromto_init(server_socket) != 0) {
277                 perror("udpfromto_init\n");
278                 waitpid(pid, NULL, WNOHANG);
279                 return 0;
280         }
281
282         if (bind(server_socket, (struct sockaddr *)&in, sizeof(in)) < 0) {
283                 perror("server: bind");
284                 waitpid(pid, NULL, WNOHANG);
285                 return 0;
286         }
287
288         printf("server: waiting for packets on INADDR_ANY:%d\n", port);
289         if ((n = recvfromto(server_socket, buf, sizeof(buf), 0,
290             (struct sockaddr *)&from, &fl,
291             (struct sockaddr *)&to, &tl)) < 0) {
292                 perror("server: recvfromto");
293                 waitpid(pid, NULL, WNOHANG);
294                 return 0;
295         }
296
297         printf("server: received a packet of %d bytes [%s] ", n, buf);
298         printf("(src ip:port %s:%d ",
299                 inet_ntoa(from.sin_addr), ntohs(from.sin_port));
300         printf(" dst ip:port %s:%d)\n",
301                 inet_ntoa(to.sin_addr), ntohs(to.sin_port));
302
303         printf("server: replying from address packet was received on to source address\n");
304
305         if ((n = sendfromto(server_socket, buf, n, 0,
306                 (struct sockaddr *)&to, tl,
307                 (struct sockaddr *)&from, fl)) < 0) {
308                 perror("server: sendfromto");
309         }
310
311         waitpid(pid, NULL, 0);
312         return 0;
313
314 client:
315         close(server_socket);
316         client_socket = socket(PF_INET, SOCK_DGRAM, 0);
317         if (udpfromto_init(client_socket) != 0) {
318                 perror("udpfromto_init");
319                 _exit(0);
320         }
321         /* bind client on different port */
322         in.sin_port = htons(port+1);
323         if (bind(client_socket, (struct sockaddr *)&in, sizeof(in)) < 0) {
324                 perror("client: bind");
325                 _exit(0);
326         }
327
328         in.sin_port = htons(port);
329         in.sin_addr.s_addr = inet_addr(destip);
330
331         printf("client: sending packet to %s:%d\n", destip, port);
332         if (sendto(client_socket, TESTSTRING, TESTLEN, 0,
333                         (struct sockaddr *)&in, sizeof(in)) < 0) {
334                 perror("client: sendto");
335                 _exit(0);
336         }
337
338         printf("client: waiting for reply from server on INADDR_ANY:%d\n", port+1);
339
340         if ((n = recvfromto(client_socket, buf, sizeof(buf), 0,
341             (struct sockaddr *)&from, &fl,
342             (struct sockaddr *)&to, &tl)) < 0) {
343                 perror("client: recvfromto");
344                 _exit(0);
345         }
346
347         printf("client: received a packet of %d bytes [%s] ", n, buf);
348         printf("(src ip:port %s:%d",
349                 inet_ntoa(from.sin_addr), ntohs(from.sin_port));
350         printf(" dst ip:port %s:%d)\n",
351                 inet_ntoa(to.sin_addr), ntohs(to.sin_port));
352
353         _exit(0);
354 }
355
356 #endif /* TESTING */
357 #endif /* WITH_UDPFROMTO */