#include #include #include #include #define AUTH_TIMEOUT 5000 #define ASSOC_TIMEOUT 5000 #define DEAUTHENTICATION_REASON_LEAVING 3 // if more space is needed, this can be changed #define ASSOCIATION_INFORMATION_ELEMENTS_MAX_SIZE 1024 typedef struct { void *completion_work; void *auth_timeout_work; void *auth_timeout_timer; void *assoc_timeout_work; void *assoc_timeout_timer; unsigned int generation; u80211_device_t *device; u80211_ap_t *ap; // this keeps a reference, the reference in the device is only incremented only when fully associated void *information_elements; size_t information_elements_size; } u80211_association_context_t; typedef struct { u80211_list_node_t node; void *semaphore; int result; } u80211_association_waiter_t; static void wake_association_waiters(u80211_device_t *device, int result) { u80211_list_node_t *node; while ((node = u80211_list_pop_front(&device->association_waiters)) != NULL) { u80211_association_waiter_t *waiter = container_of(node, u80211_association_waiter_t, node); waiter->result = result; u80211_kernel_signal_semaphore(waiter->semaphore); } } static void destroy_association_context(u80211_association_context_t *association_context) { u80211_kernel_free_timer(association_context->auth_timeout_timer); u80211_kernel_free_timer(association_context->assoc_timeout_timer); u80211_kernel_free_work(association_context->completion_work); u80211_kernel_free_work(association_context->auth_timeout_work); u80211_kernel_free_work(association_context->assoc_timeout_work); if (association_context->information_elements != NULL) u80211_kernel_free(association_context->information_elements); u80211_kernel_free(association_context); } bool u80211_association_is_duplicate(u80211_device_t *device, const u80211_header_description_t *header, int expected_state) { bool duplicate = false; u80211_kernel_acquire_spinlock(device->association_spinlock); bool active_stream = u80211_get_device_state(device) == expected_state && device->ap != NULL && u80211_mac_address_equal(&device->ap->mac_address, &header->addresses[1]); if (active_stream) { duplicate = (header->frame_control & U80211_HEADER_FRAME_CONTROL_RETRY) && device->received_sequence_control_valid && device->received_sequence_control == header->sequence_control; if (!duplicate) { device->received_sequence_control = header->sequence_control; device->received_sequence_control_valid = true; } } u80211_kernel_release_spinlock(device->association_spinlock); return duplicate; } static void release_disconnected_ap(void *ctx) { u80211_device_t *device = ctx; u80211_kernel_acquire_spinlock(device->association_spinlock); u80211_ap_t *ap = device->disconnected_ap; device->disconnected_ap = NULL; u80211_kernel_release_spinlock(device->association_spinlock); if (ap != NULL) u80211_ap_release(ap); } static void cleanup_association(void *ctx) { u80211_device_t *device = ctx; u80211_kernel_acquire_spinlock(device->association_spinlock); u80211_association_context_t *association_context = device->association_cleanup_context; u80211_ap_t *disconnected_ap = device->disconnected_ap; device->disconnected_ap = NULL; u80211_kernel_release_spinlock(device->association_spinlock); if (association_context != NULL) { u80211_ap_release(association_context->ap); destroy_association_context(association_context); } if (disconnected_ap != NULL) u80211_ap_release(disconnected_ap); if (association_context != NULL) { u80211_kernel_acquire_spinlock(device->association_spinlock); device->association_cleanup_context = NULL; device->association_cleanup_pending = false; wake_association_waiters(device, device->association_result); u80211_kernel_release_spinlock(device->association_spinlock); } } static void schedule_association_cleanup(u80211_device_t *device, u80211_association_context_t *association_context) { device->association_context = NULL; device->association_cleanup_context = association_context; device->association_cleanup_pending = true; } static void association_process_teardown(u80211_device_t *device, const u80211_mac_address_t *address, bool deauthentication) { bool cleanup_needed = false; u80211_kernel_acquire_spinlock(device->association_spinlock); if (!device->ap || !u80211_mac_address_equal(&device->ap->mac_address, address)) goto leave; int state = u80211_get_device_state(device); bool attempt_in_progress = state == U80211_DEVICE_STATE_AUTHENTICATING || state == U80211_DEVICE_STATE_ASSOCIATING; if (state != U80211_DEVICE_STATE_ASSOCIATED && (!deauthentication || !attempt_in_progress)) goto leave; if (!u80211_set_device_state(device, state, U80211_DEVICE_STATE_DOWN)) goto leave; if (attempt_in_progress) { u80211_association_context_t *association_context = device->association_context; if (association_context == NULL) goto leave; schedule_association_cleanup(device, association_context); device->association_result = U80211_STATUS_REJECTED; cleanup_needed = true; } else { device->disconnected_ap = device->ap; cleanup_needed = true; } device->ap = NULL; ++device->association_generation; leave: u80211_kernel_release_spinlock(device->association_spinlock); if (cleanup_needed) u80211_kernel_enqueue_work(device->association_cleanup_work, cleanup_association, device); } void u80211_association_process_deauthentication(u80211_device_t *device, u80211_deauthentication_data_t *deauthentication_data) { association_process_teardown(device, &deauthentication_data->address, true); } void u80211_association_process_disassociation(u80211_device_t *device, u80211_disassociation_data_t *disassociation_data) { association_process_teardown(device, &disassociation_data->address, false); } void u80211_association_process_response(u80211_device_t *device, u80211_association_response_data_t *association_data) { bool cleanup_needed = false; u80211_kernel_acquire_spinlock(device->association_spinlock); u80211_association_context_t *association_context = device->association_context; if (!association_context || !device->ap) goto leave; if (!u80211_mac_address_equal(&association_context->ap->mac_address, &association_data->address)) goto leave; int new_state = association_data->status ? U80211_DEVICE_STATE_DOWN : U80211_DEVICE_STATE_ASSOCIATED; if (!u80211_set_device_state(device, U80211_DEVICE_STATE_ASSOCIATING, new_state)) goto leave; if (association_data->status) device->ap = NULL; else u80211_ap_hold(device->ap); schedule_association_cleanup(device, association_context); ++device->association_generation; device->association_result = association_data->status ? U80211_STATUS_REJECTED : U80211_STATUS_SUCCESS; cleanup_needed = true; leave: u80211_kernel_release_spinlock(device->association_spinlock); if (cleanup_needed) u80211_kernel_enqueue_work(device->association_cleanup_work, cleanup_association, device); } static void association_timeout(void *ctx, int expected_state) { u80211_association_context_t *association_context = ctx; u80211_device_t *device = association_context->device; bool cleanup_needed = false; u80211_kernel_acquire_spinlock(device->association_spinlock); // not associating/different association generation if (device->association_context != association_context || !device->ap || device->association_generation != association_context->generation) goto leave; if (u80211_set_device_state(device, expected_state, U80211_DEVICE_STATE_DOWN)) { device->ap = NULL; schedule_association_cleanup(device, association_context); ++device->association_generation; device->association_result = U80211_STATUS_TIMED_OUT; cleanup_needed = true; } leave: u80211_kernel_release_spinlock(device->association_spinlock); if (cleanup_needed) u80211_kernel_enqueue_work(device->association_cleanup_work, cleanup_association, device); } static void assoc_timeout(void *ctx) { association_timeout(ctx, U80211_DEVICE_STATE_ASSOCIATING); } static void auth_completion_work(void *ctx) { u80211_association_context_t *association_context = ctx; u80211_device_t *device = association_context->device; u80211_send_association_request(device, association_context->information_elements, association_context->information_elements_size); u80211_kernel_enqueue_delayed_work(association_context->assoc_timeout_work, association_context->assoc_timeout_timer, assoc_timeout, association_context, ASSOC_TIMEOUT); } void u80211_association_process_authentication(u80211_device_t *device, u80211_auth_data_t *auth_data) { bool cleanup_needed = false; u80211_kernel_acquire_spinlock(device->association_spinlock); // not associating/different AP if (!device->ap || !u80211_mac_address_equal(&device->ap->mac_address, &auth_data->address)) goto leave; // not the same algo/different transaction stage than expected if (auth_data->auth_algorithm != U80211_AUTH_ALGORITHM_OPEN || auth_data->auth_transaction != 2) goto leave; if (auth_data->status) { // failure if (!u80211_set_device_state(device, U80211_DEVICE_STATE_AUTHENTICATING, U80211_DEVICE_STATE_DOWN)) goto leave; u80211_association_context_t *association_context = device->association_context; device->ap = NULL; schedule_association_cleanup(device, association_context); ++device->association_generation; device->association_result = U80211_STATUS_REJECTED; cleanup_needed = true; } else { if (!u80211_set_device_state(device, U80211_DEVICE_STATE_AUTHENTICATING, U80211_DEVICE_STATE_ASSOCIATING)) goto leave; u80211_association_context_t *ctx = device->association_context; u80211_kernel_enqueue_work(ctx->completion_work, auth_completion_work, ctx); } leave: u80211_kernel_release_spinlock(device->association_spinlock); if (cleanup_needed) u80211_kernel_enqueue_work(device->association_cleanup_work, cleanup_association, device); } static void auth_timeout(void *ctx) { association_timeout(ctx, U80211_DEVICE_STATE_AUTHENTICATING); } int u80211_associate(u80211_device_t *device, u80211_ap_t *ap, const void *information_elements, size_t information_elements_size) { if (information_elements_size > ASSOCIATION_INFORMATION_ELEMENTS_MAX_SIZE) return U80211_STATUS_NOT_ENOUGH_SPACE; release_disconnected_ap(device); u80211_association_context_t *association_context = u80211_kernel_allocate(sizeof(u80211_association_context_t)); if (association_context == NULL) return U80211_STATUS_ENOMEM; association_context->completion_work = u80211_kernel_allocate_work(); if (association_context->completion_work == NULL) { u80211_kernel_free(association_context); return U80211_STATUS_ENOMEM; } association_context->auth_timeout_work = u80211_kernel_allocate_work(); if (association_context->auth_timeout_work == NULL) { u80211_kernel_free_work(association_context->completion_work); u80211_kernel_free(association_context); return U80211_STATUS_ENOMEM; } association_context->auth_timeout_timer = u80211_kernel_allocate_timer(); if (association_context->auth_timeout_timer == NULL) { u80211_kernel_free_work(association_context->auth_timeout_work); u80211_kernel_free_work(association_context->completion_work); u80211_kernel_free(association_context); return U80211_STATUS_ENOMEM; } association_context->assoc_timeout_work = u80211_kernel_allocate_work(); if (association_context->assoc_timeout_work == NULL) { u80211_kernel_free_timer(association_context->auth_timeout_timer); u80211_kernel_free_work(association_context->auth_timeout_work); u80211_kernel_free_work(association_context->completion_work); u80211_kernel_free(association_context); return U80211_STATUS_ENOMEM; } association_context->assoc_timeout_timer = u80211_kernel_allocate_timer(); if (association_context->assoc_timeout_timer == NULL) { u80211_kernel_free_work(association_context->assoc_timeout_work); u80211_kernel_free_timer(association_context->auth_timeout_timer); u80211_kernel_free_work(association_context->auth_timeout_work); u80211_kernel_free_work(association_context->completion_work); u80211_kernel_free(association_context); return U80211_STATUS_ENOMEM; } association_context->information_elements = NULL; association_context->information_elements_size = information_elements_size; if (information_elements_size != 0) { association_context->information_elements = u80211_kernel_allocate(information_elements_size); if (association_context->information_elements == NULL) { destroy_association_context(association_context); return U80211_STATUS_ENOMEM; } u80211_memcpy(association_context->information_elements, information_elements, information_elements_size); } association_context->device = device; association_context->ap = ap; u80211_kernel_acquire_spinlock(device->association_spinlock); if (device->association_cleanup_pending || !u80211_set_device_state(device, U80211_DEVICE_STATE_DOWN, U80211_DEVICE_STATE_AUTHENTICATING)) { u80211_kernel_release_spinlock(device->association_spinlock); destroy_association_context(association_context); return U80211_STATUS_BUSY; } association_context->generation = device->association_generation; device->association_context = association_context; device->association_result = U80211_STATUS_UNKNOWN_ERROR; device->ap = ap; device->received_sequence_control = 0; device->received_sequence_control_valid = false; u80211_ap_hold(ap); u80211_kernel_release_spinlock(device->association_spinlock); u80211_auth_data_t auth_data = { .address = ap->mac_address, .auth_algorithm = U80211_AUTH_ALGORITHM_OPEN, .auth_transaction = 1, .status = 0, }; device->ops->set_channel(device->driver_data, ap->channel); u80211_send_authentication(device, &auth_data); u80211_kernel_enqueue_delayed_work(association_context->auth_timeout_work, association_context->auth_timeout_timer, auth_timeout, association_context, AUTH_TIMEOUT); return 0; } static int u80211_deauthenticate(u80211_device_t *device, uint16_t reason) { u80211_deauthentication_data_t deauthentication_data = { .reason = reason, }; u80211_kernel_acquire_spinlock(device->association_spinlock); if (device->ap == NULL || u80211_get_device_state(device) != U80211_DEVICE_STATE_ASSOCIATED) { // TODO: Support cancelling an authentication or association attempt. u80211_kernel_release_spinlock(device->association_spinlock); return U80211_STATUS_NOT_ASSOCIATED; } if (!u80211_set_device_state(device, U80211_DEVICE_STATE_ASSOCIATED, U80211_DEVICE_STATE_DEAUTHENTICATING)) { u80211_kernel_release_spinlock(device->association_spinlock); return U80211_STATUS_NOT_ASSOCIATED; } deauthentication_data.address = device->ap->mac_address; u80211_kernel_release_spinlock(device->association_spinlock); int status = u80211_send_deauthentication(device, &deauthentication_data); u80211_ap_t *disconnected_ap = NULL; u80211_kernel_acquire_spinlock(device->association_spinlock); if (device->ap != NULL && u80211_get_device_state(device) == U80211_DEVICE_STATE_DEAUTHENTICATING) { disconnected_ap = device->ap; device->ap = NULL; ++device->association_generation; u80211_set_device_state(device, U80211_DEVICE_STATE_DEAUTHENTICATING, U80211_DEVICE_STATE_DOWN); } u80211_kernel_release_spinlock(device->association_spinlock); if (disconnected_ap != NULL) u80211_ap_release(disconnected_ap); return status; } int u80211_disassociate(u80211_device_t *device) { return u80211_deauthenticate(device, DEAUTHENTICATION_REASON_LEAVING); } int u80211_wait_for_association_completion(u80211_device_t *device) { void *semaphore = u80211_kernel_allocate_semaphore(0); if (semaphore == NULL) return U80211_STATUS_RETRY; u80211_association_waiter_t waiter = { .semaphore = semaphore, }; u80211_kernel_acquire_spinlock(device->association_spinlock); int state = u80211_get_device_state(device); if (state != U80211_DEVICE_STATE_AUTHENTICATING && state != U80211_DEVICE_STATE_ASSOCIATING && !device->association_cleanup_pending) { int result = device->association_result; u80211_kernel_release_spinlock(device->association_spinlock); u80211_kernel_free_semaphore(semaphore); return result; } u80211_list_push_back(&device->association_waiters, &waiter.node); u80211_kernel_release_spinlock(device->association_spinlock); u80211_kernel_wait_semaphore(semaphore); u80211_kernel_free_semaphore(semaphore); return waiter.result; }