Июль 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).
Рис. 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).