Июль 2016

Том 31, номер 7

Современные приложения - Создаем сканер Wi-Fi на платформе UWP

Фрэнк Ла-Вине | Июль 2016

Исходный код можно скачать по ссылке

За последнее десятилетие или около того Wi-Fi распространился повсеместно. Многие магазины и кафе предлагают клиентам бесплатный Wi-Fi для их удобства. Практически все отели предлагают своим гостям какой-то вид беспроводного Интернета. У большинства из нас дома есть беспроводные сети. Поскольку лишь у некоторых планшетов и мобильных устройств есть разъемы Ethernet, Wi-Fi стал неотъемлемой частью нашей жизни. Кроме того, мы редко задумываемся о нем.

Возникает уйма вопросов. Какова зона покрытия Wi-Fi-сетей вокруг нас? Сколько их? Безопасны ли они? На каком канале они работают? Как они называются? Можно ли засекать их и наносить на карту? Что можно узнать из метаданных Wi-Fi-сети?

Недавно выгуливая своих собак, я случайно взглянул на экран сетевых соединений Wi-Fi на своем телефоне и заметил некоторые весьма остроумные названия сетей. Мне стало интересно, сколько еще людей предпочло комичные названия практичным. Потом у меня появилась идея просканировать и нанести на карту беспроводные сети в моем районе. Если бы автоматизировать бы этот процесс, я мог бы сканировать и наносить на карту беспроводные сети за время поездки из пригорода в город. В идеале, я мог бы написать программу, выполняемую на Raspberry Pi, которая периодически сканировала бы беспроводные сети и записывала бы эти данные в какой-то веб-сервис. Это определенно было бы практичнее, чем периодически пялиться в свой телефон.

Оказывается, что Universal Windows Platform (UWP) обеспечивает богатые возможности доступа к данным беспроводных сетей через классы в пространстве имен Windows.Devices.WiFi. Как вы знаете, UWP-приложение может работать не только на телефоне или ПК, но и на Raspberry Pi 2 под управлением Windows 10 IoT Core. Теперь у меня было все, что нужно для разработки моего проекта.

В этой статье я исследую основы сканирования Wi-Fi-сетей с помощью API, встроенных непосредственно в UWP.

Пространство имен Windows.Devices.WiFi

Классы в пространстве имен Windows.Devices.WiFi содержат все необходимое для сканирования и исследования беспроводных сетей и их адаптеров в пределах досягаемости (within range). После создания нового UWP-проекта в Visual Studio добавьте новый класс с именем WifiScanner и включите следующее свойство:

public WiFiAdapter WiFiAdapter { get; private set; }

Поскольку в конкретной системе может быть несколько адаптеров Wi-Fi, вы должны выбрать тот адаптер, который хотите использовать. Метод InitializeFirstAdapter получает первый из них, перечисленный в системе, как показано на рис. 1.

Рис. 1. Нахождение первого адаптера Wi-Fi, подключенного к системе, и его инициализация

private async Task InitializeFirstAdapter()
{
  var access = await WiFiAdapter.RequestAccessAsync();

  if (access != WiFiAccessStatus.Allowed)
  {
    throw new Exception("WiFiAccessStatus not allowed");
  }
  else
  {
    var wifiAdapterResults = await DeviceInformation.
      FindAllAsync(WiFiAdapter.GetDeviceSelector());

  if (wifiAdapterResults.Count >= 1)
    {
      this.WiFiAdapter = await WiFiAdapter.FromIdAsync(
        wifiAdapterResults[0].Id);
    }
    else
    {
      throw new Exception("WiFi Adapter not found.");
    }
  }
}

Добавление аппаратной возможности Wi-Fi

Вероятно, вы заметили, что в примере есть проверка доступа к Wi-Fi и что этот код генерирует исключение, если метод RequestAccessAsync возвращает false. Дело в том, что приложению нужна аппаратная возможность (device capability), которая позволит ему сканировать Wi-Fi-сети и подключаться к ним. Эта возможность не перечисляется на вкладке Capabilities в редакторе свойств манифеста. Чтобы добавить эту возможность, щелкните правой кнопкой мыши файл Package.appxmanager и выберите View Code.

