opencapwap简要分析

注:分析开源代opencapwap,其代码分为三个部分WTP端,AC端和WUA。这里我不对WUA进行分析,因为我并没使用到它。

AC :

int main (int argc, const char * argv[]) {
    /* Daemon mode */
    if (argc <= 1)
        printf("Usage: AC working_path\n");

    if (chdir(argv[1]) != 0) {
        printf("chdir fail\n");
        exit(1);
    }
    CWACInit(); //读取配置文件,初始化连接,时钟,锁等等
    CWCreateConnectionWithHostapdAC();//与hostapd交互的初始化
    CWACEnterMainLoop();//主循环
    CWACDestroy();  //销毁程序
    return 0;
}

篇幅过长的,都会选择一些主要的文件进行解析,下面都一样。

void CWACInit(){
    ...
    CWLogInitFile(AC_LOG_FILE_NAME); //log文件设置
    ...
    if(!CWParseSettingsFile()) //解析读取设置文件 settings.ac.txt
    ...
    if(!CWErr(CWParseConfigFile()) || //解析配置文件 config.ac
#ifndef CW_NO_DTLS
       !CWErr(CWSecurityInitLib()) || //使用的安全库初始化
#endif
       !CWErr(CWNetworkInitSocketServerMultiHomed(&gACSocket, CW_CONTROL_PORT, gMulticastGroups, gMulticastGroupsCount)) || // 建立capwap协议的控制和数据,以及广播的socket
       !CWErr(CWNetworkGetInterfaceAddresses(&gACSocket, &addresses, &IPv4Addresses)) || //获取创建的socket的网络地址
       !CWErr(CWCreateThreadMutex(&gWTPsMutex)) ||  //初始化锁
       !CWErr(CWCreateThreadMutex(&gActiveWTPsMutex))) {

        /* error starting */
        CWLog("Can't start AC");
        printf("Can't start AC2\n");
        exit(1);
    }
    ...
    //初始化话连接的WTP连接使用的gWTPs结构
    //(每个WTP都会有一个gWTPs)
    for(i = 0; i < gMaxWTPs; i++) {
    //  分析 : gMaxWTPs
    // gMaxWTPs = gConfigValues[3].value.int_value; -->   -->  config.ac
        gWTPs[i].isNotFree = CW_FALSE;
                if (!gWTPs[i].tap_fd){
                  init_AC_tap_interface(i); //创建虚拟网卡/dev/net/tun (ps:open使用就是虚拟网卡)
        }

    }
    ...
}
void CWACEnterMainLoop() {
    ...
    //提供给其他程序的配置接口,用来配置无线配置等
    if(!CWErr(CWCreateThread(&thread_interface, CWInterface, NULL))) {
        CWLog("Error starting Interface Thread");
        printf("Error starting Interface Thread\n");
        exit(1);
    }
    ...
    //核心部分
    CW_REPEAT_FOREVER {
        /* CWACManageIncomingPacket will be called 
         * when a new packet is ready to be read 
         */
        if(!CWErr(CWNetworkUnsafeMultiHomed(&gACSocket, 
                            CWACManageIncomingPacket,
                            CW_FALSE))) 
            exit(1);
        }
    }
}

主要核心在CWNetworkUnsafeMultiHomed 和 CWACManageIncomingPacket

CWNetworkUnsafeMultiHomed :
使用select进行数据侦听 :
接收分为三个部分:

  1. gWTPs[i].tap_fd
    分析 :
    {
    //将TAP接受到的数据转发向WTP
    }

  2. capwap控制帧(通道5246) –> 接收capwap帧
    sockPtr->interfaces[i].sock
    {
    //接收处理调用CWACManageIncomingPacket
    –> ACEnterRun 处理
    }

  3. capwap数据帧(通道5247) –> 接收802.11帧 即业务流量
    sockPtr->interfaces[i].dataSock
    –> CWACManageIncomingPacket
    –> CWManageWTP
    – > ACEnterRun 处理
    –> 两个方向 tap_fd 和 发到 hostapd
    –> 发送到 hostapd 的是802.11 管理帧
    –> 发送到 tap_fd 的是802.11 的数据帧
    }

传入dataFlag 标志位区分是数据帧还是控制帧
接收的数据处理有两种方式
1. 如果是已经连接上的WTP发送的数据,则将数据包装好放到wtpPtr->packetReceiveList里面
2. 未连接上的WTP发送的数据,则进行capwap协议规定的连接握手过程。
(ps :具体协议看RFC5415 和 RFC5416)

其后查找取出packetReceiveList数据的位置,定位在函数CWManageWTP里面。
每一个WTP都有一个CWManageWTP进行维护。

CW_THREAD_RETURN_TYPE CWManageWTP(void *arg) {
    ...  //前面各种初始化,我就不管了,主要还是看循环怎么分析
    CW_REPEAT_FOREVER {
        ...     pBuffer=(char*)CWRemoveHeadElementFromSafeListwithDataFlag(gWTPs[i].packetReceiveL  ist, &readBytes,&dataFlag);// 取出数据
    ...
    memcpy(gWTPs[i].buf, pBuffer, readBytes);
    ....
    //解析处理数据帧
    if(!CWProtocolParseFragment(gWTPs[i].buf,
                            readBytes,
                            &(gWTPs[i].fragmentsList),
                            &msg,
                            &dataFlag,
                            gWTPs[i].RadioMAC))
    }
    ...
    switch(gWTPs[i].currentState)
    {
        //这里面处理各个状态下的数据
        //下面我只分析在run状态下的数据,其他都是依照协议的处理方式
        // 函数ACEnterRun
    }else {
        //这里的XXX_XXXX_CMD就是前面提到的提供给其他进程的控制数据的处理点即           CWInterface函数
    }
    后面是配置后的参数处理工作
    ...

}

然后我们分析 ACEnterRun(i, &msg, dataFlag)这个函数

CWBool ACEnterRun(int WTPIndex, CWProtocolMessage *msgPtr, CWBool dataFlag){
    ...
    //分为数据帧和控制帧
    if(dataFlag){
        //分为
        CW_DATA_MSG_FRAME_TYPE :处理sta控制请求,添加sta或者删除
        CW_DATA_MSG_KEEP_ALIVE_TYPE:处理KEEP ALIVE帧(数据通道保活帧)
        CW_IEEE_802_3_FRAME_TYPE:
        // write(gWTPs[WTPIndex].tap_fd, msgPtr->msg, msglen); 发送到gWTPs[WTPIndex].tap_fd 即上面提到的虚拟网卡。
        CW_IEEE_802_11_FRAME_TYPE:
        WLAN_FC_TYPE_MGMT 
            管理帧发送到hostapd
        WLAN_FC_TYPE_DATA
            数据帧发送到虚拟网卡上
        ...
    }
    ...
    switch(controlVal.messageTypeValue)
    {
        ...
        //处理各种协议信息 具体看RFC5416
    }
    ... 
    if(toSend){
        //发送协议回复到WTP
    }

}

WTP:

int main (int argc, const char * argv[]){
    ...
    CWLogInitFile(WTP_LOG_FILE_NAME); //初始化日志文件
    ... 
    if(!CWParseSettingsFile()){ // setttings.wtp.txt
    ...
    if( !CWErr(CWWTPLoadConfiguration()) ) // config.wtp
    ...
    if(!CWErr(CWCreateThread(&thread_ipc_with_wtp_hostapd,CWWTPThread_read_data_fro m_hostapd, NULL))) { // 与hostapd交互线程
    ...
    if(!CWErr(CWCreateThread(&thread_receiveStats, CWWTPReceiveStats, NULL))) {
    ...
    if(!CWErr(CWCreateThread(&thread_receiveFreqStats, CWWTPReceiveFreqStats, NULL))) {
    ...
    //下面是主循环函数
    CW_REPEAT_FOREVER {
        switch(nextState) {
            case CW_ENTER_DISCOVERY:
                nextState = CWWTPEnterDiscovery();
                break;
            case CW_ENTER_SULKING:
                nextState = CWWTPEnterSulking();
                break;
            case CW_ENTER_JOIN:
                nextState = CWWTPEnterJoin();
                break;
            case CW_ENTER_CONFIGURE:
                nextState = CWWTPEnterConfigure();
                break;  
            case CW_ENTER_DATA_CHECK:
                nextState = CWWTPEnterDataCheck();
                break;  
            case CW_ENTER_RUN:
                nextState = CWWTPEnterRun();
                break;
            case CW_ENTER_RESET:
                nextState = CW_ENTER_DISCOVERY;
                break;
            case CW_QUIT:
                CWWTPDestroy();
                return 0;
        }
    }
}

先分析run状态:

CWStateTransition CWWTPEnterRun() {

    if (!CWErr(CWStartEchoRequestTimer())) {
        goto CLEAR_RUN_STATE; //return CW_ENTER_RESET;
    }//开启echo定时器(ps : opencapwap并没有添加keep alive定时器需自己添加)
    ...
    CW_REPEAT_FOREVER{
        if ((CWGetCountElementFromSafeList(gPacketReceiveList) == 0) &&                             (CWGetCountElementFromSafeList(gFrameList) == 0)) {  
                //查看gPacketReceiveList或者gFrameList里面是否有数据收到
    }
    ...
    if (bReceivePacket){
        //接受处理bReceivePacket里的数据
        if (!(CWReceiveMessage(&msg))) {
        }
        if (!CWErr(CWWTPManageGenericRunMessage(&msg))) {
        } // 处理capwap协议
    }
    //接受处理bReveiveBinding里的数据
    if (bReveiveBinding)
        CWWTPCheckForBindingFrame(); // 通过数据通道发给AC
}
//下面是退出run状态的一系列处理
...

分析gPacketReceiveList 和gFrameList的俩个数据来源

gPacketReceiveList:
–>CWWTPReceiveDtlsPacket //接收capwap的控制数据

gFrameList
–> CWWTPReceiveFrame //这里面无用,本意是开启monitor模式将数据接收过来发给AC,这里没做好
建议可以看下这边这个代码,作者改善的很好
https://github.com/iosifpeterfi/openCAPWAP-OpenWRT

-->CWWTPThread_read_data_from_hostapd //接收hostapd发送给AC的数据

WTP接收到AC发送过来的数据:

CW_THREAD_RETURN_TYPE CWWTPReceiveDataPacket(void *arg){
    CW_DATA_MSG_KEEP_ALIVE_TYPE:
    CW_IEEE_802_3_FRAME_TYPE:
    CW_IEEE_802_11_FRAME_TYPE:
}

//这个函数opencapwap并没有进行使用,实际上是如果数据流走capwap的话,必须开启这个函数,然后利用monitor将数据发送到无线网卡上面。

最后提一下capwap的业务数据流向,有两种方式
1. 走capwap的data通道
2. 不走capwap,直接路由转发

上面分析的是走capwap的data通道,不走的话简单点,capwap的数据通道直接就没用了。

下面是我按我的理解话的一个数据流量走向:
opencapwap简要分析_第1张图片

总结 : 总体来说opencapwap的代码还有很多不完善的地方,不过它很好的提供了一个简单的使用案例,我们可以在其基础上进行进一步的修改。

你可能感兴趣的:(无线网络)