Linux 策略路由与多 VPN 并存最佳实践

#GFW促进学习系列

以在路由器上配置为例.

需要 iproute2, 部分设备 (如某些 ddwrt 路由固件默认并不支持, 可能需要自己编辑安装 iproute2 包). 各个脚本路径与所用设备及系统有关, dd-wrt 固件参考 Wiki)

Startup 脚本
在设备 startup 脚本里, 创建策略路由的 ip rule 规则

ip rule add from all fwmark 0x1/0x1 table 1 prio 1
ip rule add from all fwmark 0x2/0x2 table 2 prio 2
ip rule add from all table 3 prio 3
ip rule add from all table 4 prio 4
ip rule add from all table 5 prio 5

# table 10 及以上的路由表留给 VPN, 每个 VPN 一个

# 创建内网的路由规则, 避免被后面的 VPN 全局路由表覆盖
ip rule add to 192.168.0.0/16 table main prio 1
ip rule add to 127.0.0.0/8 table main prio 1
ip rule add to 169.254.0.0/16 table main prio 1

chnroutes
推荐用这个版本, 使用 iproute2 batch 批量导入路由表, 几千条国内路由只需要一秒即可加载完毕. 需要修改下 chnroutes.py, 使其生成的 vpn-up.sh 脚本把路由项加载在 table 4 而非默认路由表上.

chnroutes.py

    upfile.write('route add %s/%s $OLDGW\n' % (ip, mask))

改为

    upfile.write('route add %s/%s $OLDGW table 4\n' % (ip, mask))

pppoe 脚本
在设备 pppoe 拨号成功脚本里, 创建与外网 NIC 相关的路由规则

# 这个命令结果类似 'via 1.2.3.4 dev ppp0 ', 1.2.3.4 是你的 pppoe 远端网关 (P-t-P IP)
WAN_GW=$(ip route show 0/0 | sed -e 's/^default//')

# table 1: 使拥有 0x1 MARK 的数据包全部走默认网关
ip route add default $WAN_GW table 1

# table 3: 在这里加入所有 VPN 的服务器 IP, 使这些 IP 都直接走默认网关.
ip route add A.B.C.D/32 $WAN_GW table 3
....

# table 4: 加载 chnroutes
/path/to/chnroutes/vpn-up.sh

VPN 脚本
在每个 VPN 的连接成功脚本里增加对应的路由规则. OpenVPN 请把 redirect-gateway 选项去掉, 使用 “up up_script.sh” 执行下面的自定义路由脚本

# 192.168.100.1 是该 VPN 的网关地址
ip route add default via 192.168.100.1 table 10

# 或者使用 tun 接口名指定, 如果配置 VPN 使用固定 tun device name 的话
# ip route add default dev tun0 table 10

P-t-P link

[email protected]:/etc/openvpn# ip addr show tun0
19: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1411 qdisc pfifo_fast state UNKNOWN qlen 100
    link/none 
    inet 192.168.100.2 peer 192.168.100.1/32 scope global tun0

对于上面这个tun设备 (OpenVPN 创建),本地 IP 是 192.168.100.2, 远端链路IP (P-t-P IP)是 192.168.100.1,P-t-P 与路由的 next hop (下一跳)概念不同。例如,对 ip route add 1.2.3.4/32 via 192.168.100.1 这条路由表,系统会直接将 packet 发送到对应的 tun 设备上。对于 192.168.100.1 这个 IP (或其所属虚拟网段) 本身的访问路由规则默认是在 main 表里自动添加的

192.168.100.1 dev tun0  proto kernel  scope link  src 192.168.100.2
# or
192.168.100.0/24 dev tun0  proto kernel  scope link  src 192.168.100.2

添加路由规则时, 使用 via 参数指定同一网段内的网关 (Gateway IP) 或 P-t-P 链路的远端IP。

ip route add A.B.C.D/32 via 192.168.100.1

PS. 这种情况下会自动根据 via 后面的地址计算出此条路由规则所属 interface 设备名; 貌似即使 tun 虚拟网卡没有指定对应的 P-t-P IP (ifconfig tun0 192.168.100.2 netmask 255.255.255.0 pointopoint 192.168.100.1)时仍然有效.

如果使用这种形式:

ip route add A.B.C.D/32 dev tun0

则直接设置数据包外发接口。

可以使用 “src 192.168.100.2” 设定数据包从接口发出时设置的源 IP (貌似一般无必要?).

NAT
设置 VPN 的 NAT 以及防火墙规则

iptables -I FORWARD -i br0 -o tun+ -j ACCEPT
iptables -I FORWARD -i tun+ -o br0 -j ACCEPT
iptables -I FORWARD -i br0 -o ipsec+ -j ACCEPT
iptables -I FORWARD -i ipsec+ -o br0 -j ACCEPT
iptables -t nat -A POSTROUTING -o tun+ -s 192.168.1.0/24 -j MASQUERADE
iptables -t nat -A POSTROUTING -o ipsec+ -s 192.168.1.0/24 -j MASQUERADE

# net.ipv4.ip_forward 不用设了, 路由器肯定是打开的.

MTU / MSS 相关
关于 TCP 连接的 MSS negotiation:

Each system announces its MSS, and the other system abides by it. When computer A sends a SYN packet with an MSS of 1,460 and computer B responds with a SYN/ACK that has an MSS of 1,380, computer A is not “proposing” that they use 1,460, and computer B is not countering with a different proposal; they are simply each announcing their own limit. B will not send any packets with a segment larger than 1,460 bytes to A, and A will not send any packets with a segment larger than 1,380 bytes to B. (There is no requirement that B send 1,460 byte segments to A; only that B not EXCEED 1,460 bytes. B can limit itself to 1,380 bytes if it chooses.). TCP 会话的一方应该会取对方告知的 MSS 和自己外发数据网卡 MTU – 40 这两者之中的较小值作为每次发送数据字节数.

只有 OpenVPN 提供 mssfix 选项, 其它 VPN 基本上都不支持设置 TCP 的 mss. 所以最好的方式是在用 iptables 统一设置所有通过 VPN tunnel 的 TCP 连接的 mss

# 自动将所有通过设备转发的数据包 TCP 连接的 mss 设为 tun MTU - 40 (理论最优值, 40 是 IPv4 Header + TCP header 长度) (如果是 IPv6, IP header 长度是 40)
iptables -t mangle -A FORWARD -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

应该只需要在路由器 或者 VPN 服务器其中之一设置即可, 貌似 ddwrt 路由器默认 iptables 规则已经包含着一条. 说明:
The –clamp-mss-to-pmtu automatically sets the MSS to the proper value, hence you don’t need to explicitly set it. It is automatically set to PMTU (Path Maximum Transfer Unit) minus 40 bytes, which should be a reasonable value for most applications.

Path Maximum Transfer Unit: 整个链路中各节点 MTU 的最小值. iptables 应该是将收到的数据包 -i 和 -o 两个 interface MTU 其中的较小值作为 PMTU.

然后设置 VPN tun 设备的 MTU, 许多 VPN 都提供了对应的配置参数 / 选项, 也可以直接用 iproute2 设置

# OpenVPN 配置 MTU 参数
tun-mtu 1400

# 通用
ip link set dev tun0 mtu 1400

需要保证 VPN 隧道经过封装后的数据包实际通过外网连接时不超过链路实际 MTU. 测试 tun MTU:

# 假设 tun 设备 MTU 设置为 1450
# 1450 - 20 (IPV4 header) -8 (ICMP header) = 1422
ping 8.8.8.8 -s 1422 -M do

如果能正常 ping 通, 则表示无问题.

对于 OpenVPN 而言, 最好的设置 MTU 方法是设置 link-mtu 参数, 将其修改为实际 Internet 网络链路MTU (1492 for pppoe) – 28 (IPv4 + UDP header), 客户端和服务器端均需要配置. 然后 OpenVPN 会自动设置生成的 tun MTU 为合适的值.

做完这些后, 多个 VPN 可以同时连接 (测试方法是能 ping 通每个 VPN 的远端 Gateway IP: ping 192.168.100.1).

策略路由
可以自行设置哪些流量走哪些 VPN.

# 优先级 (prio) 较小的路由规则优先匹配
ip rule add from all fwmark 0x4/0x4 lookup 10 prio 10
ip rule add from all lookup 11 prio 11

配合 iptables 对指定的转发流量打标记:

iptables -t mangle -A PREROUTING -j MARK --set-mark 0x4/0x4

备注:

iptables -t mangle 表里, 设置 MARK 必须在 PREROUTING / OUTPUT 里做 (因为显然必须在路由选择之前); 而设置 TCP mss 则最好在 FORWARD里做 (不适合在 POSTROUTING , 因为本机发出的数据包也会经过 POSTROUTING. 而本机发起的 TCP 连接 MSS 是自动设置为合适的值的, 不需要 iptables 再重复设置了).

设置和匹配 MARK 相关命令中的 0x4/0x4 这种写法表示对 MARK 中的某个比特位进行匹配。这样允许 MARK 里不同比特位的标记安全并存。注意 iptables 的 MARK target 不会停止遍历当前 chain。

0 Responses to “Linux 策略路由与多 VPN 并存最佳实践”


Comments are currently closed.