はじめに
ネットワークの現場で Wi-Fi の調査をしていると、こんなことを知りたくなります。
- 周りの AP は Wi-Fi 6 なのか 6E なのか 7 なのか
- 80 MHz 幅で動いているのか、160 MHz 幅なのか
- 海外仕様の機器が、日本では使えないチャネルで電波を出していないか
Windows 標準の機能や netsh wlan show networks mode=bssid でも SSID・チャネル・強さくらいは分かりますが、チャネル幅や Wi-Fi 6E / 7 の判定、電波法との照らし合わせまではしてくれません。
そこで、Windows の無線 LAN API でビーコンの生データを取り、自分で解析するデスクトップアプリ「Doku WiFi Survey」を作りました。この記事では、その仕組みを紹介します。
全体の流れ
wlanapi.dll でスキャン
↓ WlanGetNetworkBssList
BSS ごとのビーコンの生データ(IE)
↓ IE を解析
世代(Wi-Fi 4〜7)・チャネル幅・占有している周波数
↓ 地域の規定と照らし合わせ
電波法のチェック結果
C#(.NET / WPF)で作っていますが、考え方は言語に関係なく使えます。
1. ビーコンの生データを取る
Windows では Native Wifi API(wlanapi.dll)を使います。ポイントは WlanGetNetworkBssList です。SSID 単位の WlanGetAvailableNetworkList と違い、BSS(AP の電波1つ)単位で、ビーコンの IE(Information Element)の生データまで返してくれます。
返ってくる WLAN_BSS_ENTRY の中で、IE は「エントリの先頭から ulIeOffset バイト目に ulIeSize バイト」という形で入っています。
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct WLAN_BSS_ENTRY
{
public DOT11_SSID dot11Ssid;
public uint uPhyId;
public fixed byte dot11Bssid[6];
public DOT11_BSS_TYPE dot11BssType;
public int dot11BssPhyType;
public int lRssi; // dBm
public uint uLinkQuality;
public byte bInRegDomain;
public ushort usBeaconPeriod;
public ulong ullTimestamp;
public ulong ullHostTimestamp; // ビーコンを受信した時刻(FILETIME)
public ushort usCapabilityInformation;
public uint ulChCenterFrequency; // kHz
public WLAN_RATE_SET wlanRateSet;
public uint ulIeOffset; // エントリ先頭からのオフセット
public uint ulIeSize;
}
読み出し部分はこんな感じです。リスト全体のサイズを超えていないか確認してから読むのが安全です。
var totalSize = *(uint*)ptr; // WLAN_BSS_LIST.dwTotalSize
var count = (int)*((uint*)ptr + 1); // WLAN_BSS_LIST.dwNumberOfItems
var first = (byte*)ptr + 8;
for (var i = 0; i < count; i++)
{
var entryPtr = first + (i * sizeof(WLAN_BSS_ENTRY));
var e = (WLAN_BSS_ENTRY*)entryPtr;
var ieStart = (long)(entryPtr - (byte*)ptr) + e->ulIeOffset;
if (e->ulIeSize > 0 && ieStart + e->ulIeSize <= totalSize)
{
var ie = new ReadOnlySpan<byte>(entryPtr + e->ulIeOffset, (int)e->ulIeSize).ToArray();
// → IE の解析へ
}
}
ハマりどころ:OS のキャッシュ
WlanGetNetworkBssList が返すのはドライバがキャッシュしている一覧なので、もう電波が届いていない AP も、しばらく古い情報のまま返ってきます。
そこで ullHostTimestamp(NIC が実際にビーコンを受信した時刻)を見て、前回と同じ時刻なら「新しく受信していない」と判断するようにしました。これで、電源を切った AP がいつまでも「見えている」扱いになる問題を防げます。
ハマりどころ:位置情報の許可
Windows 11 では、Wi-Fi のスキャン結果を取るアプリに位置情報の許可が必要です。許可がないと ERROR_ACCESS_DENIED になるので、起動時に確認して、未許可なら「設定を開く」案内を出しています。
2. IE から世代とチャネル幅を判定する
IE は「ID(1バイト)・長さ(1バイト)・本体」の TLV がずらっと並んだものです。Wi-Fi の世代やチャネル幅は、どの IE が入っているかとその中身で分かります。
| 世代 | 判定に使う IE |
|---|---|
| Wi-Fi 4(11n) | HT Capabilities / HT Operation |
| Wi-Fi 5(11ac) | VHT Capabilities / VHT Operation |
| Wi-Fi 6 / 6E(11ax) | HE Capabilities / HE Operation(Extension ID) |
| Wi-Fi 7(11be) | EHT Capabilities / EHT Operation(Extension ID) |
新しい規格の IE が1つでもあればその世代、というシンプルな判定です。6 GHz 帯で HE なら Wi-Fi 6E にします。
var generation = phy switch
{
_ when phy.HasFlag(PhyFlags.Eht) => WifiGeneration.WiFi7,
_ when phy.HasFlag(PhyFlags.He) => band == Band.Ghz6 ? WifiGeneration.WiFi6E : WifiGeneration.WiFi6,
_ when phy.HasFlag(PhyFlags.Vht) => WifiGeneration.WiFi5,
_ when phy.HasFlag(PhyFlags.Ht) => WifiGeneration.WiFi4,
_ when phy != PhyFlags.None => WifiGeneration.Legacy,
_ => WifiGeneration.Unknown,
};
OS が返す PHY 種別は当てにならない
WLAN_BSS_ENTRY にも dot11BssPhyType がありますが、実機で試すと 2.4 GHz の AP が VHT(11ac)と返ってくることがありました。11ac は 5 GHz の規格です。
調べると、一部のメーカーが 2.4 GHz で 256-QAM を使うために VHT の IE を独自に載せているためでした。そのため世代は OS の値を使わず IE から判定し、2.4 GHz の VHT の IE は無視しています。
チャネル幅は優先順位をつけて決める
チャネル幅は、新しい規格の IE から順に見ていきます。
- EHT Operation(Wi-Fi 7。320 MHz もここ)
- HE Operation の 6 GHz Operation Information(6 GHz 帯)
- VHT Operation(5 GHz の 80 / 160 / 80+80 MHz)
- HT Operation(40 MHz。セカンダリが上か下か)
- どれもなければ 20 MHz
VHT Operation で 160 MHz を表す方法は、古い書き方と新しい書き方の2通りあるのが面倒なところです。新しい書き方では「チャネル幅=80 MHz」と書いたうえで、2つの中心チャネル(CCFS0 / CCFS1)の差で 160 MHz か 80+80 MHz かを判断します。
// CCFS1 が CCFS0 から 8 離れていれば 160 MHz(中心は CCFS1)、
// 16 より離れていれば 80+80 MHz
var diff = Math.Abs(ccfs1 - ccfs0);
return diff == 8 ? (160, ccfs1, null)
: diff > 16 ? (160, ccfs0, ccfs1)
: (80, ccfs0, null);
幅と中心チャネルが分かれば、その AP が実際に使っている周波数の範囲(例:5170〜5250 MHz)を計算できます。これが次の電波法チェックの材料になります。
3. 日本の電波法と照らし合わせる
日本の 5 GHz 帯の無線 LAN は、ざっくり次のように決まっています。
| 帯域 | 範囲 | 屋外 | DFS |
|---|---|---|---|
| W52(ch36〜48) | 5150〜5250 MHz | 条件付き | なし |
| W53(ch52〜64) | 5250〜5350 MHz | 不可(屋内のみ) | あり |
| W56(ch100〜144) | 5470〜5730 MHz | 可 | あり |
2.4 GHz の ch14 は 11b(DSSS)専用、といった決まりもあります。これを JSON のデータにしてアプリに埋め込み、手順2で計算した「使っている周波数の範囲」と突き合わせています。
チェックしている内容の例です。
- 使えないチャネル:使っている範囲が、日本で使える帯域に収まっていない(海外仕様の機器でありがち)
- 屋内限定の帯域で屋外を申告:ビーコンの Country 要素で「屋外(O)」と申告しているのに W53 を使っている
- ch14 を 11g 以降で使用:ch14 なのに OFDM のレートを持っている
- 国コードの不一致:AP の国設定が JP 以外になっている
Country 要素には国コードの後ろに「屋内(I)/屋外(O)/両方(空白)」を表す1バイトがあり、AP 自身が「どこに設置されているか」を申告しています。実機で見ると、日本の家庭用ルーターは 4A 50 20("JP" + 空白)が多かったです。iPhone のテザリングは 4A 50 04 という規格の表にない値を返してきたので、知らない値は「不明」として扱うようにしています。
なお、これはあくまでビーコンに書かれた申告値と周波数からの推定です。アプリでも「違反と断定するものではありません」と表示しています。
作ったもの
ここまでの仕組みを、一覧とグラフで見られる Windows アプリにまとめました。
- AP の一覧(SSID、BSSID、ベンダー、RSSI、チャネル、幅、世代、最大レート、セキュリティ など)
- 2.4 / 5 / 6 GHz のチャネルグラフ(背景に W52 / W53 / W56 や DFS 帯を色分け表示)
- 電波強度の時系列グラフ
- 規制チェック(日本・米国・EU)
- 測定の記録と再生、CSV 出力
- ビーコンの IE をすべて表示する詳細ペイン
- 日本語 / 英語、ライト / ダークテーマ、x64 / ARM64 対応
現在は無料のβ版として公開しています。Windows 10 / 11 で動きます。
不具合や「こんな機能がほしい」といった声があれば、GitHub の Issues かメール(wifisurvey@dokurock.info)で気軽に教えてください。
おわりに
ビーコンの IE を自分で読んでみると、AP が想像以上にいろいろな情報を発信していることが分かって面白いです。Wi-Fi 7 の MLO(マルチリンク)や 320 MHz 幅の AP はまだ周りで見かけないので、見つけたらぜひ教えてください。
