eapol: Cache early EAPoL frames until ready to process

Split eapol_start into two calls, one to register the state machine so
that the PAE read handler knows not to discard frames for that ifindex,
and eapol_start to actually start processing the frames.  This is needed
because, as per the comment in netdev.c, due to scheduling the PAE
socket read handler may trigger before the CMD_CONNECT event handler,
which needs to parse the FTE from the Associate Response frame and
supply it to the eapol SM before it can do anything with the message 1
of 4 of the FT handshake.

Another issue is that depending on the driver or timing, the underlying
link might not be marked as 'ready' by the kernel.  In this case, our
response to Message 1 of the 4-way Handshake is written and accepted by
the kernel, but gets dropped on the floor internally.  Which leads to
timeouts if the AP doesn't retransmit.
This commit is contained in:
Andrew Zaborowski 2016-10-04 05:48:08 +02:00 committed by Denis Kenzior
parent c35366fc38
commit c548898635
3 changed files with 57 additions and 6 deletions

View File

@ -708,6 +708,11 @@ struct eapol_key *eapol_create_gtk_2_of_2(
return step2;
}
struct eapol_buffer {
size_t len;
uint8_t data[0];
};
struct eapol_sm {
enum eapol_protocol_version protocol_version;
uint32_t ifindex;
@ -732,8 +737,10 @@ struct eapol_sm {
bool ptk_complete:1;
bool wpa_ie:1;
bool have_pmk:1;
bool started:1;
bool use_eapol_start:1;
struct eap_state *eap;
struct eapol_buffer *early_frame;
};
static void eapol_sm_destroy(void *value)
@ -749,6 +756,8 @@ static void eapol_sm_destroy(void *value)
if (sm->eap)
eap_free(sm->eap);
l_free(sm->early_frame);
l_free(sm);
}
@ -1643,6 +1652,26 @@ static void eapol_rx_packet(struct eapol_sm *sm,
{
const struct eapol_header *eh;
if (!sm->started) {
struct eapol_buffer *buf;
/*
* If the state machine hasn't started yet save the frame
* for processing later.
*/
if (sm->early_frame) /* Is the 1-element queue full */
return;
buf = l_malloc(sizeof(struct eapol_buffer) + len);
buf->len = len;
memcpy(buf->data, frame, len);
sm->early_frame = buf;
return;
}
/* Validate Header */
if (len < sizeof(struct eapol_header))
return;
@ -1767,11 +1796,19 @@ static bool eapol_get_nonce(uint8_t nonce[])
return l_getrandom(nonce, 32);
}
void eapol_start(uint32_t ifindex, struct eapol_sm *sm)
void eapol_register(uint32_t ifindex, struct eapol_sm *sm)
{
sm->ifindex = ifindex;
l_queue_push_head(state_machines, sm);
}
void eapol_start(struct eapol_sm *sm)
{
sm->timeout = l_timeout_create(2, eapol_timeout, sm, NULL);
sm->started = true;
if (sm->use_eapol_start) {
/*
* We start a short timeout, if EAP packets are not received
@ -1781,7 +1818,14 @@ void eapol_start(uint32_t ifindex, struct eapol_sm *sm)
l_timeout_create(1, send_eapol_start, sm, NULL);
}
l_queue_push_head(state_machines, sm);
/* Process any frames received early due to scheduling */
if (sm->early_frame) {
struct eapol_buffer *tmp = sm->early_frame;
sm->early_frame = NULL;
eapol_rx_packet(sm, tmp->data, tmp->len);
l_free(tmp);
}
}
void eapol_cancel(uint32_t ifindex)

View File

@ -207,7 +207,8 @@ uint32_t eapol_sm_get_pairwise_cipher(struct eapol_sm *sm);
uint32_t eapol_sm_get_group_cipher(struct eapol_sm *sm);
const uint8_t *eapol_sm_get_own_ie(struct eapol_sm *sm, size_t *out_ie_len);
void eapol_start(uint32_t ifindex, struct eapol_sm *sm);
void eapol_register(uint32_t ifindex, struct eapol_sm *sm);
void eapol_start(struct eapol_sm *sm);
void eapol_cancel(uint32_t ifindex);
void eapol_pae_open();

View File

@ -1017,6 +1017,13 @@ static void netdev_connect_event(struct l_genl_msg *msg,
goto error;
if (netdev->eapol_active) {
/*
* Start processing EAPoL frames now that the state machine
* has all the input data even in FT mode.
*/
eapol_start(netdev->sm);
netdev->sm = NULL;
if (netdev->event_filter)
netdev->event_filter(netdev,
NETDEV_EVENT_4WAY_HANDSHAKE,
@ -1058,16 +1065,15 @@ static void netdev_cmd_connect_cb(struct l_genl_msg *msg, void *user_data)
netdev->user_data);
/*
* We start the eapol state machine here, in case the PAE
* We register the eapol state machine here, in case the PAE
* socket receives EAPoL packets before the nl80211 socket
* receives the connected event. The logical sequence of
* events can be reversed (e.g. connect_event, then PAE data)
* due to scheduling
*/
if (netdev->sm) {
eapol_start(netdev->index, netdev->sm);
eapol_register(netdev->index, netdev->sm);
netdev->eapol_active = true;
netdev->sm = NULL;
}
return;