ap: Handle most WSC IEs using ap_write_extra_ies

Make the WSC IE processing and writing more self-contained (i.e. so that
it can be more easily moved to a separate file if desired) by using the
new ap_write_extra_ies() mechanism.
This commit is contained in:
Andrew Zaborowski 2021-03-11 14:07:24 +01:00 committed by Denis Kenzior
parent 18a63f91fd
commit f868c2989c
1 changed files with 288 additions and 271 deletions

559
src/ap.c
View File

@ -467,6 +467,289 @@ static void ap_set_rsn_info(struct ap_state *ap, struct ie_rsn_info *rsn)
rsn->group_cipher = ap->group_cipher;
}
static void ap_wsc_exit_pbc(struct ap_state *ap)
{
if (!ap->wsc_pbc_timeout)
return;
l_timeout_remove(ap->wsc_pbc_timeout);
ap->wsc_dpid = 0;
ap_update_beacon(ap);
ap->ops->handle_event(AP_EVENT_PBC_MODE_EXIT, NULL, ap->user_data);
}
struct ap_pbc_record_expiry_data {
uint64_t min_time;
const uint8_t *mac;
};
static bool ap_wsc_pbc_record_expire(void *data, void *user_data)
{
struct ap_wsc_pbc_probe_record *record = data;
const struct ap_pbc_record_expiry_data *expiry_data = user_data;
if (record->timestamp > expiry_data->min_time &&
memcmp(record->mac, expiry_data->mac, 6))
return false;
l_free(record);
return true;
}
#define AP_WSC_PBC_MONITOR_TIME 120
#define AP_WSC_PBC_WALK_TIME 120
static void ap_process_wsc_probe_req(struct ap_state *ap, const uint8_t *from,
const uint8_t *wsc_data,
size_t wsc_data_len)
{
struct wsc_probe_request req;
struct ap_pbc_record_expiry_data expiry_data;
struct ap_wsc_pbc_probe_record *record;
uint64_t now;
bool empty;
uint8_t first_sta_addr[6] = {};
const struct l_queue_entry *entry;
if (wsc_parse_probe_request(wsc_data, wsc_data_len, &req) < 0)
return;
if (!(req.config_methods & WSC_CONFIGURATION_METHOD_PUSH_BUTTON))
return;
if (req.device_password_id != WSC_DEVICE_PASSWORD_ID_PUSH_BUTTON)
return;
/* Save the address of the first enrollee record */
record = l_queue_peek_head(ap->wsc_pbc_probes);
if (record)
memcpy(first_sta_addr, record->mac, 6);
now = l_time_now();
/*
* Expire entries older than PBC Monitor Time. While there also drop
* older entries from the same Enrollee that sent us this new Probe
* Request. It's unclear whether we should also match by the UUID-E.
*/
expiry_data.min_time = now - AP_WSC_PBC_MONITOR_TIME * 1000000;
expiry_data.mac = from;
l_queue_foreach_remove(ap->wsc_pbc_probes, ap_wsc_pbc_record_expire,
&expiry_data);
empty = l_queue_isempty(ap->wsc_pbc_probes);
if (!ap->wsc_pbc_probes)
ap->wsc_pbc_probes = l_queue_new();
/* Add new record */
record = l_new(struct ap_wsc_pbc_probe_record, 1);
memcpy(record->mac, from, 6);
memcpy(record->uuid_e, req.uuid_e, sizeof(record->uuid_e));
record->timestamp = now;
l_queue_push_tail(ap->wsc_pbc_probes, record);
/*
* If queue was non-empty and we've added one more record then we
* now have seen more than one PBC enrollee during the PBC Monitor
* Time and must exit "active PBC mode" due to "session overlap".
* WSC v2.0.5 Section 11.3:
* "Within the PBC Monitor Time, if the Registrar receives PBC
* probe requests from more than one Enrollee [...] then the
* Registrar SHALL signal a "session overlap" error. As a result,
* the Registrar shall refuse to enter active PBC mode and shall
* also refuse to perform a PBC-based Registration Protocol
* exchange [...]"
*/
if (empty)
return;
if (ap->wsc_pbc_timeout) {
l_debug("Exiting PBC mode due to Session Overlap");
ap_wsc_exit_pbc(ap);
}
/*
* "If the Registrar is engaged in PBC Registration Protocol
* exchange with an Enrollee and receives a Probe Request or M1
* Message from another Enrollee, then the Registrar should
* signal a "session overlap" error".
*
* For simplicity just interrupt the handshake with that enrollee.
*/
for (entry = l_queue_get_entries(ap->sta_states); entry;
entry = entry->next) {
struct sta_state *sta = entry->data;
if (!sta->associated || sta->assoc_rsne)
continue;
/*
* Check whether this enrollee is in PBC Registration
* Protocol by comparing its mac with the first (and only)
* record we had in ap->wsc_pbc_probes. If we had more
* than one record we wouldn't have been in
* "active PBC mode".
*/
if (memcmp(sta->addr, first_sta_addr, 6) ||
!memcmp(sta->addr, from, 6))
continue;
l_debug("Interrupting handshake with %s due to Session Overlap",
util_address_to_string(sta->addr));
if (sta->hs) {
netdev_handshake_failed(sta->hs,
MMPDU_REASON_CODE_DISASSOC_AP_BUSY);
sta->sm = NULL;
}
ap_remove_sta(sta);
}
}
static size_t ap_get_wsc_ie_len(struct ap_state *ap,
enum mpdu_management_subtype type,
const struct mmpdu_header *client_frame,
size_t client_frame_len)
{
return 256;
}
static size_t ap_write_wsc_ie(struct ap_state *ap,
enum mpdu_management_subtype type,
const struct mmpdu_header *client_frame,
size_t client_frame_len,
uint8_t *out_buf)
{
const uint8_t *from = client_frame->address_2;
uint8_t *wsc_data;
size_t wsc_data_size;
uint8_t *wsc_ie;
size_t wsc_ie_size;
size_t len = 0;
/* WSC IE */
if (type == MPDU_MANAGEMENT_SUBTYPE_PROBE_RESPONSE) {
struct wsc_probe_response wsc_pr = {};
const struct mmpdu_probe_request *req =
mmpdu_body(client_frame);
size_t req_ies_len = (void *) client_frame + client_frame_len -
(void *) req->ies;
ssize_t req_wsc_data_size;
/*
* Process the client Probe Request WSC IE first as it may
* cause us to exit "active PBC mode" and that will be
* immediately reflected in our Probe Response WSC IE.
*/
wsc_data = ie_tlv_extract_wsc_payload(req->ies, req_ies_len,
&req_wsc_data_size);
if (wsc_data) {
ap_process_wsc_probe_req(ap, from, wsc_data,
req_wsc_data_size);
l_free(wsc_data);
}
wsc_pr.version2 = true;
wsc_pr.state = WSC_STATE_CONFIGURED;
if (ap->wsc_pbc_timeout) {
wsc_pr.selected_registrar = true;
wsc_pr.device_password_id = ap->wsc_dpid;
wsc_pr.selected_reg_config_methods =
WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
}
wsc_pr.response_type = WSC_RESPONSE_TYPE_AP;
memcpy(wsc_pr.uuid_e, ap->wsc_uuid_r, sizeof(wsc_pr.uuid_e));
wsc_pr.primary_device_type =
ap->config->wsc_primary_device_type;
if (ap->config->wsc_name)
l_strlcpy(wsc_pr.device_name, ap->config->wsc_name,
sizeof(wsc_pr.device_name));
wsc_pr.config_methods =
WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
if (ap->config->authorized_macs_num) {
size_t len;
len = ap->config->authorized_macs_num * 6;
if (len > sizeof(wsc_pr.authorized_macs))
len = sizeof(wsc_pr.authorized_macs);
memcpy(wsc_pr.authorized_macs,
ap->config->authorized_macs, len);
}
wsc_data = wsc_build_probe_response(&wsc_pr, &wsc_data_size);
} else if (type == MPDU_MANAGEMENT_SUBTYPE_BEACON) {
struct wsc_beacon wsc_beacon = {};
wsc_beacon.version2 = true;
wsc_beacon.state = WSC_STATE_CONFIGURED;
if (ap->wsc_pbc_timeout) {
wsc_beacon.selected_registrar = true;
wsc_beacon.device_password_id = ap->wsc_dpid;
wsc_beacon.selected_reg_config_methods =
WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
}
if (ap->config->authorized_macs_num) {
size_t len;
len = ap->config->authorized_macs_num * 6;
if (len > sizeof(wsc_beacon.authorized_macs))
len = sizeof(wsc_beacon.authorized_macs);
memcpy(wsc_beacon.authorized_macs,
ap->config->authorized_macs, len);
}
wsc_data = wsc_build_beacon(&wsc_beacon, &wsc_data_size);
} else if (L_IN_SET(type, MPDU_MANAGEMENT_SUBTYPE_ASSOCIATION_RESPONSE,
MPDU_MANAGEMENT_SUBTYPE_REASSOCIATION_RESPONSE)) {
struct wsc_association_response wsc_resp = {};
struct sta_state *sta =
l_queue_find(ap->sta_states, ap_sta_match_addr, from);
if (!sta || sta->assoc_rsne)
return 0;
wsc_resp.response_type = WSC_RESPONSE_TYPE_AP;
wsc_resp.version2 = sta->wsc_v2;
wsc_data = wsc_build_association_response(&wsc_resp,
&wsc_data_size);
} else
return 0;
if (!wsc_data) {
l_error("wsc_build_<mgmt-subtype> error (stype 0x%x)", type);
return 0;
}
wsc_ie = ie_tlv_encapsulate_wsc_payload(wsc_data, wsc_data_size,
&wsc_ie_size);
l_free(wsc_data);
if (!wsc_ie) {
l_error("ie_tlv_encapsulate_wsc_payload error (stype 0x%x)",
type);
return 0;
}
memcpy(out_buf + len, wsc_ie, wsc_ie_size);
len += wsc_ie_size;
l_free(wsc_ie);
return len;
}
static size_t ap_get_extra_ies_len(struct ap_state *ap,
enum mpdu_management_subtype type,
const struct mmpdu_header *client_frame,
@ -474,6 +757,8 @@ static size_t ap_get_extra_ies_len(struct ap_state *ap,
{
size_t len = 0;
len += ap_get_wsc_ie_len(ap, type, client_frame, client_frame_len);
if (ap->ops->get_extra_ies_len)
len += ap->ops->get_extra_ies_len(type, client_frame,
client_frame_len,
@ -490,6 +775,9 @@ static size_t ap_write_extra_ies(struct ap_state *ap,
{
size_t len = 0;
len += ap_write_wsc_ie(ap, type, client_frame, client_frame_len,
out_buf + len);
if (ap->ops->write_extra_ies)
len += ap->ops->write_extra_ies(type,
client_frame, client_frame_len,
@ -581,10 +869,6 @@ static size_t ap_build_beacon_pr_tail(struct ap_state *ap,
{
size_t len;
struct ie_rsn_info rsn;
uint8_t *wsc_data;
size_t wsc_data_size;
uint8_t *wsc_ie;
size_t wsc_ie_size;
/* TODO: Country IE between TIM IE and RSNE */
@ -594,85 +878,6 @@ static size_t ap_build_beacon_pr_tail(struct ap_state *ap,
return 0;
len = 2 + out_buf[1];
/* WSC IE */
if (stype == MPDU_MANAGEMENT_SUBTYPE_PROBE_RESPONSE) {
struct wsc_probe_response wsc_pr = {};
wsc_pr.version2 = true;
wsc_pr.state = WSC_STATE_CONFIGURED;
if (ap->wsc_pbc_timeout) {
wsc_pr.selected_registrar = true;
wsc_pr.device_password_id = ap->wsc_dpid;
wsc_pr.selected_reg_config_methods =
WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
}
wsc_pr.response_type = WSC_RESPONSE_TYPE_AP;
memcpy(wsc_pr.uuid_e, ap->wsc_uuid_r, sizeof(wsc_pr.uuid_e));
wsc_pr.primary_device_type =
ap->config->wsc_primary_device_type;
if (ap->config->wsc_name)
l_strlcpy(wsc_pr.device_name, ap->config->wsc_name,
sizeof(wsc_pr.device_name));
wsc_pr.config_methods =
WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
if (ap->config->authorized_macs_num) {
size_t len;
len = ap->config->authorized_macs_num * 6;
if (len > sizeof(wsc_pr.authorized_macs))
len = sizeof(wsc_pr.authorized_macs);
memcpy(wsc_pr.authorized_macs,
ap->config->authorized_macs, len);
}
wsc_data = wsc_build_probe_response(&wsc_pr, &wsc_data_size);
} else {
struct wsc_beacon wsc_beacon = {};
wsc_beacon.version2 = true;
wsc_beacon.state = WSC_STATE_CONFIGURED;
if (ap->wsc_pbc_timeout) {
wsc_beacon.selected_registrar = true;
wsc_beacon.device_password_id = ap->wsc_dpid;
wsc_beacon.selected_reg_config_methods =
WSC_CONFIGURATION_METHOD_PUSH_BUTTON;
}
if (ap->config->authorized_macs_num) {
size_t len;
len = ap->config->authorized_macs_num * 6;
if (len > sizeof(wsc_beacon.authorized_macs))
len = sizeof(wsc_beacon.authorized_macs);
memcpy(wsc_beacon.authorized_macs,
ap->config->authorized_macs, len);
}
wsc_data = wsc_build_beacon(&wsc_beacon, &wsc_data_size);
}
if (!wsc_data)
return 0;
wsc_ie = ie_tlv_encapsulate_wsc_payload(wsc_data, wsc_data_size,
&wsc_ie_size);
l_free(wsc_data);
if (!wsc_ie)
return 0;
memcpy(out_buf + len, wsc_ie, wsc_ie_size);
len += wsc_ie_size;
l_free(wsc_ie);
len += ap_write_extra_ies(ap, stype, req, req_len, out_buf + len);
return len;
}
@ -725,18 +930,6 @@ void ap_update_beacon(struct ap_state *ap)
l_error("Issuing SET_BEACON failed");
}
static void ap_wsc_exit_pbc(struct ap_state *ap)
{
if (!ap->wsc_pbc_timeout)
return;
l_timeout_remove(ap->wsc_pbc_timeout);
ap->wsc_dpid = 0;
ap_update_beacon(ap);
ap->ops->handle_event(AP_EVENT_PBC_MODE_EXIT, NULL, ap->user_data);
}
static uint32_t ap_send_mgmt_frame(struct ap_state *ap,
const struct mmpdu_header *frame,
size_t frame_len,
@ -859,24 +1052,6 @@ error:
ap_del_station(sta, MMPDU_REASON_CODE_UNSPECIFIED, true);
}
struct ap_pbc_record_expiry_data {
uint64_t min_time;
const uint8_t *mac;
};
static bool ap_wsc_pbc_record_expire(void *data, void *user_data)
{
struct ap_wsc_pbc_probe_record *record = data;
const struct ap_pbc_record_expiry_data *expiry_data = user_data;
if (record->timestamp > expiry_data->min_time &&
memcmp(record->mac, expiry_data->mac, 6))
return false;
l_free(record);
return true;
}
static void ap_stop_handshake_schedule(struct sta_state *sta)
{
if (sta->stop_handshake_work)
@ -1294,41 +1469,9 @@ static uint32_t ap_assoc_resp(struct ap_state *ap, struct sta_state *sta,
resp->ies[ies_len++] = count;
ies_len += count;
if (sta && !sta->assoc_rsne) {
struct wsc_association_response wsc_resp = {};
uint8_t *wsc_data;
size_t wsc_data_len;
uint8_t *wsc_ie;
size_t wsc_ie_len;
wsc_resp.response_type = WSC_RESPONSE_TYPE_AP;
wsc_resp.version2 = sta->wsc_v2;
wsc_data = wsc_build_association_response(&wsc_resp,
&wsc_data_len);
if (!wsc_data) {
l_error("wsc_build_beacon error");
goto send_frame;
}
wsc_ie = ie_tlv_encapsulate_wsc_payload(wsc_data, wsc_data_len,
&wsc_ie_len);
l_free(wsc_data);
if (!wsc_ie) {
l_error("ie_tlv_encapsulate_wsc_payload error");
goto send_frame;
}
memcpy(resp->ies + ies_len, wsc_ie, wsc_ie_len);
ies_len += wsc_ie_len;
l_free(wsc_ie);
}
ies_len += ap_write_extra_ies(ap, stype, req, req_len,
resp->ies + ies_len);
send_frame:
return ap_send_mgmt_frame(ap, mpdu, resp->ies + ies_len - mpdu_buf,
callback, sta);
}
@ -1705,118 +1848,6 @@ bad_frame:
l_error("Sending error Reassociation Response failed");
}
#define AP_WSC_PBC_MONITOR_TIME 120
#define AP_WSC_PBC_WALK_TIME 120
static void ap_process_wsc_probe_req(struct ap_state *ap, const uint8_t *from,
const uint8_t *wsc_data,
size_t wsc_data_len)
{
struct wsc_probe_request req;
struct ap_pbc_record_expiry_data expiry_data;
struct ap_wsc_pbc_probe_record *record;
uint64_t now;
bool empty;
uint8_t first_sta_addr[6] = {};
const struct l_queue_entry *entry;
if (wsc_parse_probe_request(wsc_data, wsc_data_len, &req) < 0)
return;
if (!(req.config_methods & WSC_CONFIGURATION_METHOD_PUSH_BUTTON))
return;
if (req.device_password_id != WSC_DEVICE_PASSWORD_ID_PUSH_BUTTON)
return;
/* Save the address of the first enrollee record */
record = l_queue_peek_head(ap->wsc_pbc_probes);
if (record)
memcpy(first_sta_addr, record->mac, 6);
now = l_time_now();
/*
* Expire entries older than PBC Monitor Time. While there also drop
* older entries from the same Enrollee that sent us this new Probe
* Request. It's unclear whether we should also match by the UUID-E.
*/
expiry_data.min_time = now - AP_WSC_PBC_MONITOR_TIME * 1000000;
expiry_data.mac = from;
l_queue_foreach_remove(ap->wsc_pbc_probes, ap_wsc_pbc_record_expire,
&expiry_data);
empty = l_queue_isempty(ap->wsc_pbc_probes);
if (!ap->wsc_pbc_probes)
ap->wsc_pbc_probes = l_queue_new();
/* Add new record */
record = l_new(struct ap_wsc_pbc_probe_record, 1);
memcpy(record->mac, from, 6);
memcpy(record->uuid_e, req.uuid_e, sizeof(record->uuid_e));
record->timestamp = now;
l_queue_push_tail(ap->wsc_pbc_probes, record);
/*
* If queue was non-empty and we've added one more record then we
* now have seen more than one PBC enrollee during the PBC Monitor
* Time and must exit "active PBC mode" due to "session overlap".
* WSC v2.0.5 Section 11.3:
* "Within the PBC Monitor Time, if the Registrar receives PBC
* probe requests from more than one Enrollee [...] then the
* Registrar SHALL signal a "session overlap" error. As a result,
* the Registrar shall refuse to enter active PBC mode and shall
* also refuse to perform a PBC-based Registration Protocol
* exchange [...]"
*/
if (empty)
return;
if (ap->wsc_pbc_timeout) {
l_debug("Exiting PBC mode due to Session Overlap");
ap_wsc_exit_pbc(ap);
}
/*
* "If the Registrar is engaged in PBC Registration Protocol
* exchange with an Enrollee and receives a Probe Request or M1
* Message from another Enrollee, then the Registrar should
* signal a "session overlap" error".
*
* For simplicity just interrupt the handshake with that enrollee.
*/
for (entry = l_queue_get_entries(ap->sta_states); entry;
entry = entry->next) {
struct sta_state *sta = entry->data;
if (!sta->associated || sta->assoc_rsne)
continue;
/*
* Check whether this enrollee is in PBC Registration
* Protocol by comparing its mac with the first (and only)
* record we had in ap->wsc_pbc_probes. If we had more
* than one record we wouldn't have been in
* "active PBC mode".
*/
if (memcmp(sta->addr, first_sta_addr, 6) ||
!memcmp(sta->addr, from, 6))
continue;
l_debug("Interrupting handshake with %s due to Session Overlap",
util_address_to_string(sta->addr));
if (sta->hs) {
netdev_handshake_failed(sta->hs,
MMPDU_REASON_CODE_DISASSOC_AP_BUSY);
sta->sm = NULL;
}
ap_remove_sta(sta);
}
}
static void ap_probe_resp_cb(int err, void *user_data)
{
if (err == -ECOMM)
@ -1848,8 +1879,6 @@ static void ap_probe_req_cb(const struct mmpdu_header *hdr, const void *body,
l_malloc(512 + ap_get_extra_ies_len(ap,
MPDU_MANAGEMENT_SUBTYPE_PROBE_RESPONSE, hdr,
body + body_len - (void *) hdr));
uint8_t *wsc_data;
ssize_t wsc_data_len;
l_info("AP Probe Request from %s",
util_address_to_string(hdr->address_2));
@ -1922,18 +1951,6 @@ static void ap_probe_req_cb(const struct mmpdu_header *hdr, const void *body,
if (!match)
return;
/*
* Process the WSC IE first as it may cause us to exit "active PBC
* mode" and that can be immediately reflected in our Probe Response.
*/
wsc_data = ie_tlv_extract_wsc_payload(req->ies, body_len - sizeof(*req),
&wsc_data_len);
if (wsc_data) {
ap_process_wsc_probe_req(ap, hdr->address_2,
wsc_data, wsc_data_len);
l_free(wsc_data);
}
len = ap_build_beacon_pr_head(ap,
MPDU_MANAGEMENT_SUBTYPE_PROBE_RESPONSE,
hdr->address_2, resp, sizeof(resp));