wifi配置工具iw源码解析

iw是一个基于nl80211接口的无线配置工具,用于替代原先基于wext接口的iwconfig。iw源码可以在网址 https://www.kernel.org/pub/software/network/iw/ 获取,或者使用git命令从http://git.kernel.org/?p=linux/kernel/git/jberg/iw.git. 中下载。

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。

猜你喜欢

转载自blog.csdn.net/u010503121/article/details/78183666
今日推荐