Linuxにおける無線LANのソフト構成


無線LANのソフトの全体像

Linuxにおける無線LANの機能は以下の要素によって実装されています。

  • wpa_supplicant:STA(ステーション)として動作。802.1X/WPA認証、接続処理を担当
  • hostapd:SoftAP(アクセスポイント)として動作
  • iw:ドライバの設定、状態取得を実施
  • libnl:ユーザー空間とカーネル空間のI/F
  • cfg80211:チャンネル等の設定を担当
  • mac80211:スキャン、接続等のコア処理を担当
  • WLANドライバ:バスドライバ(SDIO、PCIe等)を制御
  • バスドライバ:SDIO、PCIe等のI/Fを通じてデバイスを制御

ユーザー空間では、wpa_suppliant、hostapd、iwが動作し、カーネル空間ではcfg80211、mac80211、ドライバが動作します。libnlはwpa_supplicant等のユーザー空間で動作するソフトとcfg80211とのインターフェースです。ソケットを通じてユーザー空間〜カーネル空間との間でメッセージのやり取りをします。

使用するデバイスによっては、mac80211を使わないこともありますし、wpa_supplicantが直接ドライバにioctlを発行して(libnlを使わずに)制御することもあります。

以下、各ソフトと各々の界面がどのように実装されているか説明していきます。

wpa_supplicantについて

WPA/WPA2/802.1X認証、802.11の Authentication, Associationといった処理が実装されています。また、WiFi Protected Setup, WiFi Directといった、WiFi Alliance関連の規格にも準拠しています。
Androidを含む組み込みLinuxでのClientの実装としては、大半はwpa_supplicantを使って実装されています。
詳しい解説とソースコードは本家の以下のサイトにあります。
https://w1.fi/wpa_supplicant/

wpa_supplicantはデーモンとして動作するソフトであり、wpa_cli、wpa_guiといったツールで制御できます。自作ソフトで制御する場合は以下を参考にすると良いです。
https://w1.fi/wpa_supplicant/devel/ctrl_iface_page.html

wpa_cliで使用可能なコマンドはREADMEを見るか、以下のコマンドをパースする箇所を参考にすると良いでしょう。ソースを見て初めて分かるコマンドオプションもあります。

wpa_supplicant/ctrl_iface.c
char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
                                         char *buf, size_t *resp_len)
{
 〜省略〜
    } else if (os_strcmp(buf, "P2P_FIND") == 0) {
        if (p2p_ctrl_find(wpa_s, ""))
            reply_len = -1;
    } else if (os_strcmp(buf, "P2P_STOP_FIND") == 0) {
        wpas_p2p_stop_find(wpa_s);
 〜省略〜
}

wpa_supplicantと下位層(libnl又はドライバ)との接点はsrc/driverの中にあります。
例えば、デフォルトのlibnlの場合、 以下のように関数ポインタを設定し、ポインタをサプリカント内部処理で呼び出しています。

src/drivers/driver_nl80211.c
const struct wpa_driver_ops wpa_driver_nl80211_ops = {
    .name = "nl80211",
    .desc = "Linux nl80211/cfg80211",
    .get_bssid = wpa_driver_nl80211_get_bssid,
 〜省略〜
    .get_pref_freq_list = nl80211_get_pref_freq_list,
    .set_prob_oper_freq = nl80211_set_prob_oper_freq,
};

関数ポインタの実体は同じソース内で定義されています。以下一例です。

src/drivers/driver_nl80211.c
static int nl80211_set_channel(struct i802_bss *bss,
                   struct hostapd_freq_params *freq, int set_chan)
{
〜省略〜
    msg = nl80211_drv_msg(drv, 0, set_chan ? NL80211_CMD_SET_CHANNEL :
                  NL80211_CMD_SET_WIPHY);
〜省略〜
    ret = send_and_recv_msgs(drv, msg, NULL, NULL);
〜省略〜
}

NL80211_CMD_XXXは、Linuxカーネル内の無線LANスタックに対するコマンドです。
以下のように定義されています。

src/drivers/nl80211_copy.h
enum nl80211_commands {
/* don't change the order or add anything between, this is ABI! */
    NL80211_CMD_UNSPEC,
〜省略〜
    NL80211_CMD_SET_CHANNEL,
〜省略〜
};

このenumはLinuxカーネルのソースでも定義されているため、NL80211_CMD_XXXの順番は変更できません。ABIとはApplication Binary Interfaceの略で、アプリとカーネルの間のI/Fを意味します。
本来、Linuxカーネル内で定義されたヘッダを直接includeするべきですが、enumの定義の順番を入れ替えないという前提で、アプリ側でこのenumを含むヘッダを作成することを許容しているようです。
上述したnl80211_set_channel()の中で、send_and_recv_msgs()の引数にABIを設定しています。さらに見ていくと以下にたどり着き、nl_xxx()という名称のlibnlのAPIを使っていることが分かります。

src/drivers/driver_nl80211.c
static int send_and_recv(struct nl80211_global *global,
             struct nl_handle *nl_handle, struct nl_msg *msg,
             int (*valid_handler)(struct nl_msg *, void *),
             void *valid_data)
{
〜省略〜
    err = nl_send_auto_complete(nl_handle, msg);
〜省略〜
    while (err > 0) {
        int res = nl_recvmsgs(nl_handle, cb);
〜省略〜
}

以上をまとめると、アプリ層とカーネル層との橋渡しする仕組みがlibnlで実装されており、その仕組みを使って、無線LANスタック及びドライバを制御するためのコマンド(NL80211_CMD_XXXと定義されたABI)をアプリ層とカーネル層との間でやり取りします。