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>
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
digwith 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:
-
dig +short domain.com Aresolves the IPs - For each IP:
sudo route -n add <ip> <gateway>adds a static route that bypasses the VPN tunnel - 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
Tech Stack
- SwiftUI — native macOS UI, macOS 13.0+
-
AppKit —
NSStatusItemfor menu bar,NSTextViewfor the log panel -
Swift Concurrency —
async/awaitfor IP resolution and rule management - UserDefaults — persisting rules and settings
- LaunchAgent plist — startup integration
-
Shell commands —
dig,route,netstatviaProcess()
No third-party dependencies. Zero.
Screenshot
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
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
Arecords, notAAAA - macOS only — obviously
Links
- Download: Latest Version
- GitHub: github.com/amirhp-com
- Website: amirhp.com/landing
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)