Skip to content

Commit

Permalink
Rework the listening IP/interface selection code
Browse files Browse the repository at this point in the history
Closes #11561
  • Loading branch information
sledgehammer999 committed Dec 11, 2019
1 parent d051519 commit 8200ef6
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 162 deletions.
240 changes: 123 additions & 117 deletions src/base/bittorrent/session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,6 @@ Session::Session(QObject *parent)
, m_networkInterface(BITTORRENT_SESSION_KEY("Interface"))
, m_networkInterfaceName(BITTORRENT_SESSION_KEY("InterfaceName"))
, m_networkInterfaceAddress(BITTORRENT_SESSION_KEY("InterfaceAddress"))
, m_isIPv6Enabled(BITTORRENT_SESSION_KEY("IPv6Enabled"), false)
, m_encryption(BITTORRENT_SESSION_KEY("Encryption"), 0)
, m_isProxyPeerConnectionsEnabled(BITTORRENT_SESSION_KEY("ProxyPeerConnections"), false)
, m_chokingAlgorithm(BITTORRENT_SESSION_KEY("ChokingAlgorithm"), ChokingAlgorithm::FixedSlots
Expand Down Expand Up @@ -1244,68 +1243,7 @@ void Session::loadLTSettings(lt::settings_pack &settingsPack)
// It will not take affect until the listen_interfaces settings is updated
settingsPack.set_int(lt::settings_pack::listen_queue_size, socketBacklogSize());

#ifdef Q_OS_WIN
QString chosenIP;
#endif
if (!m_listenInterfaceConfigured) {
const int port = useRandomPort() ? 0 : this->port();
if (port > 0) // user specified port
settingsPack.set_int(lt::settings_pack::max_retry_port_bind, 0);

for (const QString &ip : asConst(getListeningIPs())) {
if (ip.isEmpty()) {
const QString anyIP = QHostAddress(QHostAddress::AnyIPv4).toString();
const std::string endpoint = anyIP.toStdString() + ':' + std::to_string(port);
settingsPack.set_str(lt::settings_pack::listen_interfaces, endpoint);
LogMsg(tr("Trying to listen on IP: %1, port: %2"
, "e.g: Trying to listen on IP: 192.168.0.1, port: 6881")
.arg(anyIP, QString::number(port))
, Log::INFO);
break;
}

lt::error_code ec;
const lt::address addr = lt::address::from_string(ip.toStdString(), ec);
if (!ec) {
const std::string endpoint = (addr.is_v6()
? ('[' + addr.to_string() + ']')
: addr.to_string())
+ ':' + std::to_string(port);
settingsPack.set_str(lt::settings_pack::listen_interfaces, endpoint);
LogMsg(tr("Trying to listen on IP: %1, port: %2"
, "e.g: Trying to listen on IP: 192.168.0.1, port: 6881")
.arg(ip, QString::number(port))
, Log::INFO);
#ifdef Q_OS_WIN
chosenIP = ip;
#endif
break;
}
}

#ifdef Q_OS_WIN
// On Vista+ versions and after Qt 5.5 QNetworkInterface::name() returns
// the interface's LUID and not the GUID.
// Libtorrent expects GUIDs for the 'outgoing_interfaces' setting.
const QString netInterface = networkInterface();
if (!netInterface.isEmpty()) {
const QString guid = convertIfaceNameToGuid(netInterface);
if (!guid.isEmpty()) {
settingsPack.set_str(lt::settings_pack::outgoing_interfaces, guid.toStdString());
}
else {
settingsPack.set_str(lt::settings_pack::outgoing_interfaces, chosenIP.toStdString());
LogMsg(tr("Could not get GUID of configured network interface. Binding to IP: %1").arg(chosenIP)
, Log::WARNING);
}
}
#else
settingsPack.set_str(lt::settings_pack::outgoing_interfaces, networkInterface().toStdString());
#endif // Q_OS_WIN

m_listenInterfaceConfigured = true;
}

configureNetworkInterfaces(settingsPack);
applyBandwidthLimits(settingsPack);

// The most secure, rc4 only so that all streams are encrypted
Expand Down Expand Up @@ -1496,6 +1434,75 @@ void Session::loadLTSettings(lt::settings_pack &settingsPack)
}
}

