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:

  1. 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.

  2. Web UI / API – Exposes the UPnP state through AppController (src/webui/api/appcontroller.cpp), allowing remote clients to read and modify the setting via the upnp JSON key.

  3. PortForwarder Abstraction – Provides a singleton interface (Net::PortForwarder in src/base/net/portforwarder.h) used throughout the codebase for decoupled access.

  4. Concrete Implementation – PortForwarderImpl (src/base/bittorrent/portforwarderimpl.h) inherits from the abstract class and forwards requests to the BitTorrent session.

  5. BitTorrent Session – SessionImpl (src/base/bittorrent/sessionimpl.cpp) applies libtorrent settings and manages individual port mappings through add_port_mapping() and delete_port_mapping().

Data Flow

When a user toggles the UPnP checkbox or sends an API request:

  1. OptionsDialog or AppController calls Net::PortForwarder::instance()->setEnabled(bool).

  2. PortForwarderImpl::setEnabled() stores the value in m_storeActive and invokes start() or stop().

  3. PortForwarderImpl::start() triggers SessionImpl::enablePortMapping(), which builds a lt::settings_pack with enable_upnp = true and enable_natpmp = true, applying it to the native libtorrent session.

  4. When ports change, SessionImpl::addMappedPorts() calls m_nativeSession->add_port_mapping(lt::session::tcp, port, port) for each port, storing returned handles in m_mappedPorts.

  5. Disabling UPnP triggers disablePortMapping(), which clears m_mappedPorts and 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::PortForwarder abstraction that decouples the UI from the networking core.
  • Implementation files: src/base/net/portforwarder.h defines the interface, while src/base/bittorrent/portforwarderimpl.cpp and src/base/bittorrent/sessionimpl.cpp handle libtorrent delegation.
  • Protocol activation: SessionImpl::enablePortMapping() sets enable_upnp and enable_natpmp flags in the libtorrent settings pack.
  • Mapping lifecycle: Ports are mapped via add_port_mapping() with handles stored in m_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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →