DEV Community

Philip Stayetski
Philip Stayetski

Posted on

What You Actually Trade Off Building a Zero-Dependency Go Networking Library

I spent three months building a peer-to-peer overlay network entirely on Go's standard library — net, crypto, sync, encoding/binary, nothing outside GOROOT. No google.golang.org/grpc, no github.com/quic-go/quic-go, no github.com/libp2p/go-libp2p. Just go build and you're done.

The decision was ideological at first. I wanted something an agent could install without dragging in a vendor directory. What I learned is that the tradeoffs are sharper than I expected, and they matter differently depending on who — or what — is importing your module.

What the standard library actually gives you

Go's stdlib is surprisingly complete for layer-4 transport. You get:

  • TCP listeners and dialers with timeout and keepalive control
  • UDP sockets including connected UDP and WriteTo/ReadFrom for stateless datagrams
  • TLS 1.2/1.3 via crypto/tls — full client and server with cert pinning
  • X25519 ECDH and AES-GCM in crypto/curve25519 and crypto/aes + crypto/cipher
  • Ed25519 signatures for identity and attestation
  • STUN-style attribute encoding with encoding/binary — no need for a message-pack library
  • JSON and protobuf-like encoding via encoding/json, encoding/xml, or manual binary packing
  • Goroutine-per-connection concurrency without an event-loop framework

For a transport layer, this is enough. Pilot Protocol's UDP tunnel uses exactly this stack: X25519 key exchange, AES-GCM session encryption, Ed25519 node identity, all over plain UDP sockets. Zero external imports.

// This is real code that compiles with zero dependencies outside stdlib
import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/curve25519"
    "crypto/ed25519"
    "crypto/rand"
    "net"
)

func dialTunnel(server string) (*aesGCMConn, error) {
    priv := make([]byte, 32)
    rand.Read(priv)
    pub, _ := curve25519.X25519(priv, curve25519.Basepoint)
    // ... exchange, derive key, wrap conn in AES-GCM
}
Enter fullscreen mode Exit fullscreen mode

No go get, no go mod tidy pulling in 47 transitive dependencies. You can audit the whole crypto path by reading one file.

What you give up

Here's where it stings. No external dependencies means you opt out of:

HTTP/2 and HTTP/3. Go's stdlib net/http has HTTP/2 for servers, but client-side HTTP/2 is limited and there is no QUIC (HTTP/3) at all. If your protocol wants multiplexed streams over a single connection, you're building your own framing — or falling back to one-goroutine-per-stream, which works but wastes memory under load.

Protocol Buffers. Go's stdlib has encoding/gob and encoding/json, but neither matches the schema enforcement or wire efficiency of protobuf. Gob is Go-specific; JSON is wasteful for control messages. You end up hand-rolling binary wire formats with encoding/binary, which is fast but tedious and error-prone.

NAT traversal libraries. There's no pion/stun or pion/turn in the standard library. You write your own STUN client from scratch — about 200 lines of attribute encoding — or skip it and rely on relay fallback.

Performance shortcuts. QUIC libraries (quic-go, msquic) give you kernel-bypass UDP with built-in flow control and loss recovery. The equivalent on raw stdlib is a userspace reliability layer that you own entirely — every packet loss signal, every retransmit timer, every congestion decision.

Integration convenience. If the rest of your stack speaks gRPC, going zero-dep means you can't reuse its service discovery, load balancing, or streaming primitives.

When the tradeoff flips

The zero-dep bet pays off hard in exactly one scenario: when someone else installs your module.

Agent infrastructure is that scenario. A Go networking library that an AI agent installs via go get — or pulls as a prebuilt binary from a curl | sh — has a fundamentally different supply chain surface than one running inside a datacenter with locked go.sum files. Every transitive dependency is a trust decision loaded at install time, often with no human reviewing the diff.

This is why Pilot Protocol ships as a single Go binary with zero external runtime dependencies. The pilotctl CLI and pilot-daemon compile to a statically linked binary. No libp2p tree, no gRPC C library, no QUIC shared object. The entire cryptographic stack — X25519, AES-GCM, Ed25519 — is baked into the stdlib and auditable by anyone who reads Go's source.

# This is the entire install surface
curl -fsSL https://pilotprotocol.network/install.sh | sh
# No package manager, no shared libraries, no Rust toolchain
Enter fullscreen mode Exit fullscreen mode

The build itself is a single go build that produces ~12 MB of statically linked machine code. Supply chain concerns reduce to "do you trust Go's standard library" — which is a well-audited, narrow question — rather than "do you trust 80 transitive Go modules from 30 different orgs."

Where it hurts most in practice

The pain points I hit hardest were:

Reliability over UDP. Go's net package gives you raw UDP sockets. Adding reliability — sequence numbers, ACKs, retransmission, RTT estimation — is a weeks-long project, not a go get. You either ship a basic stop-and-wait or invest serious time in a TCP-style sliding window. Most transport protocols need this; most implementors underestimate the engineering.

TLS for peer identities. crypto/tls is Go's crown jewel, but it's designed for the client-server web PKI model. Using it for mesh-style mutual authentication (agent A → agent B, both ephemeral, no CA) requires creative config that the library authors never intended. Noise or a simple X25519 + Ed25519 handshake is actually simpler to build from raw primitives than to wedge into crypto/tls.

Fragmentation. The stdlib gives you no help with MTU discovery or IP fragmentation handling. For small control messages (< 1200 bytes) this is fine. For anything approaching jumbo frames you're guessing or implementing path MTU discovery yourself.

The bottom line

Building a zero-dependency transport layer on Go's standard library is a completely rational choice — but only when you understand what you're paying for the auditability.

You trade modern protocol ergonomics (HTTP/3 multiplexing, protobuf schemas, battle-tested QUIC implementations) for a narrow, verifiable supply chain. For most datacenter services, that's a bad trade. For agent infrastructure where the binary propagates to machines you don't control, it's often the right one.

Pilot Protocol makes this trade explicitly. Its entire transport — addressing, encryption, NAT traversal, peer routing — fits in the Go standard library. The result is a network layer an agent can adopt with one command and no transitive trust beyond Go's own source.

If you're evaluating transport libraries for an agent system, ask one question first: "Who imports this, and on whose machine does it run?" The answer decides whether zero-dependency is a feature or a handicap.


Pilot Protocol's architecture and trust model are documented at pilotprotocol.network/docs. The source is at github.com/pilot-protocol.

Top comments (0)