Skip to main content

Server

Struct Server 

Source
pub struct Server {
Show 14 fields pub hostname: Option<String>, pub auth_key: Option<String>, pub control_url: Option<String>, pub ephemeral: bool, pub advertise_tags: Vec<String>, pub port: Option<u16>, pub run_web_client: bool, pub client_id: Option<String>, pub client_secret: Option<String>, pub id_token: Option<String>, pub audience: Option<String>, pub dir: Option<PathBuf>, pub store: Option<Arc<dyn StateStore>>, pub tun: Option<TunSpec>, /* private fields */
}
Expand description

An embedded Tailscale node, shaped like Go’s tsnet.Server.

Set the public fields (they map onto Config; see the table in docs/TSNET_FACADE_DESIGN.md), then call any method — the wrapped Device is constructed lazily on the first method call (Go: “fields may be changed until the first method call”). In Rust this ordering is enforced by the borrow checker: methods borrow &self, so you cannot mutate a field after the first call.

For fork capabilities beyond Go tsnet parity (accept-routes, exit nodes, residential-proxy exit egress, …), reach the underlying Config with Server::configure, or drop to the full engine surface with Server::device.

Fields§

§hostname: Option<String>

Hostname to present to control (Go Hostname) → Config::requested_hostname. Empty ⇒ engine default.

§auth_key: Option<String>

Auth key to register with (Go AuthKey) → Config::auth_key and the auth_key arg of Device::new. Falls back to TS_AUTH_KEY.

§control_url: Option<String>

Coordination server URL (Go ControlURL) → Config::control_server_url. None ⇒ the fork’s default control server.

§ephemeral: bool

Register as an ephemeral node (Go Ephemeral) → Config::ephemeral. Defaults to false to match Go (note: bare Config::default defaults this to true).

§advertise_tags: Vec<String>

ACL tags to advertise (Go AdvertiseTags) → Config::requested_tags.

§port: Option<u16>

WireGuard/peer-to-peer UDP port pin (Go Port) → Config::wireguard_listen_port.

§run_web_client: bool

Run the node web client (Go RunWebClient) → Config::run_web_client. Partial: the pref is carried but no embedded web client runs (see docs/TSNET_PARITY.md).

§client_id: Option<String>

OAuth/WIF client id (Go ClientID) → Config::client_id.

§client_secret: Option<String>

OAuth client secret (Go ClientSecret) → Config::client_secret.

§id_token: Option<String>

IdP-issued OIDC token (Go IDToken) → Config::id_token.

§audience: Option<String>

Audience for a requested ID token (Go Audience) → Config::audience.

§dir: Option<PathBuf>

State directory (Go Dir). Persists node identity keys to dir/STATE_FILE via the engine’s key-file format. Absent store, this is the standard on-disk identity.

§store: Option<Arc<dyn StateStore>>

A pluggable state store (Go Store). Takes precedence over Server::dir. None + no dir ⇒ an ephemeral in-memory identity (fresh every run).

§tun: Option<TunSpec>

Run over a real kernel TUN interface instead of the userspace netstack (Go Tun) → Config::use_tun.

Implementations§

Source§

impl Server

Source

pub fn new() -> Self

A new server with Go-default fields (not ephemeral, default control server, no persistence).

Source

pub fn configure<F>(&mut self, f: F) -> &mut Self
where F: Fn(&mut Config) + Send + Sync + 'static,

Register a hook to customize the derived Config just before the device is built — the escape hatch to fork capabilities that have no Go tsnet field. Ignored if the server has already started.

Source

pub async fn start(&self) -> Result<(), Error>

Connect to the tailnet (Go Start). Idempotent — subsequent calls are no-ops.

Source

pub async fn up(&self, timeout: Option<Duration>) -> Result<Status, Error>

Connect and wait until the node is Running, returning its status (Go Up). timeout None waits forever.

Source

pub async fn device(&self) -> Result<&Device, Error>

