本页目录

透明代理与 TUN

不需要客户端配置的代理——在网关处静默劫持流量,对用户完全透明。核心挑战是分流:从 ClientHello 的 SNI 或 fake-ip DNS 判断"这个连接去哪儿",TPROXY 和 TUN 是两种劫持实现,各有利弊。

概述

透明代理不需要客户端配置——在网关处静默劫持流量,对用户完全透明。实现方式从 nftables REDIRECT 到 TPROXY 到 TUN 虚拟网卡。核心挑战是分流:代理如何知道一个连接去的是 baidu.com 还是 google.com?TLS SNI sniffing 从 ClientHello 提取域名,fake-ip DNS 则更彻底——将域名编码为假 IP,连接时反向查表。

透明代理解决的问题

传统代理需要 client 显式配置代理地址 (浏览器设置 / 系统 proxy / 应用层 proxy)。透明代理消除这个要求: 在网关处无声劫持流量, client 完全不知道代理存在。

三种实现方式

REDIRECT (iptables NAT)

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

原理: DNAT — 改写目标 IP:port 为 127.0.0.1:7891
问题: 代理不知道原始目标地址 (原本的 dst IP:port 丢失了)
→ 代理需要:
  1. getsockopt(SO_ORIGINAL_DST) — 只对 REDIRECT 有效 (conntrack 记录了原始目标)
  2. sniffing — 从 TLS ClientHello SNI 或 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

TPROXY 与 REDIRECT 的区别:
  REDIRECT: 改 dst IP:port → 丢失原始目标
  TPROXY: 不改 dst IP:port → 保留原始目标地址
  → 代理: 原本的 dst IP:port 还在 → 直接读 socket 的原本 destination → 不需要 SO_ORIGINAL_DST
  → 可以代理非本机的流量 (作为真正的网关透明代理)

  TPROXY mark: 标记包 → policy routing (table 100) → 路由到本地 (即使 dst 不是本机)
  原理: "local default dev lo" → 强制交给本地协议栈处理 → 代理接收包

TUN device

TUN 不需要 iptables—它创建一个虚拟 NIC,把流量在 IP 层拦截:

[TUN interface: utun / tun0] ← mihomo / WireGuard / any VPN
  ↑ 路由: 0.0.0.0/0 → tun0 (所有流量走 TUN)

应用 → 路由 → tun0 → mihomo 协议栈处理 → 分流:
  → DIRECT: 重新路由 (绕过 tun0) → 物理网卡出去
  → PROXY: 加密 + 封装 → 物理网卡 → 代理节点

nftables 辅助 (在 tun0 出口处):
  table inet mihomo {
    chain prerouting {
      type filter hook prerouting priority -150; policy accept;
      ip daddr 192.168.0.0/16 return   ← 局域网不劫持
      meta mark 0x1 return              ← 已标记的不再劫持
    }
  }

域名分流: Sniffer + Fake-IP

代理需要知道"这个连接是去 baidu.com 还是 google.com"来决策 DIRECT vs PROXY。两种方式获取域名:

Sniffer (被动嗅探)

TLS ClientHello (明文, 在 Encrypted ClientHello 普遍部署前):
  偏移 5 + 32 bytes (random) + session_id + cipher_suites
  → SNI Extension (type=0x0000) → hostname
  → mihomo: 在收到 ClientHello 时, peek 前几个 bytes → 提取 SNI → 分流决策

HTTP:
  GET http://example.com/path HTTP/1.1
  Host: example.com
  → 直接读 Host header

QUIC:
  QUIC Initial packet 的 SNI (在 TLS ClientHello 中, 但 QUIC 用 CRYPTO frames)
  → 类似 TLS, 但需要解析 QUIC frame wrapping

Fake-IP DNS

普通 DNS: client 获得到 baidu.com → 123.125.115.110 → TCP 连接时只看到 IP → 代理必须用 IP 归属 (GEOIP) 猜测域名。

Fake-IP: DNS 返回假的 198.18.0.0/15 地址,每个 IP 对应唯一域名:

Client: DNS query google.com
mihomo (fake-ip DNS): 返回 198.18.0.42 (不是真实 IP)
Client: TCP connect 198.18.0.42:443
mihomo TUN: 收到 SYN → 查 fake-ip table → 198.18.0.42 = google.com → 分流决策

GEOIP 退化为 fallback。域名分流依赖 fake-ip 的准确性。

参考

  • 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