要把看不见的星星写在纸上~

锐捷自动认证 & 交叉编译至mipsel

最近咱们实验室搞到了那么点经费,于是趁这个机会,就给换了一个路由器:红米AC2100🥳(原来那个路由器才百兆,局域网传文件实在太慢了🤬)。
我一直想做一个程序,用于自动登录学校的校园网,不然每隔一段时间就得手动登录一下,很是麻烦。之前的那个路由器上完全没内存安装一些库了,所以这个idea一直没落实,既然现在换了,那就肯定要把这个坑填上!!红米AC2100自带的系统并不好用,还是得给它刷个OpenWRT,然后再跑上我的程序~所以就有了这么一篇文章。

1. 刷入OpenWRT

具体刷机方法可以参考这篇文章:红米AC2100刷机Padavan固件全图解,超超超详细_路由器_什么值得买,只不过把Padavan固件换成OpenWRT而已,至于固件我采用的是这个:AC2100 Openwrt,闭源无线+多拨+科学+SDK硬件加速驱动+交换机,breed直刷-小米无线路由器以及小米无线相关的设备-恩山无线论坛

2. 锐捷认证

我们学校采用的是锐捷ePortal Web认证,界面长这样:

2.1 分析过程

既然是网页认证,那肯定还是打开F12抓包分析:

在我点击登录按钮之后,可以看到网页向http://[锐捷eportal-web的IP]/eportal/InterFace.do?method=login发出了一个POST请求,携带了八个参数:

参数名作用
userId用户名
password密码
service运营商
queryString查询参数
operatorPwd不清楚
operatorUserId不清楚
validcode验证码
passwordEncrypt密码字段是否加密

在我们学校,用户名userId字段默认是学号,password是密码,可以加密也可以不加密,取决于最后一个passwordEncrypt字段,如果为true,则password字段需要传递加密后的密码;如果为false,则可以直接传递明文密码。

service字段是运营商选择参数,但我们学校没有可选项,所以该字段也为空。

queryString是查询参数,由未登录时任何一个网页经过301重定向后url中所携带参数,根据参数里面包含的字符,猜测应该含有网络连接方式,用户端口等参数,反正在登录时,直接把301重定向后url中这个字段里的所有参数都传过去就完事儿。

operatorPwdoperatorUserId不知道是做什么用的,反正在我们学校,这两个字段为空。

validcode为验证码,默认为空,若多次登录失败,则需要验证码。关于这个验证码如何跳过,最开始我还以为只能通过机器学习去识别,后来我发现只要把cookies清掉就可以了哈哈哈哈哈。

2.2 自动认证

基于以上的分析,可以写出相应的代码,以完成自动登录,然后交叉编译至ramips架构的二进制代码文件,方便直接执行。

以下是c++代码,其中,curl.h调用的是系统中的curl库,至于怎么安装请自行百度;然后ylog.h是这个项目GitHub - ywsswy/ylog: C++ 轻量级日志类

#include <iostream>
#include <string>
#include <curl/curl.h>
#include "ylog.h"

using namespace std;

// 网络状态检测地址,正常会返回204
const char *captiveServerUrl = "http://connectivitycheck.platform.hicloud.com/generate_204";
// 包含userIndex的URL地址
const char *redirectToSuccessUrl = "http://[锐捷eportal-web的IP]/eportal/redirectortosuccess.jsp";
// 登出地址
const char *logoutUrl = "http://[锐捷eportal-web的IP]/eportal/InterFace.do?method=logout";

// 日志系统,创建日志对象log1,如果文件存在则追加,日志输出下限级别为INFO级别
YLog log1(YLog::INFO, "ruijie.log", YLog::ADD);

// 写日志函数
void writeLog(YLog::Level level, const char *title, const char *content)
{
    cout << "[" << level << "]" << title << " " << content << endl;
    log1.W(__FILE__, __LINE__, YLog::INFO, title, content);
}

