Privacy Tools Guide

VPN Tunnel Interface vs Full Tunnel Routing: A Technical Guide

Choose tunnel interface routing if you need simultaneous access to local resources (printers, NAS, Docker networks) and VPN-protected resources – it routes only specific subnets through the VPN while keeping everything else on your direct connection. Choose full tunnel routing if your organization mandates all traffic through the VPN for compliance, or if you need complete protection on untrusted networks like public WiFi. The core difference is control granularity: tunnel interface gives you precise per-subnet routing decisions, while full tunnel encrypts everything at the cost of added latency and lost local network access.

What Is a Tunnel Interface?

A tunnel interface represents a virtual network interface created by the VPN software. Instead of routing all traffic through the VPN, you can treat the tunnel as a specific network route—similar to how you might have multiple network adapters on a single machine. The tunnel interface gets its own IP address from the VPN’s address space, and you decide which traffic flows through it.

On Linux, you can view tunnel interfaces after connecting to a VPN:

ip addr show | grep -i tun
# Output typically shows tun0, tun1, etc. With VPN-assigned IPs

The tunnel interface becomes another pathway into your routing table. You control what goes through it explicitly using routing policies.

Full Tunnel Routing Explained

Full tunnel routing sends every single packet from your device through the VPN tunnel. Nothing goes directly to your local network or ISP. When you connect to a corporate VPN from a coffee shop, full tunnel ensures all your traffic—browsing, email, background updates—appears to originate from the corporate network.

The routing table in full tunnel mode looks something like this:

# Default route points through tunnel (0.0.0.0/0)
ip route show
# 0.0.0.0/0 via 10.8.0.1 dev tun0

Your ISP sees only encrypted traffic heading to the VPN server. Local network resources become inaccessible unless the VPN configuration specifically allows split tunneling—or unless you accept the performance cost of routing everything through the tunnel and back.

Key Differences Between Tunnel Interface and Full Tunnel

The primary distinction involves control granularity. Tunnel interface routing gives you precise control: you can route specific subnets, individual hosts, or even single ports through the VPN while keeping everything else on your direct connection. Full tunnel routing gives you simplicity: one configuration covers all traffic, but you lose the ability to access local resources without going through the VPN first.

Consider a practical scenario. You work remotely and need to access both company resources (which require the VPN) and local network printers or file shares. With tunnel interface routing, you add specific routes:

# Route only corporate subnet through VPN
ip route add 10.0.0.0/8 via 10.8.0.1 dev tun0
# All other traffic uses default gateway

With full tunnel, your local printer becomes unreachable unless you reconfigure the VPN to allow split tunneling or unless you accept that every print job travels through your corporate network.

Performance implications differ significantly. Full tunnel adds latency for every connection since traffic must reach the VPN server before heading to its final destination. A packet to a nearby website might take 50ms directly but 150ms through a VPN server on the other coast. Tunnel interface routing lets you optimize: local traffic stays local, while only VPN-required traffic incurs the tunnel penalty.

Use Cases for Each Approach

When to Use Tunnel Interface Routing

Developers often prefer tunnel interface routing when working with multiple environments. You might need production database access through the VPN while keeping development servers on your direct connection. Container environments benefit particularly from this approach, since Docker networks and Kubernetes clusters need to remain accessible without VPN interference.

Security researchers conducting network analysis sometimes use tunnel interfaces to route only suspicious traffic through an additional layer of protection while maintaining clean access to standard services. The ability to selectively apply VPN protection provides flexibility that full tunnel cannot match.

Network debugging becomes more straightforward with tunnel interfaces. You can route specific traffic through the VPN while capturing local traffic with tools like tcpdump or Wireshark on your primary interface.

When to Use Full Tunnel Routing

Corporate environments frequently mandate full tunnel routing for security compliance. When employees access sensitive systems, organizations need assurance that no traffic escapes the protected path. Regulatory requirements sometimes explicitly require all traffic to flow through approved network inspection points.

Public WiFi protection represents another full tunnel scenario. When connecting from airports, hotels, or coffee shops, full tunnel ensures your traffic never touches the untrusted local network directly. Even seemingly innocuous DNS queries could leak information about your activities.

Threat models involving sophisticated adversaries who might perform traffic analysis benefit from full tunnel. By encrypting everything, you prevent observers from determining which services you access based on packet timing and size patterns.

Implementation Examples

WireGuard Configuration

WireGuard configurations demonstrate tunnel interface routing clearly. The AllowedIPs directive controls what gets routed through the tunnel:

# Full tunnel - route everything
[Peer]
PublicKey = SERVER_PUBLIC_KEY
AllowedIPs = 0.0.0.0/0, ::/0

# Tunnel interface - route only specific subnets
[Peer]
PublicKey = SERVER_PUBLIC_KEY
AllowedIPs = 10.0.0.0/8, 192.168.100.0/24

OpenVPN Split Tunnel

OpenVPN achieves the same with routing directives:

# Full tunnel (default behavior when no special config)
# All traffic encrypted through VPN

# Split tunnel - corporate network only
route 10.0.0.0 255.0.0.0
# Redirect gateway would enable full tunnel instead

macOS Network Extension

For macOS developers building VPN clients, Network Extension frameworks provide programmatic control:

// Creating a tunnel network settings object
let tunnelSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "vpn.example.com")

// Configure DNS - for full tunnel, use VPN DNS
// For split tunnel, keep local DNS or specify different servers
tunnelSettings.dnsSettings = NEDNSSettings(servers: ["10.8.0.1"])

// Configure IPv4 settings with routing
let ipv4Settings = NEIPv4Settings(addresses: ["10.8.0.2"], subnetMasks: ["255.255.255.0"])

// Full tunnel: include 0.0.0.0/0
ipv4Settings.includedRoutes = [NEIPv4Route.default()]

// Split tunnel: include only required subnets
ipv4Settings.includedRoutes = [
 NEIPv4Route(destinationAddress: "10.0.0.0", subnetMask: "255.0.0.0"),
 NEIPv4Route(destinationAddress: "192.168.100.0", subnetMask: "255.255.255.0")
]

Security Considerations

Tunnel interface routing introduces potential information leakage. DNS queries for non-VPN domains might still go through your ISP’s DNS servers, revealing your browsing destinations. Always configure tunnel DNS settings to match your routing:

# Ensure DNS queries for VPN-routed domains go through VPN
resolvectl tun0 dns 10.8.0.1

Full tunnel simplifies this concern but creates a single point of failure. If the VPN connection drops, applications might silently fall back to direct connections. Always use kill switches with full tunnel configurations:

# WireGuard kill switch using iptables
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -o wg0 -j ACCEPT
iptables -A OUTPUT -j DROP

Choosing the Right Approach

Your specific requirements determine the best choice. Consider these questions:

Do you need simultaneous access to local network resources and VPN-protected resources? Tunnel interface routing becomes necessary. Local printers, smart home devices, or network-attached storage all require direct local access.

Does your organization mandate all traffic through the VPN for compliance? Full tunnel satisfies security requirements without exception.

Are you primarily concerned about public WiFi eavesdropping while wanting minimal performance impact? Tunnel interface routing lets you protect sensitive traffic while maintaining reasonable speeds for everything else.

Do you run services that need to be accessible from your local network while the VPN runs? Full tunnel blocks incoming connections unless explicitly port-forwarded through the VPN.

Understanding these tradeoffs enables informed decisions rather than one-size-fits-all configurations. Most modern VPN clients support both approaches, giving you the flexibility to choose based on your current task.

Advanced Routing Policy Management

For complex network scenarios, policy-based routing provides granular control:

# Linux: Advanced policy routing beyond basic tunnel/full tunnel
# Add multiple routing tables for different traffic classes

# Define routing tables
echo "200 corporate" >> /etc/iproute2/rt_tables
echo "201 personal" >> /etc/iproute2/rt_tables

# Create rules for VPN routing
ip rule add from 192.168.1.100 lookup corporate
ip rule add from 192.168.1.101 lookup personal

