A fork of tailscale/tailscale-rs.
tailscale-rs is a work-in-progress Tailscale library written in Rust, with language bindings to
C, Elixir, and Python.
Note
This project is not associated with Tailscale Inc. — it is an independent, unofficial fork. See Legal.
Caution
This software is unstable and insecure.
I welcome enthusiasm and interest, but please do not build production software using these libraries or rely on it for data privacy until I've had a chance to batten down some hatches and complete a third-party audit.
See Caveats for more details.
The following instructions are for Rust! For other languages, see the language-specific README:
Add this dependency line to your Cargo.toml:
[dependencies]
# Published as `geiserx_tailscale`; imported as `tailscale`.
tailscale = { package = "geiserx_tailscale", version = "0.6" }Or depend on the latest from git:
[dependencies] tailscale = { package = "geiserx_tailscale", git = "https://github.com/GeiserX/tailscale-rs" }
Either way, you import it as tailscale (e.g. use tailscale::Device;) — the crate name on
crates.io is geiserx_tailscale, but the library name is tailscale.
Examples of using the tailscale crate can be found in examples/.
For instructions on how to run tests, lints, etc., see CONTRIBUTING.md. For the high-level architecture and repository layout, see ARCHITECTURE.md.
A simple UDP client that periodically sends messages to a tailnet peer at 100.64.0.1:5678:
use std::{
time::Duration,
net::Ipv4Addr,
error::Error,
};
use tailscale::{Config, Device};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Open a new connection to tailscale
let dev = Device::new(
&Config::default_with_key_file("tsrs_keys.json").await?,
Some("YOUR_AUTH_KEY_HERE".to_owned()),
).await?;
// Bind a UDP socket on this node's tailnet IP, port 1234
let sock = dev.udp_bind((dev.ipv4().await?, 1234).into()).await?;
// Send a packet containing "ping" to 100.64.0.1:5678 once per second
loop {
sock.send_to((Ipv4Addr::new(100, 64, 0, 1), 5678).into(), b"ping").await?;
tokio::time::sleep(Duration::from_secs(1)).await;
}
}A Device joins the tailnet by authenticating to the control plane over Tailscale's
TS2021 (Noise) channel, then moves data over an in-process WireGuard data plane — connecting
peer-to-peer where NAT traversal allows, and relaying through DERP otherwise.
flowchart LR
App["Your app<br/>(Rust / C / Elixir / Python)"] --> Device["tailscale::Device"]
Device -->|"TS2021 / Noise"| Control["Control plane<br/>(login, MapResponse)"]
Device --> WG["WireGuard data plane<br/>(userspace netstack)"]
WG -->|"direct (STUN + Disco)"| Peer["Tailnet peer"]
WG -.->|"fallback relay"| DERP["DERP relay"]
DERP -.-> Peer
For the full module layout and design notes, see ARCHITECTURE.md.
This software is still a work-in-progress! I'm providing it in the open at this stage out of a belief in open-source and to see where the community runs with it, but please be aware of a few important considerations:
- This implementation contains unaudited cryptography and hasn't undergone a comprehensive security analysis. Conservatively, assume there could be a critical security hole meaning anything you send or receive could be in the clear on the public Internet.
- There are no compatibility guarantees at the moment. This is early-days software — I may break dependent code in order to get things right.
- Direct peer-to-peer connections via NAT traversal are implemented (STUN-discovered endpoints and
Disco, with
CallMeMaybehole-punching over DERP), and traffic falls back to DERP relays when no direct path is available. Symmetric-NAT birthday-paradox hole-punching is not yet implemented, so behind some NATs a flow stays relayed through DERP, which caps its throughput. - The
TS_RS_EXPERIMENTenvironment variable is required to be set tothis_is_unstable_softwarefor all code linked againsttailscale-rs; this includes Rust, C, Elixir, and Python code. I'll remove this requirement after a third-party code/cryptography audit and any necessary fixes.
For the full security posture — unaudited cryptography, the Tailnet Lock enforcement gap, peerAPI capability limitations, at-rest key handling, and how to report a vulnerability — see SECURITY.md.
I follow semver and aim to make a point release roughly monthly. Since this is pre-1.0, there are no backwards-compatability guarantees. I'm aiming for a stable 1.0 release as soon as I can, but there's currently no timeline.
The current MSRV is 1.94.1. The current edition is Rust 2024.
tailscale-rs has a rolling MSRV (Minimum Supported Rust Version) policy to support the current
and previous Rust compiler versions, and the latest
edition of Rust.
I may lag the latest version/edition in rare cases to let dependencies catch up and to perform any necessary fixes.
The following platforms and architectures are supported:
- Linux (
x86_64/ARM64) - macOS (
ARM64) - Windows (
x86_64)
tailscale-rs is a work-in-progress - I'm still rapidly iterating, fixing bugs, and adding new
features. I aim to keep this section up-to-date, but the issue tracker
is the best way to see the latest updates.
These are features that are currently implemented:
- Basics
- Create TCP and UDP sockets on the tailnet
- Direct connections via NAT traversal (STUN-discovered endpoints and Disco, with
CallMeMaybehole-punching over DERP); traffic falls back to public DERP relays only when no direct path is available - Peer lookups (addressing peers by MagicDNS name, in-process)
- MagicDNS (an in-netstack resolver on
100.100.100.100:53answering A/AAAA/PTR for tailnet peers and control-pushed static records (ExtraRecords); by default fail-closed — with nofallback_resolversconfigured, non-tailnet names get NXDOMAIN and are never forwarded upstream. Configuringfallback_resolversopts in to forwarding non-tailnet query names to those upstream resolvers.) - Split DNS (per-domain
routesfrom control: a query matching a route's suffix is forwarded to that route's resolvers by longest-suffix match; a route with an empty resolver list is a negative route answering NXDOMAIN. Recursive forwarding tofallback_resolvers/resolversapplies only when no route matches.) - Using a subnet router (accepting peer-advertised subnet routes via
accept_routes; opt-in, fail-closed off by default) - Using an exit node (routing internet-bound traffic through a chosen peer via
exit_node, selectable by Tailscale stable ID, tailnet IP, or MagicDNS name; opt-in, fail-closed off by default). The exit node can also be changed at runtime withDevice::set_exit_node— the equivalent of Gotsnet'sLocalClient.EditPrefs(ExitNodeID/ExitNodeIP)— without recreating the device. - Recursive MagicDNS forwarding in TUN mode (the
100.100.100.100:53resolver now forwards non-tailnet names recursively in TUN mode, matching the netstack-mode behavior; the same fail-closed default applies — withoutfallback_resolvers, non-tailnet names get NXDOMAIN) - Tailscale Serve:
Proxy,Text,TcpForward, plus HTTPPath(path-prefix mux) andRedirect(HTTP 3xx) handlers; all TLS-terminating targets are validated and dispatched fail-closed (unmatched path → 404, backend dial failure → drop) - Tailnet Lock (TKA), partial: per-peer node-key signature verification is wired and
unit-tested at the peer-trust chokepoint and fails closed when a trusted-key
Authorityis supplied. Live enforcement is currently inert — the AUM-chain sync RPC that would supply theAuthorityis not yet implemented, so a compromised control plane can still inject peer keys. See SECURITY.md before relying on it. - Communicate with the Tailscale Go client,
tsnet, andlibtailscale
- Language support
- Rust API
- C, Elixir, and Python bindings
These are features or efforts I have in the pipeline and am actively working towards, but with no guarantees on timeline or completion:
- Third-party code and cryptography audit
This is an incomplete list of features in the Tailscale Go client, tsnet, and/or libtailscale
that are not currently supported. I'd like to add all of these eventually! If there's something
on this list you'd like to see supported, or something not on this list you're not sure about,
please open an issue!
Unsupported features
- Networking
- Peer relays
- Exit Nodes (being one — advertising a default route; using one is supported)
- Exit node DNS (
ExitNodeDNSResolvers— routing DNS through the exit node) - Private DERP relays
- Subnet Routers (being one — advertising routes; using one is supported)
- Platforms
- AIX
- Android
- BSDs
- iOS
- Plan9
- QNAP
- Synology DSM
- Observability
- Client Metrics
- Endpoint Collection
- Device Posture Collection
- Log Streaming
- Network Flow Logs
- Other Features
- Application Capabilities
- Automatic Key Rotation
- HTTPS Certificates
- Kubernetes
- Mullvad VPN
- Node Sharing
- Taildrive
- Taildrop
- Tailnet Lock — full enforcement (signature verification is wired, but the AUM-sync RPC that
supplies the trusted-key
Authorityis not yet built, so enforcement is inert; see SECURITY.md) - Tailscale Funnel
- Tailscale Serve — the stored serve-config runtime and accept-loop (the
Path/Redirect/Proxy/Text/TcpForwardhandlers themselves are implemented) - Tailscale SSH
- Tailscale Services
- Webhooks
- Any other features not listed in "Implemented" or "Coming Soon"
This project is not associated with Tailscale Inc. It is an independent, unofficial fork. "Tailscale" is a trademark of Tailscale Inc.; it is used here only to describe interoperability and the upstream project this is forked from.
WireGuard is a registered trademark of Jason A. Donenfeld.