# ratatoskr

Ratatoskr runs a [[yggdrasil-network|Yggdrasil]] node inside your own process and hands you the results as `net.Conn`, `net.Listener`, and `net.PacketConn`. There is no TUN device, no root, and no separate `yggdrasil` daemon to supervise — the data path is a [[gvisor]] netstack driven by Yggdrasil core and Ironwood. `ratatoskr.New` builds core, the gVisor TCP/UDP stack, NodeInfo querying, and an optional managed-peer selector behind a single lifecycle, and `node.DialContext` drops straight into `http.Transport`. The module has four direct external requirements: Yggdrasil core, gVisor, `golang.org/x/net`, and `go-socks5`. Requires Golang 1.25 or newer.

It solves the same problem as [[asciimoth-ygg]]'s `ygglib` + VTun, which is covered in [[yggdrasil-embedded-go]], but from a different angle: instead of forking upstream Yggdrasil to make it library-shaped, Ratatoskr keeps upstream as a dependency and wraps it, then splits the surrounding services into subpackages you can import without the root facade.

## Structure

The root package is a facade. Each `mod/` subpackage depends on a narrow local interface rather than on `ratatoskr.Obj`, so you can use `mod/socks` with your own dialer, or `mod/forward` with any five-method network implementation, and never construct a node at all.

- `mod/core` — Yggdrasil core, the gVisor NIC and sockets, identity, peers, multicast
- `mod/peermgr` — validates peer candidates, probes them in bounded batches, keeps the lowest-latency one per protocol (`MaxPerProto`)
- `mod/socks` — SOCKS5 over TCP or a Unix socket, with TCP and UDP ASSOCIATE limits
- `mod/resolver` — resolves `<64-hex>.pk.ygg`, IP literals, and optionally real DNS through a supplied dialer
- `mod/forward` — immutable TCP/UDP mappings between the local network and Yggdrasil
- `mod/ninfo` — remote NodeInfo lookup, with concurrent queries for the same target coalesced
- `mod/probe` — bounded breadth-first topology discovery, spanning-tree paths, route traces
- `mod/sigils` — typed NodeInfo fragments

Sigils are the more interesting design idea. Yggdrasil NodeInfo is an open `map[string]any`, which makes it useless to software that wants a schema and unusable as a schema if you close it. A sigil owns a group of NodeInfo keys, validates local data, publishes those keys, and recognizes the same shape coming back from a remote node. Four ship built in: `info` (name, role, location, contacts), `services` (named ports), `public` (peering endpoints by transport), and `inet` (public Internet addresses). A key conflict between sigils, or between a sigil and `Config.NodeInfo`, aborts `New` with `ErrInvalidSigils` rather than publishing partial metadata. `Ask` and `AskAddr` parse the sigils they know and preserve unknown-but-valid ones untouched.

## Usage

```go
node, err := ratatoskr.New(ratatoskr.ConfigObj{
    Ctx: ctx,
    Peers: &peermgr.ConfigObj{
        Peers:       []string{"tls://peer.example:17117", "quic://peer.example:17117"},
        MaxPerProto: 1,
    },
})
if err != nil {
    panic(err)
}
defer node.Close()

client := &http.Client{Transport: &http.Transport{DialContext: node.DialContext}}
```

A `nil` `Config` generates random keys and disables the admin listener; for a stable identity you load a persisted `config.NodeConfig` (HJSON, following `PrivateKeyPath`) before calling `New`. Setting peers in both `Config.Peers` and `ConfigObj.Peers` fails with `ErrPeersConflict`, since two owners would be managing one peer set. Listeners come from `node.Listen("tcp", ":8080")` and live inside the userspace stack. `Close` is safe to call repeatedly, runs dependents concurrently, then the core, all under one `CloseTimeout` budget (10s by default); overrunning it returns `ErrCloseTimedOut` and lets teardown finish in the background instead of blocking the caller.

## Distribution through Yggdrasil

Ratatoskr publishes itself three ways, and they are three distinct Golang module identities that must not be mixed inside one module: the canonical `github.com/voluminor/ratatoskr`, an HTTPS mirror at `ratatoskr.space/pkg/ratatoskr`, and the same mirror reachable over the mesh itself at `14cc7d57b5e70f679b851fe5b272ce17c70632ff4beb5b35ab64bc706b2485af.pk.ygg`. The mesh route needs `GOPROXY` pointed at the host, `GOSUMDB=off` (the public checksum database indexes the GitHub path, not the rewritten one), and `GOINSECURE` for that host because traffic inside the encrypted overlay is plain HTTP:

```sh
YGG_HOST="14cc7d57b5e70f679b851fe5b272ce17c70632ff4beb5b35ab64bc706b2485af.pk.ygg"
GOPROXY="http://${YGG_HOST}" GOSUMDB=off GOINSECURE="${YGG_HOST}/*" \
  go get "${YGG_HOST}/pkg/ratatoskr@latest"
```

That is the module-proxy protocol used as ordinary transport over an overlay network, the same property that [[cursed-bundler-go-get-ruby-gems]] exploits in the other direction.

## Caveats

Throughput is the price of a userspace stack. The project's own benchmark against a direct Docker path on one host puts TCP at 198 MiB/s median versus 4,809 MiB/s (4.1%) and UDP at 44.5 MiB/s versus 440 MiB/s (10.1%, and that is receiver goodput under 24% packet loss, not lossless capacity). Profiles put the cost in Yggdrasil and Ironwood cryptography, gVisor packet handling, syscalls, and copies — not in Ratatoskr's own control code, which is the useful part of the result but also means there is no fix available at this layer.

`Core().EnableAdmin` is documented as deliberately unhardened: it passes straight through to upstream Yggdrasil's admin implementation, which can call `os.Exit(1)` on a bad address or a socket cleanup failure, has no authentication, no request-size cap, and no per-connection deadline, and can leave keepalive connections alive past `DisableAdmin`. Treat access to it as full process control.

In `mod/forward`, `MaxTCPConnections == 0` and `MaxUDPSessions == 0` mean unlimited, so a mapping reachable by untrusted nodes will happily allocate goroutines per accepted connection and per UDP source. Set both explicitly at any public boundary.

CI runs on Golang 1.26.5 and tests release source on Linux, macOS, and Windows, with compile checks across 25 GOOS/GOARCH combinations including FreeBSD, OpenBSD, and NetBSD. Unix sockets and some Yggdrasil transports are platform-specific. Note also that the development branch does not track generated files — a checkout needs the bootstrap in `CONTRIBUTING.md`, while tagged releases ship the generated `target` package and a complete `go.mod`.

## Repo

[github.com/voluminor/ratatoskr](https://github.com/voluminor/ratatoskr) — Go, LGPL-2.1. Single-author project, first releases in 2026.
