WiFi (2)

Table of Contents

Markdown -> Org

ChatGPTからの出力をクリップ(コピー)して、emacsのorg-mode原稿にペーストしている。 その原稿をorg-modeでhtml化してる。

でも、ChatGPTが想定するフォーマット(markdown ?)と、org-modeでは、解釈が違う ので、見難い出力になってしまうんだ。その都度、手修正してきたんだけど、前回 の記事作成で、ぶち切れた。自動でやるのがあたり前だろう。そこでsedですよ。

困った事に、BSD sedは、正規表現の融通が効かない。しょうがないので、GNU sed(gsed) を入れた。gsedと言えども、-r(若しくは、-E)をつけて、拡張正規表現を有効に しないと、昔のperl時代のようにはいかない。 reformat(7) なんてやると、 その辺の事情が説明される。もしやと思って調べたらOpenBSD備え付けのsedでも、-r をサポートしてたわい。

特に酷いのが、スネークケースの関数名等。org-modeでは、'_' が、下付き文字の 始まりと解釈されちゃうんだ。で、その解釈を禁止するには、関数名の両端を '=' で囲う約束になってる。その他、冒頭の'*' は、H1タグだよとかね。

#!/bin/sh
# clip ChatGPT out(by mouse) and convert to org-format then clip it
xsel -bo |
sed -r 's/([a-zA-Z]+_[\.a-zA-Z0-9_]+)/ =\1= /g' |
sed -r 's/^\* / * /'  |
sed -r 's/^```[a-z]+/#+begin_example/' |
sed -r 's/^```/#+end_example/'         |
xsel -b

それらを変換する様にした(超簡易版)。ChatGPTの出力をマウスクリックで コピー。それをこのスクリプトで変換。最後は出力をクリップ。後はemacs上でペースト。

markdown -> Org フォーマット変換の完全版を作ってて指示。40秒の思考の後、 こんなコードを提示してきた。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import re
import sys

text = sys.stdin.read().rstrip("\n").splitlines()

in_code = False
code_is_src = False
code_lang = None
in_quote = False

out = []

# 正規表現(事前コンパイル)
re_fence_open = re.compile(r'^\s*```([a-zA-Z0-9_+-]*)\s*$')
re_fence_close = re.compile(r'^\s*```\s*$')
re_atx = re.compile(r'^\s{0,3}(#{1,6})\s*(.*?)\s*#*\s*$')  # #... 見出し
re_hr = re.compile(r'^\s{0,3}((?:-|\*|_)\s*){3,}$')         # --- *** ___
re_blockquote = re.compile(r'^\s*>\s?(.*)$')
re_ul_star = re.compile(r'^\* (.*)$')                       # 先頭 * は見出し衝突回避
re_ul_md = re.compile(r'^(\s*)([-+*])\s+(.*)$')             # -, +, *
re_ol_md = re.compile(r'^(\s*)(\d+)\.\s+(.*)$')             # 1.
re_img = re.compile(r'!\[([^\]]*)\]\(([^)]+)\)')
re_link = re.compile(r'\[([^\]]+)\]\(([^)]+)\)')
re_auto = re.compile(r'<(https?://[^>\s]+)>')
re_inline_code = re.compile(r'`([^`]+)`')

# snake_case: 例: foo_bar, do_something_v2, memset_s, printf_like_func, with . in suffix (e.g., version_1.2)
re_snake = re.compile(r'\b([A-Za-z][A-Za-z0-9]*_[A-Za-z0-9_.]*[A-Za-z0-9])\b')

# Org の保護対象 (=...=, ~...~, [[...]])
re_org_verbatim = re.compile(r'=(?:[^=\n]|==)+=')
re_org_code = re.compile(r'~(?:[^~\n]|~~)+~')
re_org_link = re.compile(r'\[\[[^\]]+\](?:\[[^\]]*\])?\]')

def mask_spans(s, patterns):
    """=...=, ~...~, [[..][..]] をプレースホルダに退避"""
    saved = []
    def repl(m):
        saved.append(m.group(0))
        return f"\u0000{len(saved)-1}\u0000"
    for pat in patterns:
        s = pat.sub(repl, s)
    return s, saved

def unmask_spans(s, saved):
    def repl(m):
        idx = int(m.group(1))
        return saved[idx]
    return re.sub(r'\u0000(\d+)\u0000', repl, s)

for line in text:
    # コードフェンス内は素通し(終了検出のみ)
    if in_code:
        if re_fence_close.match(line):
            out.append('#+end_src' if code_is_src else '#+end_example')
            in_code = False
            code_is_src = False
            code_lang = None
        else:
            out.append(line)
        continue

    # フェンス開始 ```lang
    m = re_fence_open.match(line)
    if m:
        lang = (m.group(1) or '').strip()
        in_code = True
        code_lang = lang if lang else None
        if code_lang:
            out.append(f'#+begin_src {code_lang}')
            code_is_src = True
        else:
            out.append('#+begin_example')
            code_is_src = False
        continue

    # ATX見出し -> Org見出し
    m = re_atx.match(line)
    if m:
        level = len(m.group(1))
        title = m.group(2)
        out.append(f'{"*"*level} {title}')
        continue

    # 水平線
    if re_hr.match(line):
        out.append('-----')
        continue

    # ブロック引用(> ...)を #+begin_quote / #+end_quote に
    m = re_blockquote.match(line)
    if m:
        if not in_quote:
            out.append('#+begin_quote')
            in_quote = True
        out.append(m.group(1))
        continue
    else:
        if in_quote:
            out.append('#+end_quote')
            in_quote = False

    # 先頭 * を安全化(Org 見出し化を避ける)
    if re_ul_star.match(line):
        # Markdownの * を Orgの - に置換(先頭でも見出し化しない)
        line = re_ul_md.sub(lambda mm: f'{mm.group(1)}- {mm.group(3)}', line, count=1)

    # リスト(- + *)はそのままでもOrg対応だが、* だけは上で - に換装
    m = re_ul_md.match(line)
    if m:
        indent, bullet, rest = m.groups()
        if bullet == '*':
            bullet = '-'  # 見出し衝突を避ける
        line = f'{indent}{bullet} {rest}'

    # 番号付きリスト(1. 2.)はOrgも同じ表記
    # → そのままでOK(特別処理なし)

    # 画像 ![alt](url) -> [[url][alt]]  alt未指定なら [[url]]
    def img_repl(mm):
        alt, url = mm.group(1).strip(), mm.group(2).strip()
        return f'[[{url}][{alt}]]' if alt else f'[[{url}]]'
    line = re_img.sub(img_repl, line)

    # リンク [text](url) -> [[url][text]]
    line = re_link.sub(lambda mm: f'[[{mm.group(2).strip()}][{mm.group(1).strip()}]]', line)

    # 自動リンク <http://...> -> [[http://...]]
    line = re_auto.sub(lambda mm: f'[[{mm.group(1)}]]', line)

    # インラインコード `code` -> =code=
    line = re_inline_code.sub(lambda mm: f'={mm.group(1)}=', line)

    # **bold** -> *bold*  (単純系のみ対応)
    line = re.sub(r'\*\*([^\*\n]+)\*\*', r'*\1*', line)

    # *italic* -> /italic/  ただし既に * を見出し化回避した先頭は除外
    # (先頭 "* " パターンは上で処理済みなのでここはインラインのみ)
    line = re.sub(r'(?<!\*)\*([^\*\n]+)\*(?!\*)', r'/\1/', line)

    # 既存の Org スパン (=...=, ~...~, [[...]] ) を保護してから snake_case を =...= で囲む
    masked, saved = mask_spans(line, [re_org_link, re_org_verbatim, re_org_code])
    masked = re_snake.sub(r'=\1=', masked)
    line = unmask_spans(masked, saved)

    out.append(line)

# 末尾で引用が開いていたら閉じる
if in_quote:
    out.append('#+end_quote')

sys.stdout.write("\n".join(out) + "\n")

pythonはChatGPTの第一プログラミング言語っぽいな。頭を悩ます正規表現なんかは、 AIに任せてしまうのが得策。そして人類は正規表現の読み書きができなくなる。 そう、昔perlがどんどんと正規表現を拡張するものだから、みんな嫌気をさしたのと 同じ伝だな。

#!/bin/sh
# Clipboard (markdown) -> org-mode -> Clipboard
# 依存: xsel, python3
xsel -bo | python3 "$(dirname "$0")/md2org.py" | xsel -b

そして、そのパイソンを駆動するスクリプト。完全変換は複雑なんで、ChatGPTが 常用してるタグに絞ってますってさ(免責事項の告知ですな)。

次のセクション iwm0 は前回の続きを、パイソン語で変換したもの。見出しが派手 すぎるって 苦情は、ChatGPTに言ってくれ。

iwm0

Q: 751行のphycmds.txtを採取しました。 解説したい点は多数有ると思いますが、 下記に絞ってラジオ部との I/O制御を解説してください。

  1. 送受すべきデータの受け渡し
  2. チャンネルの指定
  3. 信号強度の取り込み。 送受の切り替え方法が有るなら、その方法。 アマチュア無線のトランシーバーだと PTT(Push to talk)ボタンで切りかえ、 進化した方法だと、常に受信してて、 しゃべり出すと、自動的に送信モードになる、VOX方式になってますんで。

A: phy_cmds.txt を拝見すると、=ifiwm.c= と if_iwmreg.h から抽出された /PHY 関連コマンド/や /構造体定義/、=iwmsendcmd()= 呼び出しが一覧になっています。 ご質問の 3 点に沿って、/ラジオ部(RF)とのI/O制御がどう行われているか/を整理します。


