From c5a281781d2d4504ed0789cdc725789da4b8b3bd Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Wed, 18 Jan 2023 13:18:00 -0800 Subject: [PATCH] ap: support PTK rekeys This adds support for rekeys to AP mode. A single timer is used and reset to the next station needing a rekey. A default rekey timer of 600 seconds is used unless the profile sets a timeout. --- src/ap.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/src/ap.c b/src/ap.c index 1d937103..531babb2 100644 --- a/src/ap.c +++ b/src/ap.c @@ -106,6 +106,9 @@ struct ap_state { struct l_dbus_message *scan_pending; struct l_queue *networks; + struct l_timeout *rekey_timeout; + unsigned int rekey_time; + bool started : 1; bool gtk_set : 1; bool netconfig_set_addr4 : 1; @@ -137,6 +140,7 @@ struct sta_state { bool wsc_v2; struct l_dhcp_lease *ip_alloc_lease; bool ip_alloc_sent; + uint64_t rekey_time; bool ht_support : 1; bool ht_greenfield : 1; @@ -345,6 +349,11 @@ static void ap_reset(struct ap_state *ap) l_queue_destroy(ap->networks, l_free); ap->networks = NULL; } + + if (ap->rekey_timeout) { + l_timeout_remove(ap->rekey_timeout); + ap->rekey_timeout = NULL; + } } static bool ap_event_done(struct ap_state *ap, bool prev_in_event) @@ -377,6 +386,8 @@ static bool ap_event(struct ap_state *ap, enum ap_event_type event, return ap_event_done(ap, prev); } +static void ap_check_rekeys(struct ap_state *ap); + static void ap_del_station(struct sta_state *sta, uint16_t reason, bool disassociate) { @@ -439,6 +450,93 @@ static void ap_del_station(struct sta_state *sta, uint16_t reason, ap_event_done(ap, prev); } + + /* + * Set the rekey time to zero which will skip this station when + * determining the next rekey. + */ + sta->rekey_time = 0; + ap_check_rekeys(ap); +} + +static void ap_start_rekey(struct ap_state *ap, struct sta_state *sta) +{ + l_debug("Rekey STA "MAC, MAC_STR(sta->addr)); + + eapol_start(sta->sm); +} + +static void ap_rekey_timeout(struct l_timeout *timeout, void *user_data) +{ + struct ap_state *ap = user_data; + + ap_check_rekeys(ap); +} + +/* + * Used to check/start any rekeys which are due and reset the rekey timer to the + * next soonest station needing a rekey. + * + * TODO: Could adapt this to also take into account the next GTK rekey and + * service that as well. But GTK rekeys are not yet supported in AP mode. + */ +static void ap_check_rekeys(struct ap_state *ap) +{ + const struct l_queue_entry *e; + uint64_t now = l_time_now(); + uint64_t next = 0; + + if (!ap->rekey_time) + return; + + /* Find the station(s) that need a rekey and start it */ + for (e = l_queue_get_entries(ap->sta_states); e; e = e->next) { + struct sta_state *sta = e->data; + + if (!sta->associated || !sta->rsna || sta->rekey_time == 0) + continue; + + if (l_time_before(now, sta->rekey_time)) { + uint64_t diff = l_time_diff(now, sta->rekey_time); + + /* Finding the next rekey time */ + if (next < diff) + next = diff; + + continue; + } + + ap_start_rekey(ap, sta); + } + + /* + * Set the next rekey to the station needing it the soonest, or remove + * if a single station and wait until the rekey is complete to reset + * the timer. + */ + if (next) + l_timeout_modify(ap->rekey_timeout, l_time_to_secs(next)); + else { + l_timeout_remove(ap->rekey_timeout); + ap->rekey_timeout = NULL; + } +} + +static void ap_set_sta_rekey_timer(struct ap_state *ap, struct sta_state *sta) +{ + if (!ap->rekey_time) + return; + + sta->rekey_time = l_time_now() + ap->rekey_time - 1; + + /* + * First/only station authenticated, set rekey timer. Any more stations + * will just set their rekey time and be serviced by the single callback + */ + if (!ap->rekey_timeout) + ap->rekey_timeout = l_timeout_create( + l_time_to_secs(ap->rekey_time), + ap_rekey_timeout, ap, NULL); } static bool ap_sta_match_addr(const void *a, const void *b) @@ -479,6 +577,8 @@ static void ap_new_rsna(struct sta_state *sta) sta->rsna = true; + ap_set_sta_rekey_timer(ap, sta); + event_data.mac = sta->addr; event_data.assoc_ies = sta->assoc_ies; event_data.assoc_ies_len = sta->assoc_ies_len; @@ -1372,6 +1472,9 @@ static void ap_handshake_event(struct handshake_state *hs, sta->hs->go_ip_addr = IP4_FROM_STR(own_addr_str); break; } + case HANDSHAKE_EVENT_REKEY_COMPLETE: + ap_set_sta_rekey_timer(ap, sta); + return; default: break; } @@ -3628,6 +3731,19 @@ static int ap_load_config(struct ap_state *ap, const struct l_settings *config, l_strfreev(strvval); } + if (l_settings_has_key(config, "General", "RekeyTimeout")) { + unsigned int uintval; + + if (!l_settings_get_uint(config, "General", + "RekeyTimeout", &uintval)) { + l_error("AP [General].RekeyTimeout is not valid"); + return -EINVAL; + } + + ap->rekey_time = uintval * L_USEC_PER_SEC; + } else + ap->rekey_time = 0; + /* * Since 5GHz won't ever support only CCK rates we can ignore this * setting on that band.