The wrapped Device (Go LocalClient-and-more), starting it if needed. The escape hatch to the full engine surface (whois, ping, set_* prefs, taildrop, TKA, …).

Source

pub async fn dial(&self, network: &str, addr: &str) -> Result<DialConn, Error>

Dial a tailnet address over TCP or UDP (Go Dial(ctx, network, address)), returning the tsnet-shaped DialConn whose arm matches the transport.

network is one of "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"; addr is a host:port string — a MagicDNS name or an IP literal (bracketed for IPv6, [2001:db8::1]:443). The network string is parsed at the facade boundary, so an unsupported network is a typed Error::UnsupportedNetwork reported before the device is started (fail-fast, no network I/O). For the common case, Server::dial_tcp / Server::dial_udp hand back the transport’s stream / socket directly.

Routing: the unsuffixed "tcp"/"udp" forward to the transport-specific typed accessors Device::dial_tcp / Device::dial_udp (for Family::Any these are Device::dial’s arms); the family-pinned …4/…6 forms forward to Device::dial, which enforces the v4/v6 constraint that the family-agnostic sub-calls do not.

Source

pub async fn dial_tcp(&self, addr: &str) -> Result<TcpStream, Error>

Dial a tailnet TCP address, yielding the overlay stream directly — the common case of Server::dial for "tcp". This is the building block for HTTP-over-tailnet: a hyper/ reqwest connector dials with dial_tcp(&format!("{host}:{port}")), mirroring Go tsnet.Server.HTTPClient.

Source

pub async fn dial_udp(&self, addr: &str) -> Result<ConnectedUdpSocket, Error>

Dial a tailnet UDP address, yielding the connected overlay socket directly — the "udp" sibling of Server::dial_tcp and the common case of Server::dial for "udp".

Returns a ConnectedUdpSocket (send/recv against a fixed peer) — the connected-net.Conn shape Go’s Dial("udp", …) returns, as opposed to Server::listen_packet’s unconnected packet socket.

Source

pub async fn listen( &self, network: &str, addr: &str, ) -> Result<TcpListener, Error>

Listen for inbound TCP on the tailnet (Go Listen).

network is a stream network — "tcp", "tcp4", or "tcp6"; a "udp*" or unknown value is Error::InvalidNetwork (Go’s net.Listen likewise rejects a packet network). addr may be a bare ":80" — the wildcard host, 0.0.0.0 for tcp/tcp4 and [::] for tcp6 — or a full "100.x.y.z:80". Returns the std::net-style overlay netstack::TcpListener (backed by ts_netstack_smoltcp): .accept() it for inbound streams, exactly as Go .Accept()s the returned net.Listener.

Source

pub async fn listen_packet( &self, network: &str, addr: &str, ) -> Result<UdpSocket, Error>

Listen for inbound UDP on the tailnet (Go ListenPacket).

network is a packet network — "udp", "udp4", or "udp6"; a "tcp*" or unknown value is Error::InvalidNetwork. addr is a host:port IP literal (a bare ":0" is filled with the family’s wildcard host); like Go’s ListenPacket, a MagicDNS name is not accepted — and neither does Server::listen accept one (both bind by IP literal; only Server::dial resolves MagicDNS names). An unspecified host binds this node’s tailnet address. Returns the std::net-style overlay netstack::UdpSocket (backed by ts_netstack_smoltcp), a net.PacketConn analog (recv_from/send_to).

Source

pub async fn listen_funnel( &self, cfg: &ServeConfig, opts: FunnelOptions, ) -> Result<FunnelAcceptedReceiver, ListenFunnelError>

Expose a tailnet TLS service to the public internet via Tailscale Funnel (Go ListenFunnel).

A thin wrapper over Device::listen_funnel: it lazily starts the node, converts FunnelOptions into the engine’s ts_control::FunnelOptions serve config, and hands cfg (the MagicDNS name + tailnet port) straight through. On success it yields the engine’s FunnelAcceptedReceiver.

