qBittorrent UPnP and NAT-PMP Integration: Automatic Port Forwarding Implementation
qBittorrent implements automatic port forwarding through a layered architecture where the UI and API interact with a singleton Net::PortForwarder class that delegates to SessionImpl, which configures libtorrent to handle UPnP and NAT-PMP protocols with routers.
The qbittorrent/qBittorrent repository provides seamless router configuration via UPnP and NAT-PMP integration, eliminating manual port setup. This feature automatically requests the router to open listening ports for incoming peer connections. The implementation spans from the Qt-based desktop interface to the libtorrent networking core, offering consistent behavior across the GUI, Web UI, and REST API.
Architecture Overview
qBittorrent’s UPnP and NAT-PMP integration follows a clean, layered design that isolates router-interaction logic from the rest of the client. The architecture ensures consistent state across the desktop application, Web UI, and programmatic interfaces.
Layered Design
The system comprises five distinct layers:
-
UI (Qt) – Presents the “Use UPnP / NAT‑PMP port forwarding from my router” checkbox in
OptionsDialog(src/gui/optionsdialog.cpp) and forwards changes to the core. -
Web UI / API – Exposes the UPnP state through
AppController(src/webui/api/appcontroller.cpp), allowing remote clients to read and modify the setting via theupnpJSON key. -
PortForwarder Abstraction – Provides a singleton interface (
Net::PortForwarderinsrc/base/net/portforwarder.h) used throughout the codebase for decoupled access. -
Concrete Implementation –
PortForwarderImpl(src/base/bittorrent/portforwarderimpl.h) inherits from the abstract class and forwards requests to the BitTorrent session. -
BitTorrent Session –
SessionImpl(src/base/bittorrent/sessionimpl.cpp) applies libtorrent settings and manages individual port mappings throughadd_port_mapping()anddelete_port_mapping().
Data Flow
When a user toggles the UPnP checkbox or sends an API request:
-
OptionsDialogorAppControllercallsNet::PortForwarder::instance()->setEnabled(bool). -
PortForwarderImpl::setEnabled()stores the value inm_storeActiveand invokesstart()orstop(). -
PortForwarderImpl::start()triggersSessionImpl::enablePortMapping(), which builds alt::settings_packwithenable_upnp = trueandenable_natpmp = true, applying it to the native libtorrent session. -
When ports change,
SessionImpl::addMappedPorts()callsm_nativeSession->add_port_mapping(lt::session::tcp, port, port)for each port, storing returned handles inm_mappedPorts. -
Disabling UPnP triggers
disablePortMapping(), which clearsm_mappedPortsand sets libtorrent flags to false.
Implementation Details
The PortForwarder Singleton
The Net::PortForwarder class provides a global access point for port forwarding operations. Defined in src/base/net/portforwarder.h, it uses a singleton pattern to ensure only one instance manages router mappings:
// src/base/net/portforwarder.h
class PortForwarder : public QObject
{
Q_DISABLE_COPY_MOVE(PortForwarder)
public:
explicit PortForwarder(QObject *parent = nullptr);
~PortForwarder() override;
static PortForwarder *instance();
virtual bool isEnabled() const = 0;
virtual void setEnabled(bool enabled) = 0;
virtual void setPorts(const QString &profile, QSet<quint16> ports) = 0;
virtual void removePorts(const QString &profile) = 0;
};
The implementation in src/base/net/portforwarder.cpp manages the static instance:
// src/base/net/portforwarder.cpp
PortForwarder::PortForwarder(QObject *parent) : QObject {parent}
{
Q_ASSERT(!m_instance);
m_instance = this;
}
PortForwarder::~PortForwarder()
{
m_instance = nullptr;
}
PortForwarder *PortForwarder::instance()
{
return m_instance;
}
PortForwarder *PortForwarder::m_instance = nullptr;
Concrete Implementation in PortForwarderImpl
PortForwarderImpl (src/base/bittorrent/portforwarderimpl.cpp) bridges the abstract interface with the libtorrent session. It maintains a cache of active port profiles and delegates all protocol operations to SessionImpl:
// src/base/bittorrent/portforwarderimpl.cpp (excerpt)
PortForwarderImpl::PortForwarderImpl(BitTorrent::SessionImpl *provider, QObject *parent)
: Net::PortForwarder(parent)
, m_storeActive {u"Network/PortForwardingEnabled"_s, true}
, m_provider {provider}
{
if (isEnabled())
start();
}
void PortForwarderImpl::start()
{
m_provider->enablePortMapping();
for (const QSet<quint16> &ports : asConst(m_portProfiles))
m_provider->addMappedPorts(ports);
}
void PortForwarderImpl::stop()
{
m_provider->disablePortMapping();
}
Session-Level Mapping
The SessionImpl class in src/base/bittorrent/sessionimpl.cpp performs the actual UPnP/NAT-PMP configuration. The enablePortMapping() method activates both protocols in libtorrent:
// src/base/bittorrent/sessionimpl.cpp (excerpt)
void SessionImpl::enablePortMapping()
{
invokeAsync([this] {
if (m_isPortMappingEnabled)
return;
lt::settings_pack settingsPack;
settingsPack.set_bool(lt::settings_pack::enable_upnp, true);
settingsPack.set_bool(lt::settings_pack::enable_natpmp, true);
m_nativeSession->apply_settings(std::move(settingsPack));
m_isPortMappingEnabled = true;
LogMsg(tr("UPnP/NAT-PMP support: ON"), Log::INFO);
});
}
To create individual mappings, addMappedPorts() iterates through the port set and stores the mapping handles:
void SessionImpl::addMappedPorts(const QSet<quint16> &ports)
{
invokeAsync([this, ports] {
if (!m_isPortMappingEnabled)
return;
for (const quint16 port : ports)
if (!m_mappedPorts.contains(port))
m_mappedPorts.insert(port, m_nativeSession->add_port_mapping(lt::session::tcp, port, port));
});
}
The UPnP lease duration setting (configured in the UI as “UPnP lease duration [0: permanent lease]”) is passed to libtorrent via settings_pack::upnp_lease_duration before applying settings.
UI and API Integration
The desktop GUI wires the checkbox in OptionsDialog (src/gui/optionsdialog.cpp):
// src/gui/optionsdialog.cpp (excerpt)
m_ui->checkUPnP->setChecked(Net::PortForwarder::instance()->isEnabled());
connect(m_ui->checkUPnP, &QAbstractButton::toggled,
this, &OptionsDialog::onUPnPToggled);
void OptionsDialog::onUPnPToggled(bool checked)
{
Net::PortForwarder::instance()->setEnabled(checked);
}
The Web UI exposes the same functionality through AppController (src/webui/api/appcontroller.cpp):
// src/webui/api/appcontroller.cpp (excerpt)
data[u"upnp"_s] = Net::PortForwarder::instance()->isEnabled();
// …
if (hasKey(u"upnp"_s))
Net::PortForwarder::instance()->setEnabled(it.value().toBool());
Practical Usage Examples
Enabling Port Forwarding Programmatically
To enable automatic port forwarding in C++:
// Enable UPnP/NAT-PMP
Net::PortForwarder::instance()->setEnabled(true);
// Configure specific ports for a profile
QSet<quint16> ports;
ports << 6881 << 6882;
Net::PortForwarder::instance()->setPorts(QStringLiteral("default"), ports);
Disabling and Clearing Mappings
// Disable and remove all mappings
Net::PortForwarder::instance()->setEnabled(false);
Querying State via Web API
Retrieve the current UPnP status using curl:
# Get current status
curl -s http://localhost:8080/api/v2/app/preferences | jq .upnp
# Enable UPnP via API
curl -X POST -d "json={\"upnp\":true}" http://localhost:8080/api/v2/app/setPreferences
Summary
- qBittorrent UPnP and NAT-PMP integration relies on a singleton
Net::PortForwarderabstraction that decouples the UI from the networking core. - Implementation files:
src/base/net/portforwarder.hdefines the interface, whilesrc/base/bittorrent/portforwarderimpl.cppandsrc/base/bittorrent/sessionimpl.cpphandle libtorrent delegation. - Protocol activation:
SessionImpl::enablePortMapping()setsenable_upnpandenable_natpmpflags in the libtorrent settings pack. - Mapping lifecycle: Ports are mapped via
add_port_mapping()with handles stored inm_mappedPorts; cleanup occurs automatically on disable or shutdown. - Consistent API: Both the Qt GUI (
OptionsDialog) and Web UI (AppController) interact with the same singleton instance.
Frequently Asked Questions
How does qBittorrent handle UPnP lease duration?
The lease duration is configured through the UI field “UPnP lease duration [0: permanent lease]” and passed to libtorrent via settingsPack.set_int(lt::settings_pack::upnp_lease_duration, duration). A value of 0 creates a permanent mapping, while positive values specify the lease lifetime in seconds.
What happens to port mappings when qBittorrent shuts down?
During application shutdown, the SessionImpl destructor clears the PortForwarder singleton, which triggers disablePortMapping(). This invokes delete_port_mapping(handle) for all active handles stored in m_mappedPorts, ensuring routers remove the temporary port forwards.
Can I enable UPnP/NAT-PMP via the Web API without using the GUI?
Yes. Send a POST request to /api/v2/app/setPreferences with a JSON payload containing "upnp": true. The AppController processes this in src/webui/api/appcontroller.cpp by calling Net::PortForwarder::instance()->setEnabled(true), identical to the desktop checkbox behavior.
Does qBittorrent differentiate between UPnP and NAT-PMP in the settings?
No. The qBittorrent implementation treats UPnP and NAT-PMP as a single feature flag. When enabled via enablePortMapping(), both lt::settings_pack::enable_upnp and lt::settings_pack::enable_natpmp are set to true simultaneously, allowing the client to use whichever protocol the router supports.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →