校园网giWiFi解决方案
校园网终极折腾指南
核心目标
本指南旨在彻底解决校园网的诸多痛点,实现以下四大终极目标:
- 突破设备限制:绕过 3 台设备限制,并防止深信服 DPI 检测封号。
- 防 DNS 劫持:防止网关透明劫持 53 端口,保护隐私。
- 全局无感去广告:在解析阶段直接屏蔽广告域名。
- 全自动断线重连:彻底告别每天手动点击认证弹窗的烦恼。
一、网络架构与检测原理
1. 整体架构
采用 OpenWrt(主路由)+ AdGuard Home(旁路 DNS) 的方案,常以 DietPi/树莓派等独立环境作为旁路设备:
- NAT 伪装:所有设备连接 OpenWrt,校园网计费系统仅可见路由器 WAN 口的单一 MAC/IP 地址。
- DoH 加密:拦截局域网 53 端口(UDP)请求,由 AdGuard Home 封装为 HTTPS 流量(443 端口)转发,绕过校园网网关的 DPI 检测与 DNS 劫持。
- 黑名单阻断:利用本地规则在解析阶段直接屏蔽广告域名,实现全网无感去广告。
- 绿灯通道:放行系统探测域名与认证域名走明文,保证认证页面正常弹出。
2. 多设备检测原理
校园网主要通过 DPI(深度包检测) 分析不同设备流量的特征来判断是否存在多台设备共享上网。常见检测特征包括:
- 不同的 TTL 值(不同操作系统初始 TTL 不同,且每过一个路由器递减 1)
- 不同的 NTP 时间服务器请求(Windows / macOS / Android 默认对时服务器不同)
- 不同的 HTTP User-Agent(暴露浏览器与设备厂商信息)
- IPID 递增规律(不同设备 IP 标识符递增模式不同)
因此,需要在路由器层面将这些特征统一,把多台设备伪装成单一设备。
二、OpenWrt 预防多设备检测
版本说明
从 OpenWrt 22.03 开始,防火墙已升级为fw4(nftables),旧版的iptables命令已失效。以下配置针对基于fw4的新版固件(如 Kwrt 24.10)。为兼容部分缺失特定内核模块的固件,建议使用自定义链。
1. 统一 TTL 值
通过 SSH 登录 OpenWrt 终端执行以下命令,写入规则并重启防火墙:
# 写入自定义的 nftables TTL 修改规则
cat << 'EOF' > /etc/nftables.d/12-mangle-ttl-128.nft
chain custom_ttl_set {
type filter hook postrouting priority 300; policy accept;
oifname "wan" ip ttl set 128
oifname "wan" ip6 hoplimit set 128
oifname "pppoe-wan" ip ttl set 128
oifname "pppoe-wan" ip6 hoplimit set 128
}
EOF
# 重启防火墙使规则生效
service firewall restart
验证命令(可选): nft list ruleset | grep -A 6 "custom_ttl_set"
2. 劫持 NTP 请求
不同设备(Windows/Mac/Android)默认的 NTP 时间服务器不同。理论上应让路由器统一接管局域网内的对时请求,让所有设备的 NTP 包都路由到同一台上游,消除"Windows 找 time.windows.com、macOS 找 time.apple.com、Android 找 pool.ntp.org"这类指纹差异。
配置位置说明
该选项不在网络 -> DHCP/DNS -> 常规设置里(那里的"截获 NTP 请求 (Intercept NTP)"已在较新版本中调整或并入此处)。正确位置是系统->时间同步选项卡,对应的开关叫 "作为 NTP 服务器提供服务"(对照下方截图)。
操作步骤(网页后台):
- 导航至
系统->时间同步。 - 切换到 时间同步 选项卡。
- 勾选 作为 NTP 服务器提供服务。
- (可选)确认 候选 NTP 服务器 至少包含
ntp.aliyun.com或0.openwrt.pool.ntp.org等可达节点。 - 点击
保存并应用。
截图示意:

