Monthly Archive for May, 2015

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。