Expand description
A Go-idiomatic tsnet.Server-shaped facade over
Device and Config, behind the tsnet cargo feature.
§Why this exists
The fork’s native embedding surface is Device + Config: you build a Config, then
Device::new(&config, auth_key).await. That is idiomatic Rust (construct-from-config, typed
errors) and is a documented superset of Go tsnet (see docs/TSNET_PARITY.md). But a large
body of existing code — and muscle memory — is written against Go’s shape:
srv := &tsnet.Server{Hostname: "web", AuthKey: key}
ln, _ := srv.Listen("tcp", ":80")
defer srv.Close()Server is a thin, Go-shaped ergonomics layer over the existing engine. It changes nothing
about the dataplane, control, or netstack: it maps a set of Go-tsnet.Server-parity fields onto
Config, defers construction of the wrapped Device until the first method call (Go’s
“fields may be changed until the first method call”), and forwards every call straight to the
Device it wraps. It is deliberately not a re-implementation and not a new crate.
§What stays Rust-native (on purpose)
“Go-idiomatic” here means the lifecycle model and method names (settable fields, lazy
start, up/close, Listen/Dial/Loopback/ListenFunnel/ListenService), not Go’s
return types. A thin facade must not re-wrap the engine’s types into net.Conn/net.Listener
trait objects — that would be a translation layer, not a facade, and would throw away the fork’s
typed returns. So:
- inbound/outbound connections keep their engine types (
DialConn,netstack::TcpListener, …); - specialized calls keep the fork’s typed errors (
ServiceError,FunnelError) — carried unchanged as a variant of a thin wrapper (ListenFunnelError/ListenServiceError) whose other variant keeps a lazy-start failure distinct from the engine’s typed error (so a node that never registered is never misreported as an access denial); the plain lifecycle path — a single opaqueerrorin Go — unifies intoError; - addresses are accepted as Go-style
network, addrstrings ("tcp",":80") for familiarity, and parsed to the typedSocketAddrthe engine wants.
See docs/TSNET_FACADE_DESIGN.md for the full rationale, the field-by-field mapping table, and
the state-root (Dir/Store) design over Config::key_state.
§Go tsnet.Server → tsnet::Server, at a glance
Construct with Server::new, set the public fields where Go sets struct fields, then call a
method — which lazily builds and starts the wrapped Device. Method names are Go’s (in Rust
snake_case); return types stay the fork’s typed values (see What stays Rust-native, above). Each
item’s own rustdoc names its Go equivalent; the full field-by-field mapping with verdicts lives in
docs/TSNET_FACADE_DESIGN.md, and the authoritative parity matrix in docs/TSNET_PARITY.md.
§Fields — set before the first method call
Go tsnet.Server field | Field on Server |
|---|---|
Hostname | Server::hostname |
AuthKey | Server::auth_key |
ControlURL | Server::control_url |
Ephemeral | Server::ephemeral |
AdvertiseTags | Server::advertise_tags |
Port | Server::port |
Dir | Server::dir |
Store | Server::store (a StateStore) |
Tun | Server::tun |
RunWebClient | Server::run_web_client |
ClientID / ClientSecret / IDToken / Audience | Server::client_id / Server::client_secret / Server::id_token / Server::audience |
(no Go field — fork escape hatch to Config) | Server::configure |
§Methods — each lazily starts the node on first use
Go tsnet.Server | Method | Returns |
|---|---|---|
Start() | Server::start | () |
Up(ctx) | Server::up | Status |
Dial(ctx, network, addr) | Server::dial | DialConn |
| (TCP fast path) | Server::dial_tcp | netstack::TcpStream |
| (UDP fast path) | Server::dial_udp | ConnectedUdpSocket |
Listen(network, addr) | Server::listen | netstack::TcpListener |
ListenPacket(network, addr) | Server::listen_packet | netstack::UdpSocket |
ListenFunnel(…) | Server::listen_funnel | a Funnel accept receiver |
ListenService(name, mode) | Server::listen_service | ServiceListener |
Loopback() | Server::loopback | Loopback |
LocalClient() | Server::local_client | LocalClient |
HTTPClient() | Server::http_client (feature hyper) | a hyper client |
TailscaleIPs() | Server::tailscale_ips | (Ipv4Addr, Option<Ipv6Addr>) |
CertDomains() | Server::cert_domains | Vec<String> |
LocalClient().Status | Server::status | Status |
LocalClient().Logout | Server::logout | () |
Close() | Server::close | bool (shut down cleanly within the timeout?) |
Sys() / full LocalClient() | Server::device | Device reference — the whole engine surface |
Lifecycle errors unify into the Go-shaped Error; the specialized Funnel/Service calls keep
their fork-typed errors while distinguishing a lazy-start failure from the engine error
(Server::listen_funnel → ListenFunnelError over ts_control::FunnelError,
Server::listen_service → ListenServiceError over ServiceError).
Structs§
- File
Store - An on-disk JSON
StateStorerooted at a single file (Gostore.FileStore). - Funnel
Options - Options for
Server::listen_funnel— the Rust analog of Go’s variadic...FunnelOption(FunnelOnly,FunnelTLSConfig). - Local
Client - A minimal client for the in-process LocalAPI HTTP server started alongside the loopback proxy —
the Rust analog of what Go’s
tsnet.Server.LocalClient()returns (a*local.Clientwired to the node’s own LocalAPI). - Loopback
- The result of
Server::loopback— the full GoLoopback() (addr, proxyCred, localAPICred, err)surface: both credentials, and an in-process LocalAPI HTTP server actually running. - MemStore
- An in-memory
StateStore(Gomem.Store): identity is regenerated every run and never persisted. This is the implicit behavior when neitherServer::dirnorServer::storeis set; provide it explicitly to be unambiguous. - Server
- An embedded Tailscale node, shaped like Go’s
tsnet.Server. - Service
Listener - A listener for a hosted Tailscale VIP service, the Rust analog of Go’s
*tsnet.ServiceListener(anet.Listenerplus the service’sFQDN). - TunSpec
- How to run the application overlay over a real kernel TUN interface (Go
Server.Tun).
Enums§
- Error
- The unified error for
Server’s lifecycle surface (start/up/dial/listen/…). - Listen
Funnel Error - Why
Server::listen_funnelfailed — a lifecycle/start failure kept distinct from the engine’s typed Funnel error. - Listen
Service Error - Why
Server::listen_servicefailed — a lifecycle/start failure kept distinct from the engine’s typed VIP-service error.
Constants§
- STATE_
FILE - Default on-disk state file name under
Server::dir(Go persists node state toDir/tailscaled.state; this fork persists only node identity keys, see the module docs anddocs/TSNET_FACADE_DESIGN.md§“State root”). - STATE_
KEY - The well-known key under which the node identity blob (
PersistState) is stored in aStateStore(Go stores the machine key + profile under fixed keys in theStateStore).
Traits§
- State
Store - A pluggable key/value state store — the Rust analog of Go’s
ipn.StateStore(Server::store).
Type Aliases§
- Configure
Hook - A hook to customize the derived
Configafter the field mapping and before the device is built — the escape hatch to fork capabilities with no Gotsnetfield. SeeServer::configure.