个人实测:未勾选"作为 NTP 服务器提供服务"也可行
我当时实际配置中并未勾选这一项,UA2F + 统一 TTL 仍能稳定防住多设备检测,原因有两点:
- 路由器自身时间已准确:只要"启用 NTP 客户端"勾选、候选 NTP 服务器可达,路由器本机时间就是对的;UA2F 与 TTL 规则只依赖本机时间,不依赖局域网内其他设备是否走这台对时。
- 校园网 DPI 权重低:实测中 giWiFi 等校园网对"不同 NTP 服务器"差异的检测权重较低,主要指纹仍是 TTL 与 HTTP User-Agent,所以即便不统一局域网对时源,配合 §1(TTL)与 §3(UA2F)通常已足够。
因此该项建议勾选以做最完整的防御,但不是强制项——把它当作"加分项"而非"必做项"。
3. 统一 User-Agent(UA2F)
明文 HTTP 流量会暴露不同设备的浏览器 UA。使用 UA2F(User-Agent to Firefox)插件统一修改未加密 HTTP 流量的特征。
- 导航至
服务->UA2F。 - 勾选
启用、自动设置防火墙规则、处理来自内网的 HTTP 流量。 - 点击
保存并应用。 - 使用底部的
检查 User-Agent测试服务器端 UA 是否已统一。
前置条件与故障排查
必须关闭流量卸载:在网络->防火墙->常规设置中将 路由/NAT 卸载 设为 禁用。流量卸载会绕过 CPU 与防火墙,导致 UA2F 无法抓包。代理软件冲突:Open*****、PassWall 等代理工具可能劫持 80/443 端口流量,导致 UA2F 拿不到数据。配置与测试时建议先暂时关闭代理,或在代理规则中排除校园网检测流量。
4. 进阶防御(按需配置)
若完成上述步骤后仍被检测,可叠加以下方案:
- 修改 IPID 防检测:编译固件时加入
kmod-rkp-ipid模块,修改 IPID 递增规律。 - 开启 DNS 加密:使用
https-dns-proxy或SmartDNS等插件,将上游 DNS 设为 DoH/DoT 加密服务器,防止 DNS 请求暴露设备厂商特征(如苹果 mDNS 请求、小米服务器请求)。
三、部署 AdGuard Home 旁路 DNS
利用独立环境(如 DietPi/树莓派)部署旁路 DNS,把局域网 DNS 请求加密封装为 HTTPS(DoH)流量,绕过校园网对 53 端口的劫持。
1. 容器化部署
使用 host 网络模式确保无缝接管局域网端口,并建立数据持久化:
mkdir -p ~/docker-files/adguard-data/work
mkdir -p ~/docker-files/adguard-data/conf
docker run -d \
--name adguardhome \
--restart unless-stopped \
--network host \
-v ~/docker-files/adguard-data/work:/opt/adguardhome/work \
-v ~/docker-files/adguard-data/conf:/opt/adguardhome/conf \
adguard/adguardhome
2. DNS 上游与"绿灯通道"配置(关键)
进入面板(http://192.168.1.133)-> 设置 -> DNS 设置:
-
上游 DNS 服务器(开启并行请求):
https://dns.alidns.com/dns-query https://doh.pub/dns-query [/wifiopenapiauth.com/msftconnecttest.com/msftncsi.com/captive.apple.com/connectivitycheck.gstatic.com/connect.rom.miui.com/]114.114.114.114绿灯通道解析
最后一行极其重要!它能让系统网络探测域名和 giWiFi 认证域名走明文、主动被校园网劫持,从而保证认证页面能正常弹出,避免出现"安全死锁"(即 DoH 加密后认证弹窗无法跳出)。 -
Bootstrap DNS(用于解析上游节点):
223.5.5.5 119.29.29.29 114.114.114.114
3. 拦截规则配置
进入 过滤器 -> DNS 黑名单,启用精简规则以防误杀:
CHN: anti-AD(国内去广告主力)AdGuard DNS filter(基础兜底)OISD Blocklist Big(大白名单无感过滤)
强屏蔽环境应对策略
若校园网强屏蔽导致 GitHub 规则无法拉取,可利用国内加速镜像替换规则 URL;或直接在宿主机新建规则文件(路径如~/docker-files/adguard-data/work/my_local_filter.txt),并在面板"自定义列表"中填入容器内绝对路径:/opt/adguardhome/work/my_local_filter.txt。
四、OpenWrt 强制引流与防泄漏
必须切断操作系统的"并发查询"机制,确保解析请求 100% 经过 AdGuard Home:
-
修改 DHCP 下发配置:
- 路径:
网络 -> 接口 -> LAN -> DHCP 服务器 -> 高级设置。 - 设置 DHCP 选项 为:
6,192.168.1.133。
致命禁忌
绝对不能添加备用 DNS(如,192.168.1.1),否则会导致操作系统并发查询,引发 DNS 泄露和广告漏网,使防劫持彻底失效。必须容忍单点故障风险以换取绝对安全。 - 路径:
-
隔绝上级污染:
- 路径:
网络 -> DHCP/DNS -> 常规设置。 - 勾选 忽略解析文件(Ignore resolve file)。
- 路径:
配置完成后,局域网设备需断开 WiFi 重新连接,以获取新的 DNS 参数。
五、全自动断线无感重连
利用计划任务(Crontab)每分钟检测网络,一旦发现被校园网踢下线,自动发送认证请求完成无感重连。本节记录的是本人实战踩坑后的最终可行方案:先用"抓包 + 静态脚本"的朴素思路,结果因为 giWiFi 的凭证动态过期而失败;最终改用 Python 动态加密脚本,从登录页实时提取动态密钥、再加密账号密码发起认证,验证成功。
0. 为什么"抓包 + 静态脚本"会失败?(关键教训)
最直觉的方案是:浏览器抓一次登录时的 authLogin POST 请求,把整段 cURL 塞进一个每分钟运行的 Shell 脚本里。这个方案在 giWiFi 上是行不通的,原因有两个:
- 凭证是"动态且一次性"的:giWiFi 的登录请求里有两个会变的东西——
PHPSESSID(会话 Cookie)和加密载荷里的iv(初始向量)。它们会随会话刷新甚至每次登录都不同。把抓包那一刻的旧凭证写死在脚本里,一旦过期,服务器直接拒绝。 - 过期请求会被判定为重放攻击:断网时脚本照旧把那段已过期的旧凭证发给网关,防火墙不仅拒绝登录,还可能认定你在恶意重放,直接把路由器的 MAC 拉黑——原本几秒钟就能自愈的网络波动,反而被脚本弄成了真正的"死线"(尤其在有 NAS 长期挂机的场景下是灾难)。
结论
不要用"抓一次包 → 写死 cURL"的静态脚本做 giWiFi 自动重连。要做,就必须让脚本每次都能动态拿到当次的加密密钥,也就是下面的 Python 方案。
1. 最终方案:Python 动态加密脚本(部署在旁路设备,主要是本人路由器性能不够)
giWiFi 的前端用 JavaScript + 一次性密钥对账号密码做 AES-CBC 加密,所以纯 curl/Shell 无法模拟。必须在路由器或旁路设备(树莓派/DietPi)上跑 Python,复刻浏览器的完整流程:
- 请求登录页 → 正则提取当次的
iv、sign等隐藏参数; - 用固定 AES 密钥 + 动态
iv对账号密码表单做加密; - 发起
authLoginPOST 认证。
依赖安装(以 DietPi 为例):
sudo apt update
sudo apt install -y python3 python3-pip
pip3 install requests pyaes
为什么用
pyaes而不是pycryptodome
giWiFi 前端用的是纯 JavaScript 的 AES 实现(密钥固定为1234567887654321,CBC 模式、Zero Padding)。pyaes是纯 Python 实现、无需编译、依赖更干净,在树莓派/DietPi 上更省事,且能精确复刻前端zero_pad(补\x00而非 PKCS#7)的行为。
脚本模板(/root/giwifi_auto.py,把 USERNAME/PASSWORD 换成你自己的):
#!/usr/bin/env python3
import requests, urllib.parse, re, time, base64, pyaes
USERNAME = "你的手机号"
PASSWORD = "你的校园网密码"
LOGIN_PAGE = "http://10.189.1.3/gportal/web/login?wlanacname=LITBAS1"
AUTH_URL = "http://10.189.1.3/gportal/web/authLogin"
AES_KEY = "1234567887654321" # giWiFi 前端固定常量,可能需要改,未作验证
def zero_pad(data_bytes):
pad_len = 16 - (len(data_bytes) % 16)
if pad_len != 16:
return data_bytes + b'\x00' * pad_len
return data_bytes
def encrypt_data(raw_text, key, iv):
padded = zero_pad(raw_text.encode('utf-8'))
aes = pyaes.AESModeOfOperationCBC(key.encode('utf-8'), iv=iv.encode('utf-8'))
enc = b"".join(aes.encrypt(padded[i:i+16]) for i in range(0, len(padded), 16))
return base64.b64encode(enc).decode('utf-8')
def check_network():
try:
res = requests.get("http://www.msftconnecttest.com/connecttest.txt", timeout=3)
return "Microsoft Connect Test" in res.text
except Exception:
return False
def login():
s = requests.Session()
s.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"X-Requested-With": "XMLHttpRequest"
})
try:
res = s.get(LOGIN_PAGE, timeout=5)
html = res.text
def get_val(name):
m = re.search(r'name=["\']' + name + r'["\'][^>]*value=["\']([^"\']*)["\']', html)
return m.group(1) if m else ""
iv = get_val("iv") or "532c12b6b081904d"
sign = get_val("sign")
user_ip = get_val("userIp") or "10.50.254.236"
form_dict = {
"nasName": get_val("nasName") or "LITBAS1",
"nasIp": get_val("nasIp"),
"userIp": user_ip, "userMac": "", "ssid": "", "apMac": "",
"pid": get_val("pid") or "23", "vlan": "", "sign": sign, "iv": iv,
"redirectUrl": "http://1.2.3.4",
"portalTemplateId": get_val("portalTemplateId") or "23",
"account_type": "",
"name": USERNAME, "password": PASSWORD,
}
raw_query = urllib.parse.urlencode(form_dict)
enc_data = encrypt_data(raw_query, AES_KEY, iv)
payload = {"data": enc_data, "iv": iv}
round_num = int(time.time() * 1000) % 1000
r = s.post(f"{AUTH_URL}?round={round_num}", data=payload, timeout=5)
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 响应: {r.text}")
except Exception as e:
print(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 出错: {e}")
if __name__ == "__main__":
if check_network():
print("网络正常,无需登录。")
else:
print("检测到断网,执行认证...")
login()
实测验证
手动执行python3 /root/giwifi_auto.py,终端返回"info":"认证成功,稍后跳转","status":1(即网关认证通过、外网恢复),证明"动态取值 + AES 加密"这条路完全走通。
2. 设置计划任务
把脚本挂到 Crontab,每分钟检测一次、断网自愈。用下面的命令安全追加(不覆盖已有任务):
(crontab -l 2>/dev/null; echo "* * * * * /usr/bin/python3 /root/giwifi_auto.py > /dev/null 2>&1") | crontab -
用 crontab -l 验证末尾已出现该行即可。此后无论何时掉线,树莓派/旁路设备都会在一分钟内完成动态取值、加密并触发重连。
断网检测为什么用
msftconnecttest.com而不是ping
校园网网关普遍劫持 ICMP,ping经常"假通"或"假断",误判率高。改用 HTTP 请求微软的连通性测试页(http://www.msftconnecttest.com/connecttest.txt,正常返回字符串Microsoft Connect Test),既能准确反映"能否真正访问外网",又不会被网关劫持成认证页而误判。
3. 失效预警:什么情况下这套脚本还会挂
这套动态脚本抗衰减能力很强(sign/iv 每次实时提取,不会自然过期),但以下三种外部变动仍可能导致失效:
- 学校 Portal 改版:若前端把固定
AES_KEY换掉,或升级为动态 RSA 协商,旧密钥会解密失败 → 需重新抓包分析新算法。 - 新增校验字段:若系统加了图形验证码、滑块、强制 MAC/设备指纹绑定等,当前字段组合会过不了后端校验。
- 业务层到期:账号欠费/被封禁,或改密码后未同步
PASSWORD。
出问题怎么快速定位
断网且未自愈时,手动执行python3 /root/giwifi_auto.py,观察终端响应: {...}里的status与info(如"密钥无效""验证码缺失""账号欠费"),几秒内即可锁定原因。
六、验证与排障指南
1. 验证防劫持
在电脑终端执行:
nslookup baidu.com 192.168.1.133
- 穿透成功:Server 返回
DietPi.lan或正确节点 IP。 - 遭遇劫持:Server 显示
wifiopenapiauth.com等认证页特征,需重新检查上述配置。
2. 10 秒宕机自救法
若 AdGuard Home 意外崩溃导致全网无法解析(表现为微信正常、网页打不开):
- 登录 OpenWrt 后台,删除 DHCP 选项中的
6,192.168.1.133,保存并应用。 - 手机等设备重连 WiFi 即可恢复基础网络;待修复容器后,再恢复该 DHCP 设置。
3. 常见故障排查汇总
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| UA2F 抓不到包、UA 未统一 | 路由/NAT 卸载未关闭 | 网络 -> 防火墙 -> 常规设置 禁用卸载 |
| UA2F 无数据 | 代理软件劫持 80/443 | 临时关闭 Open*****/PassWall 再测试 |
| 仍被多设备检测 | 缺少 IPID / DNS 加密防御 | 叠加"进阶防御"方案 |
| 规则无法拉取 | 校园网屏蔽 GitHub | 用国内镜像或本地 .txt 规则文件 |
| 认证弹窗无法弹出 | DoH 导致"安全死锁" | 确认已配置绿灯通道域名分流 |
| 网页打不开但微信正常 | AdGuard Home 宕机 | 删除 DHCP 选项 6,... 临时恢复 |
| 自动重连脚本失效 | Portal 改版 / 凭证或密钥变动 | 手动跑 python3 /root/giwifi_auto.py 看 status/info 定位 |
参考资料
- Openwrt 编译与防校园网多设备检测配置全流程
- OpenWrt 绕过校园网多设备检测
- giWiFi 自动认证 Python 方案(GitHub)(社区现成脚本,可参考
Warm-rain/giwifi、imldy/giwifi-web-auth等)

浙公网安备 33010602011771号