Files
vpn-loopback-firewall/README.md
T
lou b55c0cb7bb 开源化: MIT LICENSE + 详细 README + deploy/watchdog + 全仓脱敏(云服IP/学校域/凭据 → RFC5737 占位)
- LICENSE: MIT © 2026 edgevoid
- README: 三层架构/逐字段伪装对照/部署/验证/安全边界/看门狗自启
- 脱敏(filter-repo 全历史): 8.136.202.225→203.0.113.10, .zjipc.cn→.internal.example, 学校DNS→192.0.2.53
- hook.sh 更新为安全形态(FORWARD-only 绑 WAN, 去掉 OUTPUT 全挂断网形态)
- router_deploy.sh: SSHPASS 改环境变量注入(明文密码清除)
- watchdog.sh 纳入 deploy/(接口变量化)
- 内部语境泛化(认证网关/学校DNS/寝室部署备注)
2026-09-09 18:14:10 +08:00

11 KiB

vpn-loopback-firewall(内网出口伪装系统)

内网多设备出口流量特征归一化系统:让网络侧检测设备看到的是一台 Windows 设备,而不是 NAT 后面一堆 Linux / Android / 路由器。

在需要账号认证、且检测共享接入的受限网络环境(校园网、酒店网、企业访客网)中,多设备通过一台网关 NAT 共享账号时,网关与接入设备的 TCP/IP 栈特征(TTL、TCP options、时间戳时钟)会暴露"NAT 后面有多个异构设备"。本系统把出口流量改写成单一 Windows 指纹,并对明文 HTTP 与 DNS 查询做加密通道套壳,使出口流量形态与"一台 Windows 设备直连"无法区分。

⚠️ 请合规使用:仅用于你有权接入的网络环境。绕过网络使用条款可能违反服务协议或当地法律,后果自负。


架构总览

                      ┌─────────────────────────────────────────────┐
                      │              检测设备(网络侧)                  │
                      │  看到: 1 个 IP · 1 套 Windows SYN 指纹         │
                      │        纯 HTTPS + 一条 8444 加密流(像普通网页)   │
                      └─────────────────────────────────────────────┘
                                        ▲
                         HTTPS 直连      │   8444 TLS(套壳通道)
                        (Windows 指纹)  │   内容加密, 无明文特征
        ┌───────────────────────────────┴───────────────┐
        │            L1 伪装器(masquerader/)             │
        │   NFQUEUE 截 FORWARD SYN → 重写 Windows 布局    │
        │   TTL 全协议统一; wscale 10→8; 剥 Timestamp     │
        └───────────────┬───────────────────────────────┘
                        │
        ┌───────────────▼───────────────────────────────┐
        │            L2 分流器(channel/proxy_*)          │
        │   HTTPS    → 直连(大头, 不动)                   │
        │   HTTP 80  → 透明劫持 → 云通道(加密)             │
        │   DNS 53   → 按域分流: 内网域本地 / 公网域云递归  │
        └───────────────┬───────────────────────────────┘
                        │  TLS 长连接(多路复用帧)
        ┌───────────────▼───────────────────────────────┐
        │            L3 云端(channel/server.py)          │
        │   AUTH token 鉴权 → 解包:                       │
        │   HTTP → 统一 Chrome UA 重新发起 HTTPS          │
        │   DNS  → 上游递归(如 223.5.5.5)                 │
        └───────────────────────────────────────────────┘

检测方视角的出口流量 = HTTPS 直连 + 一条到云服务器的加密连接,明文 HTTP 与 DNS 查询零暴露。

三层详解

L1 伪装器(masquerader/)

把 Linux/Android 的 SYN 布局改写为 Windows:

