diff --git a/src/libs/utils/tcpportsgatherer.cpp b/src/libs/utils/tcpportsgatherer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2a89f17ef2b46b606e7c9ead3c7f66ec5327bf06 --- /dev/null +++ b/src/libs/utils/tcpportsgatherer.cpp @@ -0,0 +1,260 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "tcpportsgatherer.h" +#include "qtcassert.h" +#include <QFile> +#include <QStringList> +#include <QProcess> + +#ifdef Q_OS_WIN +#include <winsock2.h> +#include <ws2tcpip.h> +#include <iphlpapi.h> +#endif + +namespace Utils { +namespace Internal { + +class TcpPortsGathererPrivate +{ +public: + TcpPortsGathererPrivate(TcpPortsGatherer::ProtocolFlags protocolFlags) + : protocolFlags(protocolFlags) {} + + TcpPortsGatherer::ProtocolFlags protocolFlags; + PortList usedPorts; + + void updateWin(TcpPortsGatherer::ProtocolFlags protocolFlags); + void updateLinux(TcpPortsGatherer::ProtocolFlags protocolFlags); + void updateNetstat(TcpPortsGatherer::ProtocolFlags protocolFlags); +}; + +#ifdef Q_OS_WIN +template <typename Table, ULONG (__stdcall *Func)(Table*, PULONG, BOOL) > +QSet<int> usedTcpPorts() +{ + Table *table = static_cast<Table*>(malloc(sizeof(Table))); + DWORD dwSize = sizeof(Table); + + // get the necessary size into the dwSize variable + DWORD dwRetVal = Func(table, &dwSize, false); + if (dwRetVal == ERROR_INSUFFICIENT_BUFFER) { + free(table); + table = static_cast<Table*>(malloc(dwSize)); + } + + // get the actual data + QSet<int> result; + dwRetVal = Func(table, &dwSize, false); + if (dwRetVal == NO_ERROR) { + for (quint32 i = 0; i < table->dwNumEntries; i++) { + quint16 port = ntohs(table->table[i].dwLocalPort); + if (!result.contains(port)) + result.insert(port); + } + } else { + qWarning() << "TcpPortsGatherer: GetTcpTable failed with" << dwRetVal; + } + + free(table); + return result; +} +#endif + +void TcpPortsGathererPrivate::updateWin(TcpPortsGatherer::ProtocolFlags protocolFlags) +{ +#ifdef Q_OS_WIN + QSet<int> ports; + + if (protocolFlags & TcpPortsGatherer::IPv4Protocol) + ports.unite(usedTcpPorts<MIB_TCPTABLE, GetTcpTable>()); + if (protocolFlags & TcpPortsGatherer::IPv6Protocol) + ports.unite(usedTcpPorts<MIB_TCP6TABLE, GetTcp6Table>()); + + foreach (int port, ports) { + if (!usedPorts.contains(port)) + usedPorts.addPort(port); + } +#endif + Q_UNUSED(protocolFlags); +} + +void TcpPortsGathererPrivate::updateLinux(TcpPortsGatherer::ProtocolFlags protocolFlags) +{ + QStringList filePaths; + if (protocolFlags & TcpPortsGatherer::IPv4Protocol) + filePaths.append(QLatin1String("/proc/net/tcp")); + if (protocolFlags & TcpPortsGatherer::IPv6Protocol) + filePaths.append(QLatin1String("/proc/net/tcp6")); + + foreach (const QString &filePath, filePaths) { + QFile file(filePath); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + qWarning() << "TcpPortsGatherer: Cannot open file" + << filePath << ":" << file.errorString(); + continue; + } + + if (file.atEnd()) // read first line describing the output + file.readLine(); + + static QRegExp pattern(QLatin1String("^\\s*" // start of line, whitespace + "\\d+:\\s*" // integer, colon, space + "[0-9A-Fa-f]+:" // hexadecimal number (ip), colon + "([0-9A-Fa-f]+)" // hexadecimal number (port!) + )); + while (!file.atEnd()) { + QByteArray line = file.readLine(); + if (pattern.indexIn(line) != -1) { + bool isNumber; + quint16 port = pattern.cap(1).toUShort(&isNumber, 16); + QTC_ASSERT(isNumber, continue); + if (!usedPorts.contains(port)) + usedPorts.addPort(port); + } else { + qWarning() << "TcpPortsGatherer: File" << filePath << "has unexpected format."; + continue; + } + } + } +} + +// Only works with FreeBSD version of netstat like we have on Mac OS X +void TcpPortsGathererPrivate::updateNetstat(TcpPortsGatherer::ProtocolFlags protocolFlags) +{ + QStringList netstatArgs; + + netstatArgs.append(QLatin1String("-a")); // show also sockets of server processes + netstatArgs.append(QLatin1String("-n")); // show network addresses as numbers + netstatArgs.append(QLatin1String("-p")); + netstatArgs.append(QLatin1String("tcp")); + if (protocolFlags != TcpPortsGatherer::AnyIPProcol) { + netstatArgs.append(QLatin1String("-f")); // limit to address family + if (protocolFlags == TcpPortsGatherer::IPv4Protocol) + netstatArgs.append(QLatin1String("inet")); + else + netstatArgs.append(QLatin1String("inet6")); + } + + QProcess netstatProcess; + netstatProcess.start(QLatin1String("netstat"), netstatArgs); + if (!netstatProcess.waitForFinished(30000)) { + qWarning() << "TcpPortsGatherer: netstat did not return in time."; + return; + } + + QList<QByteArray> output = netstatProcess.readAllStandardOutput().split('\n'); + foreach (const QByteArray &line, output) { + static QRegExp pattern(QLatin1String("^tcp[46]+" // "tcp", followed by "4", "6", "46" + "\\s+\\d+" // whitespace, number (Recv-Q) + "\\s+\\d+" // whitespace, number (Send-Q) + "\\s+(\\S+)")); // whitespace, Local Address + if (pattern.indexIn(line) != -1) { + QString localAddress = pattern.cap(1); + + // Examples of local addresses: + // '*.56501' , '*.*' 'fe80::1%lo0.123' + int portDelimiterPos = localAddress.lastIndexOf("."); + if (portDelimiterPos == -1) + continue; + + localAddress = localAddress.mid(portDelimiterPos + 1); + bool isNumber; + quint16 port = localAddress.toUShort(&isNumber); + if (!isNumber) + continue; + + if (!usedPorts.contains(port)) + usedPorts.addPort(port); + } + } +} + +} // namespace Internal + + +/*! + \class Utils::TcpPortsGatherer + + \brief Gather the list of local TCP ports already in use. + + Query the system for the list of local TCP ports already in use. This information can be used + to select a port for use in a range. +*/ + +TcpPortsGatherer::TcpPortsGatherer(TcpPortsGatherer::ProtocolFlags protocolFlags) + : d(new Internal::TcpPortsGathererPrivate(protocolFlags)) +{ + update(); +} + +TcpPortsGatherer::~TcpPortsGatherer() +{ + delete d; +} + +void TcpPortsGatherer::update() +{ + d->usedPorts = PortList(); + +#if defined(Q_OS_WIN) + d->updateWin(d->protocolFlags); +#elif defined(Q_OS_LINUX) + d->updateLinux(d->protocolFlags); +#else + d->updateNetstat(d->protocolFlags); +#endif +} + +PortList TcpPortsGatherer::usedPorts() const +{ + return d->usedPorts; +} + +/*! + Select a port out of \a freePorts that is not yet used. + + Returns the port, or 0 if no free port is available. + */ +quint16 TcpPortsGatherer::getNextFreePort(PortList *freePorts) +{ + QTC_ASSERT(freePorts, return 0); + while (freePorts->hasMore()) { + const int port = freePorts->getNext(); + if (!d->usedPorts.contains(port)) + return port; + } + return 0; +} + +} // namespace Utils diff --git a/src/libs/utils/tcpportsgatherer.h b/src/libs/utils/tcpportsgatherer.h new file mode 100644 index 0000000000000000000000000000000000000000..4ad6e24bf2022c1ac17ef9ce64995804e6699d7c --- /dev/null +++ b/src/libs/utils/tcpportsgatherer.h @@ -0,0 +1,67 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef TCPPORTSGATHERER_H +#define TCPPORTSGATHERER_H + +#include "portlist.h" + +namespace Utils { +namespace Internal { +class TcpPortsGathererPrivate; +} + +class QTCREATOR_UTILS_EXPORT TcpPortsGatherer +{ +public: + enum NetworkLayerProtocol { + IPv4Protocol = 0x1, + IPv6Protocol = 0x2, + AnyIPProcol = IPv4Protocol | IPv6Protocol + }; + Q_DECLARE_FLAGS(ProtocolFlags, NetworkLayerProtocol) + + TcpPortsGatherer(ProtocolFlags flags); + ~TcpPortsGatherer(); + + void update(); + + PortList usedPorts() const; + quint16 getNextFreePort(PortList *port); + +private: + Internal::TcpPortsGathererPrivate *d; +}; + +} // namespace Utils + +#endif // TCPPORTSGATHERER_H diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri index ffe02726533e428c7abb99085f356a967e82b26a..a81c43cc13bbe720cf28ba18e19f6585e76ffc4d 100644 --- a/src/libs/utils/utils-lib.pri +++ b/src/libs/utils/utils-lib.pri @@ -94,7 +94,8 @@ SOURCES += $$PWD/environment.cpp \ $$PWD/persistentsettings.cpp \ $$PWD/completingtextedit.cpp \ $$PWD/json.cpp \ - $$PWD/portlist.cpp + $$PWD/portlist.cpp \ + $$PWD/tcpportsgatherer.cpp win32 { SOURCES += \ @@ -205,7 +206,8 @@ HEADERS += \ $$PWD/json.h \ $$PWD/multitask.h \ $$PWD/runextensions.h \ - $$PWD/portlist.h + $$PWD/portlist.h \ + $$PWD/tcpportsgatherer.h FORMS += $$PWD/filewizardpage.ui \ $$PWD/projectintropage.ui \ diff --git a/src/libs/utils/utils.pro b/src/libs/utils/utils.pro index 0a1888c1138b88957cb5cc0c2393f1e2f9ca2fd0..88417900406c76f525bced4847e66e58df7d6cd2 100644 --- a/src/libs/utils/utils.pro +++ b/src/libs/utils/utils.pro @@ -20,3 +20,5 @@ SOURCES += \ proxyaction.cpp win32: LIBS += -lUser32 +# PortsGatherer +win32: LIBS += -liphlpapi -lWs2_32 diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs index 64291c9c6b8f64a2d8e72320bbeb9442fd51ea63..d97e483651a977dd33e701f96846c91254498e2b 100644 --- a/src/libs/utils/utils.qbs +++ b/src/libs/utils/utils.qbs @@ -14,7 +14,7 @@ DynamicLibrary { Properties { condition: qbs.targetOS == "windows" - cpp.dynamicLibraries: ["User32.lib"] + cpp.dynamicLibraries: ["User32.lib", "iphlpapi.lib", "Ws2_32.lib"] } Depends { name: "cpp" } @@ -127,6 +127,8 @@ DynamicLibrary { "submitfieldwidget.h", "synchronousprocess.cpp", "synchronousprocess.h", + "tcpportsgatherer.cpp", + "tcpportsgatherer.h", "textfileformat.cpp", "textfileformat.h", "treewidgetcolumnstretcher.cpp", diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro index 5e4e50c7ae21cc752f4f26321cb0af1a088cc710..29f79d6c4226d38da49e0fb8456d3184f8e91569 100644 --- a/tests/manual/manual.pro +++ b/tests/manual/manual.pro @@ -5,7 +5,8 @@ cplusplus-frontend \ fakevim \ debugger \ preprocessor \ -subdir_proparser +subdir_proparser \ +utils unix { # Uses popen diff --git a/tests/manual/utils/tcpportsgatherer/main.cpp b/tests/manual/utils/tcpportsgatherer/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..df553b63bfaa8d781d3aaa0c1ea5dbe8423f36fc --- /dev/null +++ b/tests/manual/utils/tcpportsgatherer/main.cpp @@ -0,0 +1,36 @@ +#include <QCoreApplication> +#include <utils/tcpportsgatherer.h> +#include <QDebug> +#include <QStringList> + +using namespace Utils; +int main() +{ + qDebug() << "Used TCP Ports (IP4):"; + + TcpPortsGatherer ip4Ports(TcpPortsGatherer::IPv4Protocol); + qDebug() << ip4Ports.usedPorts().toString(); + + qDebug() << "Used TCP Ports (IP6):"; + TcpPortsGatherer ip6Ports(TcpPortsGatherer::IPv6Protocol); + qDebug() << ip6Ports.usedPorts().toString(); + + qDebug() << "All Used TCP Ports:"; + TcpPortsGatherer ipPorts(TcpPortsGatherer::AnyIPProcol); + qDebug() << ipPorts.usedPorts().toString(); + + qDebug() << "Getting a few ports ..."; + PortList portList = PortList::fromString("10000-10100"); + QStringList ports; + for (int i = 0; i < 10; ++i) { + quint16 port = ipPorts.getNextFreePort(&portList); + Q_ASSERT(!ipPorts.usedPorts().contains(port)); + Q_ASSERT(port >= 10000); + Q_ASSERT(port < 10100); + QString portStr = QString::number(port); + Q_ASSERT(!ports.contains(portStr)); + ports.append(QString::number(port)); + } + qDebug() << ports.join(", "); + return 0; +} diff --git a/tests/manual/utils/tcpportsgatherer/tcpportsgatherer.pro b/tests/manual/utils/tcpportsgatherer/tcpportsgatherer.pro new file mode 100644 index 0000000000000000000000000000000000000000..87d05661e95789cecdd03dd187fee3bf930db15a --- /dev/null +++ b/tests/manual/utils/tcpportsgatherer/tcpportsgatherer.pro @@ -0,0 +1,27 @@ +TEMPLATE = app +TARGET = tcpportsgatherer + +QT = core gui +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += console +CONFIG -= app_bundle + +include(../../../../qtcreator.pri) +include(../../../../src/rpath.pri) + + +INCLUDEPATH += ../../../../src/libs +UTILSDIR = ../../../../src/libs/utils + +DEFINES += QTCREATOR_UTILS_STATIC_LIB + +HEADERS += \ + $${UTILSDIR}/portlist.h \ + $${UTILSDIR}/tcpportsgatherer.h +SOURCES += \ + $${UTILSDIR}/portlist.cpp \ + $${UTILSDIR}/tcpportsgatherer.cpp + +win32:LIBS += -liphlpapi -lWs2_32 +SOURCES += main.cpp diff --git a/tests/manual/utils/tcpportsgatherer/tcpportsgatherer.qbp b/tests/manual/utils/tcpportsgatherer/tcpportsgatherer.qbp new file mode 100644 index 0000000000000000000000000000000000000000..e909add0d79e6a9657488e2648162ab8353a5eba --- /dev/null +++ b/tests/manual/utils/tcpportsgatherer/tcpportsgatherer.qbp @@ -0,0 +1,24 @@ +import qbs.base 1.0 + +Application { + name: "tcpportsgatherer" + + files: [ + "main.cpp", + "../../../../src/libs/utils/portlist.cpp", + "../../../../src/libs/utils/portlist.h", + "../../../../src/libs/utils/tcpportsgatherer.cpp", + "../../../../src/libs/utils/tcpportsgatherer.h" + ] + + cpp.includePaths: [ "../../../../src/libs" ] + cpp.defines: [ "QTCREATOR_UTILS_STATIC_LIB" ] + + Properties { + condition: qbs.targetOS == "windows" + cpp.dynamicLibraries: [ "iphlpapi.lib", "Ws2_32.lib" ] + } + + Depends { name: "cpp" } + Depends { name: "Qt"; submodules: ["gui"] } +} diff --git a/tests/manual/utils/utils.pro b/tests/manual/utils/utils.pro new file mode 100644 index 0000000000000000000000000000000000000000..a4899f90bb6c4c89cfaadc5a0a64a383f05364f9 --- /dev/null +++ b/tests/manual/utils/utils.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs + +SUBDIRS = tcpportsgatherer