// 输出使用帮助
void printUsage()
{
    cout << "\t-----------------脚本使用帮助-----------------" << endl;
    cout << "\t账号登录: $ ./ruijie login [学号] [密码]" << endl;
    cout << "\t账号下线: $ ./ruijie logout" << endl;
    cout << "\t--------------------------------------------" << endl;
    cout << "\t使用样例: $ ./ruijie login 20201101381 123456" << endl;
    cout << "\t----------最后更新时间:2022年11月23日----------" << endl;
}

// 替换字符串
string replaceAll(string str, const string &from, const string &to)
{
    size_t start_pos = 0;
    while ((start_pos = str.find(from, start_pos)) != string::npos)
    {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length();
    }
    return str;
}

// CURL输出回调
size_t noPrintWriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
    return size * nmemb;
}
size_t writeCallback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
    ((string *)userdata)->append((char *)ptr, size * nmemb);
    return size * nmemb;
}

// 从html主体中获取登陆页URL
string getLoginPageUrlFromHtmlResp(const string &htmlResp)
{
    auto leftPosition = htmlResp.find_first_of('\'');
    auto rightPosition = htmlResp.find_last_of('\'');
    return htmlResp.substr(leftPosition + 1, rightPosition - leftPosition - 1);
}

// 从html主体中获取queryString字段
string getQueryStringFromHtmlResp(string &htmlResp)
{
    auto leftPosition = htmlResp.find_first_of('?');
    auto rightPosition = htmlResp.find_last_of('\'');
    string queryString = htmlResp.substr(leftPosition + 1, rightPosition - leftPosition - 1);
    queryString = replaceAll(queryString, "&", "%2526");
    queryString = replaceAll(queryString, "=", "%253D");
    return queryString;
}

// 从登陆页URL替换为登录URL
string getLoginUrlFromLoginPageUrl(const string &loginPageUrl)
{
    string baseUrl = loginPageUrl.substr(0, loginPageUrl.find_first_of('?'));
    baseUrl.replace(baseUrl.find("index.jsp"), strlen("index.jsp"), "InterFace.do?method=login");
    return baseUrl;
}

// 获取返回的resp主体
string getResponseContent(const char *url)
{
    CURL *curl;
    CURLcode curlCode;
    string responseContent;
    curl = curl_easy_init();
    if (curl)
    {
        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &responseContent);
        curlCode = curl_easy_perform(curl);
        if (curlCode != CURLE_OK)
        {
            writeLog(YLog::ERR, "获取报文主体失败", "错误代码为:" + (int)curlCode);
            exit(EXIT_FAILURE);
        }
        curl_easy_cleanup(curl);
    }
    return responseContent;
}

// 获取返回的resp状态码
long getResponseStatusCode(const char *url)
{
    CURL *curl;
    CURLcode curlCode;
    long responseCode = -1;
    curl = curl_easy_init();
    if (curl)
    {
        curl_easy_setopt(curl, CURLOPT_URL, url);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, noPrintWriteCallback);
        curlCode = curl_easy_perform(curl);
        if (curlCode != CURLE_OK)
        {
            writeLog(YLog::ERR, "获取状态码失败", "错误代码为:" + curlCode);
            exit(EXIT_FAILURE);
        }
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
        curl_easy_cleanup(curl);
    }
    return responseCode;
}

// 获取userIndex参数
string getUserIndex()
{
    char *redirectUrl = nullptr;
    CURL *curl;
    CURLcode curlCode;
    curl = curl_easy_init();
    if (curl)
    {
        curl_easy_setopt(curl, CURLOPT_URL, redirectToSuccessUrl);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, noPrintWriteCallback);
        curlCode = curl_easy_perform(curl);
        if (curlCode != CURLE_OK)
        {
            writeLog(YLog::ERR, "获取userIndex参数失败", "错误代码:" + curlCode);
            exit(EXIT_FAILURE);
        }
        curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &redirectUrl);
        curl_easy_cleanup(curl);
    }
    string redirectUrlString = redirectUrl;
    int tempIndex = redirectUrlString.find("userIndex=");
    redirectUrlString = redirectUrlString.substr(tempIndex + strlen("userIndex="), redirectUrlString.length() - tempIndex);
    return redirectUrlString;
}

