wifi_data_provider_win.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
1// Copyright (c) 2010 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Windows Vista uses the Native Wifi (WLAN) API for accessing WiFi cards. See
6// http://msdn.microsoft.com/en-us/library/ms705945(VS.85).aspx. Windows XP
7// Service Pack 3 (and Windows XP Service Pack 2, if upgraded with a hot fix)
8// also support a limited version of the WLAN API. See
9// http://msdn.microsoft.com/en-us/library/bb204766.aspx. The WLAN API uses
10// wlanapi.h, which is not part of the SDK used by Gears, so is replicated
11// locally using data from the MSDN.
12//
13// Windows XP from Service Pack 2 onwards supports the Wireless Zero
14// Configuration (WZC) programming interface. See
15// http://msdn.microsoft.com/en-us/library/ms706587(VS.85).aspx.
16//
17// The MSDN recommends that one use the WLAN API where available, and WZC
18// otherwise.
19//
20// However, it seems that WZC fails for some wireless cards. Also, WLAN seems
21// not to work on XP SP3. So we use WLAN on Vista, and use NDIS directly
22// otherwise.
23
24#include "content/browser/geolocation/wifi_data_provider_win.h"
25
26#include <windows.h>
27#include <winioctl.h>
28#include <wlanapi.h>
29
30#include "base/metrics/histogram.h"
31#include "base/utf_string_conversions.h"
32#include "base/win/windows_version.h"
33#include "content/browser/geolocation/wifi_data_provider_common.h"
34#include "content/browser/geolocation/wifi_data_provider_common_win.h"
35
36// Taken from ndis.h for WinCE.
37#define NDIS_STATUS_INVALID_LENGTH   ((NDIS_STATUS)0xC0010014L)
38#define NDIS_STATUS_BUFFER_TOO_SHORT ((NDIS_STATUS)0xC0010016L)
39
40namespace content {
41namespace {
42// The limits on the size of the buffer used for the OID query.
43const int kInitialBufferSize = 2 << 12;  // Good for about 50 APs.
44const int kMaximumBufferSize = 2 << 20;  // 2MB
45
46// Length for generic string buffers passed to Win32 APIs.
47const int kStringLength = 512;
48
49// The time periods, in milliseconds, between successive polls of the wifi data.
50const int kDefaultPollingInterval = 10000;  // 10s
51const int kNoChangePollingInterval = 120000;  // 2 mins
52const int kTwoNoChangePollingInterval = 600000;  // 10 mins
53const int kNoWifiPollingIntervalMilliseconds = 20 * 1000; // 20s
54
55// WlanOpenHandle
56typedef DWORD (WINAPI* WlanOpenHandleFunction)(DWORD dwClientVersion,
57                                               PVOID pReserved,
58                                               PDWORD pdwNegotiatedVersion,
59                                               PHANDLE phClientHandle);
60
61// WlanEnumInterfaces
62typedef DWORD (WINAPI* WlanEnumInterfacesFunction)(
63    HANDLE hClientHandle,
64    PVOID pReserved,
65    PWLAN_INTERFACE_INFO_LIST* ppInterfaceList);
66
67// WlanGetNetworkBssList
68typedef DWORD (WINAPI* WlanGetNetworkBssListFunction)(
69    HANDLE hClientHandle,
70    const GUID* pInterfaceGuid,
71    const  PDOT11_SSID pDot11Ssid,
72    DOT11_BSS_TYPE dot11BssType,
73    BOOL bSecurityEnabled,
74    PVOID pReserved,
75    PWLAN_BSS_LIST* ppWlanBssList
76);
77
78// WlanFreeMemory
79typedef VOID (WINAPI* WlanFreeMemoryFunction)(PVOID pMemory);
80
81// WlanCloseHandle
82typedef DWORD (WINAPI* WlanCloseHandleFunction)(HANDLE hClientHandle,
83                                                PVOID pReserved);
84
85
86// Local classes and functions
87class WindowsWlanApi : public WifiDataProviderCommon::WlanApiInterface {
88 public:
89  virtual ~WindowsWlanApi();
90  // Factory function. Will return NULL if this API is unavailable.
91  static WindowsWlanApi* Create();
92
93  // WlanApiInterface
94  virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data);
95
96 private:
97  // Takes ownership of the library handle.
98  explicit WindowsWlanApi(HINSTANCE library);
99
100  // Loads the required functions from the DLL.
101  void GetWLANFunctions(HINSTANCE wlan_library);
102  int GetInterfaceDataWLAN(HANDLE wlan_handle,
103                           const GUID& interface_id,
104                           WifiData::AccessPointDataSet* data);
105
106  // Logs number of detected wlan interfaces.
107  static void LogWlanInterfaceCount(int count);
108
109  // Handle to the wlanapi.dll library.
110  HINSTANCE library_;
111
112  // Function pointers for WLAN
113  WlanOpenHandleFunction WlanOpenHandle_function_;
114  WlanEnumInterfacesFunction WlanEnumInterfaces_function_;
115  WlanGetNetworkBssListFunction WlanGetNetworkBssList_function_;
116  WlanFreeMemoryFunction WlanFreeMemory_function_;
117  WlanCloseHandleFunction WlanCloseHandle_function_;
118};
119
120class WindowsNdisApi : public WifiDataProviderCommon::WlanApiInterface {
121 public:
122  virtual ~WindowsNdisApi();
123  static WindowsNdisApi* Create();
124
125  // WlanApiInterface
126  virtual bool GetAccessPointData(WifiData::AccessPointDataSet* data);
127
128 private:
129  static bool GetInterfacesNDIS(
130      std::vector<string16>* interface_service_names_out);
131
132  // Swaps in content of the vector passed
133  explicit WindowsNdisApi(std::vector<string16>* interface_service_names);
134
135  bool GetInterfaceDataNDIS(HANDLE adapter_handle,
136                            WifiData::AccessPointDataSet* data);
137  // NDIS variables.
138  std::vector<string16> interface_service_names_;
139
140  // Remembers scan result buffer size across calls.
141  int oid_buffer_size_;
142};
143
144// Extracts data for an access point and converts to Gears format.
145bool GetNetworkData(const WLAN_BSS_ENTRY& bss_entry,
146                    AccessPointData* access_point_data);
147bool UndefineDosDevice(const string16& device_name);
148bool DefineDosDeviceIfNotExists(const string16& device_name);
149HANDLE GetFileHandle(const string16& device_name);
150// Makes the OID query and returns a Win32 error code.
151int PerformQuery(HANDLE adapter_handle,
152                 BYTE* buffer,
153                 DWORD buffer_size,
154                 DWORD* bytes_out);
155bool ResizeBuffer(int requested_size, scoped_ptr_malloc<BYTE>* buffer);
156// Gets the system directory and appends a trailing slash if not already
157// present.
158bool GetSystemDirectory(string16* path);
159}  // namespace
160
161template<>
162WifiDataProviderImplBase* WifiDataProvider::DefaultFactoryFunction() {
163  return new Win32WifiDataProvider();
164}
165
166Win32WifiDataProvider::Win32WifiDataProvider() {
167}
168
169Win32WifiDataProvider::~Win32WifiDataProvider() {
170}
171
172WifiDataProviderCommon::WlanApiInterface* Win32WifiDataProvider::NewWlanApi() {
173  // Use the WLAN interface if we're on Vista and if it's available. Otherwise,
174  // use NDIS.
175  WlanApiInterface* api = WindowsWlanApi::Create();
176  if (api) {
177    return api;
178  }
179  return WindowsNdisApi::Create();
180}
181
182PollingPolicyInterface* Win32WifiDataProvider::NewPollingPolicy() {
183  return new GenericPollingPolicy<kDefaultPollingInterval,
184                                  kNoChangePollingInterval,
185                                  kTwoNoChangePollingInterval,
186                                  kNoWifiPollingIntervalMilliseconds>;
187}
188
189// Local classes and functions
190namespace {
191
192// WindowsWlanApi
193WindowsWlanApi::WindowsWlanApi(HINSTANCE library)
194    : library_(library) {
195  GetWLANFunctions(library_);
196}
197
198WindowsWlanApi::~WindowsWlanApi() {
199  FreeLibrary(library_);
200}
201
202WindowsWlanApi* WindowsWlanApi::Create() {
203  if (base::win::GetVersion() < base::win::VERSION_VISTA)
204    return NULL;
205  // We use an absolute path to load the DLL to avoid DLL preloading attacks.
206  string16 system_directory;
207  if (!GetSystemDirectory(&system_directory)) {
208    return NULL;
209  }
210  DCHECK(!system_directory.empty());
211  string16 dll_path = system_directory + L"wlanapi.dll";
212  HINSTANCE library = LoadLibraryEx(dll_path.c_str(),
213                                    NULL,
214                                    LOAD_WITH_ALTERED_SEARCH_PATH);
215  if (!library) {
216    return NULL;
217  }
218  return new WindowsWlanApi(library);
219}
220
221void WindowsWlanApi::GetWLANFunctions(HINSTANCE wlan_library) {
222  DCHECK(wlan_library);
223  WlanOpenHandle_function_ = reinterpret_cast<WlanOpenHandleFunction>(
224      GetProcAddress(wlan_library, "WlanOpenHandle"));
225  WlanEnumInterfaces_function_ = reinterpret_cast<WlanEnumInterfacesFunction>(
226      GetProcAddress(wlan_library, "WlanEnumInterfaces"));
227  WlanGetNetworkBssList_function_ =
228      reinterpret_cast<WlanGetNetworkBssListFunction>(
229      GetProcAddress(wlan_library, "WlanGetNetworkBssList"));
230  WlanFreeMemory_function_ = reinterpret_cast<WlanFreeMemoryFunction>(
231      GetProcAddress(wlan_library, "WlanFreeMemory"));
232  WlanCloseHandle_function_ = reinterpret_cast<WlanCloseHandleFunction>(
233      GetProcAddress(wlan_library, "WlanCloseHandle"));
234  DCHECK(WlanOpenHandle_function_ &&
235         WlanEnumInterfaces_function_ &&
236         WlanGetNetworkBssList_function_ &&
237         WlanFreeMemory_function_ &&
238         WlanCloseHandle_function_);
239}
240
241void WindowsWlanApi::LogWlanInterfaceCount(int count) {
242  UMA_HISTOGRAM_CUSTOM_COUNTS(
243      "Net.Wifi.InterfaceCount",
244      count,
245      1,
246      5,
247      5);
248}
249
250bool WindowsWlanApi::GetAccessPointData(
251    WifiData::AccessPointDataSet* data) {
252  DCHECK(data);
253
254  // Get the handle to the WLAN API.
255  DWORD negotiated_version;
256  HANDLE wlan_handle = NULL;
257  // We could be executing on either Windows XP or Windows Vista, so use the
258  // lower version of the client WLAN API. It seems that the negotiated version
259  // is the Vista version irrespective of what we pass!
260  static const int kXpWlanClientVersion = 1;
261  if ((*WlanOpenHandle_function_)(kXpWlanClientVersion,
262                                  NULL,
263                                  &negotiated_version,
264                                  &wlan_handle) != ERROR_SUCCESS) {
265    LogWlanInterfaceCount(0);
266    return false;
267  }
268  DCHECK(wlan_handle);
269
270  // Get the list of interfaces. WlanEnumInterfaces allocates interface_list.
271  WLAN_INTERFACE_INFO_LIST* interface_list = NULL;
272  if ((*WlanEnumInterfaces_function_)(wlan_handle, NULL, &interface_list) !=
273      ERROR_SUCCESS) {
274    LogWlanInterfaceCount(0);
275    return false;
276  }
277  DCHECK(interface_list);
278
279  LogWlanInterfaceCount(interface_list->dwNumberOfItems);
280
281  // Go through the list of interfaces and get the data for each.
282  for (int i = 0; i < static_cast<int>(interface_list->dwNumberOfItems); ++i) {
283    // Skip any interface that is midway through association; the
284    // WlanGetNetworkBssList function call is known to hang indefinitely
285    // when it's in this state. http://crbug.com/39300
286    if (interface_list->InterfaceInfo[i].isState ==
287        wlan_interface_state_associating) {
288      LOG(WARNING) << "Skipping wifi scan on adapter " << i << " ("
289                   << interface_list->InterfaceInfo[i].strInterfaceDescription
290                   << ") in 'associating' state. Repeated occurrences "
291                      "indicates a non-responding adapter.";
292      continue;
293    }
294    GetInterfaceDataWLAN(wlan_handle,
295                         interface_list->InterfaceInfo[i].InterfaceGuid,
296                         data);
297  }
298
299  // Free interface_list.
300  (*WlanFreeMemory_function_)(interface_list);
301
302  // Close the handle.
303  if ((*WlanCloseHandle_function_)(wlan_handle, NULL) != ERROR_SUCCESS) {
304    return false;
305  }
306
307  return true;
308}
309
310// Appends the data for a single interface to the data vector. Returns the
311// number of access points found, or -1 on error.
312int WindowsWlanApi::GetInterfaceDataWLAN(
313    const HANDLE wlan_handle,
314    const GUID& interface_id,
315    WifiData::AccessPointDataSet* data) {
316  DCHECK(data);
317
318  const base::TimeTicks start_time = base::TimeTicks::Now();
319
320  // WlanGetNetworkBssList allocates bss_list.
321  WLAN_BSS_LIST* bss_list = NULL;
322  if ((*WlanGetNetworkBssList_function_)(wlan_handle,
323                                         &interface_id,
324                                         NULL,   // Use all SSIDs.
325                                         dot11_BSS_type_any,
326                                         false,  // bSecurityEnabled - unused
327                                         NULL,   // reserved
328                                         &bss_list) != ERROR_SUCCESS) {
329    return -1;
330  }
331  // According to http://www.attnetclient.com/kb/questions.php?questionid=75
332  // WlanGetNetworkBssList can sometimes return success, but leave the bss
333  // list as NULL.
334  if (!bss_list)
335    return -1;
336
337  const base::TimeDelta duration = base::TimeTicks::Now() - start_time;
338
339  UMA_HISTOGRAM_CUSTOM_TIMES(
340      "Net.Wifi.ScanLatency",
341      duration,
342      base::TimeDelta::FromMilliseconds(1),
343      base::TimeDelta::FromMinutes(1),
344      100);
345
346
347  int found = 0;
348  for (int i = 0; i < static_cast<int>(bss_list->dwNumberOfItems); ++i) {
349    AccessPointData access_point_data;
350    if (GetNetworkData(bss_list->wlanBssEntries[i], &access_point_data)) {
351      ++found;
352      data->insert(access_point_data);
353    }
354  }
355
356  (*WlanFreeMemory_function_)(bss_list);
357
358  return found;
359}
360
361// WindowsNdisApi
362WindowsNdisApi::WindowsNdisApi(
363    std::vector<string16>* interface_service_names)
364    : oid_buffer_size_(kInitialBufferSize) {
365  DCHECK(!interface_service_names->empty());
366  interface_service_names_.swap(*interface_service_names);
367}
368
369WindowsNdisApi::~WindowsNdisApi() {
370}
371
372WindowsNdisApi* WindowsNdisApi::Create() {
373  std::vector<string16> interface_service_names;
374  if (GetInterfacesNDIS(&interface_service_names)) {
375    return new WindowsNdisApi(&interface_service_names);
376  }
377  return NULL;
378}
379
380bool WindowsNdisApi::GetAccessPointData(WifiData::AccessPointDataSet* data) {
381  DCHECK(data);
382  int interfaces_failed = 0;
383  int interfaces_succeeded = 0;
384
385  for (int i = 0; i < static_cast<int>(interface_service_names_.size()); ++i) {
386    // First, check that we have a DOS device for this adapter.
387    if (!DefineDosDeviceIfNotExists(interface_service_names_[i])) {
388      continue;
389    }
390
391    // Get the handle to the device. This will fail if the named device is not
392    // valid.
393    HANDLE adapter_handle = GetFileHandle(interface_service_names_[i]);
394    if (adapter_handle == INVALID_HANDLE_VALUE) {
395      continue;
396    }
397
398    // Get the data.
399    if (GetInterfaceDataNDIS(adapter_handle, data)) {
400      ++interfaces_succeeded;
401    } else {
402      ++interfaces_failed;
403    }
404
405    // Clean up.
406    CloseHandle(adapter_handle);
407    UndefineDosDevice(interface_service_names_[i]);
408  }
409
410  // Return true if at least one interface succeeded, or at the very least none
411  // failed.
412  return interfaces_succeeded > 0 || interfaces_failed == 0;
413}
414
415bool WindowsNdisApi::GetInterfacesNDIS(
416    std::vector<string16>* interface_service_names_out) {
417  HKEY network_cards_key = NULL;
418  if (RegOpenKeyEx(
419      HKEY_LOCAL_MACHINE,
420      L"Software\\Microsoft\\Windows NT\\CurrentVersion\\NetworkCards",
421      0,
422      KEY_READ,
423      &network_cards_key) != ERROR_SUCCESS) {
424    return false;
425  }
426  DCHECK(network_cards_key);
427
428  for (int i = 0; ; ++i) {
429    TCHAR name[kStringLength];
430    DWORD name_size = kStringLength;
431    FILETIME time;
432    if (RegEnumKeyEx(network_cards_key,
433                     i,
434                     name,
435                     &name_size,
436                     NULL,
437                     NULL,
438                     NULL,
439                     &time) != ERROR_SUCCESS) {
440      break;
441    }
442    HKEY hardware_key = NULL;
443    if (RegOpenKeyEx(network_cards_key, name, 0, KEY_READ, &hardware_key) !=
444        ERROR_SUCCESS) {
445      break;
446    }
447    DCHECK(hardware_key);
448
449    TCHAR service_name[kStringLength];
450    DWORD service_name_size = kStringLength;
451    DWORD type = 0;
452    if (RegQueryValueEx(hardware_key,
453                        L"ServiceName",
454                        NULL,
455                        &type,
456                        reinterpret_cast<LPBYTE>(service_name),
457                        &service_name_size) == ERROR_SUCCESS) {
458      interface_service_names_out->push_back(service_name);
459    }
460    RegCloseKey(hardware_key);
461  }
462
463  RegCloseKey(network_cards_key);
464  return true;
465}
466
467
468bool WindowsNdisApi::GetInterfaceDataNDIS(HANDLE adapter_handle,
469                                          WifiData::AccessPointDataSet* data) {
470  DCHECK(data);
471
472  scoped_ptr_malloc<BYTE> buffer(
473      reinterpret_cast<BYTE*>(malloc(oid_buffer_size_)));
474  if (buffer == NULL) {
475    return false;
476  }
477
478  DWORD bytes_out;
479  int result;
480
481  while (true) {
482    bytes_out = 0;
483    result = PerformQuery(adapter_handle, buffer.get(),
484                          oid_buffer_size_, &bytes_out);
485    if (result == ERROR_GEN_FAILURE ||  // Returned by some Intel cards.
486        result == ERROR_INSUFFICIENT_BUFFER ||
487        result == ERROR_MORE_DATA ||
488        result == NDIS_STATUS_INVALID_LENGTH ||
489        result == NDIS_STATUS_BUFFER_TOO_SHORT) {
490      // The buffer we supplied is too small, so increase it. bytes_out should
491      // provide the required buffer size, but this is not always the case.
492      if (bytes_out > static_cast<DWORD>(oid_buffer_size_)) {
493        oid_buffer_size_ = bytes_out;
494      } else {
495        oid_buffer_size_ *= 2;
496      }
497      if (!ResizeBuffer(oid_buffer_size_, &buffer)) {
498        oid_buffer_size_ = kInitialBufferSize;  // Reset for next time.
499        return false;
500      }
501    } else {
502      // The buffer is not too small.
503      break;
504    }
505  }
506  DCHECK(buffer.get());
507
508  if (result == ERROR_SUCCESS) {
509    NDIS_802_11_BSSID_LIST* bssid_list =
510        reinterpret_cast<NDIS_802_11_BSSID_LIST*>(buffer.get());
511    GetDataFromBssIdList(*bssid_list, oid_buffer_size_, data);
512  }
513
514  return true;
515}
516
517bool GetNetworkData(const WLAN_BSS_ENTRY& bss_entry,
518                    AccessPointData* access_point_data) {
519  // Currently we get only MAC address, signal strength and SSID.
520  DCHECK(access_point_data);
521  access_point_data->mac_address = MacAddressAsString16(bss_entry.dot11Bssid);
522  access_point_data->radio_signal_strength = bss_entry.lRssi;
523  // bss_entry.dot11Ssid.ucSSID is not null-terminated.
524  UTF8ToUTF16(reinterpret_cast<const char*>(bss_entry.dot11Ssid.ucSSID),
525              static_cast<ULONG>(bss_entry.dot11Ssid.uSSIDLength),
526              &access_point_data->ssid);
527  // TODO(steveblock): Is it possible to get the following?
528  // access_point_data->signal_to_noise
529  // access_point_data->age
530  // access_point_data->channel
531  return true;
532}
533
534bool UndefineDosDevice(const string16& device_name) {
535  // We remove only the mapping we use, that is \Device\<device_name>.
536  string16 target_path = L"\\Device\\" + device_name;
537  return DefineDosDevice(
538      DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE,
539      device_name.c_str(),
540      target_path.c_str()) == TRUE;
541}
542
543bool DefineDosDeviceIfNotExists(const string16& device_name) {
544  // We create a DOS device name for the device at \Device\<device_name>.
545  string16 target_path = L"\\Device\\" + device_name;
546
547  TCHAR target[kStringLength];
548  if (QueryDosDevice(device_name.c_str(), target, kStringLength) > 0 &&
549      target_path.compare(target) == 0) {
550    // Device already exists.
551    return true;
552  }
553
554  if (GetLastError() != ERROR_FILE_NOT_FOUND) {
555    return false;
556  }
557
558  if (!DefineDosDevice(DDD_RAW_TARGET_PATH,
559                       device_name.c_str(),
560                       target_path.c_str())) {
561    return false;
562  }
563
564  // Check that the device is really there.
565  return QueryDosDevice(device_name.c_str(), target, kStringLength) > 0 &&
566      target_path.compare(target) == 0;
567}
568
569HANDLE GetFileHandle(const string16& device_name) {
570  // We access a device with DOS path \Device\<device_name> at
571  // \\.\<device_name>.
572  string16 formatted_device_name = L"\\\\.\\" + device_name;
573
574  return CreateFile(formatted_device_name.c_str(),
575                    GENERIC_READ,
576                    FILE_SHARE_READ | FILE_SHARE_WRITE,  // share mode
577                    0,  // security attributes
578                    OPEN_EXISTING,
579                    0,  // flags and attributes
580                    INVALID_HANDLE_VALUE);
581}
582
583int PerformQuery(HANDLE adapter_handle,
584                 BYTE* buffer,
585                 DWORD buffer_size,
586                 DWORD* bytes_out) {
587  DWORD oid = OID_802_11_BSSID_LIST;
588  if (!DeviceIoControl(adapter_handle,
589                       IOCTL_NDIS_QUERY_GLOBAL_STATS,
590                       &oid,
591                       sizeof(oid),
592                       buffer,
593                       buffer_size,
594                       bytes_out,
595                       NULL)) {
596    return GetLastError();
597  }
598  return ERROR_SUCCESS;
599}
600
601bool ResizeBuffer(int requested_size, scoped_ptr_malloc<BYTE>* buffer) {
602  DCHECK_GT(requested_size, 0);
603  DCHECK(buffer);
604  if (requested_size > kMaximumBufferSize) {
605    buffer->reset();
606    return false;
607  }
608
609  buffer->reset(reinterpret_cast<BYTE*>(
610      realloc(buffer->release(), requested_size)));
611  return buffer != NULL;
612}
613
614bool GetSystemDirectory(string16* path) {
615  DCHECK(path);
616  // Return value includes terminating NULL.
617  int buffer_size = ::GetSystemDirectory(NULL, 0);
618  if (buffer_size == 0) {
619    return false;
620  }
621  scoped_ptr<char16[]> buffer(new char16[buffer_size]);
622
623  // Return value excludes terminating NULL.
624  int characters_written = ::GetSystemDirectory(buffer.get(), buffer_size);
625  if (characters_written == 0) {
626    return false;
627  }
628  DCHECK_EQ(buffer_size - 1, characters_written);
629
630  path->assign(buffer.get(), characters_written);
631
632  if (*path->rbegin() != L'\\') {
633    path->append(L"\\");
634  }
635  DCHECK_EQ(L'\\', *path->rbegin());
636  return true;
637}
638}  // namespace
639
640}  // namespace content
641