# What Userspace Components Does Tailcat Use for Its Network Stack?

> Discover Tailcat's userspace network stack. Learn how it combines WireGuard and gVisor for kernel-free encrypted tunnels and standard Go net.Conn interfaces.

- Repository: [Tailscale/tailcat](https://github.com/tailscale/tailcat)
- Tags: internals
- Published: 2026-09-08

---

**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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) and related files.

### Running a TCP Listener via Netstack

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

```go
// 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:

```go
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:

```go
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`](https://github.com/tailscale/tailcat/blob/main/tailcat.go) — Core server/client importing all userspace networking components (lines 31-112)
- [`wire.go`](https://github.com/tailscale/tailcat/blob/main/wire.go) — CBOR wire format for Tailcat address encoding
- [`tailcat_ssh.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_ssh.go) — SSH server running over the userspace netstack
- [`tailcat_sftp.go`](https://github.com/tailscale/tailcat/blob/main/tailcat_sftp.go) — SFTP implementation leveraging the same stack
- [`cmd/tailcat/tailcat.go`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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`](https://github.com/tailscale/tailcat/blob/main/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.