外观
校园网不让我多拨?那我就换一根网线
约 4815 字大约 16 分钟
OpenWrt双WAN校园网mwan3
—— 西南交大宿舍 OpenWrt 双 WAN 折腾记
起因:一根网线,和一个贪心的想法
宿舍的网速一直很佛系。测速跑不满,下载慢悠悠打游戏还会偶尔抽一下。
某天我盯着 LuCI 那条孤零零的 wan 状态看了很久,心想:我不是还有个室友的宽带账号吗?两条线,同时用。
这个想法在脑子里转了三天。
技术上它叫 PPPoE 多拨 + mwan3 负载均衡。说白了就是让路由器同时拨两条宽带,然后把流量像切蛋糕一样分给它们。配置听起来不复杂,但我隐约有种预感——校园网这种地方,不会让这种操作太顺。
事实证明,它比我想的还要麻烦。
一、硬件:两根线,和一根杆子
先解决最朴素的问题:第二条线从哪来?

路由器放在书桌角落,墙上网口在房间另一头。为了把第二条线从舍友那边拉过来并固定住,我用了一根晾衣杆当走线支架——不是什么高级方案,但它稳稳地把网线架在了半空,不占用地面,也避开了桌脚。
插一句翻车记录:中途我想把
wan挪到房间另一个墙口,结果插上去eth1直接carrier=0,PHY 层根本起不来 link——ethtool显示Link detected: no,强制 100M 全双工也救不回来。换回原来的墙口立刻正常。同样的墙口、同样的网线、我的 MacBook 接上去拨号却好好的,所以这事儿至今没找到根因,大概率是那个墙口或者中间某段布线的物理问题。先记下,占个坑。
第二条线接进路由器的 LAN1 口。物理层面准备完毕。
二、翻车第一弹:同口多拨,账号被"请喝茶"
我最初的想法很朴素:在 OpenWrt 里新建 wan2,device 也填 eth1,和 wan 共用同一个物理口,两个不同的账号。
uci set network.wan2='interface'
uci set network.wan2.device='eth1'
uci set network.wan2.proto='pppoe'
uci set network.wan2.username='SCXY19828928845' # 舍友的号ifup wan2。
网断了。
——开个玩笑,lan 是独立的,SSH 不会断。但 wan2 报错是实打实的:
"errors": [{ "subsystem": "pppoe", "code": "CONNECT_FAILED" }]一开始我以为是路由冲突(两条默认路由打架),于是老老实实给 wan2 加了隔离配置:
uci set network.wan2.metric='20'
uci set network.wan2.defaultroute='0' # 不抢主路由再 ifup。还是 CONNECT_FAILED。
好,那看日志。既然有 pppd,就有发言权:
pppd[25217]: Recv PPPOE Discovery V1T1 PADO session 0x0
pppd[25217]: Send PPPOE Discovery V1T1 PADR session 0x0
pppd[25217]: Recv PPPOE Discovery V1T1 PADS session 0x3e9b
pppd[25217]: Remote message: SUCCESS.
pppd[25217]: PAP authentication succeeded ← 认证居然成功了?
pppd[25217]: local IP address 10.169.45.234
pppd[25217]: primary DNS address 202.115.64.33看到 PAP authentication succeeded 的那一刻我是懵的。认证成功了,IP 也拿到了,DNS 都发下来了。 我甚至已经开始想怎么庆祝了。
然后,往下翻两行:
pppd[25217]: Recv PPPOE Discovery V1T1 PADT session 0x3e9b
pppd[25217]: Modem hangup
pppd[25217]: Connection terminated.……
PADT 是 PPPoE 的"主动终止会话"。翻译成人话就是:
校园网的 AC 认证通过了,然后反手把这个会话掐了。
AC 的名字我查到了,叫 X-XNJD-ME60-1。它的策略很明确:同一个物理端口,只允许一个 PPPoE 会话。 你可以认证成功、可以拿到地址,但只要检测到同一端口上出现第二条会话,2 秒后就把你踢下线。
这很有校园网的风格——门是敞开的,但只放你一个人进,而且会在你把行李搬进去之后反锁。
三、翻车第二弹:换 MAC,校园网不吃这一套
网上常见的思路是:既然它按端口限制,那我换个 MAC 地址总行吧?用 macvlan 给 wan2 造一张新身份证。
ip link add link eth1 name eth1-wan2 type macvlan mode bridge
ip link set eth1-wan2 address de:8e:8d:cd:a1:4f # 精心编的本地管理 MAC
ip link set eth1-wan2 up
uci set network.wan2.device='eth1-wan2'eth1 是 dc:8e:8d:cd:a1:4f,eth1-wan2 是 de:8e:8d:cd:a1:4f——只有第一个字节不同,足够像 twins 了。
拨号。这次日志不太一样:
pppd[15299]: Recv PPPOE Discovery V1T1 PADO ← 有回应
pppd[15299]: Send PPPOE Discovery V1T1 PADR
pppd[15299]: Send PPPOE Discovery V1T1 PADR ← 反复请求
pppd[15299]: Send PPPOE Discovery V1T1 PADR
pppd[15299]: Timeout waiting for PADS packets ← 但 PADS 永远不来
pppd[15299]: Unable to complete PPPoE Discovery进展了一步:换 MAC 之后,AC 连"假装同意再踢你"都不装了,直接在 PADO(提供发现应答)之后就失聪,PADS(建立会话)永远不来。
所以这条路也死了。校园网按端口限流的判断发生在 MAC 层之上,换张身份证没用。
四、破局:既然按端口限,那就给他两个端口
到这里我已经排除了"配置写错"这个可能性——两次失败都指向同一条铁律:物理端口是硬边界。
那思路就很直接了:不要在一个端口上开两条线,给它两个端口。
但这里又有个小坑。LAN1 口在我这套固件里并不是普通的 LAN 口,它被单独划进了一个叫 server-lan 的桥:
network.@device[1].name='server-lan'
network.@device[1].ports='lan1'
network.server_lan=interface
network.server_lan.device='server-lan'
network.server_lan.ipaddr='192.168.68.1'
network.server_lan.disabled='1'关键在最后一行——server_lan 这个三层接口是 disabled 的,也就是说 LAN1 平时根本不参与我主 LAN 的业务。它是一个闲置的、独立的口。
这简直是天赐良机。确认了 lan1 的物理链路是 UP, LOWER_UP 之后,我直接把它指给 wan2:
uci set network.wan2.device='lan1'
uci commit network
ifup wan2{
"up": true,
"l3_device": "pppoe-wan2",
"metric": 20,
"ipv4-address": [{ "address": "10.169.108.192", "mask": 32 }]
}up: true。IP 拿到。两条 PPPoE 同时在线。
从"物理端口限制"到"物理端口解决",前后花掉的时间不到半小时。 早知道这样,第一天就该先把 lan1 拉出来试。
顺手说一句,那次误建的 macvlan 接口 eth1-wan2 我已经 ip link delete 掉了。macvlan 这条路虽然失败,但它证明了校园网的限制确实在 MAC 之上——这算是它唯一"有价值的"贡献。
五、配置 mwan3:和一个很蠢的坑搏斗
两条线都通了,接下来是重头戏:mwan3。
我原本的 /etc/config/mwan3 里已经有了一个历史遗留的 balanced 策略,但只挂着一个 wan 成员。要加第二条线。

第一次用 uci add 添加接口,踩了个经典的坑:
uci add mwan3 interface
# → cfg076d96我兴冲冲地设了 interface='wan'、track_ip='223.5.5.5'、enabled='1',结果 ubus call mwan3 status 里:
"cfg076d96": { "status": "unknown", "enabled": false, "track_ip": [] }track_ip 是空的,配置根本没被加载。
问题在于 section 的名字。 mwan3 要求 interface section 的名字必须和网络接口同名(wan、wan2),而 uci add 生成的是匿名 section(@interface[0])。更坑的是,我第一次 uci rename mwan3.@interface[0]=wan 的时候,名字 wan 已经被旧的配置占用了,于是:
uci rename mwan3.@interface[0]=wan
# 结果:mwan3.wan2 变成了管理 wan 的配置
# @interface[1] 成了没人管的野孩子一个 rename 把两个 section 的名字搞反了。
后来学乖了:先把占位的 wan 临时改名让路,再逐个 rename 到正确名字,最后一步才 commit。整个过程像在雷区里挪家具。
顺便记一下 use_member 这个字段的规矩——它引用的是 member 的 UCI 名字,不是 uci add 返回的那种 cfgxxxx ID。我第一次填了 cfg096fdd,规则静默不生效;uci rename mwan3.cfg096fdd=wan2_m1_w1 之后,名字对上了,策略才真的把两条线都吃进去。
配置最终长这样:
config member 'wan_m1_w3' # 名字骗人,weight 其实 =1
option interface 'wan'
option metric '1'
option weight '1'
config member 'wan2_m1_w1'
option interface 'wan2'
option metric '1'
option weight '1'
config policy 'balanced'
list use_member 'wan_m1_w3'
list use_member 'wan2_m1_w1'顺便吐槽一句:成员名
wan_m1_w3里的w3让我以为权重是 3,会导致 3:1 倾斜分配。实际上option weight '1',两个都是 1,所以是标准的 50/50 均分。名字和实际配置对不上这种事,排查时真的很容易被带偏,建议改配置时顺手把名字也理一下。
六、插曲:AdGuardHome 把整个宿舍的网断了
配置 mwan3 的间隙,我需要查外部 IP,顺手去 LuCI 点了一下 DNS 状态——然后整个宿舍断网了。
排查过程并不复杂,但结论有点冤:
nslookup baidu.com 223.5.5.5 # 通!上游 DNS 没问题
nslookup baidu.com 127.0.0.1 # 超时/etc/resolv.conf 指向 127.0.0.1,而 53 端口被 AdGuardHome 占着。翻它的日志,满屏都是:
[error] dnsproxy: response received upstream_type=bootstrap
addr=202.99.104.68:53 status="i/o timeout"看配置才明白:AdGuardHome 的上游用的是 DoH(加密 DNS),而要解析 doh.360.cn 这些 DoH 域名的前置 DNS(bootstrap)填的是 202.99.104.68 和 202.99.96.68——这两个地址在当前网络里根本不可达。
用一个不通的 DNS 去解析另一个 DNS 的域名,而那个 DNS 又需要先解析才能建立连接。死锁。
改成 223.5.5.5 之后,秒解。整件事的教训是:装任何 DNS 转发/过滤类插件之前,先确认它的 bootstrap 能通。
七、最难的一环:RTNETLINK File exists,以及它的真正元凶
配置都齐了,我满怀期待地重启 mwan3:
/etc/init.d/mwan3 restart然后终端开始疯狂刷:
RTNETLINK answers: File exists
uci: Entry not found
RTNETLINK answers: File exists一行接一行,停不下来。stop 掉之后,配置里明明什么都对,restart 还是同样报错。我开始怀疑人生:
- 是防火墙 zone 的问题?→ 检查,NAT 里确实没有
pppoe-wan2,但这不该导致启动失败 - 是 IPv6 默认规则的问题?→ 删掉
default_rule_v6,照刷不误 - 是
/etc/iproute2/rt_tables里我手动加的100 wan/200 wan2跟 mwan3 冲突?→ 不太可能,编号差得很远 - 是 mwan3 版本太老?→ 2.8.12-1,看起来挺新
我甚至去翻了 mwan3 的源码,想从 ip route add 那一行里找出端倪:
$IP4 route add table $tid $line后来日志给了我答案——mwan3 自己老老实实建了路由表和策略规则,mwan3rtmon 也在跑,但规则加不进去。
真正的问题在 network 配置文件里,一个我从来没在意过的字段:接口跃点数(metric)。
我在 LuCI 的 mwan3 接口页看到过一行黄字提示"未设置接口跃点数!",当时以为是界面的美学问题,随手就飘过去了。但实际上——
wan2 有 metric 20,而 wan 没有设 metric。
mwan3 要给每个 WAN 建一张独立的路由表(wan → table 2,wan2 → table 1),靠的就是接口 metric 来区分。没有 wan 的 metric,mwan3 建不出 wan 那张表的完整结构,重启时残缺的规则和已有状态撞车——File exists。
一行命令,完事:
uci set network.wan.metric='10' # wan2 已经有 20 了
uci commit network
/etc/init.d/mwan3 restart两口瞬间从 offline and tracking is down 变成 online,tracking is active。
interface wan2 is online ... and tracking is active
interface wan is online ... and tracking is active
Current ipv4 policies:
balanced:
wan2 (50%)
wan (50%)有时候最难的 bug,答案就藏在第一篇教程的第一行里。 我不是没看过 metric,只是当时没把它当成"必需项"——直到它以最不可爱的形式回来找我。
八、NAT 补票:wan2 还没进 WAN zone
mwan3 跑起来了,两条线都显示 online,但我隐约觉得哪里不对——去 wan2 上 ping 外网,源地址根本不会被转换。
查 NAT 表,zone_wan_postrouting 里只有 pppoe-wan 的痕迹,pppoe-wan2 一片空白。
果然,防火墙的 wan zone 只声明了两个网络:
firewall.@zone[1].name='wan'
firewall.@zone[1].network='wan' 'wan6' # 没有 wan2路由通了,NAT 却是空的——这种"半通不通"的状态最讨厌:连接被选中、路由被查表,然后在上游被丢弃。
补票:
uci add_list firewall.@zone[1].network='wan2'
/etc/init.d/firewall reloadip route get 现在能正确解析两条出口了,NAT 也终于认了这两条线。/etc/init.d/firewall reload 一般不会断 SSH,但如果遇到了,等几秒重连即可。
九、验证:计数器在对我撒谎
配置全部就位,接下来是验证。我写了个脚本连上路由器,每 2 秒采一次两条 WAN 的流量。
第一次跑完,我傻了:
wan= 0.00 Mbps wan2= 0.00 Mbps
wan= 0.00 Mbps wan2= 0.00 Mbps
(全程 0)我明明开着 16 线程在下载王者荣耀的安装包,怎么会全是 0?
是我读错了接口。 之前那些"读到 0 所以没叠加"的结论,其实都是同一个错误:我读的是 pppoe-wan 和 pppoe-wan2。

上图才是真相——wan(pppoe-wan)发送 438.45 MB,pppoe-wan2 发送 425.69 MB。几乎一模一样。
问题在于,PPPoE 是虚拟接口,它的计数器在 /proc/net/dev 里不随流量正常递增(或者说,在这套固件上表现得不正常)。要测真实流量,得读底层的物理网口:eth1(wan 的底子)和 lan1(wan2 的底子)。
改完脚本再跑一次 16 线程下载:
| 指标 | 数值 |
|---|---|
eth1 (wan) 下载 | ≈ 55 Mbps |
lan1 (wan2) 下载 | ≈ 51 Mbps |
客户端实收(wlan1/br-lan TX) | ≈ 110 Mbps |
两条线各自承载约 50 Mbps,加起来约 110 Mbps,客户端实收也约 110 Mbps——账对上了,闭环了。之前反复出现的"0",纯粹是我在读一口没有水的井。
十、实测:单连接和多连接的差别有多大
验证阶段最反直觉的一点:测速工具的数字和真实体感差别很大。

上面这个测速跑的下载只有 21.1 Mbps。而同一次测试里,两条物理链路上跑的流量加起来是 110 Mbps。差了五倍。
不是叠加没生效,是这个测速只用到了一条线。
要理解这一点,得看 mwan3 到底怎么选路。翻开 iptables 规则,真相很清楚:
Chain mwan3_policy_balanced
MARK statistic mode random probability 0.50000000000 /* wan2 1 2 */ MARK xset 0x100/0x3f00
MARK /* wan 1 1 */ MARK xset 0x200/0x3f00配合上面的 CONNMARK save / restore:
- 选路是按连接随机 50/50 的
- 但一条 TCP 连接一旦选定,就锁定走那条线(conntrack 记住了)
- 所以单连接(一次测速、一个视频流、一局游戏)→ 只能吃一条线的带宽
- 多连接(16 线程下载、多设备同时上网)→ 才会分摊,叠加生效
这正是负载均衡的本质,也是它和"单连接真·聚合"(比如 MPTCP、OpenMPTCProuter)的区别。后者需要所有流量先汇聚到一台中转服务器上再合并回传,代价是延迟和额外带宽成本。
所以结论很实际:
- 多线程下载 / 多设备同时用:能跑到两条线之和 ≈ 110 Mbps ✅
- 单线程下载 / 单个视频流 / 一局游戏:只走一条线 ≈ 21~50 Mbps(这是正常现象,不是故障)
对日常上网来说,多设备并发的场景本来就占绝大多数,所以 50/50 均分对上网体验的提升是实打实的。
顺带一提,我一开始在 mwan3 里看到过一个 https 规则,作用是把 443 端口的连接打上 sticky 标记(强制走同一条线)。这在 HTTPS 成为主流的今天是个"反叠加"的设置——所有网页流量都会挤在一条线上。关掉它:
uci set mwan3.https.sticky='0'多线程 HTTPS 下载这才真正能分摊到两条线。

这种几十 GB 级别的多连接下载,是最能体现叠加价值的使用场景。
十一、收尾:现在的状态,和一条遗留的债
到这儿,配置算是彻底完成了。让我连上路由器跑了一遍完整的健康检查:
{
"interfaces": {
"wan2": { "status": "online", "running": true, "score": 10, "lost": 0 },
"wan": { "status": "online", "running": true, "score": 10, "lost": 0 }
},
"policies": {
"ipv4": { "balanced": [
{ "interface": "wan2", "percent": 50 },
{ "interface": "wan", "percent": 50 }
] }
}
}两条线都在线,丢包 0,50/50 均分,持续运行了三小时无报错。策略路由规则也齐了:
1001: from all iif pppoe-wan2 lookup 1
1002: from all iif pppoe-wan lookup 2
2001: from all fwmark 0x100/0x3f00 lookup 1
2002: from all fwmark 0x200/0x3f00 lookup 2遗留的一笔债:排查过程中,我手动往 /etc/iproute2/rt_tables 里加过 100 wan 和 200 wan2 两行。当时以为要用它们,结果发现 mwan3 用的是 table 1 和 table 2,这两行名字完全没用上。它们不会影响功能(没有对应路由),但属于我留下的垃圾。需要的话我可以清掉。
十二、几句真心话
折腾了这么久,几个当初踩坑时希望有人提前告诉我的点:
1. 校园网是按物理端口限流的,不是按账号。 同一物理口多拨必被 AC 主动 PADT 踢掉,换 MAC 只会让它提前失聪。正确解法是给两个物理口。
2. ip rule 不要随便加。 排查时我加过一条 ip rule add from 192.168.1.0/24 table 200,结果整个 LAN 的回程流量被切走,SSH 直接断联。这条规则不是持久化的,重启即消失——但断联的那几分钟确实让人心率飙到 120。不要用会影响自己管理连接的方式做测试。
3. metric(跃点数)是 mwan3 的隐形支柱。 它在 LuCI 上只是一行黄字,在 mwan3 的接口页上只是一句"未设置"。但没有它,mwan3 建不出路由表,然后你会面对一屏 RTNETLINK answers: File exists,完全不知道问题出在两个配置文件之外的地方。任何一个 WAN 接口没设 metric,mwan3 就会起不来。
4. mwan3 的 section 名必须和接口名一致。 uci add 出来的匿名 section(@interface[0])不会被加载,必须 uci rename 成 wan/wan2。而 rename 前要先给占位的旧名字让路。use_member 引用的是 member 名字,不是 cfgxxx ID。
5. 测流量别读 PPPoE 虚拟接口。 pppoe-wan/pppoe-wan2 的计数器在 /proc/net/dev 里不可靠,要读底层物理口(我这个设备上 eth1 对应 wan,lan1 对应 wan2)。这个问题让我白白怀疑了整整三轮"叠加到底生效没有"。
6. 50/50 是均分,不是叠加单连接。 多连接能跑满两条线之和,单连接只有一条线。这不是缺陷,是负载均衡的定义。
7. 装 DNS 插件先测 bootstrap。 AdGuardHome 的 bootstrap_dns 填了不可达的地址,结果整个宿舍断网,而外部 DNS 本身是好的。排查方向一开始就跑偏了。
从一条网线到两条网线,从 File exists 刷屏到 50/50 稳定在线,中间最关键的转折点其实很朴素——当一个方向被堵死的时候,先回到物理层看看有没有别的路可以走。
软件配置出问题的时候,多半是硬件拓扑本身就摆错了。
参考资料
- mwan3 官方文档 · https://openwrt.org/docs/guide-user/network/wan/mwan3
- PPPoE(RFC 2516)会话流程:PADI → PADO → PADR → PADS → PADT
- OpenWrt 策略路由:
ip rule/ip route/rt_tables