After a few years of development, I released a lightweight Wi-Fi scanner for the Windows1 command-line interface.

It provides some information about nearby networks that built-in tools do not provide. This includes RSSI, dissection of some information elements, and more.

Sharing Knowledge Is Important

Many have graciously passed on knowledge to me. This is the primary reason why I decided to share this project with others and make the source code available. You can find the code on my personal GitHub joshschmelzle/lswifi account.

Naming Things Is Hard

I called it lswifi because all the other clever names on Python Package Index (PyPI) were already taken. This was probably the only thing I regret. Not properly reserving the names that I wanted when I discovered they were available. I even rented a domain for a year which expired before I released this project. Oops.

How to Dissect 802.11 Information Elements

This project has taught me a lot about different information elements found in 802.11 beacon and probe response frames across different vendors.

From the vendor attributes containing AP names, to the RSNE containing security information, to WMM and EDCA values.

Parsing out these information elements with code was an incredibly valuable learning experience.

Python Packaging

I learned how to package scripts into sharable tools. This was absolutely required to get beyond code that only runs on my machine.

I started this project back when Ken Reitz was the primary maintainer of requests and adapted my packaging approach based on his. Requests is now maintained by the Python Software Foundation (PSF).

I uploaded lswifi to PyPI. This means you can download it with pip install lswifi.

The console scripts entry point is named lswifi. On install a command-line wrapper is created around the entry point. By default when you run lswifi it will respond with a list of nearby networks.

C:\Users\josh>where.exe lswifi
C:\Users\josh\AppData\Local\Programs\Python\Python38\Scripts\lswifi.exe

After install, you should be able to type lswifi and hit enter after installing. If that doesn’t work. Likely the Python scripts directory is not in the PATH environment variable.

How to Decide What to Support

lswifi is built with Python and requires Python 3.7 or higher. I want to be able to use some of the features in 3.7 like dataclasses and improvements to stdlib packages like asyncio.

I will only support Windows 10 even if it might work on older versions of Windows. I no longer have access to anything running an older version of Windows. Nor do I wish to.

Now and in the foreseeable future, lswifi is a CLI tool only. I do not intend to create a GUI for it. But I may add a background service that provides notifications on certain events like connection and roaming events.

I get to pick and choose what to support because my time is valuable. Every minute I spend working on lswifi is a minute taken away from spending time with my family. I am also not compensated for this work.

Improved Knowledge of a Programming Language

I picked Python. And unfortunately this required creating a wrapper around Native Wifi wlanapi.h. This is because the Native Wifi API is designed for C/C++ developers. This project has taught me a lot about Python. I currently have no third party dependencies on anything outside of the standard library.

ctypes Required

To interface with the Native Wifi library using Python, you must write wrapper code around the wlanapi.h.

This is painful and not for the faint hearted. Along with reading the official ctypes documentation, I did a lot of googling to figure out how to write wrapper code. The folks I pinged about it sent me towards articles which I had already looked at.

I did find some similar Python wrapper libraries around wlanapi.h, but they had problems. I ended up re-writing anything I found to adapt it to my project.

I wouldn’t have been able to do this without Microsoft’s Native Wifi documentation. Helge Magnus Keck was also kind to share bits and pieces of knowledge with me. Thank you Helge.

Limitations of the Native Wifi API

When you ask for scan results from the Native Wifi wlanapi.h, you have no control over certain things.

No control over what channels get scanned. That is up to the driver, per scan request. I often see mine skip DFS channels where I know APs are. The best solution I have come up is to run lswifi a few more times to get more scan results.

We have no control over how long the dwell time is on a certain channel. The wlanapi expects the driver to complete and return scan results with-in 4 seconds, but sometimes it will respond faster. The approach we use is a callback to detect when the results available. Once available, we ask for the results and get a list of networks and their fixed and tagged parameters (information elements or IEs).

