Ubuntu 软路由(篇二):v2ray 与透明(全局)代理

如何在路由器上配置全局透明代理,以免被防火墙误伤。


接上一篇:Ubuntu 软路由(篇一):配置基于 ubuntu 的软路由

最早的时候,我使用了 shadowsocks 为基础的端口转发方案,但是配置复杂且不稳定。
这里使用的是 V2Ray + iptables 的转发方案。实际上由于 V2ray 支持路由,做网络的分流会方便很多,也更容易管理。

安装、配置 V2Ray

安装

下载解压稳定的 v2ray 文件到安装目录,我这里用的是 /home/ubuntu/app/install/v2ray。为了获取稳定的新版本,我使用的是从 github 仓库下载的最后一版,仓库地址

1
2
3
cd /home/ubuntu/app/install
wget https://github.com/v2fly/v2ray-core/releases/download/v4.33.0/v2ray-linux-64.zip
unzip -d v2ray v2ray-linux-64.zip

解压之后目录内容如下:

/home/ubuntu/app/install/v2ray :

1
2
3
4
5
6
7
8
9
10
11
├── config.json
├── geoip.dat
├── geosite.dat
├── systemd
│   └── system
│   ├── v2ray.service
│   └── v2ray@.service
├── v2ctl
├── v2ray
├── vpoint_socks_vmess.json
└── vpoint_vmess_freedom.json

接下来准备 systemd 的服务注册文件(用于将 v2ray 注册称一个服务以便自启动),
修改文件 /home/ubuntu/app/install/v2ray/systemd/system/v2ray.service, 原内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=V2Ray Service
Documentation=https://www.v2fly.org/
After=network.target nss-lookup.target

[Service]
User=nobody
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
NoNewPrivileges=true
ExecStart=/usr/local/bin/v2ray -config /usr/local/etc/v2ray/config.json
Restart=on-failure
RestartPreventExitStatus=23

[Install]
WantedBy=multi-user.target

有两处需要修改:

1
2
# ExecStart... 启动命令,修改启动文件位置、配置文件位置:
ExecStart=/home/ubuntu/app/install/v2ray/v2ray -config /home/ubuntu/app/install/v2ray/config.json

接下来将 v2ray 注册到 systemd,
(这里每次修改 service 文件之后都需要重新执行这个操作)

1
2
3
4
5
6
# 将service文件复制到系统的service文件存放位置
cp /home/ubuntu/app/install/v2ray/systemd/system/v2ray.service /etc/systemd/system/v2ray.service
# 更新service目录
sudo systemctl daemon-reload
# 启动v2ray
sudo systemctl start v2ray.service

查看 v2ray 运行状态:

sudo systemctl status v2ray.service

如下 log,当 Active 状态是 active 时,启动完成。

