blob: 8b296ae1f4e84d546b2063be834608a05dc25a6e [file] [log] [blame]
// 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 "device/geolocation/wifi_data_provider_win.h"
#include <windows.h>
#include <winioctl.h>
#include <wlanapi.h>
#include "base/memory/free_deleter.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/windows_version.h"
#include "device/geolocation/wifi_data_provider_common.h"
#include "device/geolocation/wifi_data_provider_common_win.h"
#include "device/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 device {
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:
// Factory function. Will return NULL if this API is unavailable.
static std::unique_ptr<WindowsWlanApi> Create();
// Takes ownership of the library handle.
explicit WindowsWlanApi(HINSTANCE library);
~WindowsWlanApi() override;
// WlanApiInterface
bool GetAccessPointData(WifiData::AccessPointDataSet* data) override;
private:
// 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:
static std::unique_ptr<WindowsNdisApi> Create();
// Swaps in content of the vector passed
explicit WindowsNdisApi(std::vector<base::string16>* interface_service_names);
~WindowsNdisApi() override;
// WlanApiInterface
bool GetAccessPointData(WifiData::AccessPointDataSet* data) override;
private:
static bool GetInterfacesNDIS(
std::vector<base::string16>* interface_service_names_out);
bool GetInterfaceDataNDIS(HANDLE adapter_handle,
WifiData::AccessPointDataSet* data);
// NDIS variables.
std::vector<base::string16> 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,
std::unique_ptr<BYTE, base::FreeDeleter>* buffer);
// Gets the system directory and appends a trailing slash if not already
// present.
bool GetSystemDirectory(base::string16* path);
} // anonymous namespace
WifiDataProvider* WifiDataProviderManager::DefaultFactoryFunction() {
return new WifiDataProviderWin();
}
WifiDataProviderWin::WifiDataProviderWin() {}
WifiDataProviderWin::~WifiDataProviderWin() {}
std::unique_ptr<WifiDataProviderCommon::WlanApiInterface>
WifiDataProviderWin::CreateWlanApi() {
// Use the WLAN interface if we're on Vista and if it's available. Otherwise,
// use NDIS.
std::unique_ptr<WlanApiInterface> api = WindowsWlanApi::Create();
if (api)
return api;
return WindowsNdisApi::Create();
}
std::unique_ptr<WifiPollingPolicy> WifiDataProviderWin::CreatePollingPolicy() {
return base::MakeUnique<GenericWifiPollingPolicy<
kDefaultPollingInterval, kNoChangePollingInterval,
kTwoNoChangePollingInterval, kNoWifiPollingIntervalMilliseconds>>();
}
// Local classes and functions
namespace {
// WindowsWlanApi
WindowsWlanApi::WindowsWlanApi(HINSTANCE library) : library_(library) {
GetWLANFunctions(library_);
}
WindowsWlanApi::~WindowsWlanApi() {
FreeLibrary(library_);
}
std::unique_ptr<WindowsWlanApi> WindowsWlanApi::Create() {
if (base::win::GetVersion() < base::win::VERSION_VISTA)
return nullptr;
// We use an absolute path to load the DLL to avoid DLL preloading attacks.
base::string16 system_directory;
if (!GetSystemDirectory(&system_directory))
return nullptr;
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 nullptr;
return base::MakeUnique<WindowsWlanApi>(library);
}
void WindowsWlanApi::GetWLANFunctions(HINSTANCE wlan_library) {
DCHECK(wlan_library);
WlanOpenHandle_function_ = reinterpret_cast<WlanOpenHandleFunction>(
GetProcAddress(wlan_library, "WlanOpenHandle"));
WlanEnumInterfaces_function_ = reinterpret_cast<WlanEnumInterfacesFunction>(
GetProcAddress(wlan_library, "WlanEnumInterfaces"));
WlanGetNetworkBssList_function_ =
reinterpret_cast<WlanGetNetworkBssListFunction>(
GetProcAddress(wlan_library, "WlanGetNetworkBssList"));
WlanFreeMemory_function_ = reinterpret_cast<WlanFreeMemoryFunction>(
GetProcAddress(wlan_library, "WlanFreeMemory"));
WlanCloseHandle_function_ = reinterpret_cast<WlanCloseHandleFunction>(
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<int>(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<int>(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<base::string16>* interface_service_names)
: oid_buffer_size_(kInitialBufferSize) {
DCHECK(!interface_service_names->empty());
interface_service_names_.swap(*interface_service_names);
}
WindowsNdisApi::~WindowsNdisApi() {}
std::unique_ptr<WindowsNdisApi> WindowsNdisApi::Create() {
std::vector<base::string16> interface_service_names;
if (GetInterfacesNDIS(&interface_service_names))
return base::MakeUnique<WindowsNdisApi>(&interface_service_names);
return nullptr;
}
bool WindowsNdisApi::GetAccessPointData(WifiData::AccessPointDataSet* data) {
DCHECK(data);
int interfaces_failed = 0;
int interfaces_succeeded = 0;
for (int i = 0; i < static_cast<int>(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<base::string16>* 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<LPBYTE>(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);
std::unique_ptr<BYTE, base::FreeDeleter> buffer(
static_cast<BYTE*>(malloc(oid_buffer_size_)));
if (!buffer)
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<DWORD>(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<NDIS_802_11_BSSID_LIST*>(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<const char*>(bss_entry.dot11Ssid.ucSSID),
static_cast<ULONG>(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\<device_name>.
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\<device_name>.
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\<device_name> at
// \\.\<device_name>.
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,
std::unique_ptr<BYTE, base::FreeDeleter>* buffer) {
DCHECK_GT(requested_size, 0);
DCHECK(buffer);
if (requested_size > kMaximumBufferSize) {
buffer->reset();
return false;
}
buffer->reset(
reinterpret_cast<BYTE*>(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;
std::unique_ptr<base::char16[]> 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 device