背景及需求

我校的电信有线网络并不会限制路由器的使用。然而,有线网络的认证方式是动态 IP + Web 认证。在每次重启路由器后,访问任意非 HTTPS 页面就会被劫持到电信的认证网页,在该网页上输入账号密码后,才能联网。

电信的认证网页

虽说不算太麻烦,但每次都要手动输入账号密码,还是让人有些不爽。正好前段时间购买了一台 newwifi 3 路由器并刷入了 Padavan 系统,因此探索一下使用路由器实现校园网自动认证的方法。

原理

我校有线网络 Web 认证的本质,就是发送一个 HTTP-POST 请求到认证服务器。因此,我们只需要用 curl 构造一个 POST 请求,并且在每次路由器重启后都发送一遍即可实现自动认证。

尽管不同学校的 POST 请求可能会有一些差别,但只要使用了 Web 认证,其原理和实现方法都是相同的。

抓取登录所用的 HTTP-POST 请求

这里,我们使用 Chrome 的开发者工具来抓取请求。重启路由器后打开认证页面,按 command - option - I 调出开发者工具,切换到 Network 选项卡并勾选 Preserve log

打开开发者工具

随后,我们正常输入用户名和密码登录。此时,开发者工具中会出现一些 HTTP 请求。我们需要在请求中寻找登录所需的项。一般来说,该项的 Request URL 会含有 login 等字段,且 Request MethodPOST

登录网络的 HTTP-POST 请求

在找到这个请求后,我们在请求上单击右键,选择 Copy > Copy as cURL

将请求复制为 cURL

将复制到的 cURL 粘贴到任意文本编辑器中,以待进一步的处理。

待处理的 cURL

修改 cURL 使其永久可用

以我校为例,在上一步中我们获得的 cURL 如下:

curl 'http://172.25.249.8/eportal/InterFace.do?method=login' -H 'Connection: keep-alive' -H 'Origin: http://172.25.249.8' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36' -H 'DNT: 1' -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' -H 'Accept: */*' -H 'Referer: http://172.25.249.8/eportal/index.jsp?userip=100.66.137.149&wlanacname=&nasip=171.88.130.251&wlanparameter=78-4f-43-4c-f0-01&url=http://123.123.123.123/&userlocation=ethtrunk/3:691.3201' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: zh-CN,zh-TW;q=0.9,zh;q=0.8,en;q=0.7' -H 'Cookie: EPORTAL_COOKIE_OPERATORPWD=; EPORTAL_COOKIE_SERVER=; EPORTAL_COOKIE_SERVER_NAME=; EPORTAL_COOKIE_USERNAME=; EPORTAL_COOKIE_PASSWORD=; EPORTAL_AUTO_LAND=; EPORTAL_USER_GROUP=null; JSESSIONID=684400A38AA6F9CAF582BD43C001BDE3' --data 'userId=<宽带账号>&password=<宽带密码>&service=&queryString=userip%253D100.66.137.149%2526wlanacname%253D%2526nasip%253D171.88.130.251%2526wlanparameter%253D78-4f-43-4c-f0-01%2526url%253Dhttp%253A%252F%252F123.123.123.123%252F%2526userlocation%253Dethtrunk%252F3%253A691.3201&operatorPwd=&operatorUserId=&validcode=&passwordEncrypt=false' --compressed --insecure

首先将末尾的 --compressed --insecure 去除。分析 cURL,前大半部分都是 HTTP 请求的标头(-H 后的内容),你可以酌情作一些修改或删除。比如把 -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36' 改为 -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36" 就能伪装成 Windows 的 User-Agent 等等,在此不多赘述。

最后的 --data 部分,才是我们要关注的重点。根据抓到的请求,userId= 后是我们的宽带账号,password= 后是我们的宽带密码。同时,后面的 userip%253D[1] 后是我们获得的内网 IP,wlanparameter%253D 后是我们设备的 MAC 地址。

为了构造可永久使用的 cURL,首先要确保宽带账号、宽带密码是正确的。最后需要处理的,就是内网 IP 和设备 MAC 地址的问题。在 Padavan 的 Linux 环境下,你可以使用以下命令获取当前的内网 IP:

