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#

FileBuild conditionBinary producedPurpose
main.go!windowswireguard-goFull daemon: creates TUN, opens UAPI socket, optionally daemonizes, handles signals
main_windows.gowindowswireguard-goTest/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:

    PackageRole
    deviceCore engine — the WireGuard protocol, peer management, cryptography, packet routing. The Device struct 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. device is the central package that imports conn, tun, ipc, ratelimiter, replay, and tai64n. The utility packages (ratelimiter, replay, tai64n, rwcancel) have zero intra-project imports — they are fully self-contained. conn imports rwcancel; tun/netstack imports tun. No circular dependencies. No hexagonal/clean-architecture formalism; the structure follows protocol decomposition rather than architectural dogma.

Build system#

  • Build tool: GNU Make (Makefile) wrapping go build and go test
  • Key targets:
    • all / generate-version-and-build: regenerates version.go from git describe, then builds the wireguard-go binary
    • wireguard-go: go build -v -o wireguard-go ./...
    • test: go test ./...
    • install: copies binary to $(PREFIX)/bin
    • clean: 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/GOARCH env vars; no special Makefile gymnastics needed

Notable structural decisions#

  1. Root-as-main with library packages alongside: The module root is both the daemon’s package main and the import path for the library. Callers like Tailscale import golang.zx2c4.com/wireguard/device directly. This avoids cmd/ indirection and makes the package structure slightly unusual by Go convention but maximally usable as a library.

  2. 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.

  3. 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.

  4. Self-contained utility packages with zero intra-project deps: ratelimiter, replay, tai64n, and rwcancel have 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.

  5. Optional gVisor netstack as a subdirectory: tun/netstack is a sub-package of tun that pulls in the heavyweight gvisor.dev/gvisor dependency. By placing it as a subdirectory rather than merging it into the tun package, consumers who don’t need netstack never pay the gVisor import cost.

  6. Daemonization via self-re-exec: The Unix main.go uses os.StartProcess to re-launch itself, passing the pre-opened TUN and UAPI file descriptors as inherited FDs. This avoids a C fork()/daemon() dependency while achieving proper background-daemon semantics — an elegant pure-Go approach to a traditionally POSIX-specific operation.