/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "net/netlink_manager.h" #include #include #include #include #include #include #include #include "net/kernel-header-latest/nl80211.h" #include "net/mlme_event.h" #include "net/mlme_event_handler.h" #include "net/nl80211_attribute.h" #include "net/nl80211_packet.h" using android::base::unique_fd; using std::array; using std::placeholders::_1; using std::string; using std::unique_ptr; using std::vector; namespace android { namespace wificond { namespace { // netlink.h suggests NLMSG_GOODSIZE to be at most 8192 bytes. constexpr int kReceiveBufferSize = 8 * 1024; constexpr uint32_t kBroadcastSequenceNumber = 0; constexpr int kMaximumNetlinkMessageWaitMilliSeconds = 300; uint8_t ReceiveBuffer[kReceiveBufferSize]; void AppendPacket(vector>* vec, unique_ptr packet) { vec->push_back(std::move(packet)); } // Convert enum nl80211_chan_width to enum ChannelBandwidth ChannelBandwidth getBandwidthType(uint32_t bandwidth) { switch (bandwidth) { case NL80211_CHAN_WIDTH_20_NOHT: return BW_20_NOHT; case NL80211_CHAN_WIDTH_20: return BW_20; case NL80211_CHAN_WIDTH_40: return BW_40; case NL80211_CHAN_WIDTH_80: return BW_80; case NL80211_CHAN_WIDTH_80P80: return BW_80P80; case NL80211_CHAN_WIDTH_160: return BW_160; } LOG(ERROR) << "Unknown bandwidth type: " << bandwidth; return BW_INVALID; } } // namespace NetlinkManager::NetlinkManager(EventLoop* event_loop) : started_(false), event_loop_(event_loop), sequence_number_(0) { } NetlinkManager::~NetlinkManager() { } uint32_t NetlinkManager::GetSequenceNumber() { if (++sequence_number_ == kBroadcastSequenceNumber) { ++sequence_number_; } return sequence_number_; } void NetlinkManager::ReceivePacketAndRunHandler(int fd) { ssize_t len = read(fd, ReceiveBuffer, kReceiveBufferSize); if (len == -1) { LOG(ERROR) << "Failed to read packet from buffer"; return; } if (len == 0) { return; } // There might be multiple message in one datagram payload. uint8_t* ptr = ReceiveBuffer; while (ptr < ReceiveBuffer + len) { // peek at the header. if (ptr + sizeof(nlmsghdr) > ReceiveBuffer + len) { LOG(ERROR) << "payload is broken."; return; } const nlmsghdr* nl_header = reinterpret_cast(ptr); unique_ptr packet( new NL80211Packet(vector(ptr, ptr + nl_header->nlmsg_len))); ptr += nl_header->nlmsg_len; if (!packet->IsValid()) { LOG(ERROR) << "Receive invalid packet"; return; } // Some document says message from kernel should have port id equal 0. // However in practice this is not always true so we don't check that. uint32_t sequence_number = packet->GetMessageSequence(); // Handle multicasts. if (sequence_number == kBroadcastSequenceNumber) { BroadcastHandler(std::move(packet)); continue; } auto itr = message_handlers_.find(sequence_number); // There is no handler for this sequence number. if (itr == message_handlers_.end()) { LOG(WARNING) << "No handler for message: " << sequence_number; return; } // A multipart message is terminated by NLMSG_DONE. // In this case we don't need to run the handler. // NLMSG_NOOP means no operation, message must be discarded. uint32_t message_type = packet->GetMessageType(); if (message_type == NLMSG_DONE || message_type == NLMSG_NOOP) { message_handlers_.erase(itr); return; } if (message_type == NLMSG_OVERRUN) { LOG(ERROR) << "Get message overrun notification"; message_handlers_.erase(itr); return; } // In case we receive a NLMSG_ERROR message: // NLMSG_ERROR could be either an error or an ACK. // It is an ACK message only when error code field is set to 0. // An ACK could be return when we explicitly request that with NLM_F_ACK. // An ERROR could be received on NLM_F_ACK or other failure cases. // We should still run handler in this case, leaving it for the caller // to decide what to do with the packet. bool is_multi = packet->IsMulti(); // Run the handler. itr->second(std::move(packet)); // Remove handler after processing. if (!is_multi) { message_handlers_.erase(itr); } } } void NetlinkManager::OnNewFamily(unique_ptr packet) { if (packet->GetMessageType() != GENL_ID_CTRL) { LOG(ERROR) << "Wrong message type for new family message"; return; } if (packet->GetCommand() != CTRL_CMD_NEWFAMILY) { LOG(ERROR) << "Wrong command for new family message"; return; } uint16_t family_id; if (!packet->GetAttributeValue(CTRL_ATTR_FAMILY_ID, &family_id)) { LOG(ERROR) << "Failed to get family id"; return; } string family_name; if (!packet->GetAttributeValue(CTRL_ATTR_FAMILY_NAME, &family_name)) { LOG(ERROR) << "Failed to get family name"; return; } if (family_name != NL80211_GENL_NAME) { LOG(WARNING) << "Ignoring none nl80211 netlink families"; } MessageType nl80211_type(family_id); message_types_[family_name] = nl80211_type; // Exract multicast groups. NL80211NestedAttr multicast_groups(0); if (packet->GetAttribute(CTRL_ATTR_MCAST_GROUPS, &multicast_groups)) { vector groups; if (!multicast_groups.GetListOfNestedAttributes(&groups)) { return; } for (auto& group : groups) { string group_name; uint32_t group_id = 0; if (!group.GetAttributeValue(CTRL_ATTR_MCAST_GRP_NAME, &group_name)) { LOG(ERROR) << "Failed to get group name"; continue; } if (!group.GetAttributeValue(CTRL_ATTR_MCAST_GRP_ID, &group_id)) { LOG(ERROR) << "Failed to get group id"; continue; } message_types_[family_name].groups[group_name] = group_id; } } } bool NetlinkManager::Start() { if (started_) { LOG(DEBUG) << "NetlinkManager is already started"; return true; } bool setup_rt = SetupSocket(&sync_netlink_fd_); if (!setup_rt) { LOG(ERROR) << "Failed to setup synchronous netlink socket"; return false; } setup_rt = SetupSocket(&async_netlink_fd_); if (!setup_rt) { LOG(ERROR) << "Failed to setup asynchronous netlink socket"; return false; } // Request family id for nl80211 messages. if (!DiscoverFamilyId()) { return false; } // Watch socket. if (!WatchSocket(&async_netlink_fd_)) { return false; } // Subscribe kernel NL80211 broadcast of regulatory changes. if (!SubscribeToEvents(NL80211_MULTICAST_GROUP_REG)) { return false; } // Subscribe kernel NL80211 broadcast of scanning events. if (!SubscribeToEvents(NL80211_MULTICAST_GROUP_SCAN)) { return false; } // Subscribe kernel NL80211 broadcast of MLME events. if (!SubscribeToEvents(NL80211_MULTICAST_GROUP_MLME)) { return false; } started_ = true; return true; } bool NetlinkManager::IsStarted() const { return started_; } bool NetlinkManager::RegisterHandlerAndSendMessage( const NL80211Packet& packet, std::function)> handler) { if (packet.IsDump()) { LOG(ERROR) << "Do not use asynchronous interface for dump request !"; return false; } if (!SendMessageInternal(packet, async_netlink_fd_.get())) { return false; } message_handlers_[packet.GetMessageSequence()] = handler; return true; } bool NetlinkManager::SendMessageAndGetResponses( const NL80211Packet& packet, vector>* response) { if (!SendMessageInternal(packet, sync_netlink_fd_.get())) { return false; } // Polling netlink socket, waiting for GetFamily reply. struct pollfd netlink_output; memset(&netlink_output, 0, sizeof(netlink_output)); netlink_output.fd = sync_netlink_fd_.get(); netlink_output.events = POLLIN; uint32_t sequence = packet.GetMessageSequence(); int time_remaining = kMaximumNetlinkMessageWaitMilliSeconds; // Multipart messages may come with seperated datagrams, ending with a // NLMSG_DONE message. // ReceivePacketAndRunHandler() will remove the handler after receiving a // NLMSG_DONE message. message_handlers_[sequence] = std::bind(AppendPacket, response, _1); while (time_remaining > 0 && message_handlers_.find(sequence) != message_handlers_.end()) { nsecs_t interval = systemTime(SYSTEM_TIME_MONOTONIC); int poll_return = poll(&netlink_output, 1, time_remaining); if (poll_return == 0) { LOG(ERROR) << "Failed to poll netlink fd: time out "; message_handlers_.erase(sequence); return false; } else if (poll_return == -1) { PLOG(ERROR) << "Failed to poll netlink fd"; message_handlers_.erase(sequence); return false; } ReceivePacketAndRunHandler(sync_netlink_fd_.get()); interval = systemTime(SYSTEM_TIME_MONOTONIC) - interval; time_remaining -= static_cast(ns2ms(interval)); } if (time_remaining <= 0) { LOG(ERROR) << "Timeout waiting for netlink reply messages"; message_handlers_.erase(sequence); return false; } return true; } bool NetlinkManager::SendMessageAndGetSingleResponse( const NL80211Packet& packet, unique_ptr* response) { unique_ptr response_or_error; if (!SendMessageAndGetSingleResponseOrError(packet, &response_or_error)) { return false; } if (response_or_error->GetMessageType() == NLMSG_ERROR) { // We use ERROR because we are not expecting to receive a ACK here. // In that case the caller should use |SendMessageAndGetAckOrError|. LOG(ERROR) << "Received error message: " << strerror(response_or_error->GetErrorCode()); return false; } *response = std::move(response_or_error); return true; } bool NetlinkManager::SendMessageAndGetSingleResponseOrError( const NL80211Packet& packet, unique_ptr* response) { vector> response_vec; if (!SendMessageAndGetResponses(packet, &response_vec)) { return false; } if (response_vec.size() != 1) { LOG(ERROR) << "Unexpected response size: " << response_vec.size(); return false; } *response = std::move(response_vec[0]); return true; } bool NetlinkManager::SendMessageAndGetAckOrError(const NL80211Packet& packet, int* error_code) { unique_ptr response; if (!SendMessageAndGetSingleResponseOrError(packet, &response)) { return false; } uint16_t type = response->GetMessageType(); if (type != NLMSG_ERROR) { LOG(ERROR) << "Receive unexpected message type :" << type; return false; } *error_code = response->GetErrorCode(); return true; } bool NetlinkManager::SendMessageAndGetAck(const NL80211Packet& packet) { int error_code; if (!SendMessageAndGetAckOrError(packet, &error_code)) { return false; } if (error_code != 0) { LOG(ERROR) << "Received error messsage: " << strerror(error_code); return false; } return true; } bool NetlinkManager::SendMessageInternal(const NL80211Packet& packet, int fd) { const vector& data = packet.GetConstData(); ssize_t bytes_sent = TEMP_FAILURE_RETRY(send(fd, data.data(), data.size(), 0)); if (bytes_sent == -1) { PLOG(ERROR) << "Failed to send netlink message"; return false; } return true; } bool NetlinkManager::SetupSocket(unique_fd* netlink_fd) { struct sockaddr_nl nladdr; memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; netlink_fd->reset( socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_GENERIC)); if (netlink_fd->get() < 0) { PLOG(ERROR) << "Failed to create netlink socket"; return false; } // Set maximum receive buffer size. // Datagram which is larger than this size will be discarded. if (setsockopt(netlink_fd->get(), SOL_SOCKET, SO_RCVBUFFORCE, &kReceiveBufferSize, sizeof(kReceiveBufferSize)) < 0) { PLOG(ERROR) << "Failed to set uevent socket SO_RCVBUFFORCE option"; return false; } if (bind(netlink_fd->get(), reinterpret_cast(&nladdr), sizeof(nladdr)) < 0) { PLOG(ERROR) << "Failed to bind netlink socket"; return false; } return true; } bool NetlinkManager::WatchSocket(unique_fd* netlink_fd) { // Watch socket bool watch_fd_rt = event_loop_->WatchFileDescriptor( netlink_fd->get(), EventLoop::kModeInput, std::bind(&NetlinkManager::ReceivePacketAndRunHandler, this, _1)); if (!watch_fd_rt) { LOG(ERROR) << "Failed to watch fd: " << netlink_fd->get(); return false; } return true; } uint16_t NetlinkManager::GetFamilyId() { return message_types_[NL80211_GENL_NAME].family_id; } bool NetlinkManager::DiscoverFamilyId() { NL80211Packet get_family_request(GENL_ID_CTRL, CTRL_CMD_GETFAMILY, GetSequenceNumber(), getpid()); NL80211Attr family_name(CTRL_ATTR_FAMILY_NAME, NL80211_GENL_NAME); get_family_request.AddAttribute(family_name); unique_ptr response; if (!SendMessageAndGetSingleResponse(get_family_request, &response)) { LOG(ERROR) << "Failed to get NL80211 family info"; return false; } OnNewFamily(std::move(response)); if (message_types_.find(NL80211_GENL_NAME) == message_types_.end()) { LOG(ERROR) << "Failed to get NL80211 family id"; return false; } return true; } bool NetlinkManager::SubscribeToEvents(const string& group) { auto groups = message_types_[NL80211_GENL_NAME].groups; if (groups.find(group) == groups.end()) { LOG(ERROR) << "Failed to subscribe: group " << group << " doesn't exist"; return false; } uint32_t group_id = groups[group]; int err = setsockopt(async_netlink_fd_.get(), SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group_id, sizeof(group_id)); if (err < 0) { PLOG(ERROR) << "Failed to setsockopt"; return false; } return true; } void NetlinkManager::BroadcastHandler(unique_ptr packet) { if (packet->GetMessageType() != GetFamilyId()) { LOG(ERROR) << "Wrong family id for multicast message"; return; } uint32_t command = packet->GetCommand(); if (command == NL80211_CMD_NEW_SCAN_RESULTS || // Scan was aborted, for unspecified reasons.partial scan results may be // available. command == NL80211_CMD_SCAN_ABORTED) { OnScanResultsReady(std::move(packet)); return; } if (command == NL80211_CMD_SCHED_SCAN_RESULTS || command == NL80211_CMD_SCHED_SCAN_STOPPED) { OnSchedScanResultsReady(std::move(packet)); return; } // Driver which supports SME uses both NL80211_CMD_AUTHENTICATE and // NL80211_CMD_ASSOCIATE, otherwise it uses NL80211_CMD_CONNECT // to notify a combination of authentication and association processses. // Currently we monitor CONNECT/ASSOCIATE/ROAM event for up-to-date // frequency and bssid. // TODO(nywang): Handle other MLME events, which help us track the // connection state better. if (command == NL80211_CMD_CONNECT || command == NL80211_CMD_ASSOCIATE || command == NL80211_CMD_ROAM || command == NL80211_CMD_DISCONNECT || command == NL80211_CMD_DISASSOCIATE) { OnMlmeEvent(std::move(packet)); return; } if (command == NL80211_CMD_REG_CHANGE || command == NL80211_CMD_WIPHY_REG_CHANGE) { OnRegChangeEvent(std::move(packet)); return; } // Station eventsFor AP mode. if (command == NL80211_CMD_NEW_STATION || command == NL80211_CMD_DEL_STATION) { uint32_t if_index; if (!packet->GetAttributeValue(NL80211_ATTR_IFINDEX, &if_index)) { LOG(WARNING) << "Failed to get interface index from station event"; return; } const auto handler = on_station_event_handler_.find(if_index); if (handler != on_station_event_handler_.end()) { array mac_address; if (!packet->GetAttributeValue(NL80211_ATTR_MAC, &mac_address)) { LOG(WARNING) << "Failed to get mac address from station event"; return; } if (command == NL80211_CMD_NEW_STATION) { handler->second(NEW_STATION, mac_address); } else { handler->second(DEL_STATION, mac_address); } } return; } if (command == NL80211_CMD_CH_SWITCH_NOTIFY) { OnChannelSwitchEvent(std::move(packet)); return; } if (command == NL80211_CMD_FRAME_TX_STATUS) { OnFrameTxStatusEvent(std::move(packet)); return; } } void NetlinkManager::OnRegChangeEvent(unique_ptr packet) { uint8_t reg_type; if (!packet->GetAttributeValue(NL80211_ATTR_REG_TYPE, ®_type)) { LOG(ERROR) << "Failed to get NL80211_ATTR_REG_TYPE"; } string country_code; // NL80211_REGDOM_TYPE_COUNTRY means the regulatory domain set is one that // pertains to a specific country if (reg_type == NL80211_REGDOM_TYPE_COUNTRY) { if (!packet->GetAttributeValue(NL80211_ATTR_REG_ALPHA2, &country_code)) { LOG(ERROR) << "Failed to get NL80211_ATTR_REG_ALPHA2"; return; } } else if (reg_type == NL80211_REGDOM_TYPE_WORLD || reg_type == NL80211_REGDOM_TYPE_CUSTOM_WORLD || reg_type == NL80211_REGDOM_TYPE_INTERSECTION) { // NL80211_REGDOM_TYPE_WORLD refers to the world regulartory domain. // NL80211_REGDOM_TYPE_CUSTOM_WORLD refers to the driver specific world // regulartory domain. // NL80211_REGDOM_TYPE_INTERSECTION refers to an intersection between two // regulatory domains: // The previously set regulatory domain on the system and the last accepted // regulatory domain request to be processed. country_code = ""; } else { LOG(ERROR) << "Unknown type of regulatory domain change: " << (int)reg_type; return; } for (const auto& handler : on_reg_domain_changed_handler_) { handler.second(handler.first, country_code); } } void NetlinkManager::OnMlmeEvent(unique_ptr packet) { uint32_t if_index; if (!packet->GetAttributeValue(NL80211_ATTR_IFINDEX, &if_index)) { LOG(ERROR) << "Failed to get interface index from a MLME event message"; return; } const auto handler = on_mlme_event_handler_.find(if_index); if (handler == on_mlme_event_handler_.end()) { LOG(DEBUG) << "No handler for mlme event from interface" << " with index: " << if_index; return; } uint32_t command = packet->GetCommand(); if (command == NL80211_CMD_CONNECT) { auto event = MlmeConnectEvent::InitFromPacket(packet.get()); if (event != nullptr) { handler->second->OnConnect(std::move(event)); } return; } if (command == NL80211_CMD_ASSOCIATE) { auto event = MlmeAssociateEvent::InitFromPacket(packet.get()); if (event != nullptr) { handler->second->OnAssociate(std::move(event)); } return; } if (command == NL80211_CMD_ROAM) { auto event = MlmeRoamEvent::InitFromPacket(packet.get()); if (event != nullptr) { handler->second->OnRoam(std::move(event)); } return; } if (command == NL80211_CMD_DISCONNECT) { auto event = MlmeDisconnectEvent::InitFromPacket(packet.get()); if (event != nullptr) { handler->second->OnDisconnect(std::move(event)); } return; } if (command == NL80211_CMD_DISASSOCIATE) { auto event = MlmeDisassociateEvent::InitFromPacket(packet.get()); if (event != nullptr) { handler->second->OnDisassociate(std::move(event)); } return; } } void NetlinkManager::OnSchedScanResultsReady(unique_ptr packet) { uint32_t if_index; if (!packet->GetAttributeValue(NL80211_ATTR_IFINDEX, &if_index)) { LOG(ERROR) << "Failed to get interface index from scan result notification"; return; } const auto handler = on_sched_scan_result_ready_handler_.find(if_index); if (handler == on_sched_scan_result_ready_handler_.end()) { LOG(DEBUG) << "No handler for scheduled scan result notification from" << " interface with index: " << if_index; return; } // Run scan result notification handler. handler->second(if_index, packet->GetCommand() == NL80211_CMD_SCHED_SCAN_STOPPED); } void NetlinkManager::OnScanResultsReady(unique_ptr packet) { uint32_t if_index; if (!packet->GetAttributeValue(NL80211_ATTR_IFINDEX, &if_index)) { LOG(ERROR) << "Failed to get interface index from scan result notification"; return; } bool aborted = false; if (packet->GetCommand() == NL80211_CMD_SCAN_ABORTED) { aborted = true; } const auto handler = on_scan_result_ready_handler_.find(if_index); if (handler == on_scan_result_ready_handler_.end()) { LOG(WARNING) << "No handler for scan result notification from interface" << " with index: " << if_index; return; } vector> ssids; NL80211NestedAttr ssids_attr(0); if (!packet->GetAttribute(NL80211_ATTR_SCAN_SSIDS, &ssids_attr)) { if (!aborted) { LOG(WARNING) << "Failed to get scan ssids from scan result notification"; } } else { if (!ssids_attr.GetListOfAttributeValues(&ssids)) { return; } } vector freqs; NL80211NestedAttr freqs_attr(0); if (!packet->GetAttribute(NL80211_ATTR_SCAN_FREQUENCIES, &freqs_attr)) { if (!aborted) { LOG(WARNING) << "Failed to get scan freqs from scan result notification"; } } else { if (!freqs_attr.GetListOfAttributeValues(&freqs)) { return; } } // Run scan result notification handler. handler->second(if_index, aborted, ssids, freqs); } void NetlinkManager::OnChannelSwitchEvent(unique_ptr packet) { uint32_t if_index = 0; if (!packet->GetAttributeValue(NL80211_ATTR_IFINDEX, &if_index)) { LOG(WARNING) << "Failed to get NL80211_ATTR_IFINDEX" << "from channel switch event"; return; } uint32_t frequency = 0; if (!packet->GetAttributeValue(NL80211_ATTR_WIPHY_FREQ, &frequency)) { LOG(WARNING) << "Failed to get NL80211_ATTR_WIPHY_FREQ" << "from channel switch event"; return; } uint32_t bandwidth = 0; if (!packet->GetAttributeValue(NL80211_ATTR_CHANNEL_WIDTH, &bandwidth)) { LOG(WARNING) << "Failed to get NL80211_ATTR_CHANNEL_WIDTH" << "from channel switch event"; return; } const auto handler = on_channel_switch_event_handler_.find(if_index); if (handler != on_channel_switch_event_handler_.end()) { handler->second(frequency, getBandwidthType(bandwidth)); } } void NetlinkManager::OnFrameTxStatusEvent( unique_ptr packet) { uint32_t if_index; if (!packet->GetAttributeValue(NL80211_ATTR_IFINDEX, &if_index)) { LOG(WARNING) << "Failed to get NL80211_ATTR_IFINDEX" << "from NL80211_CMD_FRAME_TX_STATUS event"; return; } uint64_t cookie; if (!packet->GetAttributeValue(NL80211_ATTR_COOKIE, &cookie)) { LOG(WARNING) << "Failed to get NL80211_ATTR_COOKIE" << "from NL80211_CMD_FRAME_TX_STATUS event"; return; } bool was_acked = packet->HasAttribute(NL80211_ATTR_ACK); const auto handler = on_frame_tx_status_event_handler_.find(if_index); if (handler != on_frame_tx_status_event_handler_.end()) { handler->second(cookie, was_acked); } } void NetlinkManager::SubscribeStationEvent( uint32_t interface_index, OnStationEventHandler handler) { on_station_event_handler_[interface_index] = handler; } void NetlinkManager::UnsubscribeStationEvent(uint32_t interface_index) { on_station_event_handler_.erase(interface_index); } void NetlinkManager::SubscribeChannelSwitchEvent( uint32_t interface_index, OnChannelSwitchEventHandler handler) { on_channel_switch_event_handler_[interface_index] = handler; } void NetlinkManager::UnsubscribeChannelSwitchEvent(uint32_t interface_index) { on_channel_switch_event_handler_.erase(interface_index); } void NetlinkManager::SubscribeRegDomainChange( uint32_t wiphy_index, OnRegDomainChangedHandler handler) { on_reg_domain_changed_handler_[wiphy_index] = handler; } void NetlinkManager::UnsubscribeRegDomainChange(uint32_t wiphy_index) { on_reg_domain_changed_handler_.erase(wiphy_index); } void NetlinkManager::SubscribeScanResultNotification( uint32_t interface_index, OnScanResultsReadyHandler handler) { on_scan_result_ready_handler_[interface_index] = handler; } void NetlinkManager::UnsubscribeScanResultNotification( uint32_t interface_index) { on_scan_result_ready_handler_.erase(interface_index); } void NetlinkManager::SubscribeMlmeEvent(uint32_t interface_index, MlmeEventHandler* handler) { on_mlme_event_handler_[interface_index] = handler; } void NetlinkManager::UnsubscribeMlmeEvent(uint32_t interface_index) { on_mlme_event_handler_.erase(interface_index); } void NetlinkManager::SubscribeSchedScanResultNotification( uint32_t interface_index, OnSchedScanResultsReadyHandler handler) { on_sched_scan_result_ready_handler_[interface_index] = handler; } void NetlinkManager::UnsubscribeSchedScanResultNotification( uint32_t interface_index) { on_sched_scan_result_ready_handler_.erase(interface_index); } void NetlinkManager::SubscribeFrameTxStatusEvent( uint32_t interface_index, OnFrameTxStatusEventHandler handler) { on_frame_tx_status_event_handler_[interface_index] = handler; } void NetlinkManager::UnsubscribeFrameTxStatusEvent(uint32_t interface_index) { on_frame_tx_status_event_handler_.erase(interface_index); } } // namespace wificond } // namespace android