1. 程式人生 > >wifi配置工具iw原始碼解析

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。