pub struct Config {Show 27 fields
pub key_state: PersistState,
pub client_name: Option<String>,
pub control_server_url: Url,
pub allow_http_key_fetch: bool,
pub requested_hostname: Option<String>,
pub requested_tags: Vec<String>,
pub ephemeral: bool,
pub accept_routes: bool,
pub exit_node: Option<ExitNodeSelector>,
pub advertise_routes: Vec<IpNet>,
pub advertise_exit_node: bool,
pub forward_tcp_ports: Vec<u16>,
pub forward_udp_ports: Vec<u16>,
pub forward_all_ports: bool,
pub forward_exit_egress: bool,
pub exit_proxy: Option<ExitProxyConfig>,
pub tcp_buffer_size: Option<usize>,
pub enable_ipv6: bool,
pub transport_mode: TransportMode,
pub wire_ingress: bool,
pub advertise_services: Vec<String>,
pub taildrop_dir: Option<PathBuf>,
pub auth_key: Option<String>,
pub client_id: Option<String>,
pub client_secret: Option<String>,
pub id_token: Option<String>,
pub audience: Option<String>,
}Expand description
Config for connecting to Tailscale.
Fields§
§key_state: PersistStateThe cryptographic keys representing this node’s identity.
client_name: Option<String>The name of this client.
This is reported to control in the Hostinfo.App field.
control_server_url: UrlThe URL of the control server to connect to.
allow_http_key_fetch: boolAllow fetching the control server’s machine public key (GET /key) over plain http when
control_server_url is http://.
By default (false) the key bootstrap is always upgraded to https, even for an http://
control URL — so registration fails against a control plane that only serves plain http
(e.g. a self-hosted Headscale on a http://host:port LAN endpoint / NodePort with no TLS).
Set true for such a deployment. Only safe when you control both ends over a trusted network
path; no effect when the control URL is https://. Fail-closed default is false.
requested_hostname: Option<String>The hostname this node will request.
If left blank, uses the hostname reported by the OS.
Tags this node will request.
ephemeral: boolWhether this node registers as ephemeral.
This is the equivalent of tailscale up --ephemeral. An ephemeral node is
garbage-collected by the control server shortly after it disconnects, which is the right
default for short-lived clients. A long-lived node that must survive brief disconnects —
such as a persistent exit node or subnet router — should set this to false, or control
will GC it out of the tailnet while it is momentarily offline. Defaults to true.
accept_routes: boolWhether to accept (and route traffic to) subnet routes advertised by peers.
This is the equivalent of tailscale up --accept-routes. Defaults to false: only each
peer’s own tailnet address is reachable. Set to true to use peers that act as subnet
routers, so traffic destined for an advertised subnet egresses via the advertising peer.
exit_node: Option<ExitNodeSelector>The peer to route internet-bound traffic through (exit node).
This is the equivalent of tailscale up --exit-node. The peer may be named by stable node
ID, tailnet IP, or MagicDNS name via ExitNodeSelector (a bare
IP or name can be parsed with selector.parse()). Defaults to None: internet-bound
traffic has no overlay route and is dropped (fail-closed). When set to a peer that
advertises a default route, all traffic not matching a more-specific route egresses through
that peer. The selection is re-resolved as the netmap changes.
advertise_routes: Vec<IpNet>Subnet routes to advertise as a subnet router.
This is the equivalent of tailscale up --advertise-routes. Defaults to empty: this node
advertises no routes. Each prefix is sent to the control server in HostInfo.RoutableIPs;
once the route is approved, peers with accept_routes may send traffic for that subnet
through this node. Only IPv4 prefixes are advertised — IPv6 prefixes are dropped to uphold
the IPv6-off posture (we never forward IPv6, so advertising it would be a black hole).
advertise_exit_node: boolWhether to advertise this node as an exit node.
This is the equivalent of tailscale up --advertise-exit-node. Defaults to false. When
true, the default route 0.0.0.0/0 is advertised so that, once approved, other peers may
route their internet-bound traffic out through this node’s real origin IP. Because that
means other peers’ traffic egresses via our IP, it is strictly opt-in. ::/0 is never
advertised (IPv6-off).
forward_tcp_ports: Vec<u16>TCP ports the inbound forwarder accepts and splices to real OS sockets, for every advertised
route (advertise_routes / advertise_exit_node).
Acting as a subnet router or exit node means inbound overlay flows to advertised
destinations are dialed out as real OS connections (mirroring Go tsnet’s forwarders). The
underlying netstack has no all-port accept mode, so the set of forwarded ports is explicit
rather than the full 1–65535 range. Defaults to empty: a node may advertise routes but
forward nothing until ports are configured (fail-closed — nothing is dialed).
forward_udp_ports: Vec<u16>UDP ports the inbound forwarder accepts and splices to real OS sockets, for every advertised
route. See forward_tcp_ports; defaults to empty.
forward_all_ports: boolForward all TCP/UDP ports (1–65535) on every advertised route, like a Go subnet router.
This is the equivalent of a tailscale up --advertise-routes node forwarding every port,
instead of the explicit forward_tcp_ports /
forward_udp_ports sets. When true, those explicit sets are
ignored and the forwarder runs an on-demand per-port listener manager. Anti-leak is
unchanged: every flow still routes through the same dialer chokepoint, so
forward_exit_egress still governs exit-node egress. Defaults
to false.
forward_exit_egress: boolWhether exit-node (0.0.0.0/0) inbound flows are actually egressed via this host’s real
origin IP.
Anti-leak opt-in, separate from advertise_exit_node:
advertising the default route only offers this node as an exit to control; it does not by
itself egress a peer’s internet-bound traffic. Defaults to false (fail-closed): the
forwarder structurally refuses exit-node egress, dropping 0.0.0.0/0 flows at dial time
rather than leaking them out our real IP. Set to true only on a node whose real IP is the
intended egress (e.g. a residential exit), never on a host whose IP must stay hidden (e.g. a
cloud VPS). Subnet routes are dialed identically regardless of this flag.
exit_proxy: Option<ExitProxyConfig>Optional upstream proxy that exit-node egress is routed through, so the node egresses via the proxy’s IP rather than its own origin IP.
This is a product capability beyond strict Go tsnet parity: it lets a cloud exit node
route the traffic it egresses through a residential proxy provider configured by the
deployer, so the cloud host’s real IP never appears upstream. Only consulted when
forward_exit_egress is true. When Some, the forwarder is
wired with a SOCKS5 / HTTP CONNECT proxy dialer that fails closed — any proxy connect
or handshake failure drops the flow rather than dialing direct, so the real IP never leaks.
When None (the default) and exit egress is enabled, egress uses this host’s real IP. See
the proxy-egress section of the repo’s AGENTS.md/CLAUDE.md.
tcp_buffer_size: Option<usize>Per-direction TCP send/receive buffer size (bytes) for the userspace netstack, or None to
use the netstack default (256 KiB per direction, ~512 KiB per socket).
The underlying smoltcp stack has no TCP window auto-tuning, so this value is the hard cap on a single flow’s bandwidth-delay product: at an 80 ms RTT a 16 KiB window throttles a flow to ~1.6 Mbps, which visibly slows large model-API responses even at 1x. Each socket allocates this size for both its rx and tx buffer, so a socket consumes ~2× this value. The default (256 KiB) suits high-RTT links carrying a few large flows; lower it on memory-constrained deployments running many concurrent sockets. Applies to both the application and forwarder netstacks.
enable_ipv6: boolWhether to enable IPv6 on the tailnet overlay (peer-to-peer reachability over the node’s
Tailscale IPv6 address). Defaults to false: the node is IPv4-only on the overlay.
This is an opt-in for general embedders that want Go tsnet-style dual-stack overlay
reachability. It is deliberately off by default to preserve this fork’s sacred anti-leak
posture: its primary deployment is a privacy proxy / cloud exit node where IPv6 is disabled
everywhere to prevent tunnel-bypass IP leakage. When false, behavior is byte-for-byte the
historical IPv4-only path: the underlay binds 0.0.0.0:0, IPv6 candidates/STUN are refused,
the netstack is handed no IPv6 overlay address, and MagicDNS answers AAAA as NODATA.
This flag governs only the overlay. It has NO effect on the exit-node / forwarder egress
path: exit and subnet egress to the public internet stays hardcoded IPv4 in ts_forwarder
regardless of this flag, so the residential-proxy / real-origin-IP isolation invariant can
never be weakened by enabling overlay IPv6. On a host with IPv6 disabled at the kernel, the
dual-stack overlay bind simply fails and the node stays inert on IPv6 rather than panicking.
transport_mode: TransportModeHow this node’s application overlay data path is realized.
Defaults to TransportMode::Netstack, the userspace
smoltcp netstack used by the fork’s primary unprivileged proxy / exit-node deployment.
TransportMode::Tun instead routes the node’s overlay
packets through a real kernel TUN interface (for embedders that want the host OS networking
stack to see the tailnet directly); it requires privileges (root / CAP_NET_ADMIN) and a
platform with TUN support. This governs only the application data path — never the
exit-node / forwarder egress path, which keeps its own IPv4-only userspace netstack.
wire_ingress: boolWhether to ask control to wire this node up server-side for Tailscale Funnel, even when no
Funnel endpoint is currently active (Go tsnet’s “would like to be wired up for Funnel”
signal, HostInfo.WireIngress, capver 113).
When true, registration and map requests set HostInfo.WireIngress so control provisions
the DNS / ingress records a Funnel node needs, making a later
Device::listen_funnel (or
serve) session work immediately. Defaults to false (fail-closed): a node requests Funnel
wiring only when explicitly opted in.
Note this fork cannot yet terminate public Funnel ingress — Device::listen_funnel is
fail-closed (no client-side ACME engine, and a self-hosted control plane provides no public
ingress relay). Setting this flag only requests server-side wiring; it does not by itself
make Funnel live.
advertise_services: Vec<String>VIP services this node advertises that it hosts (svc:<dns-label> names), the advertise
side of Tailscale VIP services (Go tsnet’s Hostinfo.ServicesHash + c2n
GET /vip-services).
Each entry is a full svc:-prefixed name. The valid names (each validated as a well-formed
svc:<dns-label>; malformed names are dropped and logged) are hashed into
HostInfo.ServicesHash on registration and every map request, and reported when control
fetches the hosted-service list via the c2n /vip-services endpoint. Defaults to empty:
advertise nothing (the hash is "", behavior unchanged). Actually hosting a service still
requires control to assign it a VIP and the node to be tagged.
taildrop_dir: Option<PathBuf>Filesystem directory that received Taildrop files land in, or None to disable Taildrop
(the default, fail-closed).
When Some(dir) and a peerAPI port is configured (Taildrop is served on the shared
peerAPI listener, so it needs the same bind), the runtime serves the Taildrop peerAPI route
PUT /v0/put/<name> and writes incoming files under dir (created if absent). When None,
no Taildrop server is run and a peer’s PUT is refused (403). The embedder consumes
received files via the Device::taildrop_waiting_files
/ taildrop_open_file /
taildrop_delete_file methods.
auth_key: Option<String>Pre-auth key for non-interactive registration (Go tsnet.Server.AuthKey). When set, used as
the registration auth key. If it is an OAuth client secret (prefix tskey-client-) and the
identity-federation feature is enabled, it is exchanged for an auth key before registration.
Falls back to the TS_AUTH_KEY env var (see auth_key_from_env). Defaults to None.
client_id: Option<String>OAuth client ID for workload-identity federation (Go tsnet.Server.ClientID). SaaS-only;
requires the identity-federation feature. With id_token or
audience, the node exchanges an IdP-issued OIDC token for a Tailscale
auth key. Defaults to None (TS_CLIENT_ID env fallback).
client_secret: Option<String>OAuth client secret used to mint auth keys via OAuth (Go tsnet.Server.ClientSecret).
SaaS-only; requires the identity-federation feature. Defaults to None (TS_CLIENT_SECRET).
Treat as fully operator-trusted input: a tskey-client-…?baseURL=… secret redirects the
credential exchange to that host, so a hostile value would exfiltrate the secret and the
minted auth key. Never source it from a less-trusted origin.
id_token: Option<String>IdP-issued OIDC ID token to exchange with control for an auth key via workload-identity
federation (Go tsnet.Server.IDToken). SaaS-only; requires the identity-federation feature
and client_id. Mutually exclusive with audience.
Defaults to None (TS_ID_TOKEN).
audience: Option<String>Audience for requesting an OIDC ID token from the ambient workload identity (GitHub Actions /
GCP / AWS), to exchange for an auth key via workload-identity federation (Go
tsnet.Server.Audience). SaaS-only; requires the identity-federation feature +
client_id. Mutually exclusive with id_token.
Defaults to None (TS_AUDIENCE).
Implementations§
Source§impl Config
impl Config
Sourcepub async fn default_with_key_file(p: impl AsRef<Path>) -> Result<Self, Error>
pub async fn default_with_key_file(p: impl AsRef<Path>) -> Result<Self, Error>
Create a new config with its key_state populated from the specified key file and using
default options for other configuration.
See load_key_file for more details and an alternative with more options for reading
the key file.
Sourcepub fn default_from_env() -> Config
pub fn default_from_env() -> Config
Construct a default config, setting certain fields from environment variables.
The fields are only set if the corresponding environment variable is present, using the default value otherwise.
Loads:
control_server_urlfromTS_CONTROL_URLrequested_hostnamefromTS_HOSTNAMEauth_keyfromTS_AUTH_KEYclient_idfromTS_CLIENT_IDclient_secretfromTS_CLIENT_SECRETid_tokenfromTS_ID_TOKENaudiencefromTS_AUDIENCE
Sourcepub fn rotate_node_key(&mut self)
pub fn rotate_node_key(&mut self)
Rotate this config’s node key in place for an embedder-driven re-registration, mirroring Go’s
regen flow: the current node key is recorded as the old key and a fresh node key is
generated. Re-create the Device from this config to perform the rotation;
the next registration sends the prior key as OldNodeKey for key continuity.
Reactive and embedder-driven by design (you decide when to rotate, e.g. after observing
Device::self_key_expired flip, or on a policy of your
own). This fork does not auto-rotate before expiry — neither does Go, which treats key expiry
as a deliberate periodic re-authentication checkpoint. Rotation still requires a valid auth
key, exactly like a fresh registration.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Config
impl RefUnwindSafe for Config
impl Send for Config
impl Sync for Config
impl Unpin for Config
impl UnsafeUnpin for Config
impl UnwindSafe for Config
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,
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
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