家用网络透明代理实践

所需设备

  1. 主路由选择以下三种路由设备之一,基础版面向的普通用户(一般买的家用无线路由器都支持),进阶版与linux系列路由器可定制一些高级功能,并且能在旁路由不稳定的情况下,保证家中不断网。
  • 基础版(支持关闭DHCP功能的路由器均可)
  • 进阶版(能修改路由表、配置DHCP Option的路由器均可)
    • Openwrt/Padavan/RouterOS 等底层是linux的路由器
  1. 旁路由选择性能比较好的设备,安装linux系统或以linux为底层的路由器系统。
  • 基础版:斐讯N1、树莓派等微型计算机
  • 进阶版:x86软路由,虚拟机等

网络拓扑结构

旁路由设备与普通电脑、手机、电视设备一样接入到主路由的LAN网络中。
旁路由建议网线连接。

设计目标

  1. 访问国内IPv4/IPv6地址段直连,其余流量走旁路由设备
  2. 旁路由设备运行Clash TUN模式,由TUN网卡接管流量,交由Clash进一步分流处理。
  3. 双栈域名解析,IPv4优先(IPv6路由太差,不够稳定)
  4. 根据用户设备分为两类,一类正常上网,一类走旁路由设备分流代理。
  5. 不需要在每台设备挂代理与配置静态IP,客户端零配置,直接接入家庭网络即可。

运营商光猫与主路由默认配置

  1. 光猫改桥接
  2. 主路由PPPoE拨号

方案一(主路由为基础版)

因为主路由的功能有限,尤其是网上买的普通家用无线路由器。
这种情况下,如果不更换主路由就需要由旁路由设备来代管一些功能。
要求旁路由设备与主路由设备同样稳定,避免影响家里其他用户正常上网。

主路由配置

关闭DHCP即可,其他参照默认配置。

旁路由配置

旁路由需要开启路由转发。

DHCP配置(dnsmasq)

设计目标

根据用户设备分为两类,一类走主路由,主路由提供的DNS正常上网,一类走旁路由设备分流代理。

实施方法

对于需要透明代理的设备下发以下DHCP配置:
DHCP Options 3(默认网关):旁路由IP地址
DHCP Options 5(DNS):旁路由IP地址
对于不需要透明代理的设备默认配置即可,即网关为主路由,DNS为主路由或运营商下发的DNS。

那么问题来了,怎么做到针对不同的设备下发不同的DHCP配置呢?
这里可以通过配置dnsmasq来实现,参考底部参考文献[2]的文章
大致是通过MAC地址或者IP段来匹配,从而下发不同的DHCP配置。

缺点

  1. 需要提前搜集用户设备的MAC地址,如果用户设备的MAC地址发生变化,那么就会导致DHCP配置失效,需要重新配置。
  2. 多网卡设备,像笔记本电脑既可以插网线,又能连wifi,这是两个独立的网卡,因此两个mac地址都要配置。
  3. 不够安全,知道套路的人,可以自己配置静态ip使用代理。
  4. 不够灵活,每次修改DHCP配置,都需要等待用户设备重新获取IP地址,才能生效。

DNS服务器配置(mosdns)

mosdns的介绍和二进制可执行文件,参考项目官方地址[3].

设计目标

  1. 针对解析结果为国内IP的域名,转发至国内DNS,解析结果为海外IP的域名,转发至Clash DNS。
  2. 针对DNS解析结果分流,解析结果为国内IP的正常返回,解析结果为海外IP的返回fake-ip。
  3. 针对纯IPv6的域名解析,直接返回。(机场一般不支持v6,v6直连最好)

实施方法

我写了一份配置文件,参考了这个配置[4],有以下几点需要注意:

  1. forward_remote部分填写Clash的DNS地址。
  2. china_ip中的文件自己从网上搜索[6],主要是排除国内IP段与本地网络。是否排除本地网段[7]看自己需求,GFW有将IP污染到保留地址的先例。
  3. 纯IPv6的域名解析,我希望能直接直连,因此专门增加了规则。不需要可以删除。
  4. 转发至国内的DNS,建议填写国内的加密DNS,避免因配置防火墙53端口劫持的时候污染到结果。
  5. 添加fallback插件,当forward_remote挂了,自动回滚到forward_local。
  6. 设置ttl为1,方便clash恢复时能尽快拿到fake-IP。
/etc/mosdns/config.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
log:
level: error
file: "/tmp/mosdns.log"

