diff --git a/kernel-src/io/net/u80211/include/u80211/ap.h b/kernel-src/io/net/u80211/include/u80211/ap.h new file mode 100644 index 00000000..ae1b66bd --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/ap.h @@ -0,0 +1,32 @@ +#ifndef U80211_AP_H +#define U80211_AP_H + +#include +#include + +typedef struct { + int refcount; + u80211_mac_address_t mac_address; + uint16_t interval; + uint16_t capabilities; + uint8_t channel; + uint8_t rate_bitmap[16]; + char ssid[33]; + uint8_t *rsn; + size_t rsn_size; + u80211_rbtree_t cache_node; +} u80211_ap_t; + +u80211_ap_t *u80211_ap_allocate(const u80211_beacon_data_t *beacon_data); +void u80211_ap_inactive(u80211_ap_t *ap); + +static inline void u80211_ap_hold(u80211_ap_t *ap) { + __atomic_add_fetch(&ap->refcount, 1, __ATOMIC_RELAXED); +} + +static inline void u80211_ap_release(u80211_ap_t *ap) { + if (__atomic_sub_fetch(&ap->refcount, 1, __ATOMIC_RELAXED) == 0) + u80211_ap_inactive(ap); +} + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/association.h b/kernel-src/io/net/u80211/include/u80211/association.h new file mode 100644 index 00000000..e99d0446 --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/association.h @@ -0,0 +1,13 @@ +#ifndef U80211_ASSOCIATION_H +#define U80211_ASSOCIATION_H + +#include +#include + +void u80211_association_process_authentication(u80211_device_t *device, u80211_auth_data_t *auth_data); // called from interrupt context +void u80211_association_process_response(u80211_device_t *device, u80211_association_response_data_t *association_data); // called from interrupt context +void u80211_association_process_deauthentication(u80211_device_t *device, u80211_deauthentication_data_t *deauthentication_data); // called from interrupt context +void u80211_association_process_disassociation(u80211_device_t *device, u80211_disassociation_data_t *disassociation_data); // called from interrupt context +bool u80211_association_is_duplicate(u80211_device_t *device, const u80211_header_description_t *header, int expected_state); // called from interrupt context + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/bss_cache.h b/kernel-src/io/net/u80211/include/u80211/bss_cache.h new file mode 100644 index 00000000..4c20b6e4 --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/bss_cache.h @@ -0,0 +1,24 @@ +#ifndef U80211_BSS_CACHE_H +#define U80211_BSS_CACHE_H + +#include + +#include + +typedef struct { + void *rwlock; + u80211_rbtree_t *root; + size_t entry_count; +} bss_cache_t; + +int u80211_bss_cache_init(bss_cache_t *cache); +void u80211_bss_cache_deinit(bss_cache_t *cache); +void u80211_bss_cache_purge(bss_cache_t *cache); +void u80211_bss_cache_insert(bss_cache_t *cache, u80211_ap_t *ap); +void u80211_bss_cache_remove(bss_cache_t *cache, u80211_mac_address_t *mac); +u80211_ap_t *u80211_bss_cache_find(bss_cache_t *cache, u80211_mac_address_t *mac); +// each AP pointer written to buffer has an acquired reference that the caller must release. +size_t u80211_bss_cache_get_aps(bss_cache_t *cache, u80211_ap_t **buffer, size_t capacity); +size_t u80211_bss_cache_get_count(bss_cache_t *cache); + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/kernel_interface.h b/kernel-src/io/net/u80211/include/u80211/kernel_interface.h new file mode 100644 index 00000000..ac0fde95 --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/kernel_interface.h @@ -0,0 +1,89 @@ +#ifndef U80211_KERNEL_INTERFACE_H +#define U80211_KERNEL_INTERFACE_H + +#include +#include + +// allocates 'size' bytes of memory +// return NULL on allocation failure +void *u80211_kernel_allocate(size_t size); + +// frees the kernel memory referenced by 'ptr' +// 'ptr' will never be NULL +void u80211_kernel_free(void *ptr); + +// allocates an opaque mutex object +// return NULL on allocation failure +void *u80211_kernel_allocate_mutex(void); + +// frees the mutex object associated with the 'mutex' pointer +// 'mutex' will never be NULL +void u80211_kernel_free_mutex(void *mutex); + +// basic mutex operations +void u80211_kernel_acquire_mutex(void *mutex); +void u80211_kernel_release_mutex(void *mutex); + +// allocates an opaque semaphore object +// 'initial_count' contains the initial value of the semaphore +void *u80211_kernel_allocate_semaphore(unsigned int initial_count); +// frees the semaphore object referenced by 'semaphore' +// 'semaphore' will never be NULL +void u80211_kernel_free_semaphore(void *semaphore); + +// basic semphore operations +void u80211_kernel_wait_semaphore(void *semaphore); +void u80211_kernel_signal_semaphore(void *semaphore); + +// allocates an opaque spinlock object +// return NULL on allocation failure +void *u80211_kernel_allocate_spinlock(void); + +// frees the spinlock object referenced by 'spinlock' +// 'spinlock' will never be NULL +void u80211_kernel_free_spinlock(void *spinlock); + +// basic spinlock operations +// in case the kernel implements interrupt priorities, these functions +// must raise/lower it from the network interrupt priority. +// otherwise, disable interrupts. +void u80211_kernel_acquire_spinlock(void *spinlock); +void u80211_kernel_release_spinlock(void *spinlock); + +// allocates an opaque rwlock object +// return NULL on allocation failure +void *u80211_kernel_allocate_rwlock(void); + +// frees the rwlock object referenced by 'rwlock' +// 'spinlock' will never be NULL +void u80211_kernel_free_rwlock(void *rwlock); + +// basic rwlock operations +// an acquire will always be paired with a release of the same type +void u80211_kernel_acquire_rwlock_exclusive(void *rwlock); +void u80211_kernel_acquire_rwlock_shared(void *rwlock); +void u80211_kernel_release_rwlock_exclusive(void *rwlock); +void u80211_kernel_release_rwlock_shared(void *rwlock); + +typedef void (*u80211_kernel_work_fn_t)(void *context); + +// allocates an opaque work object +// return NULL on allocation failure +void *u80211_kernel_allocate_work(void); + +// enqueue threaded work. this might be called from an interrupt context +// 'ms' == 0 enqueues the work immediatelly. otherwise, +// work is enqueued 'ms' milliseconds in the future +// if work is already pending, the new request must be ignored +void u80211_kernel_enqueue_work(void *work, u80211_kernel_work_fn_t function, void *context, size_t ms); + +// frees the work object referenced by 'work' +// 'work' will never be NULL +// may be called by the work's callback +void u80211_kernel_free_work(void *work); + +// kernel packet receive callback +// buffer has an ethernet header +void u80211_kernel_receive_callback(u80211_device_t *device, void *buffer, size_t size); + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/key.h b/kernel-src/io/net/u80211/include/u80211/key.h new file mode 100644 index 00000000..e299f95b --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/key.h @@ -0,0 +1,55 @@ +#ifndef U80211_KEY_H +#define U80211_KEY_H + +#include +#include +#include + +typedef enum { + U80211_CIPHER_CCMP, + U80211_CIPHER_TKIP, + U80211_CIPHER_WEP40, + U80211_CIPHER_WEP104, +} u80211_cipher_t; + +typedef enum { + U80211_KEY_PAIRWISE = 1 << 0, + U80211_KEY_GROUP = 1 << 1, + U80211_KEY_RX = 1 << 2, + U80211_KEY_TX = 1 << 3, +} u80211_key_flags_t; + +typedef struct { + u80211_cipher_t cipher; + uint8_t index; + u80211_mac_address_t peer; + const uint8_t *key; + size_t key_len; + const uint8_t *rx_seq; + size_t rx_seq_len; + uint32_t flags; +} u80211_key_t; + +typedef struct { + const u80211_header_description_t *header; + uint8_t key_index; + const u80211_mac_address_t *destination; + const u80211_mac_address_t *source; + uint8_t priority; + const void *data; + size_t data_size; + uint8_t sequence[6]; + uint8_t mic[8]; +} u80211_tkip_data_t; + +int u80211_set_key(u80211_device_t *device, const u80211_key_t *key); +int u80211_del_key(u80211_device_t *device, uint8_t index, const u80211_mac_address_t *peer, uint32_t flags); +int u80211_select_key(u80211_device_t *device, const u80211_header_description_t *header); +int u80211_select_cipher(u80211_device_t *device, const u80211_header_description_t *header); +int u80211_select_cipher_by_index(u80211_device_t *device, const u80211_header_description_t *header, uint8_t index); +bool u80211_key_update_rx_sequence(u80211_device_t *device, const u80211_header_description_t *header, uint8_t index, u80211_cipher_t cipher, const uint8_t *sequence, size_t sequence_size); +bool u80211_key_validate_tkip_rx(u80211_device_t *device, const u80211_tkip_data_t *data); +bool u80211_key_prepare_tkip_tx(u80211_device_t *device, u80211_tkip_data_t *data); +bool u80211_key_next_tx_sequence(u80211_device_t *device, const u80211_header_description_t *header, uint8_t index, u80211_cipher_t cipher, uint8_t *sequence, size_t sequence_size); + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/list.h b/kernel-src/io/net/u80211/include/u80211/list.h new file mode 100644 index 00000000..2ad80a85 --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/list.h @@ -0,0 +1,84 @@ +#ifndef U80211_LIST_H +#define U80211_LIST_H + +#include + +typedef struct u80211_list_node { + struct u80211_list_node *prev; + struct u80211_list_node *next; +} u80211_list_node_t; + +typedef struct { + u80211_list_node_t *head; + u80211_list_node_t *tail; +} u80211_list_t; + +#define U80211_LIST_INIT_VALUE ((u80211_list_t) {.head = NULL, .tail = NULL}) + +static inline void u80211_list_init(u80211_list_t *list) { + list->head = NULL; + list->tail = NULL; +} + +static inline void u80211_list_push_front(u80211_list_t *list, u80211_list_node_t *node) { + node->prev = NULL; + node->next = list->head; + if (list->head != NULL) { + list->head->prev = node; + } else { + list->tail = node; + } + list->head = node; +} + +static inline void u80211_list_push_back(u80211_list_t *list, u80211_list_node_t *node) { + node->next = NULL; + node->prev = list->tail; + if (list->tail != NULL) { + list->tail->next = node; + } else { + list->head = node; + } + list->tail = node; +} + +static inline void u80211_list_remove(u80211_list_t *list, u80211_list_node_t *node) { + if (node->prev != NULL) { + node->prev->next = node->next; + } else { + list->head = node->next; + } + if (node->next != NULL) { + node->next->prev = node->prev; + } else { + list->tail = node->prev; + } + node->prev = NULL; + node->next = NULL; +} + +static inline u80211_list_node_t *u80211_list_pop_front(u80211_list_t *list) { + if (list->head == NULL) { + return NULL; + } + u80211_list_node_t *node = list->head; + u80211_list_remove(list, node); + return node; +} + +static inline u80211_list_node_t *u80211_list_pop_back(u80211_list_t *list) { + if (list->tail == NULL) { + return NULL; + } + u80211_list_node_t *node = list->tail; + u80211_list_remove(list, node); + return node; +} + +#define u80211_list_for_each(LIST, ITER) \ + for (u80211_list_node_t *ITER = (LIST)->head; ITER != NULL; ITER = ITER->next) + +#define u80211_list_for_each_safe(LIST, ITER) \ + for (u80211_list_node_t *ITER = (LIST)->head, *_next = (ITER ? ITER->next : NULL); ITER != NULL; ITER = _next, _next = (ITER ? ITER->next : NULL)) + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/michael.h b/kernel-src/io/net/u80211/include/u80211/michael.h new file mode 100644 index 00000000..6bedde40 --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/michael.h @@ -0,0 +1,9 @@ +#ifndef U80211_MICHAEL_H +#define U80211_MICHAEL_H + +#include +#include + +void u80211_michael_mic(const uint8_t key[8], const uint8_t da[6], const uint8_t sa[6], uint8_t priority, const void *data, size_t len, uint8_t out[8]); + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/packet.h b/kernel-src/io/net/u80211/include/u80211/packet.h new file mode 100644 index 00000000..82b6535f --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/packet.h @@ -0,0 +1,112 @@ +#ifndef U80211_PACKET_H +#define U80211_PACKET_H + +#include +#include +#include + +typedef struct u80211_device u80211_device_t; + +typedef struct { + void *data; + size_t size; + size_t current_offset; +} u80211_tx_buffer_descriptor_t; + +typedef struct { + int key; + int cipher; +} u80211_transmit_options_t; + +typedef struct { + uint8_t bytes[6]; +} u80211_mac_address_t; + +static inline bool u80211_mac_address_equal(const u80211_mac_address_t *a, const u80211_mac_address_t *b) { + for (size_t i = 0; i < sizeof(a->bytes); ++i) { + if (a->bytes[i] != b->bytes[i]) + return false; + } + + return true; +} + +#define U80211_HEADER_FRAME_CONTROL_GET_VERSION(x) ((x) & 0x3) +#define U80211_HEADER_FRAME_CONTROL_GET_TYPE(x) (((x) & 0xc) >> 2) +#define U80211_HEADER_FRAME_CONTROL_TYPE_MANAGEMENT 0 +#define U80211_HEADER_FRAME_CONTROL_TYPE_CONTROL 1 +#define U80211_HEADER_FRAME_CONTROL_TYPE_DATA 2 +#define U80211_HEADER_FRAME_CONTROL_GET_SUBTYPE(x) (((x) & 0xf0) >> 4) +#define U80211_HEADER_FRAME_CONTROL_SUBTYPE_ASSOCIATION_REQUEST 0 +#define U80211_HEADER_FRAME_CONTROL_SUBTYPE_ASSOCIATION_RESPONSE 1 +#define U80211_HEADER_FRAME_CONTROL_SUBTYPE_PROBE_REQUEST 4 +#define U80211_HEADER_FRAME_CONTROL_SUBTYPE_PROBE_RESPONSE 5 +#define U80211_HEADER_FRAME_CONTROL_SUBTYPE_DISASSOCIATION 10 +#define U80211_HEADER_FRAME_CONTROL_SUBTYPE_AUTHENTICATION 11 +#define U80211_HEADER_FRAME_CONTROL_SUBTYPE_DEAUTHENTICATION 12 +#define U80211_HEADER_FRAME_CONTROL_SUBTYPE_DATA 0 +#define U80211_HEADER_FRAME_CONTROL_SUBTYPE_NULL_DATA 4 +#define U80211_HEADER_FRAME_CONTROL_TO_DS 0x100 +#define U80211_HEADER_FRAME_CONTROL_FROM_DS 0x200 +#define U80211_HEADER_FRAME_CONTROL_MORE_FRAGMENTS 0x400 +#define U80211_HEADER_FRAME_CONTROL_RETRY 0x800 +#define U80211_HEADER_FRAME_CONTROL_POWER_MANAGEMENT 0x1000 +#define U80211_HEADER_FRAME_CONTROL_MORE_DATA 0x2000 +#define U80211_HEADER_FRAME_CONTROL_PROTECTED_FRAME 0x4000 +#define U80211_HEADER_FRAME_CONTROL_ORDER 0x8000 + +typedef struct { + uint16_t frame_control; + uint16_t duration_id; + u80211_mac_address_t addresses[4]; + uint16_t sequence_control; +} u80211_header_description_t; + +typedef struct { + u80211_mac_address_t mac_address; + uint16_t interval; + uint16_t capabilities; + uint8_t channel; + uint8_t rate_bitmap[16]; + char ssid[33]; + const uint8_t *rsn; + size_t rsn_size; +} u80211_beacon_data_t; + +#define U80211_AUTH_ALGORITHM_OPEN 0 +typedef struct { + u80211_mac_address_t address; + uint16_t auth_algorithm; + uint16_t auth_transaction; + uint16_t status; +} u80211_auth_data_t; + +typedef struct { + u80211_mac_address_t address; + uint16_t capabilities; + uint16_t status; + uint16_t association_id; + uint8_t rate_bitmap[16]; +} u80211_association_response_data_t; + +typedef struct { + u80211_mac_address_t address; + uint16_t reason; +} u80211_deauthentication_data_t; + +typedef struct { + u80211_mac_address_t address; + uint16_t reason; +} u80211_disassociation_data_t; + +int u80211_deserialize_header(void *source, size_t source_size, u80211_header_description_t *header, void **data_start, size_t *data_size); +int u80211_serialize_header(u80211_header_description_t *header, u80211_tx_buffer_descriptor_t *descriptor); +void u80211_process_management_packet(u80211_device_t *device, u80211_header_description_t *header, const void *data, size_t data_size); // called from an interrupt context +void u80211_process_data_packet(u80211_device_t *device, u80211_header_description_t *header, void *data, size_t data_size); // called from an interrupt context + +int u80211_send_probe_request(u80211_device_t *device); +int u80211_send_authentication(u80211_device_t *device, u80211_auth_data_t *auth_data); +int u80211_send_deauthentication(u80211_device_t *device, u80211_deauthentication_data_t *deauthentication_data); +int u80211_send_association_request(u80211_device_t *device, const void *information_elements, size_t information_elements_size); + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/rbtree.h b/kernel-src/io/net/u80211/include/u80211/rbtree.h new file mode 100644 index 00000000..4d7e5a10 --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/rbtree.h @@ -0,0 +1,24 @@ +#ifndef U80211_RBTREE_H +#define U80211_RBTREE_H + +typedef struct u80211_rbtree_t { + struct u80211_rbtree_t *left, *right, *parent; +} u80211_rbtree_t; + +// return -1 when a < b +// return 0 when a = b +// return 1 when a > b +typedef int (*u80211_rbtree_compare_fn_t)(u80211_rbtree_t *a, u80211_rbtree_t *b); +typedef int (*u80211_rbtree_value_compare_fn_t)(void *a, u80211_rbtree_t *b); + +u80211_rbtree_t *u80211_rbtree_lookup(u80211_rbtree_t *rbtree, void *key, u80211_rbtree_value_compare_fn_t compare_fn); +void u80211_rbtree_insert(u80211_rbtree_t **rbtreep, u80211_rbtree_t *node, u80211_rbtree_compare_fn_t compare_fn); +void u80211_rbtree_remove(u80211_rbtree_t **rbtreep, u80211_rbtree_t *node); +void u80211_rbtree_check(u80211_rbtree_t *rbtree); +u80211_rbtree_t *u80211_rbtree_first(u80211_rbtree_t *rbtree); +u80211_rbtree_t *u80211_rbtree_last(u80211_rbtree_t *rbtree); +u80211_rbtree_t *u80211_rbtree_successor(u80211_rbtree_t *node); +u80211_rbtree_t *u80211_rbtree_predecessor(u80211_rbtree_t *node); +u80211_rbtree_t *u80211_rbtree_find_first_larger_equal(u80211_rbtree_t *rbtree, void *key, u80211_rbtree_value_compare_fn_t compare_fn); + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/regulatory_database.h b/kernel-src/io/net/u80211/include/u80211/regulatory_database.h new file mode 100644 index 00000000..888d7740 --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/regulatory_database.h @@ -0,0 +1,17 @@ +#ifndef U80211_REGULATORY_DATABASE_H +#define U80211_REGULATORY_DATABASE_H + +// TODO: this will have safe defaults for transmission on all countries. +// eventually a proper "database" should be implemented to map a 2-character +// country code into channel rules. + +#define U80211_CHANNEL_RULES_DISABLED 1 // no receive/transmission. +#define U80211_CHANNEL_RULES_PASSIVE 2 // only transmit when a passive scan returns an AP in that channel. +typedef struct { + int flags; + int max_dbm; +} u80211_channel_rules_t; + +u80211_channel_rules_t u80211_get_channel_rules(int channel); + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/ringbuffer.h b/kernel-src/io/net/u80211/include/u80211/ringbuffer.h new file mode 100644 index 00000000..c96491c1 --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/ringbuffer.h @@ -0,0 +1,27 @@ +#ifndef U80211_RINGBUFFER_H +#define U80211_RINGBUFFER_H + +#include +#include + +typedef struct { + size_t size; + uintmax_t write; + uintmax_t read; + void *data; +} u80211_ringbuffer_t; + +int u80211_ringbuffer_init(u80211_ringbuffer_t *ringbuffer, size_t size); +void u80211_ringbuffer_destroy(u80211_ringbuffer_t *ringbuffer); + +size_t u80211_ringbuffer_read(u80211_ringbuffer_t *ringbuffer, void *buffer, size_t count); +size_t u80211_ringbuffer_write(u80211_ringbuffer_t *ringbuffer, const void *buffer, size_t count); +size_t u80211_ringbuffer_truncate(u80211_ringbuffer_t *ringbuffer, size_t count); +size_t u80211_ringbuffer_peek(const u80211_ringbuffer_t *ringbuffer, void *buffer, uintmax_t offset, size_t count); +size_t u80211_ringbuffer_remove(u80211_ringbuffer_t *ringbuffer, size_t count); + +#define U80211_RINGBUFFER_DATA_COUNT(x) ((x)->write - (x)->read) +#define U80211_RINGBUFFER_SIZE(x) ((x)->size) +#define U80211_RINGBUFFER_FREE_SPACE(x) (U80211_RINGBUFFER_SIZE(x) - U80211_RINGBUFFER_DATA_COUNT(x)) + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/scan.h b/kernel-src/io/net/u80211/include/u80211/scan.h new file mode 100644 index 00000000..4832dc57 --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/scan.h @@ -0,0 +1,8 @@ +#ifndef U80211_SCAN_H +#define U80211_SCAN_H + +#include + +void u80211_scan_process_response(u80211_device_t *device, u80211_beacon_data_t *beacon_data); + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/status.h b/kernel-src/io/net/u80211/include/u80211/status.h new file mode 100644 index 00000000..e68d4aab --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/status.h @@ -0,0 +1,16 @@ +#ifndef U80211_STATUS_H +#define U80211_STATUS_H + +#define U80211_STATUS_SUCCESS 0 +#define U80211_STATUS_ENOMEM 1 +#define U80211_STATUS_UNSUPPORTED 2 +#define U80211_STATUS_NOT_ENOUGH_SPACE 3 +#define U80211_STATUS_BUSY 4 +#define U80211_STATUS_NOT_PERMITTED 5 +#define U80211_STATUS_RETRY 6 +#define U80211_STATUS_REJECTED 7 +#define U80211_STATUS_TIMED_OUT 8 +#define U80211_STATUS_NOT_ASSOCIATED 9 +#define U80211_STATUS_UNKNOWN_ERROR 0xffff + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/string.h b/kernel-src/io/net/u80211/include/u80211/string.h new file mode 100644 index 00000000..2111c716 --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/string.h @@ -0,0 +1,10 @@ +#ifndef U80211_STRING_H +#define U80211_STRING_H + +#include + +void *u80211_memcpy(void *destination, const void *source, size_t size); +int u80211_memcmp(const void *a, const void *b, size_t size); +void *u80211_memset(void *destination, int value, size_t size); + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/u80211.h b/kernel-src/io/net/u80211/include/u80211/u80211.h new file mode 100644 index 00000000..a42aa51a --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/u80211.h @@ -0,0 +1,100 @@ +#ifndef U80211_U80211_H +#define U80211_U80211_H + +#include +#include +#include +#include +#include +#include +#include +#include + +static inline void *u80211_descriptor_allocate_space(u80211_tx_buffer_descriptor_t *descriptor, size_t size) { + if (size > descriptor->current_offset) + return NULL; + + descriptor->current_offset -= size; + return (uint8_t *)descriptor->data + descriptor->current_offset; +} + +typedef struct { + int (*allocate_tx_buffer)(u80211_device_t *device, size_t size, u80211_tx_buffer_descriptor_t *buffer_descriptor); + int (*free_tx_buffer)(u80211_device_t *device, u80211_tx_buffer_descriptor_t *buffer_descriptor); + int (*transmit)(u80211_device_t *device, u80211_tx_buffer_descriptor_t *buffer_descriptor, const u80211_transmit_options_t *options); + int (*set_channel)(u80211_device_t *device, int channel); + int (*set_key)(u80211_device_t *device, const u80211_key_t *key); + int (*del_key)(u80211_device_t *device, uint8_t index, const u80211_mac_address_t *peer, uint32_t flags); +} u80211_device_ops_t; + +typedef struct { + u80211_mac_address_t mac_address; + uint8_t rate_bitmap[16]; +} u80211_device_metadata_t; + +#define U80211_DEVICE_STATE_DOWN 0 +#define U80211_DEVICE_STATE_SCANNING 1 +#define U80211_DEVICE_STATE_AUTHENTICATING 2 +#define U80211_DEVICE_STATE_ASSOCIATING 3 +#define U80211_DEVICE_STATE_ASSOCIATED 4 +#define U80211_DEVICE_STATE_DEAUTHENTICATING 5 +struct u80211_device { + u80211_device_metadata_t metadata; + void *driver_data; + const u80211_device_ops_t *ops; + + int state; + size_t packet_count; + uint16_t tx_sequence_control; + uint16_t received_sequence_control; + bool received_sequence_control_valid; + void *key_spinlock; + u80211_list_t keys; + + void *scan_spinlock; + void *scan_context; + u80211_list_t scan_waiters; + + bss_cache_t bss_cache; + + void *association_spinlock; + void *association_context; + void *association_cleanup_work; + u80211_list_t association_waiters; + unsigned int association_generation; + int association_result; + u80211_ap_t *ap; + u80211_ap_t *disconnected_ap; +}; + +static inline int u80211_get_device_state(u80211_device_t *device) { + return __atomic_load_n(&device->state, __ATOMIC_ACQUIRE); +} + +static inline bool u80211_set_device_state(u80211_device_t *device, int old_state, int new_state) { + return __atomic_compare_exchange_n(&device->state, &old_state, new_state, false, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED); +} + +int u80211_scan(u80211_device_t *device); +int u80211_wait_for_scan_completion(u80211_device_t *device); + +void u80211_process_packet(u80211_device_t *device, void *packet, size_t packet_size); + +// these are mostly diagnostic and should not be relied upon +size_t u80211_get_packet_count(u80211_device_t *device); +void u80211_reset_packet_count(u80211_device_t *device); + +int u80211_register_device(const u80211_device_metadata_t *metadata, const u80211_device_ops_t *ops, void *driver_data, u80211_device_t **device_out); +void u80211_unregister_device(u80211_device_t *device); + +int u80211_associate(u80211_device_t *device, u80211_ap_t *ap, const void *information_elements, size_t information_elements_size); +int u80211_wait_for_association_completion(u80211_device_t *device); +int u80211_disassociate(u80211_device_t *device); + +// allocates an ethernet-sized buffer +int u80211_allocate_tx_buffer(u80211_device_t *device, u80211_tx_buffer_descriptor_t *descriptor); +// expects an ethernet header. descriptor **must** be a buffer returned by u80211_allocate_tx_buffer. +// consumes the descriptor regardless of the returned status. +int u80211_transmit_buffer(u80211_device_t *device, u80211_tx_buffer_descriptor_t *descriptor); + +#endif diff --git a/kernel-src/io/net/u80211/include/u80211/util.h b/kernel-src/io/net/u80211/include/u80211/util.h new file mode 100644 index 00000000..73977c0f --- /dev/null +++ b/kernel-src/io/net/u80211/include/u80211/util.h @@ -0,0 +1,85 @@ +#ifndef U80211_UTIL_H +#define U80211_UTIL_H + +#include + +#include + +static inline uint16_t u80211_host_to_le16(uint16_t value) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return value; +#else + return __builtin_bswap16(value); +#endif +} + +static inline uint32_t u80211_host_to_le32(uint32_t value) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return value; +#else + return __builtin_bswap32(value); +#endif +} + +static inline uint64_t u80211_host_to_le64(uint64_t value) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + return value; +#else + return __builtin_bswap64(value); +#endif +} + +static inline uint16_t u80211_le_to_host16(uint16_t value) { + return u80211_host_to_le16(value); +} + +static inline uint32_t u80211_le_to_host32(uint32_t value) { + return u80211_host_to_le32(value); +} + +static inline uint64_t u80211_le_to_host64(uint64_t value) { + return u80211_host_to_le64(value); +} + +#define host_to_le(value) _Generic((value), \ + uint16_t: u80211_host_to_le16, \ + uint32_t: u80211_host_to_le32, \ + uint64_t: u80211_host_to_le64 \ +)(value) + +#define le_to_host(value) _Generic((value), \ + uint16_t: u80211_le_to_host16, \ + uint32_t: u80211_le_to_host32, \ + uint64_t: u80211_le_to_host64 \ +)(value) + +static inline uint16_t deserialize_le16(const void *source) { + uint16_t value; + u80211_memcpy(&value, source, sizeof(value)); + return le_to_host(value); +} + +static inline uint32_t deserialize_le32(const void *source) { + uint32_t value; + u80211_memcpy(&value, source, sizeof(value)); + return le_to_host(value); +} + +static inline void serialize_le16(uint8_t *destination, uint16_t value) { + uint16_t little_endian_value = host_to_le(value); + u80211_memcpy(destination, &little_endian_value, sizeof(little_endian_value)); +} + +static inline void serialize_le32(uint8_t *destination, uint32_t value) { + uint32_t little_endian_value = host_to_le(value); + u80211_memcpy(destination, &little_endian_value, sizeof(little_endian_value)); +} + +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#define min(a, b) ((a) < (b) ? (a) : (b)) + +#define container_of(ptr, type, member) \ + ((type *)((uintptr_t)ptr - offsetof(type, member))) + +#endif diff --git a/kernel-src/io/net/u80211/kernel_interface.c b/kernel-src/io/net/u80211/kernel_interface.c new file mode 100644 index 00000000..f4de219f --- /dev/null +++ b/kernel-src/io/net/u80211/kernel_interface.c @@ -0,0 +1,344 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static scache_t *pushlock_cache; +static scache_t *semaphore_cache; +static scache_t *spinlock_cache; +static scache_t *work_cache; + +void *u80211_kernel_allocate(size_t size) { + return alloc(size); +} + +void u80211_kernel_free(void *ptr) { + free(ptr); +} + +void *u80211_kernel_allocate_mutex(void) { + mutex_t *mutex = slab_allocate(pushlock_cache); + if (mutex) + MUTEX_INIT(mutex); + + return mutex; +} + +void u80211_kernel_free_mutex(void *mutex) { + slab_free(pushlock_cache, mutex); +} + +void u80211_kernel_acquire_mutex(void *mutex) { + MUTEX_ACQUIRE(mutex); +} + +void u80211_kernel_release_mutex(void *mutex) { + MUTEX_RELEASE(mutex); +} + +void *u80211_kernel_allocate_semaphore(unsigned int initial_count) { + semaphore_t *semaphore = slab_allocate(semaphore_cache); + if (semaphore) + SEMAPHORE_INIT(semaphore, initial_count); + + return semaphore; +} + +void u80211_kernel_free_semaphore(void *semaphore) { + slab_free(semaphore_cache, semaphore); +} + +void u80211_kernel_wait_semaphore(void *semaphore) { + semaphore_wait(semaphore, false); +} + +void u80211_kernel_signal_semaphore(void *semaphore) { + semaphore_signal(semaphore); +} + +typedef struct { + spinlock_t lock; + long old_ipl; +} u80211_spinlock_t; + + +void *u80211_kernel_allocate_spinlock(void) { + u80211_spinlock_t *spinlock = slab_allocate(spinlock_cache); + if (spinlock) + SPINLOCK_INIT(spinlock->lock); + + return spinlock; +} + +void u80211_kernel_free_spinlock(void *spinlock) { + slab_free(spinlock_cache, spinlock); +} + +void u80211_kernel_acquire_spinlock(void *opaque_spinlock) { + u80211_spinlock_t *spinlock = opaque_spinlock; + spinlock->old_ipl = spinlock_acquire_raise_ipl(&spinlock->lock, IPL_NET); +} + +void u80211_kernel_release_spinlock(void *opaque_spinlock) { + u80211_spinlock_t *spinlock = opaque_spinlock; + spinlock_release_lower_ipl(&spinlock->lock, spinlock->old_ipl); +} + +void *u80211_kernel_allocate_rwlock(void) { + pushlock_t *rwlock = slab_allocate(pushlock_cache); + if (rwlock) + *rwlock = 0; + + return rwlock; +} + +void u80211_kernel_free_rwlock(void *rwlock) { + slab_free(pushlock_cache, rwlock); +} + +void u80211_kernel_acquire_rwlock_exclusive(void *rwlock) { + pushlock_acquire_exclusive(rwlock); +} + +void u80211_kernel_acquire_rwlock_shared(void *rwlock) { + pushlock_acquire_shared(rwlock); +} + +void u80211_kernel_release_rwlock_exclusive(void *rwlock) { + pushlock_release_exclusive(rwlock); +} + +void u80211_kernel_release_rwlock_shared(void *rwlock) { + pushlock_release_shared(rwlock); +} + +typedef struct u80211_work_t { + struct u80211_work_t *next; + timespec_t deadline; + u80211_kernel_work_fn_t function; + void *context; + eventheader_t finished_event; + bool pending; + bool running; + bool freeing; + bool destroy_on_return; +} u80211_work_t; + +static spinlock_t work_queue_lock; +static eventheader_t work_queue_event; +static u80211_work_t *work_queue; +static u80211_work_t *current_work; +static thread_t *worker_thread; + +static long acquire_work_queue_lock(void) { + return spinlock_acquire_raise_ipl(&work_queue_lock, IPL_NET); +} + +static void release_work_queue_lock(long old_ipl) { + spinlock_release_lower_ipl(&work_queue_lock, old_ipl); +} + +static timespec_t deadline_after_ms(size_t ms) { + timespec_t delay = { + .s = ms / 1000, + .ns = (ms % 1000) * 1000000, + }; + + return timespec_add(timekeeper_timefromboot(), delay); +} + +static void insert_work(u80211_work_t *work) { + // insert in the ordered list + u80211_work_t **entry = &work_queue; + while (*entry && !timespec_bigger((*entry)->deadline, work->deadline)) + entry = &(*entry)->next; + + work->next = *entry; + *entry = work; + work->pending = true; +} + +static bool remove_work(u80211_work_t *work) { + // find previous in the ordered list + u80211_work_t **entry = &work_queue; + while (*entry && *entry != work) + entry = &(*entry)->next; + + if (*entry == NULL) + return false; + + *entry = work->next; + work->next = NULL; + work->pending = false; + return true; +} + +static time_t deadline_wait_us(timespec_t deadline, timespec_t now) { + return (deadline.s - now.s) * 1000000 + (deadline.ns - now.ns) / 1000; +} + +static void wake_work_thread(void) { + EVENT_SIGNAL(&work_queue_event); +} + +static void work_thread(void) { + for (;;) { + eventlistener_t listener; + EVENT_INITLISTENER(&listener); + EVENT_ATTACH(&listener, &work_queue_event); + + long old_ipl = acquire_work_queue_lock(); + u80211_work_t *work = work_queue; + if (work == NULL) { + release_work_queue_lock(old_ipl); + EVENT_WAIT(&listener, 0); + EVENT_DETACHALL(&listener); + continue; + } + + timespec_t now = timekeeper_timefromboot(); + if (timespec_bigger(work->deadline, now)) { + time_t wait_us = deadline_wait_us(work->deadline, now); + release_work_queue_lock(old_ipl); + EVENT_WAIT(&listener, wait_us); + EVENT_DETACHALL(&listener); + continue; + } + + work_queue = work->next; + + work->next = NULL; + work->pending = false; + work->running = true; + + current_work = work; + + u80211_kernel_work_fn_t function = work->function; + void *context = work->context; + + release_work_queue_lock(old_ipl); + EVENT_DETACHALL(&listener); + + function(context); + + old_ipl = acquire_work_queue_lock(); + current_work = NULL; + work->running = false; + bool destroy = work->freeing && work->destroy_on_return; + bool notify = work->freeing && !work->destroy_on_return; + release_work_queue_lock(old_ipl); + + if (notify) + EVENT_SIGNAL(&work->finished_event); + else if (destroy) + slab_free(work_cache, work); + } +} + +void *u80211_kernel_allocate_work(void) { + u80211_work_t *work = slab_allocate(work_cache); + if (work) { + memset(work, 0, sizeof(*work)); + EVENT_INITHEADER(&work->finished_event); + } + + return work; +} + +void u80211_kernel_enqueue_work(void *opaque_work, u80211_kernel_work_fn_t function, void *context, size_t ms) { + u80211_work_t *work = opaque_work; + timespec_t deadline = deadline_after_ms(ms); + + long old_ipl = acquire_work_queue_lock(); + if (!work->pending && !work->freeing) { + work->deadline = deadline; + work->function = function; + work->context = context; + + insert_work(work); + release_work_queue_lock(old_ipl); + + wake_work_thread(); + return; + } + + release_work_queue_lock(old_ipl); +} + +void u80211_kernel_free_work(void *opaque_work) { + u80211_work_t *work = opaque_work; + bool removed; + eventlistener_t listener; + EVENT_INITLISTENER(&listener); + EVENT_ATTACH(&listener, &work->finished_event); + + long old_ipl = acquire_work_queue_lock(); + removed = remove_work(work); + if (!work->running) { + release_work_queue_lock(old_ipl); + EVENT_DETACHALL(&listener); + + if (removed) + wake_work_thread(); + + slab_free(work_cache, work); + return; + } + + work->freeing = true; + if (current_thread() == worker_thread && current_work == work) { + work->destroy_on_return = true; + release_work_queue_lock(old_ipl); + EVENT_DETACHALL(&listener); + + if (removed) + wake_work_thread(); + + return; + } + + release_work_queue_lock(old_ipl); + + if (removed) + wake_work_thread(); + + EVENT_WAIT(&listener, 0); + EVENT_DETACHALL(&listener); + slab_free(work_cache, work); +} + +static void u80211_kernel_interface_init(void) { + pushlock_cache = slab_newcache(sizeof(pushlock_t), 0, NULL, NULL); + semaphore_cache = slab_newcache(sizeof(semaphore_t), 0, NULL, NULL); + spinlock_cache = slab_newcache(sizeof(u80211_spinlock_t), 0, NULL, NULL); + work_cache = slab_newcache(sizeof(u80211_work_t), 0, NULL, NULL); + __assert(pushlock_cache && semaphore_cache && spinlock_cache && work_cache); + + SPINLOCK_INIT(work_queue_lock); + EVENT_INITHEADER(&work_queue_event); + + worker_thread = sched_newthread(work_thread, PAGE_SIZE * 4, 0, NULL, NULL); + __assert(worker_thread); + sched_queue(worker_thread); +} + +INIT_ROUTINE_DEFINE(u80211, INIT_ROUTINE_FLAGS_NONE, u80211_kernel_interface_init, scheduler); + +void u80211_kernel_receive_callback(u80211_device_t *device, void *buffer, size_t size) { + (void)size; + eth_process(device->driver_data, buffer); +} diff --git a/kernel-src/io/net/u80211/src/ap.c b/kernel-src/io/net/u80211/src/ap.c new file mode 100644 index 00000000..42c10625 --- /dev/null +++ b/kernel-src/io/net/u80211/src/ap.c @@ -0,0 +1,40 @@ +#include +#include +#include + +u80211_ap_t *u80211_ap_allocate(const u80211_beacon_data_t *beacon_data) { + u80211_ap_t *ap = u80211_kernel_allocate(sizeof(*ap)); + if (ap == NULL) + return NULL; + + ap->rsn_size = beacon_data->rsn_size; + if (ap->rsn_size) { + ap->rsn = u80211_kernel_allocate(ap->rsn_size); + if (ap->rsn == NULL) { + u80211_kernel_free(ap); + return NULL; + } + + u80211_memcpy(ap->rsn, beacon_data->rsn, ap->rsn_size); + } else { + ap->rsn = NULL; + } + + __atomic_store_n(&ap->refcount, 1, __ATOMIC_RELAXED); + ap->mac_address = beacon_data->mac_address; + ap->interval = beacon_data->interval; + ap->capabilities = beacon_data->capabilities; + ap->channel = beacon_data->channel; + u80211_memcpy(ap->rate_bitmap, beacon_data->rate_bitmap, sizeof(ap->rate_bitmap)); + u80211_memcpy(ap->ssid, beacon_data->ssid, sizeof(ap->ssid)); + return ap; +} + +void u80211_ap_inactive(u80211_ap_t *ap) { + __atomic_thread_fence(__ATOMIC_ACQ_REL); + + if (ap->rsn != NULL) + u80211_kernel_free(ap->rsn); + + u80211_kernel_free(ap); +} diff --git a/kernel-src/io/net/u80211/src/association.c b/kernel-src/io/net/u80211/src/association.c new file mode 100644 index 00000000..ed11ecb0 --- /dev/null +++ b/kernel-src/io/net/u80211/src/association.c @@ -0,0 +1,372 @@ +#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 *assoc_timeout_work; + unsigned int generation; + unsigned int timeouts_in_flight; + 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_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 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) { + device->association_context = NULL; + device->association_result = U80211_STATUS_REJECTED; + wake_association_waiters(device, device->association_result); + } 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, release_disconnected_ap, device, 0); +} + +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) { + 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); + + device->association_context = NULL; + ++device->association_generation; + device->association_result = association_data->status ? U80211_STATUS_REJECTED : U80211_STATUS_SUCCESS; + wake_association_waiters(device, device->association_result); + +leave: + u80211_kernel_release_spinlock(device->association_spinlock); +} + +static void association_timeout(void *ctx, int expected_state) { + u80211_association_context_t *association_context = ctx; + u80211_device_t *device = association_context->device; + bool free = true; + + u80211_kernel_acquire_spinlock(device->association_spinlock); + + // not associating/different association generation + if (!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; + device->association_context = NULL; + ++device->association_generation; + device->association_result = U80211_STATUS_TIMED_OUT; + wake_association_waiters(device, device->association_result); + } else { + // already progressed + free = false; + } + +leave: + u80211_kernel_release_spinlock(device->association_spinlock); + if (__atomic_sub_fetch(&association_context->timeouts_in_flight, 1, __ATOMIC_ACQ_REL) == 0 && free) { + u80211_ap_release(association_context->ap); + destroy_association_context(association_context); + } +} + +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); + __atomic_add_fetch(&association_context->timeouts_in_flight, 1, __ATOMIC_RELAXED); + u80211_kernel_enqueue_work(association_context->assoc_timeout_work, assoc_timeout, association_context, ASSOC_TIMEOUT); +} + +void u80211_association_process_authentication(u80211_device_t *device, u80211_auth_data_t *auth_data) { + 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; + + device->ap = NULL; + device->association_context = NULL; + ++device->association_generation; + device->association_result = U80211_STATUS_REJECTED; + wake_association_waiters(device, device->association_result); + } 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, 0); + } + +leave: + u80211_kernel_release_spinlock(device->association_spinlock); +} + +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->assoc_timeout_work = u80211_kernel_allocate_work(); + if (association_context->assoc_timeout_work == 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->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; + __atomic_store_n(&association_context->timeouts_in_flight, 1, __ATOMIC_RELAXED); + + u80211_kernel_acquire_spinlock(device->association_spinlock); + if (!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, ap->channel); + u80211_send_authentication(device, &auth_data); + + u80211_kernel_enqueue_work(association_context->auth_timeout_work, 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) { + 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; +} diff --git a/kernel-src/io/net/u80211/src/bss_cache.c b/kernel-src/io/net/u80211/src/bss_cache.c new file mode 100644 index 00000000..796d6e4d --- /dev/null +++ b/kernel-src/io/net/u80211/src/bss_cache.c @@ -0,0 +1,157 @@ +#include + +#include +#include +#include +#include +#include + +static int mac_compare(u80211_mac_address_t *a, u80211_mac_address_t *b) { + for (size_t i = 0; i < sizeof(a->bytes); i++) { + if (a->bytes[i] < b->bytes[i]) + return -1; + if (a->bytes[i] > b->bytes[i]) + return 1; + } + + return 0; +} + +static int rbtree_mac_compare(u80211_rbtree_t *a, u80211_rbtree_t *b) { + u80211_ap_t *ap_a = container_of(a, u80211_ap_t, cache_node); + u80211_ap_t *ap_b = container_of(b, u80211_ap_t, cache_node); + + return mac_compare(&ap_a->mac_address, &ap_b->mac_address); +} + +static int rbtree_value_compare(void *a, u80211_rbtree_t *b) { + u80211_ap_t *ap_b = container_of(b, u80211_ap_t, cache_node); + + return mac_compare(a, &ap_b->mac_address); +} + +int u80211_bss_cache_init(bss_cache_t *cache) { + cache->root = NULL; + cache->entry_count = 0; + cache->rwlock = u80211_kernel_allocate_rwlock(); + if (unlikely(cache->rwlock == NULL)) + return U80211_STATUS_ENOMEM; + + return U80211_STATUS_SUCCESS; +} + +void u80211_bss_cache_deinit(bss_cache_t *cache) { + u80211_kernel_acquire_rwlock_exclusive(cache->rwlock); + + u80211_rbtree_t *iterator = cache->root == NULL ? NULL : u80211_rbtree_first(cache->root); + while (iterator != NULL) { + u80211_rbtree_t *next = u80211_rbtree_successor(iterator); + u80211_ap_t *ap = container_of(iterator, u80211_ap_t, cache_node); + + u80211_rbtree_remove(&cache->root, iterator); + --cache->entry_count; + u80211_ap_release(ap); + iterator = next; + } + + u80211_kernel_release_rwlock_exclusive(cache->rwlock); + u80211_kernel_free_rwlock(cache->rwlock); + cache->rwlock = NULL; + cache->root = NULL; +} + +void u80211_bss_cache_purge(bss_cache_t *cache) { + u80211_kernel_acquire_rwlock_exclusive(cache->rwlock); + + u80211_rbtree_t *iterator = cache->root == NULL ? NULL : u80211_rbtree_first(cache->root); + while (iterator != NULL) { + u80211_rbtree_t *next = u80211_rbtree_successor(iterator); + + u80211_ap_t *ap = container_of(iterator, u80211_ap_t, cache_node); + if (__atomic_load_n(&ap->refcount, __ATOMIC_RELAXED) == 1) { + u80211_rbtree_remove(&cache->root, iterator); + --cache->entry_count; + u80211_ap_release(ap); + } + + iterator = next; + } + + u80211_kernel_release_rwlock_exclusive(cache->rwlock); +} + +void u80211_bss_cache_insert(bss_cache_t *cache, u80211_ap_t *ap) { + u80211_kernel_acquire_rwlock_exclusive(cache->rwlock); + + u80211_rbtree_t *node = u80211_rbtree_lookup(cache->root, &ap->mac_address, rbtree_value_compare); + if (node != NULL) { + u80211_kernel_release_rwlock_exclusive(cache->rwlock); + return; + } + + u80211_rbtree_insert(&cache->root, &ap->cache_node, rbtree_mac_compare); + u80211_ap_hold(ap); + ++cache->entry_count; + + u80211_kernel_release_rwlock_exclusive(cache->rwlock); +} + +void u80211_bss_cache_remove(bss_cache_t *cache, u80211_mac_address_t *mac) { + u80211_kernel_acquire_rwlock_exclusive(cache->rwlock); + + u80211_rbtree_t *node = u80211_rbtree_lookup(cache->root, mac, rbtree_value_compare); + if (node == NULL) { + u80211_kernel_release_rwlock_exclusive(cache->rwlock); + return; + } + + u80211_rbtree_remove(&cache->root, node); + --cache->entry_count; + + u80211_ap_t *ap = container_of(node, u80211_ap_t, cache_node); + u80211_ap_release(ap); + + u80211_kernel_release_rwlock_exclusive(cache->rwlock); +} + +u80211_ap_t *u80211_bss_cache_find(bss_cache_t *cache, u80211_mac_address_t *mac) { + u80211_kernel_acquire_rwlock_shared(cache->rwlock); + + u80211_rbtree_t *node = u80211_rbtree_lookup(cache->root, mac, rbtree_value_compare); + if (node == NULL) { + u80211_kernel_release_rwlock_shared(cache->rwlock); + return NULL; + } + + u80211_ap_t *ap = container_of(node, u80211_ap_t, cache_node); + u80211_ap_hold(ap); + + u80211_kernel_release_rwlock_shared(cache->rwlock); + + return ap; +} + +size_t u80211_bss_cache_get_aps(bss_cache_t *cache, u80211_ap_t **buffer, size_t capacity) { + u80211_kernel_acquire_rwlock_shared(cache->rwlock); + + size_t count = 0; + u80211_rbtree_t *iterator = cache->root == NULL ? NULL : u80211_rbtree_first(cache->root); + while (iterator != NULL && count < capacity) { + u80211_ap_t *ap = container_of(iterator, u80211_ap_t, cache_node); + u80211_ap_hold(ap); + buffer[count++] = ap; + iterator = u80211_rbtree_successor(iterator); + } + + u80211_kernel_release_rwlock_shared(cache->rwlock); + + return count; +} + +size_t u80211_bss_cache_get_count(bss_cache_t *cache) { + u80211_kernel_acquire_rwlock_shared(cache->rwlock); + size_t count = cache->entry_count; + u80211_kernel_release_rwlock_shared(cache->rwlock); + + return count; +} diff --git a/kernel-src/io/net/u80211/src/device.c b/kernel-src/io/net/u80211/src/device.c new file mode 100644 index 00000000..b160f4d1 --- /dev/null +++ b/kernel-src/io/net/u80211/src/device.c @@ -0,0 +1,82 @@ +#include +#include +#include + +int u80211_key_state_init(u80211_device_t *device); +void u80211_key_state_deinit(u80211_device_t *device); + +int u80211_register_device(const u80211_device_metadata_t *metadata, const u80211_device_ops_t *ops, void *driver_data, u80211_device_t **device_out) { + u80211_device_t *device = u80211_kernel_allocate(sizeof(*device)); + if (device == NULL) + return U80211_STATUS_ENOMEM; + + device->metadata = *metadata; + device->packet_count = 0; + device->tx_sequence_control = 0; + device->received_sequence_control = 0; + device->received_sequence_control_valid = false; + device->state = U80211_DEVICE_STATE_DOWN; + int status = u80211_key_state_init(device); + if (status != U80211_STATUS_SUCCESS) { + u80211_kernel_free(device); + return status; + } + + device->scan_spinlock = u80211_kernel_allocate_spinlock(); + if (device->scan_spinlock == NULL) { + u80211_key_state_deinit(device); + u80211_kernel_free(device); + return U80211_STATUS_ENOMEM; + } + device->scan_context = NULL; + u80211_list_init(&device->scan_waiters); + + device->association_spinlock = u80211_kernel_allocate_spinlock(); + if (device->association_spinlock == NULL) { + u80211_kernel_free_spinlock(device->scan_spinlock); + u80211_key_state_deinit(device); + u80211_kernel_free(device); + return U80211_STATUS_ENOMEM; + } + device->association_cleanup_work = u80211_kernel_allocate_work(); + if (device->association_cleanup_work == NULL) { + u80211_kernel_free_spinlock(device->association_spinlock); + u80211_kernel_free_spinlock(device->scan_spinlock); + u80211_key_state_deinit(device); + u80211_kernel_free(device); + return U80211_STATUS_ENOMEM; + } + device->association_context = NULL; + u80211_list_init(&device->association_waiters); + device->association_generation = 0; + device->association_result = U80211_STATUS_UNKNOWN_ERROR; + device->ap = NULL; + device->disconnected_ap = NULL; + + device->ops = ops; + device->driver_data = driver_data; + + status = u80211_bss_cache_init(&device->bss_cache); + if (status != U80211_STATUS_SUCCESS) { + u80211_kernel_free_work(device->association_cleanup_work); + u80211_kernel_free_spinlock(device->association_spinlock); + u80211_kernel_free_spinlock(device->scan_spinlock); + u80211_key_state_deinit(device); + u80211_kernel_free(device); + return status; + } + + *device_out = device; + return U80211_STATUS_SUCCESS; +} + +void u80211_unregister_device(u80211_device_t *device) { + u80211_key_state_deinit(device); + u80211_kernel_free_work(device->association_cleanup_work); + if (device->disconnected_ap != NULL) + u80211_ap_release(device->disconnected_ap); + u80211_bss_cache_deinit(&device->bss_cache); + u80211_kernel_free_spinlock(device->association_spinlock); + u80211_kernel_free_spinlock(device->scan_spinlock); + u80211_kernel_free(device); +} diff --git a/kernel-src/io/net/u80211/src/key.c b/kernel-src/io/net/u80211/src/key.c new file mode 100644 index 00000000..b84af027 --- /dev/null +++ b/kernel-src/io/net/u80211/src/key.c @@ -0,0 +1,304 @@ +#include +#include +#include +#include +#include + +#define SEQUENCE_SIZE 6 +#define TKIP_KEY_SIZE 32 +#define TKIP_TX_MIC_KEY_OFFSET 16 +#define TKIP_RX_MIC_KEY_OFFSET 24 +#define MIC_SIZE 8 + +typedef struct { + u80211_list_node_t node; + u80211_cipher_t cipher; + uint8_t index; + u80211_mac_address_t peer; + uint32_t flags; + uint8_t rx_sequence[SEQUENCE_SIZE]; + uint8_t tx_sequence[SEQUENCE_SIZE]; + size_t key_len; + uint8_t key[]; +} u80211_key_metadata_t; + +static void destroy_key_metadata(u80211_key_metadata_t *metadata) { + // TODO: proper memset for zeroing crypto data + u80211_memset(metadata, 0, sizeof(*metadata) + metadata->key_len); + u80211_kernel_free(metadata); +} + +static bool key_identity_equal(const u80211_key_metadata_t *metadata, uint8_t index, const u80211_mac_address_t *peer, uint32_t flags) { + return metadata->index == index && metadata->flags == flags && u80211_mac_address_equal(&metadata->peer, peer); +} + +int u80211_key_state_init(u80211_device_t *device) { + u80211_list_init(&device->keys); + device->key_spinlock = u80211_kernel_allocate_spinlock(); + if (device->key_spinlock == NULL) + return U80211_STATUS_ENOMEM; + + return U80211_STATUS_SUCCESS; +} + +void u80211_key_state_deinit(u80211_device_t *device) { + u80211_list_node_t *node; + while ((node = u80211_list_pop_front(&device->keys)) != NULL) + destroy_key_metadata(container_of(node, u80211_key_metadata_t, node)); + + u80211_kernel_free_spinlock(device->key_spinlock); + device->key_spinlock = NULL; +} + +int u80211_set_key(u80211_device_t *device, const u80211_key_t *key) { + if (device->ops->set_key == NULL) + return U80211_STATUS_UNSUPPORTED; + + if ((key->key == NULL && key->key_len != 0) || key->key_len > SIZE_MAX - sizeof(u80211_key_metadata_t)) + return U80211_STATUS_NOT_PERMITTED; + + u80211_key_metadata_t *metadata = u80211_kernel_allocate(sizeof(*metadata) + key->key_len); + if (metadata == NULL) + return U80211_STATUS_ENOMEM; + + metadata->index = key->index; + metadata->cipher = key->cipher; + metadata->peer = key->peer; + metadata->flags = key->flags; + metadata->key_len = key->key_len; + + u80211_memset(metadata->rx_sequence, 0, sizeof(metadata->rx_sequence)); + u80211_memset(metadata->tx_sequence, 0, sizeof(metadata->tx_sequence)); + + if (key->rx_seq != NULL && key->rx_seq_len == sizeof(metadata->rx_sequence)) + u80211_memcpy(metadata->rx_sequence, key->rx_seq, sizeof(metadata->rx_sequence)); + + if (key->key_len != 0) + u80211_memcpy(metadata->key, key->key, key->key_len); + + int status = device->ops->set_key(device, key); + if (status != U80211_STATUS_SUCCESS) { + destroy_key_metadata(metadata); + return status; + } + + u80211_key_metadata_t *old_metadata = NULL; + u80211_kernel_acquire_spinlock(device->key_spinlock); + u80211_list_for_each(&device->keys, node) { + u80211_key_metadata_t *candidate = container_of(node, u80211_key_metadata_t, node); + if (key_identity_equal(candidate, key->index, &key->peer, key->flags)) { + old_metadata = candidate; + u80211_list_remove(&device->keys, node); + break; + } + } + u80211_list_push_front(&device->keys, &metadata->node); + u80211_kernel_release_spinlock(device->key_spinlock); + + if (old_metadata != NULL) + destroy_key_metadata(old_metadata); + return U80211_STATUS_SUCCESS; +} + +int u80211_del_key(u80211_device_t *device, uint8_t index, const u80211_mac_address_t *peer, uint32_t flags) { + if (device->ops->del_key == NULL) + return U80211_STATUS_UNSUPPORTED; + + int status = device->ops->del_key(device, index, peer, flags); + if (status != U80211_STATUS_SUCCESS) + return status; + + u80211_key_metadata_t *removed_metadata = NULL; + u80211_kernel_acquire_spinlock(device->key_spinlock); + u80211_list_for_each(&device->keys, node) { + u80211_key_metadata_t *candidate = container_of(node, u80211_key_metadata_t, node); + if (key_identity_equal(candidate, index, peer, flags)) { + removed_metadata = candidate; + u80211_list_remove(&device->keys, node); + break; + } + } + u80211_kernel_release_spinlock(device->key_spinlock); + + if (removed_metadata != NULL) + destroy_key_metadata(removed_metadata); + return U80211_STATUS_SUCCESS; +} + +static bool key_matches_header(const u80211_key_metadata_t *metadata, u80211_device_t *device, const u80211_header_description_t *header) { + bool tx = u80211_mac_address_equal(&header->addresses[1], &device->metadata.mac_address); + bool group = header->addresses[0].bytes[0] & 1; + uint32_t direction_flag = tx ? U80211_KEY_TX : U80211_KEY_RX; + uint32_t type_flag = group ? U80211_KEY_GROUP : U80211_KEY_PAIRWISE; + const u80211_mac_address_t *peer = tx ? &header->addresses[0] : &header->addresses[1]; + + return (metadata->flags & direction_flag) && (metadata->flags & type_flag) && (group || u80211_mac_address_equal(&metadata->peer, peer)); +} + +// expect key spinlock to be locked +static u80211_key_metadata_t *select_key(u80211_device_t *device, const u80211_header_description_t *header) { + u80211_list_for_each(&device->keys, node) { + u80211_key_metadata_t *metadata = container_of(node, u80211_key_metadata_t, node); + if (key_matches_header(metadata, device, header)) + return metadata; + } + return NULL; +} + +// expect key spinlock to be locked +static u80211_key_metadata_t *select_key_by_index(u80211_device_t *device, const u80211_header_description_t *header, uint8_t index) { + u80211_list_for_each(&device->keys, node) { + u80211_key_metadata_t *metadata = container_of(node, u80211_key_metadata_t, node); + if (metadata->index == index && key_matches_header(metadata, device, header)) + return metadata; + } + return NULL; +} + +int u80211_select_key(u80211_device_t *device, const u80211_header_description_t *header) { + u80211_kernel_acquire_spinlock(device->key_spinlock); + u80211_key_metadata_t *metadata = select_key(device, header); + int index = metadata == NULL ? -1 : metadata->index; + u80211_kernel_release_spinlock(device->key_spinlock); + return index; +} + +int u80211_select_cipher(u80211_device_t *device, const u80211_header_description_t *header) { + u80211_kernel_acquire_spinlock(device->key_spinlock); + + u80211_key_metadata_t *metadata = select_key(device, header); + int cipher = -1; + if (metadata) + cipher = metadata->cipher; + + u80211_kernel_release_spinlock(device->key_spinlock); + return cipher; +} + +int u80211_select_cipher_by_index(u80211_device_t *device, const u80211_header_description_t *header, uint8_t index) { + u80211_kernel_acquire_spinlock(device->key_spinlock); + + u80211_key_metadata_t *metadata = select_key_by_index(device, header, index); + int cipher = -1; + if (metadata) + cipher = metadata->cipher; + + u80211_kernel_release_spinlock(device->key_spinlock); + return cipher; +} + +static bool sequence_is_newer(const uint8_t *sequence, const uint8_t *previous, size_t size) { + for (size_t i = size; i != 0; --i) { + if (sequence[i - 1] != previous[i - 1]) + return sequence[i - 1] > previous[i - 1]; + } + + return false; +} + +static bool mic_equal(const uint8_t a[MIC_SIZE], const uint8_t b[MIC_SIZE]) { + uint8_t difference = 0; + for (size_t i = 0; i < MIC_SIZE; ++i) + difference |= a[i] ^ b[i]; + return difference == 0; +} + +bool u80211_key_validate_tkip_rx(u80211_device_t *device, const u80211_tkip_data_t *data) { + bool valid = false; + u80211_kernel_acquire_spinlock(device->key_spinlock); + + u80211_key_metadata_t *metadata = select_key_by_index(device, data->header, data->key_index); + if (metadata == NULL || metadata->cipher != U80211_CIPHER_TKIP || metadata->key_len != TKIP_KEY_SIZE) + goto unlock; + if (!sequence_is_newer(data->sequence, metadata->rx_sequence, SEQUENCE_SIZE)) + goto unlock; + + uint8_t calculated_mic[MIC_SIZE]; + u80211_michael_mic(metadata->key + TKIP_RX_MIC_KEY_OFFSET, data->destination->bytes, data->source->bytes, + data->priority, data->data, data->data_size, calculated_mic); + if (!mic_equal(calculated_mic, data->mic)) + goto unlock; + + u80211_memcpy(metadata->rx_sequence, data->sequence, SEQUENCE_SIZE); + valid = true; + +unlock: + u80211_kernel_release_spinlock(device->key_spinlock); + return valid; +} + +bool u80211_key_update_rx_sequence(u80211_device_t *device, const u80211_header_description_t *header, uint8_t index, u80211_cipher_t cipher, const uint8_t *sequence, size_t sequence_size) { + if (sequence_size != SEQUENCE_SIZE) + return false; + + u80211_kernel_acquire_spinlock(device->key_spinlock); + u80211_key_metadata_t *metadata = select_key_by_index(device, header, index); + bool valid = metadata != NULL && metadata->cipher == cipher && sequence_is_newer(sequence, metadata->rx_sequence, sequence_size); + if (valid) + u80211_memcpy(metadata->rx_sequence, sequence, sequence_size); + + u80211_kernel_release_spinlock(device->key_spinlock); + return valid; +} + +static bool increment_sequence(uint8_t *sequence, size_t size) { + // find where to increment + size_t carry_end = 0; + while (carry_end < size && sequence[carry_end] == UINT8_MAX) + carry_end++; + + // can't increase sequence + if (carry_end == size) + return false; + + u80211_memset(sequence, 0, carry_end); + sequence[carry_end]++; + return true; +} + +bool u80211_key_prepare_tkip_tx(u80211_device_t *device, u80211_tkip_data_t *data) { + bool valid = false; + u80211_kernel_acquire_spinlock(device->key_spinlock); + + u80211_key_metadata_t *metadata = select_key_by_index(device, data->header, data->key_index); + if (metadata == NULL || metadata->cipher != U80211_CIPHER_TKIP || metadata->key_len != TKIP_KEY_SIZE) + goto unlock; + + uint8_t sequence[SEQUENCE_SIZE]; + u80211_memcpy(sequence, metadata->tx_sequence, sizeof(sequence)); + if (!increment_sequence(sequence, sizeof(sequence))) + goto unlock; + + uint8_t mic[MIC_SIZE]; + u80211_michael_mic(metadata->key + TKIP_TX_MIC_KEY_OFFSET, data->destination->bytes, data->source->bytes, + data->priority, data->data, data->data_size, mic); + + u80211_memcpy(metadata->tx_sequence, sequence, sizeof(sequence)); + u80211_memcpy(data->sequence, sequence, sizeof(sequence)); + u80211_memcpy(data->mic, mic, sizeof(mic)); + valid = true; + +unlock: + u80211_kernel_release_spinlock(device->key_spinlock); + return valid; +} + +bool u80211_key_next_tx_sequence(u80211_device_t *device, const u80211_header_description_t *header, uint8_t index, u80211_cipher_t cipher, uint8_t *sequence, size_t sequence_size) { + if (sequence_size != SEQUENCE_SIZE) + return false; + + u80211_kernel_acquire_spinlock(device->key_spinlock); + u80211_key_metadata_t *metadata = select_key(device, header); + bool valid = false; + if (metadata == NULL || metadata->index != index || metadata->cipher != cipher) + goto unlock; + if (!increment_sequence(metadata->tx_sequence, sequence_size)) + goto unlock; + + u80211_memcpy(sequence, metadata->tx_sequence, sequence_size); + valid = true; + +unlock: + u80211_kernel_release_spinlock(device->key_spinlock); + return valid; +} diff --git a/kernel-src/io/net/u80211/src/michael.c b/kernel-src/io/net/u80211/src/michael.c new file mode 100644 index 00000000..b55a1ab2 --- /dev/null +++ b/kernel-src/io/net/u80211/src/michael.c @@ -0,0 +1,86 @@ +#include +#include +#include + +typedef struct { + uint32_t left; + uint32_t right; + uint8_t pending[4]; + size_t pending_size; +} michael_context_t; + +static uint32_t rotate_left(uint32_t value, unsigned int amount) { + return (value << amount) | (value >> (32 - amount)); +} + +static uint32_t rotate_right(uint32_t value, unsigned int amount) { + return (value >> amount) | (value << (32 - amount)); +} + +static uint32_t exchange_bytes(uint32_t value) { + return ((value & 0x00ff00ff) << 8) | ((value & 0xff00ff00) >> 8); +} + +static void michael_block(michael_context_t *context, uint32_t word) { + context->left ^= word; + context->right ^= rotate_left(context->left, 17); + context->left += context->right; + context->right ^= exchange_bytes(context->left); + context->left += context->right; + context->right ^= rotate_left(context->left, 3); + context->left += context->right; + context->right ^= rotate_right(context->left, 2); + context->left += context->right; +} + +static void michael_update(michael_context_t *context, const void *data, size_t size) { + const uint8_t *bytes = data; + + if (context->pending_size != 0) { + while (size != 0 && context->pending_size < sizeof(context->pending)) { + context->pending[context->pending_size++] = *bytes++; + size--; + } + + if (context->pending_size == sizeof(context->pending)) { + michael_block(context, deserialize_le32(context->pending)); + context->pending_size = 0; + } + } + + while (size >= sizeof(uint32_t)) { + michael_block(context, deserialize_le32(bytes)); + bytes += sizeof(uint32_t); + size -= sizeof(uint32_t); + } + + if (size != 0) { + u80211_memcpy(context->pending, bytes, size); + context->pending_size = size; + } +} + +void u80211_michael_mic(const uint8_t key[8], const uint8_t da[6], const uint8_t sa[6], uint8_t priority, const void *data, size_t len, uint8_t out[8]) { + uint8_t pseudo_header[16]; + u80211_memcpy(pseudo_header, da, 6); + u80211_memcpy(pseudo_header + 6, sa, 6); + pseudo_header[12] = priority; + u80211_memset(pseudo_header + 13, 0, 3); + + michael_context_t context = { + .left = deserialize_le32(key), + .right = deserialize_le32(key + 4), + }; + michael_update(&context, pseudo_header, sizeof(pseudo_header)); + michael_update(&context, data, len); + + const uint8_t padding_marker = 0x5a; + michael_update(&context, &padding_marker, sizeof(padding_marker)); + const uint8_t zeroes[4] = { 0 }; + if (context.pending_size != 0) + michael_update(&context, zeroes, sizeof(context.pending) - context.pending_size); + michael_update(&context, zeroes, sizeof(zeroes)); + + serialize_le32(out, context.left); + serialize_le32(out + 4, context.right); +} diff --git a/kernel-src/io/net/u80211/src/packet.c b/kernel-src/io/net/u80211/src/packet.c new file mode 100644 index 00000000..9d7a6e8f --- /dev/null +++ b/kernel-src/io/net/u80211/src/packet.c @@ -0,0 +1,291 @@ +#include +#include +#include +#include +#include +#include + +#define CCMP_HEADER_SIZE 8 +#define CCMP_MIC_SIZE 8 +#define CCMP_EXT_IV 0x20 +#define TKIP_HEADER_SIZE 8 +#define TKIP_MIC_SIZE 8 +#define TKIP_ICV_SIZE 4 +#define TKIP_EXT_IV 0x20 +#define SEQUENCE_SIZE 6 + +static bool deserialize_cipher_key_index(const void *data, size_t data_size, uint8_t *index) { + // all currently supported 802.11 cipher headers store the key id in bits 6-7 of byte 3 + if (data_size < 4) + return false; + + *index = ((const uint8_t *)data)[3] >> 6; + return true; +} + +static bool data_packet_for_device(u80211_device_t *device, const u80211_header_description_t *header) { + return u80211_mac_address_equal(&header->addresses[0], &device->metadata.mac_address) || (header->addresses[0].bytes[0] & 1); +} + +static void data_packet_addresses(const u80211_header_description_t *header, const u80211_mac_address_t **destination, const u80211_mac_address_t **source) { + bool to_ds = header->frame_control & U80211_HEADER_FRAME_CONTROL_TO_DS; + bool from_ds = header->frame_control & U80211_HEADER_FRAME_CONTROL_FROM_DS; + + if (to_ds) { + *destination = &header->addresses[2]; + *source = from_ds ? &header->addresses[3] : &header->addresses[1]; + } else { + *destination = &header->addresses[0]; + *source = from_ds ? &header->addresses[2] : &header->addresses[1]; + } +} + +static bool process_ccmp_rx(u80211_device_t *device, const u80211_header_description_t *header, uint8_t key_index, void **data_start, size_t *data_size) { + if (*data_size < CCMP_HEADER_SIZE + CCMP_MIC_SIZE) + return false; + + const uint8_t *ccmp_header = *data_start; + if (!(ccmp_header[3] & CCMP_EXT_IV)) + return false; + + const uint8_t sequence[SEQUENCE_SIZE] = { + ccmp_header[0], ccmp_header[1], ccmp_header[4], + ccmp_header[5], ccmp_header[6], ccmp_header[7], + }; + + if (!u80211_key_update_rx_sequence(device, header, key_index, U80211_CIPHER_CCMP, sequence, sizeof(sequence))) + return false; + + *data_start = (uint8_t *)*data_start + CCMP_HEADER_SIZE; + *data_size -= CCMP_HEADER_SIZE + CCMP_MIC_SIZE; + return true; +} + +static bool process_tkip_rx(u80211_device_t *device, const u80211_header_description_t *header, uint8_t key_index, void **data_start, size_t *data_size) { + // TODO: support qos data frames and use their tid as the michael priority + if (U80211_HEADER_FRAME_CONTROL_GET_TYPE(header->frame_control) != U80211_HEADER_FRAME_CONTROL_TYPE_DATA) + return false; + + if (U80211_HEADER_FRAME_CONTROL_GET_SUBTYPE(header->frame_control) != U80211_HEADER_FRAME_CONTROL_SUBTYPE_DATA) + return false; + + if (*data_size < TKIP_HEADER_SIZE + TKIP_MIC_SIZE + TKIP_ICV_SIZE) + return false; + + const uint8_t *tkip_header = *data_start; + if (!(tkip_header[3] & TKIP_EXT_IV)) + return false; + if (tkip_header[1] != ((tkip_header[0] | 0x20) & 0x7f)) + return false; + + const uint8_t *payload = tkip_header + TKIP_HEADER_SIZE; + size_t payload_size = *data_size - TKIP_HEADER_SIZE - TKIP_MIC_SIZE - TKIP_ICV_SIZE; + const uint8_t *mic = payload + payload_size; + + u80211_tkip_data_t tkip_data = { + .header = header, + .key_index = key_index, + .priority = 0, + .data = payload, + .data_size = payload_size, + .sequence = { + tkip_header[2], tkip_header[0], tkip_header[4], + tkip_header[5], tkip_header[6], tkip_header[7], + }, + }; + u80211_memcpy(tkip_data.mic, mic, sizeof(tkip_data.mic)); + data_packet_addresses(header, &tkip_data.destination, &tkip_data.source); + + if (!u80211_key_validate_tkip_rx(device, &tkip_data)) + return false; + + *data_start = (void *)payload; + *data_size = payload_size; + return true; +} + +void u80211_process_packet(u80211_device_t *device, void *packet, size_t packet_size) { + __atomic_add_fetch(&device->packet_count, 1, __ATOMIC_RELAXED); + + void *data_start; + size_t data_size; + u80211_header_description_t header; + if (u80211_deserialize_header(packet, packet_size, &header, &data_start, &data_size) != U80211_STATUS_SUCCESS) + return; + + // encrypted packets contain an additional header that we need to handle + // TODO: this might be handled by future hardware, so this needs to be a flag in the device metadata + if (header.frame_control & U80211_HEADER_FRAME_CONTROL_PROTECTED_FRAME) { + uint8_t key_index; + if (!deserialize_cipher_key_index(data_start, data_size, &key_index)) + return; + + switch (u80211_select_cipher_by_index(device, &header, key_index)) { + case U80211_CIPHER_CCMP: + if (!process_ccmp_rx(device, &header, key_index, &data_start, &data_size)) + return; + break; + case U80211_CIPHER_TKIP: + if (!process_tkip_rx(device, &header, key_index, &data_start, &data_size)) + return; + break; + default: + return; + } + } + + switch (U80211_HEADER_FRAME_CONTROL_GET_TYPE(header.frame_control)) { + case U80211_HEADER_FRAME_CONTROL_TYPE_MANAGEMENT: + u80211_process_management_packet(device, &header, data_start, data_size); + break; + case U80211_HEADER_FRAME_CONTROL_TYPE_DATA: + if (data_packet_for_device(device, &header) && + u80211_association_is_duplicate(device, &header, U80211_DEVICE_STATE_ASSOCIATED)) + return; + u80211_process_data_packet(device, &header, data_start, data_size); + break; + } +} + +size_t u80211_get_packet_count(u80211_device_t *device) { + return __atomic_load_n(&device->packet_count, __ATOMIC_RELAXED); +} + +void u80211_reset_packet_count(u80211_device_t *device) { + __atomic_store_n(&device->packet_count, 0, __ATOMIC_RELAXED); +} + +static int deserialize_management_header(void *source, size_t source_size, u80211_header_description_t *header, void **data_start, size_t *data_size) { + if (source_size < 14) + return U80211_STATUS_NOT_ENOUGH_SPACE; + + u80211_memcpy(&header->addresses[1], source, 6); + u80211_memcpy(&header->addresses[2], (const void *)((uintptr_t)source + 6), 6); + header->sequence_control = deserialize_le16((const void *)((uintptr_t)source + 12)); + + *data_start = (void *)((uintptr_t)source + 14); + *data_size = source_size - 14; + return U80211_STATUS_SUCCESS; +} + +static int deserialize_control_header(void *source, size_t source_size, u80211_header_description_t *header, void **data_start, size_t *data_size) { + (void)source; + (void)source_size; + (void)header; + (void)data_start; + (void)data_size; + return U80211_STATUS_UNSUPPORTED; +} + +static int deserialize_data_header(void *source, size_t source_size, u80211_header_description_t *header, void **data_start, size_t *data_size) { + int subtype = U80211_HEADER_FRAME_CONTROL_GET_SUBTYPE(header->frame_control); + + if (subtype != U80211_HEADER_FRAME_CONTROL_SUBTYPE_DATA && subtype != U80211_HEADER_FRAME_CONTROL_SUBTYPE_NULL_DATA) + return U80211_STATUS_UNSUPPORTED; + + size_t header_size = (header->frame_control & U80211_HEADER_FRAME_CONTROL_TO_DS) && (header->frame_control & U80211_HEADER_FRAME_CONTROL_FROM_DS) ? 20 : 14; + if (source_size < header_size) + return U80211_STATUS_NOT_ENOUGH_SPACE; + + u80211_memcpy(&header->addresses[1], source, 6); + u80211_memcpy(&header->addresses[2], (const void *)((uintptr_t)source + 6), 6); + + if (header_size == 20) { + header->sequence_control = deserialize_le16((const void *)((uintptr_t)source + 12)); + u80211_memcpy(&header->addresses[3], (const void *)((uintptr_t)source + 14), 6); + } else { + header->sequence_control = deserialize_le16((const void *)((uintptr_t)source + 12)); + } + + *data_start = (void *)((uintptr_t)source + header_size); + *data_size = source_size - header_size; + return U80211_STATUS_SUCCESS; +} + +int u80211_deserialize_header(void *source, size_t source_size, u80211_header_description_t *header, void **data_start, size_t *data_size) { + if (source_size < 10) + return U80211_STATUS_NOT_ENOUGH_SPACE; + + header->frame_control = deserialize_le16(source); + header->duration_id = deserialize_le16((const void *)((uintptr_t)source + 2)); + u80211_memcpy(&header->addresses[0], (const void *)((uintptr_t)source + 4), 6); + + void *next_part = (void *)((uintptr_t)source + 10); + size_t next_part_size = source_size - 10; + + switch (U80211_HEADER_FRAME_CONTROL_GET_TYPE(header->frame_control)) { + case U80211_HEADER_FRAME_CONTROL_TYPE_MANAGEMENT: + return deserialize_management_header(next_part, next_part_size, header, data_start, data_size); + case U80211_HEADER_FRAME_CONTROL_TYPE_CONTROL: + return deserialize_control_header(next_part, next_part_size, header, data_start, data_size); + case U80211_HEADER_FRAME_CONTROL_TYPE_DATA: + return deserialize_data_header(next_part, next_part_size, header, data_start, data_size); + default: + return U80211_STATUS_UNSUPPORTED; + } +} + + +static void serialize_common_header(u80211_header_description_t *header, uint8_t *destination) { + serialize_le16(destination, header->frame_control); + serialize_le16(destination + 2, header->duration_id); + u80211_memcpy(destination + 4, &header->addresses[0], 6); +} + +static int serialize_management_header(u80211_header_description_t *header, u80211_tx_buffer_descriptor_t *descriptor) { + uint8_t *destination = u80211_descriptor_allocate_space(descriptor, 24); + if (destination == NULL) + return U80211_STATUS_NOT_ENOUGH_SPACE; + + serialize_common_header(header, destination); + u80211_memcpy(destination + 10, &header->addresses[1], 6); + u80211_memcpy(destination + 16, &header->addresses[2], 6); + serialize_le16(destination + 22, header->sequence_control); + + return U80211_STATUS_SUCCESS; +} + +static int serialize_control_header(u80211_header_description_t *header, u80211_tx_buffer_descriptor_t *descriptor) { + (void)header; + (void)descriptor; + return U80211_STATUS_UNSUPPORTED; +} + +static int serialize_data_header(u80211_header_description_t *header, u80211_tx_buffer_descriptor_t *descriptor) { + int subtype = U80211_HEADER_FRAME_CONTROL_GET_SUBTYPE(header->frame_control); + + if (subtype != U80211_HEADER_FRAME_CONTROL_SUBTYPE_DATA && subtype != U80211_HEADER_FRAME_CONTROL_SUBTYPE_NULL_DATA) + return U80211_STATUS_UNSUPPORTED; + + size_t header_size = (header->frame_control & U80211_HEADER_FRAME_CONTROL_TO_DS) && (header->frame_control & U80211_HEADER_FRAME_CONTROL_FROM_DS) ? 30 : 24; + + uint8_t *destination = u80211_descriptor_allocate_space(descriptor, header_size); + if (destination == NULL) + return U80211_STATUS_NOT_ENOUGH_SPACE; + + serialize_common_header(header, destination); + u80211_memcpy(destination + 10, &header->addresses[1], 6); + u80211_memcpy(destination + 16, &header->addresses[2], 6); + + if (header_size == 30) { + serialize_le16(destination + 22, header->sequence_control); + u80211_memcpy(destination + 24, &header->addresses[3], 6); + } else { + serialize_le16(destination + 22, header->sequence_control); + } + + return U80211_STATUS_SUCCESS; +} + +int u80211_serialize_header(u80211_header_description_t *header, u80211_tx_buffer_descriptor_t *descriptor) { + switch (U80211_HEADER_FRAME_CONTROL_GET_TYPE(header->frame_control)) { + case U80211_HEADER_FRAME_CONTROL_TYPE_MANAGEMENT: + return serialize_management_header(header, descriptor); + case U80211_HEADER_FRAME_CONTROL_TYPE_CONTROL: + return serialize_control_header(header, descriptor); + case U80211_HEADER_FRAME_CONTROL_TYPE_DATA: + return serialize_data_header(header, descriptor); + default: + return U80211_STATUS_UNSUPPORTED; + } +} diff --git a/kernel-src/io/net/u80211/src/packet_data.c b/kernel-src/io/net/u80211/src/packet_data.c new file mode 100644 index 00000000..d9609b17 --- /dev/null +++ b/kernel-src/io/net/u80211/src/packet_data.c @@ -0,0 +1,212 @@ +#include +#include +#include +#include + +#define ETHERNET_HEADER_SIZE 14 +#define ETHERNET_MAX_FRAME_SIZE 1514 +#define LLCSNAP_SIZE 8 +#define TX_BUFFER_HEADROOM 64 +#define CCMP_HEADER_SIZE 8 +#define CCMP_EXT_IV 0x20 +#define TKIP_HEADER_SIZE 8 +#define TKIP_MIC_SIZE 8 +#define TKIP_EXT_IV 0x20 +#define TX_BUFFER_TAILROOM TKIP_MIC_SIZE +#define SEQUENCE_SIZE 6 + +// this is the expected LLC/SNAP header for our use-case. it is then followed by a big-endian 16-bit ethertype. +const uint8_t byte_header[6] = {0xaa, 0xaa, 0x03, 0x00, 0x00, 0x00}; + +void u80211_process_data_packet(u80211_device_t *device, u80211_header_description_t *header, void *data, size_t data_size) { + if (data_size < LLCSNAP_SIZE) + return; + + if (u80211_memcmp(data, byte_header, sizeof(byte_header)) != 0) + return; + + uint16_t ethertype; + u80211_memcpy(ðertype, (void *)((uintptr_t)data + 6), sizeof(ethertype)); + + // create an ethernet header before the payload + // doing this in-place is safe due to the LLC/SNAP header + 802.11 header being much + // larger than the ethernet header. + data = (void *)((uintptr_t)data + LLCSNAP_SIZE - ETHERNET_HEADER_SIZE); + data_size = data_size + ETHERNET_HEADER_SIZE - LLCSNAP_SIZE; + + u80211_mac_address_t *destination; + u80211_mac_address_t *source; + bool to_ds = header->frame_control & U80211_HEADER_FRAME_CONTROL_TO_DS; + bool from_ds = header->frame_control & U80211_HEADER_FRAME_CONTROL_FROM_DS; + + if (to_ds) { + destination = &header->addresses[2]; + source = from_ds ? &header->addresses[3] : &header->addresses[1]; + } else { + destination = &header->addresses[0]; + source = from_ds ? &header->addresses[2] : &header->addresses[1]; + } + + u80211_memcpy(data, destination, sizeof(*destination)); + u80211_memcpy((void *)((uintptr_t)data + 6), source, sizeof(*source)); + u80211_memcpy((void *)((uintptr_t)data + 12), ðertype, sizeof(ethertype)); + + u80211_kernel_receive_callback(device, data, data_size); +} + +static int prepare_ccmp_tx(u80211_device_t *device, u80211_header_description_t *header, uint8_t key, u80211_tx_buffer_descriptor_t *descriptor) { + uint8_t *ccmp_header = u80211_descriptor_allocate_space(descriptor, CCMP_HEADER_SIZE); + if (ccmp_header == NULL) + return U80211_STATUS_NOT_ENOUGH_SPACE; + + uint8_t sequence[SEQUENCE_SIZE]; + if (!u80211_key_next_tx_sequence(device, header, key, U80211_CIPHER_CCMP, sequence, sizeof(sequence))) + return U80211_STATUS_NOT_PERMITTED; + + // TODO: other hardware might provide more complete cipher handling, including the CCMP header + ccmp_header[0] = sequence[0]; + ccmp_header[1] = sequence[1]; + ccmp_header[2] = 0; + ccmp_header[3] = CCMP_EXT_IV | key << 6; + ccmp_header[4] = sequence[2]; + ccmp_header[5] = sequence[3]; + ccmp_header[6] = sequence[4]; + ccmp_header[7] = sequence[5]; + header->frame_control |= U80211_HEADER_FRAME_CONTROL_PROTECTED_FRAME; + return U80211_STATUS_SUCCESS; +} + +static int prepare_tkip_tx(u80211_device_t *device, u80211_header_description_t *header, uint8_t key, u80211_tx_buffer_descriptor_t *descriptor) { + u80211_tkip_data_t tkip_data = { + .header = header, + .key_index = key, + .destination = &header->addresses[2], + .source = &header->addresses[1], + .priority = 0, + .data = (uint8_t *)descriptor->data + descriptor->current_offset, + .data_size = descriptor->size - descriptor->current_offset, + }; + + uint8_t *tkip_header = u80211_descriptor_allocate_space(descriptor, TKIP_HEADER_SIZE); + if (tkip_header == NULL) + return U80211_STATUS_NOT_ENOUGH_SPACE; + + if (!u80211_key_prepare_tkip_tx(device, &tkip_data)) + return U80211_STATUS_NOT_PERMITTED; + + // TODO: other hardware might not expect something exactly like this. this code adds both + // the header and the trailing mic. + tkip_header[0] = tkip_data.sequence[1]; + tkip_header[1] = (tkip_data.sequence[1] | 0x20) & 0x7f; + tkip_header[2] = tkip_data.sequence[0]; + tkip_header[3] = TKIP_EXT_IV | key << 6; + tkip_header[4] = tkip_data.sequence[2]; + tkip_header[5] = tkip_data.sequence[3]; + tkip_header[6] = tkip_data.sequence[4]; + tkip_header[7] = tkip_data.sequence[5]; + u80211_memcpy((uint8_t *)descriptor->data + descriptor->size, tkip_data.mic, TKIP_MIC_SIZE); + descriptor->size += TKIP_MIC_SIZE; + header->frame_control |= U80211_HEADER_FRAME_CONTROL_PROTECTED_FRAME; + return U80211_STATUS_SUCCESS; +} + +int u80211_allocate_tx_buffer(u80211_device_t *device, u80211_tx_buffer_descriptor_t *descriptor) { + int status = device->ops->allocate_tx_buffer(device, ETHERNET_MAX_FRAME_SIZE + TX_BUFFER_HEADROOM + TX_BUFFER_TAILROOM, descriptor); + if (status != U80211_STATUS_SUCCESS) + return status; + + // keep MIC tailroom outside the logical buffer so other ciphers do not transmit it + descriptor->data = (void *)((uintptr_t)descriptor->data + TX_BUFFER_HEADROOM); + descriptor->size -= TX_BUFFER_HEADROOM + TX_BUFFER_TAILROOM; + descriptor->current_offset -= TX_BUFFER_HEADROOM + TX_BUFFER_TAILROOM; + return U80211_STATUS_SUCCESS; +} + +int u80211_transmit_buffer(u80211_device_t *device, u80211_tx_buffer_descriptor_t *descriptor) { + descriptor->data = (uint8_t *)descriptor->data - TX_BUFFER_HEADROOM; + descriptor->size += TX_BUFFER_HEADROOM; + descriptor->current_offset += TX_BUFFER_HEADROOM; + + u80211_kernel_acquire_spinlock(device->association_spinlock); + u80211_ap_t *ap = device->ap; + if (u80211_get_device_state(device) != U80211_DEVICE_STATE_ASSOCIATED || ap == NULL) { + u80211_kernel_release_spinlock(device->association_spinlock); + device->ops->free_tx_buffer(device, descriptor); + return U80211_STATUS_NOT_ASSOCIATED; + } + u80211_ap_hold(ap); + u80211_kernel_release_spinlock(device->association_spinlock); + + size_t ethernet_frame_size = descriptor->size - descriptor->current_offset; + if (ethernet_frame_size < ETHERNET_HEADER_SIZE) { + u80211_ap_release(ap); + device->ops->free_tx_buffer(device, descriptor); + return U80211_STATUS_NOT_ENOUGH_SPACE; + } + + uint8_t *ethernet_header = (uint8_t *)descriptor->data + descriptor->current_offset; + u80211_mac_address_t destination; + u80211_mac_address_t source; + uint16_t ethertype; + u80211_memcpy(&destination, ethernet_header, sizeof(destination)); + u80211_memcpy(&source, ethernet_header + 6, sizeof(source)); + u80211_memcpy(ðertype, ethernet_header + 12, sizeof(ethertype)); + + descriptor->current_offset += ETHERNET_HEADER_SIZE; + uint8_t *llc_snap = u80211_descriptor_allocate_space(descriptor, LLCSNAP_SIZE); + if (llc_snap == NULL) { + u80211_ap_release(ap); + device->ops->free_tx_buffer(device, descriptor); + return U80211_STATUS_NOT_ENOUGH_SPACE; + } + u80211_memcpy(llc_snap, byte_header, sizeof(byte_header)); + u80211_memcpy(llc_snap + sizeof(byte_header), ðertype, sizeof(ethertype)); + + u80211_header_description_t header = { + .frame_control = (U80211_HEADER_FRAME_CONTROL_TYPE_DATA << 2) | U80211_HEADER_FRAME_CONTROL_TO_DS, + .sequence_control = __atomic_fetch_add(&device->tx_sequence_control, 0x10, __ATOMIC_RELAXED), + .addresses = { + ap->mac_address, + source, + destination, + }, + }; + u80211_ap_release(ap); + + int key = u80211_select_key(device, &header); + int cipher = -1; + if (key >= 0) { + // supported cipher headers have two bits for key selection + if (key > 3) { + device->ops->free_tx_buffer(device, descriptor); + return U80211_STATUS_NOT_PERMITTED; + } + + cipher = u80211_select_cipher_by_index(device, &header, key); + int cipher_status; + switch (cipher) { + case U80211_CIPHER_CCMP: + cipher_status = prepare_ccmp_tx(device, &header, key, descriptor); + break; + case U80211_CIPHER_TKIP: + cipher_status = prepare_tkip_tx(device, &header, key, descriptor); + break; + default: + device->ops->free_tx_buffer(device, descriptor); + return U80211_STATUS_UNSUPPORTED; + } + if (cipher_status != U80211_STATUS_SUCCESS) { + device->ops->free_tx_buffer(device, descriptor); + return cipher_status; + } + } + + int status = u80211_serialize_header(&header, descriptor); + if (status != U80211_STATUS_SUCCESS) { + device->ops->free_tx_buffer(device, descriptor); + return status; + } + + const u80211_transmit_options_t options = { .key = key, .cipher = cipher }; + return device->ops->transmit(device, descriptor, &options); +} diff --git a/kernel-src/io/net/u80211/src/packet_management.c b/kernel-src/io/net/u80211/src/packet_management.c new file mode 100644 index 00000000..fe400bd0 --- /dev/null +++ b/kernel-src/io/net/u80211/src/packet_management.c @@ -0,0 +1,369 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#define IE_ID_SSID 0 +#define IE_ID_RATES 1 +#define IE_ID_CHANNEL 3 +#define IE_ID_RSN 48 +#define IE_ID_RATES_EXT 50 + +#define MANAGEMENT_HEADER_SIZE 24 +#define AUTH_DATA_SIZE 6 +#define REASON_CODE_SIZE 2 +#define ASSOCIATION_REQUEST_FIXED_SIZE 4 +#define ASSOCIATION_RESPONSE_FIXED_SIZE 6 +#define CAPABILITY_ESS 0x0001 +#define CAPABILITY_SHORT_PREAMBLE 0x0020 +#define CAPABILITY_SHORT_SLOT_TIME 0x0400 +// TODO: Only hardcode these capabilities on 2.4 GHz. +#define ASSOCIATION_CAPABILITIES (CAPABILITY_ESS | CAPABILITY_SHORT_PREAMBLE | CAPABILITY_SHORT_SLOT_TIME) +// TODO: Revisit the listen interval when power saving is implemented. +#define ASSOCIATION_LISTEN_INTERVAL 10 +#define MAX_SSID_SIZE 32 +#define MAX_RATE_VALUE 125 + +// TODO check if basic rates are supported by the device +static bool handle_rates(uint8_t rate_bitmap[16], const uint8_t *rates, size_t count) { + for (size_t i = 0; i < count; ++i) { + uint8_t rate = rates[i] & 0x7f; + + // these two have special meanings, so ignore them + if (rate == 126 || rate == 127) + continue; + + rate_bitmap[rate / 8] |= 1 << (rate % 8); + } + + return true; +} + +static bool management_packet_for_device(u80211_device_t *device, u80211_header_description_t *header) { + return u80211_mac_address_equal(&header->addresses[1], &header->addresses[2]) && u80211_mac_address_equal(&header->addresses[0], &device->metadata.mac_address); +} + +static bool management_packet_for_device_or_broadcast(u80211_device_t *device, u80211_header_description_t *header) { + const u80211_mac_address_t broadcast_address = { .bytes = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } }; + + return u80211_mac_address_equal(&header->addresses[1], &header->addresses[2]) && + (u80211_mac_address_equal(&header->addresses[0], &device->metadata.mac_address) || + u80211_mac_address_equal(&header->addresses[0], &broadcast_address)); +} + +static bool process_information_elements(uint8_t rate_bitmap[16], char *ssid, uint8_t *channel, const uint8_t **rsn, size_t *rsn_size, const void *data, size_t data_size) { + const uint8_t *bytes = data; + size_t offset = 0; + while (offset < data_size) { + if (data_size - offset < 2) + return false; + + uint8_t id = bytes[offset]; + uint8_t size = bytes[offset + 1]; + offset += 2; + + if (data_size - offset < size) + return false; + + switch (id) { + case IE_ID_SSID: + if (size > MAX_SSID_SIZE) + return false; + + if (ssid != NULL) { + u80211_memcpy(ssid, bytes + offset, size); + ssid[size] = '\0'; + } + break; + case IE_ID_CHANNEL: + if (size != 1) + return false; + + if (channel != NULL) + *channel = bytes[offset]; + break; + case IE_ID_RATES: + if (size > 8) + return false; + /* fall through */ + case IE_ID_RATES_EXT: + if (!handle_rates(rate_bitmap, bytes + offset, size)) + return false; + break; + case IE_ID_RSN: + if (rsn != NULL && rsn_size != NULL) { + *rsn = bytes + offset; + *rsn_size = size; + } + break; + } + + offset += size; + } + + return true; +} + +static size_t count_rates(const uint8_t rate_bitmap[16]) { + size_t rate_count = 0; + for (size_t rate = 0; rate <= MAX_RATE_VALUE; ++rate) { + if (rate_bitmap[rate / 8] & (1 << (rate % 8))) + ++rate_count; + } + + return rate_count; +} + +static size_t rate_information_elements_size(size_t rate_count) { + size_t size = 2 + min(rate_count, 8); + + if (rate_count > 8) + size += 2 + rate_count - 8; + + return size; +} + +static size_t serialize_rate_information_elements(uint8_t *destination, const uint8_t rate_bitmap[16], size_t rate_count) { + size_t supported_rate_count = min(rate_count, 8); + size_t extended_rate_count = rate_count - supported_rate_count; + size_t offset = 0; + + destination[offset++] = IE_ID_RATES; + destination[offset++] = supported_rate_count; + + size_t rate_index = 0; + for (size_t rate = 0; rate <= MAX_RATE_VALUE; ++rate) { + if (!(rate_bitmap[rate / 8] & (1 << (rate % 8)))) + continue; + + if (rate_index == supported_rate_count && extended_rate_count != 0) { + destination[offset++] = IE_ID_RATES_EXT; + destination[offset++] = extended_rate_count; + } + + destination[offset++] = rate; + ++rate_index; + } + + return offset; +} + +static int prepare_management_packet(u80211_device_t *device, const u80211_mac_address_t *address, int subtype, size_t data_size, u80211_tx_buffer_descriptor_t *descriptor, uint8_t **data) { + int status = device->ops->allocate_tx_buffer(device, MANAGEMENT_HEADER_SIZE + data_size, descriptor); + if (status != U80211_STATUS_SUCCESS) + return status; + + *data = u80211_descriptor_allocate_space(descriptor, data_size); + if (*data == NULL) { + device->ops->free_tx_buffer(device, descriptor); + return U80211_STATUS_NOT_ENOUGH_SPACE; + } + + u80211_header_description_t header = { + .frame_control = subtype << 4, + .sequence_control = __atomic_fetch_add(&device->tx_sequence_control, 0x10, __ATOMIC_RELAXED), + .addresses = { + *address, + device->metadata.mac_address, + *address, + }, + }; + + status = u80211_serialize_header(&header, descriptor); + if (status != U80211_STATUS_SUCCESS) + device->ops->free_tx_buffer(device, descriptor); + + return status; +} + +static void process_probe_response(u80211_device_t *device, u80211_header_description_t *header, const void *data, size_t data_size) { + if (data_size < 12) + return; + + if (!management_packet_for_device(device, header)) + return; + + u80211_beacon_data_t beacon_data; + u80211_memset(&beacon_data, 0, sizeof(beacon_data)); + beacon_data.interval = deserialize_le16((const void *)((uintptr_t)data + 8)); + beacon_data.capabilities = deserialize_le16((const void *)((uintptr_t)data + 10)); + beacon_data.mac_address = header->addresses[2]; + + if (!process_information_elements(beacon_data.rate_bitmap, beacon_data.ssid, &beacon_data.channel, &beacon_data.rsn, &beacon_data.rsn_size, (const void *)((uintptr_t)data + 12), data_size - 12)) + return; + + u80211_scan_process_response(device, &beacon_data); +} + +static void process_auth_packet(u80211_device_t *device, u80211_header_description_t *header, const void *data, size_t data_size) { + if (data_size < 6) + return; + + if (!management_packet_for_device(device, header)) + return; + + u80211_auth_data_t auth_data; + auth_data.address = header->addresses[2]; + auth_data.auth_algorithm = deserialize_le16(data); + auth_data.auth_transaction = deserialize_le16((const void *)((uintptr_t)data + 2)); + auth_data.status = deserialize_le16((const void *)((uintptr_t)data + 4)); + + if (u80211_association_is_duplicate(device, header, U80211_DEVICE_STATE_AUTHENTICATING)) + return; + + u80211_association_process_authentication(device, &auth_data); +} + +static void process_association_response(u80211_device_t *device, u80211_header_description_t *header, const void *data, size_t data_size) { + if (data_size < ASSOCIATION_RESPONSE_FIXED_SIZE) + return; + + if (!management_packet_for_device(device, header)) + return; + + u80211_association_response_data_t association_data; + u80211_memset(&association_data, 0, sizeof(association_data)); + association_data.address = header->addresses[2]; + association_data.capabilities = deserialize_le16(data); + association_data.status = deserialize_le16((const void *)((uintptr_t)data + 2)); + association_data.association_id = deserialize_le16((const void *)((uintptr_t)data + 4)); + + if (!process_information_elements(association_data.rate_bitmap, NULL, NULL, NULL, NULL, (const void *)((uintptr_t)data + ASSOCIATION_RESPONSE_FIXED_SIZE), data_size - ASSOCIATION_RESPONSE_FIXED_SIZE)) + return; + + if (u80211_association_is_duplicate(device, header, U80211_DEVICE_STATE_ASSOCIATING)) + return; + + u80211_association_process_response(device, &association_data); +} + +static void process_deauthentication_packet(u80211_device_t *device, u80211_header_description_t *header, const void *data, size_t data_size) { + if (data_size < REASON_CODE_SIZE) + return; + + if (!management_packet_for_device_or_broadcast(device, header)) + return; + + u80211_deauthentication_data_t deauthentication_data = { + .address = header->addresses[2], + .reason = deserialize_le16(data), + }; + u80211_association_process_deauthentication(device, &deauthentication_data); +} + +static void process_disassociation_packet(u80211_device_t *device, u80211_header_description_t *header, const void *data, size_t data_size) { + if (data_size < REASON_CODE_SIZE) + return; + + if (!management_packet_for_device_or_broadcast(device, header)) + return; + + u80211_disassociation_data_t disassociation_data = { + .address = header->addresses[2], + .reason = deserialize_le16(data), + }; + u80211_association_process_disassociation(device, &disassociation_data); +} + +void u80211_process_management_packet(u80211_device_t *device, u80211_header_description_t *header, const void *data, size_t data_size) { + int subtype = U80211_HEADER_FRAME_CONTROL_GET_SUBTYPE(header->frame_control); + + switch (subtype) { + case U80211_HEADER_FRAME_CONTROL_SUBTYPE_ASSOCIATION_RESPONSE: + process_association_response(device, header, data, data_size); + break; + case U80211_HEADER_FRAME_CONTROL_SUBTYPE_PROBE_RESPONSE: + process_probe_response(device, header, data, data_size); + break; + case U80211_HEADER_FRAME_CONTROL_SUBTYPE_AUTHENTICATION: + process_auth_packet(device, header, data, data_size); + break; + case U80211_HEADER_FRAME_CONTROL_SUBTYPE_DEAUTHENTICATION: + process_deauthentication_packet(device, header, data, data_size); + break; + case U80211_HEADER_FRAME_CONTROL_SUBTYPE_DISASSOCIATION: + process_disassociation_packet(device, header, data, data_size); + break; + } +} + +int u80211_send_authentication(u80211_device_t *device, u80211_auth_data_t *auth_data) { + u80211_tx_buffer_descriptor_t descriptor; + uint8_t *data; + int status = prepare_management_packet(device, &auth_data->address, U80211_HEADER_FRAME_CONTROL_SUBTYPE_AUTHENTICATION, AUTH_DATA_SIZE, &descriptor, &data); + if (status != U80211_STATUS_SUCCESS) + return status; + + serialize_le16(data, auth_data->auth_algorithm); + serialize_le16(data + 2, auth_data->auth_transaction); + serialize_le16(data + 4, auth_data->status); + + const u80211_transmit_options_t options = { .key = -1, .cipher = -1 }; + return device->ops->transmit(device, &descriptor, &options); +} + +int u80211_send_deauthentication(u80211_device_t *device, u80211_deauthentication_data_t *deauthentication_data) { + u80211_tx_buffer_descriptor_t descriptor; + uint8_t *data; + int status = prepare_management_packet(device, &deauthentication_data->address, U80211_HEADER_FRAME_CONTROL_SUBTYPE_DEAUTHENTICATION, REASON_CODE_SIZE, &descriptor, &data); + if (status != U80211_STATUS_SUCCESS) + return status; + + serialize_le16(data, deauthentication_data->reason); + + const u80211_transmit_options_t options = { .key = -1, .cipher = -1 }; + return device->ops->transmit(device, &descriptor, &options); +} + +int u80211_send_probe_request(u80211_device_t *device) { + size_t rate_count = count_rates(device->metadata.rate_bitmap); + size_t probe_request_data_size = 2 + rate_information_elements_size(rate_count); + const u80211_mac_address_t broadcast_address = { .bytes = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff } }; + + u80211_tx_buffer_descriptor_t descriptor; + uint8_t *probe_request_data; + int status = prepare_management_packet(device, &broadcast_address, U80211_HEADER_FRAME_CONTROL_SUBTYPE_PROBE_REQUEST, probe_request_data_size, &descriptor, &probe_request_data); + if (status != U80211_STATUS_SUCCESS) + return status; + + probe_request_data[0] = IE_ID_SSID; + probe_request_data[1] = 0; + serialize_rate_information_elements(probe_request_data + 2, device->metadata.rate_bitmap, rate_count); + + const u80211_transmit_options_t options = { .key = -1, .cipher = -1 }; + return device->ops->transmit(device, &descriptor, &options); +} + +int u80211_send_association_request(u80211_device_t *device, const void *information_elements, size_t information_elements_size) { + u80211_ap_t *ap = device->ap; + size_t ssid_size = 0; + while (ssid_size < MAX_SSID_SIZE && ap->ssid[ssid_size] != '\0') + ++ssid_size; + + size_t rate_count = count_rates(device->metadata.rate_bitmap); + size_t generated_data_size = ASSOCIATION_REQUEST_FIXED_SIZE + 2 + ssid_size + rate_information_elements_size(rate_count); + size_t association_request_data_size = generated_data_size + information_elements_size; + + u80211_tx_buffer_descriptor_t descriptor; + uint8_t *data; + int status = prepare_management_packet(device, &ap->mac_address, U80211_HEADER_FRAME_CONTROL_SUBTYPE_ASSOCIATION_REQUEST, association_request_data_size, &descriptor, &data); + if (status != U80211_STATUS_SUCCESS) + return status; + + serialize_le16(data, ASSOCIATION_CAPABILITIES); + serialize_le16(data + 2, ASSOCIATION_LISTEN_INTERVAL); + data[4] = IE_ID_SSID; + data[5] = ssid_size; + u80211_memcpy(data + 6, ap->ssid, ssid_size); + size_t rate_information_size = serialize_rate_information_elements(data + 6 + ssid_size, device->metadata.rate_bitmap, rate_count); + if (information_elements_size != 0) + u80211_memcpy(data + 6 + ssid_size + rate_information_size, information_elements, information_elements_size); + + const u80211_transmit_options_t options = { .key = -1, .cipher = -1 }; + return device->ops->transmit(device, &descriptor, &options); +} diff --git a/kernel-src/io/net/u80211/src/rbtree.c b/kernel-src/io/net/u80211/src/rbtree.c new file mode 100644 index 00000000..afc54421 --- /dev/null +++ b/kernel-src/io/net/u80211/src/rbtree.c @@ -0,0 +1,555 @@ +#include +#include +#include + +#define RBTREE_COLOUR_BLACK 0 +#define RBTREE_COLOUR_RED 1 + +// helper macros as the least significant bit of the parent pointer is used for the colour +#define RBTREE_NODE_GET_PARENT(x) ((u80211_rbtree_t *)((uintptr_t)(x)->parent & ~1lu)) +#define RBTREE_NODE_GET_COLOUR(x) ((x) == NULL ? RBTREE_COLOUR_BLACK : ((uintptr_t)(x)->parent & 1)) +#define RBTREE_NODE_SET_COLOUR(x, c) ((x)->parent = (u80211_rbtree_t *)((uintptr_t)RBTREE_NODE_GET_PARENT(x) | (c))) +#define RBTREE_NODE_SET_PARENT(x, p) ((x)->parent = (u80211_rbtree_t *)((uintptr_t)(p) | RBTREE_NODE_GET_COLOUR(x))) +#define RBTREE_CONSTRUCT_PARENT(x, c) ((u80211_rbtree_t *)((uintptr_t)(x) | (c))) + +static void rotate_right(u80211_rbtree_t **rootp, u80211_rbtree_t *node) { + /* G G + // | | + // P -> N + // / \ / \ + // N 3 1 P + // / \ / \ + // 1 2 2 3 + */ + + u80211_rbtree_t *parent = RBTREE_NODE_GET_PARENT(node); + u80211_rbtree_t *grand_parent = RBTREE_NODE_GET_PARENT(parent); + + // N + if (grand_parent == NULL) + *rootp = node; + else if (grand_parent->left == parent) + grand_parent->left = node; + else + grand_parent->right = node; + + RBTREE_NODE_SET_PARENT(node, grand_parent); + + // P + u80211_rbtree_t *node_right_subtree = node->right; + + node->right = parent; + RBTREE_NODE_SET_PARENT(parent, node); + + // 2 + parent->left = node_right_subtree; + if (node_right_subtree) + RBTREE_NODE_SET_PARENT(node_right_subtree, parent); +} + +static void rotate_left(u80211_rbtree_t **rootp, u80211_rbtree_t *node) { + /* G G + // | | + // P N + // / \ -> / \ + // 1 N P 3 + // / \ / \ + // 2 3 1 2 + */ + + u80211_rbtree_t *parent = RBTREE_NODE_GET_PARENT(node); + u80211_rbtree_t *grand_parent = RBTREE_NODE_GET_PARENT(parent); + + // N + if (grand_parent == NULL) + *rootp = node; + else if (grand_parent->left == parent) + grand_parent->left = node; + else + grand_parent->right = node; + + RBTREE_NODE_SET_PARENT(node, grand_parent); + + // P + u80211_rbtree_t *node_left_subtree = node->left; + + node->left = parent; + RBTREE_NODE_SET_PARENT(parent, node); + + // 2 + parent->right = node_left_subtree; + if (node_left_subtree) + RBTREE_NODE_SET_PARENT(node_left_subtree, parent); +} + +u80211_rbtree_t *u80211_rbtree_find_first_larger_equal(u80211_rbtree_t *rbtree, void *key, u80211_rbtree_value_compare_fn_t compare_fn) { + u80211_rbtree_t *found = NULL; + for (;;) { + if (rbtree == NULL) + return found; + + int comparison = compare_fn(key, rbtree); + + if (comparison == 0) + return rbtree; + + if (comparison == -1) + found = rbtree; + + rbtree = comparison == 1 ? rbtree->right : rbtree->left; + } +} + +u80211_rbtree_t *u80211_rbtree_lookup(u80211_rbtree_t *rbtree, void *key, u80211_rbtree_value_compare_fn_t compare_fn) { + for (;;) { + if (rbtree == NULL) + return NULL; + + int comparison = compare_fn(key, rbtree); + + if (comparison == 0) + return rbtree; + + rbtree = comparison > 0 ? rbtree->right : rbtree->left; + } +} + +void u80211_rbtree_insert(u80211_rbtree_t **rbtreep, u80211_rbtree_t *node, u80211_rbtree_compare_fn_t compare_fn) { + u80211_rbtree_t *rbtree = *rbtreep; + if (rbtree == NULL) { + *rbtreep = node; + node->right = node->left = node->parent = NULL; // NULL implicitly sets to black + return; + } + + // find last node + int comparison; + u80211_rbtree_t **next; + for (;;) { + comparison = compare_fn(node, rbtree); + next = comparison > 0 ? &rbtree->right : &rbtree->left; + if (*next == NULL) + break; + + rbtree = *next; + } + + // set tree pointers + *next = node; + node->right = NULL; + node->left = NULL; + node->parent = RBTREE_CONSTRUCT_PARENT(rbtree, RBTREE_COLOUR_RED); // all inserted nodes are red + + // parent is red, iterate up the tree to fix any property 3 violations we find + // node now means the node whose parent we are checking for a property 3 violation + u80211_rbtree_t *parent, *grand_parent, *uncle; + for (;;) { + parent = RBTREE_NODE_GET_PARENT(node); + // check for property 3 violation (red parent and red child) + if (RBTREE_NODE_GET_COLOUR(parent) == RBTREE_COLOUR_BLACK) + return; // no violation, stop here + + grand_parent = RBTREE_NODE_GET_PARENT(parent); + uncle = grand_parent->left == parent ? grand_parent->right : grand_parent->left; + + // if the uncle is black, we can leave the loop as there are no more checks needed in this case + if (RBTREE_NODE_GET_COLOUR(uncle) == RBTREE_COLOUR_BLACK) + break; + + // uncle is red, recolour the tree such as: + // parent = black, grandparent = red, uncle = black + // and go up for another violation check, as changing the grandparent to red might have + // violated it again + RBTREE_NODE_SET_COLOUR(parent, RBTREE_COLOUR_BLACK); + RBTREE_NODE_SET_COLOUR(uncle, RBTREE_COLOUR_BLACK); + // SPECIAL CASE: if grandparent is root, do nothing. we always want the root to be black. + if (RBTREE_NODE_GET_PARENT(grand_parent)) + RBTREE_NODE_SET_COLOUR(grand_parent, RBTREE_COLOUR_RED); + node = grand_parent; + } + + // black uncle, we have to do a rotation in order to be able to recolour it + + /* if grandparent -> parent and parent -> child are in opposite directions, + // we will have to do an extra rotation to straighten it out first + // Left rotation: Right rotation: + // G G G G + // / \ / \ / \ / \ + // P U -> N U U P -> U N + // \ / / \ + // N P N P + */ + + if (grand_parent->left == parent && parent->right == node) { + rotate_left(rbtreep, node); + node = parent; + parent = grand_parent->left; + } else if (grand_parent->right == parent && parent->left == node) { + rotate_right(rbtreep, node); + node = parent; + parent = grand_parent->right; + } + + /* now, we have to do a rotation so that we can recolour the nodes without + // breaking any properties of the tree + // G P G P + // / \ / \ / \ / \ + // P U -> N G OR U P -> G N + // / \ \ / + // N U N U + */ + + if (grand_parent->left == parent) + rotate_right(rbtreep, parent); + else + rotate_left(rbtreep, parent); + + /* finally, recolour the nodes: + // R B R B + // / \ / \ / \ / \ + // R B -> R R OR B R -> R R + // \ \ / / + // B B B B + */ + + // same operation on both cases + RBTREE_NODE_SET_COLOUR(parent, RBTREE_COLOUR_BLACK); + RBTREE_NODE_SET_COLOUR(grand_parent, RBTREE_COLOUR_RED); + // no need to change node or uncle colour +} + +void u80211_rbtree_check(u80211_rbtree_t *rbtree) { + if (rbtree == NULL) + return; + + // check that the root is black (always will be in this implementation) + u80211_rbtree_t *parent = RBTREE_NODE_GET_PARENT(rbtree); + + // perform a preorder transversal and ensure the properties are followed + u80211_rbtree_t *node = rbtree; + int leaf_black_count = 0; + int current_black_count = 0; + int count_blacks = 1; + int last_direction = 0; // 1 = left, 2 = right + int going_up = 0; + for (;;) { + int node_colour = RBTREE_NODE_GET_COLOUR(node); + if (node_colour == RBTREE_COLOUR_RED) { + } else if (!going_up) { + ++current_black_count; + leaf_black_count += count_blacks; + } + + if (node == NULL) { + // this is a NULL node, do nescessary checks and go up + count_blacks = 0; + + --current_black_count; + node = parent; + parent = RBTREE_NODE_GET_PARENT(node); + going_up = 1; + } else if (going_up && last_direction == 1) { + // we have gone up from the left node + // go to the right + last_direction = 2; + parent = node; + node = node->right; + going_up = 0; + } else if (going_up && last_direction == 2) { + // we have gone up from the right + // go up again + if (parent == NULL) + break; // end of tree + + if (node_colour == RBTREE_COLOUR_BLACK) + --current_black_count; + last_direction = parent->left == node ? 1 : 2; + node = parent; + parent = RBTREE_NODE_GET_PARENT(parent); + } else { + // we have gone down, go to the left + last_direction = 1; + parent = node; + node = node->left; + } + } +} + +u80211_rbtree_t *u80211_rbtree_first(u80211_rbtree_t *rbtree) { + while (rbtree->left) + rbtree = rbtree->left; + + return rbtree; +} + +u80211_rbtree_t *u80211_rbtree_last(u80211_rbtree_t *rbtree) { + while (rbtree->right) + rbtree = rbtree->right; + + return rbtree; +} + + +u80211_rbtree_t *u80211_rbtree_successor(u80211_rbtree_t *node) { + u80211_rbtree_t *iterator = node->right; + if (iterator == NULL) { + // no right, go up until we leave a left node + iterator = node; + u80211_rbtree_t *parent = RBTREE_NODE_GET_PARENT(node); + while (parent) { + if (parent->left == iterator) + return parent; + + iterator = parent; + parent = RBTREE_NODE_GET_PARENT(iterator); + } + + // end of tree + return NULL; + } + + // we have gone right, go all the way down left + while (iterator->left != NULL) + iterator = iterator->left; + + return iterator; +} + +u80211_rbtree_t *u80211_rbtree_predecessor(u80211_rbtree_t *node) { + u80211_rbtree_t *iterator = node->left; + if (iterator == NULL) { + // no left, go up until we leave a right node + iterator = node; + u80211_rbtree_t *parent = RBTREE_NODE_GET_PARENT(node); + while (parent) { + if (parent->right == iterator) + return parent; + + iterator = parent; + parent = RBTREE_NODE_GET_PARENT(iterator); + } + + // end of tree + return NULL; + } + + // we have gone right, go all the way down left + while (iterator->right != NULL) + iterator = iterator->right; + + return iterator; +} + +static void u80211_rbtree_remove_leaf(u80211_rbtree_t **rbtreep, u80211_rbtree_t *node) { + u80211_rbtree_t *og_node = node; + if (*rbtreep == node) { + // if root, set root to null + *rbtreep = NULL; + return; + } + + if (RBTREE_NODE_GET_COLOUR(node) == RBTREE_COLOUR_RED) { + // if red there is no rebalancing needed, just remove it + goto do_remove; + } + + u80211_rbtree_t *parent = RBTREE_NODE_GET_PARENT(node); + + // black leaf node + u80211_rbtree_t *sibling; + for (;;) { + if (parent == NULL) + goto do_remove; + + sibling = parent->left == node ? parent->right : parent->left; + + // the only case where we have to loop upwards is when everyone is black + if (RBTREE_NODE_GET_COLOUR(parent) == RBTREE_COLOUR_RED || + RBTREE_NODE_GET_COLOUR(sibling) == RBTREE_COLOUR_RED || + RBTREE_NODE_GET_COLOUR(sibling->left) == RBTREE_COLOUR_RED || + RBTREE_NODE_GET_COLOUR(sibling->right) == RBTREE_COLOUR_RED) + break; + + // colour the sibling red and move upwards + RBTREE_NODE_SET_COLOUR(sibling, RBTREE_COLOUR_RED); + + node = parent; + parent = RBTREE_NODE_GET_PARENT(node); + } + + // red sibling, do a rotation so that the sibling is now the grandparent + // and switch the colours of the sibling and parent + // P S | R B + // / \ / | / / + // N S -> P | B -> R + // / | / / + // N | B B + if (RBTREE_NODE_GET_COLOUR(sibling) == RBTREE_COLOUR_RED) { + // set parent to red and sibling to black + RBTREE_NODE_SET_COLOUR(parent, RBTREE_COLOUR_RED); + RBTREE_NODE_SET_COLOUR(sibling, RBTREE_COLOUR_BLACK); + + // rotate + if (parent->left == sibling) { + rotate_right(rbtreep, sibling); + sibling = parent->left; + } else { + rotate_left(rbtreep, sibling); + sibling = parent->right; + } + } + + /* if all of the sibling nodes are black and parent is red, we can + // recolour the tree so that the imbalance in black paths is fixed + // Example: + // R B + // / \ / \ + // B B -> B R + // / \ / \ + // B B B B + */ + if (RBTREE_NODE_GET_COLOUR(parent) == RBTREE_COLOUR_RED && + RBTREE_NODE_GET_COLOUR(sibling) == RBTREE_COLOUR_BLACK && + RBTREE_NODE_GET_COLOUR(sibling->left) == RBTREE_COLOUR_BLACK && + RBTREE_NODE_GET_COLOUR(sibling->right) == RBTREE_COLOUR_BLACK + ) { + RBTREE_NODE_SET_COLOUR(parent, RBTREE_COLOUR_BLACK); + RBTREE_NODE_SET_COLOUR(sibling, RBTREE_COLOUR_RED); + goto do_remove; + } + + u80211_rbtree_t *close_nephew, *far_nephew; + if (parent->right == sibling) { + close_nephew = sibling->left; + far_nephew = sibling->right; + } else { + close_nephew = sibling->right; + far_nephew = sibling->left; + } + + /* if the nearest nephew is red and the farthest is black, rotate it into the new sibling + // and exchange the colour between it and the old sibling + // example: + // + // P P | X X + // / \ / \ | / \ / \ + // N S N C | B R B B + // / \ -> \ | \ -> \ + // C F S | B R + // \ | \ \ + // F | B B + */ + if (RBTREE_NODE_GET_COLOUR(close_nephew) == RBTREE_COLOUR_RED && RBTREE_NODE_GET_COLOUR(far_nephew) == RBTREE_COLOUR_BLACK) { + // do rotation + if (close_nephew == sibling->left) + rotate_right(rbtreep, close_nephew); + else + rotate_left(rbtreep, close_nephew); + + // update for next step + far_nephew = sibling; + sibling = close_nephew; + + // recolour + RBTREE_NODE_SET_COLOUR(far_nephew, RBTREE_COLOUR_RED); + RBTREE_NODE_SET_COLOUR(sibling, RBTREE_COLOUR_BLACK); + } + + /* in this case, the closest nephew is black and the distant nephew is red. + // there is no need to check for this, as the result of the last operation, if it happened, guarantees it. + // we need to rotate such that the sibling becomes the new grandparent and recolour such that: + // sibling -> parent's colour + // parent -> black + // farthest nephew -> black + // example: + // P S | B X + // / \ / \ | / \ / \ + // N S -> P F | X R -> B B + // / \ / \ | / \ / \ + // C F N C | B B B B + */ + + if (parent->right == sibling) + rotate_left(rbtreep, sibling); + else + rotate_right(rbtreep, sibling); + + RBTREE_NODE_SET_COLOUR(sibling, RBTREE_NODE_GET_COLOUR(parent)); + RBTREE_NODE_SET_COLOUR(parent, RBTREE_COLOUR_BLACK); + RBTREE_NODE_SET_COLOUR(far_nephew, RBTREE_COLOUR_BLACK); + + do_remove: + // do the actual removal of the node + parent = RBTREE_NODE_GET_PARENT(og_node); + if (parent->left == og_node) + parent->left = NULL; + else + parent->right = NULL; +} + +void u80211_rbtree_remove(u80211_rbtree_t **rbtreep, u80211_rbtree_t *node) { + if (node->left == NULL && node->right == NULL) { + // leaf node? + u80211_rbtree_remove_leaf(rbtreep, node); + return; + } else if (!(node->left && node->right)) { + // has one child + // replace it with the child and colour the child black + + u80211_rbtree_t *parent = RBTREE_NODE_GET_PARENT(node); + u80211_rbtree_t *child = node->left ? node->left : node->right; + if (parent == NULL) + *rbtreep = child; + else if (parent->left == node) + parent->left = child; + else + parent->right = child; + + child->parent = RBTREE_CONSTRUCT_PARENT(parent, RBTREE_COLOUR_BLACK); + return; + } + + // two children + // swap itself with its successor and delete it again + // doing this guarantees that it will only have, at most, one child when deleted again + u80211_rbtree_t *succ = u80211_rbtree_successor(node); + u80211_rbtree_t *succ_parent = RBTREE_NODE_GET_PARENT(succ); + u80211_rbtree_t *parent = RBTREE_NODE_GET_PARENT(node); + + // set the successor in place + if (parent == NULL) { + // root + *rbtreep = succ; + } else if (parent->left == node) { + parent->left = succ; + } else { + parent->right = succ; + } + + int succ_colour = RBTREE_NODE_GET_COLOUR(succ); + succ->parent = node->parent; // copy both the parent and colour + succ->left = node->left; + u80211_rbtree_t *old_succ_right = succ->right; + succ->right = node->right; + + RBTREE_NODE_SET_PARENT(succ->left, succ); + if (succ_parent != node) + RBTREE_NODE_SET_PARENT(succ->right, succ); + + // set the node in place + if (succ_parent == node) { + succ->right = node; + node->parent = RBTREE_CONSTRUCT_PARENT(succ, succ_colour); + } else { + succ_parent->left = node; + node->parent = RBTREE_CONSTRUCT_PARENT(succ_parent, succ_colour); + } + + node->left = NULL; // sucessor has no left + node->right = old_succ_right; + if (node->right) + RBTREE_NODE_SET_PARENT(node->right, node); + + // remove the node. this will not recurse again as the node will have only one child + u80211_rbtree_remove(rbtreep, node); +} diff --git a/kernel-src/io/net/u80211/src/regulatory_database.c b/kernel-src/io/net/u80211/src/regulatory_database.c new file mode 100644 index 00000000..85d8ad57 --- /dev/null +++ b/kernel-src/io/net/u80211/src/regulatory_database.c @@ -0,0 +1,29 @@ +#include +#include + +#define CHANNEL_COUNT 14 +static u80211_channel_rules_t channels[CHANNEL_COUNT] = { + {0, 20}, // ch1 + {0, 20}, // ch2 + {0, 20}, // ch3 + {0, 20}, // ch4 + {0, 20}, // ch5 + {0, 20}, // ch6 + {0, 20}, // ch7 + {0, 20}, // ch8 + {0, 20}, // ch9 + {0, 20}, // ch10 + {0, 20}, // ch11 + {U80211_CHANNEL_RULES_DISABLED, 0}, // ch12 + {U80211_CHANNEL_RULES_DISABLED, 0}, // ch13 + {U80211_CHANNEL_RULES_DISABLED, 0}, // ch14 +}; + +u80211_channel_rules_t u80211_get_channel_rules(int channel) { + if (channel > CHANNEL_COUNT) { + const u80211_channel_rules_t disabled_channel = {U80211_CHANNEL_RULES_DISABLED, 0}; + return disabled_channel; + } + + return channels[channel - 1]; +} diff --git a/kernel-src/io/net/u80211/src/ringbuffer.c b/kernel-src/io/net/u80211/src/ringbuffer.c new file mode 100644 index 00000000..e1f0b1c9 --- /dev/null +++ b/kernel-src/io/net/u80211/src/ringbuffer.c @@ -0,0 +1,93 @@ +#include + +#include +#include +#include +#include +#include + +int u80211_ringbuffer_init(u80211_ringbuffer_t *ringbuffer, size_t size) { + ringbuffer->size = 0; + ringbuffer->write = 0; + ringbuffer->read = 0; + ringbuffer->data = NULL; + + if (size == 0) + return U80211_STATUS_NOT_ENOUGH_SPACE; + + ringbuffer->data = u80211_kernel_allocate(size); + if (ringbuffer->data == NULL) + return U80211_STATUS_ENOMEM; + + ringbuffer->size = size; + ringbuffer->write = 0; + ringbuffer->read = 0; + return U80211_STATUS_SUCCESS; +} + +void u80211_ringbuffer_destroy(u80211_ringbuffer_t *ringbuffer) { + if (ringbuffer->data != NULL) + u80211_kernel_free(ringbuffer->data); + ringbuffer->size = 0; + ringbuffer->write = 0; + ringbuffer->read = 0; + ringbuffer->data = NULL; +} + +size_t u80211_ringbuffer_truncate(u80211_ringbuffer_t *ringbuffer, size_t count) { + size_t true_count = min(U80211_RINGBUFFER_DATA_COUNT(ringbuffer), count); + ringbuffer->read += true_count; + return true_count; +} + +size_t u80211_ringbuffer_read(u80211_ringbuffer_t *ringbuffer, void *buffer, size_t count) { + count = min(count, U80211_RINGBUFFER_DATA_COUNT(ringbuffer)); + size_t first_pass_offset = ringbuffer->read % ringbuffer->size; + size_t first_pass_remaining = ringbuffer->size - first_pass_offset; + size_t first_pass_count = min(count, first_pass_remaining); + + u80211_memcpy(buffer, (const void *)((uintptr_t)ringbuffer->data + first_pass_offset), first_pass_count); + if (first_pass_count != count) + u80211_memcpy((void *)((uintptr_t)buffer + first_pass_count), ringbuffer->data, count - first_pass_count); + + ringbuffer->read += count; + return count; +} + +size_t u80211_ringbuffer_peek(const u80211_ringbuffer_t *ringbuffer, void *buffer, uintmax_t offset, size_t count) { + size_t data_count = U80211_RINGBUFFER_DATA_COUNT(ringbuffer); + if (offset >= data_count) + return 0; + + uintmax_t read = ringbuffer->read + offset; + count = min(count, data_count - offset); + size_t first_pass_offset = read % ringbuffer->size; + size_t first_pass_remaining = ringbuffer->size - first_pass_offset; + size_t first_pass_count = min(count, first_pass_remaining); + + u80211_memcpy(buffer, (const void *)((uintptr_t)ringbuffer->data + first_pass_offset), first_pass_count); + if (first_pass_count != count) + u80211_memcpy((void *)((uintptr_t)buffer + first_pass_count), ringbuffer->data, count - first_pass_count); + + return count; +} + +size_t u80211_ringbuffer_write(u80211_ringbuffer_t *ringbuffer, const void *buffer, size_t count) { + count = min(count, U80211_RINGBUFFER_FREE_SPACE(ringbuffer)); + size_t first_pass_offset = ringbuffer->write % ringbuffer->size; + size_t first_pass_remaining = ringbuffer->size - first_pass_offset; + size_t first_pass_count = min(count, first_pass_remaining); + + u80211_memcpy((void *)((uintptr_t)ringbuffer->data + first_pass_offset), buffer, first_pass_count); + if (first_pass_count != count) + u80211_memcpy(ringbuffer->data, (const void *)((uintptr_t)buffer + first_pass_count), count - first_pass_count); + + ringbuffer->write += count; + return count; +} + +size_t u80211_ringbuffer_remove(u80211_ringbuffer_t *ringbuffer, size_t count) { + size_t true_count = min(U80211_RINGBUFFER_DATA_COUNT(ringbuffer), count); + ringbuffer->write -= true_count; + return true_count; +} diff --git a/kernel-src/io/net/u80211/src/scan.c b/kernel-src/io/net/u80211/src/scan.c new file mode 100644 index 00000000..ba9d7d58 --- /dev/null +++ b/kernel-src/io/net/u80211/src/scan.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WAIT_MS 75 +#define BUFFER_COUNT 64 +#define MAX_RSN_SIZE UINT8_MAX +#define BUFFER_SIZE (BUFFER_COUNT * (sizeof(u80211_beacon_data_t) + MAX_RSN_SIZE)) + +typedef struct { + u80211_ringbuffer_t buffer; + uint8_t rsn[MAX_RSN_SIZE]; // use this as a small scratch space + u80211_device_t *device; + bool receiving; + int current_channel; + void *work; +} u80211_scan_state_t; + +typedef struct { + u80211_list_node_t node; + void *semaphore; +} u80211_scan_waiter_t; + +static void wake_scan_waiters(u80211_device_t *device) { + u80211_list_node_t *node; + while ((node = u80211_list_pop_front(&device->scan_waiters)) != NULL) { + u80211_scan_waiter_t *waiter = container_of(node, u80211_scan_waiter_t, node); + u80211_kernel_signal_semaphore(waiter->semaphore); + } +} + +static void destroy_scan_state(u80211_scan_state_t *scan_state) { + u80211_kernel_free_work(scan_state->work); + u80211_ringbuffer_destroy(&scan_state->buffer); + u80211_kernel_free(scan_state); +} + +static void complete_scan(u80211_scan_state_t *scan_state) { + u80211_device_t *device = scan_state->device; + + u80211_kernel_acquire_spinlock(device->scan_spinlock); + + u80211_set_device_state(device, U80211_DEVICE_STATE_SCANNING, U80211_DEVICE_STATE_DOWN); + wake_scan_waiters(device); + + u80211_kernel_release_spinlock(device->scan_spinlock); + + destroy_scan_state(scan_state); +} + +void u80211_scan_process_response(u80211_device_t *device, u80211_beacon_data_t *beacon_data) { + u80211_kernel_acquire_spinlock(device->scan_spinlock); + u80211_scan_state_t *scan_state = device->scan_context; + + if (scan_state == NULL || !scan_state->receiving || beacon_data->rsn_size > MAX_RSN_SIZE) { + u80211_kernel_release_spinlock(device->scan_spinlock); + return; + } + + size_t record_size = sizeof(*beacon_data) + beacon_data->rsn_size; + if (U80211_RINGBUFFER_FREE_SPACE(&scan_state->buffer) < record_size) { + u80211_kernel_release_spinlock(device->scan_spinlock); + return; + } + + if (beacon_data->channel == 0) + beacon_data->channel = scan_state->current_channel; + + u80211_ringbuffer_write(&scan_state->buffer, beacon_data, sizeof(*beacon_data)); + u80211_ringbuffer_write(&scan_state->buffer, beacon_data->rsn, beacon_data->rsn_size); + + u80211_kernel_release_spinlock(device->scan_spinlock); +} + +static bool valid_bssid(const u80211_mac_address_t *mac_address) { + bool all_zero = true; + bool all_broadcast = true; + for (size_t i = 0; i < sizeof(mac_address->bytes); ++i) { + if (mac_address->bytes[i] != 0) + all_zero = false; + if (mac_address->bytes[i] != 0xff) + all_broadcast = false; + } + + return !all_zero && !all_broadcast && !(mac_address->bytes[0] & 1); +} + +// TODO: this obviously won't work for anything other than 2.4ghz wifi +static int next_channel(int current) { + for (int channel = current + 1; channel <= 14; ++channel) { + if (u80211_get_channel_rules(channel).flags & (U80211_CHANNEL_RULES_DISABLED | U80211_CHANNEL_RULES_PASSIVE)) + continue; + + return channel; + } + + return -1; +} + +static void scan_work(void *ctx) { + u80211_scan_state_t *scan_state = ctx; + u80211_device_t *device = scan_state->device; + + u80211_kernel_acquire_spinlock(device->scan_spinlock); + scan_state->receiving = false; + u80211_kernel_release_spinlock(device->scan_spinlock); + + while (U80211_RINGBUFFER_DATA_COUNT(&scan_state->buffer) >= sizeof(u80211_beacon_data_t)) { + u80211_beacon_data_t beacon_data; + if (u80211_ringbuffer_read(&scan_state->buffer, &beacon_data, sizeof(beacon_data)) != sizeof(beacon_data)) + break; + + if (u80211_ringbuffer_read(&scan_state->buffer, scan_state->rsn, beacon_data.rsn_size) != beacon_data.rsn_size) + break; + beacon_data.rsn = beacon_data.rsn_size == 0 ? NULL : scan_state->rsn; + + if (beacon_data.channel != scan_state->current_channel || !valid_bssid(&beacon_data.mac_address)) + continue; + + u80211_ap_t *ap = u80211_ap_allocate(&beacon_data); + if (ap == NULL) + continue; + + u80211_bss_cache_insert(&device->bss_cache, ap); + u80211_ap_release(ap); + } + + int current_channel = next_channel(scan_state->current_channel); + if (current_channel < 0) { + complete_scan(scan_state); + return; + } + + u80211_kernel_acquire_spinlock(device->scan_spinlock); + scan_state->current_channel = current_channel; + u80211_kernel_release_spinlock(device->scan_spinlock); + + device->ops->set_channel(device, scan_state->current_channel); + + u80211_kernel_acquire_spinlock(device->scan_spinlock); + scan_state->receiving = true; + u80211_kernel_release_spinlock(device->scan_spinlock); + + u80211_send_probe_request(device); + + u80211_kernel_enqueue_work(scan_state->work, scan_work, scan_state, WAIT_MS); +} + +int u80211_scan(u80211_device_t *device) { + u80211_scan_state_t *scan_state = u80211_kernel_allocate(sizeof(u80211_scan_state_t)); + if (scan_state == NULL) + return U80211_STATUS_ENOMEM; + + int status = u80211_ringbuffer_init(&scan_state->buffer, BUFFER_SIZE); + if (status != U80211_STATUS_SUCCESS) { + u80211_kernel_free(scan_state); + return status; + } + + scan_state->work = u80211_kernel_allocate_work(); + if (scan_state->work == NULL) { + u80211_ringbuffer_destroy(&scan_state->buffer); + u80211_kernel_free(scan_state); + return U80211_STATUS_ENOMEM; + } + + scan_state->receiving = false; + scan_state->device = device; + scan_state->current_channel = next_channel(0); + if (scan_state->current_channel < 0) { + destroy_scan_state(scan_state); + return U80211_STATUS_NOT_PERMITTED; + } + + if (!u80211_set_device_state(device, U80211_DEVICE_STATE_DOWN, U80211_DEVICE_STATE_SCANNING)) { + destroy_scan_state(scan_state); + return U80211_STATUS_BUSY; + } + + u80211_kernel_acquire_spinlock(device->scan_spinlock); + device->scan_context = scan_state; + u80211_kernel_release_spinlock(device->scan_spinlock); + + u80211_bss_cache_purge(&device->bss_cache); + + device->ops->set_channel(device, scan_state->current_channel); + + u80211_kernel_acquire_spinlock(device->scan_spinlock); + scan_state->receiving = true; + u80211_kernel_release_spinlock(device->scan_spinlock); + + u80211_send_probe_request(device); + + u80211_kernel_enqueue_work(scan_state->work, scan_work, scan_state, WAIT_MS); + + return U80211_STATUS_SUCCESS; +} + +int u80211_wait_for_scan_completion(u80211_device_t *device) { + void *semaphore = u80211_kernel_allocate_semaphore(0); + if (semaphore == NULL) + return U80211_STATUS_RETRY; + + u80211_scan_waiter_t waiter = { + .semaphore = semaphore, + }; + + u80211_kernel_acquire_spinlock(device->scan_spinlock); + if (u80211_get_device_state(device) != U80211_DEVICE_STATE_SCANNING) { + u80211_kernel_release_spinlock(device->scan_spinlock); + u80211_kernel_free_semaphore(semaphore); + return U80211_STATUS_SUCCESS; + } + + u80211_list_push_back(&device->scan_waiters, &waiter.node); + u80211_kernel_release_spinlock(device->scan_spinlock); + + u80211_kernel_wait_semaphore(semaphore); + u80211_kernel_free_semaphore(semaphore); + return U80211_STATUS_SUCCESS; +} diff --git a/kernel-src/io/net/u80211/src/string.c b/kernel-src/io/net/u80211/src/string.c new file mode 100644 index 00000000..2e0ea92b --- /dev/null +++ b/kernel-src/io/net/u80211/src/string.c @@ -0,0 +1,33 @@ +#include +#include + +void *u80211_memcpy(void *destination, const void *source, size_t size) { + uint8_t *destination_bytes = destination; + const uint8_t *source_bytes = source; + + for (size_t i = 0; i < size; i++) + destination_bytes[i] = source_bytes[i]; + + return destination; +} + +int u80211_memcmp(const void *a, const void *b, size_t size) { + const uint8_t *a_bytes = a; + const uint8_t *b_bytes = b; + + for (size_t i = 0; i < size; ++i) { + if (a_bytes[i] != b_bytes[i]) + return a_bytes[i] < b_bytes[i] ? -1 : 1; + } + + return 0; +} + +void *u80211_memset(void *destination, int value, size_t size) { + uint8_t *destination_bytes = destination; + + for (size_t i = 0; i < size; i++) + destination_bytes[i] = (uint8_t)value; + + return destination; +}