Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Portcullis

Dependency-free NAT port mapping for Node.js via UPnP, NAT-PMP, and PCP.

npm install portcullis

DISCLAIMER: For now this is for personal use and may have flaws, it's entirely built by claude (with some guidance) so my confidence is not super high. It works well for me right now.

Quick Start

import { createClient } from "portcullis";

const client = await createClient();

// Map external port 8080 to local port 8080 (TCP)
const mapping = await client.map({ publicPort: 8080 });
console.log(`Mapped ${mapping.publicPort} -> ${mapping.privatePort} (${mapping.natProtocol})`);

// Check your public IP
const ip = await client.externalIp();
console.log(`Reachable at ${ip}:${mapping.publicPort}`);

// Clean up
await client.unmap({ publicPort: 8080 });
await client.close();

API

createClient(options?): Promise<NatClient>

Auto-detects the gateway and probes protocols in order: PCP, NAT-PMP, UPnP. Returns the first client that successfully communicates with the gateway.

Options:

  • gateway?: string — Gateway IP. Auto-detected if omitted.
  • timeout?: number — Timeout in ms for network operations (default: 10000).
  • signal?: AbortSignal — Cancel detection/probing.
  • logger?: { debug(msg: string): void } — Optional debug logger.

client.map(options): Promise<MappingResult>

Create a port mapping on the gateway.

  • publicPort: number — External port (0-65535).
  • privatePort?: number — Internal port (defaults to publicPort).
  • protocol?: "tcp" | "udp" — Transport protocol (default: "tcp").
  • ttl?: number — Lifetime in seconds (default: 7200).
  • description?: string — Human-readable label (UPnP only — silently ignored by NAT-PMP and PCP).

client.unmap(options): Promise<void>

Remove a port mapping.

  • publicPort: number — External port to unmap.
  • protocol?: "tcp" | "udp" — Transport protocol (default: "tcp").

client.externalIp(): Promise<string>

Get the external (public) IP address of the gateway.

client.close(): Promise<void>

Release resources (abort in-flight UDP requests, clear state).

Protocol-Specific Clients

Use these when you want to target a specific protocol:

import { createUpnpClient, createPmpClient, createPcpClient, detectGateway } from "portcullis";

const gateway = await detectGateway();

const upnp = await createUpnpClient(gateway);  // UPnP (most widely supported)
const pmp = createPmpClient(gateway);            // NAT-PMP
const pcp = createPcpClient(gateway);            // PCP (most modern)

Error Handling

import {
  createClient,
  GatewayNotFoundError,
  NoProtocolError,
  TimeoutError,
  ProtocolError,
} from "portcullis";

try {
  const client = await createClient();
  await client.map({ publicPort: 8080 });
} catch (err) {
  if (err instanceof GatewayNotFoundError) {
    // Not connected to a network / can't find router
  } else if (err instanceof NoProtocolError) {
    // Router doesn't support UPnP, NAT-PMP, or PCP
  } else if (err instanceof TimeoutError) {
    // Network operation timed out
  } else if (err instanceof ProtocolError) {
    // Protocol-level error (e.g., port already mapped)
  }
}

More Examples

See docs/examples.md for detailed usage patterns including mapping renewal, HTTP server integration, and protocol detection.

Requirements

  • Node.js 20+

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages