Transparent Proxy and TUN

On this page

Transparent proxy allows applications to operate without explicitly using HTTP or SOCKS proxy interfaces. The host or gateway takes over selected traffic and forwards it according to policies. This does not mean applications can absolutely not observe differences, nor does it equate to transparent decryption: connection timing, addresses, protocol support, and certificate boundaries may still change.

We need to answer separately: how packets reach the proxy, how the proxy obtains the original destination, how to infer domain names, and how the proxy's own outbound traffic avoids being taken over again. Configuring only one listening port or one default route is usually insufficient to complete the entire closed loop.

RequirementKey Mechanism
Receive outbound traffic from local applicationsLocal OUTPUT path, routing, or TUN takeover
Receive traffic forwarded by other machines via gatewayInbound PREROUTING and routing delivery
Preserve the original destination address of packetsTransparent socket mechanisms like TPROXY
Obtain the domain name requested by the applicationDNS mapping, visible handshake information, or application metadata
Prevent the proxy from recursively taking over itselfOutbound marking, routing rules, or isolation boundaries
Cover both IPv4 and IPv6 simultaneouslyRouting, rules, and DNS behavior for both address families

The Three Takeover Methods Operate at Different Abstraction Layers

Taking over traffic and identifying the target are two different tasks REDIRECT: Rewrite the destination endpoint NAT / conntrack; original destination restoration depends on protocol and implementation TPROXY: Preserve the packet's destination address Transparent socket + matching rules + policy routing work together TUN: Deliver IP packets to user-space programs The program then forwards IP, or terminates the connection and converts to a proxy protocol None of the three automatically provides domain name information, nor do they automatically decrypt HTTPS; independent domain and trust policies are still required.

REDIRECT: Modifying the Destination Endpoint, Restoring Relationships via Connection Tracking

REDIRECT is a special form of Destination NAT. In the IPv4 local OUTPUT scenario, it can redirect to a loopback address; in the PREROUTING scenario, it usually selects the local address of the incoming interface; it is not correct to write all cases as changing to 127.0.0.1. iptables REDIRECT

NAT changes the destination address of the current packet, but conntrack can preserve the metadata of the original direction. TCP proxies can query the original destination via the applicable SO_ORIGINAL_DST. This is not a dedicated interface that only makes sense under the REDIRECT target, nor should it be simply written as "the original address is permanently lost."

In the Linux 7.2 source code, net/netfilter/nf_nat_redirect.c distinguishes between LOCAL_OUT and PRE_ROUTING address selection; nf_conntrack_proto.c's getorigdst queries the conntrack original destination and restricts it to corresponding protocols. UDP cannot treat this TCP interface as a universal restoration method; obtaining the original destination of datagrams requires separate design.

REDIRECT changes the normal destination address to a local address, but the proxy still needs to understand the protocol and target of the forwarded traffic. Using TLS SNI as a substitute for the original destination is also incomplete: SNI does not include the destination port, nor does it guarantee that every connection has a visible domain name.

TPROXY: Packets Do Not Undergo Destination NAT, But Still Need to Be Delivered to Local Transparent Sockets

TPROXY delivers matched traffic to sockets that support transparent reception, preserving the IP/transport layer destination endpoint. Its effective operation requires transparent listening, rule selection, marking, and policy routing to work together; applications need to set options such as IP_TRANSPARENT as required.

For example, if a rule marks selected packets as 1, a policy rule looks up table 100, and table 100 contains local type routes, non-local destination addresses can be handed to the local protocol stack. local ... dev lo here expresses local delivery, not rewriting the original destination IP to 127.0.0.1.

Having only the TPROXY action without corresponding routing, or only building routing while the proxy does not support transparent sockets, cannot form a complete path. Locally generated traffic and gateway inbound traffic take different hooks; one cannot assume that a single PREROUTING rule takes over all local outbound traffic. Linux TPROXY Documentation

The target of a TCP connection can be obtained from applicable socket semantics; UDP usually needs to handle destination metadata per datagram. Actual programs still need to correctly handle address families, ports, and return packet sources, rather than assuming a single UDP listening socket has only one permanent destination.

TUN: User-Space Device Interface for Sending and Receiving IP Packets

TUN is oriented towards IP packets, while TAP is oriented towards Ethernet frames. Linux programs create or attach devices via interfaces such as /dev/net/tun, read data sent by the kernel to that device from the file descriptor, and write returned data into the kernel's receiving path. Whether there is a 4-byte packet information prefix depends on settings like IFF_NO_PI; one cannot simply skip the same length for all implementations.

TUN itself does not perform encryption, does not parse domain names, and does not automatically provide SOCKS exits. User-space programs can encapsulate IP packets into a VPN, or run a protocol stack, terminating local TCP and establishing another connection to the proxy. The latter is not equivalent to sending original TCP segments one by one intact into a CONNECT. Linux TUN/TAP

Virtual network interfaces are not necessarily /dev/net/tun. For example, the Linux kernel's WireGuard uses its own network device implementation; the user-space version might deliver IP packets via TUN; one cannot draw all WireGuard and TUN proxies as having exactly the same data path.

Outbound Bypass: DIRECT Also Encounters Loops

The proxy's own outbound connections must have an exit that bypasses the takeover path Application Sends Packet Routing selects TUN User-Space Proxy Reads IP packet, selects forwarding policy Proxy Outbound Socket Mark / Interface / Routing Isolation Physical Exit Direct connection to target or connect to proxy node If outbound traffic again goes through the default TUN, it forms recursive takeover; DIRECT must also truly bypass TUN.

When an application's target packet is routed to TUN, and the proxy decides on DIRECT, it still needs to create a real outbound connection. If the new socket continues to hit the same default route, the connection returns to TUN, forming a recursion. The same applies to connecting to a remote proxy node; one cannot only exclude DIRECT targets and forget the node address.

Methods such as policy marks, dedicated routing tables, binding interfaces, or network namespaces can be used; specific priorities and address overrides must be coordinated. Bypassing by process UID also has scope and shared user issues; one cannot assume that any platform has the same rule semantics.

The following only simulates a single IPv4 teaching strategy: proxy outbound mark prioritizes bypass, explicit local network segments bypass, and the rest is handed to TUN. It does not modify real routing, nor does it cover all special addresses or deployment rules.

from ipaddress import ip_address, ip_network

BYPASS_MARK = 0x80
local_ranges = [ip_network('127.0.0.0/8'), ip_network('192.168.0.0/16')]

def route(destination, mark=0):
    addr = ip_address(destination)
    if mark & BYPASS_MARK:
        return 'physical'
    if any(addr in net for net in local_ranges):
        return 'local'
    return 'tun'

assert route('203.0.113.10') == 'tun'
assert route('203.0.113.10', BYPASS_MARK) == 'physical'
assert route('192.168.1.10') == 'local'
assert route('198.18.0.42') == 'tun'
# Changing the target to a remote proxy node will not naturally bypass TUN; an exit policy is required.
assert route('192.0.2.20') == 'tun'
assert route('192.0.2.20', BYPASS_MARK) == 'physical'
print('Local bypass, proxy exit, and recursion risk model passed')

"Saying 'creating TUN does not require iptables'" does not mean deployment does not need firewalls or policy routing. Mihomo's auto-route, auto-redirect, strict-route, and other options have platform and implementation limitations; one should read the current version's configuration rather than interpreting the names as fixed cross-platform behaviors. Mihomo TUN Configuration

Original Destination IP Does Not Equal Domain Name

The same IP can serve many domain names, and the same domain name can correspond to multiple addresses. IP ownership or geographic databases cannot reliably restore the name the application wants to access; "IP falling in a certain country" is not an equivalent substitute for domain name classification.

Information SourceWhat It Can ProvideMissing or Unreliable Situations
Original IP/PortNetwork targetDoes not contain service domain name
Plaintext HTTP HostApplication-declared HTTP targetCannot be directly read under HTTPS, and may be inconsistent with the connection target
TLS ClientHello SNIVisible service name hintNo SNI, ECH, or incomplete parsing materials
DNS Association TableCorrelation between a resolution and subsequent connectionsShared addresses, caching, side-channel parsing, and lifecycle differences
Fake-IP MappingName represented by locally synthesized addressMapping loss, conflicts, or client bypassing this DNS