void Session::configureNetworkInterfaces(lt::settings_pack &settingsPack)
{
if (m_listenInterfaceConfigured)
return;

const int port = useRandomPort() ? 0 : this->port();
if (port > 0) // user specified port
settingsPack.set_int(lt::settings_pack::max_retry_port_bind, 0);

QStringList endpoints;
QStringList outgoingInterfaces;
const QString portString = ':' + QString::number(port);

for (const QString &ip : asConst(getListeningIPs())) {
const QHostAddress addr {ip};
if (!addr.isNull()) {
endpoints << ((addr.protocol() == QAbstractSocket::IPv6Protocol)
? ('[' + Utils::Net::canonicalIPv6Addr(addr).toString() + ']')
: addr.toString())
+ portString;
}
else {
// ip holds an interface name
#ifdef Q_OS_WIN
// On Vista+ versions and after Qt 5.5 QNetworkInterface::name() returns
// the interface's LUID and not the GUID.
// Libtorrent expects GUIDs for the 'listen_interfaces' setting.
const QString guid = convertIfaceNameToGuid(ip);
if (!guid.isEmpty()) {
endpoints << (guid + portString);
outgoingInterfaces << guid;
}
else {
LogMsg(tr("Could not get GUID of network interface: %1").arg(ip) , Log::WARNING);
}
#else
endpoints << (ip + portString);
outgoingInterfaces << ip;
#endif
}
}

if (outgoingInterfaces.isEmpty()) {
#ifdef Q_OS_WIN
// On Vista+ versions and after Qt 5.5 QNetworkInterface::name() returns
// the interface's LUID and not the GUID.
// Libtorrent expects GUIDs for the 'outgoing_interfaces' setting.
const QString netInterface = networkInterface();
if (!netInterface.isEmpty()) {
const QString guid = convertIfaceNameToGuid(netInterface);
if (!guid.isEmpty())
outgoingInterfaces << guid;
else
LogMsg(tr("Could not get GUID of network interface: %1").arg(netInterface) , Log::WARNING);
}
#else
outgoingInterfaces << networkInterface();
#endif // Q_OS_WIN
}

const QString finalEndpoints = endpoints.join(',');
settingsPack.set_str(lt::settings_pack::listen_interfaces, finalEndpoints.toStdString());
LogMsg(tr("Trying to listen on: %1", "e.g: Trying to listen on: 192.168.0.1:6881")
.arg(finalEndpoints), Log::INFO);

settingsPack.set_str(lt::settings_pack::outgoing_interfaces, outgoingInterfaces.join(',').toStdString());
m_listenInterfaceConfigured = true;
}

void Session::configurePeerClasses()
{
lt::ip_filter f;
Expand Down Expand Up @@ -2416,22 +2423,50 @@ QStringList Session::getListeningIPs() const

const QString ifaceName = networkInterface();
const QString ifaceAddr = networkInterfaceAddress();
const bool listenIPv6 = isIPv6Enabled();
const QHostAddress configuredAddr(ifaceAddr);
const bool allIPv4 = (ifaceAddr == QLatin1String("0.0.0.0")); // Means All IPv4 addresses
const bool allIPv6 = (ifaceAddr == QLatin1String("::")); // Means All IPv6 addresses

if (!ifaceAddr.isEmpty()) {
QHostAddress addr(ifaceAddr);
if (addr.isNull()) {
LogMsg(tr("Configured network interface address %1 isn't valid.", "Configured network interface address 124.5.1568.1 isn't valid.").arg(ifaceAddr), Log::CRITICAL);
IPs.append("127.0.0.1"); // Force listening to localhost and avoid accidental connection that will expose user data.
return IPs;
}
if (!ifaceAddr.isEmpty() && !allIPv4 && !allIPv6 && configuredAddr.isNull()) {
LogMsg(tr("Configured network interface address %1 isn't valid.", "Configured network interface address 124.5.158.1 isn't valid.").arg(ifaceAddr), Log::CRITICAL);
IPs.append("127.0.0.1"); // Force listening to localhost and avoid accidental connection that will expose user data.
return IPs;
}

if (ifaceName.isEmpty()) {
if (!ifaceAddr.isEmpty())
IPs.append(ifaceAddr);
else
IPs.append(QString());
if (ifaceAddr.isEmpty())
return {QLatin1String("0.0.0.0"), QLatin1String("::")}; // Indicates all interfaces + all addresses (aka default)

if (allIPv4)
return {QLatin1String("0.0.0.0")};

if (allIPv6)
return {QLatin1String("::")};
}

const auto checkAndAddIP = [allIPv4, allIPv6, &IPs](const QHostAddress &addr, const QHostAddress &match)
{
if ((allIPv4 && (addr.protocol() != QAbstractSocket::IPv4Protocol))
|| (allIPv6 && (addr.protocol() != QAbstractSocket::IPv6Protocol)))
return;

if ((match == addr) || allIPv4 || allIPv6)
IPs.append(addr.toString());
};

if (ifaceName.isEmpty()) {
const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
for (const auto &addr : addresses)
checkAndAddIP(addr, configuredAddr);

// At this point ifaceAddr was non-empty
// If IPs.isEmpty() it means the configured Address was not found
if (IPs.isEmpty()) {
LogMsg(tr("Can't find the configured address '%1' to listen on"
, "Can't find the configured address '192.168.1.3' to listen on")
.arg(ifaceAddr), Log::CRITICAL);
IPs.append("127.0.0.1"); // Force listening to localhost and avoid accidental connection that will expose user data.
}

return IPs;
}
Expand All @@ -2445,40 +2480,24 @@ QStringList Session::getListeningIPs() const
return IPs;
}

if (ifaceAddr.isEmpty()) {
IPs.append(ifaceName);
return IPs; // On Windows calling code converts it to GUID
}

const QList<QNetworkAddressEntry> addresses = networkIFace.addressEntries();
qDebug("This network interface has %d IP addresses", addresses.size());
QHostAddress ip;
QString ipString;
QAbstractSocket::NetworkLayerProtocol protocol;
for (const QNetworkAddressEntry &entry : addresses) {
ip = entry.ip();
ipString = ip.toString();
protocol = ip.protocol();
Q_ASSERT((protocol == QAbstractSocket::IPv4Protocol) || (protocol == QAbstractSocket::IPv6Protocol));
if ((!listenIPv6 && (protocol == QAbstractSocket::IPv6Protocol))
|| (listenIPv6 && (protocol == QAbstractSocket::IPv4Protocol)))
continue;

// If an iface address has been defined to only allow ip's that match it to go through
if (!ifaceAddr.isEmpty()) {
if (ifaceAddr == ipString) {
IPs.append(ipString);
break;
}
}
else {
IPs.append(ipString);
}
}
for (const QNetworkAddressEntry &entry : addresses)
checkAndAddIP(entry.ip(), configuredAddr);

// Make sure there is at least one IP
// At this point there was a valid network interface, with no suitable IP.
if (IPs.size() == 0) {
LogMsg(tr("qBittorrent didn't find an %1 local address to listen on"
, "qBittorrent didn't find an IPv4 local address to listen on")
.arg(listenIPv6 ? "IPv6" : "IPv4"), Log::CRITICAL);
// At this point there was an explicit interface and an explicit address set
// and the address should have been found
if (IPs.isEmpty()) {
LogMsg(tr("Can't find the configured address '%1' to listen on"
, "Can't find the configured address '192.168.1.3' to listen on")
.arg(ifaceAddr), Log::CRITICAL);
IPs.append("127.0.0.1"); // Force listening to localhost and avoid accidental connection that will expose user data.
return IPs;
}

return IPs;
Expand Down Expand Up @@ -2734,19 +2753,6 @@ void Session::setNetworkInterfaceAddress(const QString &address)
}
}

bool Session::isIPv6Enabled() const
{
return m_isIPv6Enabled;
}

void Session::setIPv6Enabled(const bool enabled)
{
if (enabled != isIPv6Enabled()) {
m_isIPv6Enabled = enabled;
configureListeningInterface();
}
}

