### 一、预备知识(eap_sm、eap_method结构体)
~~~
struct eap_sm { //状态机,存储eap的状态
enum { //枚举eap的各种状态
EAP_DISABLED, EAP_INITIALIZE, EAP_IDLE, EAP_RECEIVED,
EAP_INTEGRITY_CHECK, EAP_METHOD_RESPONSE, EAP_METHOD_REQUEST,
EAP_PROPOSE_METHOD, EAP_SELECT_ACTION, EAP_SEND_REQUEST,
EAP_DISCARD, EAP_NAK, EAP_RETRANSMIT, EAP_SUCCESS, EAP_FAILURE,
EAP_TIMEOUT_FAILURE, EAP_PICK_UP_METHOD,
EAP_INITIALIZE_PASSTHROUGH, EAP_IDLE2, EAP_RETRANSMIT2,
EAP_RECEIVED2, EAP_DISCARD2, EAP_SEND_REQUEST2,
EAP_AAA_REQUEST, EAP_AAA_RESPONSE, EAP_AAA_IDLE,
EAP_TIMEOUT_FAILURE2, EAP_FAILURE2, EAP_SUCCESS2
} EAP_state;
/* Constants */
int MaxRetrans; //最大重传次数,eap支持超时重发机制.eap_sm在初始化时赋值为5
struct eap_eapol_interface eap_if;//主要放些直接与消息相关的,如req及resp的数据,当前是req还是resp,是否到了重传的时机(retransWhile)等
/* Full authenticator state machine local variables */
/* Long-term (maintained between packets) */
EapType currentMethod; //当前采用的Method,初始为EAP_TYPE_NONE,其后根据响应中的type定或自选
int currentId; //当前eap id,开始设为-1,作为backend_AAA时被设为响应消息eapid,需要发送eapreq的时候设为nextId
enum {
METHOD_PROPOSED, METHOD_CONTINUE, METHOD_END
} methodState;
int retransCount;//传送次数
struct wpabuf *lastReqData;//记下已经发出的请求数据,如需要重传时需要发此数据
int methodTimeout;
/* Short-term (not maintained between packets) */
Boolean rxResp; //收到消息的id为resp时设置rxResp为TRUE
int respId;//收到的resp消息的id
EapType respMethod;
int respVendor;
u32 respVendorMethod;
Boolean ignore;
enum {
DECISION_SUCCESS, DECISION_FAILURE, DECISION_CONTINUE,
DECISION_PASSTHROUGH
} decision;
/* Miscellaneous variables */
const struct eap_method *m; /* selected EAP method,当前选定的eap method */
/* not defined in RFC 4137 */
Boolean changed;//状态机是否改变,在不变时则退出状态机运行,后续可能要发送eapreq,eapsuccess或eapfailure,或在pending时不做事情
void *eapol_ctx, *msg_ctx;//eapol_ctx:上下文信息,在状态机初始化时指向session,之后不动;msg_ctx:尚未使用
struct eapol_callbacks *eapol_cb;//状态机初始化时设置eapol_cb。struct eapol_callbacks为多个需要用到的回调函数如get_eap_user等组成的结构体
void *eap_method_priv;//由各个eap method定义的数据,在EAP_INITIALIZE是sm->eap_method_priv = sm->m->initPickUp(sm) 指向eap_identity_data
//在具体EAP method阶段则是具体eap_xxx_data
u8 *identity; //在eap_identity_process内赋值,取自eap-resp/identity
size_t identity_len;
/* Whether Phase 2 method should validate identity match */
int require_identity_match; //EAP-GTC用到
int lastId; /* Identifier used in the last EAP-Packet */
struct eap_user *user;
int user_eap_method_index;
int init_phase2; //eap_ttls_phase2_eap_init eap_peap_phase2_init两个函数内设置为1
void *ssl_ctx; //在状态机初始化eap_server_sm_init内设置为一个全局的g_ssl_context上下文。后者通过g_ssl_context = tls_init(NULL)实现初始化
struct eap_sim_db_data *eap_sim_db_priv;//指向系统配置的eap_sim/aka的配置信息,为eap_sim_db_data结构。主要含有与hlr的通信套接字信息,假名表,
//重鉴权用户信息,pending的用户查询等
Boolean backend_auth; //是否作为backend authentication server
Boolean update_user; //sm->identity是否更新了的标志,如为true时可能需要重新获取用户信息
int eap_server; //是作为eapserver还是passthrough
int num_rounds; //eap交互次数,最大允许EAP_MAX_AUTH_ROUNDS=50次
enum {
METHOD_PENDING_NONE, METHOD_PENDING_WAIT, METHOD_PENDING_CONT
} method_pending;
/*状态机初始化时method_pending为METHOD_PENDING_NONE,因业务需要,可以将method_pending设置为METHOD_PENDING_WAIT。
eap状态机在处理EAP_PROPOSE_METHOD或EAP_METHOD_RESPONSE时,如果为WAIT则什么不做,退出状态机。
如果为CONT则设置method_pending = METHOD_PENDING_NONE并继续执行EAP_METHOD_RESPONSE状态。
eap具体method业务在收到响应等需要的时候调用eap_sm_pending_cb,他会设置method_pending为CONT,这样再激活状态机他会继续执行*/
u8 *auth_challenge;
u8 *peer_challenge; //均是eap-mschapv2鉴权过程中的参数,分别由server和peer生成的随机数
u8 *pac_opaque_encr_key;
u8 *eap_fast_a_id;
size_t eap_fast_a_id_len;
char *eap_fast_a_id_info;
enum {
NO_PROV, ANON_PROV, AUTH_PROV, BOTH_PROV
} eap_fast_prov;
int pac_key_lifetime;
int pac_key_refresh_time;
int eap_sim_aka_result_ind;
int tnc; //以上均取自配置文件,eap server用不着这些,可以到配置文件中查看配置的这些变量的值
u16 pwd_group;
struct wps_context *wps;
struct wpabuf *assoc_wps_ie;
struct wpabuf *assoc_p2p_ie;
Boolean start_reauth;
u8 peer_addr[ETH_ALEN];
/* Fragmentation size for EAP method init() handler */
int fragment_size;
int pbc_in_m1;
const u8 *server_id;
size_t server_id_len;
#ifdef CONFIG_TESTING_OPTIONS
u32 tls_test_flags;
#endif /* CONFIG_TESTING_OPTIONS */
};
~~~
~~~
struct eap_method { //这个结构体用来存放每种加密方法的各种操作函数和变量
int vendor; //存放eap vender ID
EapType method; //EapType是一个枚举类型,里面的值定义了method type
const char *name; //存放method的名字,比如PSK
void * (*init)(struct eap_sm *sm); //初始化eap method
void * (*initPickUp)(struct eap_sm *sm);
void (*reset)(struct eap_sm *sm, void *priv);
struct wpabuf * (*buildReq)(struct eap_sm *sm, void *priv, u8 id); //处理一个eap request 请求包
int (*getTimeout)(struct eap_sm *sm, void *priv);
Boolean (*check)(struct eap_sm *sm, void *priv, struct wpabuf *respData);
void (*process)(struct eap_sm *sm, void *priv,struct wpabuf *respData);
Boolean (*isDone)(struct eap_sm *sm, void *priv);
u8 * (*getKey)(struct eap_sm *sm, void *priv, size_t *len); //从eap method中获取秘钥内容
Boolean (*isSuccess)(struct eap_sm *sm, void *priv);
void (*free)(struct eap_method *method); //释放eap method 数据
#define EAP_SERVER_METHOD_INTERFACE_VERSION 1
int version; //peer端EAP interface版本
struct eap_method *next; // 用于建立链表,指向下一个节点
u8 * (*get_emsk)(struct eap_sm *sm, void *priv, size_t *len);//获取扩展的秘钥内容
};
~~~
上面两个结构体封装了很多参数和方法,显得尤其重要,接下来,我们进入hostapd_global_init()函数。
~~~
<span style="color:#000000;">static int hostapd_global_init(struct hapd_interfaces *interfaces,const char *entropy_file)
{
os_memset(&global, 0, sizeof(global));//重置global变量
hostapd_logger_register_cb(hostapd_logger_cb);
if (eap_server_register_methods()) { //注册eap server的加密方法
wpa_printf(MSG_ERROR, "Failed to register EAP methods");
return -1;
}
if (eloop_init()) { //
wpa_printf(MSG_ERROR, "Failed to initialize event loop");
return -1;
}
random_init(entropy_file);
#ifndef CONFIG_NATIVE_WINDOWS
eloop_register_signal(SIGHUP, handle_reload, interfaces);
eloop_register_signal(SIGUSR1, handle_dump_state, interfaces);
#endif /* CONFIG_NATIVE_WINDOWS */
eloop_register_signal_terminate(handle_term, interfaces);
for (i = 0; wpa_drivers[i]; i++)
global.drv_count++;
if (global.drv_count == 0) {
wpa_printf(MSG_ERROR, "No drivers enabled");
return -1;
}
global.drv_priv = os_calloc(global.drv_count, sizeof(void *));
if (global.drv_priv == NULL)
return -1;
return 0;
}</span>
~~~
1. 使用eap_server_register_methods函数注册eap server支持的安全模式,并存放在一个链表里面,下图是支持的安全模式。
![](https://box.kancloud.cn/2016-04-15_57108d8dc08ea.jpg)
~~~
int eap_server_register_methods(void)
{
int ret = 0;
#ifdef EAP_SERVER_IDENTITY
if (ret == 0)
ret = eap_server_identity_register();
#endif /* EAP_SERVER_IDENTITY */
#ifdef EAP_SERVER_MD5
if (ret == 0)
ret = eap_server_md5_register();
#endif /* EAP_SERVER_MD5 */
#ifdef EAP_SERVER_TLS
if (ret == 0)
ret = eap_server_tls_register();
.......
~~~
根据宏开关来确认哪些安全模式是支持的,并调用相应协议的注册函数,注册一个加密安全模式放在struct eap_method链表中,因为这些安全模式注册函数都差不多,所以只介绍其中一种模式eap_server_psk_register()。
~~~
int eap_server_psk_register(void)
{
struct eap_method *eap;
int ret;
eap = eap_server_method_alloc(EAP_SERVER_METHOD_INTERFACE_VERSION,
EAP_VENDOR_IETF, EAP_TYPE_PSK, "PSK");
if (eap == NULL)
return -1;
eap->init = eap_psk_init;
eap->reset = eap_psk_reset;
eap->buildReq = eap_psk_buildReq;
eap->check = eap_psk_check;
eap->process = eap_psk_process;
eap->isDone = eap_psk_isDone;
eap->getKey = eap_psk_getKey;
eap->isSuccess = eap_psk_isSuccess;
eap->get_emsk = eap_psk_get_emsk;
ret = eap_server_method_register(eap);
if (ret)
eap_server_method_free(eap);
return ret;
}
~~~
首先定义一个struct eap_method 对象,用eap_server_method_alloc给这个对象申请一块空间,然后给这个对象根据不同的安全模式指向不同的操作函数,最后将这个eap对象通过eap_server_method_register函数添加到struct eap_method 结构体对象的链表里面。
2.使用eloop_init()对struct eloop_data eloop对象进行初始化,至于struct eloop_data的作用将在后面介绍。
~~~
int eloop_init(void)
{
os_memset(&eloop, 0, sizeof(eloop));
dl_list_init(&eloop.timeout);
#ifdef CONFIG_ELOOP_EPOLL
eloop.epollfd = epoll_create1(0);
if (eloop.epollfd < 0) {
wpa_printf(MSG_ERROR, "%s: epoll_create1 failed. %s\n",
__func__, strerror(errno));
return -1;
}
eloop.readers.type = EVENT_TYPE_READ;
eloop.writers.type = EVENT_TYPE_WRITE;
eloop.exceptions.type = EVENT_TYPE_EXCEPTION;
#endif /* CONFIG_ELOOP_EPOLL */
#ifdef WPA_TRACE
signal(SIGSEGV, eloop_sigsegv_handler);
#endif /* WPA_TRACE */
return 0;
~~~
这个函数主要是重置eloop对象和初始化链表,然后对eloop成员的一些赋值等
3.random_init()
~~~
void random_init(const char *entropy_file)
{
os_free(random_entropy_file);
if (entropy_file)
random_entropy_file = os_strdup(entropy_file);
else
random_entropy_file = NULL;
random_read_entropy();
#ifdef __linux__
if (random_fd >= 0)
return;
random_fd = open("/dev/random", O_RDONLY | O_NONBLOCK);
if (random_fd < 0) {
#ifndef CONFIG_NO_STDOUT_DEBUG
int error = errno;
perror("open(/dev/random)");
wpa_printf(MSG_ERROR, "random: Cannot open /dev/random: %s",
strerror(error));
#endif /* CONFIG_NO_STDOUT_DEBUG */
return;
}
wpa_printf(MSG_DEBUG, "random: Trying to read entropy from "
"/dev/random");
eloop_register_read_sock(random_fd, random_read_fd, NULL, NULL);
#endif /* __linux__ */
random_write_entropy();
}
~~~
这里面eloop_register_read_sock很重要,具体的需要用源代码去深入跟踪。
4.最后是中断的注册和global对像的赋值等操作。
这篇主要对初始化过程进行了介绍,功能的具体实现将在后面篇幅中讲述。