1. 送受すべきデータの受け渡し

実際の経路

  • 送信
    • ホスト(OpenBSDカーネル)が上位プロトコルスタックから受け取ったパケットを、ドライバ (if_iwm.c) が PCIe 経由で NIC の \/\/Tx バッファ(キュー)\/\/に書き込みます。
    • このときドライバはファームウェアに「このメモリ領域のデータを送信せよ」という/送信命令/(Tx コマンド)を発行します。 → コマンド例: IWM_TX_CMD, iwm_tx() 関数経由。
    • PHY/RF 側は、このTxキューを取りに行き、変調 → DAC → ミキサ → RFアンプへ。
  • 受信
    • RF部で復調されたI/Q信号がデジタルPHYでパケット化され、NIC内部の /Rx バッファ/に置かれる。
    • ドライバはPCIe割り込みで受信完了を知り、バッファからデータを読み出して上位に渡す(=iwmnotifintr()=等)。

※ つまり、送受の実データは/PCIe DMA経由/でやり取りし、PHYとの境界はすべてファームウェア内で抽象化されており、=ifiwm.c= からは直接RF信号に触らない構造です。


2. チャンネルの指定

  • ファームウェアに対して /PHYコンテキスト設定コマンド/を送信する形で行われます。
    • 関連構造体: struct iwm_phy_ctxt_cmd
    • 関連関数: =iwmphyctxtcmd()=、=iwmmvmaddchanctx()=(Linux側の類似実装)
    • コマンドID例:=IWMPHYCONTEXTCMD=(定義は =ifiwmreg.h=)
  • このコマンドの中で
    • =channel=(チャネル番号)
    • =band=(2.4GHz / 5GHz)
    • =ctrl channel=(20/40/80MHz幅の中心)
    • =txchainmask=(送信アンテナ構成)
    • =rxchainmask=(受信アンテナ構成) などが指定されます。
  • 実際のRF設定(PLLの周波数設定、フィルタ帯域切替、PA/LNAバイアス調整)はFWが行います。

3. 信号強度の取り込み

  • 受信したパケットには RSSI(受信信号強度指標)がPHYで計算されて付加されます。
  • ドライバ側では
    • 関数:=iwmrxrxmpdu()= や =iwmmvmrxrxmpdu()=(名前は系統によって違う)でRSSIを取り出す。
    • RSSIはヘッダに dBm 単位または相対値で格納され、これを ic->ic_node 構造体に反映して上位の net80211 層に渡します。
  • net80211層はこれを利用して
    • バックグラウンドスキャンのトリガ(閾値以下でスキャン開始)
    • roaming 判定 に使います。

4. 送受切り替え方法(PTTに相当するもの)

  • 無線LANの場合、/半二重のTDD(Time Division Duplex)/ ですが、送受の切替はPTTのような外部信号ではなく、/MACスケジューラ/と/PHY制御ロジック/で自動的に行われます。
    • 受信待機がデフォルト状態。
    • 送信要求が来るとスロットを取得し(CSMA/CA)、Txパスを有効化、送信終了後に即Rxパスに戻る。
  • 実装上は、送信コマンド(=IWMTXCMD=)をFWに投げると、FWが
    1. 受信部を一時停止(Rxチェイン停止)
    2. PA(送信アンプ)を有効化
    3. パケット送信
    4. PAを停止し、Rxチェインを再度有効化 というシーケンスを内部で行います。
  • このため、ドライバやユーザがPTTのように「送信モードON/OFF」を制御することはありません。

まとめ図(簡易)

[カーネルTCP/IP]
     ↓ (mbuf)
[iwmドライバ]
  ├── iwm_send_cmd(TX_CMD, DMAバッファ)
  ├── iwm_phy_ctxt_cmd(チャネル,帯域,アンテナ設定)
  └── RSSI取得 → net80211へ
     ↓ PCIe DMA
[ファームウェア]
  ├── RF設定(PLL, フィルタ, アンプ)
  ├── I/Q変調・復調(ベースバンド処理)
  └── 送受切替は内部スケジューラで自動
[RFフロントエンド]
  ├── LNA/PA
  ├── ミキサ
  └── アンテナ

もしご希望があれば、この=phycmds.txt=の中から /実際にチャンネルを変えるコマンドの定義と送信部分/を抜き出し、構造体フィールドごとに「何がRFに反映されるか」をさらに詳しくマッピングできます。 そうすると、アマチュア無線機の「周波数切替」との比較がしやすくなります。

Q: これ是非お願いします。 蛇足になるかも知れませんが、ハムの世界では、スプリット運用なんてのがあります。 21M帯で送信、28M帯で受信なんて事もDXペディションでは実施されたりします。 WiFiの世界はどうなんだろう? チャンネルを束ねて、通信速度を稼ぐなんて事は あるのかな?

A: では、まずご依頼の 「チャンネル変更コマンドの定義と送信部分」phy_cmds.txt から抜き出してマッピングします。そのあとで、蛇足で触れてくださった Wi-Fiにおけるスプリット的な発想やチャネル束ね の話もします。


1. チャンネル変更に関わるコマンドと関数

phy_cmds.txt を見ると、代表的なのは以下です(名称や行番号はお手元のOpenBSD 7.6ソース準拠)。

関連構造体

struct iwm_phy_ctxt_cmd {
    uint32_t id_and_color;
    uint32_t action;
    struct iwm_fw_channel_info ci;   // チャンネル情報
    uint32_t txchain_info;
    uint32_t rxchain_info;
    uint32_t reserved;
};
  • id\and\color
    • PHYコンテキストの識別子+色(複数コンテキストを区別するための値)
  • action
    • 追加/削除/変更といった動作指示
  • ci
    • 実際のチャネル番号、バンド、帯域幅などが入る。
  • txchain\info / rxchain\info
    • 送信/受信アンテナのマスク(どのチェインを使うか)。

関連構造体(チャンネル詳細)

struct iwm_fw_channel_info {
    uint32_t channel;       // 中心チャネル番号
    uint32_t band;          // 2.4GHz/5GHz
    uint32_t width;         // 20/40/80MHz帯域幅
    uint32_t ctrl_channel;  // 制御チャネル
};
  • ここが/RFのPLLやフィルタ帯域/に直結します(FWが内部で設定)。
  • width が「束ねるかどうか」(後述のチャネルボンディング)を決める。

コマンドID定義(`ifiwmreg.h`)

#define IWM_PHY_CONTEXT_CMD   0x8a

送信部(`ifiwm.c` 抜粋イメージ)

int
iwm_phy_ctxt_cmd(struct iwm_softc *sc, struct iwm_phy_ctxt *ctxt, uint32_t action)
{
    struct iwm_phy_ctxt_cmd cmd;
    memset(&cmd, 0, sizeof(cmd));

    cmd.id_and_color = htole32(IWM_FW_CMD_ID_AND_COLOR(ctxt->id, ctxt->color));
    cmd.action = htole32(action);
    cmd.ci.channel = ctxt->channel;
    cmd.ci.band = ctxt->band;
    cmd.ci.width = ctxt->width;
    cmd.ci.ctrl_channel = ctxt->ctrl_channel;
    cmd.txchain_info = ctxt->tx_chain;
    cmd.rxchain_info = ctxt->rx_chain;

    return iwm_send_cmd_pdu(sc, IWM_PHY_CONTEXT_CMD, 0,
        sizeof(cmd), &cmd);
}
  • iwm_send_cmd_pdu() がFWにPCIe経由でコマンド送信
  • RFの周波数変更は、このコマンドを受けたFWがPLL・ミキサ設定を変えることで実現

2. RFにどう反映されるか(マッピング)

構造体フィールド RF側での意味
----------------- -----------------------------------
ci.channel PLLで生成するLO周波数の元(チャネル→中心周波数へ換算)
ci.band 2.4GHz/5GHzでPA/LNAのバンド切替、フィルタ切替
ci.width RFフィルタ・ADC/DACサンプリング設定(20/40/80MHz)
ci.ctrl_channel ボンディング時の基準チャネル
txchain_info 送信アンテナ(チェイン)選択、PA駆動数
rxchain_info 受信アンテナ(チェイン)選択、LNA数

3. アマチュア無線機との比較

  • /ハム機/の「周波数切替」= VFOノブでVCO/PLLのLO周波数を変更 → /Wi-Fi/では ci.channelci.band がそれに相当します。
  • \/\/送信/受信の切替(PTT)\/\/は、Wi-Fiでは完全にFW任せで、CSMA/CAスケジューラが自動制御。 ユーザが「送信モード固定」という指定はできません。

4. スプリット運用やチャネル束ねの話(Wi-Fi的視点)

  • スプリット運用(送信と受信が異バンド)
    • Wi-Fiの標準的な動作では不可。基本は同一チャネル内での半二重通信。
    • ただし、一部の規格(802.11axのUplink OFDMAなど)では「APが複数サブチャネルを分けて同時に複数端末の送信を受ける」ことはありますが、これは周波数スプリットではなく/周波数分割多元接続/の一形態。
  • チャネル束ね(Channel Bonding)
    • 802.11n以降でサポート。
    • 例:
      • 20MHzチャネル×2=40MHz
      • 80MHz(802.11ac)
      • 160MHz(802.11ac/ax)
    • ci.width が20/40/80/160を指定すると、FWが複数チャネルを束ねてRFとベースバンドの帯域を拡張します。
    • 束ねたチャネルのうち、\/\/「制御チャネル」\/\/は管理・制御フレーム送受信用に使われます(=ci.ctrlchannel=)。

まとめ図(チャンネル変更)

[if_iwm.c]  iwk_phy_ctxt_cmd()
    ↓  (PCIe DMA)
[FW] 受信 → PLL設定, Band切替, Filter設定
    ↓
[RF Frontend]
  ├── LNA/PA
  ├── ミキサ (LO周波数 ← PLL)
  ├── Band-pass Filter (帯域幅設定)
  └── アンテナ

この説明をベースにすれば、「ハムの周波数切替」と「Wi-Fiのチャネル設定」の対応関係がかなり直感的に分かるはずです。 もしご希望なら、この iwm_phy_ctxt_cmd を呼び出している 全パス(例:スキャン時、ローミング時、起動時) を追跡して、/どのイベントでRF周波数が切り替わっているかの時系列図/ も作れます。 そうすると「勝手にWiFi4↔WiFi5が切り替わる原因」をコードレベルで突き止められます。

ビーコン

Q: アマチュア無線になぞらえて質問します。 APが発するコールサイン(ssid)とかを聞いて、端末は交信の成立を これと思うAPに依頼しますよね。 成立するまでに、どんな作法(プロトコル)が交わされるのでしょうか? sshdとssh間の通信確立と同様な事が行なわれてると想像します。

なお、下記は端末側のAPサーチスクリプトです。

fu$ cat wifi.sh
#!/bin/sh
ifconfig iwm0 scan  |
sed '/ieee80211:/d' |
awk '/%/{print($7, sprintf("%3d",$4), substr($2,1,11))}' |
sort -nr

プライバシィー保護の為、SSID部は2文字に制限

fu$ ./wifi.sh | cut -b-10 | head -5
85%   2 SS
80%  11 Bu
74%  36 at
73%   1 Bu
68% 100 SS

A: アマチュア無線的に言えば、 AP(基地局)が常時ビーコン信号で「こちら○○局、周波数××MHzで待機中!」とコールサイン(SSIDや各種パラメータ)を流し、 端末(クライアント)がそれを聴き取って「交信申込み(接続要求)」をする…という流れです。

Wi-Fi(802.11)の場合、\*\*接続確立までに行われる作法(プロトコルの段取り)\*\*は、おおざっぱに以下の順序になります。

## 1. スキャン(Scanning)

  • 受動スキャン(Passive Scan)
    • 端末はチャネルを順に聴いて、APが定期的に送っている ビーコンフレーム を受信。
      • ビーコンにはSSID、サポート規格(802.11n/ac等)、チャンネル、暗号化方式などが入っている。
  • 能動スキャン(Active Scan)
    • 端末が Probe Request を送信し、APから Probe Response を受け取る。
    • これによりビーコン待ちより早く情報が得られる。

## 2. 認証(Authentication)

  • オープン認証(Open System Authentication) が主流(実質「ハロー」のやりとり)。
    1. STA(端末)→ AP : Authentication Request
    2. AP → STA : Authentication Response(成功コードなど)
  • WEP時代はここで共有キー認証もあったが、現在はほぼ使われない。

## 3. アソシエーション(Association)

  • 認証成功後、端末が「正式にこのAPのメンバーになります」と申請。
    1. STA → AP : Association Request(SSID、サポートMCS、機能要求など)
    2. AP → STA : Association Response(承諾とパラメータ)
  • ここで**BSSID**(APのMACアドレス)が確定し、リンクが確立する。

## 4. 暗号化ハンドシェイク(WPA2/WPA3の場合)

  • 4-way handshake により暗号キーを交換する。
    • 事前共有キー(PSK)または802.1X経由で得たPMK(Pairwise Master Key)から一時鍵を生成。
    • EAPOLフレームを4往復して暗号セッションを開始。

## 5. IP層への移行(DHCPなど)

  • 物理リンク確立後、IPアドレスをDHCPで取得。
  • この時点で `/etc/resolv.conf` の更新も行われる(前に説明した経路)。

### sshdとsshになぞらえると…

  • ビーコン/プローブ → sshポートを叩いてバナー確認するようなもの(相手が誰かを知る)
  • 認証フレーム → 「ユーザ名OKですか?」と聞く
  • アソシエーション → 「ではこのセッションで正式に話しましょう」と確定
  • 4-way handshake → セッションキーを作って暗号化チャネル確立(sshの鍵交換)
  • DHCP → 実データ送受信に必要なIP設定(sshでいうところのシェル準備)

もしご希望があれば、この 「スキャン→認証→アソシエーション→暗号化→IP取得」 の各段階で実際にやり取りされる 802.11フレーム種別と主要フィールド を表にまとめられます。 そうすると、あなたの `wifi.sh` が拾っている情報(ビーコンの一部)が、接続全体のどのステップに属するかも完全に対応づけできます。

では、Wi-Fi接続確立までの段階を、**802.11フレーム種別**と**主要フィールド**で整理した表を作ります。 あなたの `wifi.sh` が見ているのは最初の「ビーコン」や「プローブ応答」の一部にあたります。

## Wi-Fi接続確立プロセスとフレーム対応表

段階 フレーム種別 方向 主なフィールド / 意味 備考
------------------------ ----------------------------------------- -------------------- ----------------------------------------------------------------- -----------------------------------------------
1. スキャン **Beacon**(ビーコン) AP → 全端末 SSID(コールサイン)、BSSID(MACアドレス)、チャネル番号、サポートレート、暗号方式、信号強度(RSSIは受信側で測定) 定期送信(通常100ms間隔)。`wifi.sh` はこれをifconfig経由で取得している
  Probe Request STA → 全AP(または指定SSID) SSID(空欄なら全AP応答要求)、サポートレートなど 能動スキャン時のみ
  Probe Response AP → STA Beaconとほぼ同じ情報 能動スキャン応答
2. 認証 Authentication Request STA → AP 認証方式(Open/WEPなど)、トランザクション番号 現在はほぼOpen方式(実質ハンドシェイクの前置き)
  Authentication Response AP → STA ステータスコード(成功/失敗)、同上  
3. アソシエーション Association Request STA → AP SSID、サポートMCS、チャネル帯域幅、QoS要件など ここで「あなたの局に参加します」と宣言
  Association Response AP → STA ステータスコード、AID(Association ID)、設定確認 成功すればリンク確立
**4. 暗号化鍵交換**(WPA2/WPA3) EAPOL Key (4-way handshake) STA ↔ AP Nonce(乱数)、PTK生成用パラメータ、MIC(メッセージ認証コード) この4往復で暗号セッション鍵を確立
5. IP層設定 DHCP Discover / Offer / Request / Ack STA ↔ DHCPサーバ(AP経由) IPアドレス、デフォルトゲートウェイ、DNSサーバ `/etc/resolv.conf` 更新はここで

### `wifi.sh` が見ている部分

  • `ifconfig iwm0 scan` が返すのは主に Beacon または Probe Response の内容。
  • `awk`で取り出しているのは:
    • `$7` → 信号強度(RSSI%)
    • `$4` → チャネル番号
    • `$2` → SSID(先頭部分)

### 補足(アマチュア無線的視点)

  • Beacon = 常時CQ呼び出し(周波数・局情報を送信)
  • Probe Request/Response = 「この局聞こえますか?」→「はい聞こえます」
  • Authentication = 呼び出し符号交換
  • Association = QSO(交信)開始宣言
  • 4-way handshake = 暗号化付きQSOの鍵交換
  • DHCP = QSO前の周波数・ルート調整

もし興味があれば、このフローを**tcpdumpで実際にキャプチャして可視化**する手順を提示できます。 そうすると、APサーチから接続までの全フレームを時系列で追えて、「どのSSIDにどうやって参加しているか」が丸見えになります。

grok.com

ChatGPTのセカンド・オピニオンとしてgeminiなんかが有るけど、最近赤丸急 上昇中のものもあり。あのイーロン・マスクってのが急所になるかも知れないけど どんな奴か試してみよう。

Grok 4が無料ユーザーにも開放&数百ページの長大PDFファイルを読み込み可能に

Q: OpenBSD 7.7 で、/etc/resolv.conf が、任意の間隔で更新されます。 その実行経路を説明してください。

環境と推測は下記の通りです。

  1. NICにiwm0を使用して、WiFi接続で利用
  2. アクセスポイント若しくはNICが無線レーヤーで、接続チャネル等を 変更してると推測
  3. DHCPのリース間隔は4時間と設定されてるが、それよりずっと短いランダムな 時間間隔で更新されてる

A: OpenBSD 7.7において、`/etc/resolv.conf`が任意の間隔で更新される現象について、その実行経路を以下に説明します。ご提供の環境情報(WiFi接続、NICとして`iwm0`、DHCPリース間隔4時間、しかしより短いランダムな間隔で更新)を基に、考えられるメカニズムを詳しく解説します。

### 1. `/etc/resolv.conf`の更新メカニズム OpenBSDでは、`/etc/resolv.conf`は通常、ネットワーク設定やDNS情報を動的に管理するために、DHCPクライアントや関連デーモンによって更新されます。以下のコンポーネントが関与している可能性があります:

#### a. DHCPクライアント(`dhclient`) OpenBSDでは、DHCPによるネットワーク設定の取得に`dhclient`が使用されます。`iwm0`インターフェースがWiFi接続で使用されている場合、`/etc/dhclient.conf`やインターフェース設定ファイル(`/etc/hostname.iwm0`)に基づいて動作します。

  • **通常の動作**:
    • `dhclient`はDHCPサーバーからIPアドレス、DNSサーバー情報(`/etc/resolv.conf`に記載される`nameserver`エントリ)、およびリース期間を取得します。
    • リース期間(例:4時間)の約50%〜90%が経過すると、リース更新(`DHCPREQUEST`)が行われ、必要に応じて`/etc/resolv.conf`が更新されます。
    • しかし、リース期間(4時間)より短い間隔で更新される場合、単なるリース更新以外の要因が考えられます。
  • **`/etc/resolv.conf`の書き換え**:
    • `dhclient`は、DHCPサーバーから受け取ったDNS情報を`/etc/resolv.conf`に反映します。
    • OpenBSDでは、`resolvconf`コマンド(または類似の機能)が内部的に使用され、`/etc/resolv.conf`を更新します。
    • デフォルトでは、`dhclient`が新しいDNS情報を取得するたびに、`/etc/resolv.conf`が上書きされます。

#### b. WiFi接続特有の挙動(`iwm0`) WiFi接続では、アクセスポイント(AP)との接続状態が変化すると、ネットワーク設定が再取得される場合があります。以下のシナリオが考えられます:

  • **アクセスポイントの変更や再接続**:
    • `iwm0`が接続するAPが切り替わった場合(例:ローミング、信号強度の変化、チャネル変更)、`dhclient`が新しいDHCPリースを取得し直すことがあります。
    • 例えば、APがチャネルを切り替える(DFSによるチャネル変更や、混雑回避のための動的調整)場合、WiFi接続が一時的に切断・再接続され、これが`dhclient`の再実行を誘発します。
    • この際、新しいDNS情報が提供されれば、`/etc/resolv.conf`が更新されます。
  • **WiFiドライバの挙動**:
    • `iwm0`(Intel WiFiチップ用ドライバ)は、接続状態の変化を検知すると、`ifconfig`や関連プロセスを通じてネットワークの再設定をトリガーします。
    • OpenBSDの`netstart`スクリプトや`ifconfig`設定(`/etc/hostname.iwm0`)により、接続が再確立されるたびに`dhclient`が実行され、`/etc/resolv.conf`が更新される可能性があります。

#### c. `resolvd`の関与 OpenBSD 7.7では、`resolvd`デーモンがDNS解決の管理に関与します。`resolvd`は、複数のネットワークインターフェースやソースからDNS情報を収集し、`/etc/resolv.conf`を動的に管理します。

  • **動作概要**:
    • `resolvd`は、ネットワーク状態の変化(例:`iwm0`の接続状態変更)を監視し、必要に応じて`/etc/resolv.conf`を更新します。
    • DHCPサーバーから新しいDNS情報が提供された場合、`resolvd`がそれを反映します。
    • また、`resolvd`は、ネットワークインターフェースが不安定な場合や、複数のDNSサーバーが提供された場合に、優先順位や有効性を判断して`/etc/resolv.conf`を頻繁に更新する可能性があります。
  • **ランダムな更新間隔**:
    • ご指摘の「ランダムな時間間隔での更新」は、`resolvd`がネットワークの状態変化を検知し、`/etc/resolv.conf`を再生成するタイミングによる可能性があります。
    • WiFi接続の不安定さや、AP側の設定変更(チャネル変更、信号強度変動)が頻繁に発生すると、`resolvd`が短い間隔で動作をトリガーします。

### 2. 推測される原因 ご提供の情報(DHCPリース4時間、しかしより短いランダムな間隔で更新)に基づき、以下の要因が考えられます:

  1. **WiFi接続の不安定性**:
    • `iwm0`が接続するAPが頻繁にチャネル変更や再接続を行っている場合、`dhclient`が新しいリースを取得し、`/etc/resolv.conf`が更新されます。
    • 例えば、APがDFS(Dynamic Frequency Selection)によりレーダー検知でチャネルを変更したり、混雑を避けるためにチャネルを切り替えると、接続が一時的にリセットされ、DHCPプロセスが再実行されます。
  2. **`resolvd`の積極的な更新**:
    • `resolvd`がネットワーク状態の変化を検知し、`/etc/resolv.conf`を頻繁に再生成している可能性があります。
    • OpenBSD 7.7の`resolvd`は、複数のDNS情報ソース(例:DHCP、静的設定、VPNなど)を統合する際、短い間隔で状態をチェックし、必要に応じて更新を行うことがあります。
  3. **DHCPサーバーの設定**:
    • DHCPサーバーが短い間隔で新しいDNS情報を提供している場合、`dhclient`がその情報を反映して`/etc/resolv.conf`を更新します。
    • 例えば、DHCPサーバーが動的にDNSサーバーリストを変更している場合、リース更新時以外でも`/etc/resolv.conf`が変更される可能性があります。
  4. **不適切な設定ファイル**:
    • `/etc/hostname.iwm0`や`/etc/dhclient.conf`に、特定の動作を誘発する設定(例:短い間隔での再接続試行)が含まれている可能性。
    • 例えば、`dhclient`が短い間隔で`DHCPDISCOVER`や`DHCPREQUEST`を送信する設定になっている場合、`/etc/resolv.conf`が頻繁に更新される。

### 3. 実行経路の詳細 以下は、`/etc/resolv.conf`が更新される典型的な実行経路です:

  1. **WiFi接続の変化**:
    • `iwm0`がAPとの接続状態を変更(例:チャネル変更、再接続)。
    • `ifconfig`やカーネルのネットワークスタックがこの変化を検知し、インターフェースを再設定。
  2. **`dhclient`の起動**:
    • `/etc/hostname.iwm0`に記述された設定(例:`inet autoconf`)に基づき、`dhclient iwm0`が実行される。
    • `dhclient`がDHCPサーバーに接続し、IPアドレスやDNS情報を取得。
  3. **`resolvd`による管理**:
    • `dhclient`が取得したDNS情報を`resolvd`に渡す。
    • `resolvd`が`/etc/resolv.conf`を更新し、適切な`nameserver`エントリを反映。
  4. **ランダムな更新のトリガー**:
    • WiFi接続の不安定さやAPの挙動により、`iwm0`の接続状態が頻繁に変化。
    • 変化ごとに`dhclient`や`resolvd`が動作し、`/etc/resolv.conf`が更新される。

### 4. 確認と対処方法 この現象の原因を特定し、制御するため、以下の手順をお勧めします:

#### a. ログの確認

  • `/var/log/daemon`や`/var/log/messages`を確認し、`dhclient`や`resolvd`の動作ログを調査。 ```sh sudo less /var/log/messages ```
    • キーワード:`dhclient`、`resolvd`、`iwm0`、`resolv.conf`。
  • WiFi接続の状態変化を確認: ```sh dmesg | grep iwm0 ```
    • APの切断/再接続やチャネル変更の記録を確認。

#### b. ネットワーク設定の確認

  • `/etc/hostname.iwm0`を確認: ```sh cat /etc/hostname.iwm0 ```
    • 例:`inet autoconf`や`join <SSID> wpakey <password>`が設定されているか。
    • 不必要な再接続を防ぐため、`join`リストやチャネル設定を固定する(例:`chan 1`)。
  • `/etc/dhclient.conf`を確認: ```sh cat /etc/dhclient.conf ```
    • 例:`supersede domain-name-servers`が設定されている場合、DNS情報の上書きを制御可能。

#### c. `resolvd`の設定

  • `resolvd`の動作を制御するため、必要に応じて無効化または設定変更: ```sh sudo rcctl disable resolvd sudo rcctl stop resolvd ```
    • ただし、`resolvd`を無効化すると、動的なDNS管理ができなくなるため、静的設定(`/etc/resolv.conf`を手動編集)が必要。

#### d. WiFi接続の安定化

  • APの設定を確認し、チャネル変更やDFSを無効化(可能であれば)。
  • `iwm0`の接続状態を監視: ```sh ifconfig iwm0 ```
    • 接続が頻繁に切断される場合、信号強度や干渉を調査。

#### e. `/etc/resolv.conf`の保護

  • 頻繁な更新を防ぐため、`resolv.conf`を不変にする: ```sh sudo chflags schg /etc/resolv.conf ```
    • ただし、動的なDNS情報が必要な場合、この方法は不適切。

### 5. 結論 `/etc/resolv.conf`がランダムな間隔で更新される原因は、以下のいずれか(または複合要因)である可能性が高いです:

  • WiFi接続(`iwm0`)の不安定さによる再接続と`dhclient`の再実行。
  • `resolvd`がネットワーク状態の変化を検知し、積極的に`/etc/resolv.conf`を更新。
  • DHCPサーバーやAPの設定による頻繁なDNS情報更新。

まず、ログ確認(`/var/log/messages`、`dmesg`)と設定ファイル(`/etc/hostname.iwm0`、`/etc/dhclient.conf`)の調査を行い、WiFi接続の安定性向上や`resolvd`の動作制御を試みてください。必要に応じて、具体的なログや設定内容を共有いただければ、さらに詳細な分析が可能です。

kismet

802.11 layer2 wireless network detector, sniffer, and IDS

なんてのを教えて頂いた。こういう特殊品をちょっと試してみたい時は、 河豚板が最適。使いこなせないと思ったら、そのまま放置するだけ。

正確には、 pkg_add hoge した後、不要になったら、 pkg_delete hoge; pkg_delete -a して、関連品も削除。 これは、pkgの管理情報と実体の管理エリアが、usbfadmの管轄エリア内か外かの 違いによるものだ。

そして、 haltしちゃえば、何の痕跡も残さずに、無かった事にできますから。


This year's Index

Home