int Session::encryption() const
{
return m_encryption;
Expand Down
4 changes: 1 addition & 3 deletions src/base/bittorrent/session.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,6 @@ namespace BitTorrent
void setNetworkInterfaceName(const QString &name);
QString networkInterfaceAddress() const;
void setNetworkInterfaceAddress(const QString &address);
bool isIPv6Enabled() const;
void setIPv6Enabled(bool enabled);
int encryption() const;
void setEncryption(int state);
bool isProxyPeerConnectionsEnabled() const;
Expand Down Expand Up @@ -532,6 +530,7 @@ namespace BitTorrent
void configureComponents();
void initializeNativeSession();
void loadLTSettings(lt::settings_pack &settingsPack);
void configureNetworkInterfaces(lt::settings_pack &settingsPack);
void configurePeerClasses();
void adjustLimits(lt::settings_pack &settingsPack);
void applyBandwidthLimits(lt::settings_pack &settingsPack) const;
Expand Down Expand Up @@ -662,7 +661,6 @@ namespace BitTorrent
CachedSettingValue<QString> m_networkInterface;
CachedSettingValue<QString> m_networkInterfaceName;
CachedSettingValue<QString> m_networkInterfaceAddress;
CachedSettingValue<bool> m_isIPv6Enabled;
CachedSettingValue<int> m_encryption;
CachedSettingValue<bool> m_isProxyPeerConnectionsEnabled;
CachedSettingValue<ChokingAlgorithm> m_chokingAlgorithm;
Expand Down
1 change: 0 additions & 1 deletion src/base/settingsstorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ namespace
{"BitTorrent/Session/BandwidthSchedulerEnabled", "Preferences/Scheduler/Enabled"},
{"BitTorrent/Session/Port", "Preferences/Connection/PortRangeMin"},
{"BitTorrent/Session/UseRandomPort", "Preferences/General/UseRandomPort"},
{"BitTorrent/Session/IPv6Enabled", "Preferences/Connection/InterfaceListenIPv6"},
{"BitTorrent/Session/Interface", "Preferences/Connection/Interface"},
{"BitTorrent/Session/InterfaceName", "Preferences/Connection/InterfaceName"},
{"BitTorrent/Session/InterfaceAddress", "Preferences/Connection/InterfaceAddress"},
Expand Down
32 changes: 32 additions & 0 deletions src/base/utils/net.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

#include "net.h"

#include <QNetworkInterface>
#include <QSslCertificate>
#include <QSslKey>
#include <QString>
Expand Down Expand Up @@ -91,6 +92,37 @@ namespace Utils
return subnet.first.toString() + '/' + QString::number(subnet.second);
}

QHostAddress canonicalIPv6Addr(const QHostAddress &addr)
{
// Link-local IPv6 textual address always contains a scope id (or zone index)
// The scope id is appended to the IPv6 address using the '%' character
// The scope id can be either a interface name or an interface number
// Examples:
// fe80::1%ethernet_17
// fe80::1%13
// The interface number is the mandatory supported way
// Unfortunately for us QHostAddress::toString() outputs (at least on Windows)
// the interface name, and libtorrent/boost.asio only support an interface number
// as scope id. Furthermore, QHostAddress doesn't have any convenient method to
// affect this, so we jump through hoops here.
if (addr.protocol() != QAbstractSocket::IPv6Protocol)
return QHostAddress{addr.toIPv6Address()};

// QHostAddress::setScopeId(addr.scopeId()); // Even though the docs say that setScopeId
// will convert a name to a number, this doesn't happen. Probably a Qt bug.
const QString scopeIdTxt = addr.scopeId();
if (scopeIdTxt.isEmpty())
return addr;

const int id = QNetworkInterface::interfaceIndexFromName(scopeIdTxt);
if (id == 0) // This failure might mean that the scope id was already a number
return addr;

QHostAddress canonical(addr.toIPv6Address());
canonical.setScopeId(QString::number(id));
return canonical;
}

QList<QSslCertificate> loadSSLCertificate(const QByteArray &data)
{
const QList<QSslCertificate> certs {QSslCertificate::fromData(data)};
Expand Down
1 change: 1 addition & 0 deletions src/base/utils/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ namespace Utils
bool isLoopbackAddress(const QHostAddress &addr);
bool isIPInRange(const QHostAddress &addr, const QVector<Subnet> &subnets);
QString subnetToString(const Subnet &subnet);
QHostAddress canonicalIPv6Addr(const QHostAddress &addr);

const int MAX_SSL_FILE_SIZE = 1024 * 1024;
QList<QSslCertificate> loadSSLCertificate(const QByteArray &data);
Expand Down
Loading

0 comments on commit 8200ef6

Please sign in to comment.