Errors are a ListenFunnelError, which keeps a lifecycle/start failure (Start) distinct from the engine’s typed Funnel error (Funnel): a node that never registered surfaces as a startup failure carrying its real cause, never misdiagnosed as a Funnel access denial.

Source

pub async fn listen_service( &self, name: &str, mode: ServiceMode, ) -> Result<ServiceListener, ListenServiceError>

Host a Tailscale VIP service (Go ListenService), returning a Go-shaped ServiceListener (overlay listener + resolved FQDN).

A thin wrapper over Device::listen_service: it lazily starts the node, passes name + the ServiceMode serve config straight through, and pairs the returned overlay listener with the node’s resolved service FQDN.

Errors are a ListenServiceError, which keeps a lifecycle/start failure (Start) distinct from the engine’s typed ServiceError (Service, whose UntaggedHost == Go ErrUntaggedServiceHost): a node that never registered surfaces as a startup failure carrying its real cause, never misdiagnosed as a listener bind failure.

Source

pub async fn loopback(&self) -> Result<Loopback, Error>

Start (once) the loopback surface and return its addresses + both credentials (Go Loopback() (addr, proxyCred, localAPICred, err)).

Brings up two things, living for the Server’s lifetime (torn down by Server::close):

Idempotent: repeated calls return the same addresses and credentials. See Loopback for the one honest delta from Go (two 127.0.0.1 listeners rather than one muxed listener).

Source

pub async fn local_client(&self) -> Result<LocalClient, Error>

A LocalClient for this node’s in-process LocalAPI HTTP server (Go tsnet.Server.LocalClient()), starting the loopback surface if needed.

The returned client authenticates with the loopback’s local_api_cred and speaks plain HTTP to 127.0.0.1 (no hyper dependency). For typed status prefer the in-process Server::status; the LocalClient is the Go-shaped path that actually round-trips through the LocalAPI server.

Source

pub async fn http_client<B>(&self) -> Result<Client<TailnetConnector, B>, Error>
where B: Body + Send + 'static, B::Data: Send,

A hyper HTTP client whose every request egresses over the tailnet (Go tsnet.Server.HTTPClient()), built over Device::http_connector.

The exact analog of Go’s &http.Client{Transport: &http.Transport{DialContext: s.Dial}}: a pooled hyper_util client wired to the tailnet connector, with TLS/redirects/pooling left to the client (the connector is plaintext — see TailnetConnector for wrapping it in TLS). B is your request body type (e.g. String, or http_body_util::Full<Bytes>).

Available only with the hyper crate feature (as in the engine).

Source

pub async fn tailscale_ips(&self) -> Result<(Ipv4Addr, Option<Ipv6Addr>), Error>

This node’s tailnet addresses (Go TailscaleIPs). The tuple shape mirrors Device::tailscale_ips verbatim (v6 is None on an IPv4-only tailnet).

Source

pub async fn listen_tls( &self, cfg: &ServeConfig, ) -> Result<TlsAcceptor, CertError>

Build a TlsAcceptor that terminates TLS for cfg.name on the tailnet overlay using this node’s own certificate (Go tsnet.Server.ListenTLS’s cert path).

A thin delegation to Device::listen_tls. The serve config is validated at the facade boundary first — a non-tailnet cfg.name or a zero cfg.port is a typed ts_control::CertError returned before the lazy device start, so a misconfiguration never touches the network (fail-fast, exactly as Server::dial/Server::listen reject a bad network/address up front). The certificate is then acquired through ts_control::tls via the node’s ACME-aware cert path.

acme feature. Issuance is fail-closed: with the acme feature this issues a real Let’s Encrypt certificate (DNS-01, published via the node’s set-dns RPC — SaaS-only); without it (the default) it surfaces ts_control::CertError::Unimplemented rather than ever serving a self-signed cert or downgrading to plaintext. Either way the acceptor is ring-only (ts_control::tls_acceptor pins the ring provider — no aws-lc/openssl).