# Configure corporate table (through VPN)
ip route add 10.0.0.0/8 via 10.8.0.1 dev tun0 table corporate
ip route add 0.0.0.0/0 via 10.8.0.1 dev tun0 table corporate

# Configure personal table (direct connection)
ip route add 0.0.0.0/0 via 192.168.1.1 dev eth0 table personal

# Verify routing
ip route show table corporate
ip route show table personal

This enables per-source routing where different devices on your network use different tunnel configurations.

DNS Configuration for Split Routing

DNS remains the critical leak vector in tunnel interface configurations:

# Systemd-resolved with per-interface DNS
cat > /etc/systemd/resolved.conf.d/vpn-dns.conf <<EOF
[Resolve]
# VPN tunnel interface gets VPN DNS
DNS=10.8.0.1
Domains=corporate.internal

# Local interface gets local DNS
FallbackDNS=8.8.8.8
EOF

# Verify DNS routing by interface
resolvectl status

# Test DNS leak for specific domain
nslookup corporate.internal
# Should use 10.8.0.1 (VPN)

nslookup google.com
# Should use ISP DNS (if split tunnel is configured)

Incorrect DNS configuration undermines tunnel interface security, allowing local observers to determine which services you access.

Linux iptables Rules for Tunnel Interface Enforcement

For systems requiring no DNS leaks regardless of application misconfiguration:

#!/bin/bash
# Strict tunnel interface firewall rules

# Default: drop all traffic
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT DROP

# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow VPN interface traffic
iptables -A OUTPUT -o tun0 -j ACCEPT
iptables -A INPUT -i tun0 -j ACCEPT

# Allow VPN tunnel creation traffic to specific server
iptables -A OUTPUT -d YOUR_VPN_SERVER -p tcp --dport 1194 -j ACCEPT
iptables -A OUTPUT -d YOUR_VPN_SERVER -p udp --dport 1194 -j ACCEPT

# Allow specific local traffic (e.g., printer)
iptables -A OUTPUT -d 192.168.1.100 -j ACCEPT

# DNS over VPN only
iptables -A OUTPUT -p udp --dport 53 -o tun0 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -o tun0 -j ACCEPT

# Save rules
iptables-save > /etc/iptables/rules.v4

This enforcement ensures no application can bypass the tunnel interface through misconfiguration.

macOS Packet Tunnel Implementation

macOS developers building VPN extensions need to implement packet filtering:

// Implementing split tunnel in Network Extension
class SplitTunnelPacketHandler {
 let allowedDomains = ["corporate.com", "internal.example.com"]

 func shouldRouteThroughTunnel(for address: String) -> Bool {
 // Check if address matches allowed domains
 for domain in allowedDomains {
 if address.hasSuffix(domain) {
 return true
 }
 }
 return false
 }

 func setupSplitTunnelRoutes() {
 let tunnelSettings = NEPacketTunnelNetworkSettings(
 tunnelRemoteAddress: "vpn.example.com"
 )

 // Configure DNS for corporate domain only
 let dnsSettings = NEDNSSettings(servers: ["10.8.0.1"])
 dnsSettings.matchDomains = ["corporate.com", "internal.example.com"]
 tunnelSettings.dnsSettings = dnsSettings

 // IPv4 routing: split tunnel approach
 let ipv4Settings = NEIPv4Settings(
 addresses: ["10.8.0.2"],
 subnetMasks: ["255.255.255.0"]
 )

 // Only route corporate networks through VPN
 ipv4Settings.includedRoutes = [
 NEIPv4Route(destinationAddress: "10.0.0.0", subnetMask: "255.0.0.0"),
 NEIPv4Route(destinationAddress: "172.16.0.0", subnetMask: "255.240.0.0")
 ]

 tunnelSettings.ipv4Settings = ipv4Settings
 }
}

Windows Network Extension (Wintun)

Windows users can achieve tunnel interface control through Wintun:

// Wintun packet filtering example
#include <wintun.h>

