DEV Community

Cover image for TunnelGuard: A Free macOS App to Exclude Domains from Your VPN (Split Tunneling)
Amirhossein Hosseinpour
Amirhossein Hosseinpour

Posted on

TunnelGuard: A Free macOS App to Exclude Domains from Your VPN (Split Tunneling)

The Problem

If you use a VPN for work, you've probably hit this wall: you're connected to your company VPN, and suddenly your local streaming service, your home NAS, or some dev server you run locally becomes unreachable — or painfully slow — because all traffic is being tunneled.

Most VPN clients don't give you granular control over what goes through the tunnel. You either tunnel everything or nothing. Split tunneling is the answer, but most apps either don't support it, lock it behind enterprise plans, or make it a nightmare to configure.

So I built TunnelGuard.


What Is TunnelGuard?

TunnelGuard is a native macOS SwiftUI app that lets you define a list of domains and automatically adds static routing rules so those domains bypass your VPN — at the OS level, using route commands.

It resolves the IPs for each domain via dig, then calls:

sudo route -n add <ip> <gateway>
Enter fullscreen mode Exit fullscreen mode

No VPN client integration needed. No kernel extensions. No magic. Just pure macOS routing.


Features

  • Domain-based rules — Add any domain, TunnelGuard resolves its IPs and routes them around the VPN
  • Auto IP resolution via dig with configurable DNS server
  • Gateway auto-detection — reads your default route from netstat -nr, or you set it manually
  • Toggle rules on/off without deleting them
  • Refresh IPs on demand (useful for dynamic domains)
  • Activity log — fully selectable, copyable, color-coded terminal-style output
  • Launch at startup via LaunchAgent
  • Menu bar item + Dock icon — configurable (Menu Bar Only / Dock Only / Both)
  • Dark / Light / System theme — liquid glass UI with proper macOS feel
  • Quick access to macOS VPN Settings — one click opens System Preferences VPN panel
  • No account, no telemetry, no cloud — everything stays local

How It Works

The core is simple. When you add a domain:

  1. dig +short domain.com A resolves the IPs
  2. For each IP: sudo route -n add <ip> <gateway> adds a static route that bypasses the VPN tunnel
  3. On rule toggle-off: sudo route -n delete <ip> removes it

The gateway is auto-detected from netstat -nr or set manually. Routes are session-based by default (cleared on reboot), unless you enable "Apply on launch" which re-applies them every time the app starts.

The Sudoers Setup

Since route requires sudo, you either get a password prompt every time or set up passwordless sudo for /sbin/route:

sudo visudo
# Add:
%admin ALL=(ALL) NOPASSWD: /sbin/route
Enter fullscreen mode Exit fullscreen mode

Tech Stack

  • SwiftUI — native macOS UI, macOS 13.0+
  • AppKitNSStatusItem for menu bar, NSTextView for the log panel
  • Swift Concurrencyasync/await for IP resolution and rule management
  • UserDefaults — persisting rules and settings
  • LaunchAgent plist — startup integration
  • Shell commandsdig, route, netstat via Process()

No third-party dependencies. Zero.


Screenshot

Exclusion Rules view

Exclusion Rules view

The UI has a sidebar with Rules / Logs / Settings / About, plus a direct link to macOS VPN Settings that opens x-apple.systempreferences:...?VPN.

The log panel is a native NSTextView — fully selectable, copyable, color-coded (green = success, orange = warning, red = error).


Installation

# Clone
git clone https://github.com/amirhp-com/TunnelGuard
cd TunnelGuard

# Build with Swift Package Manager
swift build -c release

# Or open in Xcode
open TunnelGuard.xcodeproj
Enter fullscreen mode Exit fullscreen mode

Then set up sudoers for passwordless route commands (see SUDOERS_SETUP.md in the repo).


Caveats & Honest Limitations

  • Routes are IP-based — if a domain's IPs change (CDN, dynamic DNS), you need to refresh. The app makes this one click, but it's not automatic yet.
  • Session-based — routes clear on reboot unless you enable "Apply on launch"
  • No IPv6 support yet — only A records, not AAAA
  • macOS only — obviously

Links


Built by Amirhossein Hosseinpour — open to PRs, issues, and feedback.

If this saves you from VPN headaches, give it a ⭐ on GitHub.

Top comments (0)