wireguard-go — Structure#
Layout pattern#
Custom / Flat-Library Hybrid — not the standard cmd/internal/pkg layout. The repository root doubles as both the main package (the daemon binary) and a library host. All domain packages (device, conn, tun, ipc, etc.) sit as immediate top-level directories without the pkg/ or internal/ distinction. There is no cmd/ subdirectory; the two main.go files (main.go for Unix, main_windows.go for Windows) live directly in the root. This flat-root-as-main style is common in small, focused Go projects where the binary is secondary to the library use case.
Directory map#
wireguard-go/
├── main.go # Unix daemon entry point (build tag: !windows)
├── main_windows.go # Windows test/demo entry point
├── version.go # Generated: const Version = "..."
├── format_test.go # Top-level gofmt conformance test
├── go.mod / go.sum # Module: golang.zx2c4.com/wireguard
├── Makefile # Build, test, install targets
│
├── device/ # Core: WireGuard protocol engine
│ ├── allowedips.go # Routing: AllowedIPs trie (IP → peer mapping)
│ ├── channels.go # Bounded channels / queue management
│ ├── constants.go # Protocol constants
│ ├── cookie.go # Cookie reply mechanism (DoS mitigation)
│ ├── device.go # Device struct: central orchestration type
│ ├── indextable.go # Session index lookup table
│ ├── keypair.go # Handshake keypair lifecycle
│ ├── logger.go # Logger abstraction (Verbosef/Errorf/Silentf)
│ ├── mobilequirks.go # Mobile platform workarounds
│ ├── noise-helpers.go # Noise protocol helpers
│ ├── noise-protocol.go # Noise IKpsk2 handshake implementation
│ ├── noise-types.go # Noise type definitions (keys, etc.)
│ ├── peer.go # Peer state management
│ ├── pools.go # sync.Pool for packet buffers
│ ├── receive.go # Inbound packet processing goroutines
│ ├── send.go # Outbound packet processing goroutines
│ ├── timers.go # Keepalive and rekey timers
│ ├── tun.go # Device ↔ TUN interface glue
│ ├── uapi.go # UAPI (userspace API) command handler
│ ├── queueconstants_*.go # Per-platform queue sizes (android/ios/windows/default)
│ └── sticky_*.go # Sticky sockets (linux vs default)
│
├── conn/ # Network bind: UDP socket management
│ ├── conn.go # Bind interface definition
│ ├── bind_std.go # Standard UDP bind (non-Windows)
│ ├── bind_windows.go # Windows RIO (Registered I/O) bind
│ ├── default.go # NewDefaultBind() factory
│ ├── controlfns*.go # Socket control: platform-specific (linux/unix/windows)
│ ├── gso_*.go # GSO (Generic Segmentation Offload): linux vs default
│ ├── features_*.go # Feature flags: linux vs default
│ ├── sticky_*.go # Sticky source IP: linux vs default
│ ├── mark_*.go # Socket mark: unix vs default
│ ├── errors_*.go # Error classification: linux vs default
│ ├── boundif_android.go # Android-specific bound interface
│ ├── bindtest/ # Test helper: in-memory bind for unit tests
│ └── winrio/ # Windows RIO (Registered I/O) implementation
│
├── tun/ # TUN device abstraction
│ ├── tun.go # Device interface definition
│ ├── tun_linux.go # Linux TUN implementation
│ ├── tun_darwin.go # macOS TUN implementation
│ ├── tun_windows.go # Windows TUN via wintun driver
│ ├── tun_freebsd.go # FreeBSD TUN implementation
│ ├── tun_openbsd.go # OpenBSD TUN implementation
│ ├── checksum.go # Checksum utilities (shared)
│ ├── offload_linux.go # GRO/GSO offload support (Linux)
│ ├── operateonfd.go # fd-based TUN construction
│ ├── netstack/ # Optional: gVisor netstack TUN
│ │ ├── tun.go # Userspace TCP/IP stack TUN adapter
│ │ └── examples/ # Usage examples (HTTP client/server, ping)
│ └── tuntest/ # Test helper: in-memory TUN for unit tests
│
├── ipc/ # UAPI socket: configuration IPC channel
│ ├── uapi_linux.go # Linux: Unix domain socket in /var/run/wireguard/
│ ├── uapi_bsd.go # BSD: similar Unix socket approach
│ ├── uapi_windows.go # Windows: named pipe
│ ├── uapi_wasm.go # WASM stub
│ └── namedpipe/ # Windows named-pipe implementation
│
├── ratelimiter/ # Handshake rate limiter (token bucket)
│ ├── ratelimiter.go
│ └── ratelimiter_test.go
│
├── replay/ # Anti-replay sliding window (RFC-style)
│ ├── replay.go
│ └── replay_test.go
│
├── tai64n/ # TAI64N timestamp encoding (WireGuard handshake)
│ ├── tai64n.go
│ └── tai64n_test.go
│
├── rwcancel/ # Cancellable read/write on file descriptors
│ ├── rwcancel.go # Unix: epoll-based cancellation
│ └── rwcancel_stub.go # Non-Unix stub
│
└── tests/
└── netns.sh # Network namespace integration test (shell script)Entry points#
| File | Build condition | Binary produced | Purpose |
|---|---|---|---|
main.go | !windows | wireguard-go | Full daemon: creates TUN, opens UAPI socket, optionally daemonizes, handles signals |
main_windows.go | windows | wireguard-go | Test/debug binary for Windows; real Windows client is in the separate wireguard-windows repo |
Both entry points follow the same three-step bootstrap: open TUN → create device.Device → start UAPI listener. The Unix version adds optional daemonization via os.StartProcess self-re-exec with file descriptor passing.
Package organization#
Internal packages: None — there is no
internal/directory. All packages are technically importable by external consumers, which is intentional: wireguard-go is designed as a library as well as a daemon.Public packages (pkg/): None — wireguard-go does not use the
pkg/convention. All library packages live as top-level directories directly under the module root.Package roles:
Package Role deviceCore engine — the WireGuard protocol, peer management, cryptography, packet routing. The Devicestruct is the central orchestration object.connNetwork bind — UDP socket lifecycle, platform-specific socket options (GSO, sticky, marks, RIO on Windows). tunTUN abstraction — kernel TUN/TAP integration per OS, plus gVisor netstack for userspace-only operation. ipcUAPI channel — the WireGuard userspace API protocol; configuration accepted over Unix socket or named pipe. ratelimiterSecurity utility — handshake rate limiting to mitigate amplification attacks. replaySecurity utility — anti-replay sliding window for data packets. tai64nProtocol utility — TAI64N timestamp codec used in handshake messages. rwcancelI/O utility — cancellable blocking reads, used by the conn package on Unix. Layering: Loose layered design.
deviceis the central package that importsconn,tun,ipc,ratelimiter,replay, andtai64n. The utility packages (ratelimiter,replay,tai64n,rwcancel) have zero intra-project imports — they are fully self-contained.connimportsrwcancel;tun/netstackimportstun. No circular dependencies. No hexagonal/clean-architecture formalism; the structure follows protocol decomposition rather than architectural dogma.
Build system#
- Build tool: GNU Make (
Makefile) wrappinggo buildandgo test - Key targets:
all/generate-version-and-build: regeneratesversion.gofromgit describe, then builds thewireguard-gobinarywireguard-go:go build -v -o wireguard-go ./...test:go test ./...install: copies binary to$(PREFIX)/binclean: removes the binary
- Docker: None — wireguard-go is a native daemon; Docker is not used in the build or test pipeline
- Platform cross-compilation: Handled entirely via Go build tags and
GOOS/GOARCHenv vars; no special Makefile gymnastics needed
Notable structural decisions#
Root-as-main with library packages alongside: The module root is both the daemon’s
package mainand the import path for the library. Callers like Tailscale importgolang.zx2c4.com/wireguard/devicedirectly. This avoidscmd/indirection and makes the package structure slightly unusual by Go convention but maximally usable as a library.No
internal/packaging: Everything is exported. This signals that the project treats external embedding (e.g., Tailscale, the Windows client) as a first-class use case rather than an afterthought. The API stability burden is accepted.Platform specificity via file naming, not interfaces over stubs: Rather than defining platform-agnostic interfaces with full stub implementations per OS, the project uses Go’s build-tag file naming (
_linux.go,_darwin.go, etc.) at every layer (conn,tun,ipc). The interfaces (tun.Device,conn.Bind) unify them at the type level, but the per-file variation is done structurally, not abstractly.Self-contained utility packages with zero intra-project deps:
ratelimiter,replay,tai64n, andrwcancelhave no imports from within the same module. They are independently testable and re-usable, embodying single-responsibility to a degree rarely seen in projects of this complexity.Optional gVisor netstack as a subdirectory:
tun/netstackis a sub-package oftunthat pulls in the heavyweightgvisor.dev/gvisordependency. By placing it as a subdirectory rather than merging it into thetunpackage, consumers who don’t need netstack never pay the gVisor import cost.Daemonization via self-re-exec: The Unix main.go uses
os.StartProcessto re-launch itself, passing the pre-opened TUN and UAPI file descriptors as inherited FDs. This avoids a Cfork()/daemon()dependency while achieving proper background-daemon semantics — an elegant pure-Go approach to a traditionally POSIX-specific operation.