From 08b681655c9a95d3346fb9debd0b233ccdac6f0e Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Wed, 23 Nov 2005 20:48:06 +0000 Subject: [PATCH] another attempt to get Linux and IPv6 right svn: r1386 --- src/mzscheme/sconfig.h | 30 ++++++-------- src/mzscheme/src/network.c | 80 ++++++++++++++++++++++++++++++++------ 2 files changed, 81 insertions(+), 29 deletions(-) diff --git a/src/mzscheme/sconfig.h b/src/mzscheme/sconfig.h index 0f1f0a65fd..a18c45b27a 100644 --- a/src/mzscheme/sconfig.h +++ b/src/mzscheme/sconfig.h @@ -234,13 +234,7 @@ # define USE_TIMEZONE_VAR -# include -# 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 MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT # define FLAGS_ALREADY_SET @@ -1023,18 +1017,18 @@ /* MZ_BINARY is combinaed with other flags in all calls to open(); it can be defined to O_BINARY in Cygwin, for example. */ - /* MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT uses IPV6_V6ONLY for IPv6 listeners, - which means that the listener accepts only IPv6 connections. This is - used with Linux, for example, because a port cannot have both an - IPv4 and IPv6 listener if the IPv6 one doesn't use IPV6_V6ONLY. */ + /* MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT uses IPV6_V6ONLY for IPv6 + listeners when the same listener has an IPv4 address, which means + that the IpV6 listener accepts only IPv6 connections. This is used + 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 */ diff --git a/src/mzscheme/src/network.c b/src/mzscheme/src/network.c index c6f5b6da18..054691b661 100644 --- a/src/mzscheme/src/network.c +++ b/src/mzscheme/src/network.c @@ -1753,6 +1753,9 @@ tcp_listen(int argc, Scheme_Object *argv[]) unsigned short id, origid; int backlog, errid; int reuse = 0; +#ifdef MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT + int no_ipv6 = 0; +#endif const char *address; if (!CHECK_PORT_ID(argv[0])) @@ -1791,37 +1794,82 @@ tcp_listen(int argc, Scheme_Object *argv[]) id = origid; #endif + retry: + { GC_CAN_IGNORE struct addrinfo *tcp_listen_addr, *addr; int err, count = 0, pos = 0, i; 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, -#ifdef MZ_TCP_LISTEN_IPV4_ONLY - MZ_PF_INET, +#ifdef MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT + no_ipv6 ? MZ_PF_INET : -1, #else -# ifdef MZ_TCP_LISTEN_IPV4_DEFAULT - !address ? MZ_PF_INET : -1, -# else -1, -# endif #endif 1, 1); 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++; } if (tcp_listen_addr) { 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; - for (addr = tcp_listen_addr; addr; addr = addr->ai_next) { - s = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + for (addr = tcp_listen_addr; addr; ) { #ifdef MZ_TCP_LISTEN_IPV6_ONLY_SOCKOPT - if (addr->ai_family == PF_INET6) { - int on = 1; - setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); + if ((v6_loop && (addr->ai_family != PF_INET6)) + || (skip_v6 && (addr->ai_family == PF_INET6))) { + 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 @@ -1877,6 +1925,16 @@ tcp_listen(int argc, Scheme_Object *argv[]) errid = SOCK_ERRNO(); 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++) {