examples/

directory
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 22, 2026 License: MIT

README

Examples

Every example is runnable from a clean checkout. echo, turnserver, turnclient, and turncred need no network access, no Docker, and no privileges; signaling and pipecat are the two-machine story and need a port you can reach.

Example What it shows
echo The whole API: dial by peer name, net.Conn both ways, one process.
signaling The HTTP signaling server that peers on different machines use.
pipecat netcat over pipe: stdin/stdout of two machines joined by a connection.
turnserver A STUN + TURN server with per-user throughput plans and quotas.
turnclient A pipe connection forced through TURN, measured.
turncred Mints ephemeral TURN credentials from a shared secret.
docker Dockerfile, compose stack, and a coturn configuration.

The guides explain the ideas behind these; this file is the reference for the commands.

echo

go run ./examples/echo

Two endpoints in one process, signaling through the in-process hub, exchanging lines of text. No SDP, no ICE, no data channels appear in the code.

signaling

The signaling server from signaling/sse as a command. Peers receive signals over a Server-Sent Events stream and send them with POST; the server authenticates every request and only routes envelopes, never application data.

# Local development: believe whatever peer ID a client claims.
go run ./examples/signaling -insecure-trust-peer-header

# Anything else: one bearer token per peer ID.
export PIPE_SIGNAL_TOKENS="alice=$(openssl rand -hex 24),bob=$(openssl rand -hex 24)"
go run ./examples/signaling -listen :8080
Flag Default Meaning
-listen 127.0.0.1:8080 TCP address to serve on.
-path /pipe Path the handler is mounted at; clients use http://host:port/pipe.
-tokens peer=token list, or PIPE_SIGNAL_TOKENS. Tokens need 16+ characters.
-insecure-trust-peer-header off Trust the X-Pipe-Peer header without a token. Local development only.
-max-peers 0 (unlimited) Bound on concurrently known peers.
-offline-grace 30s How long a disconnected peer keeps its queue.
-tls-cert, -tls-key Serve HTTPS directly instead of behind a proxy.
-v off Debug logging.

GET /healthz reports ok peers=N.

pipecat

# Machine A
go run ./examples/pipecat -signal http://signal.example:8080/pipe -token "$BOB_TOKEN" -id bob listen > received.bin

# Machine B
go run ./examples/pipecat -signal http://signal.example:8080/pipe -token "$ALICE_TOKEN" -id alice dial bob < file.bin

Whatever one side writes, the other reads. The end of stdin closes the connection and the peer reads EOF; -hold keeps the connection open after stdin ends until the peer closes, for request-and-reply use.

Flag Env Meaning
-signal PIPE_SIGNAL_URL Signaling server URL.
-token PIPE_SIGNAL_TOKEN Bearer token.
-id PIPE_ID This peer's ID.
-stun PIPE_STUN STUN URL.
-turn, -turn-user, -turn-pass PIPE_TURN, PIPE_TURN_USERNAME, PIPE_TURN_PASSWORD TURN URL and credentials.
-relay-only Use relay candidates only; proves the relay works.
-allow Comma-separated peer IDs allowed to connect when listening.
-hold Keep reading after stdin ends until the peer closes.
-keepalive Probe interval, default 15s; 0 disables.
-timeout Dial timeout, default 60s.
-v Debug logging to stderr.

It prints one line to stderr on connect with the candidate types used, for example via relay/relay or via host/host.

turnserver

A STUN and TURN server built on pion/turn. One UDP listener answers both: STUN Binding requests need no credentials, TURN Allocate requests do. An optional TCP listener serves clients whose networks block UDP.

# Local development server: admin/admin on 127.0.0.1:3478.
go run ./examples/turnserver

# Plans: free users get 512 KiB/s per relay socket per direction and 4 sockets,
# paid users 8 MiB/s and 32 sockets. Users pick their plan with :plan.
go run ./examples/turnserver -plans 'free=512KiB/4,paid=8MiB/32' \
    -users 'alice=alice-secret:free,bob=bob-secret:paid' -stats 5s

