### 一、預備知識(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支持的安全模式,并存放在一個鏈表里面,下圖是支持的安全模式。

~~~
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對像的賦值等操作。
這篇主要對初始化過程進行了介紹,功能的具體實現將在后面篇幅中講述。