What Userspace Components Does Tailcat Use for Its Network Stack?

Tailcat implements its entire networking layer in userspace by combining WireGuard data plane logic from Tailscale's wgengine with gVisor's TCP/UDP stack, enabling kernel-free encrypted tunnels that expose standard Go net.Conn interfaces.

Tailcat is an experimental networking tool built by Tailscale that demonstrates control-plane-free WireGuard connectivity. Unlike traditional VPN implementations that rely on kernel modules or TUN devices, Tailcat operates entirely in userspace using a curated set of libraries from the Tailscale ecosystem and the gVisor project. This architecture allows the application to handle packet encryption, TCP/UDP termination, and network discovery without elevated privileges or kernel networking support.

Core WireGuard Data Plane Components

Tailcat's encryption and peer management rely on the same production-grade WireGuard implementation used across Tailscale's ecosystem.

wgengine.Engine

The wgengine.Engine drives the WireGuard data plane, handling packet encryption, peer management, and routing decisions. In tailcat.go (lines 31-35), this engine initializes the cryptographic tunnel without requiring kernel WireGuard support. It coordinates the flow of encrypted packets between peers while maintaining the security associations necessary for the tunnel.

wgengine/wgcfg

Per-peer WireGuard configuration is supplied by wgengine/wgcfg, which parses allowed IP ranges and optional pre-shared keys. According to the source in tailcat.go (lines 45-51), this component feeds the engine with cryptographic parameters and routing tables that determine which packets enter the encrypted tunnel.

tailscale.com/wireguard-go/device

The actual WireGuard UDP transport and cryptographic handshake implementation come from tailscale.com/wireguard-go/device. As seen in tailcat.go (lines 64-66), this pure-Go implementation handles the Noise protocol handshakes and packet encryption entirely in userspace, eliminating dependencies on kernel WireGuard modules.

TCP/UDP Stack Implementation

While WireGuard handles encryption, Tailcat needs a complete TCP/IP stack to terminate connections. This functionality comes from wgengine/netstack backed by gVisor.

wgengine/netstack

The wgengine/netstack package provides a full TCP/UDP stack that runs in-process with no kernel involvement. According to tailcat.go (lines 93-96), it exposes standard Go networking primitives including net.Conn, net.Listener, and net.PacketConn for use by Tailcat servers and clients. This allows applications to use familiar networking APIs while traffic flows through the encrypted WireGuard tunnel entirely in userspace.

gvisor/pkg/tcpip/stack

Underpinning the netstack package is gvisor/pkg/tcpip/stack, the underlying IPv6/IPv4 implementation from Google's gVisor project. The Tailcat source in tailcat.go (lines 66-70) imports this stack to handle IP routing, TCP congestion control, and UDP packet buffering without host kernel participation.

gvisor/pkg/tcpip/adapters/gonet

To bridge gVisor's internal networking with Go's standard library, Tailcat uses gvisor/pkg/tcpip/adapters/gonet. This adapter layer wraps the gVisor stack to provide gonet.TCPConn and gonet.UDPConn types that implement the standard net.Conn interface, as referenced in tailcat.go (lines 66-70).

Network Control and Filtering

Beyond raw packet processing, Tailcat implements traffic control and discovery mechanisms in userspace.

wgengine/filter

Inbound traffic is restricted by wgengine/filter, a packet-filter that limits reachable TCP/UDP ports based on callback registrations. The implementation in tailcat.go (lines 95-106) shows this filter enforcing the OnTCP, OnUDP, OnTCPForward, and OnUDPForward callbacks, ensuring only explicitly handled ports accept connections through the tunnel.

tailscale.com/disco

Network endpoint discovery is handled by tailscale.com/disco, which manages DERP-based discovery (via MAGICSIG) and the "meow" handshake for UDP endpoint advertisement. As shown in tailcat.go (lines 71-78), this component facilitates NAT traversal by discovering the best available paths between peers without coordination servers.

tailscale.com/net/netmon

Local network interface monitoring is performed by tailscale.com/net/netmon. According to tailcat.go (lines 96-99), this component tracks interface changes and endpoint updates, feeding real-time network conditions to the MAGICSIG layer for optimal path selection.

Connection Management and Addressing

Tailcat includes specialized components for dialing and address generation within the userspace context.

tailscale.com/tsdial.Dialer

The tsdial.Dialer implements DialContextTCP and DialContextUDP functions that forward traffic through the in-process netstack when appropriate. The source in tailcat.go (lines 105-108) demonstrates how this dialer intercepts connection requests and routes them through the WireGuard tunnel rather than the host network stack.

tailscale.com/net/tsaddr

