nl80211 是基于 802.11 netlink 的用户空间接口,用于无线硬件的新 cfg80211 配置系统。它们一起旨在取代旧的无线扩展。nl80211的当前用户包括:
- iw
- crda
- hostapd
- wpa_supplicant (-Dnl80211)
首先,让我们添加必要的包括:
#define _XOPEN_SOURCE 700
#include
#include
#include
#include
#include
#include
#include //lots of netlink functions
#include //genl_connect, genlmsg_put
#include
#include //genl_ctrl_resolve
#include //NL80211 definitions
Structs
让我们定义一些稍后需要的数据结构:
typedef struct {
int id;
struct nl_sock* socket;
struct nl_cb* cb1,* cb2;
int result1, result2;
} Netlink;
typedef struct {
char ifname[30];
int ifindex;
int signal;
int txrate;
} Wifi;
static struct nla_policy stats_policy[NL80211_STA_INFO_MAX + 1] = {
[NL80211_STA_INFO_INACTIVE_TIME] = { .type = NLA_U32 },
[NL80211_STA_INFO_RX_BYTES] = { .type = NLA_U32 },
[NL80211_STA_INFO_TX_BYTES] = { .type = NLA_U32 },
[NL80211_STA_INFO_RX_PACKETS] = { .type = NLA_U32 },
[NL80211_STA_INFO_TX_PACKETS] = { .type = NLA_U32 },
[NL80211_STA_INFO_SIGNAL] = { .type = NLA_U8 },
[NL80211_STA_INFO_TX_BITRATE] = { .type = NLA_NESTED },
[NL80211_STA_INFO_LLID] = { .type = NLA_U16 },
[NL80211_STA_INFO_PLID] = { .type = NLA_U16 },
[NL80211_STA_INFO_PLINK_STATE] = { .type = NLA_U8 },
};
static struct nla_policy rate_policy[NL80211_RATE_INFO_MAX + 1] = {
[NL80211_RATE_INFO_BITRATE] = { .type = NLA_U16 },
[NL80211_RATE_INFO_MCS] = { .type = NLA_U8 },
[NL80211_RATE_INFO_40_MHZ_WIDTH] = { .type = NLA_FLAG },
[NL80211_RATE_INFO_SHORT_GI] = { .type = NLA_FLAG },
};
Functions
我们定义我们的函数:
static int initNl80211(Netlink* nl, Wifi* w);
static int finish_handler(struct nl_msg *msg, void *arg);
static int getWifiName_callback(struct nl_msg *msg, void *arg);
static int getWifiInfo_callback(struct nl_msg *msg, void *arg);
static int getWifiStatus(Netlink* nl, Wifi* w);
initNl80211()
在我们的函数 initNl80211 中,我们初始化了与 Kernel 的通信。我们遵循以下步骤:
- 我们分配一个 netlink 套接字(使用 nl_socket_alloc)
- 或者,我们可以设置套接字缓冲区大小(使用 nl_socket_set_buffer_size)
- 我们连接到通用的 netlink 套接字(使用 genl_connect)
- 我们要求内核将家族名称“nl80211”解析为家族 ID(使用 genl_ctrl_resolve)
- 我们分配了两个新的回调句柄(使用 nl_cb_alloc)
- 我们设置了一些回调(使用 nl_cb_set)。
static int initNl80211(Netlink* nl, Wifi* w) {
nl->socket = nl_socket_alloc();
if (!nl->socket) {
fprintf(stderr, "Failed to allocate netlink socket.\n");
return -ENOMEM;
}
nl_socket_set_buffer_size(nl->socket, 8192, 8192);
if (genl_connect(nl->socket)) {
fprintf(stderr, "Failed to connect to netlink socket.\n");
nl_close(nl->socket);
nl_socket_free(nl->socket);
return -ENOLINK;
}
nl->id = genl_ctrl_resolve(nl->socket, "nl80211");
if (nl->id< 0) {
fprintf(stderr, "Nl80211 interface not found.\n");
nl_close(nl->socket);
nl_socket_free(nl->socket);
return -ENOENT;
}
nl->cb1 = nl_cb_alloc(NL_CB_DEFAULT);
nl->cb2 = nl_cb_alloc(NL_CB_DEFAULT);
if ((!nl->cb1) || (!nl->cb2)) {
fprintf(stderr, "Failed to allocate netlink callback.\n");
nl_close(nl->socket);
nl_socket_free(nl->socket);
return ENOMEM;
}
nl_cb_set(nl->cb1, NL_CB_VALID , NL_CB_CUSTOM, getWifiName_callback, w);
nl_cb_set(nl->cb1, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &(nl->result1));
nl_cb_set(nl->cb2, NL_CB_VALID , NL_CB_CUSTOM, getWifiInfo_callback, w);
nl_cb_set(nl->cb2, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &(nl->result2));
return nl->id;
}
finish_handler()
这是我们的finish_handler。
static int finish_handler(struct nl_msg *msg, void *arg) {
int *ret = arg;
*ret = 0;
return NL_SKIP;
}
finish_handler 将允许我们稍后接收来自内核的消息,如下所示:
while (nl->result1 > 0) { nl_recvmsgs(nlsocket, nl->cb1); }
while (nl->result2 > 0) { nl_recvmsgs(nlsocket, nl->cb2); }
getWifiName_callback()
这是我们的 getWifiName_callback。这里我们解析来自内核的消息,我们得到接口名称(wifi_iface)和它的索引(wifi_index)。如果您愿意,您可以使用 nl_msg_dump(msg, stdout) 查看原始消息。
static int getWifiName_callback(struct nl_msg *msg, void *arg) {
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct nlattr *tb_msg[NL80211_ATTR_MAX + 1];
//nl_msg_dump(msg, stdout);
nla_parse(tb_msg,
NL80211_ATTR_MAX,
genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0),
NULL);
if (tb_msg[NL80211_ATTR_IFNAME]) {
strcpy(((Wifi*)arg)->ifname, nla_get_string(tb_msg[NL80211_ATTR_IFNAME]));
}
if (tb_msg[NL80211_ATTR_IFINDEX]) {
((Wifi*)arg)->ifindex = nla_get_u32(tb_msg[NL80211_ATTR_IFINDEX]);
}
return NL_SKIP;
}
getWifiInfo_callback()
这是我们的getWifiInfo_callback。这里我们解析来自内核的消息,我们得到 wifi 信号 (wifi_signal) 和 txrate (wifi_bitrate)。如果您愿意,您可以使用 nl_msg_dump(msg, stdout) 查看原始消息。
static int getWifiInfo_callback(struct nl_msg *msg, void *arg) {
struct nlattr *tb[NL80211_ATTR_MAX + 1];
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
struct nlattr *sinfo[NL80211_STA_INFO_MAX + 1];
struct nlattr *rinfo[NL80211_RATE_INFO_MAX + 1];
//nl_msg_dump(msg, stdout);
nla_parse(tb,
NL80211_ATTR_MAX,
genlmsg_attrdata(gnlh, 0),
genlmsg_attrlen(gnlh, 0),
NULL);
if (!tb[NL80211_ATTR_STA_INFO]) {
fprintf(stderr, "sta stats missing!\n"); return NL_SKIP;
}
if (nla_parse_nested(sinfo, NL80211_STA_INFO_MAX,
tb[NL80211_ATTR_STA_INFO], stats_policy)) {
fprintf(stderr, "failed to parse nested attributes!\n"); return NL_SKIP;
}
if (sinfo[NL80211_STA_INFO_SIGNAL]) {
((Wifi*)arg)->signal = 100+(int8_t)nla_get_u8(sinfo[NL80211_STA_INFO_SIGNAL]);
}
if (sinfo[NL80211_STA_INFO_TX_BITRATE]) {
if (nla_parse_nested(rinfo, NL80211_RATE_INFO_MAX,
sinfo[NL80211_STA_INFO_TX_BITRATE], rate_policy)) {
fprintf(stderr, "failed to parse nested rate attributes!\n"); }
else {
if (rinfo[NL80211_RATE_INFO_BITRATE]) {
((Wifi*)arg)->txrate = nla_get_u16(rinfo[NL80211_RATE_INFO_BITRATE]);
}
}
}
return NL_SKIP;
}
getWifiInfo_callback()
现在让我们看看在我们的下一个函数 getWifiStatus 中发生了什么:
- 我们分配一个netlink消息结构(使用nlmsg_alloc)
- 我们将通用的 netlink 标头添加到 netlink 消息(使用 genlmsg_put)
- 我们完成并传输 netlink 消息(使用 nl_send_auto)
- 我们从 netlink 套接字接收一组消息(使用 nl_recvmsgs)
- 我们发布 netlink 消息参考(使用 nlmsg_free)
我们执行这些步骤两次:
a) 我们第一次使用 NL80211_CMD_GET_INTERFACE 命令标识符来获取无线接口名称和索引。
b) 第二次我们使用 NL80211_CMD_GET_STATION 命令标识符来获取信号强度和传输比特率。其原因是信号和比特率值仅相对于电台有意义。请注意,我们必须使用 nla_put_u32(msg2,NL80211_ATTR_IFINDEX, w->ifindex) 将接口索引放入消息中。
static int getWifiStatus(Netlink* nl, Wifi* w) {
nl->result1 = 1;
nl->result2 = 1;
struct nl_msg* msg1 = nlmsg_alloc();
if (!msg1) {
fprintf(stderr, "Failed to allocate netlink message.\n");
return -2;
}
genlmsg_put(msg1,
NL_AUTO_PORT,
NL_AUTO_SEQ,
nl->id,
0,
NLM_F_DUMP,
NL80211_CMD_GET_INTERFACE,
0);
nl_send_auto(nl->socket, msg1);
while (nl->result1 > 0) { nl_recvmsgs(nl->socket, nl->cb1); }
nlmsg_free(msg1);
if (w->ifindex < 0) { return -1; }
struct nl_msg* msg2 = nlmsg_alloc();
if (!msg2) {
fprintf(stderr, "Failed to allocate netlink message.\n");
return -2;
}
genlmsg_put(msg2,
NL_AUTO_PORT,
NL_AUTO_SEQ,
nl->id,
0,
NLM_F_DUMP,
NL80211_CMD_GET_STATION,
0);
nla_put_u32(msg2, NL80211_ATTR_IFINDEX, w->ifindex);
nl_send_auto(nl->socket, msg2);
while (nl->result2 > 0) { nl_recvmsgs(nl->socket, nl->cb2); }
nlmsg_free(msg2);
return 0;
}
main()
这是我们的 main() 函数。首先,我们使用 initNl80211 初始化通信,然后,我们在一个循环中连续调用 getWifiStatus(),每 1 秒一次,直到用户按下 ctrl + c:
int main(int argc, char **argv) {
Netlink nl;
Wifi w;
signal(SIGINT, ctrl_c_handler);
nl.id = initNl80211(&nl, &w);
if (nl.id < 0) {
fprintf(stderr, "Error initializing netlink 802.11\n");
return -1;
}
do {
getWifiStatus(&nl, &w);
printf("Interface: %s | signal: %d dB | txrate: %.1f MBit/s\n",
w.ifname, w.signal, (float)w.txrate/10);
sleep(1);
} while(keepRunning);
printf("\nExiting gracefully... ");
nl_cb_put(nl.cb1);
nl_cb_put(nl.cb2);
nl_close(nl.socket);
nl_socket_free(nl.socket);
printf("OK\n");
return 0;
}
该例子中只调用了NL80211_CMD_GET_INTERFACE和NL80211_CMD_GET_STATION 两个命令,其他nl80211命令可从include/uapi/linux/nl80211.h中查询
用于openwrt package的源代码:nl80211_info
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)