Commit af205ab2 authored by Kai Koehne's avatar Kai Koehne Committed by hjk

Add TcpPortsGatherer to utils

Can be used to check (non-intrusively) which TCP ports are still available.

Change-Id: I61300f6b7215cb2c0e4a3e6135de305cfd38b5a9
Reviewed-by: default avatarChristian Kandeler <christian.kandeler@nokia.com>
Reviewed-by: default avatarhjk <qthjk@ovi.com>
parent f7e127f7
/**************************************************************************
**
** 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
/**************************************************************************
**
** 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
......@@ -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 \
......
......@@ -20,3 +20,5 @@ SOURCES += \
proxyaction.cpp
win32: LIBS += -lUser32
# PortsGatherer
win32: LIBS += -liphlpapi -lWs2_32
......@@ -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",
......
......@@ -5,7 +5,8 @@ cplusplus-frontend \
fakevim \
debugger \
preprocessor \
subdir_proparser
subdir_proparser \
utils
unix {
# Uses popen
......
#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;
}
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
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"] }
}
TEMPLATE = subdirs
SUBDIRS = tcpportsgatherer
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment