企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
### 一、预备知识(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对像的赋值等操作。 这篇主要对初始化过程进行了介绍,功能的具体实现将在后面篇幅中讲述。