IPv6 address generation is handled by tailscale.com/net/tsaddr, specifically via the tcAddrForKey function referenced in tailcat.go (lines 18-22). This utility encodes node public keys into unique IPv6 addresses within the Tailcat addressing scheme.

tailscale.com/net/netmap

The network-map view of peers—including addresses, allowed IPs, and DERP home information—is maintained by tailscale.com/net/netmap. As seen in tailcat.go (lines 108-112), this data structure enables the engine to make routing decisions without external control plane queries during packet forwarding.

Practical Implementation Examples

The following patterns demonstrate how these userspace components work together in tailcat.go and related files.

Running a TCP Listener via Netstack

This server implementation uses the userspace stack to accept encrypted connections:

// In your program after creating a Server s and calling s.Start()
s.OnTCP = func(port uint16) func(net.Conn) {
    return func(c net.Conn) {
        defer c.Close()
        fmt.Fprintf(c, "Hello from Tailcat on port %d!\n", port)
    }
}

// Start the server – the netstack handles the TLS/UDP transport internally.
if err := s.Start(); err != nil {
    log.Fatalf("Tailcat server start failed: %v", err)
}

Dialing Through the Userspace Tunnel

Clients connect using standard net.Dial while traffic flows through the in-process WireGuard implementation:

c, err := net.Dial("tcp", "fd00:1234::1:8080") // IPv6 address produced by tcAddrForKey
if err != nil {
    log.Fatalf("dial failed: %v", err)
}
defer c.Close()
io.Copy(os.Stdout, c) // prints the server’s greeting

UDP Forwarding with Packet Filtering

Enable UDP handling through the filtered netstack interface:

s.OnUDPForward = func(dst netip.AddrPort) func(ConnPacketConn) {
    return func(pc ConnPacketConn) {
        // Echo UDP packets back to the sender.
        buf := make([]byte, 1500)
        for {
            n, err := pc.Read(buf)
            if err != nil { return }
            pc.Write(buf[:n])
        }
    }
}

Key Source Files

These components are wired together across the Tailcat repository:

  • tailcat.go — Core server/client importing all userspace networking components (lines 31-112)
  • wire.go — CBOR wire format for Tailcat address encoding
  • tailcat_ssh.go — SSH server running over the userspace netstack
  • tailcat_sftp.go — SFTP implementation leveraging the same stack
  • cmd/tailcat/tailcat.go — CLI entry point that wires flags to userspace components

Summary

  • Tailcat operates entirely in userspace by combining Tailscale's wgengine with gVisor's networking stack, eliminating kernel module dependencies.
  • WireGuard encryption is handled by wireguard-go/device and coordinated through wgengine.Engine for cryptographic operations and peer management.
  • TCP/UDP termination occurs in wgengine/netstack, which uses gvisor/pkg/tcpip internally but exposes standard net.Conn interfaces via gonet adapters.
  • Traffic control is enforced by wgengine/filter, while disco and netmon handle NAT traversal and endpoint discovery without coordination servers.
  • Addressing and dialing use tsaddr for IPv6 generation and tsdial.Dialer for routing connections through the in-process tunnel.

Frequently Asked Questions

How does Tailcat avoid kernel networking dependencies?

Tailcat replaces kernel networking with the wgengine/netstack package, which embeds gVisor's TCP/IP implementation directly into the application process. According to tailcat.go (lines 66-70 and 93-96), this stack handles IP routing, TCP connections, and UDP sockets internally, presenting standard Go net.Conn interfaces without creating TUN devices or modifying kernel routing tables.

What role does gVisor play in Tailcat's network stack?

The gVisor project provides the foundational TCP/IP implementation through gvisor/pkg/tcpip/stack and gvisor/pkg/tcpip/adapters/gonet. As implemented in tailcat.go (lines 66-70), gVisor handles the complex state machines for TCP congestion control, retransmission, and IP fragmenting, while Tailscale's gonet adapters translate these internal structures into familiar Go networking interfaces.

How does Tailcat handle NAT traversal without a control plane?

Tailcat uses tailscale.com/disco to perform decentralized endpoint discovery via the "meow" handshake and DERP relay fallback. The source in tailcat.go (lines 71-78) shows this component advertising UDP endpoints and detecting NAT mappings directly between peers, while netmon (lines 96-99) monitors local interface changes to update available paths dynamically.

Can Tailcat run without the Tailscale coordination server?

Yes. Tailcat is designed as a control-plane-free implementation that establishes WireGuard tunnels using pre-shared keys or direct key exchange. The wgengine/wgcfg component (lines 45-51) loads static configurations, and netmap (lines 108-112) maintains peer state locally, enabling standalone operation without querying Tailscale's coordination servers or relying on external authentication infrastructure.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →