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.
This commit is contained in:
Stefan Hacker 2015-06-04 01:46:35 +02:00 committed by Mikkel Krautz
parent 9a426b1df7
commit e934c1e648
4 changed files with 54 additions and 24 deletions

View File

@ -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
}
}

View File

@ -138,9 +138,6 @@ public:
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 {

View File

@ -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 {

View File

@ -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)