2017 年的时候写过关于 x86 软路由透明代理构建方案,当时的方案是基于 overture + redsocks + shadowsocks 做的。当时用的还挺好的。

但是随着时间的推移,整个社区在变化,方案也在变化。主要遇到的几个想要迁移修改的想法,一个是 redsocks 比较老,bug 多,不怎么稳定,而且不维护了。虽然有人自己改了 redsocks2 的方案,但也不那么完美吧。二个是 shadowsocks 的问题,这个众所周知了,就不描述背景了。三个是 clash 的崛起,确实挺好用,主要是一体化解决问题,还有节点维护管理能力。所以起了换 clash 的想法。

所以,目前新的方案是 overture + clash。其实 clash 自带 dns server,但是我觉得不够稳,也不够好用,还是继续用了 overture。部署新方案的过程中,也踩了几个坑,下面主要是想记录一下这几个坑。

基本架构

网络配置:
双网卡 + NAT(iptable)+ DHCP
DNS 解析防污染:
overture(解析)+ dnsmasq(缓存)
代理:
clash
透明代理:
iptable + ipset(国内 IP 段)

网络配置

这部分不再赘述,可以继续参考 x86 软路由透明代理构建方案 中的 网络配置 部分。

DNS 解析防污染

这部分和 x86 软路由透明代理构建方案 中的 DNS 解析防污染 部分一样,只是升级了一下 overture 的配置文件。

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
{
"BindAddress": ":54",
"DebugHTTPAddress": "127.0.0.1:5555",
"PrimaryDNS": [
{
"Name": "DNSPod",
"Address": "119.29.29.29:53",
"Protocol": "udp",
"SOCKS5Address": "",
"Timeout": 6,
"EDNSClientSubnet": {
"Policy": "disable",
"ExternalIP": "",
"NoCookie": true
}
}
],
"AlternativeDNS": [
{
"Name": "cloudflare",
"Address": "1.1.1.1:53",
"Protocol": "tcp",
"SOCKS5Address": "127.0.0.1:10080",
"Timeout": 6,
"EDNSClientSubnet": {
"Policy": "disable",
"ExternalIP": ""
}
},
{
"Name": "Google",
"Address": "8.8.8.8:53",
"Protocol": "tcp",
"SOCKS5Address": "127.0.0.1:10080",
"Timeout": 6,
"EDNSClientSubnet": {
"Policy": "disable",
"ExternalIP": ""
}
}
],
"OnlyPrimaryDNS": false,
"IPv6UseAlternativeDNS": false,
"RedirectIPv6Record": false,
"WhenPrimaryDNSAnswerNoneUse": "PrimaryDNS",
"IPNetworkFile": {
"Primary": "/etc/overture/ip_network_sample",
"Alternative": "/etc/overture/ip_network_alternative_sample"
},
"DomainFile": {
"Primary": "/etc/overture/domain_sample",
"Alternative": "/etc/overture/domain_alternative_sample"
},
"HostsFile": "/etc/overture/hosts_sample",
"MinimumTTL": 0,
"DomainTTLFile" : "/etc/overture/domain_ttl_sample",
"CacheSize" : 0,
"RejectQType": [255]
}

主要是 DNS 上游解析走 tcp,并且使用 SOCKS5Address 将 DNS 解析走代理。当然也可以用 tcp-tls 类型,就是 DNS over TLS,和 tcp 走代理差别不大。

代理

clash

安装 clash,就是从 github 上下载已经编译好的二进制包。

1
2
3
4
5
mkdir -p /opt/clash && cd /opt/clash
wget https://github.com/Dreamacro/clash/releases/download/v0.17.1/clash-linux-amd64-v0.17.1.gz
gzip -d clash-linux-amd64-v0.17.1.gz
mv clash-linux-amd64-v0.14.0 clash
chmod +x clash

一个大坑是,clash 的 direct 规则出来的请求,到下面的 iptable 规则的时候,会被重新 redirect 回来给 clash,形成回环。这种时候,socket fd 会爆掉。

所以解决方案是,给 clash 单独建一个新的 user,在 iptable 加个针对 user 的规则。主要是参考了这里的方案。通过 adduser clash 新建一个叫 clash 的用户,创建了其主目录 /home/clash/

通过 id clash 得知其 uid 是 1001。下面 iptable 规则需要用。

下面的配置文件可以放在 /home/clash/.config/clash/ 目录下。

配置文件,前若干行

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
port: 8888
socks-port: 10080
redir-port: 12345
allow-lan: true
mode: Rule
log-level: warning
external-controller: '0.0.0.0:6170'
secret: ''
cfw-latency-url: 'http://www.gstatic.com/generate_204'

# 没有用 dns
dns:
enable: false
ipv6: false
listen: 0.0.0.0:54

nameserver:
- 1.2.4.8
- 114.114.114.114
- 223.5.5.5
#- tls://dns.rubyfish.cn:853
#- https://dns.rubyfish.cn/dns-query

fallback: # 与 nameserver 内的服务器列表同时发起请求,当规则符合 GEOIP 在 CN 以外时,fallback 列表内的域名服务器生效。
#- tls://dns.rubyfish.cn:853
- tls://1.0.0.1:853
- tls://dns.google:853

#- https://dns.rubyfish.cn/dns-query
#- https://cloudflare-dns.com/dns-query
#- https://dns.google/dns-query


Proxy:
以下省略

主要是说明一下我用的几个端口号,方便后面使用。

其中的坑一,allow-lan: true 必须为 true。

clash 的启动,我还是习惯用 suprvisor,习惯了。用 github 上 clash 自己用的 pm2 也行。或者自己配置 systemd 都行。只要确定启动 clash 进程的时候,必须使用 clash 这个用户。在 supervisor 里的配置是加一个 user=clash 的配置。

1
2
3
4
5
[program:clash]
user=clash
priority=1
command=/usr/local/bin/clash -d /home/clash/.config/clash/
autorestart=true

上面路径自行替换。

透明代理

这部分和 x86 软路由透明代理构建方案 中的 透明代理 部分基本一样。只是加了几条规则。

最终的 iptable 表如下:

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
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
SHADOWSOCKS tcp -- anywhere anywhere

Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
RETURN all -- anywhere anywhere owner UID match clash
SHADOWSOCKS tcp -- anywhere anywhere

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- anywhere anywhere

Chain SHADOWSOCKS (2 references)
target prot opt source destination
RETURN tcp -- anywhere anywhere tcp dpt:10144
RETURN udp -- anywhere anywhere udp dpts:4096:65535
RETURN all -- anywhere 0.0.0.0/8
RETURN all -- anywhere 10.0.0.0/8
RETURN all -- anywhere 127.0.0.0/8
RETURN all -- anywhere link-local/16
RETURN all -- anywhere 172.16.0.0/12
RETURN all -- anywhere 192.168.0.0/16
RETURN all -- anywhere base-address.mcast.net/4
RETURN all -- anywhere 240.0.0.0/4
RETURN tcp -- anywhere anywhere match-set chnroute dst
REDIRECT tcp -- anywhere anywhere redir ports 12345

加了两条规则

1
2
iptables -t nat -A OUTPUT -m owner --uid-owner 1001 -j RETURN
iptables -t nat -A OUTPUT -p tcp -j SHADOWSOCKS

第一条是避免形成回环,第二条是让网关本身可以翻墙。

然后还是用 iptables-persistent 持久化,基本就可以用了。

内核参数

这里我又被坑了一把。之前不知道啥时候,内核参数里加了一个 net.ipv4.tcp_tw_recycle = 1,然后就坑了。

导致的问题是,设备发送的 tcp 的 syn 包,被网关默默的 drop 掉了。通过 tcpdump 发现,发送出去的 tcp syn,但是没有收到任何回复。局域网内会间歇性的无法翻墙,http connect 经常超时,请求并没有走到 clash,clash 没有相应的日志。最终在 这里 的第三部分,关于 net.ipv4.tcp_tw_recycle 部分,把 1 改回 0 就好了。

网上看了一下,这个内核参数,不要乱改,基本没啥用,改了有问题,到 linux 4.14 还是什么版本来着,这个参数直接被干掉了。