本文是继《使用 RouterOS,OSPF 和树莓派为国内外 IP 智能分流》后的第二篇文章。第一篇着重讲的是路由协议的配置,而这一篇着重讲这种配置情况下的失效模式 (failure mode) 和应对策略。

失效分析

按照上一篇文章配置好了分流路由以后,正常情况下国内外流量是会分开走了,但是我们没有考虑所有的实效情况。比如树莓派故障,或者隧道故障。这里重温一下上文使用的拓扑:

这里要讲到一点上一篇文章没有提到的地方,那就是 DNS 解析的问题,一看来说为了加速我们需要在树莓派上配置一个走隧道的 DNS 服务,而 DNS 服务也成为了一个网络的单点,所以也要一并考虑在内。

总结下来,可能的失效情况有这么几种(排除光猫故障,RouterOS 故障这类概率很低的事件):

  1. 树莓派宕机,比如掉电
  2. 树莓派正常运行,但是隧道故障

我们需要确保在这两种情况下:

  1. 所有的 IP 应该直接走运营商线路,而不会再去树莓派
  2. DNS 解析的上游从树莓派切换到运营商

下面我们就来具体分体如何实现这些目标。

树莓派宕机检测

为了保持树莓派宕机的情况下 DNS 可用,局域网内的客户通过 DHCP 获得的 DNS 服务器地址不可以是树莓派的 IP 地址。所以我们使用 RouterOS 内置的 DNS 服务器并且讲 RouterOS 的 IP 地址通过 DHCP 分发给客户端作为 DNS 的唯一选择。然后在树莓派宕机的时候自动把上游切换到运营商的递归 DNS。

这里要注意的是我们不可以使用运营商推送过来的地址,对于 PPPoE 接口和 DHCPv6 的实例都要设置 use-peer-dns=no,这样设置完了后 /ip dns print 显示 dynamic-servers: 应该为空。

接下来我们创建两个 RouterOS script,用来切换 DNS 的上游:

/system script
add dont-require-permissions=yes name=use-rpi-dns source=\
"/ip dns set servers=192.168.0.2\
\n/ip dns cache flush"
add dont-require-permissions=yes name=use-ct-dns source="/\
ip dns set servers=240e:xx:xxxx::xxxx,240e:xx:xxxx::xxxx\
\n/ip dns cache flush"

注意这里的第一个脚本, 192.168.0.2 应该替换成树莓派的 IP 地址。而第二个脚本里应该替换成运营商的递归 DNS 地址。如果有多个可以用逗号分隔。

这里的 dont-require-permissions=yes 是必须的,否则 Netwatch 执行的时候会报权限错误。

下一步配置 Netwatch 来监控树莓派的网络联通性,如果宕机就自动执行脚本切换 DNS。

/tool netwatch
add down-script=use-ct-dns host=192.168.0.2 interval=30s up-script=\
    use-rpi-dns

这样 Netwatch 工具会每隔 30s ping 树莓派,如果宕机就会切换到运营商 DNS,树莓派正常后会自动切回。

至于树莓派广播过来的国外路由,因为是走的 OSPF,如果宕机 RouterOS 会自动检测到并把这些路由删除,所以不需要特殊处理。

隧道故障检测

当隧道故障,但是树莓派还在正常工作的时候,Netwatch 无法判断和自动切换 DNS 上游。所以我们需要在树莓派上切换 DNS 的上游,这样虽然 DNS 请求还是发往树莓派,但是其实走的也是运营商的递归。

这里我写了一个简单的 shell 脚本,可以达到自动切换的效果,请根据需要和自己的 DNS 设置来修改:

#!/bin/bash

if ping -c 5 -W 1 -I wg0 2001:4860:4860::8888 > /dev/null; then
    # tunnel working
    if ! grep -q 2001:4860:4860::8888 /etc/dnsmasq.conf; then
	echo "Tuneel back up, switching DNS to Google and enabling OSPF..."
        sed -i 's/240e:xx:xxxx::xxxx/2001:4860:4860::8888/g' /etc/dnsmasq.conf
        sed -i 's/240e:xx:xxxx::xxxx/2001:4860:4860::8844/g' /etc/dnsmasq.conf
        systemctl restart dnsmasq

        /usr/sbin/birdc enable ospf1
        /usr/sbin/birdc6 enable ospf1
    fi

else
    # tunnel down
    if grep -q 2001:4860:4860::8888 /etc/dnsmasq.conf; then
	echo "Tuneel down, switching DNS to CT and disabling OSPF..."
        sed -i 's/2001:4860:4860::8888/240e:xx:xxxx::xxxx/g' /etc/dnsmasq.conf
        sed -i 's/2001:4860:4860::8844/240e:xx:xxxx::xxxx/g' /etc/dnsmasq.conf
        systemctl restart dnsmasq

        /usr/sbin/birdc disable ospf1
        /usr/sbin/birdc6 disable ospf1
    fi
fi
c

注意这里我们同时在隧道不通的时候主动禁用 OSPF 实例。这个理论上不是必须的,因为 BIRD 会检查接口的联通性,如果接口状态为 DOWN 会自动的 withdraw 路由。但是考虑到有的隧道协议可能断了以后需要等几分钟才会显示为 DOWN 状态,我们这里顺便通过 ping 来达到快速检测,快速 withdraw 路由的效果。

给脚本加上执行权后,用 root 权限创建一个 crontab 条目,一分钟运行一次即可。

* * * * * /home/pi/check_tunnel.sh

验证

拔掉树莓派的网线,或者在 RouterOS 上禁用树莓派对应的网络接口,网络应该会保持畅通,而且局域网客户 DNS 解析正常。

在树莓派上人工停掉隧道接口,观察 1 分钟内 dnsmasq.conf 里的服务器地址变为运营商递归 DNS,并且 sudo systemctl status dnsmasq 看到 dnsmasq 进程被重启,而且局域网客户 DNS 解析正常。