plugins:
# 缓存
- tag: cache
type: cache
args:
size: 1024
lazy_cache_ttl: 86400

# 中国与保留IP
- tag: china_local_ip
type: ip_set
args:
ips:
- "192.168.0.0/16"
- "10.0.0.0/8"
- "172.16.0.0/12"
- "127.0.0.0/8"
- "100.64.0.0/10"
- "::1/128"
- "fc00::/7"
- "fe80::/10"
- "fd00::/8"
files:
- ./all_cn.txt
- ./all_cn_ipv6.txt

# 转发至国内DNS,并发查询
- tag: forward_local
type: forward
args:
concurrent: 2
upstreams:
- addr: "https://223.5.5.5/dns-query"
- addr: "https://120.53.53.53/dns-query"

# 转发至国外DNS,并发查询
- tag: forward_remote
type: forward
args:
concurrent: 2
upstreams:
- tag: CLOUDFLARE
addr: "https://gd.home999.cc/dns-query"
bootstrap: "223.5.5.5"
- tag: ALIYUN
addr: "https://sub.lyz05.cn/dns-query"
bootstrap: "223.5.5.5"

# 转发至Clash
- tag: forward_clash
type: forward
args:
concurrent: 1
upstreams:
- addr: udp://192.168.2.10:1053

# 如果forward_clash挂了,可以自动回滚
- tag: "fallback"
type: fallback
args:
primary: forward_clash
secondary: forward_remote
threshold: 100
always_standby: false

# 主运行序列
- tag: main_sequence
type: sequence
args:
# 是否匹配代理列表
- matches: qname &./proxy-list.txt
exec: $forward_clash
- matches: has_resp
exec: accept

# 是否匹配直连列表
- matches: qname &./direct-list.txt
exec: $forward_local
- matches: has_resp
exec: accept

- exec: prefer_ipv4

# 动态域名跳过缓存
- matches: "!qname lyz05.cn"
exec: $cache
- matches: has_resp
exec: accept

# 执行查询后匹配解析结果是中国或保留地址
- exec: $forward_local
- matches:
- "has_resp"
- "resp_ip $china_local_ip"
exec: accept

# 接受所有的IPv4 A记录为空的本地查询结果
- matches:
- "!has_wanted_ans"
- "qtype 1"
exec: accept

# 接受所有的IPv6 AAAA记录的本地查询结果
- matches:
- "has_wanted_ans"
- "qtype 28"
exec: accept

# fallback
- exec: $fallback


# 启动监听服务
- tag: udp_server
type: udp_server
args:
entry: main_sequence
listen: :1053

- tag: tcp_server
type: tcp_server
args:
entry: main_sequence
listen: :1053

Clash

参考Clash的官方项目[5],下载二进制文件并使用。注意下载Permium版本的。
配置文件参考项目的示例文件,根据自己的需求修改即可。
大体上就是增加dns的配置
TUN模式的auto-route需要关闭,原因是:auto-route会默认添加默认路由到TUN网卡。这样国内直连的IP就会走TUN网卡,被Clash接管。对于性能弱的设备不好,影响网速。
所以也就带来了个缺点:对于不用域名解析只用IP访问的软件,会连不上,像Telagram,所以我们要单独加静态路由,让Telegram的IP[8]走TUN网卡。

/etc/clash/config.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
mixed-port: 7890
socks-port: 7891
redir-port: 7892
tproxy-port: 7893
allow-lan: true
mode: rule
log-level: warning
external-controller: :9090
external-ui: ui
profile:
store-selected: true

dns:
enable: true
enhanced-mode: fake-ip
listen: :1053
nameserver:
- "https://223.5.5.5/dns-query"
- "https://120.53.53.53/dns-query"
fake-ip-filter:
- +.stun.*.*
- +.stun.*.*.*
- +.stun.*.*.*.*
- +.stun.*.*.*.*.*
- "*.n.n.srv.nintendo.net"
- +.stun.playstation.net
- xbox.*.*.microsoft.com
- "*.*.xboxlive.com"
- "*.msftncsi.com"
- "*.msftconnecttest.com"
- WORKGROUP

tun:
enable: true
stack: system
dns-hijack:
- tcp://8.8.8.8:53
- 8.8.8.8:1053
auto-detect-interface: true
auto-route: true # 根据需要开启


模拟访问过程

设备为透明代理用户

