Skip to content

surendrajat/cfwarpd

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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 independentstart/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 reference

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 | doctor | logs                              # read-only; doctor flags DNS/route drift
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).
  • Another VPN (e.g. Pritunl): cfwarp stop, and if it needs 127.0.0.1:53, cfwarp dns default. Return with cfwarp start + cfwarp dns nextdns.
  • 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, interface, excludes + optional exclude-file — interface/device auto-detect when unset), config-native.json (WARP private key, 0600), and any split-direct.list.
  • 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

License

Stars

0 stars

Watchers

0 watching

Forks

Contributors