校园网路由器折腾记:给认证脚本补上 UA,再用一条 TTL 规则停掉“路由器共享”记录
我在宿舍用一台 x86 软路由(CatWrt,OpenWrt 24.10 系)接校园网,自动认证用的是自己写的 campus-auth:BusyBox shell 写核心,LuCI 用 Lua CBI 写配置页,procd 托管。这次和 Claude Code 一起做了两件事:
- 对照一个开源的同类插件,看看自己的脚本还差什么,顺手加上自定义 UA。
- 弄清楚门户“共享行为”里每十分钟一条的“路由器共享”是怎么来的,再决定用 UA3F、UA2F 还是别的办法。
结论先放这里:认证请求一直没带 UA,补上了;学校的共享检测看的是 TTL,在 LAN 口入口改一下 TTL 就够了,UA3F 和 UA2F 都没装。
文中的 IP、账号、学校等信息都已替换或省略。
环境
- 路由器:x86_64,内核 6.12,防火墙是 fw3 + iptables(legacy),另外装了 nftables
- WAN 口
eth3走 DHCP,LAN 口eth0、eth1、eth2桥接成br-lan - OpenClash 开着,fake-ip 模式,路由器自身流量也走代理,开了“国内 IP 绕过”
- 防火墙的软件流量卸载和硬件流量卸载都开着
- 门户是某厂商的新版 SPA,认证接口
POST /api/portal/webauth/auth,套餐是运营商代拨
一、对照同类插件:认证请求没带 UA
那个插件用 Rust 写,有一个“电脑端 / 手机端”槽位选项,认证的每一步都带同一个 User-Agent。门户按 UA 判断这次认证占电脑槽还是手机槽,一个账号各一个。
回头看自己的脚本,认证 POST 那一行没有 -A,而 curl 又用了 -q(不读 curlrc),所以门户收到的 UA 一直是 curl/x.y。之前能用,是因为门户把它判成了电脑。手机抓包里,认证成功后的跳转地址带着 terminalType=b,门户自助后台的认证日志里也分“电脑 / 手机”两类。
MAC 也看了一下,分两层:
- 表单里的
mac字段:脚本先取门户跳转地址里的值,没有再用 WAN 网卡的 MAC。门户绑定设备看的是网关上的真实 MAC,只改字段没什么用,还可能前后对不上,所以不开放修改。 - WAN 口真实 MAC(比如克隆某台设备的 MAC):用 OpenWrt 自带的“网络 → 接口 → 设备 → MAC 地址”。认证插件不该去改网络配置,改错了会直接断网。
二、给 campus-auth 加 UA 选项
UCI 里加了两个选项:
| 选项 | 默认 | 说明 |
|---|---|---|
ua | mobile | mobile / pc / custom |
ua_custom | 空 | ua=custom 时的完整 UA |
获取网关参数和提交认证两步都带这个 UA,204 探测不带。自定义 UA 会拼进请求头,所以只接受单行可打印 ASCII,最长 512 字符,防止有人塞换行进去注入额外的头。无效值回退到手机端,并往 stderr 打警告:
valid_ua() {
printf '%s\n' "$1" | LC_ALL=C awk '
NR != 1 || length($0) < 1 || length($0) > 512 || $0 ~ /[^ -~]/ { bad=1 }
END { exit (NR != 1 || bad) }
'
}
LuCI 那边是一个下拉框,选“自定义 UA”时才出现输入框,Lua 侧做同样的校验。测试结果:
- 核心测试 44 项全过(新增 2 项,在 WSL 的 dash 下跑)
- 安装 / init 的 mock 测试 74 项全过
- LuCI 表单测试 223 项全过(新增 12 项,本机没装 Lua,用 fengari 跑的)
Windows 的 Git Bash 下有 9 个锁和队列相关的测试会挂,它们依赖 Linux 的 /proc,换到 WSL 就全过。
这里我犯了一个错:默认值定成了 mobile,理由是抓包里的 UA 是手机。后来看门户的认证日志,我的手机一直占着手机槽,路由器一直占电脑槽。路由器用手机 UA 去认证,就会去抢手机的槽位。最后我在 LuCI 里选了自定义,填了一个 Mac 版 Chrome 的 UA,让路由器继续占电脑槽。
部署时的小插曲
装完发现服务没起来。原因是路由器上的配置当时是 enabled=0,安装脚本按配置办事,就没有启动。后来我在 LuCI 里重新勾上“启用”并保存,服务还是没起:config.change 触发器注册着,但这次保存没有触发 reload,最后是手动 /etc/init.d/campus-auth start 拉起来的。现在我改完配置都会看一眼“进程状态”。
三、“路由器共享”记录长什么样
门户的“共享行为”页面只显示最近两条,好在它背后有一个分页接口 /api/portal/user/proxyRecords,登录后在浏览器里带上 token 就能拉全量。拉下来是这样:
- 一共 525 条,共享方式全部是“路由器共享”(
proxyMode=7),处理方式全部是“不处理” - 全部来自路由器的 WAN IP
- 只要在线就一直在记,相邻两条的间隔有 511 次是 10 或 11 分钟,偶尔漏掉一次变成 20 分钟左右
- 中间有将近 6 天一条都没有,原因我没查出来
按 10 分钟左右的固定节奏抽查,像是网关在周期性地看流量特征,和我在做什么没关系。学校目前“不处理”,算是默许,但我还是想让它停下来,免得哪天规则一改就变成“处理”。
四、UA3F 还是 UA2F
两个都是校园网圈子里常用的工具,按我这台路由器的情况比了一下:
| UA3F | UA2F | |
|---|---|---|
| 能改什么 | HTTP 的 UA,另外有 L3 重写:TTL、IPID、TCP 时间戳、TCP 初始窗口 | 只改 HTTP 的 UA |
| 和 OpenClash 共存 | 文档里写了支持 | 要自己处理 |
| 内核要求 | TTL 只要防火墙规则;其他 L3 重写要 NFQUEUE | 要开 NETFILTER_NETLINK_GLUE_CT,固件自带内核不一定有 |
不知道学校看什么特征的话,UA3F 覆盖面更大。不过开着“国内 IP 绕过”时,国内流量不经过 Clash,UA3F 就不能用 SOCKS5 模式挂在 Clash 后面,得用 REDIRECT 或 TPROXY。
装之前我想先试最便宜的那个:如果学校看的是 TTL,一条防火墙规则就解决了,什么都不用装。
五、TTL 实验
思路很简单:Windows 默认 TTL 128,手机和 Linux 是 64,经过路由器转发会减 1。网关看到同一个 IP 发出来的包 TTL 有 63、127、64 好几种,就能猜到后面挂了路由器。把所有转发出去的包都统一成 64,看起来就跟路由器自己发的一样。
实际动手时有两个坑。
第一个是包版本。软件源里的 iptables-mod-ipopt(提供 TTL 目标)是 1.8.10,系统装的 iptables 是 1.8.7,硬装可能把 OpenClash 依赖的 iptables 也带乱。好在系统自带 nftables 1.1.1,直接用它。
第二个是流量卸载。软件和硬件卸载都开着,规则如果写在 postrouting,每个连接只有前几个包会经过它,后面的包走快速路径就绕过去了。所以规则放在 LAN 口的 netdev ingress 上,包一进路由器就改成 65,转发减 1,出 WAN 正好是 64:
nft -f - <<'EOF'
table netdev campus_ttl {
chain lan_in {
type filter hook ingress devices = { eth0, eth1, eth2 } priority -500; policy accept;
ip ttl set 65 counter
}
}
EOF
规则只在内存里,重启就没了,想撤销也只要 nft delete table netdev campus_ttl。加上之后 5 秒就有 459 个包命中,外网探测正常。
然后等了 35 分钟:
| 实验时段 | 20:55 到 21:31 |
| 按原来的节奏应该出现的记录 | 约 20:55、21:05、21:16、21:26,共 4 条 |
| 实际新增 | 0 条,最后一条还停在 20:44 |
| 这段时间规则改过的流量 | 30.8 万个包,约 62 MB |
以前偶尔也会漏一次,但连续 4 个周期都没有,基本能确定学校看的就是 TTL。
六、固化规则
fw3 每次启动防火墙都会执行 /etc/firewall.user,把规则写进去,先删旧表再加载,就不会重复添加。比实验版多了一个排除条件:发往局域网自己、组播、广播的包不改,投屏、打印机、设备发现这类局域网内部流量完全不碰。
# campus-ttl:LAN 口入口把 IPv4 TTL 设为 65,转发后出 WAN 为 64
# 撤销:删掉本段,再运行 nft delete table netdev campus_ttl
nft delete table netdev campus_ttl 2>/dev/null
nft -f - <<"NFT"
table netdev campus_ttl {
chain lan_in {
type filter hook ingress devices = { eth0, eth1, eth2 } priority -500; policy accept;
ip daddr != { 192.168.1.0/24, 224.0.0.0/4, 255.255.255.255 } ip ttl set 65 counter
}
}
NFT
192.168.1.0/24 换成自己的 LAN 网段,eth0, eth1, eth2 换成自己桥里的物理口(ls /sys/class/net/br-lan/brif/ 能看到)。改完 /etc/init.d/firewall restart 验证了一遍:TTL 表还在并且在计数,OpenClash 的 iptables 链还在,外网探测 204,认证服务正常。
七、带回家会怎样
正常上网没有影响。家里运营商不查 TTL,而且 64 本来就是 Linux 和路由器的默认值。
会受影响的只有 traceroute / tracert:它靠发 TTL 很小的包来一跳一跳地探路,被改成 65 之后中间节点就看不到了。要排查网络时,临时删掉表就行,重启后会自动加回来。
另外两点:
- 规则只改 IPv4。校园网目前是 IPv4,如果以后有 IPv6,要另外加
ip6 hoplimit set的规则。 - 家里网正常时,
campus-auth的 204 探测会成功,不会去认证;家里断网时它会去找学校门户,找不到就退避,最多五分钟试一次,不碍事。嫌它多余可以在 LuCI 里关掉。
还没验证的
写这篇的时候规则只跑了几十分钟,长期是不是一直有效,要过几天再看“共享行为”里还有没有新记录。不同学校的检测手段不一样,这里的结论只对我这所学校的这套门户成立:如果你那边改了 TTL 还在记,下一步就是 UA3F 的 L3 重写和 UA 改写。
最后,学校对这类记录目前是“不处理”。各校规定不同,动手之前先确认自己学校允许路由器共享。