Scan results can contain a beacons, probe responses, or both of those merged for a particular network. This means:

  • Some results may have IEs from only the probe response
  • Some results may have IEs from only the beacon
  • Some results provided back are from cached results
  • Some results may mix the IEs for the beacon and probe response for a particular network

This is why some results have AP names in them and some don’t. AP names are in beacons, not probe responses. So the application layer must cache those types of results in order to display them consistently in the output.

C:\Users\josh>lswifi --ap-names -include corp
display filter sensitivity -82; displaying 4 of 24 BSSIDs detected in scan results:
-==~+=~~=+++=-  -~~~=~=~=~+~+~~+-  -=~+++=+=++~====++-  -~==-  -~-  -=~~+~-  --  -=~++===~=~+-  -===~+++==~-
         ESSID  BSSID              AP NAME              RSSI   PHY  CHANNEL  SS  AMENDMENTS     AP UPTIME
[Network Name]                                          [dBm]  .11  [#@MHz]  #   [802.11]       [approx.]
-+~~=~~=~=~++-  -++~~=~=~++=+~+=-  -~~+~~+~~=++=~+~=+-  -++=-  -~-  -~=~=~-  --  -~+~~=====+~-  -++~++++~~~-
     corp-fast  b4:5d:50:1a:d3:91  Josh-205H            -73    ac    52@20   2   d/e/h/i/k/r/v  00d 1:51:58
     corp-data  b4:5d:50:1a:d3:90  Josh-205H            -75    ac    52@20   2   d/e/h/i/k/v    00d 1:51:58
     corp-fast  a8:bd:27:33:44:f1                       -77    ac    64@40-  4   d/e/h/i/k/r/v  03d 15:48:23
     corp-data  a8:bd:27:33:44:f0  Josh-AP324           -77    ac    64@40-  4   d/e/h/i/v      03d 15:48:23

We also have no idea if RSSI values are averaged across multiple frames. This could be problematic depending on what you’re trying to do with the responses. This means we may not want to use this sort of data for a passive survey. Michael Berg (TamoSoft) and Mikko Lauronen (Ekahau) explained on a recent Twitter thread that when doing a passive survey, the most accurate way to get survey data is to do a packet capture gathering beacons without keeping the packets. Most professional survey tools take an active packet capture during a passive survey and do not save the packets.

With that said, responses from the Native Wifi API still have purpose. This gives us insight into what networks are seen from and available from the clients perspective. We can do a simple discovery scan for nearby networks and display the results. That is what lswifi does.

Pick the right tool for the job at hand.

An IDE May Be Needed as Your Codebase Grows

When the codebase gets large, it is worth considering using an Integrated Development Environment (IDE). I did much of the development using PyCharm. This is not required though. VS Code is a good choice for those looking to start something today. Sublime Text 3 is also another strong choice. Real Python has a great article on ST3 for full stack Python development. If you do any Python work on SBCs, it is critical to learn Vim. If you absolutely do not want to learn Vim, you can now do remote development using VS Code. I have used each of the above to write Python code.

The Path Is Not Straight

It took me many hours of trial and error. The path was not straight forward. I’ve asked many people questions to help develop certain things. I’ve had many personal ups and downs during development. I am very grateful for those who have shared their positivity with me.

Perfection Is a Blocker

There are many things about lswifi that need improved. Let’s be honest: all code is garbage. There is always a better way to do what I am doing, but I can’t let perfection stop me. Perfection is the enemy of done. With that said, there is much work left to do on it.

I Built lswifi for Me

I built lswifi for me. I hope if others try it, they find it useful.

Got questions? Reach out. josh at joshschmelzle dot com, @joshschmelzle or @wizardfi on Twitter. I appreciate comments and feedback!

I may come back and update this post from time to time.

metal

Changelog

  • 2020/10/04: added a few more sections, formatting, clarifications, and fixed some minor spelling errors

  1. Microsoft Windows for those who subtweet.