访问国内网站(百度)

  1. baidu.com的域名,发送查询请求给DHCP下发的DNS(旁路由)。
  2. mosdns查询国内公共DNS后,发现baidu.com是国内IP,直接返回此IP。
  3. 发送访问请求到baidu.com的真实IP,此IP不是局域网IP,将数据包发给网关(旁路由)。
  4. 旁路由收到数据包,检查本机路由表,此IP不属于TUN网段198.18.0.0/16,走默认路由即物理网卡(主路由)。
    全程没有经过Clash,但流量会从旁路由饶一下,最终旁路由还是会把数据包原封不动(指网络层以上)交给主路由。

访问海外网站(Google)

  1. google.com的域名,发送查询请求给DHCP下发的DNS(旁路由)。
  2. mosdns查询国内公共DNS后,发现google.com是双栈海外IP。
  3. mosdns查询Clash的DNS,获得Clash的fake-ip,IP属于TUN网段198.18.0.0/16
  4. Google域名是双栈IP,mosdns设置了ipv4_prefer,会忽略IPv6结果。避免系统拿到IPv6走物理网卡出。
  5. 发送访问请求到google.com的fake-ip,此IP不是局域网IP,将数据包发给网关(旁路由)。
  6. 旁路由收到数据包,检查本机路由表,此IP属于TUN网段198.18.0.0/16,走Clash的TUN网卡。
  7. Clash根据配置的规则,选择一个合适的代理或直连,发送数据包。
    流量经过clash,会经过设备CPU处理,代理也有加解密的部分,会比直连的转发性能差。

方案二(主路由为进阶版或类linux系统)

主路由配置

添加TUN网段198.18.0.0/16的静态路由,让TUN网段的流量走旁路由。
对于不适用域名解析,直接使用IP联网的应用,如:Telegram[8],需要进一步添加静态路由到旁路由。
DHCP下发的DNS根据需要,分两种情况:

  • 正常上网用户:运营商下发的DNS
  • 走透明代理用户:旁路由的IP
    实际上就是将旁路由的DHCP功能放到主路由上,旁路由只做透明代理。

旁路由配置

mosdns和Clash部分跟方案一一样。
因为主路由能够控制DHCP了,所以旁路由不在需要是DHCP服务器了。
对于不适用域名解析,直接使用IP联网的应用,如:Telegram,需要打开auto-route,让旁路由自动添加默认路由到Clash的TUN网卡。

模拟访问过程

设备为透明代理用户

访问国内网站(百度)

与方案一不同的地方是,拿到国内IP会走主路由直接出去,进一步减轻了旁路有的负担。
唯有拿到TUN网段198.18.0.0/16的IP才会走旁路由。(因为设置了静态路由)
全程没有旁路由,跟正常上网一模一样,缺点是依赖旁路有的DNS,即mosdns。
如果旁路由挂了,域名解析不了。

访问海外网站(Google)

与方案一不同的地方是,数据包会先到主路由,做一次路由选择,再到旁路由。

附录

systemd配置

Clash

/etc/systemd/system/clash.service
1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=Clash daemon, A rule-based proxy in Go.
After=network-online.target

[Service]
Type=simple
Restart=always
ExecStart=/usr/local/bin/clash -d /etc/clash

[Install]
WantedBy=multi-user.target

mosdns

/etc/systemd/system/mosdns.service
1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=MosDNS DNS Server
After=network-online.target

[Service]
Type=simple
Restart=always
ExecStart=/usr/local/bin/mosdns start -c /etc/mosdns/config.yaml -d /etc/mosdns

[Install]
WantedBy=multi-user.target

自动更新订阅并下载分流规则

/root/sub.sh
1
2
3
4
5
6
7
#!/bin/bash
mv /etc/clash/config.yaml /etc/clash/config.yaml.bak
wget "订阅地址" -O /etc/clash/config.yaml
wget "https://ispip.clang.cn/all_cn.txt" -O /etc/mosdns/all_cn.txt
wget "https://ispip.clang.cn/all_cn_ipv6.txt" -O /etc/mosdns/all_cn_ipv6.txt
systemctl restart clash
systemctl restart mosdns

查看Clash中WARN级别漏网之鱼日志

当设置漏网之鱼为直连时,可能会有一些域名无法正常解析,这时候就需要查看日志,找到这些域名,然后添加到自定义代理域名中。

/root/log.sh
1
2
3
#!/bin/bash
journalctl -u clash > /tmp/clash.log
grep -P 'WRN(?=.*漏网之鱼)' /tmp/clash.log | less

参考文献