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.
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.
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
16 * Helper functions to get/set addresses of UDP packets
17 * based on recvfromto by Miquel van Smoorenburg
19 * recvfromto Like recvfrom, but also stores the destination
20 * IP address. Useful on multihomed hosts.
22 * Should work on Linux and BSD.
24 * Copyright (C) 2002 Miquel van Smoorenburg.
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 * Copyright (C) 2007 Alan DeKok <aland@deployingradius.com>
32 * sendfromto added 18/08/2003, Jan Berkel <jan@sitadelle.com>
33 * Works on Linux and FreeBSD (5.x)
38 #include <freeradius-devel/ident.h>
41 #include <freeradius-devel/udpfromto.h>
52 * More portability idiocy
53 * Mac OSX Lion doesn't define SOL_IP. But IPPROTO_IP works.
56 #define SOL_IP IPPROTO_IP
60 * glibc 2.4 and uClibc 0.9.29 introduce IPV6_RECVPKTINFO etc. and
61 * change IPV6_PKTINFO This is only supported in Linux kernel >=
64 * This is only an approximation because the kernel version that libc
65 * was compiled against could be older or newer than the one being
66 * run. But this should not be a problem -- we just keep using the
67 * old kernel interface.
70 # if defined IPV6_RECVPKTINFO
71 # include <linux/version.h>
72 # if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,14)
73 # if defined IPV6_2292PKTINFO
74 # undef IPV6_RECVPKTINFO
76 # define IPV6_RECVPKTINFO IPV6_2292PKTINFO
77 # define IPV6_PKTINFO IPV6_2292PKTINFO
84 * Linux requires IPV6_RECVPKTINFO for the setsockopt() call,
85 * but sendmsg() and recvmsg() require IPV6_PKTINFO. <sigh>
87 * We want all *other* (i.e. sane) systems to use IPV6_PKTINFO
88 * for all three calls.
92 #define FR_IPV6_RECVPKTINFO IPV6_RECVPKTINFO
94 #define FR_IPV6_RECVPKTINFO IPV6_PKTINFO
98 int udpfromto_init(int s)
100 int proto, flag, opt = 1;
101 struct sockaddr_storage si;
102 socklen_t si_len = sizeof(si);
108 if (getsockname(s, (struct sockaddr *) &si, &si_len) < 0) {
112 if (si.ss_family == AF_INET) {
113 #ifdef HAVE_IP_PKTINFO
121 #ifdef IP_RECVDSTADDR
123 * Set the IP_RECVDSTADDR option (BSD). Note:
124 * IP_RECVDSTADDR == IP_SENDSRCADDR
127 flag = IP_RECVDSTADDR;
131 } else if (si.ss_family == AF_INET6) {
134 * This should actually be standard IPv6
136 proto = IPPROTO_IPV6;
139 * Work around Linux-specific hackery.
141 flag = FR_IPV6_RECVPKTINFO;
152 * Unsupported. Don't worry about it.
154 if (proto < 0) return 0;
156 return setsockopt(s, proto, flag, &opt, sizeof(opt));
159 int recvfromto(int s, void *buf, size_t len, int flags,
160 struct sockaddr *from, socklen_t *fromlen,
161 struct sockaddr *to, socklen_t *tolen)
164 struct cmsghdr *cmsg;
168 struct sockaddr_storage si;
169 socklen_t si_len = sizeof(si);
171 #if !defined(IP_PKTINFO) && !defined(IP_RECVDSTADDR) && !defined (IPV6_PKTINFO)
173 * If the recvmsg() flags aren't defined, fall back to
180 * Catch the case where the caller passes invalid arguments.
182 if (!to || !tolen) return recvfrom(s, buf, len, flags, from, fromlen);
185 * recvmsg doesn't provide sin_port so we have to
186 * retrieve it using getsockname().
188 if (getsockname(s, (struct sockaddr *)&si, &si_len) < 0) {
193 * Initialize the 'to' address. It may be INADDR_ANY here,
194 * with a more specific address given by recvmsg(), below.
196 if (si.ss_family == AF_INET) {
197 #if !defined(IP_PKTINFO) && !defined(IP_RECVDSTADDR)
198 return recvfrom(s, buf, len, flags, from, fromlen);
200 struct sockaddr_in *dst = (struct sockaddr_in *) to;
201 struct sockaddr_in *src = (struct sockaddr_in *) &si;
203 if (*tolen < sizeof(*dst)) {
207 *tolen = sizeof(*dst);
213 else if (si.ss_family == AF_INET6) {
214 #if !defined(IPV6_PKTINFO)
215 return recvfrom(s, buf, len, flags, from, fromlen);
217 struct sockaddr_in6 *dst = (struct sockaddr_in6 *) to;
218 struct sockaddr_in6 *src = (struct sockaddr_in6 *) &si;
220 if (*tolen < sizeof(*dst)) {
224 *tolen = sizeof(*dst);
230 * Unknown address family.
237 /* Set up iov and msgh structures. */
238 memset(&msgh, 0, sizeof(struct msghdr));
241 msgh.msg_control = cbuf;
242 msgh.msg_controllen = sizeof(cbuf);
243 msgh.msg_name = from;
244 msgh.msg_namelen = fromlen ? *fromlen : 0;
249 /* Receive one packet. */
250 if ((err = recvmsg(s, &msgh, flags)) < 0) {
254 if (fromlen) *fromlen = msgh.msg_namelen;
256 /* Process auxiliary received data in msgh */
257 for (cmsg = CMSG_FIRSTHDR(&msgh);
259 cmsg = CMSG_NXTHDR(&msgh,cmsg)) {
262 if ((cmsg->cmsg_level == SOL_IP) &&
263 (cmsg->cmsg_type == IP_PKTINFO)) {
264 struct in_pktinfo *i =
265 (struct in_pktinfo *) CMSG_DATA(cmsg);
266 ((struct sockaddr_in *)to)->sin_addr = i->ipi_addr;
267 *tolen = sizeof(struct sockaddr_in);
272 #ifdef IP_RECVDSTADDR
273 if ((cmsg->cmsg_level == IPPROTO_IP) &&
274 (cmsg->cmsg_type == IP_RECVDSTADDR)) {
275 struct in_addr *i = (struct in_addr *) CMSG_DATA(cmsg);
276 ((struct sockaddr_in *)to)->sin_addr = *i;
277 *tolen = sizeof(struct sockaddr_in);
283 if ((cmsg->cmsg_level == IPPROTO_IPV6) &&
284 (cmsg->cmsg_type == IPV6_PKTINFO)) {
285 struct in6_pktinfo *i =
286 (struct in6_pktinfo *) CMSG_DATA(cmsg);
287 ((struct sockaddr_in6 *)to)->sin6_addr = i->ipi6_addr;
288 *tolen = sizeof(struct sockaddr_in6);
297 int sendfromto(int s, void *buf, size_t len, int flags,
298 struct sockaddr *from, socklen_t fromlen,
299 struct sockaddr *to, socklen_t tolen)
302 struct cmsghdr *cmsg;
308 * FreeBSD is extra pedantic about the use of IP_SENDSRCADDR,
309 * and sendmsg will fail with EINVAL if IP_SENDSRCADDR is used
310 * with a socket which is bound to something other than
313 struct sockaddr bound;
314 socklen_t bound_len = sizeof(bound);
316 if (getsockname(s, &bound, &bound_len) < 0) {
320 switch (bound.sa_family) {
322 if (((struct sockaddr_in *) &bound)->sin_addr.s_addr != INADDR_ANY) {
328 if (!IN6_IS_ADDR_UNSPECIFIED(&((struct sockaddr_in6 *) &bound)->sin6_addr)) {
334 # if !defined(IP_PKTINFO) && !defined(IP_SENDSRCADDR) && !defined(IPV6_PKTINFO)
336 * If the sendmsg() flags aren't defined, fall back to
344 * Catch the case where the caller passes invalid arguments.
346 if (!from || (fromlen == 0) || (from->sa_family == AF_UNSPEC)) {
347 return sendto(s, buf, len, flags, to, tolen);
350 /* Set up iov and msgh structures. */
351 memset(&msgh, 0, sizeof(struct msghdr));
357 msgh.msg_namelen = tolen;
359 if (from->sa_family == AF_INET) {
360 #if !defined(IP_PKTINFO) && !defined(IP_SENDSRCADDR)
361 return sendto(s, buf, len, flags, to, tolen);
363 struct sockaddr_in *s4 = (struct sockaddr_in *) from;
366 struct in_pktinfo *pkt;
368 msgh.msg_control = cbuf;
369 msgh.msg_controllen = CMSG_SPACE(sizeof(*pkt));
371 cmsg = CMSG_FIRSTHDR(&msgh);
372 cmsg->cmsg_level = SOL_IP;
373 cmsg->cmsg_type = IP_PKTINFO;
374 cmsg->cmsg_len = CMSG_LEN(sizeof(*pkt));
376 pkt = (struct in_pktinfo *) CMSG_DATA(cmsg);
377 memset(pkt, 0, sizeof(*pkt));
378 pkt->ipi_spec_dst = s4->sin_addr;
381 #ifdef IP_SENDSRCADDR
384 msgh.msg_control = cbuf;
385 msgh.msg_controllen = CMSG_SPACE(sizeof(*in));
387 cmsg = CMSG_FIRSTHDR(&msgh);
388 cmsg->cmsg_level = IPPROTO_IP;
389 cmsg->cmsg_type = IP_SENDSRCADDR;
390 cmsg->cmsg_len = CMSG_LEN(sizeof(*in));
392 in = (struct in_addr *) CMSG_DATA(cmsg);
395 #endif /* IP_PKTINFO or IP_SENDSRCADDR */
399 else if (from->sa_family == AF_INET6) {
400 #if !defined(IPV6_PKTINFO)
401 return sendto(s, buf, len, flags, to, tolen);
403 struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) from;
405 struct in6_pktinfo *pkt;
407 msgh.msg_control = cbuf;
408 msgh.msg_controllen = CMSG_SPACE(sizeof(*pkt));
410 cmsg = CMSG_FIRSTHDR(&msgh);
411 cmsg->cmsg_level = IPPROTO_IPV6;
412 cmsg->cmsg_type = IPV6_PKTINFO;
413 cmsg->cmsg_len = CMSG_LEN(sizeof(*pkt));
415 pkt = (struct in6_pktinfo *) CMSG_DATA(cmsg);
416 memset(pkt, 0, sizeof(*pkt));
417 pkt->ipi6_addr = s6->sin6_addr;
418 #endif /* IPV6_PKTINFO */
423 * Unknown address family.
430 return sendmsg(s, &msgh, flags);
436 * Small test program to test recvfromto/sendfromto
438 * use a virtual IP address as first argument to test
440 * reply packet should originate from virtual IP and not
441 * from the default interface the alias is bound to
446 #include <arpa/inet.h>
447 #include <sys/types.h>
448 #include <sys/wait.h>
450 #define DEF_PORT 20000 /* default port to listen on */
451 #define DESTIP "127.0.0.1" /* send packet to localhost per default */
452 #define TESTSTRING "foo" /* what to send */
453 #define TESTLEN 4 /* 4 bytes */
455 int main(int argc, char **argv)
457 struct sockaddr_in from, to, in;
459 char *destip = DESTIP;
461 int n, server_socket, client_socket, fl, tl, pid;
463 if (argc > 1) destip = argv[1];
464 if (argc > 2) port = atoi(argv[2]);
466 in.sin_family = AF_INET;
467 in.sin_addr.s_addr = INADDR_ANY;
468 in.sin_port = htons(port);
469 fl = tl = sizeof(struct sockaddr_in);
470 memset(&from, 0, sizeof(from));
471 memset(&to, 0, sizeof(to));
473 switch(pid = fork()) {
484 server_socket = socket(PF_INET, SOCK_DGRAM, 0);
485 if (udpfromto_init(server_socket) != 0) {
486 perror("udpfromto_init\n");
487 waitpid(pid, NULL, WNOHANG);
491 if (bind(server_socket, (struct sockaddr *)&in, sizeof(in)) < 0) {
492 perror("server: bind");
493 waitpid(pid, NULL, WNOHANG);
497 printf("server: waiting for packets on INADDR_ANY:%d\n", port);
498 if ((n = recvfromto(server_socket, buf, sizeof(buf), 0,
499 (struct sockaddr *)&from, &fl,
500 (struct sockaddr *)&to, &tl)) < 0) {
501 perror("server: recvfromto");
502 waitpid(pid, NULL, WNOHANG);
506 printf("server: received a packet of %d bytes [%s] ", n, buf);
507 printf("(src ip:port %s:%d ",
508 inet_ntoa(from.sin_addr), ntohs(from.sin_port));
509 printf(" dst ip:port %s:%d)\n",
510 inet_ntoa(to.sin_addr), ntohs(to.sin_port));
512 printf("server: replying from address packet was received on to source address\n");
514 if ((n = sendfromto(server_socket, buf, n, 0,
515 (struct sockaddr *)&to, tl,
516 (struct sockaddr *)&from, fl)) < 0) {
517 perror("server: sendfromto");
520 waitpid(pid, NULL, 0);
524 close(server_socket);
525 client_socket = socket(PF_INET, SOCK_DGRAM, 0);
526 if (udpfromto_init(client_socket) != 0) {
527 perror("udpfromto_init");
530 /* bind client on different port */
531 in.sin_port = htons(port+1);
532 if (bind(client_socket, (struct sockaddr *)&in, sizeof(in)) < 0) {
533 perror("client: bind");
537 in.sin_port = htons(port);
538 in.sin_addr.s_addr = inet_addr(destip);
540 printf("client: sending packet to %s:%d\n", destip, port);
541 if (sendto(client_socket, TESTSTRING, TESTLEN, 0,
542 (struct sockaddr *)&in, sizeof(in)) < 0) {
543 perror("client: sendto");
547 printf("client: waiting for reply from server on INADDR_ANY:%d\n", port+1);
549 if ((n = recvfromto(client_socket, buf, sizeof(buf), 0,
550 (struct sockaddr *)&from, &fl,
551 (struct sockaddr *)&to, &tl)) < 0) {
552 perror("client: recvfromto");
556 printf("client: received a packet of %d bytes [%s] ", n, buf);
557 printf("(src ip:port %s:%d",
558 inet_ntoa(from.sin_addr), ntohs(from.sin_port));
559 printf(" dst ip:port %s:%d)\n",
560 inet_ntoa(to.sin_addr), ntohs(to.sin_port));
566 #endif /* WITH_UDPFROMTO */