another attempt to get Linux and IPv6 right

svn: r1386
This commit is contained in:
Matthew Flatt 2005-11-23 20:48:06 +00:00
parent b8e937ac1f
commit 08b681655c
2 changed files with 81 additions and 29 deletions

View File

@ -234,13 +234,7 @@
# define USE_TIMEZONE_VAR # define USE_TIMEZONE_VAR
# include <linux/version.h> # define MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT
# if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,20)
# define MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT
# define MZ_TCP_LISTEN_IPV4_DEFAULT
# else
# define MZ_TCP_LISTEN_IPV4_ONLY
# endif
# define FLAGS_ALREADY_SET # define FLAGS_ALREADY_SET
@ -1023,18 +1017,18 @@
/* MZ_BINARY is combinaed with other flags in all calls to open(); /* MZ_BINARY is combinaed with other flags in all calls to open();
it can be defined to O_BINARY in Cygwin, for example. */ it can be defined to O_BINARY in Cygwin, for example. */
/* MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT uses IPV6_V6ONLY for IPv6 listeners, /* MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT uses IPV6_V6ONLY for IPv6
which means that the listener accepts only IPv6 connections. This is listeners when the same listener has an IPv4 address, which means
used with Linux, for example, because a port cannot have both an that the IpV6 listener accepts only IPv6 connections. This is used
IPv4 and IPv6 listener if the IPv6 one doesn't use IPV6_V6ONLY. */ with Linux, for example, because a port cannot have both an IPv4
and IPv6 listener if the IPv6 one doesn't use IPV6_V6ONLY. (The
two listeners might be for different interfaces, in which case
IPV6_V6ONLY is not necessary, but we must err on the side of being
too restrictive. If IPV6_V6ONLY is not #defined or if setting the
option doesn't work, then the IPv6 addresses are silently ignored
when creating the listener (but only where there is at least once
IPv4 address). */
/* MZ_TCP_LISTEN_IPV4_DEFAULT creates an IPv4 listener, only, when no
hostname is specified. */
/* MZ_TCP_LISTEN_IPV4_ONLY ignores any IPv6 addresses for an listener
hostname. This is used for Linux versions that do not support
IPV6_V6ONLY, which is needed to support both IPv6 and IPv4
listeners. */
/***********************/ /***********************/
/* Threads & Signals */ /* Threads & Signals */

View File

@ -1753,6 +1753,9 @@ tcp_listen(int argc, Scheme_Object *argv[])
unsigned short id, origid; unsigned short id, origid;
int backlog, errid; int backlog, errid;
int reuse = 0; int reuse = 0;
#ifdef MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT
int no_ipv6 = 0;
#endif
const char *address; const char *address;
if (!CHECK_PORT_ID(argv[0])) if (!CHECK_PORT_ID(argv[0]))
@ -1791,37 +1794,82 @@ tcp_listen(int argc, Scheme_Object *argv[])
id = origid; id = origid;
#endif #endif
retry:
{ {
GC_CAN_IGNORE struct addrinfo *tcp_listen_addr, *addr; GC_CAN_IGNORE struct addrinfo *tcp_listen_addr, *addr;
int err, count = 0, pos = 0, i; int err, count = 0, pos = 0, i;
listener_t *l = NULL; listener_t *l = NULL;
#ifdef MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT
int any_v4 = 0, any_v6 = 0;
#endif
tcp_listen_addr = scheme_get_host_address(address, id, &err, tcp_listen_addr = scheme_get_host_address(address, id, &err,
#ifdef MZ_TCP_LISTEN_IPV4_ONLY #ifdef MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT
MZ_PF_INET, no_ipv6 ? MZ_PF_INET : -1,
#else #else
# ifdef MZ_TCP_LISTEN_IPV4_DEFAULT
!address ? MZ_PF_INET : -1,
# else
-1, -1,
# endif
#endif #endif
1, 1); 1, 1);
for (addr = tcp_listen_addr; addr; addr = addr->ai_next) { for (addr = tcp_listen_addr; addr; addr = addr->ai_next) {
#ifdef MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT
if (addr->ai_family == MZ_PF_INET)
any_v4 = 1;
else if (addr->ai_family == PF_INET6)
any_v6 = 1;
#endif
count++; count++;
} }
if (tcp_listen_addr) { if (tcp_listen_addr) {
tcp_t s; tcp_t s;
#ifdef MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT
/* Try IPv6 listeners first, so we can retry and use just IPv4 if
IPv6 doesn't work right. */
int v6_loop = (any_v6 && any_v4), skip_v6 = 0;
#endif
errid = 0; errid = 0;
for (addr = tcp_listen_addr; addr; addr = addr->ai_next) { for (addr = tcp_listen_addr; addr; ) {
s = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
#ifdef MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT #ifdef MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT
if (addr->ai_family == PF_INET6) { if ((v6_loop && (addr->ai_family != PF_INET6))
int on = 1; || (skip_v6 && (addr->ai_family == PF_INET6))) {
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); addr = addr->ai_next;
continue;
}
#endif
s = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
#ifdef MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT
if (s == INVALID_SOCKET) {
/* Maybe it failed because IPv6 is not available: */
if ((addr->ai_family == PF_INET6) && (errno == EAFNOSUPPORT)) {
if (any_v4 && !pos) {
/* Maybe we can make it work with just IPv4. Try again. */
no_ipv6 = 1;
freeaddrinfo(tcp_listen_addr);
goto retry;
}
}
}
if (s != INVALID_SOCKET) {
if (any_v4 && (addr->ai_family == PF_INET6)) {
int ok;
# ifdef IPV6_V6ONLY
int on = 1;
ok = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
# else
ok = -1;
# endif
if (ok && !pos) {
/* IPV6_V6ONLY doesn't work */
no_ipv6 = 1;
freeaddrinfo(tcp_listen_addr);
goto retry;
}
}
} }
#endif #endif
@ -1877,6 +1925,16 @@ tcp_listen(int argc, Scheme_Object *argv[])
errid = SOCK_ERRNO(); errid = SOCK_ERRNO();
break; break;
} }
addr = addr->ai_next;
#ifdef MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT
if (!addr && v6_loop) {
v6_loop = 0;
skip_v6 = 1;
addr = tcp_listen_addr;
}
#endif
} }
for (i = 0; i < pos; i++) { for (i = 0; i < pos; i++) {