Skip to main content

Config

Struct Config 

Source
pub struct Config {
Show 28 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 persistent_keepalive_interval: Option<Duration>, 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: PersistState

The 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: Url

The URL of the control server to connect to.

§allow_http_key_fetch: bool

Allow 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.

§requested_tags: Vec<String>

Tags this node will request.

§ephemeral: bool

Whether 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: bool

Whether 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: bool

Whether 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: bool

Forward 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: bool

Whether 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.

§persistent_keepalive_interval: Option<Duration>

WireGuard persistent-keepalive interval applied to every peer, or None to disable (PersistentKeepalive; this is the equivalent of Tailscale setting PersistentKeepalive=25 on a peer when control marks it KeepAlive=true).

When Some(interval) (the default, Some(25s)), each peer emits an empty authenticated keepalive after interval of outbound silence, holding the path/NAT mapping warm. This is the load-bearing fix for idle DERP-relayed sessions wedging: on a userspace-netstack node whose only path to a peer is the relay, an idle session otherwise ages past expiry with no traffic to keep it warm and no timer to refresh it, so the next dial rehandshakes over a cold path and loops forever. The persistent keepalive re-arms unconditionally (unlike the reactive WireGuard §6.5 keepalive, which is armed only by inbound traffic and dies ~10s after the last inbound packet) and the empty packet deliberately does not advance the session’s rotation/expiry timers, so a genuinely dead peer is still detected and rekey still fires on schedule.

Set to None to opt out (e.g. an embedder that has its own keepalive strategy or only ever runs over a direct, always-warm path). The default is on because this fork’s primary deployment is the relayed case the wedge bites.

§enable_ipv6: bool

Whether 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: TransportMode

How 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: bool

Whether 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

Source

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.

Source

pub fn use_tun(self, name: Option<String>, mtu: Option<u16>) -> Self

Run the application overlay over a real kernel TUN interface instead of the default userspace netstack — a builder shortcut for setting transport_mode to TransportMode::Tun.

name is the desired interface name (None lets the OS pick, e.g. utunN on macOS); mtu is the interface MTU (None uses the transport default; Tailscale’s overlay MTU is 1280). TUN mode requires root / CAP_NET_ADMIN and the engine’s tun feature to be enabled. Chainable: Config::default().use_tun(Some("tailscale0".into()), None).

Source

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_url from TS_CONTROL_URL
  • requested_hostname from TS_HOSTNAME
  • auth_key from TS_AUTH_KEY
  • client_id from TS_CLIENT_ID
  • client_secret from TS_CLIENT_SECRET
  • id_token from TS_ID_TOKEN
  • audience from TS_AUDIENCE
Source

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§

Source§

impl Default for Config

Source§

fn default() -> Self

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

impl From<&Config> for Config

Source§

fn from(value: &Config) -> Config

Converts to this type from the input type.

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