4.4.2 EAPOL模块分析
最后更新于:2022-04-02 06:03:15
同EAP模块类似,EAPOL模块的实现参考了另外一个规范,即IEEE 802.1X。
>[info] 注意 参考IEEE 802.1X 2004版规范的主要原因是,WPAS中EAPOL模块也基于该版本的规范。另外,笔者比较了2004版和2010版的802.1X,发现2004版的内容组织相对清晰易读。
在介绍802.1X前,先来看其描述的EAP和EAPOL之间的关系,如图4-25所示。
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/729597a80d4fc618dd4aaef3c9e0332d_697x645.jpg)
图4-25 EAP和EAPOL的关系
根据上一节对EAP SUPP SM的介绍,读者会发现图4-25中所示的eapResp、eapSuccess等变量就是RFC4137中定义的用于LL层和EAP SUPP SM层交互的变量。很明显,802.1X模块(在WPAS中,它就是EAPOL模块)是EAP SUPP的LL层(参考图4-22)。
另外一个可能会让读者感到惊奇的是,802.1X规范为EAPOL Supplicant定义了5个不同的状态机,分别如下。
1. **Port Timers SM**:Port超时控制状态机。Port的概念请参考3.3.7节802.1X介绍。
2. **Supplicant PAE SM**:PAE是Port Access Entitiy的缩写。该状态机用于维护Port的状态。
3. **Supplicant Backend SM**:规范并没有明示该状态机的作用。但笔者觉得它主要用于给Authenticator发送EAPOL回复消息。
4. **The Key Receiver SM**:用于处理Key(指EAPOL-Key帧)相关流程的状态机。
5. **The Supplicant Key Transmit SM**:该状态机非必选项,所以WPAS未实现它。
说实话,EAPOL Supplicant定义5个状态机确实有些复杂。主要原因是这5个状态机相互之间都有关联,这些关联体现在它们可能都受同一个变量的影响,从而导致各自的状态发生变化。例如Port Timers SM修改了一些变量后,就有可能使得其他状态机的状态发生变化。规范中把这些变量成为全局变量(Global Varaibles)。
**规范阅读提示**
1. 除了SUPP包含的这五个状态机外,规范还为Authenticator定义了四个状态机。Authenticator也需要实现Port Timers SM和The Key Receiver SM。
2. 规范中将这些状态机统称为PACP(Port Access Control Protocol)State Machine。
下面,先来认识这些全局变量。
**1. EAPOL SUPP全局变量**
802.1X定义了一些全局变量,它们被多个状态机使用。这些全局变量的定义如表4-6所示。
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/95fd0665509d05110ca5adf5bcb5e051_892x694.jpg)
表 4-6 SUPP全局变量的定义
注意,表4-6省略了部分和Authenticator相关的全局变量。另外,规范还定义了一些全局超时变量,它们将在Port Timers SM中介绍。
**2. SUPP PACP状态机**
**①、Port Timers SM**
Port Timers SM(PT SM)对应的状态切换如图4-26所示。
PT SM的功能比较简单,就是每一秒触发一次以从ONE_SECOND状态进入TICK状态。TICK状态的EA中,它将递减(图4-26中的dec函数)某些变量的值。
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/88678b70d5da4ccedab663a712dbc0b6_1013x381.jpg)
图4-26 PT SM状态切换
注意,PT SM在SUPP和AUTH两端都有。所以,图4-26中的一些变量只用于AUTH端。这些变量的含义如表4-7所示。
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/49235273816921e5f941a3204bfc9775_1260x428.jpg)
表4-7 PT SM变量
虽然规范定义了PT SM,但WPAS中,PT SM的功能并不是通过状态机宏来实现的,而仅仅是向eloop模块注册了一个超时时间为1秒的函数eapol_port_timers_tick,其代码如下所示。
**eapol_supp_sm.c::eapol_port_timers_tick**
~~~
static void eapol_port_timers_tick(void *eloop_ctx, void *timeout_ctx)
{
struct eapol_sm *sm = timeout_ctx;
if (sm->authWhile > 0) {// 处理authWhile
sm->authWhile--;
if (sm->authWhile == 0)
wpa_printf(MSG_DEBUG, "EAPOL: authWhile --> 0");
}
// 处理heldWhile,startWhen,idleWhile(idleWhile见表4-1)
......
if (sm->authWhile | sm->heldWhile | sm->startWhen | sm->idleWhile) {
// 重新注册超时处理函数,相当于切换到图4-26中的ONE_SECOND状态
eloop_register_timeout(1, 0, eapol_port_timers_tick, eloop_ctx, sm);
} else {
sm->timer_tick_enabled = 0;
}
eapol_sm_step(sm);// 处理其他状态机的状态切换,此函数内容下文会介绍
}
~~~
上述代码中,eapol_port_timers_tick除了递减相关变量外,最后还需要调用eapol_sm_step函数以判断其他状态机是否需要切换状态。这是PT SM和其他状态机联动的关键纽带,而这个纽带在规范中并不能直接体现出来(规范中,PT SM只是修改某些变量,至于其他状态机到底怎么被触发,则没有说明。而eapol_port_timers_tick函数修改完变量后,直接调用eapol_sm_step
函数完成了对其他状态机的检查)。
下面来看第二个状态机The Key Receiver SM。
**②、The Key Receiver SM**
图4-27所示为The Key Receiver SM(以后简称TKR SM)状态切换图。主要有两点值得关注。
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/6858cdc9b6b71b0c2639f8db2e03c83e_495x620.jpg)
图4-27 TKR SM状态切换
* TKR SM包含两个状态。第一个是NO_KEY_RECEIVE状态。当rxKey(boolean型变量,当Supplicant收到EAPOL Key帧后,该值为TRUE)变为TRUE时,TKR进入KEY_RECEIVE状态。
* TKR在KEY_RECEIVE状态时需要调用processKey函数处理EAPOL Key消息。
WPAS中,TKR的代码也非常简单,如下所示。
**eapol_supp_sm.c::TRK SM相关函数**
~~~
SM_STATE(KEY_RX, NO_KEY_RECEIVE)
{
SM_ENTRY(KEY_RX, NO_KEY_RECEIVE);
}
SM_STATE(KEY_RX, KEY_RECEIVE)
{
SM_ENTRY(KEY_RX, KEY_RECEIVE);
eapol_sm_processKey(sm); // 对应图4-27所示的processKey函数
sm->rxKey = FALSE;
}
SM_STEP(KEY_RX) // TKR状态机状态切换函数
{
if (sm->initialize || !sm->portEnabled)
SM_ENTER_GLOBAL(KEY_RX, NO_KEY_RECEIVE); // 直接进入NO_KEY_RECEIVE状态
switch (sm->KEY_RX_state) {
case KEY_RX_UNKNOWN:
break;
case KEY_RX_NO_KEY_RECEIVE:
if (sm->rxKey)
SM_ENTER(KEY_RX, KEY_RECEIVE);
break;
case KEY_RX_KEY_RECEIVE:
if (sm->rxKey)
SM_ENTER(KEY_RX, KEY_RECEIVE);
break;
}
}
~~~
TKR SM的代码非常简单,此处不详述。下面来看PAE SM。
**③、PAE SM**
PAE SM比较复杂,其状态切换如图4-28所示。
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/8f9604bb5caa69f0e32a6df5e7ca22df_1099x1554.jpg)
图4-28 PAE SM状态切换
图4-28中涉及的变量定义见表4-8。
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/8298200f37fda3d0405dc5acb9e14e14_1057x499.jpg)
表4-8 PAE SM 变量定义
图4-28还包括两个函数。
* txStart:用于发送EAPOL-Start消息给Authenticator。
* txLogoff:用于发送EAPOL-Logoff消息给Authenticator。
>[info] 提示 PAE SM中的状态虽然较多,但笔者觉得它们的划分似乎并无泾渭分明的根据。另外,规范对它们的描述也仅是说明满足什么条件将进入什么状态。至于为什么划分这么多状态也没有太多可参考的依据。所以,读者也不必拘泥于求根究底了,只要把握图4-28即可。
WPAS中,PAE SM相关的代码也比较简单,此处仅看LOGOFF状态的EA,如下所示。
**eapol_supp_sm.c::SM_STATE(SUPP_PAE,LOGOFF)**
~~~
SM_STATE(SUPP_PAE, LOGOFF) // 状态机名为SUPP_PAE,状态名为LOGOFF
{
SM_ENTRY(SUPP_PAE, LOGOFF);
eapol_sm_txLogoff(sm); // 对应图4-28中的txLogoff函数
sm->logoffSent = TRUE;
sm->suppPortStatus = Unauthorized;
// 这个函数内部将通过Nl80211 API设置WLAN Driver的状态
// 属于EAPOL模块和WPAS中其他模块的交互处理
eapol_sm_set_port_unauthorized(sm);
}
~~~
**④、Backend SM**
Backend SM(BE SM)的状态转换如图4-29所示。
需要介绍和BE SM相关的变量authPeriod,它和authWhile(见表4-7)有关,默认值为30秒。
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/0bbd39bdcf354b971ea21aa4225d7a5b_651x816.jpg)
图4-29 BE SM状态切换
BE SM包含如下几个重要函数。
* abortSupp:停止认证工作,释放相关的资源。
* getSuppResp:这个函数本意是用来获取EAP Response信息的,然后用txSuppResp函数发送出去。但WPAS中,该函数没有包括任何有实质意义的内容。
* txSuppResp:发送EAPOL-Packet包给Authenticator。
BE SM的部分代码如下所示。
**eapol_supp_sm.c::SM_STATE(SUPP_BE,REQUEST)**
~~~
SM_STATE(SUPP_BE, REQUEST) // REQUEST状态对应的EA
{
SM_ENTRY(SUPP_BE, REQUEST);
sm->authWhile = 0;
sm->eapReq = TRUE;
eapol_sm_getSuppRsp(sm); // 此函数内部并无任何有实质意义的内容,读者不妨自行阅读它
}
~~~
>[info] 提示 前面几节介绍了802.1X中SUPP PACP几个状态机相关的知识。相比EAP SUPP SM而言,虽然PACP状态机的个数增加了不少,但每个状态机包含的状态却少了许多,所以PACP状态机反而容易理解。
有了理论知识后,马上来看EAPOL SUPP模块中的几个重要数据结构和函数。
**3.EAPOL SUPP代码分析**
图4-23和图4-24介绍了EAPOL和EAP模块的关系,那么EAPOL和WPAS其他模块是什么关系呢?相关数据结构如图4-30所示。
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/e8d2b83d7d27aebde5125bcec7e58923_913x669.jpg)
图4-30 WPAS中EAPOL/EAP模块数据结构
由图4-30可知,WPAS定义了一个数据结构eapol_sm来存储和PACP状态机相关的内容。其内部定义了三个状态机(TKR SM、PAE SM和BE SM)各自的状态信息(由三个状态枚举值表达)、相关变量等。EAPOL模块和WPAS中重要模块wpa_supplicant的交互接口是通过结构体eapol_ctx来定义的。EAPOL模块通过eap变量指向EAP模块的代表eap_sm结构体。
>[info] 提示 eapol_sm和eapol_ctx实际包含的成员变量非常多,此处仅列举其中一部分。
虽然WPAS包括EAPOL和EAP两个模块,但WPAS其他模块一般只和EAPOL模块交互。至于EAP模块,它的操作(例如EAP的初始化以及EAP SUPP SM的运作)则由EAPOL模块来触发。
**①、EAPOL模块的初始化**
先来看EAPOL和EAP模块的初始化函数,由wpa_supplicant_init_eapol函数完成,代码如下所示。
**wpas_glue.c::wpa_supplicant_init_eapol**
~~~
int wpa_supplicant_init_eapol(struct wpa_supplicant *wpa_s)
{
#ifdef IEEE8021X_EAPOL
struct eapol_ctx *ctx;
ctx = os_zalloc(sizeof(*ctx));
......
ctx->ctx = wpa_s;
ctx->msg_ctx = wpa_s;
ctx->eapol_send_ctx = wpa_s;
ctx->preauth = 0;
ctx->eapol_done_cb = wpa_supplicant_notify_eapol_done;
ctx->eapol_send = wpa_supplicant_eapol_send;
......// 其他eapol_ctx成员变量的初始化
ctx->wps = wpa_s->wps;
ctx->eap_param_needed = wpa_supplicant_eap_param_needed;
ctx->port_cb = wpa_supplicant_port_cb;
ctx->cb = wpa_supplicant_eapol_cb;
ctx->cert_cb = wpa_supplicant_cert_cb;
ctx->cb_ctx = wpa_s;
wpa_s->eapol = eapol_sm_init(ctx); // 初始化EAPOL模块
......
#endif /* IEEE8021X_EAPOL */
return 0;
}
~~~
wpa_supplicant_init_eapol首先设置eapol_ctx对象,然后调用eapol_sm_init来完成EAPOL模块的初始化。eapol_sm_init的代码如下所示。
**eapol_supp_sm.c::eapol_sm_init**
~~~
struct eapol_sm *eapol_sm_init(struct eapol_ctx *ctx)
{
struct eapol_sm *sm;
struct eap_config conf;
sm = os_zalloc(sizeof(*sm)); // EAPOL对应的状态机信息
......
sm->ctx = ctx;// eapol_ctx是WPAS中EAPOL模块和其他模块交互的接口
sm->portControl = Auto;
sm->heldPeriod = 60;
sm->startPeriod = 30;
sm->maxStart = 3;
sm->authPeriod = 30;
os_memset(&conf, 0, sizeof(conf));
conf.opensc_engine_path = ctx->opensc_engine_path;
......
conf.wps = ctx->wps;
// 初始化EAP Supplicant SM相关资源
sm->eap = eap_peer_sm_init(sm, &eapol_cb, sm->ctx->msg_ctx, &conf);
......
// 先设置initialize变量为TRUE,然后初始化相关状态
sm->initialize = TRUE;
eapol_sm_step(sm);
sm->initialize = FALSE; // 设置为FALSE,再初始化相关状态
eapol_sm_step(sm);
sm->timer_tick_enabled = 1;
eloop_register_timeout(1, 0, eapol_port_timers_tick, NULL, sm);
return sm;
}
~~~
在eapol_sm_init代码中:
1. 先通过调用eap_peer_sm_init初始化EAP SUPP SM相关资源。
2. 然后完成EAPOL PACP三个状态机的初始化工作。初始化的方法很简单,即先设置initialize为TRUE,然后执行eapol_sm_step函数(该函数代码见下文,其主要目的是根据条件以跳转到下一个状态。initialize为TRUE时,将触发一些状态的EA被调用,从而某些变量的初值将被设定)。然后设置initialize为FALSE后,再度执行eapol_sm_step函数(这样,对应状态的EA也将被执行,从而剩余变量的初值将被设定)。
3. 最后通过注册一个eloop超时任务实现了PT SM。
**②、状态机的联动**
根据前面的介绍,EAPOL和EAP一共有四个状态机,它们到底是怎么联动的呢?答案就在eapol_sm_step中。eapol_sm_step的代码如下所示。
**eapol_supp_sm.c::eapol_sm_step**
~~~
void eapol_sm_step(struct eapol_sm *sm)
{
int i;
/*
笔者一直很好奇EAPOL和EAP中的四个状态机是怎么联动的。通过下面的代码可知,
根据EAPOL和EAP的关系,首先要运行EAPOL中的三个状态机(Port Timers SM由eloop定时任务
来实现),分别是SUPP_PAE、KEY_RX和SUPP_BE。然后执行EAP SUPP SM。如果changed变量
为TRUE,表示状态发生了切换。由于每个状态对应的EA又有可能改变其中一些变量从而引起其他状
态机状态发生变化,所以,这里有一个for语句来循环处理状态切换,直到四个状态机都没有状态切
换为止。一般情况下,for循环应该是一个无限循环,但此次通过100来控制循环次数,是为了防止
某些情况下状态机陷入死循环而不能退出(这也说明规范中定义的SM在联动时可能有逻辑错误)。
*/
for (i = 0; i < 100; i++) {
sm->changed = FALSE;
SM_STEP_RUN(SUPP_PAE);
SM_STEP_RUN(KEY_RX);
SM_STEP_RUN(SUPP_BE);
if (eap_peer_sm_step(sm->eap)) // eap_peer_sm_step返回非零,表示状态有变化
sm->changed = TRUE;
if (!sm->changed) break; // 如果没有状态变化,则跳出循环
}
/*
运行超过100次,需要重新启动EAPOL模块状态机运行,eapol_sm_step_timeout将重新调用
eapol_sm_step函数。
*/
if (sm->changed) {
eloop_cancel_timeout(eapol_sm_step_timeout, NULL, sm);
eloop_register_timeout(0, 0, eapol_sm_step_timeout, NULL, sm);
}
/*
cb_status是一个枚举类型的变量,可取值有EAPOL_CB_IN_PROGRESS, EAPOL_CB_SUCCESS和
EAPOL_CB_FAILURE。
*/
if (sm->ctx->cb && sm->cb_status != EAPOL_CB_IN_PROGRESS) {
int success = sm->cb_status == EAPOL_CB_SUCCESS ? 1 : 0;
/*
该值在PAE AUTHENTICATED状态中被置为EAPOL_CB_SUCCESS,表示认证成功。在PAE
HELD状态被置为EAPOL_CB_FAILURE,表示认证还未成功。
*/
sm->cb_status = EAPOL_CB_IN_PROGRESS;
// 回调通知WPAS,真实的函数是wpa_supplicant_eapol_cb,这个函数以后介绍
sm->ctx->cb(sm, success, sm->ctx->cb_ctx);
}
}
~~~
WPAS中状态机联动代码的实现非常巧妙,它通过循环来处理各个状态机的状态变换,直到四个状态机都稳定为止。
>[info] 注意 初始化结束后,各个状态机的状态为:SUPP_PAE为DISCONNECTED状态、KEY_RX为NO_KEY_RECEIVE状态、SUPP_BE为IDLE状态、EAP_SM为DISABLED状态。
至此,对FRC4137和IEEE 802.1X-2004协议中EAP Supplicant和EAPOL Supplicant涉及的状态机进行了详细介绍。
在具体实现中,WPAS实现的EAPOL和EAP状态机较为严格得遵循了这两个文档。所以,读者只要理解了协议中的状态切换和相关变量,则能轻松理解WPAS的实现。反之,如果仅单纯从代码入手,EAPOL/EAP状态机的代码将会非常难以理解。
关于EAPOL/EAP状态机相关的知识就介绍到这,以后碰到具体代码时,读者根据状态切换图直接进入某个状态中去看其处理函数(即EA)。
>[info] 提示 本章第二条分析路线使用的目标AP采用WPA2-PSK作为认证算法,故后续章节不会涉及太多和EAPOL及EAP相关的代码分析。感兴趣的读者可在本节基础上,自行搭建RAIDUS服务器来研究WPAS中EAPOL/EAP的工作过程。
';