// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Windows Vista uses the Native Wifi (WLAN) API for accessing WiFi cards. See // http://msdn.microsoft.com/en-us/library/ms705945(VS.85).aspx. Windows XP // Service Pack 3 (and Windows XP Service Pack 2, if upgraded with a hot fix) // also support a limited version of the WLAN API. See // http://msdn.microsoft.com/en-us/library/bb204766.aspx. The WLAN API uses // wlanapi.h, which is not part of the SDK used by Gears, so is replicated // locally using data from the MSDN. // // Windows XP from Service Pack 2 onwards supports the Wireless Zero // Configuration (WZC) programming interface. See // http://msdn.microsoft.com/en-us/library/ms706587(VS.85).aspx. // // The MSDN recommends that one use the WLAN API where available, and WZC // otherwise. // // However, it seems that WZC fails for some wireless cards. Also, WLAN seems // not to work on XP SP3. So we use WLAN on Vista, and use NDIS directly // otherwise. #include "content/browser/geolocation/wifi_data_provider_win.h" #include #include #include #include "base/memory/free_deleter.h" #include "base/metrics/histogram.h" #include "base/strings/utf_string_conversions.h" #include "base/win/windows_version.h" #include "content/browser/geolocation/wifi_data_provider_common.h" #include "content/browser/geolocation/wifi_data_provider_common_win.h" #include "content/browser/geolocation/wifi_data_provider_manager.h" // Taken from ndis.h for WinCE. #define NDIS_STATUS_INVALID_LENGTH ((NDIS_STATUS)0xC0010014L) #define NDIS_STATUS_BUFFER_TOO_SHORT ((NDIS_STATUS)0xC0010016L) namespace content { namespace { // The limits on the size of the buffer used for the OID query. const int kInitialBufferSize = 2 << 12; // Good for about 50 APs. const int kMaximumBufferSize = 2 << 20; // 2MB // Length for generic string buffers passed to Windows APIs. const int kStringLength = 512; // The time periods, in milliseconds, between successive polls of the wifi data. const int kDefaultPollingInterval = 10000; // 10s const int kNoChangePollingInterval = 120000; // 2 mins const int kTwoNoChangePollingInterval = 600000; // 10 mins const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s // WlanOpenHandle typedef DWORD (WINAPI* WlanOpenHandleFunction)(DWORD dwClientVersion, PVOID pReserved, PDWORD pdwNegotiatedVersion, PHANDLE phClientHandle); // WlanEnumInterfaces typedef DWORD (WINAPI* WlanEnumInterfacesFunction)( HANDLE hClientHandle, PVOID pReserved, PWLAN_INTERFACE_INFO_LIST* ppInterfaceList); // WlanGetNetworkBssList typedef DWORD (WINAPI* WlanGetNetworkBssListFunction)( HANDLE hClientHandle, const GUID* pInterfaceGuid, const PDOT11_SSID pDot11Ssid, DOT11_BSS_TYPE dot11BssType, BOOL bSecurityEnabled, PVOID pReserved, PWLAN_BSS_LIST* ppWlanBssList ); // WlanFreeMemory typedef VOID (WINAPI* WlanFreeMemoryFunction)(PVOID pMemory); // WlanCloseHandle typedef DWORD (WINAPI* WlanCloseHandleFunction)(HANDLE hClientHandle, PVOID pReserved); // Local classes and functions class WindowsWlanApi : public WifiDataProviderCommon::WlanApiInterface { public: ~WindowsWlanApi() override; // Factory function. Will return NULL if this API is unavailable. static WindowsWlanApi* Create(); // WlanApiInterface bool GetAccessPointData(WifiData::AccessPointDataSet* data) override; private: // Takes ownership of the library handle. explicit WindowsWlanApi(HINSTANCE library); // Loads the required functions from the DLL. void GetWLANFunctions(HINSTANCE wlan_library); int GetInterfaceDataWLAN(HANDLE wlan_handle, const GUID& interface_id, WifiData::AccessPointDataSet* data); // Logs number of detected wlan interfaces. static void LogWlanInterfaceCount(int count); // Handle to the wlanapi.dll library. HINSTANCE library_; // Function pointers for WLAN WlanOpenHandleFunction WlanOpenHandle_function_; WlanEnumInterfacesFunction WlanEnumInterfaces_function_; WlanGetNetworkBssListFunction WlanGetNetworkBssList_function_; WlanFreeMemoryFunction WlanFreeMemory_function_; WlanCloseHandleFunction WlanCloseHandle_function_; }; class WindowsNdisApi : public WifiDataProviderCommon::WlanApiInterface { public: ~WindowsNdisApi() override; static WindowsNdisApi* Create(); // WlanApiInterface bool GetAccessPointData(WifiData::AccessPointDataSet* data) override; private: static bool GetInterfacesNDIS( std::vector* interface_service_names_out); // Swaps in content of the vector passed explicit WindowsNdisApi(std::vector* interface_service_names); bool GetInterfaceDataNDIS(HANDLE adapter_handle, WifiData::AccessPointDataSet* data); // NDIS variables. std::vector interface_service_names_; // Remembers scan result buffer size across calls. int oid_buffer_size_; }; // Extracts data for an access point and converts to Gears format. bool GetNetworkData(const WLAN_BSS_ENTRY& bss_entry, AccessPointData* access_point_data); bool UndefineDosDevice(const base::string16& device_name); bool DefineDosDeviceIfNotExists(const base::string16& device_name); HANDLE GetFileHandle(const base::string16& device_name); // Makes the OID query and returns a Windows API error code. int PerformQuery(HANDLE adapter_handle, BYTE* buffer, DWORD buffer_size, DWORD* bytes_out); bool ResizeBuffer(int requested_size, scoped_ptr* buffer); // Gets the system directory and appends a trailing slash if not already // present. bool GetSystemDirectory(base::string16* path); } // namespace WifiDataProvider* WifiDataProviderManager::DefaultFactoryFunction() { return new WifiDataProviderWin(); } WifiDataProviderWin::WifiDataProviderWin() { } WifiDataProviderWin::~WifiDataProviderWin() { } WifiDataProviderCommon::WlanApiInterface* WifiDataProviderWin::NewWlanApi() { // Use the WLAN interface if we're on Vista and if it's available. Otherwise, // use NDIS. WlanApiInterface* api = WindowsWlanApi::Create(); if (api) { return api; } return WindowsNdisApi::Create(); } WifiPollingPolicy* WifiDataProviderWin::NewPollingPolicy() { return new GenericWifiPollingPolicy; } // Local classes and functions namespace { // WindowsWlanApi WindowsWlanApi::WindowsWlanApi(HINSTANCE library) : library_(library) { GetWLANFunctions(library_); } WindowsWlanApi::~WindowsWlanApi() { FreeLibrary(library_); } WindowsWlanApi* WindowsWlanApi::Create() { if (base::win::GetVersion() < base::win::VERSION_VISTA) return NULL; // We use an absolute path to load the DLL to avoid DLL preloading attacks. base::string16 system_directory; if (!GetSystemDirectory(&system_directory)) { return NULL; } DCHECK(!system_directory.empty()); base::string16 dll_path = system_directory + L"wlanapi.dll"; HINSTANCE library = LoadLibraryEx(dll_path.c_str(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH); if (!library) { return NULL; } return new WindowsWlanApi(library); } void WindowsWlanApi::GetWLANFunctions(HINSTANCE wlan_library) { DCHECK(wlan_library); WlanOpenHandle_function_ = reinterpret_cast( GetProcAddress(wlan_library, "WlanOpenHandle")); WlanEnumInterfaces_function_ = reinterpret_cast( GetProcAddress(wlan_library, "WlanEnumInterfaces")); WlanGetNetworkBssList_function_ = reinterpret_cast( GetProcAddress(wlan_library, "WlanGetNetworkBssList")); WlanFreeMemory_function_ = reinterpret_cast( GetProcAddress(wlan_library, "WlanFreeMemory")); WlanCloseHandle_function_ = reinterpret_cast( GetProcAddress(wlan_library, "WlanCloseHandle")); DCHECK(WlanOpenHandle_function_ && WlanEnumInterfaces_function_ && WlanGetNetworkBssList_function_ && WlanFreeMemory_function_ && WlanCloseHandle_function_); } void WindowsWlanApi::LogWlanInterfaceCount(int count) { UMA_HISTOGRAM_CUSTOM_COUNTS( "Net.Wifi.InterfaceCount", count, 1, 5, 6); } bool WindowsWlanApi::GetAccessPointData( WifiData::AccessPointDataSet* data) { DCHECK(data); // Get the handle to the WLAN API. DWORD negotiated_version; HANDLE wlan_handle = NULL; // We could be executing on either Windows XP or Windows Vista, so use the // lower version of the client WLAN API. It seems that the negotiated version // is the Vista version irrespective of what we pass! static const int kXpWlanClientVersion = 1; if ((*WlanOpenHandle_function_)(kXpWlanClientVersion, NULL, &negotiated_version, &wlan_handle) != ERROR_SUCCESS) { LogWlanInterfaceCount(0); return false; } DCHECK(wlan_handle); // Get the list of interfaces. WlanEnumInterfaces allocates interface_list. WLAN_INTERFACE_INFO_LIST* interface_list = NULL; if ((*WlanEnumInterfaces_function_)(wlan_handle, NULL, &interface_list) != ERROR_SUCCESS) { LogWlanInterfaceCount(0); return false; } DCHECK(interface_list); LogWlanInterfaceCount(interface_list->dwNumberOfItems); // Go through the list of interfaces and get the data for each. for (int i = 0; i < static_cast(interface_list->dwNumberOfItems); ++i) { // Skip any interface that is midway through association; the // WlanGetNetworkBssList function call is known to hang indefinitely // when it's in this state. http://crbug.com/39300 if (interface_list->InterfaceInfo[i].isState == wlan_interface_state_associating) { LOG(WARNING) << "Skipping wifi scan on adapter " << i << " (" << interface_list->InterfaceInfo[i].strInterfaceDescription << ") in 'associating' state. Repeated occurrences " "indicates a non-responding adapter."; continue; } GetInterfaceDataWLAN(wlan_handle, interface_list->InterfaceInfo[i].InterfaceGuid, data); } // Free interface_list. (*WlanFreeMemory_function_)(interface_list); // Close the handle. if ((*WlanCloseHandle_function_)(wlan_handle, NULL) != ERROR_SUCCESS) { return false; } return true; } // Appends the data for a single interface to the data vector. Returns the // number of access points found, or -1 on error. int WindowsWlanApi::GetInterfaceDataWLAN( const HANDLE wlan_handle, const GUID& interface_id, WifiData::AccessPointDataSet* data) { DCHECK(data); const base::TimeTicks start_time = base::TimeTicks::Now(); // WlanGetNetworkBssList allocates bss_list. WLAN_BSS_LIST* bss_list = NULL; if ((*WlanGetNetworkBssList_function_)(wlan_handle, &interface_id, NULL, // Use all SSIDs. dot11_BSS_type_any, false, // bSecurityEnabled - unused NULL, // reserved &bss_list) != ERROR_SUCCESS) { return -1; } // According to http://www.attnetclient.com/kb/questions.php?questionid=75 // WlanGetNetworkBssList can sometimes return success, but leave the bss // list as NULL. if (!bss_list) return -1; const base::TimeDelta duration = base::TimeTicks::Now() - start_time; UMA_HISTOGRAM_CUSTOM_TIMES( "Net.Wifi.ScanLatency", duration, base::TimeDelta::FromMilliseconds(1), base::TimeDelta::FromMinutes(1), 100); int found = 0; for (int i = 0; i < static_cast(bss_list->dwNumberOfItems); ++i) { AccessPointData access_point_data; if (GetNetworkData(bss_list->wlanBssEntries[i], &access_point_data)) { ++found; data->insert(access_point_data); } } (*WlanFreeMemory_function_)(bss_list); return found; } // WindowsNdisApi WindowsNdisApi::WindowsNdisApi( std::vector* interface_service_names) : oid_buffer_size_(kInitialBufferSize) { DCHECK(!interface_service_names->empty()); interface_service_names_.swap(*interface_service_names); } WindowsNdisApi::~WindowsNdisApi() { } WindowsNdisApi* WindowsNdisApi::Create() { std::vector interface_service_names; if (GetInterfacesNDIS(&interface_service_names)) { return new WindowsNdisApi(&interface_service_names); } return NULL; } bool WindowsNdisApi::GetAccessPointData(WifiData::AccessPointDataSet* data) { DCHECK(data); int interfaces_failed = 0; int interfaces_succeeded = 0; for (int i = 0; i < static_cast(interface_service_names_.size()); ++i) { // First, check that we have a DOS device for this adapter. if (!DefineDosDeviceIfNotExists(interface_service_names_[i])) { continue; } // Get the handle to the device. This will fail if the named device is not // valid. HANDLE adapter_handle = GetFileHandle(interface_service_names_[i]); if (adapter_handle == INVALID_HANDLE_VALUE) { continue; } // Get the data. if (GetInterfaceDataNDIS(adapter_handle, data)) { ++interfaces_succeeded; } else { ++interfaces_failed; } // Clean up. CloseHandle(adapter_handle); UndefineDosDevice(interface_service_names_[i]); } // Return true if at least one interface succeeded, or at the very least none // failed. return interfaces_succeeded > 0 || interfaces_failed == 0; } bool WindowsNdisApi::GetInterfacesNDIS( std::vector* interface_service_names_out) { HKEY network_cards_key = NULL; if (RegOpenKeyEx( HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards", 0, KEY_READ, &network_cards_key) != ERROR_SUCCESS) { return false; } DCHECK(network_cards_key); for (int i = 0; ; ++i) { TCHAR name[kStringLength]; DWORD name_size = kStringLength; FILETIME time; if (RegEnumKeyEx(network_cards_key, i, name, &name_size, NULL, NULL, NULL, &time) != ERROR_SUCCESS) { break; } HKEY hardware_key = NULL; if (RegOpenKeyEx(network_cards_key, name, 0, KEY_READ, &hardware_key) != ERROR_SUCCESS) { break; } DCHECK(hardware_key); TCHAR service_name[kStringLength]; DWORD service_name_size = kStringLength; DWORD type = 0; if (RegQueryValueEx(hardware_key, L"ServiceName", NULL, &type, reinterpret_cast(service_name), &service_name_size) == ERROR_SUCCESS) { interface_service_names_out->push_back(service_name); } RegCloseKey(hardware_key); } RegCloseKey(network_cards_key); return true; } bool WindowsNdisApi::GetInterfaceDataNDIS(HANDLE adapter_handle, WifiData::AccessPointDataSet* data) { DCHECK(data); scoped_ptr buffer( static_cast(malloc(oid_buffer_size_))); if (buffer == NULL) { return false; } DWORD bytes_out; int result; while (true) { bytes_out = 0; result = PerformQuery(adapter_handle, buffer.get(), oid_buffer_size_, &bytes_out); if (result == ERROR_GEN_FAILURE || // Returned by some Intel cards. result == ERROR_INSUFFICIENT_BUFFER || result == ERROR_MORE_DATA || result == NDIS_STATUS_INVALID_LENGTH || result == NDIS_STATUS_BUFFER_TOO_SHORT) { // The buffer we supplied is too small, so increase it. bytes_out should // provide the required buffer size, but this is not always the case. if (bytes_out > static_cast(oid_buffer_size_)) { oid_buffer_size_ = bytes_out; } else { oid_buffer_size_ *= 2; } if (!ResizeBuffer(oid_buffer_size_, &buffer)) { oid_buffer_size_ = kInitialBufferSize; // Reset for next time. return false; } } else { // The buffer is not too small. break; } } DCHECK(buffer.get()); if (result == ERROR_SUCCESS) { NDIS_802_11_BSSID_LIST* bssid_list = reinterpret_cast(buffer.get()); GetDataFromBssIdList(*bssid_list, oid_buffer_size_, data); } return true; } bool GetNetworkData(const WLAN_BSS_ENTRY& bss_entry, AccessPointData* access_point_data) { // Currently we get only MAC address, signal strength and SSID. DCHECK(access_point_data); access_point_data->mac_address = MacAddressAsString16(bss_entry.dot11Bssid); access_point_data->radio_signal_strength = bss_entry.lRssi; // bss_entry.dot11Ssid.ucSSID is not null-terminated. base::UTF8ToUTF16(reinterpret_cast(bss_entry.dot11Ssid.ucSSID), static_cast(bss_entry.dot11Ssid.uSSIDLength), &access_point_data->ssid); // TODO(steveblock): Is it possible to get the following? // access_point_data->signal_to_noise // access_point_data->age // access_point_data->channel return true; } bool UndefineDosDevice(const base::string16& device_name) { // We remove only the mapping we use, that is \Device\. base::string16 target_path = L"\\Device\\" + device_name; return DefineDosDevice( DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE, device_name.c_str(), target_path.c_str()) == TRUE; } bool DefineDosDeviceIfNotExists(const base::string16& device_name) { // We create a DOS device name for the device at \Device\. base::string16 target_path = L"\\Device\\" + device_name; TCHAR target[kStringLength]; if (QueryDosDevice(device_name.c_str(), target, kStringLength) > 0 && target_path.compare(target) == 0) { // Device already exists. return true; } if (GetLastError() != ERROR_FILE_NOT_FOUND) { return false; } if (!DefineDosDevice(DDD_RAW_TARGET_PATH, device_name.c_str(), target_path.c_str())) { return false; } // Check that the device is really there. return QueryDosDevice(device_name.c_str(), target, kStringLength) > 0 && target_path.compare(target) == 0; } HANDLE GetFileHandle(const base::string16& device_name) { // We access a device with DOS path \Device\ at // \\.\. base::string16 formatted_device_name = L"\\\\.\\" + device_name; return CreateFile(formatted_device_name.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, // share mode 0, // security attributes OPEN_EXISTING, 0, // flags and attributes INVALID_HANDLE_VALUE); } int PerformQuery(HANDLE adapter_handle, BYTE* buffer, DWORD buffer_size, DWORD* bytes_out) { DWORD oid = OID_802_11_BSSID_LIST; if (!DeviceIoControl(adapter_handle, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid, sizeof(oid), buffer, buffer_size, bytes_out, NULL)) { return GetLastError(); } return ERROR_SUCCESS; } bool ResizeBuffer(int requested_size, scoped_ptr* buffer) { DCHECK_GT(requested_size, 0); DCHECK(buffer); if (requested_size > kMaximumBufferSize) { buffer->reset(); return false; } buffer->reset(reinterpret_cast( realloc(buffer->release(), requested_size))); return buffer != NULL; } bool GetSystemDirectory(base::string16* path) { DCHECK(path); // Return value includes terminating NULL. int buffer_size = ::GetSystemDirectory(NULL, 0); if (buffer_size == 0) { return false; } scoped_ptr buffer(new base::char16[buffer_size]); // Return value excludes terminating NULL. int characters_written = ::GetSystemDirectory(buffer.get(), buffer_size); if (characters_written == 0) { return false; } DCHECK_EQ(buffer_size - 1, characters_written); path->assign(buffer.get(), characters_written); if (*path->rbegin() != L'\\') { path->append(L"\\"); } DCHECK_EQ(L'\\', *path->rbegin()); return true; } } // namespace } // namespace content