前言

家里的设备很多,我不希望在每个设备上开代理软件,实际上有些工具也不支持那么多客户端。
因此必须使用网关来进行代理,目前主流的网关方案就是透明代理。

思考

我需要实现的效果是:

  • 在主路由提供网络访问服务,包括拨号上网、DHCP、DNS 等。
  • 在旁路有提供特殊网络服务,比如科学上网。
  • 主路由监控旁路状态进行线路切换,将需要代理的流量转发到旁路处理。
  • 旁路由宕机后,主路由继续工作,主路由提供网络服务。

方案

我偏爱使用 MikroTik 路由设备,因此主路由使用 MikroTik 路由器,旁路使用 OpenWrt。以下简称 ROS 和 OP。
在 RouterOS 中实现上述需求,可以通过策略路由来解决。具体角色分配如下。

  • 旁路由开启 透明代理 ,提供无污染 DNS 服务,开启地址伪装(这将允许其他网络设备通过旁路由进行连接)。
  • 主路由配置 DNS 服务器,将 DNS 请求转发给 OP,并提供缓存。
  • 主路由配置策略表,将需要代理的流量转发给 OP,其他流量直连。
  • 主路由配置监控脚本,监测 OP 代理状态,当代理状态异常时,禁用策略路由,切换DNS,清空DNS缓存;当代理状态恢复正常,恢复策略路由,切换DNS。

如果路由决策命中需要代理,返回 DNS 解析结果,然后把对应 IP 路由通告给主路由。
如果决策为直连,则不做任何操作直接返回 DNS 解析结果,这种情况下仿佛旁路不存在。

旨在解决 ROS+OpenWrt 模式下,子网内设备使用 OP 作为默认网关,一旦 OP 故障,导致断网问题。

通过 ROS 的分流策略,即使 OP 发生故障,也只影响原本就访问不了的留学网站。

思路

OP 配置

  • 使用 Nikki 运行 Mihomo 内核提供透明代理服务,由于在 ROS 中进行了策略路由,可以选择全局模式,也可以选择规则模式,对 ROS
    泄露的路由进行二次过滤。
  • 在 OP 防火墙转发区域,或者直接在接口上配置 NAT MASQUERADE 地址伪装,这样可以让 ROS 识别经过 OP 转发的流量,避免造成路由环路。
  • [进阶] 在 OP 上运行 BGP/OSPF 动态路由协议,将需要转发的路由通告给 ROS。

ROS 配置

ROS 配置策略路由有两种方法,一种是使用 /routing rule 进行路由选择,配置比较简单;另一种是 使用/ip firewall mangle
对数据包标记路由,有更多的参数进行选择,配置更加灵活。因此选择第二种方式。

  • 创建路由表 passwall,添加一条路由规则,目的地址 0.0.0.0/0,将所有数据包路由到 OP,具体的路由规则根据后续的路由标记进行匹配。
  • 标记所有访问 ROS 本地地址的数据包,执行 accept 操作,允许数据包通过,不进行进一步的过滤。避免造成 ROS 无法访问。
  • 标记所有来自 OP 转发的数据包,根据 src-address 进行匹配,执行 accept 操作,允许数据包通过,不进行进一步的过滤。
  • 添加地址列表,将不需要转发的地址加入列表,如私有地址、大陆网段等,执行 accept 操作,允许数据包通过,不进行进一步的过滤。
  • 对所有未标记的数据包执行 mark-routing 操作,将数据包标记为 passwall,通过 passwall 路由表进行转发。
  • 添加 up/down 脚本,通过 netwatch 监控 OP 代理状态,执行切换 DNS、禁用策略路由操作。

实战

ROS 配置 (ROS v7)

1. 添加地址列表

首先将私有地址加入列表 LOCAL,这些地址不需要被代理,如果有其他不需要代理的地址,也可以添加到列表中。

/ip firewall address-list
add address=10.0.0.0/8 list=LOCAL
add address=172.16.0.0/12 list=LOCAL
add address=192.168.0.0/16 list=LOCAL
add address=100.64.0.0/10 list=LOCAL

如果有希望强制代理的 IP 地址,可以再创建一个地址列表,如 GFW,将希望被代理的 IP 地址加入列表中。

/ip firewall address-list
add address=203.208.40.0/24 list=GFW
add address=203.208.41.0/24 list=GFW
add address=203.208.43.0/24 list=GFW
add address=203.208.50.0/24 list=GFW
Tips: ROS 地址列表也可以添加域名,ROS 将自动将域名解析为 IP 地址,并添加到列表中。
/ip firewall address-list 地址列表不支持域名通配符,所以必须填写完整的域名,包含二级域名,三级域名等。
可以结合 /ip dns static 正则、子域名匹配等高级功能,将解析结果添加到地址列表中。

2. 导入地址列表

添加一个系统脚本,名称为 update_cnip,编辑内容如下:

/system/script
add comment="update cnip" dont-require-permissions=no name=update_cnip owner=\
    admin policy=read,write
edit update_cnip source 

在脚本编辑器中添加以下内容,编辑完成后按 Ctrl + O 保存并退出。

脚本将会从指定 URL 下载一个包含中国 IP 地址列表的脚本,并导入到 CN 列表中。
ROS 内置存储如果较小,可以将文件保存到 USB 存储设备中,并且导入完成后即可删除。

:local url https://www.iwik.org/ipcountry/mikrotik/CN

:local status ([/tool fetch url=$url dst-path=usb1-part1/cnip.rsc  as-value]->"status")

:if condition=($status="finished") do={
    /import file-name=/usb1-part1/cnip.rsc
    /file remove [find name=usb1-part1/cnip.rsc]
    :log info ("update cnip done")
}

执行脚本,由于记录比较多,可能需要一定时间,待成功导入后,查看相应的地址列表验证结果,应该能看到记录有大概 7500 条左右。

/system script run update_cnip

/ip firewall address-list print count-only where list=CN
Tips: 可以通过系统定时任务 system/scheduler 自动执行脚本更新地址列表。

3. 配置策略路由

# 添加路由表
/routing table
add comment=passwall fib name=passwall

# 路由标记,必须按照顺序添加
/ip firewall mangle
add action=accept chain=prerouting comment=passwall dst-address-type=local \
    in-interface=bridge
add action=accept chain=prerouting comment=passwall in-interface=bridge \
    src-address=192.168.88.2
add action=accept chain=prerouting comment=passwall dst-address-list=LOCAL\
    in-interface=bridge
add action=mark-routing chain=prerouting comment=passwall dst-address-list=GFW\
    in-interface=bridge new-routing-mark=passwall passthrough=no \
    protocol=tcp src-address=192.168.88.0/24
add action=mark-routing chain=prerouting comment=passwall dst-address-list=GFW\
    in-interface=bridge new-routing-mark=passwall passthrough=no \
    protocol=udp src-address=192.168.88.0/24
add action=accept chain=prerouting comment=passwall dst-address-list=CN \
    in-interface=bridge
add action=accept chain=prerouting comment=passwall dst-address-list=CN \
    in-interface=bridge
add action=mark-routing chain=prerouting comment=passwall dst-address-type=\
    !local in-interface=bridge new-routing-mark=passwall passthrough=no \
    protocol=tcp src-address=192.168.88.0/24
add action=mark-routing chain=prerouting comment=passwall dst-address-type=\
    !local in-interface=bridge new-routing-mark=passwall passthrough=no \
    protocol=udp src-address=192.168.88.0/24

4. 自动切换代理

使用 /tool netwatch 监控代理状态,执行相应的脚本切换代理。

添加两个系统脚本,名称分别为 enable_passwall 和 disable_passwall,编辑内容如下:

/system script
add comment="enable passwall" dont-require-permissions=yes name=\
    enable_passwall owner=admin policy=read,write
edit enable_passwall source

add comment="disable passwall" dont-require-permissions=yes name=\
    disable_passwall owner=admin policy=read,write
edit disable_passwall source

在脚本编辑器中添加以下内容,编辑完成后按 Ctrl + O 保存并退出。

enable_passwall:

enable [/ip firewall mangle find comment="passwall"]

/ip dns set servers=192.168.88.2
/ip dns cache/flush 

disable_passwall:

disable [/ip firewall mangle find comment="passwall"]

/ip dns set servers=218.2.2.2,218.4.4.4
/ip dns cache flush

添加 netwatch 监控,通过 HTTP 访问 192.168.88.2 端口 8081,如果成功,则执行 enable_passwall 脚本,否则执行 disable_passwall
脚本。选择合适的 interval(执行间隔)可以满足切换及时性需求。

/tool netwatch
add comment="passwall healthcheck" host=192.168.88.2 interval=15s name=\
    passwall_healthcheck port=8081 timeout=5s type=http-get \
    up-script=enable_passwall down-script=disable_passwall
Tips:8081 端口其实是通过 uHTTPd 服务提供的,内部通过访问 https://www.gstatic.com/generate_204 确认代理是否正常。
如果正常,则返回 HTTP 200 状态码,否则返回 503 状态码。
详细配置可以在我的另一篇博客查看: RouterOS 监测 OpenWrt 状态并进行线路切换

