Skip to main content

Module tsnet

Module tsnet 

Source
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 opaque error in Go — unifies into Error;
  • addresses are accepted as Go-style network, addr strings ("tcp", ":80") for familiarity, and parsed to the typed SocketAddr the 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.Servertsnet::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 fieldField on Server
HostnameServer::hostname
AuthKeyServer::auth_key
ControlURLServer::control_url
EphemeralServer::ephemeral
AdvertiseTagsServer::advertise_tags
PortServer::port
DirServer::dir
StoreServer::store (a StateStore)
TunServer::tun
RunWebClientServer::run_web_client
ClientID / ClientSecret / IDToken / AudienceServer::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.ServerMethodReturns
Start()Server::start()
Up(ctx)Server::upStatus
Dial(ctx, network, addr)Server::dialDialConn
(TCP fast path)Server::dial_tcpnetstack::TcpStream
(UDP fast path)Server::dial_udpConnectedUdpSocket
Listen(network, addr)Server::listennetstack::TcpListener
ListenPacket(network, addr)Server::listen_packetnetstack::UdpSocket
ListenFunnel(…)Server::listen_funnela Funnel accept receiver
ListenService(name, mode)Server::listen_serviceServiceListener
Loopback()Server::loopbackLoopback
LocalClient()Server::local_clientLocalClient
HTTPClient()Server::http_client (feature hyper)a hyper client
TailscaleIPs()Server::tailscale_ips(Ipv4Addr, Option<Ipv6Addr>)
CertDomains()Server::cert_domainsVec<String>
LocalClient().StatusServer::statusStatus
LocalClient().LogoutServer::logout()
Close()Server::closebool (shut down cleanly within the timeout?)
Sys() / full LocalClient()Server::deviceDevice 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_funnelListenFunnelError over ts_control::FunnelError, Server::listen_serviceListenServiceError over ServiceError).

Structs§

FileStore
An on-disk JSON StateStore rooted at a single file (Go store.FileStore).
FunnelOptions
Options for Server::listen_funnel — the Rust analog of Go’s variadic ...FunnelOption (FunnelOnly, FunnelTLSConfig).
LocalClient
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.Client wired to the node’s own LocalAPI).
Loopback
The result of Server::loopback — the full Go Loopback() (addr, proxyCred, localAPICred, err) surface: both credentials, and an in-process LocalAPI HTTP server actually running.
MemStore
An in-memory StateStore (Go mem.Store): identity is regenerated every run and never persisted. This is the implicit behavior when neither Server::dir nor Server::store is set; provide it explicitly to be unambiguous.
Server
An embedded Tailscale node, shaped like Go’s tsnet.Server.
ServiceListener
A listener for a hosted Tailscale VIP service, the Rust analog of Go’s *tsnet.ServiceListener (a net.Listener plus the service’s FQDN).
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/…).
ListenFunnelError
Why Server::listen_funnel failed — a lifecycle/start failure kept distinct from the engine’s typed Funnel error.
ListenServiceError
Why Server::listen_service failed — 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 to Dir/tailscaled.state; this fork persists only node identity keys, see the module docs and docs/TSNET_FACADE_DESIGN.md §“State root”).
STATE_KEY
The well-known key under which the node identity blob (PersistState) is stored in a StateStore (Go stores the machine key + profile under fixed keys in the StateStore).

Traits§

StateStore
A pluggable key/value state store — the Rust analog of Go’s ipn.StateStore (Server::store).

Type Aliases§

ConfigureHook
A hook to customize the derived Config after the field mapping and before the device is built — the escape hatch to fork capabilities with no Go tsnet field. See Server::configure.