wifi scan
netlink协议
Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口
不同于ioctl的是,ioctl只能由应用将消息单向发往内核,netlink则完全支持双工通信
netlink是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息
基于socket的通信
Netlink的通道是通过Family来组织的,但随着使用越来越多,Family ID已经不够分配了,所以才有了Generic Netlink
Generic Netlink其实是对Netlink报文中的数据进行了再次封装
依赖库
libnl-3.so //netlink
libnl-genl-3.so //Generic Netlink
结构
消息回调函数的类型
enum nl_cb_kind {
NL_CB_DEFAULT, //默认回调处理函数
NL_CB_VERBOSE, //默认处理函数,在default的基础上会将错误信息、无效信息等打印到stderr,且nl_cb_set()或者nl_cl_err()中的arg参数提供FILE*替代stderr
NL_CB_DEBUG, //默认处理函数,在VERBOSE的基础上打印所有信息到终端
NL_CB_CUSTOM, //用户自定义处理函数
__NL_CB_KIND_MAX
};
回调函数通常返回值
enum nl_cb_action {
NL_OK, //表示处理正常。
NL_SKIP, //表示停止当前netlink消息分析,转而去分析接收buffer中下一条netlink消息(消息分 片的情况)。
NL_STOP, //表示停止此次接收buffer中的消息分析。
};
type类型:处理底层不同netlink消息的情况
enum nl_cb_type {
NL_CB_VALID, //有效消息
NL_CB_FINISH, //multipart消息结尾
NL_CB_OVERRUN, //数据丢失报告
NL_CB_SKIPPED, //跳过处理消息
NL_CB_ACK, //确认消息
NL_CB_MSG_IN, //所有接收到的消息
NL_CB_MSG_OUT, //所有发出的消息,除nl_sendto()外
NL_CB_INVALID, //无效消息
NL_CB_SEQ_CHECK,
NL_CB_SEND_ACK,
NL_CB_DUMP_INTR,
__NL_CB_TYPE_MAX
};
流程说明
对于从user to kernel的通讯,driver必须先向内核注册一个struct genl_family,并且注册一些cmd的处理函数。这些cmd是跟某个family关联起来的。注册family的时候我们可以让内核自动为这个family分配一个ID。每个family都有一个唯一的ID,其中ID号0x10是被内核的nlctrl family所使用。当注册成功以后,如果user program向某个family发送cmd,那么内核就会回调对应cmd的处理函数。对于user program,使用前,除了要创建一个socket并绑定地址以外,还需要先通过family的名字获取family的ID。有了family的ID以后,才能向该family发送cmd
对于从kernel to user的通讯,采用的是广播的形式,只要user program监听了,都能收到。但是同样的,user program在监听前,也必须先查询到family的ID
流程
init:
wifiscan->ifname = strdup(IFNAME);
wifiscan->ifindex = if_nametoindex(wifiscan->ifname);
nl80211_init_nl_global()
-->nl_cb_alloc()
/*
申请回调函数
wifiscan->nl_cb = nl_cb_alloc(NL_CB_DEFAULT); //NL_CB_DEFAULT:默认处理handler类型
calloc空间,最终调用nl_cb_set()将回调函数及其参数按顺序放入nl_recvmsg_msg_cb_t cb_set[]中
*/
-->nl_create_handle() //创建nl_cb对应的nl_handle
/* wifiscan->nl = nl_create_handle(wifiscan->nl_cb, "nl"); */
-->nl80211_handle_alloc(cb)
/* struct nl_handle *handle = nl80211_handle_alloc(cb); */
-->__alloc_socket(cb);
/*
struct nl_sock *sk;
申请空间并初始化sk的参数,诸如s_local.nl_family=AF_NETLINK,s_peer,s_local.nl_pid,s_cb等信息
ps:nl_handle即为nl_sockc
*/
-->genl_connect(handle)
-->nl_connect(handle, NETLINK_GENERIC);
/*
sk即handle,protocol即NETLINK_GENERIC
sk->s_fd = socket(AF_NETLINK, SOCK_RAW | flags, protocol);
bind(sk->s_fd, (struct sockaddr*) &sk->s_local,sizeof(sk->s_local)) //绑定本地信息
*/
-->genl_ctrl_resolve() //向内核发命令,根据family name "nl80211" 获得family id (将nl连接到 内核中的nl80211模块)
/* wifiscan->nl80211_id = genl_ctrl_resolve(wifiscan->nl, "nl80211"); */
-->nl_create_handle() //创建接收消息的event socket
/* wifiscan->nl_event = nl_create_handle(wifiscan->nl_cb, "event"); */
-->nl_socket_set_nonblocking(wifiscan->nl_event); //设置该handle为非阻塞模式(fcntl)
-->nl_get_multicast_id() //将nl_event加入到组播组scan
/* ret = nl_get_multicast_id(wifiscan, "nl80211", "scan"); */
-->struct nl_msg *msg = nlmsg_alloc() //申请一个nl_msg结构用于向内核发送控制消息
-->genlmsg_put() //Add Generic Netlink headers to Netlink message
/*
void *genlmsg_put(struct nl_msg *msg, uint32_t port, uint32_t seq, int family, int hdrlen, int flags, uint8_t cmd, uint8_t version)
genlmsg_put(msg, 0, 0, genl_ctrl_resolve(wifiscan->nl, "nlctrl"), 0, 0, CTRL_CMD_GETFAMILY, 0);
//port:0,表示发非内核;family为"nlctrl",表示该消息为控制消息;cmd:CTRL_CMD_GETFAMILY,消息索引;
*/
-->send_and_recv_msgs() //发送消息并接收返回
/* ret = send_and_recv_msgs(wifiscan, msg, family_handler, &res); */
-->struct nl_cb *cb = nl_cb_clone(wifiscan->nl_cb); //克隆一个nl_cb
-->nl_send_auto_complete(wifiscan->nl, msg)
/* 发送生成的帧到内核,内核收到后会执行该消息帧中填充的命令索引和参数,如wifi扫描*/
-->nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err); //注册出错回调