How Motrix's NatManager Handles UPnP and NAT-PMP Port Mapping

Motrix's NAT manager leverages the @motrix/nat library to automate router port forwarding by dynamically selecting between UPnP, NAT-PMP, and PCP protocols based on user configuration or automatic discovery.

Motrix is a full-featured, open-source download manager built with Electron that relies on inbound connectivity for peer-to-peer networking. To eliminate manual router configuration, the application implements a sophisticated NAT traversal system that negotiates port mappings automatically. Understanding how Motrix's NatManager coordinates these protocols requires examining the factory initialization, protocol selection logic, and event-driven architecture that connects the main process to the renderer UI.

Protocol Client Architecture and Factory Initialization

The NAT management system begins in src/main/nat/nat-manager-factory.ts, which instantiates specialized clients for each supported protocol. The factory creates four key components that handle different aspects of network discovery and mapping validation.

// src/main/nat/nat-manager-factory.ts (lines 62-71)
const upnpClient = new UpnpClient({
  udpFactory: nodeUdpSocketFactory,
  http: nodeHttpClient,
})
const pmpPcpClient = new PmpPcpClient({
  udpFactory: nodeUdpSocketFactory,
  gatewayIp,
  clientIp,
})
const stunClient = new StunClient({ udpFactory: nodeUdpSocketFactory })
const portChecker = new PortChecker()

UpnpClient handles Universal Plug and Play negotiations using SSDP (Simple Service Discovery Protocol) over UDP to locate Internet Gateway Devices (IGDs), then issues SOAP requests via HTTP to create or delete mappings. PmpPcpClient manages both NAT-PMP and PCP protocols by sending UDP packets directly to the gateway IP address discovered during network initialization. The StunClient assists with NAT type detection, while PortChecker validates whether established mappings are externally accessible.

Protocol Selection and Configuration

Protocol selection logic resides in the external @motrix/nat package, but Motrix provides the configuration interface through SettingsNatProvider defined in src/core/nat/settings-nat-provider.ts. This provider reads the user's preference from the settings schema located at src/shared/types/settings.ts (line 240).

The system supports four operational modes:

  • auto – Probes each available client sequentially, selecting the first successful mapping
  • upnp – Forces UPnP IGD negotiation via UpnpClient
  • natpmp – Uses NAT-PMP protocol through PmpPcpClient
  • pcp – Employs Port Control Protocol, also via PmpPcpClient

When preferredProtocol is set to auto, the NatManager attempts PCP first, then NAT-PMP, then UPnP, ensuring maximum compatibility across different router implementations.

UPnP Implementation Details

When UPnP is selected, the UpnpClient performs multi-stage discovery. First, it broadcasts SSDP M-SEARCH messages via the nodeUdpSocketFactory to identify IGD devices on the local network. Upon discovering a compatible gateway, the client retrieves the device description XML to determine the control URL.

The client then constructs SOAP envelopes to call the AddPortMapping or DeletePortMapping actions on the router. These HTTP requests are dispatched through nodeHttpClient, allowing the application to request specific external ports or accept dynamic allocations from the gateway. The implementation handles lease duration management automatically, refreshing mappings before expiration to maintain persistent connectivity.

NAT-PMP and PCP Implementation

The PmpPcpClient handles both NAT-PMP and PCP through a unified interface, differentiating between protocols based on the gateway's support advertisement. For NAT-PMP, the client sends UDP packets to port 5351 on the gateway IP (determined during network interface enumeration), requesting port mappings with specific lifetimes.

PCP extends this mechanism with additional features like flow control and improved multi-NAT traversal, using the same UDP socket factory but implementing the more modern PCP message format. Both protocols benefit from the clientIp parameter provided during factory initialization, ensuring the mapping request correctly identifies the internal host address.

Event System and UI Integration

The factory wires NAT lifecycle events to Motrix's internal EventBus (lines 92-111 in the factory file), enabling real-time communication between the main process and renderer. The manager emits NatStateChanged and NatMappingUpdated events that propagate through the application layer.

The renderer process consumes these events through the useNatStatus hook defined in src/renderer/hooks/use-nat-status.ts, supplying reactive state to UI components like NatBadge and NatTile found in src/renderer/routes/downloads/nat-badge.tsx and src/renderer/routes/dashboard/tiles/nat-tile.tsx.

// Renderer process enabling NAT
import { transport } from '@renderer/lib/transport'
import { Commands } from '@shared/protocol/commands'

await transport.invoke(Commands.EnableNat)
// UI component reflecting NAT status
import { useNatStatus } from '@renderer/hooks/use-nat-status'

export function NatBadge() {
  const status = useNatStatus()
  return (
    <button onClick={() => transport.invoke(Commands.DisableNat)}>
      Disable NAT
    </button>
  )
}

Configuration Schema

The default configuration enforces the auto fallback strategy through Zod schema validation in src/shared/schemas/nat-settings.ts. Developers can modify the preferredProtocol property within the NatSettings interface to change default behavior across application installs.

// src/shared/types/settings.ts
export interface NatSettings {
  preferredProtocol: 'auto' | 'pcp' | 'natpmp' | 'upnp'
}

Summary

  • Motrix's NAT manager is instantiated via createNatManager in the main process, creating isolated clients for UPnP and NAT-PMP/PCP protocols.
  • Protocol selection follows a hierarchy defined in @motrix/nat, respecting the user's preferredProtocol setting stored in src/shared/types/settings.ts.
  • UPnP uses SSDP discovery and SOAP HTTP requests via UpnpClient, while NAT-PMP sends direct UDP packets to the gateway through PmpPcpClient.
  • Auto-detection mode probes PCP, NAT-PMP, and UPnP sequentially until establishing a successful mapping.
  • Event-driven architecture bridges the main process NAT state to React components through the EventBus and useNatStatus hook, enabling real-time UI updates in NatBadge and NatTile.

Frequently Asked Questions

What protocols does Motrix support for automatic port mapping?

Motrix supports three protocols through the @motrix/nat library: UPnP IGD, NAT-PMP, and PCP (Port Control Protocol). The PmpPcpClient handles both NAT-PMP and PCP, while the UpnpClient manages UPnP negotiations. All three can be used individually or in automatic fallback mode.

How does Motrix determine which protocol to use when set to "auto"?

When preferredProtocol is set to auto, the NatManager executes a cascading discovery sequence defined in the external NAT library. It first attempts PCP, falls back to NAT-PMP if PCP fails, and finally tries UPnP if neither previous protocol succeeds. The first successful mapping halts the discovery process and establishes the active route.

Where is the preferred protocol setting stored in Motrix?

The setting is defined in src/shared/types/settings.ts at line 240 within the NatSettings interface, offering the options 'auto', 'pcp', 'natpmp', or 'upnp'. The SettingsNatProvider in src/core/nat/settings-nat-provider.ts bridges this configuration to the NatManager instance, ensuring the preference persists across application restarts through Motrix's settings persistence layer.

How does the Motrix UI reflect changes in port mapping status?

The renderer process subscribes to NAT events through the useNatStatus hook, which listens to NatStateChanged and NatMappingUpdated events emitted by the main process. These events are bridged through Motrix's EventBus (wired in nat-manager-factory.ts lines 92-111). Components like NatBadge and NatTile consume this reactive state to display current mapping status and provide toggle controls for enabling or disabling NAT traversal.

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 →