OP 配置 (OpenWrt 24.10)

1. 安装 Nikki 透明代理

Github 地址:OpenWrt-nikki

我喜欢官方 OpenWrt 发行版。

添加 feed 源:

# 只需要运行一次
wget -O - https://github.com/nikkinikki-org/OpenWrt-nikki/raw/refs/heads/main/feed.sh | ash

安装 nikki 和相关依赖包,根据不同系统来选择安装方式。

# 可以通过 shell 或者 LuCI 菜单中 `软件包` 进行安装
# 使用 opkg 包管理器
opkg install nikki
opkg install luci-app-nikki
opkg install luci-i18n-nikki-zh-cn
# 使用 apk 包管理器
apk add nikki
apk add luci-app-nikki
apk add luci-i18n-nikki-zh-cn

2. 防火墙配置

可以在 LuCI 菜单中 网络 -> 防火墙 -> NAT规则 添加防火墙规则,或者通过 uci 命令配置。

uci add firewall nat
uci set firewall.@nat[-1].name='PASSWALL'
uci add_list firewall.@nat[-1].proto='all'
uci set firewall.@nat[-1].src='*'
uci set firewall.@nat[-1].target='MASQUERADE'
uci set firewall.@nat[-1].device='br-lan'
uci commit firewall

存在问题

  • 地址表过大问题:整个配置中,对设备要求比较高的在于大量的地址列表,对于 16MB ROM 的设备,可能无法导入全部地址列表,
    一旦将ROM 写满,将会导致无法对 ROS 进行任何配置。只能通过 NetInstall 重新刷入 ROM 来恢复。 此外大量脚本写入对 ROM 的损伤很大,
    hAP ac2 这类 16MB 的存储,反复写几次可能导致 FLASH 报废,设备就无法正常使用。
    因此尽量将文件写入到 USB 存储设备中,并且不要频繁更新脚本,避免 ROM 被损坏。
  • 路由标记问题:Mangle 和 FastTrack、FastPath 存在冲突,必须禁用 FastTrack、FastPath,否则路由标记可能无法生效。
    对于 X86、CHR 这些没有硬件交换芯片的设备没有太大的影响,但是对于 RouterBoard 这些使用较弱 ARM 芯片的设备,可能会有一定的性能负担。
    最好使用 CPU 强劲的产品,如 RB5009UG+S+IN

对于性能较弱的设备,可以不导入几千条 CN IP 列表,而是将所有流量全部转发给 OP 进行精细过滤处理,ROS 同时停用 Mangle,通过 /routing rule 配置基础路由规则,例如私有地址直连、防环路规则,FastTrack 也能生效,提高吞吐量。

后记

IPv6 代理

IPv6 目前在我的网络系统中仅用于远程访问 NAS 等本地服务。为了防止 IPv6 流量绕过代理,我在 DNS 层面过滤了所有 AAAA 记录的解析结果,从而强制设备使用 IPv4 连接。

如果需要实现 IPv6 透明代理,可利用 IPv6 无状态地址自动配置(SLAAC)机制,通过配置路由器广告(RA)优先级来控制设备的默认网关选择。
与 IPv4 策略路由方式不同,IPv6 支持基于 RA 优先级的网关自动发现和切换,无需复杂的防火墙规则。
具体配置为:ROS 作为上游路由器负责 PPPoE 拨号并获取 IPv6 前缀,将其 RA 优先级设为较低值; OP 作为透明代理网关,通过中继 ROS 的 IPv6 前缀并设置较高的 RA
优先级。如此,终端设备将优先选择 OP 作为 IPv6 默认网关,IPv6 流量会首先经过 OP 进行代理处理,然后再转发至 ROS 上联网络,从而实现完整的
IPv6 透明代理。

动态路由协议

从性能优化角度考虑,基于 Mangle 的策略路由机制确实存在一定的资源消耗问题。由于每个数据包都需要经过完整的防火墙规则匹配和路由标记流程,
这一过程会增加 CPU 负载,特别是在高流量环境下。相比之下,采用 BGP(边界网关协议)等标准动态路由协议能够更高效地处理大规模路由表。
BGP 作为专为处理复杂路由拓扑而设计的距离矢量协议,其路由计算和维护机制更适合海量路由条目的场景。虽然 OSPF 协议在局域网环境中表现优异,
但其链路状态算法在面对大量外部路由条目时会产生较高的内存占用和 CPU 计算开销,因此在此应用场景下并不适合作为主要路由分发手段。

最后修改:2026 年 02 月 03 日
如果觉得我的文章对你有用,请随意赞赏