@tostyssb/portcullis
v0.1.0
Published
Dependency free NAT port mapping via UPnP, NAT-PMP, and PCP
Downloads
41
Maintainers
Readme
Portcullis
Dependency-free NAT port mapping for Node.js via UPnP, NAT-PMP, and PCP.
npm install portcullisDISCLAIMER: 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 topublicPort).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+
