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
impl Runtime
Sourcepub async fn spawn(
config: Config,
auth_key: Option<String>,
keys: NodeState,
) -> Result<Self, Error>
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.
Sourcepub fn register_fallback_tcp_handler(
&self,
cb: Arc<dyn Fn(SocketAddr, SocketAddr) -> FallbackDecision + Send + Sync>,
) -> Result<FallbackTcpHandle, Error>
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.
Sourcepub async fn channel(&self) -> Result<Channel, Error>
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.
Sourcepub fn taildrop_store(&self) -> Option<Arc<TaildropStore>>
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.
Sourcepub fn funnel_ingress_slot(&self) -> FunnelIngressSlot
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.
Sourcepub fn ingress_active_flag(&self) -> Arc<AtomicBool> ⓘ
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.
Sourcepub async fn install_capture(
&self,
hook: Option<CaptureHook>,
) -> Result<(), Error>
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.
Sourcepub async fn rebind(&self) -> Result<(), Error>
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.
Sourcepub async fn status(&self) -> Result<Status, Error>
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).
Sourcepub async fn file_targets(&self) -> Result<Vec<FileTarget>, Error>
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.
Sourcepub fn active_exit_node(&self) -> Option<StableNodeId>
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).
Sourcepub async fn fetch_id_token(
&self,
audience: String,
) -> Result<String, IdTokenError>
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.
Sourcepub async fn logout(&self) -> Result<(), LogoutError>
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.
Sourcepub async fn set_dns(
&self,
name: String,
value: String,
) -> Result<(), SetDnsError>
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.
Sourcepub async fn whois(&self, addr: SocketAddr) -> Result<Option<WhoIs>, Error>
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.)
Sourcepub async fn direct_path(
&self,
dst: IpAddr,
) -> Result<Option<(SocketAddr, Duration)>, Error>
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.
Sourcepub async fn ping_disco(
&self,
dst: IpAddr,
timeout: Duration,
) -> Result<Option<(SocketAddr, Duration)>, Error>
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.
Sourcepub async fn set_exit_node(
&self,
selector: Option<ExitNodeSelector>,
) -> Result<(), Error>
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.
Sourcepub fn exit_node(&self) -> Option<ExitNodeSelector>
pub fn exit_node(&self) -> Option<ExitNodeSelector>
The currently-selected exit node, or None if none is selected.
Sourcepub async fn set_advertise_routes(
&self,
routes: Vec<IpNet>,
) -> Result<(), Error>
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:
- Wire — re-advertise
Hostinfo.RoutableIPsto control on the live map-poll connection (so control grants the node the subnet-router role for exactly these prefixes). - 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).
Sourcepub async fn set_advertise_exit_node(&self, enable: bool) -> Result<(), Error>
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).
Sourcepub async fn set_hostname(&self, hostname: String) -> Result<(), Error>
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.
Sourcepub async fn watch_netmap(&self) -> Result<Receiver<Vec<StatusNode>>, Error>
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.
Sourcepub fn device_state(&self) -> DeviceState
pub fn device_state(&self) -> DeviceState
The current device connection-DeviceState.
Sourcepub fn watch_state(&self) -> Receiver<DeviceState>
pub fn watch_state(&self) -> Receiver<DeviceState>
Watch the device connection-DeviceState (Connecting → Running / 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.
Sourcepub async fn wait_until_running(
&self,
timeout: Option<Duration>,
) -> Result<(), RegistrationError>
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 reachRunningonce 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 viawatch_state(this method returns the URL eagerly rather than blocking for the whole login).NetworkUnreachable— control unreachable. Transient (retry).Timeout— no settled state withintimeout.
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.
Sourcepub async fn watch_ipn_bus(
&self,
mask: NotifyWatchOpt,
) -> Result<IpnBusWatcher, Error>
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.
Sourcepub async fn graceful_shutdown(self, timeout: Option<Duration>) -> bool
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§
Auto Trait Implementations§
impl !Freeze for Runtime
impl !RefUnwindSafe for Runtime
impl !UnwindSafe for Runtime
impl Send for Runtime
impl Sync for Runtime
impl Unpin for Runtime
impl UnsafeUnpin for Runtime
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
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>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
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)
fn as_any(&self) -> &(dyn Any + 'static)
&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)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&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
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<A, T> DynMessage<A> for T
impl<A, T> DynMessage<A> for T
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>>
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>>
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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