Linux Transparent Proxying: TPROXY vs TUN Device Interception
Engineers deploying transparent proxies on Linux gateways face a fundamental design choice: intercept flows inside Netfilter using TPROXY, or steer raw packets into a virtual TUN interface. Both mechanisms allow a proxy daemon to intercept transit TCP and UDP traffic without client configuration or destination address rewriting. However, their performance profiles, kernel boundaries, and operational failure modes differ substantially.
Criterion 1 – Kernel Overhead and Context Switches
The core distinction between TPROXY and TUN lies in where protocol termination occurs and how packet buffers cross the kernel-user space boundary.
TPROXY operates directly inside the Netfilter subsystem. When a packet matches an iptables or nftables tproxy rule, the kernel links it to a local socket opened with the IP_TRANSPARENT socket option. The proxy process interacts with standard stream or datagram sockets via read and write syscalls. The Linux kernel manages the TCP state machine, sliding windows, and congestion control. Memory stays inside kernel sk_buff structures until payloads reach the socket buffer.
In contrast, a TUN interface operates at Layer 3 as a virtual network device. Diverting traffic into TUN forces the kernel to enqueue raw IP frames into the character device queue at /dev/net/tun. The user-space proxy must read every packet, parse IP and TCP/UDP headers, and execute an embedded user-space TCP/IP stack, such as gVisor Netstack or lwIP. This architecture requires two context switches and memory copies per packet: once when reading the raw frame from the TUN descriptor, and once when dispatching traffic out via an upstream socket.
TPROXY keeps socket bindings inside the native kernel stack, whereas a TUN device converts the proxy into a virtual router running an embedded user-space TCP/IP engine.
Under multi-gigabit traffic with high packet rates, user-space TCP/IP processing causes high CPU saturation. TPROXY provides lower per-packet latency and scales better across CPU cores by delegating transport state tracking to native kernel sockets.
Criterion 2 – Metadata Preservation and Connection Tracking
Transparent proxies must reliably extract original destination addresses and ports to steer egress connections.
TPROXY preserves packet headers by avoiding destination NAT (DNAT). The original destination IP and port remain intact. When accepting a redirected connection on an IP_TRANSPARENT socket, calling getsockname returns the original remote destination, while getpeername returns the client address. For UDP, the IP_RECVORIGDSTADDR socket option delivers the target address via recvmsg control messages (cmsg). Netfilter conntrack tracks connections without modifying tuple records.
With a TUN interface, the user-space stack inspects raw IP header bytes directly. The destination IP and port are present in the IP header of the frame delivered to user space. Preserving metadata is simple at the application layer. However, returning egress packets back into the local network requires the user-space stack to synthesize symmetric response frames, manually setting source IP addresses to match the original remote targets before writing back into the TUN descriptor.
Criterion 3 – Routing Topology and Rule Maintenance
Operational complexity differs sharply between firewall hooks and policy routing configurations.
TPROXY requires synchronized configuration between Netfilter rules and the Linux policy routing table (ip rule). Packets must be marked in the PREROUTING hook and steered to a custom routing table containing a local loopback default route.
# Divert transit traffic via nftables TPROXY
nft add table inet filter
nft add chain inet filter prerouting '{ type filter hook prerouting priority mangle; policy accept; }'
nft add rule inet filter prerouting ip daddr != 192.168.1.0/24 meta l4proto { tcp, udp } tproxy to :12345 meta mark set 0x1 accept
# Steer marked packets to local socket stack
ip rule add fwmark 0x1 table 100
ip route add local 0.0.0.0/0 dev lo table 100
A single misconfigured firewall priority or missing route entry silently breaks interception. If the local proxy process terminates, packets matching the rule drop immediately.
TUN devices require standard interface routing rules rather than complex mangle tables:
# Assign interface address and configure routing table
ip tuntap add mode tun dev tun0
ip link set dev tun0 up
ip addr add 198.18.0.1/15 dev tun0
ip rule add fwmark 0x2 table 200
ip route add default dev tun0 table 200
TUN setups are straightforward to isolate and inspect with tcpdump, ip route, and ifconfig. The interface behaves like physical hardware, exposing packet statistics under /sys/class/net/tun0/statistics.
Criterion 4 – Transport Protocol Support and UDP Edge Cases
Protocol agility is where TUN interfaces provide an architectural advantage.
TPROXY is restricted to protocols implemented in Netfilter and the Linux socket API. While TCP and UDP work as intended, capturing ICMP errors, handling fragmented frames, and forwarding raw or custom protocols is difficult. Handling symmetric UDP with TPROXY requires raw sockets bound with IP_TRANSPARENT to forge outbound response source IPs; otherwise, client stateful firewalls drop unsolicited replies.
A TUN device intercepts any payload encapsulated in IPv4 or IPv6 headers. The daemon can handle ICMP Echo requests, translate traceroute probes, and process arbitrary encapsulated traffic. This explains why modern universal proxies (including Mihomo, Sing-box, and Xray) prioritize TUN modes: the embedded user-space network stack functions identically across Linux, macOS, and BSD without relying on kernel-specific Netfilter modules.
Conclusion
Select TPROXY when building high-throughput Linux gateways where raw throughput, minimum memory allocation, and kernel TCP efficiency take precedence over cross-platform portability. TPROXY avoids user-space stack overhead, keeping CPU consumption low under high connection concurrency.
Select a TUN interface when building portable network appliances, handling custom protocols, or requiring full control over ICMP and MTU handling. While TUN incurs additional memory copies and CPU cycles, it isolates traffic cleanly from host firewall tables and delivers uniform behavior across operating systems.