// 登录函数
string login(const char *loginUrl, const char *username, const char *password, const char *queryString)
{
    CURL *curl;
    CURLcode curlCode;
    string loginResult;
    struct curl_slist *chunk = nullptr;
    chunk = curl_slist_append(chunk, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
    chunk = curl_slist_append(chunk, "Content-Type: application/x-www-form-urlencoded; charset=UTF-8");
    chunk = curl_slist_append(chunk, "Connection: keep-alive");
    chunk = curl_slist_append(chunk, "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36");

    // wlanuserip=bcb71eb76818d6fd92d42f6dd330725e&wlanacname=78f5e1bb58de90862264a5f9eaa10d06&ssid=&nasip=42beac14e4157147e09fdc2261164ec7&snmpagentip=&mac=6ca169d6cc0f0b34571e5ec76fba0ab1&t=wireless-v2&url=9504afad491426cb983532cb77b7524bd122a40a26888d9f&apmac=&nasid=78f5e1bb58de90862264a5f9eaa10d06&vid=ea88a2a97d828bd6&port=2f248c05d37ef059&nasportid=27ede84ceae69256fb564ff0fa173912654948819faa589a9fd0f695672b62738cade7087701681d
    // 101 is the length of "userId=&password=&service=&queryString=&operatorPwd=&operatorUserId=&validcode=&passwordEncrypt=false"
    char *postParam = (char *)malloc(sizeof(char) * (101 + strlen(username) + strlen(password) + strlen(queryString) + 1));
    sprintf(postParam, "userId=%s&password=%s&service=&queryString=%s&operatorPwd=&operatorUserId=&validcode=&passwordEncrypt=false", username, password, queryString);
    curl = curl_easy_init();
    if (curl)
    {
        curl_easy_setopt(curl, CURLOPT_URL, loginUrl);
        curl_easy_setopt(curl, CURLOPT_POST, 1);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postParam);
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &loginResult);
        curl_easy_setopt(curl, CURLOPT_COOKIE, "EPORTAL_COOKIE_USERNAME=; EPORTAL_COOKIE_PASSWORD=; EPORTAL_COOKIE_SERVER=; EPORTAL_COOKIE_SERVER_NAME=; EPORTAL_AUTO_LAND=; EPORTAL_USER_GROUP=; EPORTAL_COOKIE_OPERATORPWD=;");
        curlCode = curl_easy_perform(curl);
        if (curlCode != CURLE_OK)
        {
            writeLog(YLog::ERR, "登陆失败", "错误代码:" + curlCode);
            exit(EXIT_FAILURE);
        }
        curl_easy_cleanup(curl);
    }
    return loginResult;
}

// 退出账号
string logout(const string &userIndex)
{
    CURL *curl;
    CURLcode curlCode;
    string logoutResult;
    string postFieldsString = "userIndex=";
    postFieldsString += userIndex;
    char *postFields = (char *)malloc(sizeof(char) * (postFieldsString.length() + 1));
    strcpy(postFields, postFieldsString.c_str());
    curl = curl_easy_init();
    if (curl)
    {
        curl_easy_setopt(curl, CURLOPT_URL, logoutUrl);
        curl_easy_setopt(curl, CURLOPT_POST, 1);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postFields);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &logoutResult);
        curl_easy_setopt(curl, CURLOPT_COOKIE, "EPORTAL_COOKIE_USERNAME=; EPORTAL_COOKIE_PASSWORD=; EPORTAL_COOKIE_SERVER=; EPORTAL_COOKIE_SERVER_NAME=; EPORTAL_AUTO_LAND=; EPORTAL_USER_GROUP=; EPORTAL_COOKIE_OPERATORPWD=;");
        curlCode = curl_easy_perform(curl);
        if (curlCode != CURLE_OK)
        {
            writeLog(YLog::ERR, "退出账号", "发送退出账号请求失败,错误代码:" + curlCode);
            exit(EXIT_FAILURE);
        }
        curl_easy_cleanup(curl);
    }
    delete postFields;
    return logoutResult;
}