ifconfig | grep inet | grep -v inet6 | grep -v 127 | grep -v 192 | awk '{print $(NF-2)}' | cut -d ':' -f2

使用以下命令获取设备 MAC 地址:

ifconfig ra0 | grep HWaddr | awk '{print $NF}' | tr '[:upper:]' '[:lower:]' | tr ':' '-'

这时,我们就可以构造出一个永久可用的 cURL,如下:

CURRENT_IP=$(ifconfig | grep inet | grep -v inet6 | grep -v 127 | grep -v 192 | awk '{print $(NF-2)}' | cut -d ':' -f2)

MAC_ADDRESS=$(ifconfig ra0 | grep HWaddr | awk '{print $NF}' | tr '[:upper:]' '[:lower:]' | tr ':' '-')

curl -X POST "http://172.25.249.8/eportal/InterFace.do?method=login" -H "Connection: keep-alive" -H "Origin: http://172.25.249.8" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36" -H "DNT: 1" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Accept: */*" -H "Referer: http://172.25.249.8/eportal/index.jsp?userip=${CURRENT_IP}&wlanacname=&nasip=171.88.130.251&wlanparameter=${MAC_ADDRESS}&url=http://baidu.com/&userlocation=ethtrunk/3:691.3201" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9,zh;q=0.8,en;q=0.7" -H "Cookie: EPORTAL_COOKIE_OPERATORPWD=; EPORTAL_COOKIE_USERNAME=; EPORTAL_COOKIE_PASSWORD=; EPORTAL_COOKIE_SERVER=; EPORTAL_COOKIE_SERVER_NAME=; EPORTAL_AUTO_LAND=; EPORTAL_USER_GROUP=null; JSESSIONID=2B36EA2F20A0CE7361D592CE7DBDFED3" --data "userId=<宽带账号>&password=<宽带密码>&service=&queryString=userip%253D${CURRENT_IP}%2526wlanacname%253D%2526nasip%253D171.88.130.251%2526wlanparameter%253D${MAC_ADDRESS}%2526url%253Dhttp%253A%252F%252Fbaidu.com%252F%2526userlocation%253Dethtrunk%252F3%253A691.3201&operatorPwd=&operatorUserId=&validcode=&passwordEncrypt=false"

# 记得修改宽带账号、宽带密码

在这里,我们用变量 CURRENT_IP 存储获得的内网 IP,用变量 MAC_ADDRESS 存储获得的 MAC 地址,并在 curl 命令中进行了替换。需要注意的是,要在 bash 命令的引号中使用变量的话,引号必须为双引号,而不能采用由 Chrome 复制得来的单引号。

测试 cURL

重启路由器或在认证页面上登出,打开 macOS 的终端,运行以下三行命令:

CURRENT_IP=$(ifconfig | grep inet | grep -v inet6 | grep -v 127 | cut -d ' ' -f2)

MAC_ADDRESS=$(ifconfig en0 | grep ether | awk '{print $NF}' | tr ':' '-')

curl -X POST "http://172.25.249.8/eportal/InterFace.do?method=login" -H "Connection: keep-alive" -H "Origin: http://172.25.249.8" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36" -H "DNT: 1" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Accept: */*" -H "Referer: http://172.25.249.8/eportal/index.jsp?userip=${CURRENT_IP}&wlanacname=&nasip=171.88.130.251&wlanparameter=${MAC_ADDRESS}&url=http://baidu.com/&userlocation=ethtrunk/3:691.3201" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9,zh;q=0.8,en;q=0.7" -H "Cookie: EPORTAL_COOKIE_OPERATORPWD=; EPORTAL_COOKIE_USERNAME=; EPORTAL_COOKIE_PASSWORD=; EPORTAL_COOKIE_SERVER=; EPORTAL_COOKIE_SERVER_NAME=; EPORTAL_AUTO_LAND=; EPORTAL_USER_GROUP=null; JSESSIONID=2B36EA2F20A0CE7361D592CE7DBDFED3" --data "userId=<宽带账号>&password=<宽带密码>&service=&queryString=userip%253D${CURRENT_IP}%2526wlanacname%253D%2526nasip%253D171.88.130.251%2526wlanparameter%253D${MAC_ADDRESS}%2526url%253Dhttp%253A%252F%252Fbaidu.com%252F%2526userlocation%253Dethtrunk%252F3%253A691.3201&operatorPwd=&operatorUserId=&validcode=&passwordEncrypt=false"

