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

> Learn how Motrix's NatManager automates router port forwarding using UPnP, NAT-PMP, and PCP. Discover dynamic protocol selection for seamless connectivity.

- Repository: [Dr_rOot/Motrix](https://github.com/agalwood/Motrix)
- Tags: how-to-guide
- Published: 2026-08-19

---

**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`](https://github.com/agalwood/Motrix/blob/main/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.

```typescript
// 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`](https://github.com/agalwood/Motrix/blob/main/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`](https://github.com/agalwood/Motrix/blob/main/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`](https://github.com/agalwood/Motrix/blob/main/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`](https://github.com/agalwood/Motrix/blob/main/src/renderer/routes/downloads/nat-badge.tsx) and [`src/renderer/routes/dashboard/tiles/nat-tile.tsx`](https://github.com/agalwood/Motrix/blob/main/src/renderer/routes/dashboard/tiles/nat-tile.tsx).

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

await transport.invoke(Commands.EnableNat)

```

```tsx
// 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`](https://github.com/agalwood/Motrix/blob/main/src/shared/schemas/nat-settings.ts). Developers can modify the `preferredProtocol` property within the `NatSettings` interface to change default behavior across application installs.

```typescript
// 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`](https://github.com/agalwood/Motrix/blob/main/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`](https://github.com/agalwood/Motrix/blob/main/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`](https://github.com/agalwood/Motrix/blob/main/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`](https://github.com/agalwood/Motrix/blob/main/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.