---
title: 透明代理与 TUN
url: https://doc.liz6.com/networking/07-proxy-protocol/02-transparent-proxy-and-tun
locale: zh
area: networking
tags:
- networking
- 代理协议
date: 2026-06-30
modified: 2026-06-30
description: 不需要客户端配置的代理——在网关处静默劫持流量,对用户完全透明。核心挑战是分流:从 ClientHello 的 SNI 或 fake-ip DNS 判断"这个连接去哪儿",TPROXY 和 TUN 是两种劫持实现,各有利弊。
---

# 透明代理与 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*
