Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework the listening IP/interface selection code #11592

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Chocobo1 marked this conversation as resolved.
Show resolved Hide resolved
// 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()};
Chocobo1 marked this conversation as resolved.
Show resolved Hide resolved

// 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