VOID FilterPackets(WINTUN_ADAPTER* Adapter) {
 WINTUN_SESSION* Session = WintunStartSession(Adapter, 0x100000);

 for (;;) {
 WINTUN_PACKET* Packets[256];
 DWORD PacketsRead = WintunReceivePackets(Session, Packets, 256);

 for (DWORD i = 0; i < PacketsRead; ++i) {
 WINTUN_PACKET* Packet = Packets[i];

 // Inspect packet destination
 // Route corporate traffic through VPN
 // Route local traffic direct

 if (IsDestinationCorporate(Packet->Data)) {
 RouteToVPN(Packet);
 } else {
 SendDirect(Packet);
 }
 }
 }
}

Bandwidth and Latency Monitoring

Quantify the performance difference between approaches:

#!/bin/bash
# Compare tunnel interface vs full tunnel performance

test_configuration() {
 local config_name=$1
 local test_duration=60

 # Bandwidth test (download)
 echo "Testing: $config_name"
 iperf3 -c test-server.com -t $test_duration -R 2>&1 | grep "sender"

 # Latency test
 ping -c 10 test-server.com | grep "avg"

 # DNS resolution speed
 time nslookup test-domain.com
}

# Test tunnel interface (selective routing)
echo "Tunnel Interface Results:"
test_configuration "tunnel"

# Test full tunnel (all traffic)
echo "Full Tunnel Results:"
test_configuration "full-tunnel"

# Test direct connection (baseline)
echo "Direct Connection Baseline:"
test_configuration "direct"

VPN Connection Recovery and Failover

Handling VPN disconnections gracefully:

#!/bin/bash
# Auto-reconnect and failover for tunnel interface

VPN_INTERFACE="tun0"
VPN_PROCESS="openvpn"
FAILOVER_TIMEOUT=30

monitor_vpn() {
 while true; do
 if ! Ip link show $VPN_INTERFACE > /dev/null 2>&1; then
 echo "VPN interface down, attempting reconnect..."

 # Kill hanging process
 pkill -f $VPN_PROCESS
 sleep 2

 # Restart VPN
 systemctl start openvpn@myconfig

 # Wait for interface
 for i in $(seq 1 $FAILOVER_TIMEOUT); do
 if ip link show $VPN_INTERFACE > /dev/null 2>&1; then
 echo "VPN reconnected"
 break
 fi
 sleep 1
 done
 fi

 sleep 10
 done
}

# Run as systemd service
monitor_vpn

Practical Testing Framework

Verify your tunnel configuration works correctly:

#!/usr/bin/env python3
# Test tunnel interface configuration

import subprocess
import requests
import socket

def test_tunnel_configuration():
 """Verify tunnel interface routes correctly"""

 tests = {
 "vpn_connected": check_vpn_connected,
 "dns_routed": check_dns_routing,
 "local_access": check_local_access,
 "vpn_access": check_vpn_access,
 }

 results = {}
 for test_name, test_func in tests.items():
 try:
 result = test_func()
 results[test_name] = "PASS" if result else "FAIL"
 except Exception as e:
 results[test_name] = f"ERROR: {e}"

 return results

def check_vpn_connected():
 """Verify VPN interface is up"""
 result = subprocess.run(
 ["ip", "link", "show", "tun0"],
 capture_output=True
 )
 return result.returncode == 0

def check_dns_routing():
 """Verify DNS queries route correctly"""
 try:
 ip = socket.gethostbyname("corporate.com")
 return True
 except socket.gaierror:
 return False

def check_local_access():
 """Verify local resources are still accessible"""
 try:
 requests.get("http://192.168.1.1", timeout=2)
 return True
 except:
 return False

def check_vpn_access():
 """Verify VPN-protected resources are accessible"""
 try:
 requests.get("http://10.0.0.1", timeout=5)
 return True
 except:
 return False

if __name__ == "__main__":
 results = test_tunnel_configuration()
 for test, result in results.items():
 print(f"{test}: {result}")

These technical tools enable precise control over network routing, essential for complex environments requiring simultaneous VPN and direct access.

Built by theluckystrike — More at zovo.one