Skip to content
surendrajatPublic

About

Cloudflare WARP tunnel with custom DNS (macOS)

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

94 Commits

Folders and files

Repository files navigation

cfwarpd

Cloudflare WARP (MASQUE) tunnel with custom DNS

A full-tunnel replacement for the official Cloudflare WARP client, built on usque — with the one thing the official client won't do: your own DNS (NextDNS) inside the tunnel. One root daemon, a thin CLI, and an optional menubar app.

The story of why (and every wrong turn) is in docs/warp-nextdns-macos-journey.md.

cfwarp menubar

What it is

One launchd root daemon — cfwarpd — that does everything in-process:

Piece Does
tunnel usque nativetun, embedded as a library — MASQUE (QUIC/HTTP3) to Cloudflare's edge
resolver AdGuard dnsproxy, embedded — 127.0.0.1:53 → NextDNS / Cloudflare / custom / off
routing + netwatch split-default via the utun, endpoint pinned direct, re-asserted on reconnect / network change
split-tunnel one excludes list — IP/CIDR routed direct (browsers too), domains pinned on-demand via the resolver (apps)

Tunnel and DNS are independent — start/stop/restart control only the tunnel; dns controls only DNS. The CLI and menubar talk to the daemon over a user-owned unix socket.

Routing is split-default (0.0.0.0/1 + 128.0.0.0/1 via the utun) with the MASQUE endpoint pinned to the physical gateway so usque's own QUIC can't recurse into its own tunnel. The daemon re-asserts routing on every (re)connect (self-heals across sleep/wake and network switches) and retries with backoff if the tunnel can't come up, surfacing the last error in cfwarp status.

Layout

cmd/cfwarpd/      the daemon (Go): tunnel + dns + routing + netwatch + excludes
bin/cfwarp        thin CLI (bash)          bin/cfwarpd ← built by scripts/install.sh (gitignored)
launchd/          com.cfwarp.daemon.plist template (rendered to *.local.plist by scripts/install.sh)
scripts/          install · uninstall · selftest · gen-split-direct
menubar/          Swift app + login-agent template + install.sh
config/           config.yml.example       (real config + identity live in ~/.config/cfwarp/)
docs/             the writeup + split-tunnel, perf, and VPN-coexistence references

Setup

Prereqs: macOS (Apple Silicon), Go, and Xcode CLT for the menubar app.

git clone <this-repo> cfwarpd && cd cfwarpd
./scripts/install.sh                    # first run creates ~/.config/cfwarp/config.yml
$EDITOR ~/.config/cfwarp/config.yml     # set your NextDNS profile
./scripts/install.sh                    # build + register + render the plist

# THE privileged step — load the daemon (re-run this same command after upgrades):
sudo sh -c 'launchctl bootout system/com.cfwarp.daemon 2>/dev/null;
  mkdir -p /usr/local/libexec &&
  install -o root -g wheel -m 0755 "$PWD/bin/cfwarpd" /usr/local/libexec/cfwarpd &&
  cp launchd/com.cfwarp.daemon.local.plist /Library/LaunchDaemons/com.cfwarp.daemon.plist &&
  launchctl bootstrap system /Library/LaunchDaemons/com.cfwarp.daemon.plist &&
  ln -sf "$PWD/bin/cfwarp" /usr/local/bin/cfwarp'

cfwarp status                           # verify (expect warp=on)
./menubar/install.sh                    # optional: menubar shield + toggle

# optional: split-tunnel the well-peered CDNs (Google/CF/Fastly/… + NextDNS) direct
python3 scripts/gen-split-direct.py > ~/.config/cfwarp/split-direct.list
$EDITOR ~/.config/cfwarp/config.yml     # add:  exclude-file: split-direct.list

The daemon runs from a root-owned copy at /usr/local/libexec/cfwarpd (a root LaunchDaemon must not execute a user-writable binary); the cutover installs it there.

scripts/install.sh builds against the usque fork (macOS nativetun + the reconnect-deadlock and stop-teardown fixes) straight from go.mod — nothing to clone — and registers a WARP identity into ~/.config/cfwarp/config-native.json.

Usage

cfwarp start | stop | restart                              # TUNNEL only
cfwarp dns nextdns | cloudflare | custom <up> | default    # DNS only — persists across reboots
cfwarp status | stats | doctor | logs                      # read-only; stats = perf/ops counters
cfwarp exclude <ip|cidr|domain> | include <…>              # split-tunnel a host direct
  • DNS upstream formats: plain IP (UDP), https://…/dns-query (DoH), tls://… (DoT).
  • Excludes — one list (config.yml, or an exclude-file: it points at): an IP/CIDR gets a direct route immediately (covers browsers too); a domain is suffix-matched by the resolver and its IPs pinned on-demand (apps only). See docs/split-tunnel-and-perf.md.
  • The daemon autostarts on boot; pause across reboots with sudo launchctl disable system/com.cfwarp.daemon (enable to undo).
  • Other VPNs (Pritunl, Tailscale, …): split-tunnel VPNs coexist with the tunnel up; a full-default VPN or a Tailscale exit node needs cfwarp stop first. Tailscale also wants its infra excluded and dns.tailscale for MagicDNS — see docs/vpn-coexistence.md.
  • Uninstall: sudo scripts/uninstall.sh (back to factory default; add --purge to also delete the built binaries, identity, and all ~/.config/cfwarp state for a blank slate).

Notes

  • All per-machine state is in ~/.config/cfwarp/: config.yml (profile, dns mode + optional dns.fallback, interface, excludes + optional exclude-file — auto-detect when unset), config-native.json (WARP private key, 0600), any split-direct.list, dns-claimed (the network services cfwarpd pointed at itself — cleanup consults it so it never resets a resolver it doesn't own), and bench-native.json if you've used scripts/bench.sh.
  • Logs: logs/cfwarpd.log (set CFWARP_LOG=debug in the plist for more).
  • Menubar icon: filled shield = tunnel + DNS, half = tunnel only, plain = DNS only, slashed = off.

About

Cloudflare WARP tunnel with custom DNS (macOS)

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages