最早听到旁路由这个词是在 2020 年折腾 N1 的时候,这台单网口的小盒子只能用网上所说的旁路由方案接入局域网来实现期望的功能。现在回想起来,旁路由这个词有可能就是在那个发烧友大量折腾斐讯 N1/P1/T1 的时期被发明出来的。你没办法在发烧友圈子外的互联网及各种学术材料中找到对旁路由的描述和定义,当然也找不到合适的英文翻译(导致这篇文章的 slug 定义困难);从拓扑上看,旁路由更像是杂糅了二级路由和透明网关的概念,除了实体确实多接了根网线放在主路由旁边,其本身并没有真正地开启一条旁路,做的事情也基本可以和网关的定义对齐。

不过,由于这个说法主要集中于网友的交流讨论之中,而且几年来在有相关需求的广大用户中被广为接受,所以只要这个词不变为一个学术概念,那我倒也不觉得有什么不妥。下文也仍然会用「旁路由」一词来指代在此类拓扑结构中担任代理网关角色的路由器。

此外,本文对于原理的解释偏多且为新手向,对于已经熟知相关概念的读者则可以将本文作为 cheat sheet 食用。

旁路由的适用场景

有人认为旁路由的加入(手动指定方式)不会在其出现问题时影响整个网络的可用性,所以应该把非路由功能都交给旁路由来实现。但实际上如果仅仅是为了达成这个目标,那在主路由上直接分流会是个更好的方案,因为这样不仅会大量减少疑难杂症的出现,而且还可以通过设置 fallback 的方式进一步提升网络的可用性。

所以我认为旁路由实际上只适用于一个场景,那就是出于某种考虑,主路由不能被替换或被大量修改,而主路由的固件又不能满足功能诉求,这时候旁路由便是一个可选的解决方案。

旁路由的原理

网上关于旁路由的配置教程多如牛毛,其中大部分是基于 F 大的 N1 OpenWrt 固件使用教程来写的,也有很多是结合了自己的踩坑经验的细化版本。由于版本太多、完整的说明太少,而且大部分没有讲清楚教程的环境上下文和原理,导致按照这些教程来配置的用户往往不得不一遍遍地折腾重试,甚至会遇到逐步配置最终却断网、访问不了部分网站等令人头疼的问题。

授之以鱼不如授之以渔,为了能彻底讲清楚旁路由该如何配置,我们暂且不谈具体步骤,而是先搞明白旁路由的运作原理。

名词解释

由于计算机网络的术语在不同时期、不同环境下,对于细节的含义其实有比较大的差异,故首先我们先定义好下文中将使用到的各类专有名词的含义,以免出现信息不对称的情况。同时为了方便理解,我们也尽量和 OpenWrt 的名词对齐,并尝试与现实生活场景进行类比。

  1. 路由:将数据从源地址传输到目的地址的行为。可以看出,这个行为抽象涵盖了两个动作,一是找到地址、二是转发数据。可以类比为网购时快递公司将商品从卖家发送给买家的过程。
  2. 路由表:可以简单理解为路由过程中的地址关联信息的合集,也就是现实中发货地址和收货地址的映射关系。
  3. 路由器:本文中指家用的、具备路由功能的实体网络设备,其本身并不一定需要真的承担或只承担路由功能,可以抽象理解为就是一台普通的电脑或服务器。当然,它也可以是一个容器实例、一个虚拟机。对应到现实中就是快递公司。
  4. 接口:这里特指网络接口,指两个网络设备或协议层的连接点。现实中连接路由器时,wan/lan 不同的网线插槽其实就可以理解为接口。只不过接口并不一定是物理实体,所以我们才会在 OpenWrt 的接口设置里看到甚至新增许多物理设备上所没有的接口。
  5. 网关:这里特指网络中的网关,负责执行数据转发的某个抽象设备。这里可能容易与路由器的功能混淆,毕竟路由器如果用来做路由似乎也是在做转发的工作。实际上这是由于术语的历史使用缺少规范导致的边界不够清晰,可以粗略理解为承担路由功能的路由器就是网关,但网关不一定是只能由路由器担任。
  6. IP:本身指 IP 协议,本文为方便也可能将其作为 IP 地址的简称,且默认为 IPv4 。本节所有的名词其实都是基于 IP 协议运作的,而 IP 地址即上文各类「地址」的实际值。可以理解为你用于发送和接收快递的门牌号。
  7. DHCP:IP 地址的管理和分配协议,本文中不单单指协议本身,还指负责执行该协议的设备。可以理解为给你分配门牌号的物业,只不过这个门牌号是动态变化的。
  8. DNS:上文说到数据的传输需要源地址和目的地址,而这个地址就是 IP 地址。但由于 IP 地址难以记忆,所以才会有了可以作为 IP 地址别名的域名,而 DNS 就是负责进行域名和 IP 地址映射转换的系统。本文中 DNS 也指代负责运行该系统的设备。
  9. NAT:出于各种考虑,局域网与因特网的 IP 地址是隔离的,NAT 可以理解为内外网 IP 地址转换的流程。类比网购,相当于快递公司把快递送到附近快递站,快递站的快递员再把货物送到你家门口的流程。
  10. SNAT:NAT 的一种,本质上是在修改网络包的源地址,目的是可以强制网络包返回时经过期望的地址。可以理解为支付宝的作用,即钱虽然表面上是点对点转账的,但经过支付宝后,支付宝就要求相关转账信息的回执必须由它中转一次再告知转账发起人。

网络拓扑

旁路由架构的网络拓扑图

可以看到,无论如何配置,我们都需要保证数据按图中的拓扑进行流转,才能实现我们所希望的只在旁路由增加功能而不修改主路由的目的。由于流量(至少上行流量)总会流经旁路由,所以旁路由实质上就是一层透明代理。

工作原理

那么我们该怎么实现这样的网络拓扑呢?让我们先来看下数据在网络的更底层是如何流转的。

旁路由架构的数据流转示意图

从图中可以看到,当我们从手机等终端设备发出一个数据包时,数据包总是由我们的终端设备经由网关路由至目的地址,目的地址返回数据时也是相同的路径。这是因为终端设备本身通常是不具备路由功能的,单单一个路由表终端设备就搞不定。

既然数据必然经过网关,那么我们只要强制把旁路由作为终端设备对外数据交互的第一层网关即可。至此旁路由的工作原理其实就已经解释清楚了,即在另一台路由器上实现的基于网关的透明代理。而网上各式各样的教程其实都是在教我们解决如何配置网关的问题。

在这一章节还需要说明的是为什么各个教程都要求我们把旁路由的 IP 配置在和主路由相同的 IP 网段。所谓的 IP 网段实际上就是子网,同一子网下的主机(设备)可以直接通信,跨子网则需要通过某种形式转换后才能通信,而这些转换虽然可行但比较复杂,在旁路由这个场景下显然是没有必要的。本着不改变原有网络拓扑的原则,旁路由自然也要配置在和主路由及其他设备相同的子网才行。

旁路由的配置

那么我们该如何配置网关以让数据按上文的工作原理进行流转呢?

(一)旁路由的网关设置

首先我们先来解决旁路由的网关问题。在上文的拓扑图中我们可以看到,旁路由虽然挂着路由器的名字,但它本质上也是网络链路中的一个节点,因此它也需要请求上层网关才能完成数据流转。而主路由是这个网络拓扑的出口,所以旁路由的网关自然要配置为主路由的 IP 地址。这一项配置是必须且不会随终端网关配置方式的变化而改变的,无论如何指定网关请求旁路由,旁路由本身都要依赖此配置才能完成正常的流量转发。

此外,还有子网掩码需要进行配置。前面有提到,在同一子网内的主机之间才能直接通信,而 IP 和子网掩码相组合便能确定设备当前所在的子网。旁路由并不改变网络拓扑,所以需要和主路由在同一子网内。因此将旁路由的子网掩码配置设置为主路由的子网掩码即可。同理,下文的所有子网掩码配置也均需要与主路由的保持一致。

(二)旁路由的DHCP配置

虽然配置了网关后数据流转图中的左半边已经成型,但如果不对旁路由的 DHCP 进行配置,实际上会导致各种各样的疑难杂症或直接无法联网。

原因在于 DHCP 使用了 UDP 协议,UDP 是没有连接的,如果主路由和旁路由同时开启 DHCP,则任意一个 DHCP 服务器都可能会应答终端的申请,进而导致 IP 下发和路由表的混乱造成各种无法连接的疑难杂症。当然,我们可以将两个 DHCP 的子网网段拆分开来解决共存问题,但这个行为在旁路由场景下并没有实际意义。

因此我们需要保证网络中只有一个设备承担 DHCP 功能,出于不改变原网络拓扑和避免无意义 NAT 的考虑,我们通常选择关闭旁路由的 DHCP 功能(对应到 OpenWrt 则选择「忽略此接口」)。

(三)终端网关配置

对于手机、电脑等终端,我们的目标是将其网关配置为旁路由的 IP 。实现方案很多,成本较低的主要为以下两种。

手动指定

顾名思义,只需要在终端设备的网络设置中将网关手动配置为旁路由即可。以 iOS 系统为例:

iOS 网络配置界面

手动填写一个网络上未被占用的 IP 地址,而子网掩码以主路由为准,网关则填写旁路由的 IP 地址。

手动指定的好处在于完全不影响原网络的使用,设备按需配置是否使用旁路由作为网关以实现特定功能。当旁路由故障时,未手动指定的设备仍能正常上网。

缺点在于操作烦琐。手机、电脑还好,但电视或根本没有屏幕的设备设置起来就会很麻烦。

依赖DHCP指定

DHCP 除了可以管理 IP 的分配,还会下发网关和 DNS 服务器信息,因此我们还可以借助 DHCP 的这一机制来为所有终端统一设置网关,而不再需要逐个手动修改。

前面提到,我们关闭了旁路由的 DHCP 功能,因此这个统一下发的工作就要交给主路由来完成。只需要在主路由的 DHCP 配置中将网关配置为旁路由的 IP 地址即可。

OpenWrt 的 DHCP 配置界面

以 OpenWrt 为例,将主路由 DHCP 下发的网关配置为旁路由 IP 地址即可(3 表示网关,6 表示 DNS 服务器地址)。

不过有些路由器的默认固件没有开放该配置项,对于这些设备,除非可以 SSH 连接后手动改配置,不然无法使用此种指定方式。

这种方式的好处显而易见,一次配置全家受用;缺点在于当旁路由出现故障时,所有连接的设备都会无法上网。

(四)DNS的配置

细心的读者可能发现了上文只提到了网关的配置,但未提到很多教程中的 DNS 配置。实际上单单就旁路由本身来说,网关配置完成后整个网络拓扑就已经搭建完毕了。但对于一些特定诉求,比如依赖旁路由进行统一的 DNS 劫持(很多功能的底层都依赖于此),则需要将对应位置的 DNS 也配置为旁路由的 IP 以将域名解析工作也完全交由旁路由处理。

配置步骤总结

手动指定方案

  1. 为旁路由配置和主路由同网段的静态 IP 地址,同时将旁路由的网关和 DNS 指向主路由,子网掩码与主路由保持一致。
  2. 旁路由关闭 DHCP 服务。
  3. 在主路由防火墙开启 SYN-flood 防御的情况下,关闭旁路由防火墙的 SYN-flood 防御(可选)。
  4. 在需要接入旁路由的终端设备中,将网关和 DNS 配置为旁路由 IP 地址,配置同网段的 IP 地址和与主路由相同的子网掩码。

DHCP下发方案

  1. 为旁路由配置和主路由同网段的静态 IP 地址,同时将旁路由的网关和 DNS 指向主路由,子网掩码与主路由保持一致。
  2. 旁路由关闭 DHCP 服务。
  3. 在主路由防火墙开启 SYN-flood 防御的情况下,关闭旁路由防火墙的 SYN-flood 防御(可选)。
  4. 在主路由的 DHCP 配置中,将其下发的网关和 DNS 配置为旁路由的 IP 地址。

疑难杂症的解决

虽然配置步骤看上去很简单,但很多人在实际使用中都会遇到逐步配置却上不了网或网络慢的问题,这里挑几个典型案例来解析。

(一)该不该设置iptables的MASQUERADE

这可能是争议最大的一条,有人说这条规则加上后影响性能而且没意义,但也有很多人表示不配这条就是连不上网(大多为连接不上国内网络)。

这条规则的作用本质上是在旁路由上做 SNAT,只不过修改的地址不需要指定而是动态获取旁路由对应接口网卡的 IP 地址,在 OpenWrt 里被称为「IP 伪装」。

配置 MASQUERADE 后的数据流转示意图

单从旁路由的网络拓扑来说,这条规则确实没意义,因为主路由作为对外出口必做 NAT,但旁路由本身就在局域网内且只是链路上的一环,没有必要再对内网 IP 进行耗费性能的 NAT 操作。

但不要忘了,理论和现实是两回事,物理网络拓扑中的不同设备、不同固件都有可能产生各种奇怪的兼容问题。我自己倒是没有遇到过该问题,但检索网友们的各种帖子,大概可以分为以下几种原因:

  1. 主路由固件数据包处理问题:部分路由器(似乎主要为国产品牌)的无线网在桥接和 iptables 处理过程中,当旁路由将国内流量重新转发回主路由时,主路由根据流中的首个数据包的状态做判断导致后续数据包未进行 NAT 就直接访问了互联网。这篇文章对此有比较详细的讲解(CSDN 也是有很多好文章的)。
  2. 主路由 NAT 硬件加速导致的问题:可能是由于硬件加速流程对数据包的处理出现了类似于原因 1 的问题导致无法正确完成数据交互。由于硬件加速本质上是在运行特殊驱动,而各家厂商的该驱动几乎都是闭源的,所以网上也没见到有探究深层原因的资料。这种情况下把硬件加速关闭即可(会一定程度牺牲主路由 NAT 性能)。
  3. IP 和 MAC 校验机制导致包被丢弃的推论:由旁路由的网络拓扑可知,配置网关后上行流量必然经过旁路由,但在旁路由不进行 NAT 时数据包中的 IP 仍然是终端设备的,所以理论上下行数据并不会经过旁路由而是由主路由直接转发至终端。显然,由于旁路由的存在,IP 地址和 MAC 地址在某个环节会有不匹配的情况,如果主路由对此有校验,那数据包就会被丢弃掉。但这也只是个推论没有证据佐证,同时由于网络可能的复杂性,主路由通常也不会主动做这种校验。

MASQUERADE 配置其实并没有定向地去解决上面这些具体问题,而是通过 NAT 来隐藏终端设备、只向主路由暴露旁路由 IP 的方式,一刀切地避免了上述原因导致的问题。但由于上下行流量都会经过旁路由,所有流量都会被二次 NAT 和二次转发,网络的吞吐会有不小的下降,直观感受就是下载速度变慢了。

所以比较理想的策略是,先不加 MASQUERADE 规则观察是否有问题(尤其是国内流量),如果确实有问题,在权衡可以接受性能的损失后再配上该规则。

(二)LAN和WAN是否需要绑定

具体操作是取消桥接,再设置 WAN 和 LAN 共用同一个网卡(如 eth0 )。这个操作其实和 MASQUERADE 规则的效果类似,因为绑定后经过 WAN 的流量必然会被 SNAT 。适用于确实遇到了疑难杂症且能接受性能损失场景下的备选方案。

(三)是否需要关闭旁路由桥接

在许多教程里,这个操作和添加 MASQUERADE 规则是配套的。但桥接与否实际上并不会影响整体的网络拓扑,这看上去又是一项没有意义的配置。我猜测可能是网上流传的添加 MASQUERADE 规则的方式是下面这条固定命令:

1
iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE

也就是要求 SNAT 时从 eth0 网卡动态获取 IP 。而在桥接模式下,MASQUERADE 时是需要从 br-lan (常见的桥接后的默认网卡名称,也可能是其他名称,要视实际情况而定)获取 IP 的,直接复制粘贴上面的命令会导致 IP 伪装失败。所以只要把 -o 参数的值改为 br-lan 即可:

1
iptables -t nat -I POSTROUTING -o br-lan -j MASQUERADE

但似乎确实有网友不取消桥接就无法联网,这种情况大概率是旁路由固件对桥接模式的处理有问题(多见于 ARM 架构的固件,这类固件通常要做很多的魔改适配),如果真的遇到这种情况的话则确实可以取消桥接尝试下,毕竟大多数情况下旁路由内部的桥接也没什么实际意义,去掉后由于少了流量判断流程可能还会有非常微小的性能提升。

至于 N1 这种自带无线功能但实际上没人用的设备,在关掉无线后,确实可以顺便取消掉没有意义的桥接。只是在 OpenWrt 取消桥接保存配置时,要确定选中了有线网卡对应的接口比如 LAN/eth0 ,不然如果后台的前端存在体验问题自动选择了 wlan ,那保存后路由器可就直接失联了。

(四)是否需要设置DHCP强制

如果我想通过 DHCP 下发网关但又不想改变主路由的原配置该怎么办呢?在 OpenWrt 的接口 DHCP 配置中有一个选项叫做「强制」,勾选后,此设备会忽略网络上已经存在的 DHCP 服务,强制启动本机的 DHCP 服务,所以似乎我们只需要在旁路由上配置此选项,并在旁路由 DHCP 中配置好网关,主路由不做任何变更,即可实现本段开头的诉求。

但上文提到同一子网中只能有一个用于分配指定网段的 DHCP 服务,因此旁路由强制开启 DHCP 后,还需要主路由具备主动判断网络情况停止提供 DHCP 服务的能力,这个流程才能真正运转起来。但并不是所有的路由器和固件都支持这个能力,目前来看 OpenWrt 作为主路由固件时可以正确检测并停用 DHCP ,其他固件则需要在使用时做下兼容性测试。从工程角度上讲,由于这样的操作过于依赖外部能力,属于和外部组件产生了强耦合,不利于未来维护,故不太推荐此种方案。

(五)旁路由的某些功能无法使用

各种组件内部实现大不相同,如果某个组件的代码对网络结构做了强限定(如上文所说的校验 IP 和 MAC 的匹配关系),那旁路由的加入可能就会打破组件预期的网络结构导致其无法运行。这种情况在组件本身不做适配时基本无解,只能尝试下开启 MASQUERADE 等配置,看看多加一层 NAT 后的网络拓扑是否能符合组件要求,但代价同样是会牺牲性能。

(六)是否应该关闭旁路由防火墙的SYN-flood防御

SYN-flood 是一种常见的攻击方式,SYN 指 TCP 建连三次握手中的第一步报文,flood 指大量发起该步骤的报文。由于 TCP 的实现原理要求服务端接收到 SYN 报文后回复客户端 SYN+ACK 报文表明请求被接受,并在一段时间内等待客户端回复最终的 ACK 报文,那么大量的 SYN 报文就会导致服务端出现大量等待最终资源耗尽挂掉,而攻击者并不需要真的完成建连,只要持续发送 ACK 包即可。

那路由器的防火墙又是如何作防御的呢?以 OpenWrt 为例,虽然 OpenWrt 的防火墙配置已经迁移到了 fw3 ,但翻看代码历史我们就会看到当时 OpenWrt 直接基于 iptables 的早期实现:

1
2
3
4
$IPTABLES -N syn_flood
$IPTABLES -A syn_flood -p tcp --syn -m limit --limit $rate/second --limit-burst $burst -j RETURN
$IPTABLES -A syn_flood -j DROP
$IPTABLES -A INPUT -p tcp --syn -j syn_flood

即借助 iptables 的 SYN 限流能力进行防御,同时此配置位于 default 配置中,会在 NAT 等具体网络操作之前执行。

对应到主路由,wan 口接收到攻击流量后便会进行限流,攻击者无法直接向后攻击到内网主机,那么同样为内网主机的旁路由自然理论上也就不会被攻击到。

但对于旁路由来说,理论上网络中的所有上行流量都会通过它来转发,那当流量超过防火墙的限流阈值时便会触发拦截,进而在终端上表现为网络时断时续。所以在主路由已开启 SYN-flood 防御的情况下,旁路由关闭该配置可以避免出现可能的网络不稳定问题。

旁路由与IPv6

IPv6 在家用网络中通常默认是没有 NAT 转换流程的,同时其动态地址配置方案比 IPv4 要复杂得多,比如 SLAAC 和之前的 DHCP 可以说完全不是一套机制,而 DHCPv6 又分有状态和无状态两类。而前面提到,我们实现旁路由网络拓扑的过程,其实就是在指定一个具备透明代理功能的网关的过程,但 SLAAC/DHCPv6 都没有提供网关下发能力,终端设备总是会以其所交互的主机作为网关,同时大多也不支持直接修改网关。此外,运营商不支持 DHCPv6-PD 、IPv6 子网限定范围等情况,都使得旁路由支持 IPv6 非常困难,在不同场景、不同网络下要面临不同的配置,甚至无方案可配置。

网上比较流行的旁路由 IPv6 实现是个曲线救国的折中方案,即先开启主路由的内网的 IPv6 地址分配进而让旁路由获得内网 IPv6 地址,随后再通过在旁路由开启一个 DHCPv6-Client 的方式获取到公网的 IPv6 地址,这样便可以将主路由的 DHCPv6 下发的 DNSv6 配置为旁路由的 IPv6 地址。此时除了 DNSv6 的解析是在旁路由进行,其他流程仍按原链路直连。而在需要分流的场景中,OpenWrt 的相关组件可以选择在解析域名时放过不需要处理的 IPv6 流量让其正常解析出 AAAA 记录,而对域名名单中的流量强制解析为 A 记录以继续走 IPv4 协议,从而实现和此前的类似的旁路由功能。

当然,这种解析实际上依赖于组件的能力。如果组件并不支持,那通过各种方式强制定义 IPv6 的路由表保证相关 IPv6 流量必然经过旁路由也是一种解决方案。不过无论哪种实现,由于不借助网关配置,其实都已经和本文的旁路由不相关了。

此外,还有种方案是通过 radvd 等支持配置路由单播和优先级的工具,用更高的优先级来指定终端的 IPv6 路由(可以简单理解为 IPv4 的网关),这样就替代了 IPv4 下手动配置或 DHCP 下发网关对应的功能,完美满足本文所说的旁路由网络拓扑,同时对 IPv6 动态地址配置方案的要求很低,但要求终端设备支持路由优先级配置。

总结

旁路由实际上是运行透明代理功能的网关,有人认为这个概念很民科,但我并不认同,毕竟它只是在通过已有的能力来解决特定场景的问题,和我们写代码、做产品没有本质区别,而「旁路由」这个名词也不过是个约定俗成的叫法而已,不应该被批判。

另一方面,由于旁路由在不同设备、不同网络环境有可能遇到很多奇怪问题,其实对于非专业用户来说付出的时间成本很有可能会远大于直接替换主路由的成本。但生命不息,折腾不止,如果是为了收获折腾的快乐、学习到新的知识,那又有何不可呢?