Linux
無線LAN
Wi-Fi
wpa_supplicant

wpa_supplicantのコード解説 ①起動


はじめに

LinuxのWi-Fi機能はwpa_supplicantを使用します。このwpa_supplicantはユーザー空間上で動作するデーモンプロセスであり、アプリケーションからのコマンドを受け付けたり、ドライバを制御したりします。その内部構造を知りたいと思い、ソースコードを調べてみました。今後もし余力があればスキャン、接続などの動作について調べようと思いますが、まずはwpa_supplicantの起動処理について記述します。


wpa_supplicantの起動方法

私のRaspberry Piではデフォルトでwpa_supplicantが起動しているので、一旦プロセスを落としてから起動しています。手順とコマンドは以下です。


  1. LinuxのWiFiをOFFにする

  2. wpa_supplicantを再起動し、CLIツールを起動

# sudo kill PID #wpa_supplicantのPIDを指定

# sudo wpa_supplicant -i wlan0 -c ./wpa_supplicant.conf &
# sudo wpa_cli -i wlan0 -p /var/run/wpa_supplicant

主に以下の起動オプションを設定します。

オプション
内容

-B
バックグラウンドで起動(&を付けるのと同じ)

-c
設定ファイルのパス

-i
インターフェース(wlan0等)

-d
デバッグ出力を増やす(-ddでさらに増える)

-D
ドライバを指定(nl80211等)

-N
複数の-i,-cを指定してConcurrent動作をするときに使う

設定ファイルであるwpa_supplicant.confは以下のものを使っています。


wpa_supplicant.conf

ctrl_interface=/var/run/wpa_supplicant

update_config=1


起動処理の一連の流れ

起動処理は以下のように進みます。各々は以降で説明していきます。


  1. コマンドラインオプションの読み取り・設定

  2. 共通部分の初期化(イベントループ、乱数生成、制御I/Fの初期化)

  3. 各Network I/Fの初期化(ドライバの設定、ステートマシンの初期化、制御I/Fの初期化)

  4. 動作開始(アプリとの接続、イベントループの開始)


コマンドラインオプションの読み取り・設定

main()の中で各オプションを読み取っています。

共通の設定はparamsを経由してwpa_supplicant_init()で行われます。インターフェースごとの設定はifaceを経由してwpa_supplicant_add_iface()で行われます。

-Nで二つ目のインターフェースがあることを指定すると、ifacesをreallocして、ifaceに二つ目のポインタを設定してオプションを読み取ります。paramsに代入するオプションはインターフェースごとに指定できません。


wpa_supplicant/main.c

int main(int argc, char *argv[])

{
//
struct wpa_interface *ifaces, *iface; // intarfaceの設定
//
struct wpa_params params; // 共通設定(一時変数)
struct wpa_global *global; // 共通設定(グローバル変数)
//
for (;;) {
c = getopt(argc, argv,
"b:Bc:C:D:de:f:g:G:hi:I:KLMm:No:O:p:P:qsTtuvW");
if (c < 0)
break;
switch (c) {
//
case 'c':
iface->confname = optarg; // 設定ファイルのパス
break;
//
case 'D':
iface->driver = optarg; // ドライバ名(nl80211など)
break;
case 'd':
//
params.wpa_debug_level--; // デバッグレベル
break;
//
case 'i':
iface->ifname = optarg; // Network I/F名
break;
//
case 'N':
iface_count++;
iface = os_realloc_array(ifaces, iface_count,
sizeof(struct wpa_interface)); // 追加するinterface分のメモリを拡張
if (iface == NULL)
goto out;
ifaces = iface; // ifacesは最初のinterfaceを指す
iface = &ifaces[iface_count - 1]; // ifaceは追加したinterfaceを指す(この後、-i,-d等で設定)
os_memset(iface, 0, sizeof(*iface));
break;
//
global = wpa_supplicant_init(&params); // paramsの内容をglobalにコピー。共通部分の初期化(後述)
//
for (i = 0; exitcode == 0 && i < iface_count; i++) { // 各Network I/Fごとにループ
//
wpa_s = wpa_supplicant_add_iface(global, &ifaces[i], NULL); // 各Network I/Fの初期化(後述)
//
}
//
if (exitcode == 0)
exitcode = wpa_supplicant_run(global); // イベントループを起動


共通部分の初期化

共通部分の初期化はmain()から呼ばれるwpa_supplicant_init()で行います。

イベント処理、乱数生成、制御I/F(イベントを送受信するためのソケット)の初期化などを行います。


wpa_supplicant/wpa_supplicant.c

struct wpa_global * wpa_supplicant_init(struct wpa_params *params)

{
//
if (eloop_init()) { // イベントループの初期化
//
random_init(params->entropy_file); // 乱数生成の初期化
//
global->ctrl_iface = wpa_supplicant_global_ctrl_iface_init(global); // 制御I/Fの初期化
//
}

wpa_supplicant_global_ctrl_iface_init()ではアプリと通信するために、ソケットを取得して接続しています。

ctrlにソケットのパスが入っています。


wpa_supplicant/ctrl_iface_unix.c

static int wpas_global_ctrl_iface_open_sock(struct wpa_global *global,

struct ctrl_iface_global_priv *priv)
{
//
priv->sock = socket(PF_UNIX, SOCK_DGRAM, 0); // ソケットの取得
//
os_strlcpy(addr.sun_path, ctrl, sizeof(addr.sun_path)); // パス設定
if (bind(priv->sock, (struct sockaddr *) &addr, sizeof(addr)) < 0) { // 接続
//


各Network I/Fの初期化

次はwlan0などのNetwork I/Fごとの設定を見ていきます。

Network I/Fの状態変数を取得して、DISCONNECTED状態として初期化します。


wpa_supplicant/wpa_supplicant.c

struct wpa_supplicant * wpa_supplicant_add_iface(struct wpa_global *global,

struct wpa_interface *iface,
struct wpa_supplicant *parent)
{
struct wpa_supplicant *wpa_s; // I/Fごとの状態変数
struct wpa_interface t_iface;
//
wpa_s = wpa_supplicant_alloc(parent);
//
t_iface = *iface;
//
if (wpa_supplicant_init_iface(wpa_s, &t_iface)) { // I/Fの初期化(後述)
//
wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED); // 未接続状態にして追加完了
//
return wpa_s;
}

次はwpa_supplicant_init_iface()の中身です。

-cで指定した設定ファイルの読み込み、-Dで指定したドライバの設定、状態の初期化などをしています。


wpa_supplicant/wpa_supplicant.c

static int wpa_supplicant_init_iface(struct wpa_supplicant *wpa_s,

const struct wpa_interface *iface)
{
//
wpa_s->conf = wpa_config_read(wpa_s->confname, NULL); // -cで指定した設定ファイルの読み込み
//
if (wpas_init_driver(wpa_s, iface) < 0) // ドライバの設定(後述)
//
if (wpa_supplicant_init_wpa(wpa_s) < 0) // WPAステートマシン、コールバック(ctx)の設定
//
if (wpa_supplicant_driver_init(wpa_s) < 0) // 初期状態を設定
//
wpa_s->ctrl_iface = wpa_supplicant_ctrl_iface_init(wpa_s); // Network I/Fごとのソケットの初期化
//
}

ドライバの設定についてもう少し詳しく見ていきます。

wpa_driver[]にwpa_driver_xxx_ops(各ドライバのコールバック関数)の一覧があり、各opsの中身はdriverフォルダ以下のソースコードで実装されています。-Dオプションの内容から適切なopsを探し出して設定しています。


wpa_supplicant/wpa_supplicant.c

static int wpas_init_driver(struct wpa_supplicant *wpa_s,

struct wpa_interface *iface)
{
//
if (wpa_supplicant_set_driver(wpa_s, driver) < 0) // driverは-Dで指定した内容(nl80211など)
//
wpa_s->drv_priv = wpa_drv_init(wpa_s, wpa_s->ifname); // wpa_s->driver->init2を呼び出す(後述)
//
}

static int wpa_supplicant_set_driver(struct wpa_supplicant *wpa_s,
const char *name)
{
//
for (i = 0; wpa_drivers[i]; i++) {
if (os_strlen(wpa_drivers[i]->name) == len &&
os_strncmp(driver, wpa_drivers[i]->name, len) == // wpa_driversとdriver(nl80211等)を照合
0) {
/* First driver that succeeds wins */
if (select_driver(wpa_s, i) == 0) // 見つかれば関数を登録
return 0;
}
}
//
}

static int select_driver(struct wpa_supplicant *wpa_s, int i)
{
//
wpa_s->driver = wpa_drivers[i]; // 関数を登録
//
}


wpa_driver[]は以下で定義されています。

デフォルトだと一番上のwpa_driver_nl80211_opsが指定されます。

前述したwpas_init_driver()でwpa_s->driver->init2()が呼ばれていますが、このinit2()もopsで定義されています。


src/drivers/drivers.c

const struct wpa_driver_ops *const wpa_drivers[] =

{
#ifdef CONFIG_DRIVER_NL80211
&wpa_driver_nl80211_ops,
#endif /* CONFIG_DRIVER_NL80211 */
//
};


動作開始

最後にアプリと結合(ATTACH)して、イベントループを開始します。

main()から呼ばれるwpa_supplicant_run()で処理を行います。

wpa_cliなどのアプリからの結合が終わればイベントループを開始します。


wpa_supplicant/wpa_supplicant.c

int wpa_supplicant_run(struct wpa_global *global)

{
//
if (global->params.daemonize &&
(wpa_supplicant_daemon(global->params.pid_file) || // -Bオプションがあればデーモンとする
//
if (global->params.wait_for_monitor) {
for (wpa_s = global->ifaces; wpa_s; wpa_s = wpa_s->next)
if (wpa_s->ctrl_iface && !wpa_s->p2p_mgmt)
wpa_supplicant_ctrl_iface_wait( // アプリとの結合待ち(後述)
wpa_s->ctrl_iface);
}
//
eloop_run(); // イベントループの開始
//
}

以下のwpa_supplicant_ctrl_iface_wait()にてアプリからのATTACHコマンドを待ちます。ソケットから最初のコマンドを受け取り、ATTACHであればアプリと結合します。


wpa_supplicant/ctrl_iface_unix.c

void wpa_supplicant_ctrl_iface_wait(struct ctrl_iface_priv *priv)

{
//
for (;;) {
//
eloop_wait_for_read_sock(priv->sock);

res = recvfrom(priv->sock, buf, sizeof(buf) - 1, 0, // ソケットからコマンドを受け取る
(struct sockaddr *) &from, &fromlen);
//
if (os_strcmp(buf, "ATTACH") == 0) { // ATTACH以外受け付けない
/* handle ATTACH signal of first monitor interface */
if (!wpa_supplicant_ctrl_iface_attach(&priv->ctrl_dst,
&from, fromlen,
0)) {
if (sendto(priv->sock, "OK\n", 3, 0, // ATTACH成功ならOKを返す
(struct sockaddr *) &from, fromlen) <
//
}



まとめ

基本的にwpa_supplicantはアプリ又はドライバからのイベント駆動のプロセスなので、起動中にイベントループの初期化、アプリ・ドライバとの結合部分の初期化をするものだと思えば良いかもしれません。起動の処理中で出てきたイベントループ、wpa_sといった状態変数はスキャン、接続などの動作でも今後出てくるかと思います。

以上がwpa_supplicantの起動処理の説明となります。

記事をお読み頂きありがとうございました。