# Ephemeral credentials from a shared secret instead of a user list. The plan
# rides in the user ID as name@plan; see turncred.
export PIPE_TURN_SECRET=$(openssl rand -hex 32)
go run ./examples/turnserver -plans 'free=512KiB/4,paid=8MiB/32'

# A real deployment: public address, fixed relay port range, real realm.
go run ./examples/turnserver -listen 0.0.0.0:3478 -listen-tcp 0.0.0.0:3478 \
    -relay-ip 203.0.113.10 -relay-ports 49152-49352 -realm relay.example.net
Flag Default Meaning
-listen 127.0.0.1:3478 UDP address for STUN and TURN.
-listen-tcp Optional TCP address for TURN over TCP.
-realm pipe.example TURN realm; the client must match it.
-users admin=admin user=password[:plan] list, or PIPE_TURN_USERS.
-auth-secret Shared secret for ephemeral credentials, or PIPE_TURN_SECRET.
-plans name=rate[/maxallocations] list, e.g. free=512KiB/4,paid=8MiB/32.
-relay-ip listen address Relay address advertised to clients. Required behind NAT.
-relay-ports kernel picks Inclusive UDP range for relay sockets, e.g. 49152-49352.
-rate 0 (unlimited) Default plan: bytes/second per relay socket per direction.
-burst derived Default plan: token bucket depth; 0.1 s of -rate, minimum 64 KiB.
-max-delay 20ms How long a packet may be held for budget before it is dropped.
-max-allocations 0 (unlimited) Default plan: concurrent relay sockets per user.
-stats off Interval for logging traffic counters, total and per user.
-v off Debug logging.

admin/admin is a convenience for a server bound to loopback. Anything reachable from a network needs real credentials, a real realm, and a real relay address. Passwords are converted to the RFC 5389 key digest at startup and are never logged.

How the throughput budget works

A plan's rate puts a token bucket on every relay socket the server allocates for that user, in each direction:

  • Client → peer is policed. This path runs on the goroutine that serves every client of the listener, so it must never block: datagrams over budget are dropped.
  • Peer → client is shaped. This path has its own goroutine per allocation, so a datagram may be held up to -max-delay to stay inside the budget, and is dropped only if it would need longer.

The congestion is therefore real. SCTP inside the pipe sees delay and loss and backs off the way it would on a genuinely slow link — which is the point. A knob that only reported a number would not tell you anything about how your application behaves.

turnclient

Moves data through a pipe connection that cannot avoid the relay, and reports what it measured.

# Self-contained: starts its own relay, transfers 4 MiB, prints the result.
go run ./examples/turnclient

# Compare two plans. The plan is chosen by the @plan suffix of the user ID.
go run ./examples/turnclient -plans 'free=512KiB,paid=8MiB' -user alice@free -bytes 1MiB
go run ./examples/turnclient -plans 'free=512KiB,paid=8MiB' -user alice@paid -bytes 1MiB

# Ephemeral credentials minted on the fly and verified by the embedded server.
go run ./examples/turnclient -auth-secret "$(openssl rand -hex 32)" -user bob@paid -plans 'free=512KiB,paid=8MiB'

# Use a relay you started separately (or coturn, or a cloud TURN service).
go run ./examples/turnclient -embedded=false \
	-turn 'turn:127.0.0.1:3478?transport=udp' -user admin -pass admin

Credentials fall back to PIPE_TURN_USERNAME and PIPE_TURN_PASSWORD when the flags are empty.

Output from a free-plan run:

turn:        turn:127.0.0.1:54666?transport=udp
user:        alice@free (realm pipe.example)
policy:      relay
plan free:   512.0KiB/s
plan paid:   8.0MiB/s
transfer:    1.0MiB (echoed, so every byte crosses the relay four times)

