From e934c1e6483031a0ae006e71efc2abec8eef1caf Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Thu, 4 Jun 2015 01:46:35 +0200 Subject: [PATCH] Fix dual-stack UDP on Windows and improve dual-stack detection This patch fixes dual-stack UDP on Windows as well as for unsual Linux configurations by ensuring the UDP sockets we create inherit their IPV6_V6ONLY sockopt from their corresponding TCP socket. Previously on systems where IPV6_V6ONLY was enabled by default (e.g. Windows) we would incorrectly create an IPv6 only socket even though our TCP socket and the system are dual-stacked. The dual-stack detection moved from Meta.h to Server.h into the SslServer class where it is a better fit. Also modified the function to instead of querying the value of IPV6_V6ONLY on a test socket it now tries to actively disable it. While previously dual stack support was only detected if it was the default configuration for that system, it should now be detected in all cases. The function also now performs the same check on Windows socket where the default actually is to have IPV6_V6ONLY enabled. --- src/murmur/Meta.cpp | 23 ++------------------ src/murmur/Meta.h | 3 --- src/murmur/Server.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++ src/murmur/Server.h | 3 +++ 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/murmur/Meta.cpp b/src/murmur/Meta.cpp index 2369ee25e..d22ac7c6c 100644 --- a/src/murmur/Meta.cpp +++ b/src/murmur/Meta.cpp @@ -125,25 +125,6 @@ T MetaParams::typeCheckedFromSettings(const QString &name, const T &defaultValue return cfgVariable.value(); } -bool MetaParams::hasDualStackSupport() { -#ifdef Q_OS_UNIX - int s = ::socket(AF_INET6, SOCK_STREAM, 0); - if (s != -1) { - int ipv6only = 0; - socklen_t optlen = sizeof(ipv6only); - if (getsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, &optlen) == 0) { - if (ipv6only == 0) - return true; - } - close(s); - } - return false; -#else - // Assume dual-stacked for non-unix systems - return true; -#endif -} - void MetaParams::read(QString fname) { if (fname.isEmpty()) { QStringList datapaths; @@ -262,7 +243,7 @@ void MetaParams::read(QString fname) { #if QT_VERSION >= 0x050000 if (hasipv6) { - if (hasDualStackSupport() && hasipv4) { + if (SslServer::hasDualStackSupport() && hasipv4) { qlBind << QHostAddress(QHostAddress::Any); hasipv4 = false; // No need to add a separate ipv4 socket } else { @@ -281,7 +262,7 @@ void MetaParams::read(QString fname) { if (hasipv6) { qlBind << QHostAddress(QHostAddress::AnyIPv6); - if (hasDualStackSupport() && hasipv4) { + if (SslServer::hasDualStackSupport() && hasipv4) { hasipv4 = false; // No need to add a separate ipv4 socket } } diff --git a/src/murmur/Meta.h b/src/murmur/Meta.h index a3f67bb7f..884e86038 100644 --- a/src/murmur/Meta.h +++ b/src/murmur/Meta.h @@ -138,9 +138,6 @@ public: private: template T typeCheckedFromSettings(const QString &name, const T &variable); - - /// Checks whether the AF_INET6 socket has dual-stack support - static bool hasDualStackSupport(); }; class Meta : public QObject { diff --git a/src/murmur/Server.cpp b/src/murmur/Server.cpp index 7485c88a4..10e7035b6 100644 --- a/src/murmur/Server.cpp +++ b/src/murmur/Server.cpp @@ -73,6 +73,41 @@ void ExecEvent::execute() { SslServer::SslServer(QObject *p) : QTcpServer(p) { } +bool SslServer::hasDualStackSupport() { + // Create a AF_INET6 socket and try to switch off IPV6_V6ONLY. This + // should only fail if the system does not support dual-stack mode + // for this socket type. + + bool result = false; +#ifdef Q_OS_UNIX + int s = ::socket(AF_INET6, SOCK_STREAM, 0); +#else + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(2, 2); + if (WSAStartup(wVersionRequested, &wsaData) != 0) { + // Seems like we won't be doing any network stuff anyways + return false; + } + + SOCKET s = ::WSASocket(AF_INET6, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); +#endif + + if (s != -1) { // Equals INVALID_SOCKET + const int ipv6only = 0; + if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&ipv6only, sizeof(ipv6only)) == 0) { + result = true; + } +#ifdef Q_OS_UNIX + ::close(s); + } +#else + closesocket(s); + } + WSACleanup(); +#endif + return result; +} + #if QT_VERSION >= 0x050000 void SslServer::incomingConnection(qintptr v) { #else @@ -166,6 +201,20 @@ Server::Server(int snum, QObject *p) : QThread(p) { bValid = false; return; } else { + if (addr.ss_family == AF_INET6) { + // Copy IPV6_V6ONLY attribute from tcp socket, it defaults to nonzero on Windows + // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms738574%28v=vs.85%29.aspx + // This will fail for WindowsXP which is ok. Our TCP code will have split that up + // into two sockets. + int ipv6only = 0; + socklen_t optlen = sizeof(ipv6only); + if (::getsockopt(tcpsock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&ipv6only, &optlen) == 0) { + if (::setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&ipv6only, optlen) == SOCKET_ERROR) { + log(QString("Failed to copy IPV6_V6ONLY socket attribute from tcp to udp socket")); + } + } + } + if (::bind(sock, reinterpret_cast(&addr), len) == SOCKET_ERROR) { log(QString("Failed to bind UDP Socket to %1").arg(addressToString(ss->serverAddress(), usPort))); } else { diff --git a/src/murmur/Server.h b/src/murmur/Server.h index 1fb9806b6..19e26706e 100644 --- a/src/murmur/Server.h +++ b/src/murmur/Server.h @@ -99,6 +99,9 @@ class SslServer : public QTcpServer { public: QSslSocket *nextPendingSSLConnection(); SslServer(QObject *parent = NULL); + + /// Checks whether the AF_INET6 socket on this system has dual-stack support. + static bool hasDualStackSupport(); }; #define EXEC_QEVENT (QEvent::User + 959)