diff --git a/tools/hwsim.c b/tools/hwsim.c index 2fee9b19..b7d637ca 100644 --- a/tools/hwsim.c +++ b/tools/hwsim.c @@ -440,6 +440,7 @@ struct interface_info_rec { uint8_t addr[ETH_ALEN]; char *name; uint32_t iftype; + int ref; }; static struct l_queue *radio_info; @@ -515,6 +516,14 @@ static bool interface_info_match_id(const void *a, const void *b) return rec->id == id; } +static bool interface_info_match_addr(const void *a, const void *b) +{ + const struct interface_info_rec *rec = a; + const uint8_t *addr = b; + + return memcmp(rec->addr, addr, ETH_ALEN) == 0; +} + static const char *radio_get_path(const struct radio_info_rec *rec) { static char path[15]; @@ -1682,6 +1691,122 @@ static void hwsim_frame_event(struct l_genl_msg *msg) process_frame(frame); } +static bool get_tx_rx_addrs(struct l_genl_msg *msg, const uint8_t **tx_out, + const uint8_t **rx_out) +{ + struct l_genl_attr attr; + uint16_t type, len; + const void *data; + const uint8_t *tx = NULL, *rx = NULL; + + if (!l_genl_attr_init(&attr, msg)) + return false; + + while (l_genl_attr_next(&attr, &type, &len, &data)) { + switch (type) { + case HWSIM_ATTR_ADDR_TRANSMITTER: + if (len != ETH_ALEN) + return false; + + tx = data; + break; + case HWSIM_ATTR_ADDR_RECEIVER: + if (len != ETH_ALEN) + return false; + + rx = data; + break; + default: + break; + } + } + + if (!tx || !rx) + return false; + + *tx_out = tx; + *rx_out = rx; + + return true; +} + +static void hwsim_add_mac_event(struct l_genl_msg *msg) +{ + const uint8_t *tx = NULL, *rx = NULL; + struct radio_info_rec *radio_rec; + struct interface_info_rec *interface_rec; + + if (!get_tx_rx_addrs(msg, &tx, &rx)) + return; + + /* No radio matches the TX address, hwsim must not have created it */ + radio_rec = l_queue_find(radio_info, radio_info_match_addr1, tx); + if (!radio_rec) + return; + + interface_rec = l_queue_find(interface_info, + interface_info_match_addr, rx); + if (interface_rec) { + /* Existing interface, address changes handled via nl80211 */ + if (interface_rec->name) + return; + + /* + * Transient/dummy interface we already know about. This likely + * was created, then a scan changed the address temporarily. + * Reflect this change and increment the ref so the following + * DEL event doesn't destroy it + */ + __atomic_fetch_add(&interface_rec->ref, 1, __ATOMIC_SEQ_CST); + return; + } + + /* + * Create a dummy interface entry for this address that only contains + * the radio and address. This is either a transient entry due to scan + * randomization or an interface created outside this namespace. + */ + interface_rec = l_new(struct interface_info_rec, 1); + interface_rec->radio_rec = radio_rec; + interface_rec->ref = 1; + memcpy(interface_rec->addr, rx, ETH_ALEN); + + l_queue_push_tail(interface_info, interface_rec); +} + +static void hwsim_del_mac_event(struct l_genl_msg *msg) +{ + const uint8_t *tx = NULL, *rx = NULL; + struct radio_info_rec *radio_rec; + struct interface_info_rec *interface_rec; + + if (!get_tx_rx_addrs(msg, &tx, &rx)) + return; + + /* No radio matches the TX address, hwsim must not have created it */ + radio_rec = l_queue_find(radio_info, radio_info_match_addr1, tx); + if (!radio_rec) + return; + + interface_rec = l_queue_find(interface_info, + interface_info_match_addr, rx); + if (!interface_rec) + return; + + /* + * This change is handled via nl80211 so we don't want to touch this + * interface here. + */ + if (interface_rec->name) + return; + + if (__atomic_sub_fetch(&interface_rec->ref, 1, __ATOMIC_SEQ_CST)) + return; + + l_queue_remove(interface_info, interface_rec); + l_free(interface_rec); +} + static void hwsim_unicast_handler(struct l_genl_msg *msg, void *user_data) { uint8_t cmd; @@ -1695,6 +1820,12 @@ static void hwsim_unicast_handler(struct l_genl_msg *msg, void *user_data) case HWSIM_CMD_FRAME: hwsim_frame_event(msg); break; + case HWSIM_CMD_ADD_MAC_ADDR: + hwsim_add_mac_event(msg); + break; + case HWSIM_CMD_DEL_MAC_ADDR: + hwsim_del_mac_event(msg); + break; default: break; }