connected in 9ms
transferred  1.0MiB round trip in 12.752s
throughput   80.3KiB/s each way
state        connected, candidates relay/relay, read 1.0MiB, written 1.0MiB

relay        allocations=2 active=2 sent=2.2MiB/2729pkt received=2.2MiB/2729pkt dropped=330.1KiB/292pkt delayed=7pkt
user alice@free plan=free allocations=2 active=2 sent=2.2MiB received=2.2MiB dropped=330.1KiB/292pkt rejected=0

candidates relay/relay is the part that matters: -relay-only (the default) gathers relay candidates only, so ICE cannot quietly pick the direct host path and report a success that never touched TURN.

Reading the numbers honestly

Application throughput is not equal to the plan rate, and the example says so. The transfer is an echo, so every byte crosses the relay four times — out through the client's allocation, in through the server's, and again on the way back — and each crossing is metered separately. On top of that, dropped datagrams make SCTP back off. With a 512 KiB/s plan a 1 MiB round trip measures roughly 60 to 100 KiB/s each way on loopback; the relay counters in the same output show the 2.2 MiB that was actually metered and the few hundred KiB that were dropped. A one-way transfer gets much closer to the cap.

Numbers from this example describe loopback on one machine. They are a demonstration of the mechanism, not a capacity claim.

turncred

export PIPE_TURN_SECRET=...
go run ./examples/turncred -user alice@free -ttl 12h
go run ./examples/turncred -user alice@paid -ttl 1h -json -turn 'turn:relay.example.net:3478?transport=udp'

Prints a username of the form <unix expiry>:<user id> and the matching password, which any server configured with the same secret accepts until the expiry: the example turnserver -auth-secret, or coturn with use-auth-secret. -json prints an object shaped like a WebRTC ICE server entry for handing to a client.

Configuring a real STUN/TURN service

The client code is the same whichever server you use:

ep, err := pipe.New(ctx, pipe.Config{
	ID:       "alice",
	Signaler: mySignaler,
	ICEServers: []pipe.ICEServer{
		{URLs: []string{"stun:stun.example.net:3478"}},
		{
			URLs:       []string{"turn:turn.example.net:3478?transport=udp"},
			Username:   os.Getenv("PIPE_TURN_USERNAME"),
			Credential: os.Getenv("PIPE_TURN_PASSWORD"),
		},
	},
	// Optional: refuse to connect except through TURN.
	ICETransportPolicy: pipe.ICETransportPolicyRelay,
})

Signaling is a separate concern from STUN and TURN. STUN and TURN find a network path between two peers; signaling is how those peers exchange the descriptions that make the path possible. echo and turnclient use signaling/memory because both peers are in one process; pipecat uses signaling/sse because they are not.

Directories

Path Synopsis
Command echo runs a two-peer echo service over a pipe.
Command echo runs a two-peer echo service over a pipe.
Command pipecat is netcat for pipe: it connects stdin and stdout of two processes on two machines through a pipe connection.
Command pipecat is netcat for pipe: it connects stdin and stdout of two processes on two machines through a pipe connection.
Command signaling runs the HTTP signaling server from ella.to/pipe/signaling/sse.
Command signaling runs the HTTP signaling server from ella.to/pipe/signaling/sse.
Command turnclient moves data through a pipe connection that is forced onto a TURN relay, and reports what it measured.
Command turnclient moves data through a pipe connection that is forced onto a TURN relay, and reports what it measured.
Command turncred mints ephemeral TURN credentials for a relay that runs with a shared secret (turnserver -auth-secret, or coturn with use-auth-secret).
Command turncred mints ephemeral TURN credentials for a relay that runs with a shared secret (turnserver -auth-secret, or coturn with use-auth-secret).
Command turnserver runs a STUN and TURN server with per-user throughput plans, for developing against pipe and as a starting point for a small production relay.
Command turnserver runs a STUN and TURN server with per-user throughput plans, for developing against pipe and as a starting point for a small production relay.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL