見えない電波の衝突を見きわめろ!Wi-Fi隠れ端末問題とRTS/CTSハンドシェイクの深層
こんにちは。シニアネットワークエンジニアの私です。
これまで数々のオフィスフロアやスマートホーム、IoTの現場で「なぜかこのデバイスだけパケットロスが多い」「目の前にあるアクセスポイント(AP)なのに、Web APIのレスポンスが妙に揺らぐ」といった不可解なトラトラブルに直面してきました。
その原因の多くは、電波の特性が生み出す物理的な死角、すなわち「隠れ端末問題(Hidden Node Problem)」にあります。
今回は、インフラ構築やエッジデバイスからのWeb API連携に挑むエンジニアに向けて、Wi-Fiの空中で何が起きているのか、そしてそれをどうやって力づくで調停しているのか。パケットの挙動から実際のチューニング、さらにはメトリクスの監視手法まで、現場の知見を総動員して解説します。
—
1. なぜ「隠れ端末問題」は起きるのか? —— 無線媒体の宿命
有線LAN(イーサネット)の世界であれば、スイッチングハブや全二重(Full-Duplex)通信の恩恵を受け、コリジョン(衝突)の恐怖は過去のものとなりました。しかし、Wi-Fiをはじめとする無線通信は、同じ周波数帯の空間をすべての端末で共有する「共有媒体(Shared Medium)」です。
ここで、物理的な障壁や距離の限界によって、互いの電波が届かない2台のクライアント端末(仮に Node_A と Node_B とします)が存在するとしましょう。両者はどちらも中央の AP(Access Point) とは通信できます。
[ Node_A ] --- (届かない) --- [ Node_B ]
\ /
\ /
(電波OK) (電波OK)
\ /
v v
[ Access Point (AP) ]
このとき、Node_A が AP に向けて巨大なJSONペイロードを含むHTTP POSTリクエスト(Web API送信)をブロードキャストに近いタイミングで投げ始めたとします。それと全く同じミリ秒に、Node_B も AP に向けてデータを送信しました。
Node_AにはNode_Bの電波が届いていないため、キャリアセンス(CSMA/CAのCA:衝突回避)が機能しません。「今、電波は静かだな」と判断して送信してしまいます。Node_Bも同様です。
結果として、AP のアンテナの直下で両方の電波が重なり合い、ビットの衝突(コリジョン)が発生します。AP はノイズまみれのフレームを受け取るだけでデコードできず、当然ACK(確認応答)も返しません。 Node_A と Node_B は「タイムアウトしたから再送しよう」と、再び同じ過ちを繰り返す……これが、現場を地獄に叩き落とす隠れ端末問題のメカニズムです。
—
2. RTS/CTSメカニズムの内部挙動 —— 衝突を未然に防ぐハンドシェイク
この物理的なすれ違いを防ぐための特効薬が、RTS/CTS(Request to Send / Clear to Send)メカニズムです。
データ本体という「重たい荷物」をいきなり送り出す前に、超軽量なハンドシェイクの予約切符を交わすことで、周囲の隠れ端末に「今からこのチャネルを占有するから静かにしてくれ」と宣言させます。
実際のパケット送受信フローをシーケンスで追ってみましょう。
Node_A AP Node_B
| | |
|--- RTS (送信要求) ->| |
| (我が輩はこれから | |
| データを送る!) | |
| |--- CTS (送信許可)->| (※Node_Bにも届く)
| | (Node_Aが送る | 「なるほど、しばらく
| | から待てよ) | おとなしくしていよう」
|<-- CTS (送信許可) -| |
| | |
|--- Data (実データ)->| |
| (HTTP POST等) | |
|<-- ACK (確認応答) -| |
| | |
1. RTS送信 (Node_A -> AP)
Node_A は実データを送る前に、宛先とデータ長を記載した RTS フレームを AP に投げます。
2. CTSブロードキャスト (AP -> 周辺全体)
RTS を受け取った AP は、周囲の全端末に向けて CTS フレームを返します。この CTS には「これから一定時間は Node_A にチャネルを譲る」という予約情報(NAV:Network Allocation Vector)が含まれています。
3. 隠れ端末の沈黙 (Node_B の挙動)
Node_A の電波は届かなかった Node_B ですが、AP から発せられた CTS は届きます。これを受信した Node_B は、NAVタイマーを作動させ、指定された期間中は送信をピタリと止めます。
4. 安全なデータ転送
衝突のリスクが排除された空中で、Node_A は安心して Data フレームを送り、AP から ACK を受け取ります。
—
3. 現場でのチューニング:RTS/CTS閾値(Threshold)の設定
「じゃあ、すべてのパケットでRTS/CTSをやれば完璧じゃないか!」と思われるかもしれませんが、世の中そんなに甘くありません。
RTS/CTSは、いわば「本番の会話の前に、わざわざ挙手して司会者の許可を取る」ようなものです。小さなパケット(例えば数バイトのIoTセンサーのステータス送信や、細切れのTCP ACKなど)に対して毎回RTS/CTSを挟んでいると、オーバーヘッド(制御信号の割合)が跳ね上がり、スループットが劇的に低下します。
そのため、ルーターやアクセスポイントの設定では、RTS Threshold(閾値)をバイト単位で調整します。
代表的なパラメーター設定方針
- デフォルト値(通常 2347 バイト)
実質的にRTS/CTSが無効化されている状態です。通常のオフィスや家庭など、比較的見通しが良い環境ではこれで十分です。
- 低めの設定(例: 512 または 256 バイト)
金属ラックが多い倉庫、コンクリート壁に囲まれた工場、多数のスマート家電が密集して電波干渉と隠れ端末が多発する環境では、この閾値を下げることで、中・大型のデータパケットの衝突を防ぎます。
OpenWrt等(LinuxベースAP)での設定例 (/etc/config/wireless)
インフラエンジニアがCLIから直接調整する場合、無線インターフェースの設定ファイルに以下のように記述します。
config wifi-device 'radio0'
option type 'mac80211'
option channel '36'
option hwmode '11a'
option path 'pci0000:00/0000:00:1c.0'
option htmode 'VHT80'
# RTS/CTSの閾値を512バイトに設定(これを超えるパケットでRTS/CTSを発動)
option rts_threshold '512'
—
4. デバッグと検証:パケットロスとスループットの観測
「隠れ端末が原因でWeb APIのレスポンスが劣化している」と疑うとき、勘や経験だけで設定を変更するのはプロの仕事ではありません。ちゃんとデータで裏付けを取りましょう。
現場で使えるデバッグ手法と、死活監視用のスニペットを紹介します。
① Wiresharkによるエアモニタリング(無線キャプチャ)
ノートPCの無線NICをモニターモード(Monitor Mode)に切り替え、空中の無線フレームを直接キャプチャします。
# Linux環境でのモニターモード有効化手順の例
sudo ip link set wlan0 down
sudo iw dev wlan0 set type monitor
sudo ip link set wlan0 up
# tcpdumpを用いて特定のBSSIDのRTS/CTSとデータ再送(Retry=1)をキャプチャ
sudo tcpdump -i wlan0 -nn -e "ether host 00:11:22:33:44:55"
キャプチャ画面で、RTS や CTS フレームが頻繁に飛び交っているか、あるいは Retry フラグが立ったデータフレームが異常に多くないかを確認します。Retryが多いのにRTS/CTSが機能していない場合、まさに隠れ端末が暴れている決定的な証拠です。
② アプリケーション層からの検証(Pythonスクリプト)
インフラのレイヤーだけでなく、エッジデバイスから目的のWeb APIへリクエストを投げた際の「揺らぎ(ジッターやタイムアウト)」を継続的に測定するスクリプトを走らせておきます。
以下のPythonスクリプトは、Wi-Fiの微弱なパケットロスによるレイテンシのスパイクを検知するためのものです。
import time
import requests
from requests.exceptions import RequestException
# 監視対象のローカルAPIエンドポイント
API_URL = "http://192.168.1.100/api/v1/telemetry"
INTERVAL_SEC = 1.0
def monitor_api_latency():
print(f"Starting Wi-Fi link stability check against {API_URL}...")
while True:
start_time = time.time()
try:
# タイムアウトを短め(2秒)に設定し、無線起因の詰まりをあぶり出す
response = requests.get(API_URL, timeout=2.0)
elapsed = (time.time() - start_time) * 1000 # ミリ秒変換
if response.status_code == 200:
print(f"[OK] Latency: {elapsed:.2f} ms")
else:
print(f"[WARN] HTTP Status: {response.status_code}, Latency: {elapsed:.2f} ms")
except RequestException as e:
# パケットロスや深刻な隠れ端末によるタイムアウトの発生をキャッチ
print(f"[ERROR] Connection failed or timed out: {e}")
time.sleep(INTERVAL_SEC)
if __name__ == "__main__":
try:
monitor_api_latency()
except KeyboardInterrupt:
print("\nMonitoring stopped by user.")
このスクリプトの実行中に、もし「数秒おきに突然のタイムアウトや数百ミリ秒の遅延」が発生するようであれば、物理的な電波環境、あるいはチャネル幅(VHT80やHE80など、広すぎるチャネルが周囲の干渉を拾っている可能性)の見直しが必要です。
—
まとめ:見えないパケットのドラマに思いを馳せて
Wi-Fiは「目に見えないからこそ面白い」のですが、同時にトラブルシューティングの難易度を跳ね上げる元凶でもあります。
- 端末同士が互いの存在を見通せない「隠れ端末問題」は、空中のコリジョンを惹き起こす。
- それを防ぐためのRTS/CTSメカニズムは、ハンドシェイクのオーバーヘッドと衝突防止のトレードオフ。
- 現場の物理環境(障害物や干渉源)に合わせて、RTS Thresholdを適切にチューニングする。
教科書通りの理論だけでなく、パケットの挙動を想像しながらコマンドを叩き、スクリプトで実測する。この泥臭いアプローチこそが、真に堅牢なネットワークとIoTインフラを支えるエンジニアのスキルです。
皆さんの現場のWi-Fiが、今日もクリーンで軽快なパケットを奏でることを祈っています。それではまた次の技術現場でお会いしましょう!
コメント