/* * 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 "wificond/net/netlink_utils.h" #include <array> #include <algorithm> #include <bitset> #include <map> #include <string> #include <vector> #include <net/if.h> #include <linux/netlink.h> #include <android-base/logging.h> #include "wificond/net/kernel-header-latest/nl80211.h" #include "wificond/net/mlme_event_handler.h" #include "wificond/net/nl80211_packet.h" using std::array; using std::make_pair; using std::make_unique; using std::map; using std::move; using std::string; using std::unique_ptr; using std::vector; namespace android { namespace wificond { namespace { uint32_t k2GHzFrequencyLowerBound = 2400; uint32_t k2GHzFrequencyUpperBound = 2500; uint32_t k5GHzFrequencyLowerBound = 5000; // This upper bound will exclude any 5.9Ghz channels which belong to 802.11p // for "vehicular communication systems". uint32_t k5GHzFrequencyUpperBound = 5850; bool IsExtFeatureFlagSet( const std::vector<uint8_t>& ext_feature_flags_bytes, enum nl80211_ext_feature_index ext_feature_flag) { static_assert(NUM_NL80211_EXT_FEATURES <= SIZE_MAX, "Ext feature values doesn't fit in |size_t|"); // TODO:This is an unsafe cast because this assumes that the values // are always unsigned! size_t ext_feature_flag_idx = static_cast<size_t>(ext_feature_flag); size_t ext_feature_flag_byte_pos = ext_feature_flag_idx / 8; size_t ext_feature_flag_bit_pos = ext_feature_flag_idx % 8; if (ext_feature_flag_byte_pos >= ext_feature_flags_bytes.size()) { return false; } uint8_t ext_feature_flag_byte = ext_feature_flags_bytes[ext_feature_flag_byte_pos]; return (ext_feature_flag_byte & (1U << ext_feature_flag_bit_pos)); } } // namespace WiphyFeatures::WiphyFeatures(uint32_t feature_flags, const std::vector<uint8_t>& ext_feature_flags_bytes) : supports_random_mac_oneshot_scan( feature_flags & NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR), supports_random_mac_sched_scan( feature_flags & NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR) { supports_low_span_oneshot_scan = IsExtFeatureFlagSet(ext_feature_flags_bytes, NL80211_EXT_FEATURE_LOW_SPAN_SCAN); supports_low_power_oneshot_scan = IsExtFeatureFlagSet(ext_feature_flags_bytes, NL80211_EXT_FEATURE_LOW_POWER_SCAN); supports_high_accuracy_oneshot_scan = IsExtFeatureFlagSet(ext_feature_flags_bytes, NL80211_EXT_FEATURE_HIGH_ACCURACY_SCAN); // TODO (b/112029045) check if sending frame at specified MCS is supported supports_tx_mgmt_frame_mcs = false; supports_ext_sched_scan_relative_rssi = IsExtFeatureFlagSet(ext_feature_flags_bytes, NL80211_EXT_FEATURE_SCHED_SCAN_RELATIVE_RSSI); } NetlinkUtils::NetlinkUtils(NetlinkManager* netlink_manager) : netlink_manager_(netlink_manager) { if (!netlink_manager_->IsStarted()) { netlink_manager_->Start(); } uint32_t protocol_features = 0; supports_split_wiphy_dump_ = GetProtocolFeatures(&protocol_features) && (protocol_features & NL80211_PROTOCOL_FEATURE_SPLIT_WIPHY_DUMP); } NetlinkUtils::~NetlinkUtils() {} bool NetlinkUtils::GetWiphyIndex(uint32_t* out_wiphy_index, const std::string& iface_name) { NL80211Packet get_wiphy( netlink_manager_->GetFamilyId(), NL80211_CMD_GET_WIPHY, netlink_manager_->GetSequenceNumber(), getpid()); get_wiphy.AddFlag(NLM_F_DUMP); if (!iface_name.empty()) { int ifindex = if_nametoindex(iface_name.c_str()); get_wiphy.AddAttribute(NL80211Attr<uint32_t>(NL80211_ATTR_IFINDEX, ifindex)); } vector<unique_ptr<const NL80211Packet>> response; if (!netlink_manager_->SendMessageAndGetResponses(get_wiphy, &response)) { LOG(ERROR) << "NL80211_CMD_GET_WIPHY dump failed"; return false; } if (response.empty()) { LOG(DEBUG) << "No wiphy is found"; return false; } for (auto& packet : response) { if (packet->GetMessageType() == NLMSG_ERROR) { LOG(ERROR) << "Receive ERROR message: " << strerror(packet->GetErrorCode()); return false; } if (packet->GetMessageType() != netlink_manager_->GetFamilyId()) { LOG(ERROR) << "Wrong message type for new interface message: " << packet->GetMessageType(); return false; } if (packet->GetCommand() != NL80211_CMD_NEW_WIPHY) { LOG(ERROR) << "Wrong command in response to " << "a wiphy dump request: " << static_cast<int>(packet->GetCommand()); return false; } if (!packet->GetAttributeValue(NL80211_ATTR_WIPHY, out_wiphy_index)) { LOG(ERROR) << "Failed to get wiphy index from reply message"; return false; } } return true; } bool NetlinkUtils::GetWiphyIndex(uint32_t* out_wiphy_index) { return GetWiphyIndex(out_wiphy_index, ""); } bool NetlinkUtils::GetInterfaces(uint32_t wiphy_index, vector<InterfaceInfo>* interface_info) { NL80211Packet get_interfaces( netlink_manager_->GetFamilyId(), NL80211_CMD_GET_INTERFACE, netlink_manager_->GetSequenceNumber(), getpid()); get_interfaces.AddFlag(NLM_F_DUMP); get_interfaces.AddAttribute( NL80211Attr<uint32_t>(NL80211_ATTR_WIPHY, wiphy_index)); vector<unique_ptr<const NL80211Packet>> response; if (!netlink_manager_->SendMessageAndGetResponses(get_interfaces, &response)) { LOG(ERROR) << "NL80211_CMD_GET_INTERFACE dump failed"; return false; } if (response.empty()) { LOG(ERROR) << "No interface is found"; return false; } for (auto& packet : response) { if (packet->GetMessageType() == NLMSG_ERROR) { LOG(ERROR) << "Receive ERROR message: " << strerror(packet->GetErrorCode()); return false; } if (packet->GetMessageType() != netlink_manager_->GetFamilyId()) { LOG(ERROR) << "Wrong message type for new interface message: " << packet->GetMessageType(); return false; } if (packet->GetCommand() != NL80211_CMD_NEW_INTERFACE) { LOG(ERROR) << "Wrong command in response to " << "an interface dump request: " << static_cast<int>(packet->GetCommand()); return false; } // In some situations, it has been observed that the kernel tells us // about a pseudo interface that does not have a real netdev. In this // case, responses will have a NL80211_ATTR_WDEV, and not the expected // IFNAME/IFINDEX. In this case we just skip these pseudo interfaces. uint32_t if_index; if (!packet->GetAttributeValue(NL80211_ATTR_IFINDEX, &if_index)) { LOG(DEBUG) << "Failed to get interface index"; continue; } // Today we don't check NL80211_ATTR_IFTYPE because at this point of time // driver always reports that interface is in STATION mode. Even when we // are asking interfaces infomation on behalf of tethering, it is still so // because hostapd is supposed to set interface to AP mode later. string if_name; if (!packet->GetAttributeValue(NL80211_ATTR_IFNAME, &if_name)) { LOG(WARNING) << "Failed to get interface name"; continue; } array<uint8_t, ETH_ALEN> if_mac_addr; if (!packet->GetAttributeValue(NL80211_ATTR_MAC, &if_mac_addr)) { LOG(WARNING) << "Failed to get interface mac address"; continue; } interface_info->emplace_back(if_index, if_name, if_mac_addr); } return true; } bool NetlinkUtils::SetInterfaceMode(uint32_t interface_index, InterfaceMode mode) { uint32_t set_to_mode = NL80211_IFTYPE_UNSPECIFIED; if (mode == STATION_MODE) { set_to_mode = NL80211_IFTYPE_STATION; } else { LOG(ERROR) << "Unexpected mode for interface with index: " << interface_index; return false; } NL80211Packet set_interface_mode( netlink_manager_->GetFamilyId(), NL80211_CMD_SET_INTERFACE, netlink_manager_->GetSequenceNumber(), getpid()); // Force an ACK response upon success. set_interface_mode.AddFlag(NLM_F_ACK); set_interface_mode.AddAttribute( NL80211Attr<uint32_t>(NL80211_ATTR_IFINDEX, interface_index)); set_interface_mode.AddAttribute( NL80211Attr<uint32_t>(NL80211_ATTR_IFTYPE, set_to_mode)); if (!netlink_manager_->SendMessageAndGetAck(set_interface_mode)) { LOG(ERROR) << "NL80211_CMD_SET_INTERFACE failed"; return false; } return true; } bool NetlinkUtils::GetProtocolFeatures(uint32_t* features) { NL80211Packet get_protocol_features( netlink_manager_->GetFamilyId(), NL80211_CMD_GET_PROTOCOL_FEATURES, netlink_manager_->GetSequenceNumber(), getpid()); unique_ptr<const NL80211Packet> response; if (!netlink_manager_->SendMessageAndGetSingleResponse(get_protocol_features, &response)) { LOG(ERROR) << "NL80211_CMD_GET_PROTOCOL_FEATURES failed"; return false; } if (!response->GetAttributeValue(NL80211_ATTR_PROTOCOL_FEATURES, features)) { LOG(ERROR) << "Failed to get NL80211_ATTR_PROTOCOL_FEATURES"; return false; } return true; } bool NetlinkUtils::GetWiphyInfo( uint32_t wiphy_index, BandInfo* out_band_info, ScanCapabilities* out_scan_capabilities, WiphyFeatures* out_wiphy_features) { NL80211Packet get_wiphy( netlink_manager_->GetFamilyId(), NL80211_CMD_GET_WIPHY, netlink_manager_->GetSequenceNumber(), getpid()); get_wiphy.AddAttribute(NL80211Attr<uint32_t>(NL80211_ATTR_WIPHY, wiphy_index)); if (supports_split_wiphy_dump_) { get_wiphy.AddFlagAttribute(NL80211_ATTR_SPLIT_WIPHY_DUMP); get_wiphy.AddFlag(NLM_F_DUMP); } vector<unique_ptr<const NL80211Packet>> response; if (!netlink_manager_->SendMessageAndGetResponses(get_wiphy, &response)) { LOG(ERROR) << "NL80211_CMD_GET_WIPHY dump failed"; return false; } vector<NL80211Packet> packet_per_wiphy; if (supports_split_wiphy_dump_) { if (!MergePacketsForSplitWiphyDump(response, &packet_per_wiphy)) { LOG(WARNING) << "Failed to merge responses from split wiphy dump"; } } else { for (auto& packet : response) { packet_per_wiphy.push_back(move(*(packet.release()))); } } for (const auto& packet : packet_per_wiphy) { uint32_t current_wiphy_index; if (!packet.GetAttributeValue(NL80211_ATTR_WIPHY, ¤t_wiphy_index) || // Not the wihpy we requested. current_wiphy_index != wiphy_index) { continue; } if (ParseWiphyInfoFromPacket(packet, out_band_info, out_scan_capabilities, out_wiphy_features)) { return true; } } LOG(ERROR) << "Failed to find expected wiphy info " << "from NL80211_CMD_GET_WIPHY responses"; return false; } bool NetlinkUtils::ParseWiphyInfoFromPacket( const NL80211Packet& packet, BandInfo* out_band_info, ScanCapabilities* out_scan_capabilities, WiphyFeatures* out_wiphy_features) { if (packet.GetCommand() != NL80211_CMD_NEW_WIPHY) { LOG(ERROR) << "Wrong command in response to a get wiphy request: " << static_cast<int>(packet.GetCommand()); return false; } if (!ParseBandInfo(&packet, out_band_info) || !ParseScanCapabilities(&packet, out_scan_capabilities)) { return false; } uint32_t feature_flags; if (!packet.GetAttributeValue(NL80211_ATTR_FEATURE_FLAGS, &feature_flags)) { LOG(ERROR) << "Failed to get NL80211_ATTR_FEATURE_FLAGS"; return false; } std::vector<uint8_t> ext_feature_flags_bytes; if (!packet.GetAttributeValue(NL80211_ATTR_EXT_FEATURES, &ext_feature_flags_bytes)) { LOG(WARNING) << "Failed to get NL80211_ATTR_EXT_FEATURES"; } *out_wiphy_features = WiphyFeatures(feature_flags, ext_feature_flags_bytes); return true; } bool NetlinkUtils::ParseScanCapabilities( const NL80211Packet* const packet, ScanCapabilities* out_scan_capabilities) { uint8_t max_num_scan_ssids; if (!packet->GetAttributeValue(NL80211_ATTR_MAX_NUM_SCAN_SSIDS, &max_num_scan_ssids)) { LOG(ERROR) << "Failed to get the capacity of maximum number of scan ssids"; return false; } uint8_t max_num_sched_scan_ssids; if (!packet->GetAttributeValue(NL80211_ATTR_MAX_NUM_SCHED_SCAN_SSIDS, &max_num_sched_scan_ssids)) { LOG(ERROR) << "Failed to get the capacity of " << "maximum number of scheduled scan ssids"; return false; } // Use default value 0 for scan plan capabilities if attributes are missing. uint32_t max_num_scan_plans = 0; packet->GetAttributeValue(NL80211_ATTR_MAX_NUM_SCHED_SCAN_PLANS, &max_num_scan_plans); uint32_t max_scan_plan_interval = 0; packet->GetAttributeValue(NL80211_ATTR_MAX_SCAN_PLAN_INTERVAL, &max_scan_plan_interval); uint32_t max_scan_plan_iterations = 0; packet->GetAttributeValue(NL80211_ATTR_MAX_SCAN_PLAN_ITERATIONS, &max_scan_plan_iterations); uint8_t max_match_sets; if (!packet->GetAttributeValue(NL80211_ATTR_MAX_MATCH_SETS, &max_match_sets)) { LOG(ERROR) << "Failed to get the capacity of maximum number of match set" << "of a scheduled scan"; return false; } *out_scan_capabilities = ScanCapabilities(max_num_scan_ssids, max_num_sched_scan_ssids, max_match_sets, max_num_scan_plans, max_scan_plan_interval, max_scan_plan_iterations); return true; } bool NetlinkUtils::ParseBandInfo(const NL80211Packet* const packet, BandInfo* out_band_info) { NL80211NestedAttr bands_attr(0); if (!packet->GetAttribute(NL80211_ATTR_WIPHY_BANDS, &bands_attr)) { LOG(ERROR) << "Failed to get NL80211_ATTR_WIPHY_BANDS"; return false; } vector<NL80211NestedAttr> bands; if (!bands_attr.GetListOfNestedAttributes(&bands)) { LOG(ERROR) << "Failed to get bands within NL80211_ATTR_WIPHY_BANDS"; return false; } vector<uint32_t> frequencies_2g; vector<uint32_t> frequencies_5g; vector<uint32_t> frequencies_dfs; for (unsigned int band_index = 0; band_index < bands.size(); band_index++) { NL80211NestedAttr freqs_attr(0); if (!bands[band_index].GetAttribute(NL80211_BAND_ATTR_FREQS, &freqs_attr)) { LOG(DEBUG) << "Failed to get NL80211_BAND_ATTR_FREQS"; continue; } vector<NL80211NestedAttr> freqs; if (!freqs_attr.GetListOfNestedAttributes(&freqs)) { LOG(ERROR) << "Failed to get frequencies within NL80211_BAND_ATTR_FREQS"; continue; } for (auto& freq : freqs) { uint32_t frequency_value; if (!freq.GetAttributeValue(NL80211_FREQUENCY_ATTR_FREQ, &frequency_value)) { LOG(DEBUG) << "Failed to get NL80211_FREQUENCY_ATTR_FREQ"; continue; } // Channel is disabled in current regulatory domain. if (freq.HasAttribute(NL80211_FREQUENCY_ATTR_DISABLED)) { continue; } if (frequency_value > k2GHzFrequencyLowerBound && frequency_value < k2GHzFrequencyUpperBound) { frequencies_2g.push_back(frequency_value); } else if (frequency_value > k5GHzFrequencyLowerBound && frequency_value < k5GHzFrequencyUpperBound) { // If this is an available/usable DFS frequency, we should save it to // DFS frequencies list. uint32_t dfs_state; if (freq.GetAttributeValue(NL80211_FREQUENCY_ATTR_DFS_STATE, &dfs_state) && (dfs_state == NL80211_DFS_AVAILABLE || dfs_state == NL80211_DFS_USABLE)) { frequencies_dfs.push_back(frequency_value); continue; } // Put non-dfs passive-only channels into the dfs category. // This aligns with what framework always assumes. if (freq.HasAttribute(NL80211_FREQUENCY_ATTR_NO_IR)) { frequencies_dfs.push_back(frequency_value); continue; } // Otherwise, this is a regular 5g frequency. frequencies_5g.push_back(frequency_value); } } } *out_band_info = BandInfo(frequencies_2g, frequencies_5g, frequencies_dfs); return true; } bool NetlinkUtils::GetStationInfo(uint32_t interface_index, const array<uint8_t, ETH_ALEN>& mac_address, StationInfo* out_station_info) { NL80211Packet get_station( netlink_manager_->GetFamilyId(), NL80211_CMD_GET_STATION, netlink_manager_->GetSequenceNumber(), getpid()); get_station.AddAttribute(NL80211Attr<uint32_t>(NL80211_ATTR_IFINDEX, interface_index)); get_station.AddAttribute(NL80211Attr<array<uint8_t, ETH_ALEN>>( NL80211_ATTR_MAC, mac_address)); unique_ptr<const NL80211Packet> response; if (!netlink_manager_->SendMessageAndGetSingleResponse(get_station, &response)) { LOG(ERROR) << "NL80211_CMD_GET_STATION failed"; return false; } if (response->GetCommand() != NL80211_CMD_NEW_STATION) { LOG(ERROR) << "Wrong command in response to a get station request: " << static_cast<int>(response->GetCommand()); return false; } NL80211NestedAttr sta_info(0); if (!response->GetAttribute(NL80211_ATTR_STA_INFO, &sta_info)) { LOG(ERROR) << "Failed to get NL80211_ATTR_STA_INFO"; return false; } int32_t tx_good, tx_bad; if (!sta_info.GetAttributeValue(NL80211_STA_INFO_TX_PACKETS, &tx_good)) { LOG(ERROR) << "Failed to get NL80211_STA_INFO_TX_PACKETS"; return false; } if (!sta_info.GetAttributeValue(NL80211_STA_INFO_TX_FAILED, &tx_bad)) { LOG(ERROR) << "Failed to get NL80211_STA_INFO_TX_FAILED"; return false; } int8_t current_rssi; if (!sta_info.GetAttributeValue(NL80211_STA_INFO_SIGNAL, ¤t_rssi)) { LOG(ERROR) << "Failed to get NL80211_STA_INFO_SIGNAL"; return false; } NL80211NestedAttr tx_bitrate_attr(0); uint32_t tx_bitrate = 0; if (sta_info.GetAttribute(NL80211_STA_INFO_TX_BITRATE, &tx_bitrate_attr)) { if (!tx_bitrate_attr.GetAttributeValue(NL80211_RATE_INFO_BITRATE32, &tx_bitrate)) { // Return invalid tx rate to avoid breaking the get station cmd tx_bitrate = 0; } } NL80211NestedAttr rx_bitrate_attr(0); uint32_t rx_bitrate = 0; if (sta_info.GetAttribute(NL80211_STA_INFO_RX_BITRATE, &rx_bitrate_attr)) { if (!rx_bitrate_attr.GetAttributeValue(NL80211_RATE_INFO_BITRATE32, &rx_bitrate)) { // Return invalid rx rate to avoid breaking the get station cmd rx_bitrate = 0; } } *out_station_info = StationInfo(tx_good, tx_bad, tx_bitrate, current_rssi, rx_bitrate); return true; } // This is a helper function for merging split NL80211_CMD_NEW_WIPHY packets. // For example: // First NL80211_CMD_NEW_WIPHY has attribute A with payload 0x1234. // Second NL80211_CMD_NEW_WIPHY has attribute A with payload 0x5678. // The generated NL80211_CMD_NEW_WIPHY will have attribute A with // payload 0x12345678. // NL80211_ATTR_WIPHY, NL80211_ATTR_IFINDEX, and NL80211_ATTR_WDEV // are used for filtering packets so we know which packets should // be merged together. bool NetlinkUtils::MergePacketsForSplitWiphyDump( const vector<unique_ptr<const NL80211Packet>>& split_dump_info, vector<NL80211Packet>* packet_per_wiphy) { map<uint32_t, map<int, BaseNL80211Attr>> attr_by_wiphy_and_id; // Construct the map using input packets. for (const auto& packet : split_dump_info) { uint32_t wiphy_index; if (!packet->GetAttributeValue(NL80211_ATTR_WIPHY, &wiphy_index)) { LOG(ERROR) << "Failed to get NL80211_ATTR_WIPHY from wiphy split dump"; return false; } vector<BaseNL80211Attr> attributes; if (!packet->GetAllAttributes(&attributes)) { return false; } for (auto& attr : attributes) { int attr_id = attr.GetAttributeId(); if (attr_id != NL80211_ATTR_WIPHY && attr_id != NL80211_ATTR_IFINDEX && attr_id != NL80211_ATTR_WDEV) { auto attr_id_and_attr = attr_by_wiphy_and_id[wiphy_index].find(attr_id); if (attr_id_and_attr == attr_by_wiphy_and_id[wiphy_index].end()) { attr_by_wiphy_and_id[wiphy_index]. insert(make_pair(attr_id, move(attr))); } else { attr_id_and_attr->second.Merge(attr); } } } } // Generate output packets using the constructed map. for (const auto& wiphy_and_attributes : attr_by_wiphy_and_id) { NL80211Packet new_wiphy(0, NL80211_CMD_NEW_WIPHY, 0, 0); new_wiphy.AddAttribute( NL80211Attr<uint32_t>(NL80211_ATTR_WIPHY, wiphy_and_attributes.first)); for (const auto& attr : wiphy_and_attributes.second) { new_wiphy.AddAttribute(attr.second); } packet_per_wiphy->emplace_back(move(new_wiphy)); } return true; } bool NetlinkUtils::GetCountryCode(string* out_country_code) { NL80211Packet get_country_code( netlink_manager_->GetFamilyId(), NL80211_CMD_GET_REG, netlink_manager_->GetSequenceNumber(), getpid()); unique_ptr<const NL80211Packet> response; if (!netlink_manager_->SendMessageAndGetSingleResponse(get_country_code, &response)) { LOG(ERROR) << "NL80211_CMD_GET_REG failed"; return false; } if (!response->GetAttributeValue(NL80211_ATTR_REG_ALPHA2, out_country_code)) { LOG(ERROR) << "Get NL80211_ATTR_REG_ALPHA2 failed"; return false; } return true; } bool NetlinkUtils::SendMgmtFrame(uint32_t interface_index, const vector<uint8_t>& frame, int32_t mcs, uint64_t* out_cookie) { NL80211Packet send_mgmt_frame( netlink_manager_->GetFamilyId(), NL80211_CMD_FRAME, netlink_manager_->GetSequenceNumber(), getpid()); send_mgmt_frame.AddAttribute( NL80211Attr<uint32_t>(NL80211_ATTR_IFINDEX, interface_index)); send_mgmt_frame.AddAttribute( NL80211Attr<vector<uint8_t>>(NL80211_ATTR_FRAME, frame)); if (mcs >= 0) { // TODO (b/112029045) if mcs >= 0, add MCS attribute } unique_ptr<const NL80211Packet> response; if (!netlink_manager_->SendMessageAndGetSingleResponse( send_mgmt_frame, &response)) { LOG(ERROR) << "NL80211_CMD_FRAME failed"; return false; } if (!response->GetAttributeValue(NL80211_ATTR_COOKIE, out_cookie)) { LOG(ERROR) << "Get NL80211_ATTR_COOKIE failed"; return false; } return true; } void NetlinkUtils::SubscribeMlmeEvent(uint32_t interface_index, MlmeEventHandler* handler) { netlink_manager_->SubscribeMlmeEvent(interface_index, handler); } void NetlinkUtils::UnsubscribeMlmeEvent(uint32_t interface_index) { netlink_manager_->UnsubscribeMlmeEvent(interface_index); } void NetlinkUtils::SubscribeRegDomainChange( uint32_t wiphy_index, OnRegDomainChangedHandler handler) { netlink_manager_->SubscribeRegDomainChange(wiphy_index, handler); } void NetlinkUtils::UnsubscribeRegDomainChange(uint32_t wiphy_index) { netlink_manager_->UnsubscribeRegDomainChange(wiphy_index); } void NetlinkUtils::SubscribeStationEvent(uint32_t interface_index, OnStationEventHandler handler) { netlink_manager_->SubscribeStationEvent(interface_index, handler); } void NetlinkUtils::UnsubscribeStationEvent(uint32_t interface_index) { netlink_manager_->UnsubscribeStationEvent(interface_index); } void NetlinkUtils::SubscribeChannelSwitchEvent(uint32_t interface_index, OnChannelSwitchEventHandler handler) { netlink_manager_->SubscribeChannelSwitchEvent(interface_index, handler); } void NetlinkUtils::UnsubscribeChannelSwitchEvent(uint32_t interface_index) { netlink_manager_->UnsubscribeChannelSwitchEvent(interface_index); } void NetlinkUtils::SubscribeFrameTxStatusEvent( uint32_t interface_index, OnFrameTxStatusEventHandler handler) { netlink_manager_->SubscribeFrameTxStatusEvent(interface_index, handler); } void NetlinkUtils::UnsubscribeFrameTxStatusEvent(uint32_t interface_index) { netlink_manager_->UnsubscribeFrameTxStatusEvent(interface_index); } } // namespace wificond } // namespace android