wifi配置工具iw源码解析
2017-10-09 16:10
246 查看
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. 中下载。
在iw-4.9版本中,iw.c源代码有586行,并不是很多,如果去掉代码中的参数解析部分和命令选项匹配部分,就可以得到iw的最核心的代码,如下面的代码所示。
上面这个程序与iw的整体执行流程是一样的,首先与内核建立一个“nl80211”的netlink套接字连接,然后构造一个netlink消息结构,向其填入命令、属性、网卡接口等信息,通过netlink套接字发送至内核,等待接收返回数据,最后使用回调函数解析返回数据。
在每个.c文件中都可以看到几个command宏的应用,现在一步步将这个宏展开。例如如下的宏:
第一步展开:
第二步展开:
可以看到,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是一个宏,其定义如下:
所以for_each_cmd(cmd)展开就是
GCC链接器会以section的名称”name”自动生成符号__start_”name”和__end_”name”,分别指示这个section存储区域的开始位置和结束位置,所以就有__start___cmd和__end___cmd。
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。
相关文章推荐
- 用C语言实现解析简单配置文件的小工具
- struct2源码解读(4)之配置文件具体解析过程
- 编写自己的代码生成工具二:解析配置文件
- Etherlab源码解析----同步管理器SM配置(过程数据)
- Tomcat源码研究之配置文件解析
- Spring技术内幕5——利用property-placeholder节点配置Bean属性源码解析
- STL源码解析-02配置器-03自定义配置器
- Spring IoC源码解析(一)——配置文件加载和IoC容器初始化
- monit 源码之配置文件解析
- Spring源码解析——配置文件读取相关的类
- mybatis源码-解析配置文件(四-1)之配置文件Mapper解析(cache)
- Redis源码解析(十六)--- config配置文件
- spring源码解析之默认配置文件名/WEB-INF/applicationContext.xml
- WIFI P2P (WIFI直连)源码解析
- 基于sax的xml解析 含源码 各种工具类(一)
- IPerf——网络测试工具介绍与源码解析(4)
- ibatis源码学习2_初始化和配置文件解析
- XmlBeanDefinitionReader-----Spring源码解析 配置文件装载与解析
- spring源码(3)之解析配置文件的过程
- Spring源码阅读之-自定义配置的解析