On this page

Transparent Proxy and TUN

Proxies that require no client configuration—silently hijacking traffic at the gateway, completely transparent to users. The core challenge is traffic splitting: determining "where this connection should go" based on the SNI in ClientHello or fake-ip DNS. TPROXY and TUN are two hijacking implementations, each with its own pros and cons.

Overview

Transparent proxies require no client configuration—silently hijacking traffic at the gateway, completely transparent to users. Implementation methods range from nftables REDIRECT to TPROXY to TUN virtual network interfaces. The core challenge is traffic splitting: how does the proxy know whether a connection is destined for baidu.com or google.com? TLS SNI sniffing extracts the domain name from the ClientHello, while fake-ip DNS is even more thorough—encoding domain names into fake IPs and looking them up in reverse during the connection phase.

Problems Solved by Transparent Proxies

Traditional proxies require the client to explicitly configure the proxy address (browser settings / system proxy / application-level proxy). Transparent proxies eliminate this requirement: traffic is silently hijacked at the gateway, and the client is completely unaware of the proxy's existence.

Three Implementation Methods

REDIRECT (iptables NAT)

iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 7891

Principle: DNAT — Rewrites the destination IP:port to 127.0.0.1:7891
Issue: The proxy does not know the original destination address (the original dst IP:port is lost)
→ The proxy needs to:
  1. getsockopt(SO_ORIGINAL_DST) — Only valid for REDIRECT (conntrack records the original destination)
  2. Sniffing — Extract the domain name from the TLS ClientHello SNI or HTTP Host header

TPROXY

iptables -t mangle -A PREROUTING -p tcp -j TPROXY --on-port 7891 --tproxy-mark 0x1/0x1
ip rule add fwmark 0x1 table 100
ip route add local default dev lo table 100

Difference between TPROXY and REDIRECT:
  REDIRECT: Changes dst IP:port → Loses the original destination
  TPROXY: Does not change dst IP:port → Retains the original destination address
  → Proxy: The original dst IP:port is still there → Directly read the socket's original destination → No need for SO_ORIGINAL_DST
  → Can proxy non-local traffic (acting as a true gateway transparent proxy)

  TPROXY mark: Marks packets → Policy routing (table 100) → Routes to local (even if dst is not local)
  Principle: "local default dev lo" → Forces delivery to the local protocol stack → Proxy receives the packet

TUN device

TUN does not require iptables—it creates a virtual NIC and intercepts traffic at the IP layer:

[TUN interface: utun / tun0] ← mihomo / WireGuard / any VPN
  ↑ Routing: 0.0.0.0/0 → tun0 (all traffic goes through TUN)

Application → Routing → tun0 → mihomo protocol stack processing → Traffic splitting:
  → DIRECT: Reroute (bypass tun0) → Out via physical NIC
  → PROXY: Encrypt + Encapsulate → Physical NIC → Proxy node

nftables assistance (at the tun0 egress):
  table inet mihomo {
    chain prerouting {
      type filter hook prerouting priority -150; policy accept;
      ip daddr 192.168.0.0/16 return   ← LAN traffic is not hijacked
      meta mark 0x1 return              ← Already marked packets are not hijacked again
    }
  }

Domain-based Traffic Splitting: Sniffer + Fake-IP

The proxy needs to know "is this connection going to baidu.com or google.com?" to decide between DIRECT and PROXY. There are two ways to obtain the domain name:

Sniffer (Passive Sniffing)

TLS ClientHello (plaintext, before Encrypted ClientHello is widely deployed):
  Offset 5 + 32 bytes (random) + session_id + cipher_suites
  → SNI Extension (type=0x0000) → hostname
  → mihomo: Upon receiving ClientHello, peek at the first few bytes → Extract SNI → Make traffic splitting decision

HTTP:
  GET http://example.com/path HTTP/1.1
  Host: example.com
  → Directly read the Host header

QUIC:
  SNI in the QUIC Initial packet (within TLS ClientHello, but QUIC uses CRYPTO frames)
  → Similar to TLS, but requires parsing QUIC frame wrapping

Fake-IP DNS

Normal DNS: The client obtains baidu.com → 123.125.115.110 → When establishing a TCP connection, only the IP is visible → The proxy must guess the domain name based on IP geolocation (GEOIP).

Fake-IP: DNS returns fake addresses in the 198.18.0.0/15 range, where each IP corresponds to a unique domain name:

Client: DNS query google.com
mihomo (fake-ip DNS): Returns 198.18.0.42 (not the real IP)
Client: TCP connect 198.18.0.42:443
mihomo TUN: Receives SYN → Looks up fake-ip table → 198.18.0.42 = google.com → Makes traffic splitting decision

GEOIP degrades to a fallback. Domain-based traffic splitting relies on the accuracy of fake-ip.

References

  • mihomo: wiki.metacubex.one/config/dns
  • TPROXY: kernel Documentation/networking/tproxy.txt
  • nftables: wiki.nftables.org

Keywords: transparent proxy, TPROXY, TUN, sniffer, fake-ip, nftables, policy routing