用户层调用nl80211的例子

用户层调用nl80211的例子,第1张

NL80211

nl80211 是基于 802.11 netlink 的用户空间接口,用于无线硬件的新 cfg80211 配置系统。它们一起旨在取代旧的无线扩展。nl80211的当前用户包括:

  • iw
  • crda
  • hostapd
  • wpa_supplicant (-Dnl80211)
Includes

首先,让我们添加必要的包括:

#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 的通信。我们遵循以下步骤:

  1. 我们分配一个 netlink 套接字(使用 nl_socket_alloc)
  2. 或者,我们可以设置套接字缓冲区大小(使用 nl_socket_set_buffer_size)
  3. 我们连接到通用的 netlink 套接字(使用 genl_connect)
  4. 我们要求内核将家族名称“nl80211”解析为家族 ID(使用 genl_ctrl_resolve)
  5. 我们分配了两个新的回调句柄(使用 nl_cb_alloc)
  6. 我们设置了一些回调(使用 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 中发生了什么:

  1. 我们分配一个netlink消息结构(使用nlmsg_alloc)
  2. 我们将通用的 netlink 标头添加到 netlink 消息(使用 genlmsg_put)
  3. 我们完成并传输 netlink 消息(使用 nl_send_auto)
  4. 我们从 netlink 套接字接收一组消息(使用 nl_recvmsgs)
  5. 我们发布 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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1325123.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-12
下一篇 2022-06-12

发表评论

登录后才能评论

评论列表(0条)

保存