前几天使用 OpenWrt 测试 GRE 功能时发现,在 4G 网络和有线网之间切换会导致掉线,遂简单排查了一下问题所在。
情景复现 本机
系统:OpenWrt
有线网卡(wan):eth1,IP:192.168.0.194,子网掩码:255.255.255.0,网关:192.168.0.1
有线网卡(lan):br-lan,IP:192.168.3.1,子网掩码:255.255.255.0
无线网卡(wwan):usb0,IP:10.221.139.224,子网掩码:255.255.255.0,网关:10.221.139.1
无线网卡使用中国移动 4G 卡上网
GRE 对端
系统:OpenWrt
有线网卡(wan):eth1,IP:192.168.0.242,子网掩码:255.255.255.0,网关:192.168.0.1
有线网卡(lan):br-lan,IP:192.168.2.218,子网掩码:255.255.255.0
网络拓扑图
本机 GRE 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 cat /etc/config/network config interface 'gresbksg' option disabled '0' option peeraddr '192.168.0.242' // 对端IP option proto 'gre' option mtu '1280' option peerlocalip '192.168.2.0' // 对端 option peerlocalmask '255.255.255.0' option zone 'wan' config interface 'gresbksg_grestatic' option ifname '@gresbksg' option disabled '0' option ipaddr '192.168.5.1' // GRE隧道IP,对端为192.168.5.2 option netmask '255.255.255.0' option proto 'static' option zone 'wan'
GRE 正常建立,流量走 eth1,此时的路由表:
1 2 3 4 5 6 7 8 9 10 root@pg-2049671F7524:~# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.0.1 0.0.0.0 UG 10 0 0 eth1 0.0.0.0 10.221.139.1 0.0.0.0 UG 12 0 0 usb0 10.221.139.0 0.0.0.0 255.255.255.0 U 12 0 0 usb0 192.168.0.0 0.0.0.0 255.255.255.0 U 10 0 0 eth1 192.168.2.0 0.0.0.0 255.255.255.0 U 0 0 0 gre4-gresbksg 192.168.3.0 0.0.0.0 255.255.255.0 U 0 0 0 br-lan 192.168.5.0 0.0.0.0 255.255.255.0 U 0 0 0 gre4-gresbksg
此时本机可以直接 ping 通对端的 lan 口,
1 2 3 4 5 6 7 8 9 10 root@pg-2049671F7524:~# ping 192.168.2.218 PING 192.168.2.218 (192.168.2.218): 56 data bytes 64 bytes from 192.168.2.218: seq=0 ttl=64 time=1.481 ms 64 bytes from 192.168.2.218: seq=1 ttl=64 time=4.312 ms 64 bytes from 192.168.2.218: seq=2 ttl=64 time=2.344 ms 64 bytes from 192.168.2.218: seq=3 ttl=64 time=1.579 ms 64 bytes from 192.168.2.218: seq=4 ttl=64 time=3.409 ms --- 192.168.2.218 ping statistics --- 5 packets transmitted, 5 packets received, 0% packet loss round-trip min/avg/max = 1.481/2.625/4.312 ms
路由追踪:
1 2 3 4 root@pg-2049671F7524:~# traceroute 192.168.2.218 traceroute to 192.168.2.218 (192.168.2.218), 30 hops max, 38 byte packets 1 192.168.2.218 (192.168.2.218) 1.173 ms 2.722 ms 2.603 ms
拔掉 wan 口网线后,此时正常情况下应该无法 ping 通,因为 GRE 在内网中,路由表:
1 2 3 4 5 6 7 8 9 root@pg-2049671F7524:~# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 10.77.102.1 0.0.0.0 UG 12 0 0 usb0 10.77.102.0 0.0.0.0 255.255.255.0 U 12 0 0 usb0 192.168.0.242 10.77.102.1 255.255.255.255 UGH 12 0 0 usb0 192.168.2.0 0.0.0.0 255.255.255.0 U 0 0 0 gre4-gresbksg 192.168.3.0 0.0.0.0 255.255.255.0 U 0 0 0 br-lan 192.168.5.0 0.0.0.0 255.255.255.0 U 0 0 0 gre4-gresbksg
将网线接回去后,还是无法 ping 通,此时的路由表:
1 2 3 4 5 6 7 8 9 10 11 root@pg-2049671F7524:~# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.0.1 0.0.0.0 UG 10 0 0 eth1 0.0.0.0 10.77.102.1 0.0.0.0 UG 12 0 0 usb0 10.77.102.0 0.0.0.0 255.255.255.0 U 12 0 0 usb0 192.168.0.0 0.0.0.0 255.255.255.0 U 10 0 0 eth1 192.168.0.242 10.77.102.1 255.255.255.255 UGH 12 0 0 usb0 192.168.2.0 0.0.0.0 255.255.255.0 U 0 0 0 gre4-gresbksg 192.168.3.0 0.0.0.0 255.255.255.0 U 0 0 0 br-lan 192.168.5.0 0.0.0.0 255.255.255.0 U 0 0 0 gre4-gresbksg
可以发现,我们在拔掉网线后,路由表内自动添加了一条 host 路由
1 192.168.0.242 10.77.102.1 255.255.255.255 UGH 12 0 0 usb0
但是由于我们的 GRE 对端是建立在内网中的,4G 网络肯定是无法访问的,所有这条 host 路由是错误的。
当我们将网线接回后,由于已经存在了一条 host 路由,所有访问 192.168.0.242 的流量会从 usb0 出去,导致 GRE 隧道无法建立。
怎么使系统正确添加 host 路由? 思路一:关闭自动添加 host 路由 查看 OpenWrt 源码中的 gre 脚本(openwrt/package/network/config/gre/files/gre.sh)
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 gre_setup () { local cfg="$1 " local mode="$2 " local remoteip local ipaddr peeraddr peerlocalip peerlocalmask json_get_vars df ipaddr peeraddr tunlink nohostroute peerlocalip peerlocalmask [ -z "$peeraddr " ] && { proto_notify_error "$cfg " "MISSING_PEER_ADDRESS" proto_block_restart "$cfg " exit } remoteip=$(resolveip -t 10 -4 "$peeraddr " ) if [ -z "$remoteip " ]; then proto_notify_error "$cfg " "PEER_RESOLVE_FAIL" exit fi for ip in $remoteip ; do peeraddr=$ip break done if [ "${nohostroute} " != "1" ]; then ( proto_add_host_dependency "$cfg " "$peeraddr " "$tunlink " ) fi [ -z "$ipaddr " ] && { local wanif="$tunlink " if [ -z $wanif ] && ! network_find_wan wanif; then proto_notify_error "$cfg " "NO_WAN_LINK" exit fi if ! network_get_ipaddr ipaddr "$wanif " ; then proto_notify_error "$cfg " "NO_WAN_LINK" exit fi } [ -z "$df " ] && df ="1" case "$mode " in gretapip) gre_generic_setup $cfg $mode $ipaddr $peeraddr "gre4t-$cfg " route add -net "$peerlocalip " netmask "$peerlocalmask " dev "gre4t-$cfg " ;; *) gre_generic_setup $cfg $mode $ipaddr $peeraddr "gre4-$cfg " route add -net "$peerlocalip " netmask "$peerlocalmask " dev "gre4-$cfg " ;; esac }
可以看到,只有在 nohostroute 为非 1 时,才会添加 host 路由
以下是 ChatGPT 的回答:
proto_add_host_dependency
是 OpenWrt 中的一个函数,它用于向当前接口的依赖列表中添加一个 host 依赖项。
在 OpenWrt 中,每个接口都有一个依赖列表,用于记录这个接口所依赖的其他接口或主机。当这个接口启动时,系统会检查它的依赖列表,如果依赖项中有任何一个接口或主机不可达,这个接口就无法启动。这种机制可以保证网络的连通性和稳定性。
proto_add_host_dependency
函数用于向当前接口的依赖列表中添加一个 host 依赖项。它接受三个参数:当前接口的配置名称、依赖主机的 IP 地址、依赖主机所在的网络接口名称。例如,以下命令将当前接口eth0
添加一个 host 依赖项,依赖主机的 IP 地址是192.168.1.1
,所在的网络接口名称是eth1
:
1 proto_add_host_dependency "eth0" "192.168.1.1" "eth1"
在 GRE 隧道的配置中,proto_add_host_dependency
函数通常用于将对端 IP 地址添加到当前隧道的依赖列表中。这样,当对端不可达时,当前隧道就无法启动,从而避免了无效的隧道连接。
在你的问题中,proto_add_host_dependency
函数用于向当前 GRE 隧道的依赖列表中添加对端 IP 地址的 host 依赖项。如果nohostroute
字段没有设置为1
,系统会自动添加这个 host 路由,使得对端 IP 地址可达。如果nohostroute
字段设置为1
,则需要手动使用proto_add_host_dependency
函数将对端 IP 地址添加到依赖列表中,否则 GRE 隧道无法启动。
OpenWrt 官方是这样解释 这个参数的:
Name Type Required Default Description nohostroute boolean no 0 Do not add routes to ensure the tunnel endpoints are routed via non-tunnel device
于是我尝试了更改 GRE 的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 config interface 'gresbksg' option disabled '0' option peeraddr '192.168.0.242' option proto 'gre' option mtu '1280' option peerlocalip '192.168.2.0' option peerlocalmask '255.255.255.0' option zone 'wan' option nohostroute '1' config interface 'gresbksg_grestatic' option ifname '@gresbksg' option disabled '0' option ipaddr '192.168.5.1' option netmask '255.255.255.0' option proto 'static' option zone 'wan'
修改完成后重启 network,发现是可以正常使用的。此时的路由表:
1 2 3 4 5 6 7 8 9 10 root@pg-2049671F7524:~# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.0.1 0.0.0.0 UG 10 0 0 eth1 0.0.0.0 10.162.134.1 0.0.0.0 UG 12 0 0 usb0 10.162.134.0 0.0.0.0 255.255.255.0 U 12 0 0 usb0 192.168.0.0 0.0.0.0 255.255.255.0 U 10 0 0 eth1 192.168.2.0 0.0.0.0 255.255.255.0 U 0 0 0 gre4-gresbksg 192.168.3.0 0.0.0.0 255.255.255.0 U 0 0 0 br-lan 192.168.5.0 0.0.0.0 255.255.255.0 U 0 0 0 gre4-gresbksg
将网线拔掉后,路由表已经不会自动添加 host 路由了:
1 2 3 4 5 6 7 8 root@pg-2049671F7524:~# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 10.162.134.1 0.0.0.0 UG 12 0 0 usb0 10.162.134.0 0.0.0.0 255.255.255.0 U 12 0 0 usb0 192.168.2.0 0.0.0.0 255.255.255.0 U 0 0 0 gre4-gresbksg 192.168.3.0 0.0.0.0 255.255.255.0 U 0 0 0 br-lan 192.168.5.0 0.0.0.0 255.255.255.0 U 0 0 0 gre4-gresbksg
然后再接回网线,查看路由表:
1 2 3 4 5 6 7 8 9 10 root@pg-2049671F7524:~# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.0.1 0.0.0.0 UG 10 0 0 eth1 0.0.0.0 10.162.134.1 0.0.0.0 UG 12 0 0 usb0 10.162.134.0 0.0.0.0 255.255.255.0 U 12 0 0 usb0 192.168.0.0 0.0.0.0 255.255.255.0 U 10 0 0 eth1 192.168.2.0 0.0.0.0 255.255.255.0 U 0 0 0 gre4-gresbksg 192.168.3.0 0.0.0.0 255.255.255.0 U 0 0 0 br-lan 192.168.5.0 0.0.0.0 255.255.255.0 U 0 0 0 gre4-gresbksg
可以发现因为没有错误的 host 路由,我们的 GRE 隧道又能正常建立连接了。尝试 ping 一下对端的 LAN 口:
1 2 3 4 5 6 7 8 9 root@pg-2049671F7524:~# ping 192.168.2.218 PING 192.168.2.218 (192.168.2.218): 56 data bytes 64 bytes from 192.168.2.218: seq=0 ttl=64 time=1.810 ms 64 bytes from 192.168.2.218: seq=1 ttl=64 time=3.701 ms 64 bytes from 192.168.2.218: seq=2 ttl=64 time=6.526 ms 64 bytes from 192.168.2.218: seq=3 ttl=64 time=4.254 ms --- 192.168.2.218 ping statistics --- 4 packets transmitted, 4 packets received, 0% packet loss round-trip min/avg/max = 1.810/4.072/6.526 ms
疑惑:为什么用有线时,不会添加 host 路由?
思路二:将 GRE 隧道绑定到某个接口上 (未成功) 可以使用参数tunlink
Name Type Required Default Description tunlink string no (none) Bind the tunnel to the specified interface, OpenWrt 21.02+
修改 network 中 gre 的配置,将 GRE 隧道绑定到本地网络接口 eth1 上: