@santaklouse/p2p-netcat
v2.1.2
Published
TCP/QUIC netcat-like CLI addressed by libp2p PeerId instead of IP
Downloads
308
Maintainers
Readme
p2p-netcat
English | Русский
p2p-nc is a JavaScript command-line utility inspired by netcat. Instead of
the server's IP address, it uses a cryptographic libp2p PeerId. Its transport
is compatible with the IPFS networking stack: QUIC v1, TCP, Noise, Yamux, IPFS
Amino DHT, mDNS, and Circuit Relay v2.
Documentation
- Detailed architecture and connection algorithm — identity, discovery, route selection, CLI, browser, relay, encryption, backpressure, and failure handling;
- Browser PWA guide — build, GitHub Pages,
network-config.json, and WSS relay configuration; - Shared JavaScript library API — exported functions
from
@santaklouse/p2p-netcat-core; - Programmatic Circuit Relay API — start and stop a relay
from another Node.js application through
p2p-netcat/relay.
What already works
- Bidirectional, binary-transparent stdin/stdout transfer;
- QUIC v1 as the preferred direct transport, with automatic TCP fallback;
- A stable server PeerId derived from a local Ed25519 key;
- Logical ports, allowing one PeerId to expose multiple services;
- PeerId-only discovery on a LAN through mDNS and over the internet through the IPFS Amino DHT;
- a direct Trystero/WebRTC fallback signalled through public WebTorrent trackers;
- Circuit Relay v2 connections for nodes behind NAT;
- A built-in relay mode;
-l,-k,-w,-q,-z,-e,-p,-4,-6, and verbose mode;- Authenticated encryption through QUIC TLS 1.3 or Noise, including connections through a relay.
Netcat-style UDP datagram mode (-u) is deliberately rejected for now. QUIC
does use UDP underneath, but it still provides a reliable, ordered stream.
Installation
Node.js 22 or newer is required. The QUIC transport uses a native N-API module with prebuilt binaries for mainstream macOS, Linux, and Windows platforms.
Install the published CLI from npm:
npm install --global p2p-netcatFor development from source:
npm install
npm linkAfter npm link, both commands are available: p2p-nc and the shorter pnc.
QUIC is enabled by default. Use --no-quic only for diagnostics or on a system
where the native QUIC module is unavailable. The -p option uses the same
numeric port for TCP and UDP; when it is 0, the operating system chooses each
port independently.
Quick start
On the first computer:
p2p-nc -l 8080The command prints the persistent PeerId to stderr, for example:
[p2p-nc] слушатель:8080 PeerId: 12D3KooWLs9pvVfwbo6yHsYB66kRLq2RJwfH3bBQZhp94kerbFd9On the second computer, copy the printed PeerId and run:
p2p-nc 12D3KooWLs9pvVfwbo6yHsYB66kRLq2RJwfH3bBQZhp94kerbFd9 8080Once connected, everything typed in either terminal is sent to the other one. The PeerId above is illustrative: the connection command must contain the value printed by your own listener.
To print the PeerId without starting a listener:
p2p-nc idThe server key is created at ~/.config/p2p-netcat/identity.key with 0600
permissions. Deleting the key creates a new PeerId. The client identity is
ephemeral unless --identity is supplied explicitly.
Verifiable local example without manual copying
The following commands run entirely on one computer:
DEMO_KEY=/tmp/p2p-netcat-demo.key
DEMO_ID="$(p2p-nc id --identity "$DEMO_KEY")"
p2p-nc -l 8080 --identity "$DEMO_KEY" > /tmp/p2p-netcat-received.txt &
DEMO_PID=$!
sleep 2
printf 'hello over PeerId\n' | p2p-nc "$DEMO_ID" 8080
wait "$DEMO_PID"
cat /tmp/p2p-netcat-received.txtNodes behind NAT: running your own relay
The public IPFS DHT is used for discovery, but public IPFS nodes do not promise unlimited relaying of arbitrary streams. For predictable connectivity between two nodes behind NAT, run a relay once on a VPS with both TCP and UDP port 9090 open:
p2p-nc relay -4 -p 9090 \
--announce /ip4/203.0.113.10/udp/9090/quic-v1 \
--announce /ip4/203.0.113.10/tcp/9090In a real command, --announce must contain the VPS's public address. The relay
prints its complete multiaddr. Assume it prints:
/ip4/203.0.113.10/udp/9090/quic-v1/p2p/12D3KooWK4bicbvfPNGzfuMBf6xE43ecgB26NHDZRTfLM7CNh9yw
/ip4/203.0.113.10/tcp/9090/p2p/12D3KooWK4bicbvfPNGzfuMBf6xE43ecgB26NHDZRTfLM7CNh9ywServer behind NAT:
p2p-nc -l 8080 --relay /ip4/203.0.113.10/udp/9090/quic-v1/p2p/12D3KooWK4bicbvfPNGzfuMBf6xE43ecgB26NHDZRTfLM7CNh9ywThe client behind NAT uses the same relay but addresses the server only by its PeerId:
p2p-nc --relay /ip4/203.0.113.10/udp/9090/quic-v1/p2p/12D3KooWK4bicbvfPNGzfuMBf6xE43ecgB26NHDZRTfLM7CNh9yw 12D3KooWJ7satLo5LXjhSZBMVTWRG1AZ77sQYtX81qHHf2VtscdL 8080Traffic between the client and server remains end-to-end encrypted. The relay can see the participants' PeerIds and traffic volume and timing, but it cannot read the contents.
The same relay can be embedded into another Node.js process without spawning the CLI:
import { startRelay } from 'p2p-netcat/relay'
const relay = await startRelay({
identityPath: './data/p2p-netcat-relay.key',
localPort: 9090,
websocketPort: 9091,
enableMdns: false
})
console.log(relay.peerId, relay.addresses)
await relay.stop()See the programmatic relay guide for every option, persistent identity, signal handling, public announce addresses, and WSS.
Browser PWA client
The web directory contains a fully static React client without SSR, API
routes, a database, or server-side scripts. The libp2p networking stack runs in
a Web Worker. A Service Worker caches the application shell for offline PWA
startup and updates it in the background. The page includes a terminal widget,
text and file transfer, EOF, and binary-stream download.
Build the static files:
cd web
npm install
npm run buildThe result is written to web/dist and can be placed on any HTTPS static file
host; the application needs no backend. Opening it through file:// is not a
supported mode because browsers require a secure context—HTTPS or localhost—
for Service Workers and PWA installation.
A ready-to-use .github/workflows/pages.yml workflow is included for GitHub
Pages. In the repository settings, select Settings → Pages → Source → GitHub
Actions. The workflow automatically uses the repository subpath as Vite's
base URL.
The browser does not require a relay address by default. It starts two paths in parallel: Trystero/WebRTC through public WebTorrent trackers, and libp2p lookup through HTTP Delegated Routing with IPFS Amino DHT fallback. The first authenticated channel wins. Discovered libp2p multiaddrs are also raced.
If the server advertises only TCP/QUIC addresses or automatic discovery cannot find a usable route, expand the advanced settings and provide a WebSocket relay:
p2p-nc relay -4 -p 9090 --websocket-port 9091Locally, the browser can use the printed address containing
/tcp/9091/ws/p2p/. When the page is hosted on HTTPS, publish the relay over
WSS, typically through a TLS reverse proxy on port 443. See
web/README.md for the complete guide.
Shared JavaScript library
Logic that must behave identically in the CLI and browser lives in the local
@santaklouse/p2p-netcat-core npm package. It uses no Node.js APIs and
owns logical-port and protocol-ID rules, PeerId/multiaddr validation, WS/WSS
relay validation, Circuit Relay route planning, and transport preference.
The CLI imports this package directly, while the browser imports it from its
Web Worker. Platform adapters remain separate: stdin/stdout, local identities,
QUIC, and the DHT belong to the CLI; DOM integration, Worker RPC, and the PWA
lifecycle belong to web. This gives future discovery and fallback strategies
one shared interface instead of two diverging implementations.
import { createRelayDialPlan, protocolForService } from '@santaklouse/p2p-netcat-core'
const protocol = protocolForService(31337)
const plan = createRelayDialPlan({
peerId: '12D3KooWQ3uxpHgjDKE6vGmvzKS8RPbxUDLwJ7XCLaD6YXdUfbR9',
service: 31337,
relay: '/dns4/relay.example/tcp/443/wss/p2p/12D3KooWEqeQRAJ61HSv9yMPk8yzjke7NxmTFcvFt4GzwXxzVjXW',
requireWebSocket: true,
secureContext: true
})Equivalents of common netcat commands
Check reachability without exchanging data:
p2p-nc -z -v 12D3KooWLs9pvVfwbo6yHsYB66kRLq2RJwfH3bBQZhp94kerbFd9 8080Transfer a file:
p2p-nc -l 9000 > received.tar.gz
p2p-nc 12D3KooWLs9pvVfwbo6yHsYB66kRLq2RJwfH3bBQZhp94kerbFd9 9000 < archive.tar.gzRun a command for each connection:
p2p-nc -l -k 7000 -e '/bin/sh -i'The -e option gives the connected user the privileges of the local process.
Use it only with trusted PeerIds and inside an isolated environment.
Complete command reference:
p2p-nc --help
p2p-nc relay --helpHow a connection is established
- The listener loads its persistent key; the hash of the public key becomes its PeerId.
- Logical port
8080becomes the libp2p protocol/p2p-netcat/1.0.0/8080. - The server publishes a provider record for its PeerId CID to the Amino DHT.
- The client searches known addresses, mDNS, that provider record, and the DHT.
- With
--relay, it builds arelay/p2p-circuit/p2p/serverroute. - Without an explicit route, a direct Trystero/WebRTC channel is attempted in parallel.
- QUIC TLS 1.3, Noise, or a signed WebRTC challenge authenticates the PeerId.
- stdin/stdout is transferred as a raw byte stream with backpressure.
The architecture document describes every CLI and browser branch, timeout, cache, concurrent address race, and trust boundary in detail.
HTTP/3 is not part of the application protocol. Direct peers use raw libp2p QUIC streams, avoiding unnecessary HTTP request, header, and CONNECT semantics.
Practical MVP limitations
- PeerId-only internet connections work after a provider record containing a
public or relayed server address has been published. The first publication
may take about a minute;
-vreports when it succeeds. - Servers behind CGNAT must use an available Circuit Relay v2. The most reliable
option is to pass the same
--relayto both the server and client. - WebRTC improves direct connectivity, but symmetric NAT or blocked UDP may still require TURN or Circuit Relay; public trackers provide no SLA.
- An IPFS HTTP gateway is not a relay and cannot carry this protocol.
- Networks that block UDP automatically fall back to TCP when both addresses
are known;
--no-quicdisables QUIC explicitly. - In
-kmode without-e, shared stdin is inconvenient for concurrent clients; incoming streams are limited to one active session. - PeerId allowlist authorization, SOCKS/TCP forwarding, and datagram mode are natural next steps, but they are not implemented in this MVP.
Development
npm test
npm run lint
npm --prefix packages/core test
cd web && npm ci && npm test && npm run lint