# 记得修改宽带账号、宽带密码

这里 CURRENT_IPMAC_ADDRESS 后的命令与上面的略有不同,是因为 macOS 下获取内网 IP 与 MAC 地址的命令与 Padavan 有一些区别。

稍等后,结果中出现了 success 的提示,并且网络可以正常连接了,就说明我们构造的 cURL 是可用的。

测试表明 cURL 可用

编写脚本

为了自动化完成 curl 认证的过程,我们需要编写一个简单的 shell 脚本,以下是一份参考:

#!/bin/sh
logger "开始检测网络认证状态"
KEYWORD=$(curl -s http://baidu.com | grep "NextURL")
if [[ ${KEYWORD} != "" ]]; then
  logger "检测到尚未认证,尝试自动认证"
  CURRENT_IP=$(ifconfig | grep inet | grep -v inet6 | grep -v 127 | grep -v 192 | awk '{print $(NF-2)}' | cut -d ':' -f2)
  MAC_ADDRESS=$(ifconfig ra0 | grep HWaddr | awk '{print $NF}' | tr '[:upper:]' '[:lower:]' | tr ':' '-')
  LOGIN_STATUS=$(curl -s -X POST "http://172.25.249.8/eportal/InterFace.do?method=login" -H "Connection: keep-alive" -H "Origin: http://172.25.249.8" -H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.79 Safari/537.36" -H "DNT: 1" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Accept: */*" -H "Referer: http://172.25.249.8/eportal/index.jsp?userip=${CURRENT_IP}&wlanacname=&nasip=171.88.130.251&wlanparameter=${MAC_ADDRESS}&url=http://baidu.com/&userlocation=ethtrunk/3:691.3201" -H "Accept-Encoding: gzip, deflate" -H "Accept-Language: zh-CN,zh;q=0.9,zh;q=0.8,en;q=0.7" -H "Cookie: EPORTAL_COOKIE_OPERATORPWD=; EPORTAL_COOKIE_USERNAME=; EPORTAL_COOKIE_PASSWORD=; EPORTAL_COOKIE_SERVER=; EPORTAL_COOKIE_SERVER_NAME=; EPORTAL_AUTO_LAND=; EPORTAL_USER_GROUP=null; JSESSIONID=2B36EA2F20A0CE7361D592CE7DBDFED3" --data "userId=<宽带账号>&password=<宽带密码>&service=&queryString=userip%253D${CURRENT_IP}%2526wlanacname%253D%2526nasip%253D171.88.130.251%2526wlanparameter%253D${MAC_ADDRESS}%2526url%253Dhttp%253A%252F%252Fbaidu.com%252F%2526userlocation%253Dethtrunk%252F3%253A691.3201&operatorPwd=&operatorUserId=&validcode=&passwordEncrypt=false")  # 含有变量时只能使用双引号
  SUCCESS=$(echo ${LOGIN_STATUS} | grep success)
  if [[ ${SUCCESS} != "" ]]; then
    logger "自动认证成功"
  else
    LOGIN_STATUS=$(echo ${LOGIN_STATUS} | cut -d ',' -f3 | cut -d '"' -f4)
    logger "自动认证失败: ${LOGIN_STATUS}"
  fi
else
  logger "检测到已经认证"
fi

# 记得修改宽带账号、宽带密码

在脚本开始的时候,我们通过 curl -s http://baidu.com 来检测是否已经通过认证。如果尚未认证,curl 返回的结果如下:

$ curl -s http://baidu.com  # -s, 不输出任务进度

<!--
<?xml version="1.0" encoding="UTF-8"?>
<WISPAccessGatewayParam xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.acmewisp.com/WISPAccessGatewayParam.xsd">
<Proxy>
<MessageType>110</MessageType>
<NextURL>http://172.25.249.8/eportal/index.jsp?userip=<内网IP>&wlanacname=&nasip=171.88.130.251&wlanparameter=<MAC地址></NextURL>
<ResponseCode>200</ResponseCode>
</Proxy>
</WISPAccessGatewayParam>
-->

如果已经认证,curl 返回的结果如下:

$ curl -s http://baidu.com

<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>

我们可以观察到,在尚未认证时,返回的结果中含有关键词 NextURL(当然,其他与已认证时返回结果不同的内容都可以)。因此,我们使用 KEYWORD 变量来判断认证状态,如果尚未认证,KEYWORD 就不为空,反之 KEYWORD 就为空。

因此,你可以在认证前、认证后分别执行 curl http://baidu.com,找到前者含有而后者没有的特征性内容,将其放在 grep 命令后。

如果尚未认证,脚本就会使用 curl 发送 HTTP-POST 请求进行认证,并检查返回的结果中是否有 success 字样。如果有,则代表认证成功;如果没有,脚本则会记录下返回的提示信息。

将脚本上传到路由器

打开 macOS 终端,使用 ssh 连接到路由器:

ssh admin@192.168.123.1  # Padavan 的默认网关 IP 为 192.168.123.1,用户名为 admin

输入默认密码 admin 即可 ssh 登录到路由器。随后执行以下命令:

cd /etc/storage  # 进入存储脚本的目录

vi auto_login.sh  # 新建并编辑自动登录脚本

在 vi 编辑器下,按 i 进入编辑模式,将之前准备好的脚本粘贴上去,然后按 esc 退出编辑模式,随后输入 :wq 并回车即可保存。再执行以下命令赋予脚本执行权限:

chmod +x auto_login.sh

完成后,执行 exit 即可断开 ssh 连接。

登录到路由器后台,在 系统管理 > 恢复/导出/上传设置 > 保存 /etc/storage/ 内容到闪存 后点击 提交

保存脚本到路由器闪存

最后,在 自定义设置 > 脚本 > 在 WAN 上行/下行启动后执行 的内容后添加一行:/etc/storage/auto_login.sh,并点击页面最下方的 应用本页面设置 即可。

设置 WAN 启动后自动认证

小结

此时,你的路由器应该已经可以自动完成校园有线网的认证过程了。对于晚上要断电的学校(🌚比如我校),再购买一个路由器 UPS 即可真正实现 24*7 不间断 WiFi 供应。

然而,如果你的寝室断电后有线网络信号也会被切断,那么你可以利用 Padavan 的 crontab 在断电时切换到无线中继模式,并中继开放的校园 WiFi。对于我校而言,中继电信校园 WiFi 时,上述自动认证脚本一样可用。具体的实现过程,此处就不再赘述。

🌝什么?你说校园 WiFi 也会被关闭?那你为什么不老老实实用流量呢?


Update: 得益于上面提到的 NextURL 字段,其实获取内网 IP 与 MAC 地址也可以这么写,这样 macOS 和 Padavan 都能用:

CURRENT_IP=$(curl -s http://baidu.com | grep NextURL | cut -d '/' -f5 | cut -d '&' -f1 | cut -d '?' -f2 | cut -d '=' -f2)

MAC_ADDRESS=$(curl -s http://baidu.com | grep NextURL | cut -d '/' -f5 | cut -d '&' -f4 | cut -d '=' -f2 | tr -d '<')

另外还有一点,把 queryString 中两次 URL encode 过的字段再 decode 回来其实也是能成功的,而且如果要用 iOS Shortcuts 来完成自动化认证的话还必须先 decode 回来。

在参考了少数派的 这篇文章 后,咱也用 Shortcuts 实现了 iOS 设备上的自动认证,你可以 点这里 安装这个 Shortcut。但是 iOS Shortcuts 做不到抓取 NextURL 也没法直接获得 MAC 地址,所以实现上不是很完美。


参考及感谢

↩︎ 注

  1. %253D 进行两次 URL decode 后即可得到 =