Вы увидите исходный XML в файле Package.appxmanager. В узел Capabilities добавьте следующий код:

<DeviceCapability Name="wifiControl" />

Сохраните файл. Теперь у вашего приложения есть разрешение на доступ к Wi-Fi API.

Исследование беспроводных сетей

При наличии кода для идентификации адаптера Wi-Fi, с которым вы будете работать, и разрешения на доступ к нему следующий шаг — собственно сканирование сетей. К счастью, соответствующий код весьма прост; это просто вызов метода ScanAsync объекта WiFiAdapter. Добавьте следующий метод в класс WiFiScanner:

public async Task ScanForNetworks()
{
  if (this.WiFiAdapter != null)
  {
    await this.WiFiAdapter.ScanAsync();
  }
  }

После запуска ScanAsync заполняется свойство NetworkReport объекта WiFiAdapter. NetworkReport — это экземпляр WiFiNetworkReport, содержащий AvailableNetworks — List<WiFiAvailableNetwork>. Объект WiFiAvailableNework содержит множество данных о конкретной сети. Помимо других данных, вы можете найти Service Set Identifier (SSID), мощность сигнала (signal strength), метод шифрования и время бесперебойной работы точки доступа (access point uptime) — и все это без подключения к этой сети.

Перебор доступных сетей довольно прост: вы создаете POCO-объект (Plain Old CLR Object), содержащий некоторые данные из объектов WiFiAvailableNetwork:

foreach (var availableNetwork in report.AvailableNetworks)
{
  WiFiSignal wifiSignal = new WiFiSignal()
  {
    MacAddress = availableNetwork.Bssid,
    Ssid = availableNetwork.Ssid,
    SignalBars = availableNetwork.SignalBars,
    ChannelCenterFrequencyInKilohertz =
      availableNetwork.ChannelCenterFrequencyInKilohertz,
    NetworkKind = availableNetwork.NetworkKind.ToString(),
    PhysicalKind = availableNetwork.PhyKind.ToString()
  };
}

Создание UI

Хотя в конечном проекте я намерен сделать так, чтобы приложение выполнялось без UI, он полезен при разработке и анализе проблем, чтобы видеть сети в пределах досягаемости и метаданные, связанные с ними. Он также полезен для разработчиков, не имеющих в данный момент Raspberry Pi, но все равно желающих следовать за мной. Как показано на рис. 2, XAML для проекта весьма прост, и для хранения результатов сканирования используется многострочный TextBox.

Рис. 2. XAML для UI

<Grid Background="{ThemeResource
  ApplicationPageBackgroundThemeBrush}">
  <Grid.RowDefinitions>
    <RowDefinition Height="60"/>
      <RowDefinition Height="60"/>
      <RowDefinition Height="*"/>
  </Grid.RowDefinitions>
  <TextBlock FontSize="36" Grid.RowSpan="2">
    WiFi Scanner</TextBlock>
  <StackPanel Name="spButtons" Grid.Row="1"
    Orientation="Horizontal">
    <Button Name="btnScan" Click="btnScan_Click" Grid.Row="1">
      Scan For Networks</Button>
  </StackPanel>
  <TextBox Name="txbReport" TextWrapping="Wrap"
    AcceptsReturn="True" Grid.Row="2"></TextBox>
</Grid>
</Page>

Захват данных о местоположении

Чтобы программа была полезнее, в каждом скане беспроводной сети следует отмечать местоположение скана. Впоследствии это позволит предоставить интересную аналитическую картину и визуализацию данных. К счастью, добавить определение местоположения в UWP-приложения очень просто. Однако вам потребуется включить в свое приложение возможность Location. Для этого дважды щелкните файл Package.appxmanifest в Solution Explorer, щелкните вкладку Capabilities и установите флажок Location в списке Capabilities.

Следующий код будет получать местоположение, используя API, встроенные в UWP:

Geolocator geolocator = new Geolocator();
Geoposition position = await geolocator.GetGeopositionAsync();

Теперь, когда у вас есть данные о местоположении, вы захотите сохранить их. Следующий на очереди класс WiFiPointData, который хранит данные о местоположении, а также информацию о сетях, найденных в этом местоположении:

public class WiFiPointData
{
  public DateTimeOffset TimeStamp { get; set; }
  public double Latitude { get; set; }
  public double Longitude { get; set; }
  public double Accuracy { get; set; }
  public List<WiFiSignal> WiFiSignals { get; set; }
  public WiFiPointData()
  {
    this.WiFiSignals = new List<WiFiSignal>();
  }
}

К этому моменту важно понимать, что, если у вашего устройства нет GPS, приложению потребуется Wi-Fi-соединение с Интернетом, чтобы определить местоположение. Без встроенного GPS-устройства вам понадобится мобильный хот-спот (mobile hotspot) и подключение к нему вашего лэптопа или Raspberry Pi 2. Это также означает, что сообщаемое местоположение будет менее точным. Подробнее о рекомендациях по созданию UWP-приложения с поддержкой определения местоположения см. статью «Guidelines for Location-Aware Apps» на сайте Windows Dev Center по ссылке bit.ly/1P0St0C.

Периодическое сканирование

Для сценария со сканированием и определением местоположений в процессе поездки приложению нужно периодически сканировать Wi-Fi-сети. Для этого следует использовать DispatchTimer, чтобы сканировать Wi-Fi-сети через регулярные интервалы. Если вы не знакомы с тем, как работает DispatchTimer, см. документацию по ссылке bit.ly/1WPMFcp.

Заметьте, что сканирование Wi-Fi может занимать до нескольких секунд в зависимости от вашей системы. Следующий код устанавливает DispatchTimer на генерацию события через каждые 10 секунд — это более чем достаточно даже для самой медленной системы:

DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 10);
timer.Tick += Timer_Tick;
timer.Start();

Через каждые 10 секунд таймер выполняет код в методе Timer_Tick. Следующий код сканирует Wi-Fi-сети, а затем дописывает результаты в UI-элемент TextBox:

private async void Timer_Tick(object sender, object e)
{
  StringBuilder networkInfo = await RunWifiScan();
  this.txbReport.Text = this.txbReport.Text +
    networkInfo.ToString();
}

Отчет о результатах сканирования

Как уже упоминалось, после вызова метода ScanAsync результаты сохраняются в List<WiFiAvailableNetwork>. Чтобы добраться до этих результатов, достаточно пройти по списку. Код на рис. 3 делает именно это и помещает результаты в экземпляр класса WiFiPointData.

Рис. 3. Код для перебора всех сетей, найденных при сканировании

foreach (var availableNetwork in report.AvailableNetworks)
{
  WiFiSignal wifiSignal = new WiFiSignal()
  {
    MacAddress = availableNetwork.Bssid,
    Ssid = availableNetwork.Ssid,
    SignalBars = availableNetwork.SignalBars,
    NetworkKind = availableNetwork.NetworkKind.ToString(),
    PhysicalKind = availableNetwork.PhyKind.ToString(),
    Encryption = availableNetwork.SecuritySettings.
      NetworkEncryptionType.ToString()
  };

  wifiPoint.WiFiSignals.Add(wifiSignal);
}

Чтобы сделать UI простым и в то же время предоставить богатые средства анализа, вы можете преобразовать WiFiPointData в формат Comma Separated Value (CSV) и задать текст TextBox в UI. CSV — сравнительно простой формат, который можно импортировать в Excel и Power BI для анализа. Код для преобразования WiFiPointData представлен на рис. 4.

Рис. 4. Преобразование WiFiPointData

private StringBuilder CreateCsvReport(WiFiPointData wifiPoint)
{
  StringBuilder networkInfo = new StringBuilder();
  networkInfo.AppendLine(
    "MAC,SSID,SignalBars,Type,Lat,Long,Accuracy,Encryption");

  foreach (var wifiSignal in wifiPoint.WiFiSignals)
  {
    networkInfo.Append($"{wifiSignal.MacAddress},");
    networkInfo.Append($"{wifiSignal.Ssid},");
    networkInfo.Append($"{wifiSignal.SignalBars},");
    networkInfo.Append($"{wifiSignal.NetworkKind},");
    networkInfo.Append($"{wifiPoint.Latitude},");
    networkInfo.Append($"{wifiPoint.Longitude},");
    networkInfo.Append($"{wifiPoint.Accuracy},");
    networkInfo.Append($"{wifiSignal.Encryption}");
    networkInfo.AppendLine();
  }

  return networkInfo;
}

