Skip to main content

Runtime

Struct Runtime 

Source
pub struct Runtime {
    pub control: ActorRef<ControlRunner>,
    pub peer_tracker: WeakActorRef<PeerTracker>,
    /* private fields */
}
Expand description

The runtime for a tailscale device.

Fields§

§control: ActorRef<ControlRunner>

Reference to the control actor.

§peer_tracker: WeakActorRef<PeerTracker>

Reference to the peer tracker for peer lookups.

Implementations§

Source§

impl Runtime

Source

pub async fn spawn( config: Config, auth_key: Option<String>, keys: NodeState, ) -> Result<Self, Error>

Spawn a new runtime with the given parameters for connecting to a tailnet.

Source

pub fn register_fallback_tcp_handler( &self, cb: Arc<dyn Fn(SocketAddr, SocketAddr) -> FallbackDecision + Send + Sync>, ) -> Result<FallbackTcpHandle, Error>

Register a fallback TCP handler consulted for every inbound TCP flow that matches no explicit listener (tsnet.Server.RegisterFallbackTCPHandler parity).

The returned fallback_tcp::FallbackTcpHandle deregisters the handler when dropped. See fallback_tcp for the dispatch contract and anti-leak guarantees.

Returns ErrorKind::UnsupportedInTunMode in TUN transport mode, where there is no application netstack to attach a fallback handler to.

Source

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

Get a channel to send commands to the netstack.

Returns ErrorKind::UnsupportedInTunMode in TUN transport mode, where there is no application netstack.

Source

pub fn taildrop_store(&self) -> Option<Arc<TaildropStore>>

The Taildrop file store, if Taildrop is enabled (taildrop_dir configured and the store initialized). None when disabled — fail-closed. Shared with the peerAPI Taildrop server so the embedder’s read APIs and the receive path see the same on-disk store.

Source

pub fn funnel_ingress_slot(&self) -> FunnelIngressSlot

The shared Funnel ingress slot the peerAPI /v0/ingress route reads per connection.

Device::listen_funnel installs a FunnelManager’s sink here to make the route live (the peerAPI server is already running from startup). Returns a clone of the runtime-lifetime Arc so the device can write the slot without restarting the server. See crate::funnel for the ingress data path.

Source

pub fn ingress_active_flag(&self) -> Arc<AtomicBool>

The shared “Funnel ingress listener active” flag (the same Arc the control session reads to set HostInfo.IngressEnabled). Device::listen_funnel flips it true while a funnel listener is up so control routes Funnel traffic to this node; clearing it advertises no live endpoint.

Source

pub async fn install_capture( &self, hook: Option<CaptureHook>, ) -> Result<(), Error>

Install (Some) or clear (None) the debug packet-capture hook on the running dataplane. Some(hook) tees every plaintext packet crossing the datapath to hook until it is cleared; None stops capture. Mirrors Go tstun.Wrapper.InstallCaptureHook / ClearCaptureSink.

Source

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

Re-bind the underlay UDP socket after a network/link change (Wi-Fi switch, sleep/wake). The embedder’s own link monitor calls this (the engine owns the socket re-bind; the embedder owns OS netmon). Re-binds the socket (same-port-preferred, IPv4-only invariant preserved) and resets the now-stale local NAT mapping — clearing learned reflexive addresses and every confirmed direct path while keeping candidate endpoints, so peers re-probe over the new socket and relay over DERP (never a direct host dial) until a path re-confirms. Peers, control, the netmap, disco state, and DERP are untouched. A no-op when the underlay is inert (bind failed at startup, DERP-only). Mirrors Go magicsock Conn.Rebind + resetEndpointStates.

Source

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

A snapshot of the local netmap: this node plus every known peer.

Combines the self node held by the control runner with the peer set held by the peer tracker. Mirrors tsnet’s LocalClient::Status.

self_node is None until the first netmap update has been received from control. Peer entries carry no online/user/capability data (see the status module docs for that gap).

Source

pub async fn file_targets(&self) -> Result<Vec<FileTarget>, Error>

List the tailnet peers this node can Taildrop a file to (Go LocalAPI FileTargets).

Mirrors the upstream send-path filter (feature/taildrop Extension::FileTargets): a peer qualifies when it advertises a reachable peerAPI and is either owned by the same user as this node or explicitly granted the file-sharing-target capability. The whole list is gated on this node holding the file-sharing capability (control sets it when the admin enables Taildrop) — absent that, an empty list (fail-closed, not an error, matching how the receive store returns empty when disabled). Results are sorted by the peer’s MagicDNS name.

Targets are listed regardless of current online state (upstream’s FileTargets does not gate on online either; an offline target’s send will simply time out). The self node is never included. Returns empty before the first netmap.

Divergence from Go: the upstream filter also excludes tvOS peers, which this fork cannot reproduce (the domain node carries no OS string); the impact is negligible — the actual send fail-closes if such a peer refused the transfer.

Source

pub fn active_exit_node(&self) -> Option<StableNodeId>

The stable id of the exit node traffic is currently egressing through, or None if none is engaged. This is the route updater’s resolved + fail-closed answer (see Status::active_exit_node): it differs from the configured exit_node selector, which may name a peer that is absent or no longer advertising a default route (in which case egress is dropped and this returns None).

Source

pub async fn fetch_id_token( &self, audience: String, ) -> Result<String, IdTokenError>

Request an OIDC ID token from control scoped to audience (workload-identity federation).

Returns the signed JWT, or the token RPC’s own ts_control::IdTokenError. The kameo delegated-reply send error is flattened: a handler error carries the real IdTokenError, any other send failure (actor shutdown / mailbox closed) is surfaced as ts_control::IdTokenError::NetworkError.

Source

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

Log this node out of the tailnet: deregister it by expiring its current node key.

Forwards to the control runner, which re-POSTs /machine/register with a past expiry over a fresh Noise channel. This is a control-plane state change only — it does NOT shut the runtime down (the caller follows with graceful_shutdown) and does not touch the on-disk node key. The kameo delegated-reply send error is flattened the same way as fetch_id_token: a handler error carries the real ts_control::LogoutError; any other send failure (actor shutdown / mailbox closed) is surfaced as ts_control::LogoutError::NetworkError.

Source

pub async fn set_dns( &self, name: String, value: String, ) -> Result<(), SetDnsError>

Publish a TXT DNS record for this node via control’s /machine/set-dns (Go LocalClient.SetDNS).

Forwards to the control runner, which POSTs the record over a fresh Noise channel. The kameo delegated-reply send error is flattened the same way as fetch_id_token: a handler error carries the real ts_control::SetDnsError; any other send failure (actor shutdown / mailbox closed) is surfaced as ts_control::SetDnsError::NetworkError.

Source

pub async fn whois(&self, addr: SocketAddr) -> Result<Option<WhoIs>, Error>

Resolve which node owns a tailnet source address.

Maps the destination IP of addr to its owning node. Mirrors tsnet’s LocalClient::WhoIs. Returns None if no peer holds that tailnet IP.

The returned WhoIs additionally carries the flow-scoped peer-capability grants (WhoIs::cap_map, Go apitype.WhoIsResponse.CapMap): the caps control’s packet-filter application rules authorize for traffic from THIS node (the flow source) to addr (the destination). Empty when no grant matches. (The node-level cap map rides WhoIs::capabilities.)

Source

pub async fn direct_path( &self, dst: IpAddr, ) -> Result<Option<(SocketAddr, Duration)>, Error>

The current direct-path status to the peer holding tailnet IP dst: its confirmed direct UDP endpoint and that path’s last-measured RTT, or None when there is no direct path right now (the peer is relayed via DERP, is unknown, or has no disco key).

The latency is the RTT of the most recent disco ping/pong that confirmed the path — a live snapshot up to one probe interval stale, NOT a fresh on-demand round-trip (that is a separate, heavier capability). Mirrors the direct-path latency Go surfaces for ipnstate.PeerStatus.

Source

pub async fn ping_disco( &self, dst: IpAddr, timeout: Duration, ) -> Result<Option<(SocketAddr, Duration)>, Error>

Send a disco ping to the peer holding tailnet IP dst now and await the pong, returning the fresh round-trip latency and the endpoint that answered, or None if no pong arrives within timeout (or the peer is unknown / has no disco key / no candidate path). This is the true on-demand PingType::Disco (Go tailscale ping), as opposed to direct_path which reports the last periodic probe’s RTT.

The ping round-trip is awaited OFF the direct manager’s mailbox (we take a MagicSock handle and await on it directly), so a slow/timing-out ping never blocks the actor.

Source

pub async fn set_exit_node( &self, selector: Option<ExitNodeSelector>, ) -> Result<(), Error>

Change the selected exit node at runtime (the equivalent of Go tsnet’s LocalClient.EditPrefs(ExitNodeID/ExitNodeIP)), without recreating the device.

Updates the live exit-node selector, then asks the peer tracker to re-broadcast the current peer set so the route updater and source filter re-resolve the new selector immediately. None clears the exit node (internet-bound traffic is then dropped, fail-closed, unless this node egresses directly). The selection is re-resolved against the live peer set, so passing a selector for a peer not yet in the netmap simply takes effect once that peer appears.

Source

pub fn exit_node(&self) -> Option<ExitNodeSelector>

The currently-selected exit node, or None if none is selected.

Source

pub async fn set_advertise_routes( &self, routes: Vec<IpNet>, ) -> Result<(), Error>

Change the set of subnet routes this node advertises at runtime (Go tailscale set --advertise-routes). Applies BOTH halves together so the wire and the data path agree:

  1. Wire — re-advertise Hostinfo.RoutableIPs to control on the live map-poll connection (so control grants the node the subnet-router role for exactly these prefixes).
  2. Local — swap the forwarder’s accept/dial route table (so the node actually forwards the prefixes it advertises). New flows see the new set; in-flight flows keep their routing.

routes is filtered to the IPv4-only, deduplicated set this fork can honor (IPv6 prefixes are dropped under the IPv6-off posture — we never advertise a route we won’t forward), so the wire and forwarder are fed the identical final set. This sets the explicit subnet prefixes only; it does NOT touch the exit-node 0.0.0.0/0 advertisement (a separate concern).

Source

pub async fn set_advertise_exit_node(&self, enable: bool) -> Result<(), Error>

Advertise (or stop advertising) this node as an exit node — the 0.0.0.0/0 default route (Go tailscale set --advertise-exit-node). Composes with set_advertise_routes: toggling the exit node re-sends the explicit subnet routes plus (when enable) 0.0.0.0/0, so the two preferences are independent. Like set_advertise_routes, this both re-advertises Hostinfo.RoutableIPs to control AND updates the forwarder’s accept/dial set, applied together. Control still gates whether the advertised exit node is actually usable by peers (this only advertises it).

Source

pub async fn set_hostname(&self, hostname: String) -> Result<(), Error>

Change this node’s hostname at runtime (Go tailscale set --hostname), re-reporting Hostinfo.Hostname to control on the live map-poll connection. Hostname is display-only (control reflects it in the netmap), so there is no dataplane half. The new value is also what a subsequent re-registration reports, so it persists across a reconnect.

Source

pub async fn watch_netmap(&self) -> Result<Receiver<Vec<StatusNode>>, Error>

Subscribe to netmap peer-change events: the narrow peer-set view.

Returns a watch::Receiver whose value is the current set of peer StatusNodes, updated on every netmap state update from control. Await watch::Receiver::changed to react to peers joining, leaving, or changing. For the unified Go-WatchIPNBus feed that merges this with device-state and the interactive-login URL, see watch_ipn_bus; this method is the peer-only projection of the same underlying cell.

Source

pub fn device_state(&self) -> DeviceState

The current device connection-DeviceState.

Source

pub fn watch_state(&self) -> Receiver<DeviceState>

Watch the device connection-DeviceState (ConnectingRunning / NeedsLogin / Expired / Failed).

Returns a watch::Receiver; await changed to react push-style to control connection transitions instead of polling status. The initial value is the current state. Note: a transient per-reconnect dip back to Connecting is not currently emitted (control transparently reconnects below this layer); the state reflects registration outcome and node-key expiry.

Source

pub async fn wait_until_running( &self, timeout: Option<Duration>, ) -> Result<(), RegistrationError>

Wait until the device finishes registering, returning a typed outcome.

Resolves Ok(()) once the device reaches DeviceState::Running. Returns a typed RegistrationError otherwise — the actionable distinction between “retry”, “re-pair”, and “drive interactive login” that replaces polling ipv4_addr in a loop:

  • AuthRejected — bad/expired/unknown auth key. Permanent (re-pair).
  • NeedsLogin(url) — interactive authorization required (no usable auth key). Not permanent: the runtime keeps retrying and will reach Running once the user authorizes the URL. An auth-key caller should treat this as a failure; an interactive caller should ignore this return and instead drive the flow via watch_state (this method returns the URL eagerly rather than blocking for the whole login).
  • NetworkUnreachable — control unreachable. Transient (retry).
  • Timeout — no settled state within timeout.

KeyExpired is not produced by this initial wait (a node key expires only after it has come up); observe post-registration expiry via watch_state. timeout of None waits indefinitely for a settled state.

Source

pub async fn watch_ipn_bus( &self, mask: NotifyWatchOpt, ) -> Result<IpnBusWatcher, Error>

Subscribe to the unified IPN notification bus (Go ipn WatchIPNBus / LocalBackend.WatchNotifications).

Returns an IpnBusWatcher; await next to receive Notify events that coalesce device-DeviceState changes (including the interactive-login URL as browse_to_url) and netmap peer-set changes into one feed. mask (NotifyWatchOpt) selects which current-state fields are front-loaded as an initial snapshot on subscribe (INITIAL_STATE / INITIAL_NETMAP), exactly like Go’s NotifyInitialState / NotifyInitialNetMap.

This composes the same watch cells as watch_state, watch_netmap, and pop_browser_url — one source of truth, so the merged feed cannot diverge from those narrow views. Besides the registration-time login URL (carried by NeedsLogin), browse_to_url also streams the mid-session MapResponse.PopBrowserURL (re-auth / consent on an already-running node). Delivery is best-effort/lossy (a bounded per-watcher buffer; a notification is dropped rather than blocking the runtime if a slow consumer’s buffer fills), matching Go’s bus. The stream ends (next returns None) on runtime shutdown or when the watcher is dropped.

Source

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

Attempt to shut down the runtime gracefully.

Returns false if the shutdown timed out. It is still shut down if it timed out, just more violently and with possible resource leaks.

Trait Implementations§

Source§

impl Drop for Runtime

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

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<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 + Send + Sync>

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 + Send + Sync>

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<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> 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<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

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