# libtailscale: a private network inside your Flutter app

> A headless Tailscale and Headscale node for Dart and Flutter, embedded through dart:ffi. Device-to-device connectivity with no VPN and no permission dialog.

Source: https://ahmedprime.dev/projects/libtailscale

# libtailscale

- Status

  Released

- Platforms

  macOS · iOS · Android · Linux

- Version

  1.0.1 on pub.dev

- Pub points

  160/160

- Since

  September 2026

- Publisher

  Verified publisher velzosoft.com

- Stack

  Dart · FFI · Go

- Last shipped

  2026-09-02

- Links

  - [pub.dev](https://pub.dev/packages/libtailscale)
  - [GitHub](https://github.com/velzosoft/libtailscale)

libtailscale runs a full Tailscale node inside your app's own process, so your app can join a private network without anyone installing a VPN. Your app dials peers by name, serves a port and makes HTTP calls across a private network. No system VPN, no VPN entitlement, no permission dialog, no second app.

## What it's for

Apps that need private connections between devices: agents, remote control, field equipment. It works with Tailscale or with a self-hosted Headscale server, and only one URL changes.

*Diagram: libtailscale runs a Tailscale node inside the app's process, so the app reaches its peers without a system VPN. Tailscale or Headscale is the control server; the connection to the peer is direct.*

## Why it exists

[PRCHD](/projects/prchd) asks every user to install Tailscale on their phone. I'd like it to ask nobody. libtailscale is one way to get there: the network, embedded in the app. I built it before the product needed it, and it's now one of two candidates being tested for PRCHD's managed network. The other is iroh, which needs no control plane. Spikes will decide between them.

## Decisions

- **A minimal surface, not API parity.** Configure, start, observe, connect, listen. UDP, file transfer, SSH and exit nodes are left out on purpose.
- **Nothing blocks your app.** Blocking native calls run on helper isolates, and one dedicated isolate drives every socket from a single poll loop.
- **Real sockets.** `connect()` returns a standard Dart `Socket`, so existing HTTP clients, gRPC and WebSockets work unchanged.
- **A timeout is mandatory,** because an unreachable control server produces no error. It just waits forever.

## Under the hood

- Bindings to Tailscale's official C library, plus small hand-written libc bindings, so no C shim ships.
- The package ships no binaries. At build time it downloads prebuilt native libraries checked against committed SHA-256 checksums, or builds them from source with a pinned Go toolchain.
- Each platform has its own trick. iOS gets a static archive re-linked as a framework. Android gets 16 KB page alignment, and a patch that lists network interfaces without netlink, which Android 11 and later block.
- Ten native builds per release, from a CI matrix. Integration tests run in CI against an in-process control server, and releases now publish from CI.
- 124 tests.

* [pub.dev/packages/libtailscale](https://pub.dev/packages/libtailscale)
* [github.com/velzosoft/libtailscale](https://github.com/velzosoft/libtailscale)

**Still have questions?** [Ask the agent about this page](/ask?about=libtailscale)

Or [write to Ahmed](/contact)
