VPN Mode
VPN Mode
Section titled “VPN Mode”Bifrost supports a TUN-based VPN mode that captures all system traffic and routes it through the proxy with intelligent split tunneling.
Overview
Section titled “Overview”VPN mode creates a virtual network interface (TUN device) that intercepts all network traffic at the system level, providing:
- Full Traffic Capture: All TCP/UDP traffic, not just browser traffic
- Split Tunneling: Route only specific traffic through VPN
- DNS Interception: Handle DNS queries through the tunnel
- App-Based Rules: Include/exclude specific applications
graph TB
subgraph "VPN Mode"
TUN[TUN Device<br/>bifrost0]
Split[Split Tunnel<br/>Rules Engine]
DNS[DNS<br/>Interceptor]
end
Apps[Applications] --> TUN
TUN --> Split
Split --> |Match| Server[Proxy Server]
Split --> |No Match| Direct[Direct Internet]
TUN --> DNS
DNS --> |Cached| Cache[DNS Cache]
DNS --> |Miss| Upstream[Upstream DNS]
style TUN fill:#4a90e2,stroke:#2c5aa0,color:#fff
style Split fill:#7b68ee,stroke:#5a4fcf,color:#fff
style DNS fill:#50c878,stroke:#3a9d5f,color:#fff
Configuration
Section titled “Configuration”Basic VPN Configuration
Section titled “Basic VPN Configuration”vpn: enabled: true tun: # Interface name (Linux: "bifrost0", macOS: "utun", Windows: "Bifrost") name: bifrost0 # IP address and prefix for the TUN interface address: "10.255.0.1/24" # MTU (default 1400, valid range 576-65535) mtu: 1400DNS Configuration
Section titled “DNS Configuration”The built-in DNS server listens on a full host:port address — there is no separate port
setting, so change the port in listen if 53 is already taken.
vpn: enabled: true dns: enabled: true # Listen address (should match the TUN address) listen: "10.255.0.1:53" # Upstream resolvers queries are forwarded to upstream: - "1.1.1.1" - "8.8.8.8" cache_ttl: 5m # "all" intercepts every query, "tunnel_only" only tunneled destinations intercept_mode: allSplit Tunneling
Section titled “Split Tunneling”Split tunneling allows you to control which traffic goes through the VPN and which bypasses it.
Exclude Mode (Default)
Section titled “Exclude Mode (Default)”All traffic goes through VPN except matching rules:
vpn: split_tunnel: mode: exclude
# These will bypass VPN apps: - name: "Slack" - name: "Zoom"
domains: - "*.local" - "localhost"
ips: - "192.168.0.0/16" - "10.0.0.0/8"
# Always bypass, regardless of mode (checked before every other rule) always_bypass: - "127.0.0.0/8" - "169.254.0.0/16"Include Mode
Section titled “Include Mode”Only matching traffic goes through VPN:
vpn: split_tunnel: mode: include
# Only these will use VPN domains: - "*.company.com" - "*.internal.corp"
ips: - "10.100.0.0/16"Split Tunnel Rules
Section titled “Split Tunnel Rules”App-Based Rules
Section titled “App-Based Rules”Route traffic based on the application generating it. A rule has two fields: name (process
name, matched case-insensitively) and the optional path (full executable path, more specific).
vpn: split_tunnel: apps: # By process name - name: "Slack"
# By full path (more specific — checked before the name) - name: "Microsoft Teams" path: "/Applications/Microsoft Teams.app/Contents/MacOS/Teams"Rule names must be unique: a second rule with the same name is rejected as a duplicate.
Platform Support:
Matching is always by process name or executable path. What differs per platform is how the owning process of a connection is identified.
| Platform | Process lookup |
|---|---|
| macOS | lsof on the socket, executable path via the PID |
| Linux | /proc/net/{tcp,udp} plus /proc/*/fd |
| Windows | TCP/UDP owner-PID tables (IP Helper API) |
| Other | Not supported — app rules never match |
Domain-Based Rules
Section titled “Domain-Based Rules”Route traffic based on destination domain.
vpn: split_tunnel: domains: # Exact match - "example.com"
# Wildcard subdomain - "*.example.com"
# Multiple wildcards - "*.cdn.*.example.com"IP-Based Rules
Section titled “IP-Based Rules”Route traffic based on destination IP or CIDR range.
vpn: split_tunnel: ips: # Single IP - "192.168.1.1"
# CIDR range - "10.0.0.0/8" - "172.16.0.0/12" - "192.168.0.0/16"
# IPv6 - "fd00::/8"API Endpoints
Section titled “API Endpoints”VPN Status
Section titled “VPN Status”GET /api/v1/vpn/statusResponse:
{ "status": "connected", "uptime": 9240000000000, "bytes_sent": 1048576, "bytes_received": 2097152, "packets_sent": 4096, "packets_received": 8192, "active_connections": 12, "tunneled_connections": 10, "bypassed_connections": 2, "dns_queries": 340, "dns_cache_hits": 275}status is one of disabled, connecting, connected, disconnected or error, and uptime
is a duration in nanoseconds. When the VPN stopped because of an error, last_error and
last_error_time are also present.
Enable/Disable VPN
Section titled “Enable/Disable VPN”POST /api/v1/vpn/enablePOST /api/v1/vpn/disableGet VPN Connections
Section titled “Get VPN Connections”GET /api/v1/vpn/connectionsResponse:
[ { "id": "conn-123", "protocol": "tcp", "local_addr": "10.255.0.2:54321", "remote_addr": "93.184.216.34:443", "remote_host": "example.com", "action": "tunnel", "matched_by": "default", "process_info": { "pid": 4242, "name": "curl", "path": "/usr/bin/curl" }, "start_time": "2024-01-15T10:00:00Z", "bytes_sent": 1024, "bytes_received": 2048 }]Split Tunnel Rules
Section titled “Split Tunnel Rules”GET /api/v1/vpn/split/rulesResponse:
{ "mode": "exclude", "apps": [ {"name": "Slack", "path": "/Applications/Slack.app"} ], "domains": ["*.local", "localhost"], "ips": ["192.168.0.0/16", "10.0.0.0/8"], "always_bypass": ["127.0.0.0/8", "169.254.0.0/16"]}Add Split Tunnel App
Section titled “Add Split Tunnel App”POST /api/v1/vpn/split/appsContent-Type: application/json
{ "name": "Discord", "path": "/Applications/Discord.app"}Remove Split Tunnel App
Section titled “Remove Split Tunnel App”DELETE /api/v1/vpn/split/apps/{name}Add Split Tunnel Domain
Section titled “Add Split Tunnel Domain”POST /api/v1/vpn/split/domainsContent-Type: application/json
{ "pattern": "*.internal.company.com"}Add Split Tunnel IP
Section titled “Add Split Tunnel IP”POST /api/v1/vpn/split/ipsContent-Type: application/json
{ "cidr": "172.16.0.0/12"}Platform-Specific Notes
Section titled “Platform-Specific Notes”- Uses
utuninterface (e.g.,utun0); settun.nametoutunand the system assigns the number - Requires TUN/TAP kernel extension or Network Extension
- App rules resolve the owning process with
lsof, then match its name or executable path - DNS changes may require flushing:
sudo dscacheutil -flushcache
- Uses standard TUN device (default name
bifrost0) - Requires
CAP_NET_ADMINcapability or root - iptables/nftables rules for routing
- App rules resolve the owning process from
/proc, then match its name or executable path
# Grant capability to binarysudo setcap cap_net_admin+ep ./bifrost-clientWindows
Section titled “Windows”- Uses wintun driver (bundled or install separately)
- Requires Administrator privileges
- Process matching by executable path
- May need Windows Filtering Platform (WFP) for advanced rules
Troubleshooting
Section titled “Troubleshooting”VPN Not Starting
Section titled “VPN Not Starting”-
Check permissions:
Terminal window # Linuxsudo setcap cap_net_admin+ep ./bifrost-client# macOS/Windows: Run as administrator -
Verify TUN device creation:
Terminal window # Linux/macOSip link show bifrost0# Windowsnetsh interface show interface
DNS Issues
Section titled “DNS Issues”- Check DNS interception is enabled (
vpn.dns.enabled) - Verify the
vpn.dns.upstreamresolvers are reachable - Flush system DNS cache
# macOSsudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
# Linux (systemd-resolved)sudo systemd-resolve --flush-caches
# Windowsipconfig /flushdnsTraffic Not Routing
Section titled “Traffic Not Routing”- Verify split tunnel rules
- Check routing table
- Ensure firewall allows TUN interface
# Check routingip route show # Linuxnetstat -rn # macOSroute print # WindowsPerformance Issues
Section titled “Performance Issues”- Reduce
vpn.tun.mtuif fragmentation occurs (minimum 576) - Shorten
vpn.dns.cache_ttlif answers go stale — leaving it at0keeps the 5m default - Check for conflicting VPN software