1
2
3
4
5
6
7
8
9
10
● v2ray.service - V2Ray Service
Loaded: loaded (/etc/systemd/system/v2ray.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2021-03-15 00:53:22 CST; 4h 5min ago
Docs: https://www.v2ray.com/
https://www.v2fly.org/
Main PID: 56320 (v2ray)
Tasks: 11 (limit: 4501)
Memory: 27.7M
CGroup: /system.slice/v2ray.service
└─56320 /home/ubuntu/app/install/v2ray/v2ray -c /home/ubuntu/app/v2ray.json

配置 v2ray

记录一下简要的配置备忘:
为了可读性和维护性,我用了 yaml 记录配置文件。v2ray 实际并不支持 yaml 格式的配置文件,将 yaml 先转成 json 才能够正常使用。

yaml 转 json: yq -j eval my_config.yaml > my_config.json

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
log:
access: /dev/null # 输出到空设备(丢弃),留空时将输出到 stdout
error: /home/ubuntu/app/log/v2ray/error.log # 留空时将输出到 stdout
logLevel: warning # debug|info|warning|error|none # 默认waring
inbounds: 入口
- tag: in_transparent # dokodemo-door 协议的inbound,用于透明代理流量
port: 1081 # 使用环境变量: env:MY_V2RAY_PORT
listen: 192.68.1.1 # 默认 0.0.0.0
protocol: dokodemo-door # blackhole|dokodemo-door|freedom|http|shadowsocks|socks|vmess
settings: # 具体的配置内容,视协议而定
network: tcp,udp
followRedirect: true
sniffing: # 探测流量,根据目的地址重制连接的目标
enabled: true
destOverride:
- http
- tls
streamSettings: # 底层传输配置,优先于全局的传输配置
sockopt:
tproxy: redirect
outbounds: # rules没有匹配到的流量默认走第一个outbound
- tag: out_direct
protocol: freedom
- tag: &my_server_tag my_server_tag
protocol: vmess
sendThrough: 0.0.0.0
settings:
vnext: # 以下是服务器信息
- address: my_server.com
port: 2048
users:
- id: aaaaaaaa-aaaa-aaaa-aaaaa-aaaaaaaaaaaaa
alterId: 64
streamSettings:
network: tcp # 默认tcp, tcp|kcp|ws|http|domainsocket,
sockopt:
mark: 0 # 非0时,在传出连接上标记 SO_MARK
tcpFastOpen: false
mux: # 用于在一条TCP数据连接上承载多条逻辑TCP链路,可以加速握手
enabled: true
concurrency: 4 # 默认8,范围:[1,1024],-1 表示加载Mux模块
routing:
domainStrategy: IPIfNonMatch
rules:
- type: field # 通过域名匹配国外流量: 常用的网站集合
domain:
- geosite:google
- geosite:facebook
- geosite:geolocation-!cn
outboundTag: *my_server_tag
- type: field # 通过域名匹配国外流量:自定义域名
domain: # 可以添加自己的域名:
- firecore.com # infuse 官网
- thetvdb.com # jellyfin 刷新元数据用
- themoviedb.org # jellyfin 刷新元数据用
- omdbapi.com # jellyfin 刷新元数据用
outboundTag: *my_server_tag
transport: # 传输设置,暂留空
policy: # 本地策略,暂留空

之后重启 v2ray 即可生效: sudo systemctl restart v2ray.service

配置 iptables

v2ray 已经运行起来,还需要将路由器收到的流量转发至 v2ray 监听的端口,让 v2ray 来接管需要代理的流量:

这里用一条明叫 V2RAY 的链来过滤需要被接管的流量,过滤不通过的流量会按照默认规则路由,不会走到 V2ray:

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
# 首先清空这条链(不存在会报错)
sudo iptables -t nat -F V2RAY
# 新建链
sudo iptables -t nat -N V2RAY

# 私有IP处理,RETURN: 过滤不通过,返回
sudo iptables -t nat -A V2RAY -d 0/8 -j RETURN
sudo iptables -t nat -A V2RAY -d 10/8 -j RETURN
sudo iptables -t nat -A V2RAY -d 127/8 -j RETURN
sudo iptables -t nat -A V2RAY -d 169.254/16 -j RETURN
sudo iptables -t nat -A V2RAY -d 172.16/12 -j RETURN
sudo iptables -t nat -A V2RAY -d 192.168/16 -j RETURN
sudo iptables -t nat -A V2RAY -d 224/4 -j RETURN
sudo iptables -t nat -A V2RAY -d 240/4 -j RETURN
sudo iptables -t nat -A V2RAY -d 255.255.255/32 -j RETURN

# 其他不想要代理的IP:
# 这里一定需要将代理服务器的IP加进来(如果服务器是域名参考后面的说明):
sudo iptables -t nat -A V2RAY -s my_v2ray_remote_server_ip -j RETURN
sudo iptables -t nat -A V2RAY -s 10.10.20.11 -j RETURN
sudo iptables -t nat -A V2RAY -s 10.10.20.12 -j RETURN

# 需要代理的流量转发至V2ray的端口:1081,以下两行分别设置通过单个IP匹配、通过网络号匹配:
sudo iptables -t nat -A V2RAY -s 10.10.21.0/24 -p tcp -j REDIRECT --to-ports 1081
sudo iptables -t nat -A V2RAY -s 10.10.22.2 -p tcp -j REDIRECT --to-ports 1081

# 也可以直接设置剩下的所有流量全部转发,需要谨慎使用:
# sudo iptables -t nat -A V2RAY -p tcp -j REDIRECT --to-ports 1081

# 将 POSTROUTING & OUTPUT 链的流量转发到V2RAY,具体只是搜索:iptable三表五链
sudo iptables -t nat -A PREROUTING -p tcp -j V2RAY
sudo iptables -t nat -A OUTPUT -p tcp -j V2RAY

备份修改后的 iptables

1
sudo /bin/bash /home/root/myipset.bash backup

具体参考上一篇 blog.

至此,被代理的主机已经可以无感穿过防火墙了。

iptables 规则过滤域名

iptables 仅能够过滤 IP,但是可以通过曲线救国手段过滤某些域名。
这个曲线救国的工具就是 ipset ubuntu 默认不带,需要先安装:

1
2
3
4
sudo apt-get install ipset -y

# 创建一个ipset(本质就是一个ip的列表)
sudo ipset create my-v2ray-ip-list hash:ip

编辑 dnsmasq 的配置文件:sudo vim /etc/dnsmasq.conf
追加写入:

1
2
3
# 用于将域名 v2ray_server_host 匹配到的域名的ip写入 my-v2ray-ip-list 中
# 距离 baidu.com 可以匹配到 baidu.com, pan.baidu.com 等。
ipset=/v2ray_server_host/my-v2ray-ip-list

之后重启 dnsmasq 服务即可生效 systemctl restart dnsmasq.service

持久化 ipset 的配置

由于 ipset 关机会丢失,这里为它写一个自动保存和加载的脚本,参考上一篇中 iptables 的脚本。

service 文件路径 /etc/systemd/system/myipset.service
处理 ipset 的脚本路径 /home/root/ipset.bash
ipset 备份文件保存路径 /home/root/backup.ipset.ipv4

创建文件 service 文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=backup ipset when shutdown & restore ipset when boot
Before=myiptables.service

[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/bin/bash /home/root/ipset.bash restore
ExecStop=/bin/bash /home/root/ipset.bash backup

[Install]
WantedBy=multi-user.target

上面用到的保存 ipset 数据的脚本 ipset.bash 内容如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
default_backup_file="/home/root/backup.ipset.ipv4" # 关机时备份文件的位置,需要自定义
echo "$default_backup_file"

action=$1
backup_file=${2:-$default_backup_file}

if [ ! "${backup_file}" ]; then
echo "param 2 error: should not empty."
exit 1
fi
if [ "$action" == 'restore' ]; then
/sbin/ipset restore <"${backup_file}"
exit 0
fi
if [ "$action" == 'backup' ]; then
ensure_dir_exist "$(dirname "$backup_file")"
/sbin/ipset save >"$backup_file"
exit 0
fi
echo backup_file path: "${default_backup_file}"
echo "user param: 'backup' or 'restore'"
exit 1

保存好脚本
之后手动保存一次 ipset 的数据: ipset save > /home/root/backup.ipset.ipv4
接下来开始激活 ipset.service:

1
2
3
4
sudo systemctl daemon-reload # systemd刷新service文件
sudo systemctl enable myipset # 启用开机自启动 myipset
sudo systemctl start myipset # 运行 myipset

重新开关机之后也可以正常运行了。