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>
This commit is contained in:
Karsten Keil 2008-07-23 18:32:21 +02:00 committed by H. Peter Anvin
parent 7fe0fb941c
commit 28f22b6591
9 changed files with 574 additions and 185 deletions

View file

@ -42,7 +42,6 @@
#include <sys/ioctl.h>
#include <signal.h>
#include <netdb.h>
#include <ctype.h>
#include <pwd.h>
#include <limits.h>
@ -65,6 +64,12 @@ int allow_severity = -1; /* Don't log at all */
struct request_info wrap_request;
#endif
#ifdef HAVE_IPV6
int ai_fam = AF_UNSPEC;
#else
int ai_fam = AF_INET;
#endif
#define TIMEOUT 1000000 /* Default timeout (us) */
#define TRIES 6 /* Number of attempts to send each packet */
#define TIMEOUT_LIMIT ((1 << TRIES)-1)
@ -82,7 +87,9 @@ char buf[PKTSIZE];
char ackbuf[PKTSIZE];
unsigned int max_blksize = MAX_SEGSIZE;
struct sockaddr_in from;
char tmpbuf[INET6_ADDRSTRLEN], *tmp_p;
union sock_addr from;
socklen_t fromlen;
off_t tsize;
int tsize_ok;
@ -254,10 +261,16 @@ int main(int argc, char **argv)
struct tftphdr *tp;
struct passwd *pw;
struct options *opt;
struct sockaddr_in myaddr;
struct sockaddr_in bindaddr;
union sock_addr myaddr;
struct sockaddr_in bindaddr4;
#ifdef HAVE_IPV6
struct sockaddr_in6 bindaddr6;
#endif
int n;
int fd = 0;
int fd = -1;
int fd4 = -1;
int fd6 = -1;
int fdmax = 0;
int standalone = 0; /* Standalone (listen) mode */
int nodaemon = 0; /* Do not detach process */
char *address = NULL; /* Address to listen to */
@ -283,8 +296,16 @@ int main(int argc, char **argv)
srand(time(NULL) ^ getpid());
while ((c = getopt(argc, argv, "cspvVlLa:B:u:U:r:t:T:R:m:")) != -1)
while ((c = getopt(argc, argv, "46cspvVlLa:B:u:U:r:t:T:R:m:")) != -1)
switch (c) {
#ifdef HAVE_IPV6
case '4':
ai_fam = AF_INET;
break;
case '6':
ai_fam = AF_INET6;
break;
#endif
case 'c':
cancreate = 1;
break;
@ -417,12 +438,6 @@ int main(int argc, char **argv)
if (spec_umask || !unixperms)
umask(my_umask);
/* Note: on Cygwin, select() on a nonblocking socket becomes
a nonblocking select. */
#ifndef __CYGWIN__
set_socket_nonblock(fd, 1);
#endif
#ifdef WITH_REGEX
if (rewrite_file)
rewrite_rules = read_remap_rules(rewrite_file);
@ -430,16 +445,45 @@ int main(int argc, char **argv)
/* If we're running standalone, set up the input port */
if (standalone) {
fd = socket(PF_INET, SOCK_DGRAM, 0);
memset(&bindaddr, 0, sizeof bindaddr);
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = INADDR_ANY;
bindaddr.sin_port = htons(IPPORT_TFTP);
#ifdef HAVE_IPV6
if (ai_fam != AF_INET6) {
#endif
fd4 = socket(AF_INET, SOCK_DGRAM, 0);
if (fd4 < 0) {
syslog(LOG_ERR, "cannot open IPv4 socket: %m");
exit(EX_OSERR);
}
#ifndef __CYGWIN__
set_socket_nonblock(fd4, 1);
#endif
memset(&bindaddr4, 0, sizeof bindaddr4);
bindaddr4.sin_family = AF_INET;
bindaddr4.sin_addr.s_addr = INADDR_ANY;
bindaddr4.sin_port = htons(IPPORT_TFTP);
#ifdef HAVE_IPV6
}
if (ai_fam != AF_INET) {
fd6 = socket(AF_INET6, SOCK_DGRAM, 0);
if (fd6 < 0) {
if (fd4 < 0) {
syslog(LOG_ERR, "cannot open IPv6 socket: %m");
exit(EX_OSERR);
} else {
syslog(LOG_ERR,
"cannot open IPv6 socket, disable IPv6: %m");
}
}
#ifndef __CYGWIN__
set_socket_nonblock(fd6, 1);
#endif
memset(&bindaddr6, 0, sizeof bindaddr6);
bindaddr6.sin6_family = AF_INET6;
bindaddr6.sin6_port = htons(IPPORT_TFTP);
}
#endif
if (address) {
char *portptr, *eportptr;
struct hostent *hostent;
int err;
struct servent *servent;
unsigned long port;
@ -447,17 +491,40 @@ int main(int argc, char **argv)
portptr = strrchr(address, ':');
if (portptr)
*portptr++ = '\0';
else
portptr = (char *)"tftp";
if (*address) {
hostent = gethostbyname(address);
if (!hostent || hostent->h_addrtype != AF_INET) {
syslog(LOG_ERR,
"cannot resolve local bind address: %s",
address);
exit(EX_NOINPUT);
if (fd4 >= 0) {
bindaddr4.sin_family = AF_INET;
err = set_sock_addr(address,
(union sock_addr *)&bindaddr4, NULL);
if (err) {
syslog(LOG_ERR,
"cannot resolve local IPv4 bind address: %s",
address);
exit(EX_NOINPUT);
}
}
memcpy(&bindaddr.sin_addr, hostent->h_addr,
hostent->h_length);
#ifdef HAVE_IPV6
if (fd6 >= 0) {
err = set_sock_addr(address,
(union sock_addr *)&bindaddr6, NULL);
if (err) {
if (fd4 >= 0) {
syslog(LOG_ERR,
"cannot resolve local IPv6 bind address: %s"
"; using IPv4 only", address);
close(fd6);
fd6 = -1;
} else {
syslog(LOG_ERR,
"cannot resolve local IPv6 bind address: %s",
address);
exit(EX_NOINPUT);
}
}
}
#endif
} else {
/* Default to using INADDR_ANY */
}
@ -465,10 +532,20 @@ int main(int argc, char **argv)
if (portptr && *portptr) {
servent = getservbyname(portptr, "udp");
if (servent) {
bindaddr.sin_port = servent->s_port;
if (fd4 >= 0)
bindaddr4.sin_port = servent->s_port;
#ifdef HAVE_IPV6
if (fd6 >= 0)
bindaddr6.sin6_port = servent->s_port;
#endif
} else if ((port = strtoul(portptr, &eportptr, 0))
&& !*eportptr) {
bindaddr.sin_port = htons(port);
if (fd4 >= 0)
bindaddr4.sin_port = htons(port);
#ifdef HAVE_IPV6
if (fd6 >= 0)
bindaddr6.sin6_port = htons(port);
#endif
} else if (!strcmp(portptr, "tftp")) {
/* It's TFTP, we're OK */
} else {
@ -479,11 +556,37 @@ int main(int argc, char **argv)
}
}
if (bind(fd, (struct sockaddr *)&bindaddr, sizeof bindaddr) < 0) {
syslog(LOG_ERR, "cannot bind to local socket: %m");
exit(EX_OSERR);
if (fd4 >= 0) {
if (bind(fd4, (struct sockaddr *)&bindaddr4,
sizeof(bindaddr4)) < 0) {
syslog(LOG_ERR, "cannot bind to local IPv4 socket: %m");
exit(EX_OSERR);
}
}
#ifdef HAVE_IPV6
if (fd6 >= 0) {
int on = 1;
#if defined(IPV6_V6ONLY)
if (setsockopt(fd6, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&on,
sizeof(on))) {
syslog(LOG_ERR, "cannot setsockopt IPV6_V6ONLY %m");
}
#endif
if (bind(fd6, (struct sockaddr *)&bindaddr6,
sizeof(bindaddr6)) < 0) {
if (fd4 >= 0) {
syslog(LOG_ERR,
"cannot bind to local IPv6 socket,"
"IPv6 disabled: %m");
close(fd6);
fd6 = -1;
} else {
syslog(LOG_ERR, "cannot bind to local IPv6 socket: %m");
exit(EX_OSERR);
}
}
}
#endif
/* Daemonize this process */
/* Note: when running in secure mode (-s), we must not chroot, since
we are already in the proper directory. */
@ -491,10 +594,21 @@ int main(int argc, char **argv)
syslog(LOG_ERR, "cannot daemonize: %m");
exit(EX_OSERR);
}
if (fd6 > fd4)
fdmax = fd6;
else
fdmax = fd4;
} else {
/* 0 is our socket descriptor */
close(1);
close(2);
fd = 0;
fdmax = 0;
/* Note: on Cygwin, select() on a nonblocking socket becomes
a nonblocking select. */
#ifndef __CYGWIN__
set_socket_nonblock(fd, 1);
#endif
}
/* Disable path MTU discovery */
@ -533,29 +647,61 @@ int main(int argc, char **argv)
}
FD_ZERO(&readset);
FD_SET(fd, &readset);
if (standalone) {
if (fd4 >= 0) {
FD_SET(fd4, &readset);
#ifdef __CYGWIN__
/* On Cygwin, select() on a nonblocking socket returns
immediately, with a rv of 0! */
set_socket_nonblock(fd4, 0);
#endif
}
if (fd6 >= 0) {
FD_SET(fd6, &readset);
#ifdef __CYGWIN__
/* On Cygwin, select() on a nonblocking socket returns
immediately, with a rv of 0! */
set_socket_nonblock(fd6, 0);
#endif
}
} else { /* fd always 0 */
fd = 0;
#ifdef __CYGWIN__
/* On Cygwin, select() on a nonblocking socket returns
immediately, with a rv of 0! */
set_socket_nonblock(fd, 0);
#endif
FD_SET(fd, &readset);
}
tv_waittime.tv_sec = waittime;
tv_waittime.tv_usec = 0;
#ifdef __CYGWIN__
/* On Cygwin, select() on a nonblocking socket returns immediately,
with a rv of 0! */
set_socket_nonblock(fd, 0);
#endif
/* Never time out if we're in standalone mode */
rv = select(fd + 1, &readset, NULL, NULL,
rv = select(fdmax + 1, &readset, NULL, NULL,
standalone ? NULL : &tv_waittime);
if (rv == -1 && errno == EINTR)
continue; /* Signal caught, reloop */
if (rv == -1) {
syslog(LOG_ERR, "select loop: %m");
exit(EX_IOERR);
} else if (rv == 0) {
exit(0); /* Timeout, return to inetd */
}
if (standalone) {
if ((fd4 >= 0) && FD_ISSET(fd4, &readset))
fd = fd4;
else if ((fd6 >= 0) && FD_ISSET(fd6, &readset))
fd = fd6;
else /* not in set ??? */
continue;
}
#ifdef __CYGWIN__
set_socket_nonblock(fd, 1);
/* On Cygwin, select() on a nonblocking socket returns
immediately, with a rv of 0! */
set_socket_nonblock(fd, 0);
#endif
fromlen = sizeof(from);
@ -570,18 +716,32 @@ int main(int argc, char **argv)
exit(EX_IOERR);
}
}
if (from.sin_family != AF_INET) {
syslog(LOG_ERR,
"received address was not AF_INET, please check your inetd config");
#ifdef HAVE_IPV6
if ((from.sa.sa_family != AF_INET) && (from.sa.sa_family != AF_INET6)) {
syslog(LOG_ERR, "received address was not AF_INET/AF_INET6,"
" please check your inetd config");
#else
if (from.sa.sa_family != AF_INET) {
syslog(LOG_ERR, "received address was not AF_INET,"
" please check your inetd config");
#endif
exit(EX_PROTOCOL);
}
if (standalone && myaddr.sin_addr.s_addr == INADDR_ANY) {
/* myrecvfrom() didn't capture the source address; but we might
have bound to a specific address, if so we should use it */
memcpy(&myaddr.sin_addr, &bindaddr.sin_addr,
sizeof bindaddr.sin_addr);
if (standalone) {
if ((from.sa.sa_family == AF_INET) &&
(myaddr.si.sin_addr.s_addr == INADDR_ANY)) {
/* myrecvfrom() didn't capture the source address; but we might
have bound to a specific address, if so we should use it */
memcpy(SOCKADDR_P(&myaddr), &bindaddr4.sin_addr,
sizeof(bindaddr4.sin_addr));
#ifdef HAVE_IPV6
} else if ((from.sa.sa_family == AF_INET6) &&
IN6_IS_ADDR_UNSPECIFIED(SOCKADDR_P(&myaddr))) {
memcpy(SOCKADDR_P(&myaddr), &bindaddr6.sin6_addr,
sizeof(bindaddr6.sin6_addr));
#endif
}
}
/*
@ -609,14 +769,19 @@ int main(int argc, char **argv)
RQ_FILE, fd,
RQ_CLIENT_SIN, &from, RQ_SERVER_SIN, &myaddr, 0);
sock_methods(&wrap_request);
tmp_p = (char *)inet_ntop(myaddr.sa.sa_family, SOCKADDR_P(&myaddr),
tmpbuf, INET6_ADDRSTRLEN);
if (!tmp_p) {
tmp_p = tmpbuf;
strcpy(tmpbuf, "???");
}
if (hosts_access(&wrap_request) == 0) {
if (deny_severity != -1)
syslog(deny_severity, "connection refused from %s",
inet_ntoa(from.sin_addr));
syslog(deny_severity, "connection refused from %s", tmp_p);
exit(EX_NOPERM); /* Access denied */
} else if (allow_severity != -1) {
syslog(allow_severity, "connect from %s",
inet_ntoa(from.sin_addr));
syslog(allow_severity, "connect from %s", tmp_p);
}
#endif
@ -626,7 +791,7 @@ int main(int argc, char **argv)
/* Get a socket. This has to be done before the chroot(), since
some systems require access to /dev to create a socket. */
peer = socket(AF_INET, SOCK_DGRAM, 0);
peer = socket(myaddr.sa.sa_family, SOCK_DGRAM, 0);
if (peer < 0) {
syslog(LOG_ERR, "socket: %m");
exit(EX_IOERR);
@ -677,16 +842,13 @@ int main(int argc, char **argv)
exit(EX_OSERR);
}
/* Other basic setup */
from.sin_family = AF_INET;
/* Process the request... */
if (pick_port_bind(peer, &myaddr, portrange_from, portrange_to) < 0) {
syslog(LOG_ERR, "bind: %m");
exit(EX_IOERR);
}
if (connect(peer, (struct sockaddr *)&from, sizeof from) < 0) {
if (connect(peer, &from.sa, SOCKLEN(&from)) < 0) {
syslog(LOG_ERR, "connect: %m");
exit(EX_IOERR);
}
@ -776,16 +938,22 @@ int tftp(struct tftphdr *tp, int size)
exit(0);
}
if (verbosity >= 1) {
tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from),
tmpbuf, INET6_ADDRSTRLEN);
if (!tmp_p) {
tmp_p = tmpbuf;
strcpy(tmpbuf, "???");
}
if (filename == origfilename
|| !strcmp(filename, origfilename))
syslog(LOG_NOTICE, "%s from %s filename %s\n",
tp_opcode == WRQ ? "WRQ" : "RRQ",
inet_ntoa(from.sin_addr), filename);
tmp_p, filename);
else
syslog(LOG_NOTICE,
"%s from %s filename %s remapped to %s\n",
tp_opcode == WRQ ? "WRQ" : "RRQ",
inet_ntoa(from.sin_addr), origfilename,
tmp_p, origfilename,
filename);
}
ecode =
@ -998,20 +1166,41 @@ int rewrite_macros(char macro, char *output);
int rewrite_macros(char macro, char *output)
{
char *p;
char *p, tb[INET6_ADDRSTRLEN];
int l=0;
switch (macro) {
case 'i':
p = inet_ntoa(from.sin_addr);
if (output)
p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from),
tb, INET6_ADDRSTRLEN);
if (output && p)
strcpy(output, p);
return strlen(p);
if (!p)
return 0;
else
return strlen(p);
case 'x':
if (output)
sprintf(output, "%08lX",
(unsigned long)ntohl(from.sin_addr.s_addr));
return 8;
if (output) {
if (from.sa.sa_family == AF_INET) {
sprintf(output, "%08lX",
(unsigned long)ntohl(from.si.sin_addr.s_addr));
l = 8;
#ifdef HAVE_IPV6
} else {
unsigned char *c = (unsigned char *)SOCKADDR_P(&from);
p = tb;
for (l = 0; l < 16; l++) {
sprintf(p, "%02X", *c);
c++;
p += 2;
}
strcpy(output, tb);
l = strlen(tb);
#endif
}
}
return l;
default:
return -1;
@ -1398,8 +1587,14 @@ static void nak(int error, const char *msg)
length += 4; /* Add space for header */
if (verbosity >= 2) {
tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from),
tmpbuf, INET6_ADDRSTRLEN);
if (!tmp_p) {
tmp_p = tmpbuf;
strcpy(tmpbuf, "???");
}
syslog(LOG_INFO, "sending NAK (%d, %s) to %s",
error, tp->th_msg, inet_ntoa(from.sin_addr));
error, tp->th_msg, tmp_p);
}
if (send(peer, buf, length, 0) != length)