This keeps the fork’s typed ts_control::CertError (matching Device::listen_tls), not the unified lifecycle Error — the cert path is a specialized, typed surface (design doc §7). Like Go’s ListenTLS, terminate accepted overlay streams (from a Server::listen listener) with ts_control::accept_tls, reusing the one acceptor across connections.

Source

pub async fn cert_pair( &self, name: &str, min_validity: Option<Duration>, ) -> Result<(String, String), CertError>

Issue a real Let’s Encrypt certificate for this node’s MagicDNS name and return the PEM pair (cert_chain_pem, key_pem) — the analog of Go LocalClient().CertPair / CertPairWithValidity, for writing an on-disk .crt + .key. A thin delegation to Device::cert_pair; acme feature only (the wrapped engine method is itself acme-gated).

The name is checked at the facade boundary first — a non-tailnet (*.ts.net) name is ts_control::CertError::NotTailnetName before any device start (anti-leak: this fork never mints certs for off-tailnet names). min_validity is accepted for Go signature parity but does not change behavior: this fork keeps no cert cache and always issues fresh, so a freshly issued (full-lifetime) cert satisfies any min_validity (see Device::cert_pair). The second tuple element is secret key material — persist it to a 0600 file and never log it. Fail-closed and ring-only, like Server::listen_tls.

Source

pub async fn cert_domains(&self) -> Result<Vec<String>, Error>

The DNS names this node may obtain TLS certificates for (Go tsnet.Server.CertDomains).

A thin delegation to Device::cert_domains: the CertDomains control pushed in the netmap DNS config — the names to request a cert for via Server::listen_tls (or, with acme, cert_pair). Empty before the first netmap, or when control granted none (Go returns a clone of nm.DNS.CertDomains). Unlike the cert-issuance calls this only reads netmap state, so it returns the unified lifecycle Error.

Source

pub async fn status(&self) -> Result<Status, Error>

Node + peer status (Go LocalClient().Status).

Source

pub async fn logout(&self) -> Result<(), Error>

Log this node out (Go LocalClient().Logout).

Source

pub async fn close(self, timeout: Option<Duration>) -> bool

Stop the server (Go Close). Consumes self; returns whether shutdown completed within timeout (None = wait forever). A never-started server closes cleanly.

Tears down the loopback surface first (aborting the SOCKS5 and LocalAPI accept loops), then gracefully shuts the wrapped device down. The LocalAPI server holds only a Weak to the device, so this reclaims the sole strong reference for the graceful shutdown.

Trait Implementations§

Source§

impl Default for Server

Source§

fn default() -> Server

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl !Freeze for Server

§

impl !RefUnwindSafe for Server

§

impl !UnwindSafe for Server

§

impl Send for Server

§

impl Sync for Server

§

impl Unpin for Server

§

impl UnsafeUnpin for Server

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Converts Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Converts &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Converts &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSend for T
where T: Any + Send,

Source§

fn into_any_send(self: Box<T>) -> Box<dyn Any + Send>

Converts Box<Trait> (where Trait: DowncastSend) to Box<dyn Any + Send>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

Source§

fn into_any_sync(self: Box<T>) -> Box<dyn Any + Sync + Send>

Converts Box<Trait> (where Trait: DowncastSync) to Box<dyn Any + Send + Sync>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Converts Arc<Trait> (where Trait: DowncastSync) to Arc<Any>, which can then be downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<A, T> DynMessage<A> for T
where A: Actor + Message<T>, T: Send + 'static,

Source§

fn handle_dyn<'a>( self: Box<T>, state: &'a mut A, actor_ref: ActorRef<A>, tx: Option<Sender<Result<Box<dyn Any + Send>, SendError<Box<dyn Any + Send>, Box<dyn Any + Send>>>>>, stop: &'a mut bool, ) -> Pin<Box<dyn Future<Output = Result<(), Box<dyn ReplyError>>> + Send + 'a>>

Handles the dyn message with the provided actor state, ref, and reply sender.
Source§

fn as_any(self: Box<T>) -> Box<dyn Any>

Casts the type to a Box<dyn Any>.
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more