TLS ClientHello does not allow finding SNI at a fixed "5+32" offset: records, handshake length, session ID, cipher suites, and extensions all need to be parsed according to structure, and may cross TCP read boundaries. QUIC Initial also requires removing corresponding protection and reassembling CRYPTO data; one cannot directly perform fixed offset lookups on UDP payloads.

ECH hides the inner ClientHello; the outer public name is not necessarily the application's original target. Sniffing can be used for limited routing hints and should not be treated as reliable authentication. RFC 9849: ECH

Fake-IP: Allocating Local Indexes, Not Encoding Domain Names into IPs

A proxy-controlled DNS can assign 198.18.0.42 to api.example.com; the client subsequently connects to this synthesized address; the takeover program checks the local mapping, restores the domain name, and decides the actual exit. The address itself usually cannot decode the domain name alone; the key state is in the mapping table.

Common pools use 198.18.0.0/15, but the IANA purpose of this segment is network benchmark testing, not a globally unified address reserved specifically for proxies. Deployment needs to avoid conflicts with actual network usage, and cannot let synthesized addresses flow to the public internet without mapping. IANA Special Address Registry

For example, if the client caches the synthesized address for 10 minutes, the proxy restarts after 1 minute and reassigns the same address to another domain name, it may route incorrectly. Mapping persistence, address recycling, DNS TTL, and active connection lifecycles need to be coordinated; simply ensuring "one IP, one domain name" in the current table is not enough.

If the client obtains the real IP via its own DoH, the connection can still be taken over by TUN, but the Fake-IP mapping may not exist; this is "traffic already taken over, insufficient domain name information," not necessarily "traffic completely bypassing the proxy." Conversely, only taking over UDP53 does not cover TCP DNS, DoT, or DoH. Consistent policies should be applied to A and AAAA, direct domain names, and Fake-IP filtering. Mihomo DNS Configuration

After the Mapping is Cleared, Does the IP in the Cache Still Represent the Original Domain Name?

Clear the mapping first, then connect; the address itself cannot restore the name. Then, if the address is mistakenly reused, the client still wants to access api.example, but the proxy will query other.example. Fixing this requires coordinating the state of both sides, rather than just making the current mapping table look one-to-one.

Preparing the visual
After the Mapping is Cleared, Does the IP in the Cache Still Represent the Original Domain Name?

Synthesized IPs are indexes for local mappings. The client's copy and the proxy's mapping have different lifecycles; reusing addresses too early will point old intents to new objects.

MTU, Address Family, and Fault Localization

Takeover may add encapsulation, potentially reducing the effective MTU; when small requests succeed but large transfers stall, check path MTU, ICMP error propagation, and MSS as evidence. User-space TCP stacks, UDP relays, and IP encapsulation paths handle things differently; one cannot uniformly solve all problems by "changing MTU to a fixed value."

The IPv4 default route 0.0.0.0/0 does not cover IPv6. If AAAA is available but the IPv6 path is not taken over, the application may choose an IPv6 exit; if IPv6 is blocked uniformly, it may change reachability and address selection. Configuration should be explicit based on the expected coverage, rather than treating the ability to access a certain IPv4 site as complete acceptance.

Read-only checks can view current rules and routing in order, then correlate with proxy logs:

ip rule show
ip -6 rule show
ip route show table all
ip -6 route show table all
ip -d link show
ObservationNext Step for Localization
Proxy did not receive target trafficAddress family, routing, hooks, and matching conditions
Received traffic but original destination is incorrectNAT restoration, transparent sockets, UDP metadata
Can identify target but repeatedly appears outboundOutbound bypass and mark/rule priority
Only certain domain names are misroutedDNS path, mapping, ECH, and sniffing limitations
Only UDP failsProxy exit capability, association, and MTU
Only large responses or long connections are abnormalFragmentation/PMTUD, backpressure, state timeouts

These checks are used to understand the existing deployment; when configuring, check listening, takeover, bypass, and return paths as a complete solution.

Continue Reading