实现一个 WiFi 扫描器玩玩~

去年夏天,我和妻子变卖了家产,带着我们的两只狗移居了夏威夷。这里有美丽的阳光、温暖的沙滩、凉爽的冲浪等你能想到的一切。我们同样遇到了一些意料之外的事:WiFi 问题。

不过,这不是夏威夷的问题,而是我们租住公寓的问题。我们住在一个单身公寓里,与房东的公寓仅一墙之隔。我们的租房协议中包含了免费的网络连接!好耶!只不过,它是由房东的公寓里的 WiFi 提供的,哇哦……

说实话,它的效果还不错……吧?好吧,我承认它不尽如人意,并且不知道是哪里的问题。路由器明明就在墙的另一边,但我们的信号就是很不稳定,经常会自动断开连接。在家的时候,我们的 WiFi 路由器的信号能够穿过层层墙壁和地板。事实上,它所覆盖的区域比我们居住的 600 平方英尺(大约 55 平方米)的公寓还要大。

在这种情况下,一个优秀的技术人员会怎么做呢?既然想知道为什么,当然是开始排查咯!

幸运的是,我们在搬家之前并没有变卖掉树莓派 Zero W。它是如此小巧便携! 我当然就把它一起带来了。我有一个机智的想法:通过树莓派和它内置的 WiFi 适配器,使用 Go 语言编写一个小程序来测量并显示从路由器收到的 WiFi 信号。我打算先简单快速地把它实现出来,以后再去考虑优化。真是麻烦!我现在只想知道这个 WiFi 是怎么回事!

谷歌搜索了一番后,我发现了一个比较有用的 Go 软件包 mdlayher/wifi,它专门用于 WiFi 相关操作,听起来很有希望!

获取 WiFi 接口的信息

mdlayher/wifimain.go
package main

import (
    "fmt"
    "github.com/mdlayher/wifi"
)

func main() {
    c, err := wifi.New()
    defer c.Close()

    if err != nil {
        panic(err)
    }

    interfaces, err := c.Interfaces()

    for _, x := range interfaces {
        fmt.Printf("%+v\n", x)
    }
}
mdlayher/wifimain*Clientcc.Interfaces()
%+v+*Interface

运行上面的代码后,我得到了机器上的 WiFi 接口列表:

&{Index:0 Name: HardwareAddr:5c:5f:67:f3:0a:a7 PHY:0 Device:3 Type:P2P device Frequency:0}
&{Index:3 Name:wlp2s0 HardwareAddr:5c:5f:67:f3:0a:a7 PHY:0 Device:1 Type:station Frequency:2412}
HardwareAddrPHY: 0PHY
TYPE: P2Pwpl2s0TYPE: StationwifiP2P
Station

利用接口获取基站信息

利用该信息,我可以修改遍历接口的代码来获取所需信息:

for _, x := range interfaces {
    if x.Type == wifi.InterfaceTypeStation {
        // c.StationInfo(x) returns a slice of all
        // the staton information about the interface
        info, err := c.StationInfo(x)
        if err != nil {
            fmt.Printf("Station err: %s\n", err)
        }
        for _, x := range info {
            fmt.Printf("%+v\n", x)
        }
    }
}
x.Typewifi.InterfaceTypeStationinterfaceType
c.StationInfo(x)StationInfo()x
*StationInfoStationInfo+%vStationInfo

运行上面的程序后,我得到了下面的输出:

&{HardwareAddr:70:5a:9e:71:2e:d4 Connected:17m10s Inactive:1.579s ReceivedBytes:2458563 TransmittedBytes:1295562 ReceivedPackets:6355 TransmittedPackets:6135 ReceiveBitrate:2000000 TransmitBitrate:43300000 Signal:-79 TransmitRetries:2306 TransmitFailed:4 BeaconLoss:2}
SignalTransmitFailedBeaconLoss

简短科普:如何读懂 WiFi dBm

根据 MetaGeek 的说法:

  • -30 最佳,但它既不现实也没有必要
  • -67 非常好,它适用于需要可靠数据包传输的应用,例如流媒体
  • -70 还不错,它是实现可靠数据包传输的底线,适用于电子邮件和网页浏览
  • -80 很差,只是基本连接,数据包传输不可靠
  • -90 不可用,接近“背景噪声noise floor

注意:dBm 是对数尺度,-60 比 -30 要低 1000 倍。

使它成为一个真的“扫描器”

main
var i *wifi.Interface

for _, x := range interfaces {
    if x.Type == wifi.InterfaceTypeStation {
        // Loop through the interfaces, and assign the station
        // to var x
        // We could hardcode the station by name, or index,
        // or hardwareaddr, but this is more portable, if less efficient
        i = x
        break
    }
}

for {
    // c.StationInfo(x) returns a slice of all
    // the staton information about the interface
    info, err := c.StationInfo(i)
    if err != nil {
        fmt.Printf("Station err: %s\n", err)
    }

    for _, x := range info {
        fmt.Printf("Signal: %d\n", x.Signal)
    }

    time.Sleep(time.Second)
}
wifi.Interfacei
c.Interfaces()Stationi
Ctrl + C

运行上面的程序后,我得到了下面的输出:

[chris@marvin wifi-monitor]$ go run main.go
Signal: -81
Signal: -81
Signal: -79
Signal: -81

哇哦,感觉不妙。

绘制公寓信号分布图

不管怎么说,知道这些信息总比不知道要好。让树莓派连接上显示器或者电子墨水屏,并接上电源,我就可以让它在公寓里移动,并绘制出信号死角的位置。

剧透一下:由于房东的接入点在隔壁的公寓里,对我来说最大的死角是以公寓厨房的冰箱为顶点的一个圆锥体形状区域......这个冰箱与房东的公寓靠着一堵墙!

我想如果用《龙与地下城》里的黑话来说,它就是一个“沉默之锥Cone of Silence”。或者至少是一个“糟糕的网络连接之锥Cone of Poor Internet”。

go build -o wifi_scannerwifi_scanner

祝你扫描愉快!希望你的 WiFi 路由器不在你的冰箱后面!你可以在 我的 GitHub 存储库 中找到这个项目所用的代码。


本文由 LCTT 原创编译,Linux中国 荣誉推出