Skip to content
Permalink
Browse files

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.
  • Loading branch information...
hacst authored and mkrautz committed Jun 3, 2015
1 parent 9a426b1 commit e934c1e6483031a0ae006e71efc2abec8eef1caf
Showing with 54 additions and 24 deletions.
  1. +2 −21 src/murmur/Meta.cpp
  2. +0 −3 src/murmur/Meta.h
  3. +49 −0 src/murmur/Server.cpp
  4. +3 −0 src/murmur/Server.h
@@ -125,25 +125,6 @@ T MetaParams::typeCheckedFromSettings(const QString &name, const T &defaultValue
return cfgVariable.value<T>();
}

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
}
}
@@ -138,9 +138,6 @@ class MetaParams {
private:
template <class T>
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 {
@@ -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<sockaddr *>(&addr), len) == SOCKET_ERROR) {
log(QString("Failed to bind UDP Socket to %1").arg(addressToString(ss->serverAddress(), usPort)));
} else {
@@ -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)

0 comments on commit e934c1e

Please sign in to comment.
You can’t perform that action at this time.