tftp-hpa-google/tftpd/recvfrom.c
Karsten Keil 28f22b6591 Add support for IPv6 in the server and client.
Add support for IPv6 in the server and client.
You can force the use of IPv4 or IPv6 only with new
-4 and -6 commandline options, if IPv6 support was compiled in.

Signed-off-by: Karsten Keil <kkeil@suse.de>
Signed-off-by: H. Peter Anvin <hpa@zytor.com>
2008-07-23 14:29:41 -04:00

259 lines
7.3 KiB
C

/* ----------------------------------------------------------------------- *
*
* Copyright 2001-2006 H. Peter Anvin - All Rights Reserved
*
* This program is free software available under the same license
* as the "OpenBSD" operating system, distributed at
* http://www.openbsd.org/.
*
* ----------------------------------------------------------------------- */
/*
* recvfrom.c
*
* Emulate recvfrom() using recvmsg(), but try to capture the local address
* since some TFTP clients consider it an error to get the reply from another
* IP address than the request was sent to.
*
*/
#include "config.h" /* Must be included first! */
#include "common/tftpsubs.h"
#include "recvfrom.h"
#ifdef HAVE_MACHINE_PARAM_H
#include <machine/param.h> /* Needed on some versions of FreeBSD */
#endif
#if defined(HAVE_RECVMSG) && defined(HAVE_MSGHDR_MSG_CONTROL)
#include <sys/uio.h>
#ifdef IP_PKTINFO
# ifndef HAVE_STRUCT_IN_PKTINFO
# ifdef __linux__
/* Assume this version of glibc simply lacks the definition */
struct in_pktinfo {
int ipi_ifindex;
struct in_addr ipi_spec_dst;
struct in_addr ipi_addr;
};
# else
# undef IP_PKTINFO /* No definition, no way to get it */
# endif
# endif
#endif
#ifndef CMSG_LEN
# define CMSG_LEN(size) (sizeof(struct cmsghdr) + (size))
#endif
#ifndef CMSG_SPACE
# define CMSG_SPACE(size) (sizeof(struct cmsghdr) + (size))
#endif
/*
* Check to see if this is a valid local address. If so, we should
* end up having the same local and remote address when trying to
* bind to it.
*/
static int address_is_local(const union sock_addr *addr)
{
union sock_addr sa;
int sockfd = -1;
int e;
int rv = 0;
socklen_t addrlen;
/* Multicast or universal broadcast address? */
if (addr->sa.sa_family == AF_INET) {
if (ntohl(addr->si.sin_addr.s_addr) >= (224UL << 24))
return 0;
}
#ifdef HAVE_IPV6
else if (addr->sa.sa_family == AF_INET6) {
if (IN6_IS_ADDR_MULTICAST(&addr->s6.sin6_addr))
return 0;
}
#endif
else
return 0;
sockfd = socket(addr->sa.sa_family, SOCK_DGRAM, 0);
if (sockfd < 0)
goto err;
if (connect(sockfd, &addr->sa, SOCKLEN(addr)))
goto err;
addrlen = SOCKLEN(addr);
if (getsockname(sockfd, (struct sockaddr *)&sa, &addrlen))
goto err;
if (addr->sa.sa_family == AF_INET)
rv = sa.si.sin_addr.s_addr == addr->si.sin_addr.s_addr;
#ifdef HAVE_IPV6
else if (addr->sa.sa_family == AF_INET6)
rv = IN6_ARE_ADDR_EQUAL(&sa.s6.sin6_addr, &addr->s6.sin6_addr);
#endif
else
rv = 0;
err:
e = errno;
if (sockfd >= 0)
close(sockfd);
errno = e;
return rv;
}
int
myrecvfrom(int s, void *buf, int len, unsigned int flags,
struct sockaddr *from, socklen_t * fromlen,
union sock_addr *myaddr)
{
struct msghdr msg;
struct iovec iov;
int n;
struct cmsghdr *cmptr;
union {
struct cmsghdr cm;
#ifdef IP_PKTINFO
char control[CMSG_SPACE(sizeof(struct in_addr)) +
CMSG_SPACE(sizeof(struct in_pktinfo))];
#else
char control[CMSG_SPACE(sizeof(struct in_addr))];
#endif
#ifdef HAVE_IPV6
#ifdef HAVE_STRUCT_IN6_PKTINFO
char control6[CMSG_SPACE(sizeof(struct in6_addr)) +
CMSG_SPACE(sizeof(struct in6_pktinfo))];
#else
char control6[CMSG_SPACE(sizeof(struct in6_addr))];
#endif
#endif
} control_un;
int on = 1;
#ifdef IP_PKTINFO
struct in_pktinfo pktinfo;
#endif
#ifdef HAVE_STRUCT_IN6_PKTINFO
struct in6_pktinfo pktinfo6;
#endif
/* Try to enable getting the return address */
#ifdef IP_RECVDSTADDR
if (from->sa_family == AF_INET)
setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on));
#endif
#ifdef IP_PKTINFO
if (from->sa_family == AF_INET)
setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
#endif
#ifdef HAVE_IPV6
#ifdef IPV6_RECVPKTINFO
if (from->sa_family == AF_INET6)
setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on));
#endif
#endif
bzero(&msg, sizeof msg); /* Clear possible system-dependent fields */
msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un);
msg.msg_flags = 0;
msg.msg_name = from;
msg.msg_namelen = *fromlen;
iov.iov_base = buf;
iov.iov_len = len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
if ((n = recvmsg(s, &msg, flags)) < 0)
return n; /* Error */
*fromlen = msg.msg_namelen;
if (myaddr) {
bzero(myaddr, sizeof(*myaddr));
myaddr->sa.sa_family = from->sa_family;
if (msg.msg_controllen < sizeof(struct cmsghdr) ||
(msg.msg_flags & MSG_CTRUNC))
return n; /* No information available */
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL;
cmptr = CMSG_NXTHDR(&msg, cmptr)) {
if (from->sa_family == AF_INET) {
myaddr->sa.sa_family = AF_INET;
#ifdef IP_RECVDSTADDR
if (cmptr->cmsg_level == IPPROTO_IP &&
cmptr->cmsg_type == IP_RECVDSTADDR) {
memcpy(&myaddr->si.sin_addr, CMSG_DATA(cmptr),
sizeof(struct in_addr));
}
#endif
#ifdef IP_PKTINFO
if (cmptr->cmsg_level == IPPROTO_IP &&
cmptr->cmsg_type == IP_PKTINFO) {
memcpy(&pktinfo, CMSG_DATA(cmptr),
sizeof(struct in_pktinfo));
memcpy(&myaddr->si.sin_addr, &pktinfo.ipi_addr,
sizeof(struct in_addr));
}
#endif
}
#ifdef HAVE_IPV6
else if (from->sa_family == AF_INET6) {
myaddr->sa.sa_family = AF_INET6;
#ifdef IP6_RECVDSTADDR
if (cmptr->cmsg_level == IPPROTO_IPV6 &&
cmptr->cmsg_type == IPV6_RECVDSTADDR )
memcpy(&myaddr->s6.sin6_addr, CMSG_DATA(cmptr),
sizeof(struct in6_addr));
#endif
#ifdef HAVE_STRUCT_IN6_PKTINFO
if (cmptr->cmsg_level == IPPROTO_IPV6 &&
(cmptr->cmsg_type == IPV6_RECVPKTINFO ||
cmptr->cmsg_type == IPV6_PKTINFO)) {
memcpy(&pktinfo6, CMSG_DATA(cmptr),
sizeof(struct in6_pktinfo));
memcpy(&myaddr->s6.sin6_addr, &pktinfo6.ipi6_addr,
sizeof(struct in6_addr));
}
#endif
}
#endif
}
/* If the address is not a valid local address,
* then bind to any address...
*/
if (address_is_local(myaddr) != 1) {
if (myaddr->sa.sa_family == AF_INET)
((struct sockaddr_in *)myaddr)->sin_addr.s_addr = INADDR_ANY;
#ifdef HAVE_IPV6
else if (myaddr->sa.sa_family == AF_INET6)
memset(&myaddr->s6.sin6_addr, 0, sizeof(struct in6_addr));
#endif
}
}
return n;
}
#else /* pointless... */
int
myrecvfrom(int s, void *buf, int len, unsigned int flags,
struct sockaddr *from, int *fromlen, union sock_addr *myaddr)
{
/* There is no way we can get the local address, fudge it */
bzero(myaddr, sizeof(*myaddr));
myaddr->sa.sa_family = from->sa_family;
sa_set_port(myaddr, htons(IPPORT_TFTP));
return recvfrom(s, buf, len, flags, from, fromlen);
}
#endif