字段 Linux/Android 原生 伪装后(Windows)
TTL 64 128
window scale 10 8
Timestamp option 有(设备时钟!) 剥除(协商级移除, 多设备时钟分离信号失效)
MSS / SACK 1460 / 允许 同(一致)
checksum 重算
  • rewrite.py — 纯函数核心:输入原始 IPv4 TCP SYN 字节,输出改写后的包;非 SYN/非 IPv4/畸形一律返回 None(放行)
  • worker.py — NFQUEUE worker,双后端:官方 python3-netfilterqueue 优先,无此包的环境(如 OpenWrt 官方源没有预编译包)自动回退 ctypes 直调 libnetfilter_queue;所有 lib 调用显式声明 argtypes/restype(未声明时 64 位指针被 c_int 截断会段错误)
  • 实现铁律(断网事故复盘):改包后原包 ACCEPT,绝不 desync 重发;异常一律放行——宁可不伪装,不能断网

L2 分流器(channel/proxy_*.py)

  • proxy_http.py — 本地 HTTP 透明代理:iptables REDIRECT 进来的明文 80 请求,封装进云通道
  • proxy_dns.py — DNS 分流代理:配置 internal_suffixes(如 .internal.example)走本地权威 DNS(内网记录只有本地有),其余公网域名走云通道递归
  • client.py — 通道客户端:TLS 长连接 + 首帧 AUTH token + 断线重连,线程安全多路复用

L3 云端转发器(channel/server.py)

  • TLS 监听 + AUTH token 校验(防公网滥用)
  • HTTP 帧 → 以统一 Windows Chrome UA 重新发起 HTTPS 请求(明文变密文)
  • DNS 帧 → 向公共递归 DNS(可配)查询

目录结构

├── masquerader/
│   ├── rewrite.py          # SYN 改写纯函数核心(可单测)
│   ├── worker.py           # NFQUEUE worker(official/ctypes 双后端)
│   └── config/…            # 指纹配置(ttl/window/wscale/mss/…)
├── channel/
│   ├── proto.py            # 帧协议 [type][req_id][len][payload]
│   ├── client.py           # 通道客户端(L2)
│   ├── proxy_http.py       # HTTP 透明代理(L2)
│   ├── proxy_dns.py        # DNS 分流代理(L2)
│   └── server.py           # 云端转发器(L3)
├── deploy/
│   ├── hook.sh             # 防火墙规则(带 --queue-bypass, FORWARD-only)
│   ├── router_ctl.sh       # OpenWrt 一键分层起停(all/l1/l2)
│   └── watchdog.sh         # 看门狗(crond 自愈进程与规则)
├── scripts/
│   └── smoke_channel.py    # 端到端冒烟(起真 server+client 自检)
├── tools/
│   └── analyze_capture.py  # pcap 验收分析(伪装特征/明文泄漏)
└── config/
    ├── channel.json        # 通道配置模板(占位, 部署必改)
    └── masquerader.json    # 伪装指纹配置

快速开始

依赖

Python 3.10+ 标准库(通道部分零第三方依赖)。伪装器 worker 需要:

# 方式 A:官方绑定(推荐, 需可编译环境)
pip install netfilterqueue

# 方式 B:ctypes 直调(OpenWrt 等无法 pip 编译的环境自动回退,
#        需要 libnetfilter_queue.so, OpenWrt 自带)

1. 云端(L3)

# 生成自签证书(生产建议换 CA 签发)
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout key.pem -out cert.pem -days 365

# 配置 config/channel.json 的 cloud_side 段(端口/token/证书路径)
python3 -m channel.server config/channel.json

2. 网关侧(L1 + L2)

# 指纹规则(NFQUEUE 截 FORWARD SYN)
iptables -t mangle -A FORWARD -o <wan_if> -p tcp --syn \
  -j NFQUEUE --queue-num 100 --queue-bypass

# HTTP 明文劫持 + DNS 劫持(只劫 LAN 入站!)
iptables -t nat -A PREROUTING -i <lan_if> -p tcp --dport 80 \
  -j REDIRECT --to-ports 8080
iptables -t nat -A PREROUTING -i <lan_if> -p udp --dport 53 \
  -j REDIRECT --to-ports 8053

# 起进程
python3 -m masquerader.worker config/masquerader.json     # L1
python3 -m channel.proxy_http config/channel.json         # L2 HTTP
python3 -m channel.proxy_dns config/channel.json          # L2 DNS

OpenWrt 提示:官方包全部被 sstrip 掉 ELF section headers,pip 无法链接 netfilterqueue 的 C 扩展。解法:ctypes 后端直接用运行时 .so(dlopen 不需要 section headers);若必须用官方绑定,需用带 section 的完整版 .so 替换(从 Debian 源编译),且 musl 加载器不做传递依赖符号解析,启动需 LD_PRELOAD=libnfnetlink.so.0

OpenWrt 软路由部署(一键控制)

deploy/router_ctl.sh 提供分层起停,先撤规则再杀进程(杜绝断网窗口):

sh ctl.sh start            # 起全套(L1+L2)
sh ctl.sh stop             # 停全套(先撤规则 → bypass 放行 → 再杀进程)
sh ctl.sh start l2         # 只起套壳层
sh ctl.sh status           # 查看进程与规则状态

看门狗(watchdog.sh,随 crond 每 3 分钟):进程不在拉起、规则缺失重挂,幂等且静默,异常写 /tmp/watchdog.log:

*/3 * * * * /opt/masquerader/watchdog.sh

开机自启(/etc/rc.local,延时等 WAN 起来):

(sleep 25 && sh /opt/masquerader/ctl.sh start >> /tmp/boot.log 2>&1) &

配置说明(config/channel.json)

字段 含义
cloud.host/port 云服务器地址与端口(TLS)
cloud.token 通道鉴权 token,两端必须一致,部署前改掉占位值
http_proxy.listen_port 本地 HTTP 劫持目标端口(对应 REDIRECT 8080)
dns_proxy.listen_port 本地 DNS 劫持目标端口(对应 REDIRECT 8053)
dns_proxy.school_dns 内网/权威 DNS(解析内网记录)
dns_proxy.internal_suffixes 内网域名后缀列表,匹配则本地解析不进通道
cloud_side.* 云端配置段:监听端口、token、DNS 上游、转发 UA、TLS 证书

仓库内 config/channel.json脱敏模板,IP/域名均为占位(RFC 5737 测试地址),真实部署值只在你的部署机上。

验证方法(验收口径)

# 1. WAN 侧抓 SYN: 应见 win=64240 wscale=8 无 Timestamp ttl=128
tcpdump -i <wan_if> -c 200 -w wan.pcap "tcp[tcpflags] & tcp-syn != 0"
python3 tools/analyze_capture.py wan.pcap

# 2. 明文泄漏检查: 出站 dport 80 与公网 53 应为 0
#    (HTTP 全被套壳, DNS 全走通道/本地)
python3 tools/analyze_capture.py wan.pcap

# 3. 端到端冒烟(本机自测, 自签证书起真 server+client)
PYTHONPATH=. python3 scripts/smoke_channel.py

安全边界(血泪教训沉淀)

  1. NFQUEUE 必须带 --queue-bypass——worker 挂掉 = 不伪装,而不是断网
  2. 改包后原包 ACCEPT,绝无 zapret 式 desync 重发(重发包会与 AC 的乱序检测冲突)
  3. 劫持规则只挂 FORWARD/PREROUTING 入站,网关自身 OUTPUT 零规则——管理通道(ssh)永不碰劫持
  4. 按网卡/入接口收敛爆炸半径,不要全端口全链劫持;新规则先在隔离端口验证
  5. 先撤规则再杀进程(重启代理有 DNS 单点真空窗口 → 全机断解析的教训)
  6. 云端公网批量外连需注意厂商黑洞策略,deny_suffixes 可拉黑高风险目标

测试

python3 -m unittest discover -s tests -t . -v   # 单元测试
PYTHONPATH=. python3 scripts/smoke_channel.py   # 端到端冒烟

License

MIT © 2026 edgevoid