wifi配置工具iw原始碼解析
1、簡單的nl80211程式
iw的原始碼主體在iw.c檔案裡,其他檔案都是對iw相關的命令選項的實現。
在iw-4.9版本中,iw.c原始碼有586行,並不是很多,如果去掉程式碼中的引數解析部分和命令選項匹配部分,就可以得到iw的最核心的程式碼,如下面的程式碼所示。
/**
* 該程式使用nl80211命令從核心中讀取wlan0介面資訊,
* 然後在回撥函式中解析資訊,打印出wlan0的介面型別。
*/
#include "netlink/netlink.h"
#include "netlink/genl/genl.h"
#include "netlink/genl/ctrl.h"
#include <net/if.h>
//從iw複製過來
#include "nl80211.h"
static int expected_id;
static int nl_callback(struct nl_msg* msg, void* arg)
{
struct nlmsghdr* ret_hdr = nlmsg_hdr(msg);
struct nlattr *tb_msg[NL80211_ATTR_MAX + 1];
if (ret_hdr->nlmsg_type != expected_id)
{
// what is this??
return NL_STOP;
}
struct genlmsghdr *gnlh = (struct genlmsghdr*) nlmsg_data(ret_hdr);
nla_parse(tb_msg, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0), NULL);
if (tb_msg[NL80211_ATTR_IFTYPE]) {
int type = nla_get_u32(tb_msg[NL80211_ATTR_IFTYPE]);
printf("Type: %d", type);
}
}
int main(int argc, char** argv)
{
int ret;
//給socket分配空間
struct nl_sock* sk = nl_socket_alloc();
//連線核心的Generic Netlink
genl_connect(sk);
//獲取nl80211的驅動ID
expected_id = genl_ctrl_resolve(sk, "nl80211");
//關聯回撥函式
nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM,
nl_callback, NULL);
//宣告一個netlink訊息結構體nl_msg,並分配記憶體空間
struct nl_msg* msg = nlmsg_alloc();
//設定nl80211的命令,命令型別在nl80211.h定義
//這裡NL80211_CMD_GET_INTERFACE是獲取一個介面的配置資訊
enum nl80211_commands cmd = NL80211_CMD_GET_INTERFACE;
int ifIndex = if_nametoindex("wlan0");
int flags = 0;
//向msg變數中填充資料
genlmsg_put(msg, 0, 0, expected_id, 0, flags, cmd, 0);
//新增msg訊息的屬性
NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, ifIndex);
//將msg訊息傳送至核心中
ret = nl_send_auto_complete(sk, msg);
//這個函式會一直阻塞,直到該netlink socket得到返回值,
//然後自動呼叫回撥函式nl_callback
nl_recvmsgs_default(sk);
return 0;
nla_put_failure:
nlmsg_free(msg);
return 1;
}
上面這個程式與iw的整體執行流程是一樣的,首先與核心建立一個“nl80211”的netlink套接字連線,然後構造一個netlink訊息結構,向其填入命令、屬性、網絡卡介面等資訊,通過netlink套接字傳送至核心,等待接收返回資料,最後使用回撥函式解析返回資料。
2、對section的巧妙使用
iw程式針對各種型別的命令編寫了對應的.c檔案,每個命令的.c檔案是一個獨立模組。與常見的程式結構不同,iw中並沒有使用.h標頭檔案宣告函式,主檔案iw.c也沒有顯示宣告外部函式,那它是怎樣實現對其他檔案中函式的呼叫的呢?
在每個.c檔案中都可以看到幾個command巨集的應用,現在一步步將這個巨集展開。例如如下的巨集:
COMMAND(station, dump, "[-v]",
NL80211_CMD_GET_STATION, NLM_F_DUMP, CIB_NETDEV, handle_station_dump,
"List all stations known, e.g. the AP on managed interfaces");
第一步展開:
__COMMAND(&(__station_station), dump, "dump", "[-v]", NL80211_CMD_GET_STATION, NLM_F_DUMP, 0, CIB_NETDEV, handle_station_dump, "List all stations known, e.g. the AP on managed interfaces", NULL)
第二步展開:
static struct cmd
__cmd_dump_handle_station_dump_NL80211_CMD_GET_STATION_CIB_NETDEV_0
__attribute__((used)) __attribute__((section("__cmd"))) = {
.name = ("dump"),
.args = ("[-v]"),
.cmd = (NL80211_CMD_GET_STATION),
.nl_msg_flags = (NLM_F_DUMP),
.hidden = (0),
.idby = (CIB_NETDEV),
.handler = (handle_station_dump),
.help = ( "List all stations known, e.g. the AP on managed interfaces"),
.parent = &(__station_station),
.selector = (NULL),
}
可以看到,COMMAND最終聲明瞭一個cmd結構體變數,這個結構體變數使用了gcc編譯屬性__attribute__。__attribute__((used))指示編譯器在物件檔案中保留變數為靜態變數,不進行任何空間優化;__attribute__((section(“__cmd”)))指示編譯器將這個變數的記憶體空間位置放置在生成檔案的”__cmd”這個段中。
這樣就明白了,在iw程式的記憶體空間的靜態變數區有一個“__cmd”段,會順序儲存使用COMMAND巨集定義的cmd結構體變數。
cmd結構體有兩個重要的成員,一個是
const enum nl80211_commands cmd
它定義了這個cmd的nl80211命令,會填入到傳送至核心的nl_msg結構體訊息中,核心接收到這個訊息,根據命令和相關引數,返回資料。
還有一個就是:
int (*handler)(struct nl80211_state *state, struct nl_msg *msg, int argc, char **argv, enum id_input id);
這個函式並沒有特定的用法,大多情況下是用於註冊回撥函式,當netlink返回資料後,就會呼叫所註冊的回撥函式解析資料。
每個命令的.c檔案中都會使用COMMAND、TOPLEVEL這樣的巨集來定義該命令的cmd結構體變數,這些變數在同一塊儲存區域順序儲存,主程式只要從這個儲存區域的開始位置一個個提取變數,將cmd結構體的name與使用者的命令引數匹配,匹配成功,就將cmd結構體的cmd、nl_msg_flags、idby 填入nl_msg訊息中發給核心,最後使用handler註冊的回撥函式。iw程式正是這樣做的。
for_each_cmd(cmd) {
if (!cmd->handler)
continue;
if (cmd->parent != sectcmd)
continue;
/*
* ignore mismatch id by, but allow WDEV
* in place of NETDEV
*/
if (cmd->idby != command_idby &&
!(cmd->idby == CIB_NETDEV &&
command_idby == CIB_WDEV))
continue;
if (strcmp(cmd->name, command))
continue;
if (argc > 1 && !cmd->args)
continue;
match = cmd;
break;
}
for_each_cmd是一個巨集,其定義如下:
#define for_each_cmd(_cmd) \
for (_cmd = &__start___cmd; _cmd < &__stop___cmd; \
_cmd = (const struct cmd *)((char *)_cmd + cmd_size))
所以for_each_cmd(cmd)展開就是
for (cmd= &__start___cmd; cmd < &__stop___cmd; cmd = (const struct cmd *)((char *)cmd+ cmd_size))
GCC連結器會以section的名稱”name”自動生成符號__start_”name”和__end_”name”,分別指示這個section儲存區域的開始位置和結束位置,所以就有__start___cmd和__end___cmd。