注:分析开源代opencapwap,其代码分为三个部分WTP端,AC端和WUA。这里我不对WUA进行分析,因为我并没使用到它。
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进行数据侦听 :
接收分为三个部分:
gWTPs[i].tap_fd
分析 :
{
//将TAP接受到的数据转发向WTP
}
capwap控制帧(通道5246) –> 接收capwap帧
sockPtr->interfaces[i].sock
{
//接收处理调用CWACManageIncomingPacket
–> ACEnterRun 处理
}
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
}
}
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的代码还有很多不完善的地方,不过它很好的提供了一个简单的使用案例,我们可以在其基础上进行进一步的修改。