diff --git a/tools/hwsim.c b/tools/hwsim.c
index 2fee9b19..5cb28624 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,123 @@ 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);
+		memcpy(interface_rec->addr, rx, ETH_ALEN);
+		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 +1821,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;
 	}
