iw是Linux比较推荐的无线网络配置工具,它基于新的交互框架nl80211,是用于取代iwconfig等以前基于wext的配置工具。
官网链接【 https://wireless.wiki.kernel.org/en/users/documentation/iw 】
作者最近使用到了iw里面的一些小众功能,这里笔记下它的基本框架吧,看下它是如何和驱动交互的。
PS:作者没有使用linux进行实际的编译和执行,因此仅仅通过代码阅读来分析,而cmd的注册使用了段操作,作者分析可能有问题,这里先说明下~后面用linux编译后再更新~~
从官网可以下载最新的iw源码,作者这里是iw4.3,解压缩。
首先进入iw.c中,这里有main函数的入口,简单笔记下:
int main(int argc, char **argv) { struct nl80211_state nlstate; int err; const struct cmd *cmd = NULL; /* 首先计算下一个cmd的大小是多少,便于后续偏移 */ /* calculate command size including padding */ cmd_size = abs((long)&__section_set - (long)&__section_get); /* strip off self */ argc--; argv0 = *argv++; if (argc > 0 && strcmp(*argv, "--debug") == 0) { iw_debug = 1; argc--; argv++; } if (argc > 0 && strcmp(*argv, "--version") == 0) { version(); return 0; } /* need to treat "help" command specially so it works w/o nl80211 */ if (argc == 0 || strcmp(*argv, "help") == 0) { usage(argc - 1, argv + 1); return 0; } /* 初始化用户态的nl80211接口 */ err = nl80211_init(&nlstate); if (err) return 1; /* 由于iw命令支持省略dev/phy 关键字,因此解析起来会复杂一点 */ if (strcmp(*argv, "dev") == 0 && argc > 1) { argc--; argv++; err = __handle_cmd(&nlstate, II_NETDEV, argc, argv, &cmd); } else if (strncmp(*argv, "phy", 3) == 0 && argc > 1) { if (strlen(*argv) == 3) { argc--; argv++; err = __handle_cmd(&nlstate, II_PHY_NAME, argc, argv, &cmd); } else if (*(*argv + 3) == '#') err = __handle_cmd(&nlstate, II_PHY_IDX, argc, argv, &cmd); else goto detect; } else if (strcmp(*argv, "wdev") == 0 && argc > 1) { argc--; argv++; err = __handle_cmd(&nlstate, II_WDEV, argc, argv, &cmd); } else { int idx; enum id_input idby = II_NONE; detect: if ((idx = if_nametoindex(argv[0])) != 0) idby = II_NETDEV; else if ((idx = phy_lookup(argv[0])) >= 0) idby = II_PHY_NAME; err = __handle_cmd(&nlstate, idby, argc, argv, &cmd); } /* 出错了,就打印下help文档 */ if (err == 1) { if (cmd) usage_cmd(cmd); else usage(0, NULL); } else if (err < 0) fprintf(stderr, "command failed: %s (%d)\n", strerror(-err), err); /* 关掉用户态的nl80211接口 */ nl80211_cleanup(&nlstate); return err; }
整个iw框架就是这样,初始化nl接口,解析并处理命令,然后关闭nl接口。
这里继续分析,首先看下这里是如何初始化nl接口的。
iw.c中继续分析,
/* 初始化用户态的nl80211接口 */ static int nl80211_init(struct nl80211_state *state) { int err; /* 初始化socket 注意这里其实是genetlink子框架 */ state->nl_sock = nl_socket_alloc(); if (!state->nl_sock) { fprintf(stderr, "Failed to allocate netlink socket.\n"); return -ENOMEM; } nl_socket_set_buffer_size(state->nl_sock, 8192, 8192); if (genl_connect(state->nl_sock)) { fprintf(stderr, "Failed to connect to generic netlink.\n"); err = -ENOLINK; goto out_handle_destroy; } /* 看下内核态有没有对应的已经注册的genetlink框架 */ state->nl80211_id = genl_ctrl_resolve(state->nl_sock, "nl80211"); if (state->nl80211_id < 0) { fprintf(stderr, "nl80211 not found.\n"); err = -ENOENT; goto out_handle_destroy; } return 0; out_handle_destroy: nl_socket_free(state->nl_sock); return err; }
这里我们插播一段代码,在Linux2.6.32中,查看nl80211.c,可以发现如下代码:
/* the netlink family */ static struct genl_family nl80211_fam = { .id = GENL_ID_GENERATE, /* don't bother with a hardcoded ID */ .name = "nl80211", /* have users key off the name instead */ .hdrsize = 0, /* no private header */ .version = 1, /* no particular meaning now */ .maxattr = NL80211_ATTR_MAX, .netnsok = true, }; int nl80211_init(void) { int err; err = genl_register_family_with_ops(&nl80211_fam, nl80211_ops, ARRAY_SIZE(nl80211_ops)); 。。。。 }
可以看到,linux无线网络驱动中已经注册了内核中的nl80211操作接口,名字为”nl80211″,那么当初始化用户态接口时,就可以通过名字来定位到内核中的接口了。
下面简单的分析命令的解析和处理,首先看下iw命令是怎么被注册的,
查看iw.h代码,
/* iw command 命令结构体 */ struct cmd { const char *name; const char *args; const char *help; const enum nl80211_commands cmd; int nl_msg_flags; int hidden; const enum command_identify_by idby; /* * The handler should return a negative error code, * zero on success, 1 if the arguments were wrong * and the usage message should and 2 otherwise. */ int (*handler)(struct nl80211_state *state, struct nl_msg *msg, int argc, char **argv, enum id_input id); const struct cmd *(*selector)(int argc, char **argv); const struct cmd *parent; }; #define ARRAY_SIZE(ar) (sizeof(ar)/sizeof(ar[0])) #define DIV_ROUND_UP(x, y) (((x) + (y - 1)) / (y)) /* 下面的几种宏初始化了cmd结构体变量 */ #define __COMMAND(_section, _symname, _name, _args, _nlcmd, _flags, _hidden, _idby, _handler, _help, _sel)\ static struct cmd \ __cmd ## _ ## _symname ## _ ## _handler ## _ ## _nlcmd ## _ ## _idby ## _ ## _hidden\ __attribute__((used)) __attribute__((section("__cmd"))) = { \ .name = (_name), \ .args = (_args), \ .cmd = (_nlcmd), \ .nl_msg_flags = (_flags), \ .hidden = (_hidden), \ .idby = (_idby), \ .handler = (_handler), \ .help = (_help), \ .parent = _section, \ .selector = (_sel), \ } #define __ACMD(_section, _symname, _name, _args, _nlcmd, _flags, _hidden, _idby, _handler, _help, _sel, _alias)\ __COMMAND(_section, _symname, _name, _args, _nlcmd, _flags, _hidden, _idby, _handler, _help, _sel);\ static const struct cmd *_alias = &__cmd ## _ ## _symname ## _ ## _handler ## _ ## _nlcmd ## _ ## _idby ## _ ## _hidden #define COMMAND(section, name, args, cmd, flags, idby, handler, help) \ __COMMAND(&(__section ## _ ## section), name, #name, args, cmd, flags, 0, idby, handler, help, NULL) #define COMMAND_ALIAS(section, name, args, cmd, flags, idby, handler, help, selector, alias)\ __ACMD(&(__section ## _ ## section), name, #name, args, cmd, flags, 0, idby, handler, help, selector, alias) #define HIDDEN(section, name, args, cmd, flags, idby, handler) \ __COMMAND(&(__section ## _ ## section), name, #name, args, cmd, flags, 1, idby, handler, NULL, NULL) /* 定义了顶层命令 */ #define TOPLEVEL(_name, _args, _nlcmd, _flags, _idby, _handler, _help) \ struct cmd \ __section ## _ ## _name \ __attribute__((used)) __attribute__((section("__cmd"))) = { \ .name = (#_name), \ .args = (_args), \ .cmd = (_nlcmd), \ .nl_msg_flags = (_flags), \ .idby = (_idby), \ .handler = (_handler), \ .help = (_help), \ } #define SECTION(_name) \ struct cmd __section ## _ ## _name \ __attribute__((used)) __attribute__((section("__cmd"))) = { \ .name = (#_name), \ .hidden = 1, \ } #define DECLARE_SECTION(_name) \ extern struct cmd __section ## _ ## _name; extern const char iw_version[]; extern int iw_debug; int handle_cmd(struct nl80211_state *state, enum id_input idby, int argc, char **argv); 。。。。。 。。。。。 DECLARE_SECTION(set); DECLARE_SECTION(get);
(以下分析可能有错误!!)通过.h中的宏可以看到,iw定义了__cmd段,并将各种命令响应定义到了该段中,由于作者没有使用Linux,这里就懒一下,不在分析section中的排布和宏的解析了。不过需要注意的是iw的命令是树形结构的,分为两层,因此很多命令是有父命令的。这里随便举个例子吧,在interface.c中,
。。。。。 SECTION(interface); 。。。。。 COMMAND(interface, add, "<name> type <type> [mesh_id <meshid>] [4addr on|off] [flags <flag>*] [addr <mac-addr>]", NL80211_CMD_NEW_INTERFACE, 0, CIB_PHY, handle_interface_add, "Add a new virtual interface with the given configuration.\n" IFACE_TYPES "\n\n" "The flags are only used for monitor interfaces, valid flags are:\n" VALID_FLAGS "\n\n" "The mesh_id is used only for mesh mode."); COMMAND(interface, add, "<name> type <type> [mesh_id <meshid>] [4addr on|off] [flags <flag>*] [addr <mac-addr>]", NL80211_CMD_NEW_INTERFACE, 0, CIB_NETDEV, handle_interface_add, NULL);
.
发表评论