//主函数
int main(int argc, char **argv)
{
    if (argc == 1)
    {
        writeLog(YLog::INFO, "参数错误", "参数错误,请输入-help查看使用说明");
        return EXIT_FAILURE;
    }

    char *futureSelect = argv[1];
    if (strcmp(futureSelect, "-login") == 0)
    {
        // 如果是登录
        if (argc < 4)
        {
            writeLog(YLog::INFO, "参数错误", "参数错误,请输入-help查看使用说明");
            return EXIT_FAILURE;
        }
        // 检查网络状态
        long captiveServerStatusCode = getResponseStatusCode(captiveServerUrl);
        if (captiveServerStatusCode == -1)
        {
            writeLog(YLog::ERR, "登陆失败", "不能初始化curl,检查一下系统中是否安装了curl");
            return EXIT_FAILURE;
        }
        else if (captiveServerStatusCode == 204)
        {
            writeLog(YLog::INFO, "登陆成功", "您已经在线咯");
            return EXIT_SUCCESS;
        }
        else
        {
            char *username = argv[2];
            char *password = argv[3];
            string captiveServerResponseContent = getResponseContent(captiveServerUrl);
            string loginPageUrl = getLoginPageUrlFromHtmlResp(captiveServerResponseContent);
            string queryString = getQueryStringFromHtmlResp(captiveServerResponseContent);
            string loginUrl = getLoginUrlFromLoginPageUrl(loginPageUrl);
            string loginResult = login(loginUrl.c_str(), username, password, queryString.c_str());
            writeLog(YLog::INFO, "登录", loginResult.c_str());
            return EXIT_SUCCESS;
        }
    }
    else if (strcmp(futureSelect, "-logout") == 0)
    {
        // 如果是登出
        long captiveServerStatusCode = getResponseStatusCode(captiveServerUrl);
        if (captiveServerStatusCode == 204)
        {
            string userIndex = getUserIndex();
            string logoutResult = logout(userIndex);
            writeLog(YLog::INFO, "下线", logoutResult.c_str());
        }
        else
        {
            writeLog(YLog::INFO, "下线失败", "你还没登录咧");
        }
        return EXIT_SUCCESS;
    }
    else if (strcmp(futureSelect, "-help") == 0)
    {
        printUsage();
        return EXIT_SUCCESS;
    }
    else
    {
        writeLog(YLog::INFO, "参数错误", "参数错误,请输入-help查看使用说明");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

2.3 基本使用 & 工作原理

  1. 根据传递参数,选择对应功能。

    1. -help为获取帮助信息。
    2. -login [学号] [密码]为登录校园网,后面跟随两个参数分别是学号和密码。
    3. -logout为下线校园网账号。
  2. 登录步骤:

    1. 检查当前网络连接状态,首先向网络状态检测服务器http://connectivitycheck.platform.hicloud.com/generate_204发送请求,正常情况已联网的情况下是返回204状态码,但如果校园网未登录,则会返回301302状态,用来把你的页面重定向到认证页面http://[锐捷eportal-web的IP]/eportal/index.jsp?wlanuserip=xxxxxx...,我们就是需要这个认证url中的参数部分(问号后面的部分):wlanuserip=xxxxx&wlanacname=xxxxx&ssid=&nasip=xxxxx.....,把这部分作为登录POST请求中的的queryString字段的值。
    2. 拼接字符串,构造HTTP请求的头部参数,然后向http://[锐捷eportal-web的IP]/eportal/InterFace.do?method=login发出POST请求,然后自行判断返回的json结果既可,若登录成功,json中result字段会是success,反之为fail,然后可以在message字段中看到失败原因,比如"密码不匹配,请输入正确的密码!"啊啥啥啥的。
    3. 注意:向认证url发出POST请求时,必须携带Cookies,就算Cookies的值为空也没事,不然会显示设备未认证还是啥的。
  3. 下线步骤:

    1. http://[锐捷eportal-web的IP]/eportal/redirectortosuccess.jsp发出请求,若当前已联网(用户已登陆),则会302重定向到http://[锐捷eportal-web的IP]/eportal/success.jsp?userIndex=xxxxxxx,我们需要的就是最后这个userIndex参数,将这个参数取出来。
    2. http://[锐捷eportal-web的IP]/eportal/InterFace.do?method=logout发出POST请求,携带参数为上面获取到的userIndex,请求头也需要带上cookies,若请求状态码为200,则下线成功。

3. 编译二进制可执行文件

需要先在系统里安装curl和CMake。

如果是MacOS系统,系统自带了curl,只需要安装CMake即可,到cmake官网下载即可。

如果是Linux系统,比如Ubuntu,可使用sudo apt install curl libcurl4-openssl-dev来安装curl,cmake也是到cmake官网下载即可。

如果是WIndows系统,我也不太清楚,百度吧~

注意:以上指的都是编译机器上需要安装的,不是目标机器上要安装的!!!

3.1 本地编译

首先切换到任意一个目录,将当前目录作为你的工作目录。

将上面的C++代码建立为main.cpp文件,再新建一个CMakeLists.txt文件。

再将下面的代码写入CMakeLists.txt中:

# 指定cmake最小版本
cmake_minimum_required(VERSION 3.14)
# 设置项目名称
project(wsyu_ruijie)

# 设置c++版本
set(CMAKE_CXX_STANDARD 14)

# 设置curl库
set(CURL_LIBRARY "-lcurl")
find_package(CURL REQUIRED)

# 添加编译参数,忽略警告,设置可执行文件
add_compile_options(-o2)
add_definitions(-w)
add_executable(wsyu_ruijie main.cpp)

# 设置搜索库文件路径,目标链接库
include_directories(${CURL_INCLUDE_DIR})
target_link_libraries(wsyu_ruijie ${CURL_LIBRARIES})

再在当前目录新建一个build文件夹,此时,你的当前文件目录结构应该如下图所示:

├── CMakeLists.txt

├── build

├── main.cpp

└── ylog.h

打开命令行(终端terminal),执行下面的指令:

cd build
cmake ..
make

在Linux中,如果报错string.h未找到,那就改成cstring.h就行。

如果出现其他问题,善用百度,好多报错都是因为包没找到,需要你手动apt install一下。

如果一切正常的话,你的build文件夹下应该就已经有一个没有后缀名的二进制可执行文件wsyu_ruijie了。

3.2 交叉编译

这才是重点,毕竟最终代码是要跑到路由器上的,在自己电脑上编译的一般都是x86架构才能跑得起来,而我手头这个红米AC2100路由器是mipsel架构,这个时候就要交叉编译了。

交叉编译,简而言之就是在一个架构的电脑上,通过编译工具链,生成另外一个架构的可执行二进制文件。

交叉编译流程:首先需要编译curl,然后再编译上面的C++代码。

3.2.1 下载交叉编译工具链

根据自己OpenWRT的版本以及目标设备(路由器)的架构去选择相应的编译工具链,比如我上面的固件是基于ImmortalWrt 18.06-5.4-SNAPSHOT进行编译的,ImmortalWRT是OpenWRT的一个分支,根据Github上的描述,是一个更适合大陆用户的分支,所以,我们需要找这个分支上的编译工具链。首先,在Github上找到他的下载地址:https://downloads.immortalwrt.org/,在里面翻翻,因为我的目标机器(路由器)是ramips/mt7621架构,固件是ImmortalWrt 18.06-5.4-SNAPSHOT的版本,所以可以找到以下页面:https://downloads.immortalwrt.org/releases/18.06-k5.4-SNAPSHOT/targets/ramips/mt7621/,然后翻到最底下,可以找到immortalwrt-sdk-18.06-k5.4-SNAPSHOT-ramips-mt7621_gcc-8.4.0_musl.Linux-x86_64.tar.xz这个文件,这个就是我们想要的编译工具链了:

通过wget下载它到Ubuntu上,然后解压:

wget https://downloads.immortalwrt.org/releases/18.06-k5.4-SNAPSHOT/targets/ramips/mt7621/immortalwrt-sdk-18.06-k5.4-SNAPSHOT-ramips-mt7621_gcc-8.4.0_musl.Linux-x86_64.tar.xz
tar -xvf immortalwrt-sdk-18.06-k5.4-SNAPSHOT-ramips-mt7621_gcc-8.4.0_musl.Linux-x86_64.tar.xz

完成后,你会在当前文件夹看到一个已解压的SDK压缩包,文件夹里面长这样:

3.2.2 配置环境变量

现在,该配置环境变量了,由于我电脑里面用的是zsh,所以我得编辑.zshrc文件:

vim ~/.zshrc

如果你是用的是bash,那就把上面的~/.zshrc换成/etc/bash.profile进行编辑即可。

在配置文件中加入以下两行,SDK路径请根据自己的情况进行配置:

export PATH="/home/yili/SDK/immortalwrt-sdk-18.06-k5.4-SNAPSHOT-ramips-mt7621_gcc-8.4.0_musl.Linux-x86_64/staging_dir/toolchain-mipsel_24kc_gcc-8.4.0_musl/bin:$PATH"
export STAGING_DIR="/home/yili/SDK/immortalwrt-sdk-18.06-k5.4-SNAPSHOT-ramips-mt7621_gcc-8.4.0_musl.Linux-x86_64/staging_dir/toolchain-mipsel_24kc_gcc-8.4.0_musl"

如下图所示:

配置完成后记得刷新一下环境变量:

source ~/.zshrc

如果配置后一切正常,输入mipsel-openwrt-linux-g++会输出以下内容:

那么你的环境变量就配置成功了。

3.2.3 交叉编译CURL

因为我的c++代码里面使用到了curl,所以必须要手动编译一遍curl源码。毕竟最后代码是要跑在路由器上的,所以静态链接库xxx.a和动态链接库(共享库)xxx.so啥的都得手动编译。

curl的Github地址是这个:GitHub - curl/curl: A command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, POP3, POP3S, RTMP, RTMPS, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET and TFTP. libcurl offers a myriad of powerful features,点进去,然后再release页面选择对应的版本进行下载,我选的是7.86.0的版本,右键复制链接地址即可,然后到Ubuntu里面使用wget进行下载,随后进行解压:

wget https://github.com/curl/curl/releases/download/curl-7_86_0/curl-7.86.0.tar.xz
tar -xvf curl-7.86.0.tar.xz

解压后,进入文件夹,新建一个shell脚本,用于配置交叉编译:

cd ./curl-7.86.0
vim ./cross_compile.sh

将以下内容写入cross_compile.sh

#!/bin/bash

OUTPUT=$(pwd)/output
if [ -d $OUTPUT ]; then
    rm -r $OUTPUT
fi
mkdir $OUTPUT

./configure --prefix=$OUTPUT --host=mipsel-openwrt-linux --without-ssl

make && make install

其中,$OUTPUT变量作为编译输出文件的存放位置;--prefix=$OUTPUT为最终编译输出文件路径;--host=mipsel-openwrt-linux为指定交叉编译工具链命令的前缀,在生成的Makefile文件中会将需要使用的编译工具自动补全为mipsel-openwrt-linux-g++mipsel-openwrt-linux-ar等;--without-ssl指的是忽略ssl库部分,因为代码中并未使用到ssl,所以我并不需要编译这部分,如果你们学校锐捷认证需要使用到ssl(应该不会吧),你还需手动交叉编译OpenSSL,具体请自行百度。

你需要修改的就是--host=xxxx,这个需要根据你自己交叉编译工具链的前缀进行修改,至于怎么找,你就去你上面配置的SDK文件夹中的/staging_dir/toolchain-xxxxxx/bin目录中找,看看这些可执行文件都是啥开头的。

上述配置完成后,给执行权限,然后执行这个shell脚本:

chmod +x ./cross_compile.sh
./cross_compile.sh

执行完毕之后,应该是下面这样的:

目录下也应该会出现一个output文件夹,里面会有四个目录binincludelibshare,里面就包含了我们所需要的头文件,静态链接库动态链接库等等。

然后复制output下的所有文件到SDK目录下的/staging_dir/toolchain-xxxx/usr目录下,用于下一步交叉编译make自动寻找库文件:

cp -r ./output/* /home/yili/SDK/immortalwrt-sdk-18.06-k5.4-SNAPSHOT-ramips-mt7621_gcc-8.4.0_musl.Linux-x86_64/staging_dir/toolchain-mipsel_24kc_gcc-8.4.0_musl/usr

3.2.4 交叉编译C++代码

终于到最后一步了,首先切换到任意一个目录,将当前目录作为你的工作目录。

将上面的C++代码建立为main.cpp文件,再新建一个CMakeLists.txt文件。

再将下面的代码写入CMakeLists.txt中:

# cmake最低版本
cmake_minimum_required(VERSION 3.14)
# 项目名
project(wsyu_ruijie)

# 设置目标系统类型,让cmake知道这是在交叉编译
set(CMAKE_SYSTEM_NAME Linux)
# 设置目标系统的处理器架构
set(CMAKE_SYSTEM_PROCESSOR mipsel_24kc)

# 设置交叉编译工具链目录
SET(TOOLCHAIN_DIR "/home/yili/SDK/immortalwrt-sdk-18.06-k5.4-SNAPSHOT-ramips-mt7621_gcc-8.4.0_musl.Linux-x86_64/staging_dir/toolchain-mipsel_24kc_gcc-8.4.0_musl")
# c编译器路径
set(CMAKE_C_COMPILER ${TOOLCHAIN_DIR}/bin/mipsel-openwirt-linux-gcc)
# c++编译器路径
set(CMAKE_CXX_COMPILER ${TOOLCHAIN_DIR}/bin/mipsel-openwrt-linux-g++)
# ar路径
set(CMAKE_AR ${TOOLCHAIN_DIR}/bin/mipsel-openwrt-linux-gcc-ar)
# 设置c++版本
set(CMAKE_CXX_STANDARD 14)

# 让cmake从哪个目录开始寻找环境
SET(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN_DIR})

# 一些cmake模式配置,具体百度
SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)                                    
SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)                                     
SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

# 设置寻找curl库
find_package(CURL REQUIRED)

# 可执行文件信息
add_executable(wsyu_ruijie main.cpp)

# 编译参数,忽略警告
add_compile_options(-o2)
add_definitions(-w)

# 设置curl库
set(CURL_LIBRARY "-lcurl")


# 从以下目录寻找,这对应的是上一步交叉编译curl中最后复制头文件,静态链接库动态链接库到sdk中的路径
include_directories(${TOOLCHAIN_DIR}/usr/include)
link_directories(${TOOLCHAIN_DIR}/usr/lib)
# 设置目标链接库
target_link_libraries(wsyu_ruijie ${CURL_LIBRARY})

再在当前目录新建一个build文件夹,此时,你的当前文件目录结构应该如下图所示:

├── CMakeLists.txt

├── build

├── main.cpp

└── ylog.h

打开命令行(终端terminal),执行下面的指令:

cd build
cmake ..
make

在Linux中,如果报错string.h未找到,那就改成cstring.h就行。

如果出现其他问题,善用百度,好多报错都是因为包没找到,需要你手动apt install一下。

如果一切正常的话,你的build文件夹下应该就已经有一个没有后缀名的交叉编译后的二进制可执行文件wsyu_ruijie了。

3.3 上传可执行文件到路由器

交叉编译后的文件肯定是不可能在你自己的电脑上运行起来的,因为一般自己的电脑都是x86架构,而你的路由器不会是x86,所以需要上传到路由器进行测试。

你可以使用scp上传文件,也可以使用OpenWRT网页中的“文件传输”功能,我比较喜欢scp,因为比较方便~

在上一步中编译出来的文件目录:xxx/build/这个文件夹下,执行:

scp ./wsyu_ruijie root@192.168.1.1:/root

上面指令的意思是,将当前目录下的wsyu_ruijie文件复制到路由器的/root目录下,路由器的IP为:192.168.1.1,登录用户名为root

在执行了上面指令之后,需要你输入root用户的密码,如果一切正常,会显示下面的这个界面:

随后,使用ssh登录路由器:

ssh root@192.168.1.1

输入密码成功登陆后,可以开始测试脚本是否可以正常使用了。

4. 脚本使用

首先,给可执行权限:

chmod +x ./wsyu_ruijie

以下是脚本的基本使用方法:

  • -help为获取帮助信息。
  • -login [学号] [密码]为登录校园网,后面跟随两个参数分别是学号和密码。
  • -logout为下线校园网账号。

4.1 登录校园网

在终端执行下面的指令:

./wsyu_ruijie -login 20201111000 123456

意思就是,以学号20201111000,密码123456登录校园网。

执行后会返回一段json,如果result字段的值为success就是成功:

{
    "userIndex":"xxxxxxx",
    "result":"success",
    "message":"",
    "forwordurl":null,
    "keepaliveInterval":0,
    "validCodeUrl":""
}

如果result字段的值为fail则失败,message字段就是登录失败的原因:

{
    "userIndex":null,
    "result":"fail",
    "message":"登录失败的原因",
    "forwordurl":null,
    "keepaliveInterval":0,
    "validCodeUrl":""
}
如果二进制文件无法执行,善用百度谷歌,实在不行可以在底下留言,我看到后会,如果我能帮上忙会回复的。

4.2 下线校园网

在终端执行下面的指令:

./wsyu_ruijie -logout

会返回一段json,如果result字段为success则下线成功:

{
    "result":"success",
    "message":"下线成功!"
}

4.3 运行日志

每次执行都会记录运行日志,在与脚本文件同目录下会有个ruijie.log文件,里面就是运行日志了。

4.4 定时执行

这里需要用到Linux中的Crontab,它可以让你在指定的时间执行一个shell脚本或者一系列Linux命令。在LuCI界面(路由器后台管理页面)中,也有“计划任务”这个界面,这个界面更方便咱们配置Crontab:

以下是 Crontab 命令的格式:

{minute} {hour} {day-of-month} {month} {day-of-week} {full-path-to-shell-script}

  • minute: 区间为0-59;
  • hour: 区间为0-23;
  • day-of-month: 区间为0-31;
  • month: 区间为1-12,1 是1月,12是12月;
  • Day-of-week: 区间为0-7,周日可以是0或7;

在LuCI页面(路由器后台管理页面)中,我们需要配置一行指令,用于定时执行自动认证的脚本:

0 6 * * * /root/wsyu_ruijie -login 20201111000 123456

上面的指令就代表着:每天早上6点执行一次/root/wsyu_ruijie -login 20201111000 123456指令,也就是每天早上六点尝试自动登录一次。

注意:在这里,脚本路径必须是绝对路径,不能是相对路径!!!

5. 后记

这还是我第一次用C++写脚本,一开始我打算用Python写,毕竟Python在写脚本方面确实简单,用requests库十几行代码就解决了,但测试中发现,路由器内存实在太小,无法安装Python环境,遂决定采用我不是那么熟的C++。总而言之,以后咱们实验室终于可以自动联网啦,芜湖~

C++代码方面,我是参考了一位来自集美大学(或者是福州大学?)的大佬的代码,但由于那个代码是我很久很久以前保存的,现在在Github上已经搜不到了,好像这个项目也是他写的?GitHub - LGiki/jmuSupplicant: 适用于集美大学的第三方锐捷认证客户端,在里面我也看到了他写的论文,分析得是真的详细,太厉害了!!!真的很佩服,在此感谢一下他的项目。

另外,如果对于Cmake交叉编译不太了解的话,可以参考一下这篇Wiki:CrossCompiling · Wiki · CMake / Community · GitLab,对于我这个第一次搞交叉编译的,帮我解决了不少问题,芜湖~

锐捷自动认证 & 交叉编译至mipsel

https://blog.mapotofu.cn/archives/ruijie_auto_auth_and_cross_compile_to_mipsel.html

作者

麻婆豆腐

发布时间

2022-11-29

添加新评论