Elio Darras
Personal projectExperimental, unfinished

Darrao VPN

A WireGuard client in Flutter, with its native network extension

App screenshots
Real app screenshots.

Screen by screen

  • 01Home: the tunnel state, the button, the current server, and the banner in test mode.

  • 02Ten servers, each with its endpoint. Tapping a row starts the connection.

  • 03Settings: auto-connect, dark mode, and a kill switch announced as coming rather than faked.

  • 04Connecting, light theme. A tap cancels.

  • 05The 20-second watchdog cut in: "Could not reach this server. Try another location."

  • 06Settings in the light theme.

Role

Personal project

An iOS and Android VPN client that brings up a WireGuard tunnel through a Swift network extension. Ten servers to choose from, consent before any advertising, auto-connect, two themes, and an interface that mirrors the tunnel's real state. Unpublished.

What's inside

  • Flutter, Riverpod 3, go_router, Material 3.
  • Swift network extension: hand-written wg-quick parser, WireGuardKit as a local package, Go library compiled at build time.
  • Tunnel state machine subscribed to native events, 20 s watchdog, cancel while connecting.
  • Ad SDK initialised only after consent.

In detail

01What it is

An iOS and Android VPN client written in Flutter, which brings up a WireGuard tunnel through a native network extension. The model is the ad-funded free VPN: the servers are free nodes, ten countries to choose from, and an AdMob banner shows on the home screen.

1,327 lines of Dart in 15 files, about 300 lines of Swift on the extension side. Flutter with Material 3, Riverpod 3, go_router, shared_preferences, wireguard_flutter and google_mobile_ads.

02The journey and the tunnel state

On first launch, a consent screen states the VPN service, the no-logs policy and advertising. Until consent is given, the ad SDK is not initialised: that is the app's compliance mechanism.

Home shows the tunnel's real state, disconnected, connecting, connected, failed, a big round button, the current server and the banner. The interface mirrors the state stream emitted by native code, not a guess: if iOS cuts the VPN from Settings, the app sees it.

A 20-second watchdog closes the tunnel and shows an error if the connection does not complete, and the button cancels the attempt in progress. That was the fix for a real regression: disabling the button during the transition had closed the only way out of a connection that never returned.

Settings: auto-connect at launch, genuinely applied; dark and light modes, both complete; and a kill switch deliberately disabled and announced as "coming soon" rather than pretending to a protection that does not exist.

03The native bridge

Dart builds the wg-quick configuration, private key, address, DNS, MTU, peer, endpoint, keepalive, and hands it to the plugin. On the Swift side, a network extension receives the configuration, parses it by hand with a state machine, validates Curve25519 keys, address ranges and endpoints, about twenty typed error cases, then hands it to WireGuardKit, which brings up the tunnel.

The project embeds the wireguard-apple repository as a local Swift package, and an Xcode build phase compiles the Go library for the requested architecture. The packet-tunnel-provider capability requires a paid developer account.

Unpublished personal project. What remains before release, stated plainly: test AdMob identifiers to replace, ad-tracking declarations to add for Apple review, and the fact that shared free-account keys are inherent to the model: abuse by a third party takes the service down for everyone.