Визуализация данных

Естественно, я с нетерпением стремился подготовить облачный сервис для отображения и визуализации данных. Таким образом, я скопировал CSV-данные, сгенерированные приложением, и вставил их в текстовый файл. Затем сохранил этот файл с расширением .CSV. После этого я импортировал данные в Power BI Desktop. Power BI Desktop — это бесплатно скачиваемый пакет с powerbi.microsoft.com, упрощающий визуализацию и изучение данных.

Чтобы импортировать данные из приложения, щелкните Get Data на начальном экране Power Bi Desktop.

На следующем экране выберите CSV, а затем щелкните Connect. В окне выбора файла выберите CSV-файл со скопированными из приложения данными.

После его загрузки вы увидите список полей в правой части экрана. Хотя полное описание Power BI Desktop выходит за рамки этой статьи, эта программа не требует особых навыков для создания визуализации, показывающей местоположение Wi-Fi-сетей, их SSID-идентификаторы и протоколы шифрования (рис. 5).

Визуализация в Power BI данных, собранных приложением-сканером Wi-Fi
Рис. 5. Визуализация в Power BI данных, собранных приложением-сканером Wi-Fi

Как это ни удивительно, но примерно в трети сетей шифрование не используется. И если некоторые из них являются гостевыми сетями, установленными специально для общего доступа посетителей различных организаций, то остальные к ним не относятся.

Области применения на практике

Изначально я намеревался просто «замерить уровень» здравого смысла и остроумия своих соседей, но потом проект вылился в нечто более практичное. Возможность легко и автоматически сопоставлять мощность сигнала Wi-Fi с местоположением имеет интересные применения. Что мог бы получить город, если бы каждый городской автобус был оборудован IoT-устройством, выполняющим это приложение? В городах можно было бы замерить степень распространения Wi-Fi-сетей и соотнести эти данные с уровнями доходов по районам. Представители законодательных органов могли бы потом принимать обоснованные решения, отталкиваясь от этих данных. Если общество предоставляет открытую Wi-Fi-сеть в городе или в определенных его местах, можно было бы в реальном времени замерять мощность сигнала без дополнительных затрат на отправку соответствующих технических специалистов. В городах также могли бы определять, где преобладают незащищенные сети, и создавать целевые ознакомительные программы (targeted awareness programs) для повышения киберзащиты общества.

В малом масштабе возможность быстро просканировать метаданные Wi-Fi-сетей пригодится при настройке собственной сети. Многие маршрутизаторы (routers) позволяют пользователям менять широковещательный канал. Отличный пример этому — приложение Wi-Fi Analyzer (bit.ly/25ovZ0Q), которое, помимо прочего, отображает мощность и частоту соседних беспроводных сетей. Это удобно при создании Wi-Fi-сети в новом месте.

Заключение

Копирование текстовых данных из UI и их вставка в текстовый файл не особо удобна и не обеспечивает масштабирования. Более того, если ставится цель выполнять приложение на IoT-устройстве без визуального вывода, тогда это приложение должно посылать данные в облако безо всякого UI. В следующей статье вы узнаете, как подготовить облачный сервис для приема всех этих данных. Кроме того, вы увидите, как развернуть это решение на Raspberry Pi 2 под управлением Windows IoT Core.


Фрэнк Ла-Вине*(Frank La Vigne) — идеолог технологий в группе Microsoft Technology and Civic Engagement, где он помогает пользователям применять технологии и формировать соответствующие сообщества. Регулярно ведет блог на FranksWorld.com и имеет канал на YouTube под названием «Frank’s World TV» (youtube.com/FranksWorldTV).*

Выражаю благодарность за рецензирование статьи экспертам Рэчел Аппель (Rachel Appel), Роберту Бернштейну (Robert Bernstein) и Хосе Луису Маннерсу (Jose Luis Manners).