What Is the gVisor Netstack in Tailcat? A Deep Dive into Userspace TCP/IP Networking
The gVisor netstack in Tailcat is a userspace TCP/IP implementation—derived from Google's gVisor project—that enables secure, kernel-less networking by running a complete network stack inside the Go process without requiring root privileges or host network configuration changes.
Tailcat, an experimental sibling project to Tailscale, embeds this netstack at its core to provide end-to-end encrypted TCP connections over WireGuard without ever touching the operating system's kernel networking layer. This architecture enables Tailcat to run sandboxed, portable binaries that work identically across Linux, macOS, Windows, and even WebAssembly environments.
How Tailcat Implements the gVisor Netstack Architecture
The netstack integration lives in tailscale.com/wgengine/netstack, a dependency that Tailcat imports and wraps for its specific use cases. Unlike traditional networking tools that create TUN/TAP devices or modify routing tables, Tailcat instantiates the netstack entirely within user memory.
In tailcat.go, the server struct holds a pointer to the netstack implementation:
// tailcat.go L339
ns *netstack.Impl
The netstack is created during server startup via newNetstack, configured with IP addresses derived from the WireGuard public key. This eliminates the need for static IP assignment or DHCP negotiation.
Key Source File References
Tailcat's netstack integration spans several critical files:
tailcat.go: Import statement establishing the gVisor netstack dependencytailcat.go: Helper that extracts the underlying TCP/IP stack from the netstack abstractioninternal/buildtags/buildtags.go: Build tag declarations ensuring netstack code is always linked, including WebAssembly builds
The build tags are particularly significant—the ts_omit_netstack tag exists but is intentionally not used in Tailcat releases, guaranteeing the netstack remains available across all compilation targets.
Internal Packet Flow Through the Netstack
Understanding how packets move through Tailcat requires tracing four distinct stages:
1. Server Initialization
Tailcat creates a WireGuard engine with an ephemeral keypair, then calls netstack.Create to obtain a *netstack.Impl. The resulting netstack instance receives default IP addresses computed from the WireGuard public key, enabling automatic, deterministic addressing without configuration.
2. Transport Layer Encryption
The magicsock component—shared with the production Tailscale client—handles all packet transport. Magicsock selects between direct UDP connections and DERP relays for NAT traversal, feeding encrypted WireGuard packets into the netstack's receive path.
3. TCP State Machine Processing
Incoming packets pass through the gVisor-derived TCP implementation, which maintains full RFC-compliant connection state. When a listener binds to a port via the OnTCP callback, the netstack invokes the user-provided handler with a standard net.Conn interface.
4. Outbound Connection Handling
Client-side dials through DialTCPPort create new TCP connections within the netstack, with traffic flowing symmetrically through the WireGuard tunnel back to the server's netstack instance.
This design decouples the TCP/IP protocol implementation from the underlying transport, allowing Tailcat to tunnel arbitrary TCP traffic without kernel involvement.
Practical Code Examples
Minimal Echo Server
This server responds with a greeting on any TCP port:
package main
import (
"fmt"
"log"
"net"
"github.com/tailscale/tailcat"
)
func main() {
s := &tailcat.Server{
OnTCP: func(port uint16) func(net.Conn) {
return func(c net.Conn) {
fmt.Fprintf(c, "hello from port %v\n", port)
c.Close()
}
},
}
if err := s.Start(); err != nil {
log.Fatal(err)
}
fmt.Println(s.TailcatAddr()) // prints the tailcat address
select {} // block forever
}
The OnTCP callback registers a factory function that returns connection handlers—the netstack invokes this whenever a remote peer connects to a listened port.
Minimal Client Connection
This client connects to a Tailcat server and reads the response:
package main
import (
"context"
"io"
"log"
"os"
"github.com/tailscale/tailcat"
)
func main() {
// Pass the tailcat address as the first CLI argument.
cl := tailcat.NewClient(tailcat.Addr(os.Args[1]))
defer cl.Close()
c, err := cl.DialTCPPort(context.Background(), 80) // connect to port 80
if err != nil {
log.Fatal(err)
}
io.Copy(os.Stdout, c) // prints "hello from port 80"
}
Both examples appear in the official repository's README at README.md, demonstrating the library's intended public API surface.
WebAssembly Portability via Pure Go Netstack
A critical advantage of gVisor's userspace implementation is complete portability to WebAssembly. Because the netstack contains no cgo dependencies or platform-specific syscalls, Tailcat compiles to GOOS=js GOARCH=wasm without modification.
In web/main_js.go, the browser-based demo reads directly from the netstack when users interact with the UI:
// web/main_js.go L214 - netstack integration in WASM builds
This enables Tailcat to run as an in-browser peer, participating in the same encrypted mesh network as native CLI clients—a capability impossible with kernel-dependent networking stacks.
Security and Privilege Model
The gVisor netstack fundamentally reshapes Tailcat's security posture:
- No root required: The binary operates without elevated privileges, eliminating a common deployment barrier
- Host network isolation: The process never modifies routing tables, DNS configuration, or firewall rules
- Containment boundaries: Network processing occurs in memory-safe Go code rather than kernel C code
- Predictable attack surface: The TCP/IP implementation is a specific, auditable version rather than the host kernel's potentially variant stack
Summary
- The gVisor netstack in Tailcat is a userspace TCP/IP implementation providing complete network protocol handling without kernel dependencies
- Core integration lives in
tailcat.go, with the netstack pointer stored at line 339 and creation logic innewNetstack - Magicsock and WireGuard provide the encrypted transport layer, while the netstack terminates TCP connections
- Build tags in
internal/buildtags/buildtags.goensure netstack availability across all targets including WebAssembly - The architecture enables privileged-free operation, cross-platform portability, and browser-based deployment impossible with traditional networking approaches
Frequently Asked Questions
Does Tailcat require root privileges because it implements its own TCP/IP stack?
No. Because the gVisor netstack runs entirely in userspace, Tailcat operates without root privileges or capabilities. The stack processes packets in Go memory rather than through kernel networking APIs, eliminating the traditional requirement for elevated permissions when creating network interfaces or modifying routing tables.
How does the netstack differ from using a TUN device?
A TUN device would require kernel cooperation and typically elevated privileges to create. The gVisor netstack bypasses this entirely—packets flow directly from magicsock into the userspace TCP implementation without ever entering the host kernel's network stack. This enables deployment in restricted environments like containers without CAP_NET_ADMIN or browsers without any device access.
Can I use Tailcat's netstack with my own WireGuard implementation?
The *netstack.Impl type in tailcat.go is specifically wired to Tailscale's magicsock and WireGuard engine. While the underlying tailscale.com/wgengine/netstack package exposes generic interfaces, Tailcat's integration couples these components for seamless operation. Direct reuse would require reimplementing the glue logic found in tailcat.go.
Why gVisor specifically rather than another userspace stack?
Google's gVisor project provides a production-tested, RFC-compliant TCP/IP implementation written in Go with explicit sandboxing goals. Its netstack component offers the memory safety, licensing compatibility, and active maintenance that align with Tailscale's requirements. The gVisor netstack also sees wide deployment in Google's production infrastructure, providing confidence in its correctness under load.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →