Hostapd2.11解析笔记_nl80211接口交互流程_消息收发细节解析

最近在调试Hostapd,尝试通过配置使能一个支持MLO的AP,不过不知道hostapd conf里面哪些选项开启后可以使能,所以对Hostapd做一个整体解析.

本篇分析介绍hostapd的netlink信息如何发送到内核、内核如何处理这些信息、处理后如何调用cfg80211,内核如何发送cfg80211信息,驱动如何处理cfg80211信息,最后如何start_ap。

Hostapd的nl命令收发函数:

cpp 复制代码
int send_and_recv(struct nl80211_global *global,
		  struct nl_sock *nl_handle, struct nl_msg *msg,
		  int (*valid_handler)(struct nl_msg *, void *),
		  void *valid_data,
		  int (*ack_handler_custom)(struct nl_msg *, void *),
		  void *ack_data,
		  struct nl80211_err_info *err_info)
{
	struct nl_cb *cb, *s_nl_cb;
	struct nl80211_ack_err_args err;
	int opt;

	if (!msg)
		return -ENOMEM;

	err.err = -ENOMEM;

	s_nl_cb = nl_socket_get_cb(nl_handle);
	cb = nl_cb_clone(s_nl_cb);
	nl_cb_put(s_nl_cb);
	if (!cb)
		goto out;

	/* try to set NETLINK_EXT_ACK to 1, ignoring errors */
	opt = 1;
	setsockopt(nl_socket_get_fd(nl_handle), SOL_NETLINK,
		   NETLINK_EXT_ACK, &opt, sizeof(opt));

	/* try to set NETLINK_CAP_ACK to 1, ignoring errors */
	opt = 1;
	setsockopt(nl_socket_get_fd(nl_handle), SOL_NETLINK,
		   NETLINK_CAP_ACK, &opt, sizeof(opt));

	err.err = nl_send_auto_complete(nl_handle, msg);
	if (err.err < 0) {
		wpa_printf(MSG_INFO,
			   "nl80211: nl_send_auto_complete() failed: %s",
			   nl_geterror(err.err));
		/* Need to convert libnl error code to an errno value. For now,
		 * just hardcode this to EBADF; the real error reason is shown
		 * in that error print above. */
		err.err = -EBADF;
		goto out;
	}

	err.err = 1;
	err.orig_msg = msg;
	err.err_info = err_info;

	nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err);
	nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err.err);
	if (ack_handler_custom) {
		struct nl80211_ack_ext_arg *ext_arg = ack_data;

		ext_arg->err = &err.err;
		nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM,
			  ack_handler_custom, ack_data);
	} else {
		nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err);
	}

	if (valid_handler)
		nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM,
			  valid_handler, valid_data);

	while (err.err > 0) {
		int res = nl_recvmsgs(nl_handle, cb);

		if (res == -NLE_DUMP_INTR) {
			/* Most likely one of the nl80211 dump routines hit a
			 * case where internal results changed while the dump
			 * was being sent. The most common known case for this
			 * is scan results fetching while associated were every
			 * received Beacon frame from the AP may end up
			 * incrementing bss_generation. This
			 * NL80211_CMD_GET_SCAN case tries again in the caller;
			 * other cases (of which there are no known common ones)
			 * will stop and return an error. */
			wpa_printf(MSG_DEBUG, "nl80211: %s; convert to -EAGAIN",
				   nl_geterror(res));
			err.err = -EAGAIN;
		} else if (res < 0) {
			wpa_printf(MSG_INFO,
				   "nl80211: %s->nl_recvmsgs failed: %d (%s)",
				   __func__, res, nl_geterror(res));
		}
	}
 out:
	nl_cb_put(cb);
	/* Always clear the message as it can potentially contain keys */
	nl80211_nlmsg_clear(msg);
	nlmsg_free(msg);
	return err.err;
}

其中涉及的nl函数及解析

cpp 复制代码
nl_socket_get_cb(nl_handle);
nl_cb_clone(s_nl_cb);
nl_cb_put(s_nl_cb);
nl_socket_get_fd(nl_handle), SOL_NETLINK,
nl_send_auto_complete(nl_handle, msg);
nl_geterror(err.err));
nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err);
nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err.err);
nl_recvmsgs(nl_handle, cb);
nl80211_nlmsg_clear(msg);
nlmsg_free(msg);
nla_put_u32

nl_socket_get_cb

用于获取nl_sock对应的回调函数

linbl还可为每个nl_sock设置消息处理回调函数,当该socket上收到消息后,就会回调此函数进行处理。 回调函数及其重写函数都封装在结构体 struct nl_cb中

cpp 复制代码
struct nl_cb{

   nl_recvmsg_msg_cb_t      cb_set[NL_CB_TYPE_MAX+1];

   void * cb_args[NL_CB_TYPE_MAX+1];

   nl_recvmsg_err_cb_t      cb_err;

   void *cb_err_arg;

   /** May be used to replace nl_recvmsgs with your own implementation in all internal calls to nl_recvmsgs. */

  int  (*cb_recvmsgs_ow)(struct nl_sock *,     struct nl_cb *);

   /** Overwrite internal calls to nl_recv, must return the number of octets read and allocate a buffer for the received data. */

  int   (*cb_recv_ow)(struct nl_sock *, struct sockaddr_nl *,   unsigned char **, struct ucred **);

   /** Overwrites internal calls to nl_send, must send the netlink message. */

  int    (*cb_send_ow)(struct nl_sock *,  struct nl_msg *);

  int   cb_refcnt;

};

设置回调:void nl_socket_set_cb(struct nl_sock *sk, struct nl_cb *cb);

获取该nl_sock设置的回调函数信息:struct nl_cb *nl_socket_get_cb(const struct nl_sock *sk);

nl_cb_clone

初始化时的函数,

struct nl_cb *nl_cb_alloc(enum nl_cb_kind kind);//分配新的nl_cb,返回新nl_cb地址或者NULL

struct nl_cb *nl_cb_clone(struct nl_cb *orig) ; //根据给定nl_cb复制新的nl_cb

nl_cb_put

nl_socket_get_fd

获取nl_sock对应的文件描述符fd

介于netlink采用BSD socket,因此每个socket对应一个文件描述符

#include <netlink/socket.h>

int nl_socket_get_fd(const struct nl_sock *sk);

int nl_socket_set_nonblocking(const struct nl_sock *sk); //若socket用于接收notification,则最好设为非阻塞并定期查询

nl_send_auto_complete

一个nl message发送函数。

(1) nl_send_auto_complete()将 自动填充netlink message header,并根据nl_sock里面的选项或地址设置完成寻址,而后调用 nl_send()。

ps:所有nl_send_auto()内部调用都会使用nl_send()传输信息。如果nl_send()不能满足要求,可以调用nl_cb_overwrite_send()重写自己的send函数。

(2) nl_send()将netlink message插入到struct iovec结构体中,而后调用 nl_send_iovec();

ps: struct iovec结构体定义一个向量元素, 对于每一个传输的元素,指针成员iov_base指向一个缓冲区,这个缓冲区是存放的是readv所接收的数据或是writev将要发送的数据。成员iov_len在各种情况下分别确定了接收的最大长度以及实际写入的长度。

struct iovec{

void *iov_base; /* Pointer to data. */

size_t iov_len; /* Length of data. */

};

  • int readv(int fd, const struct iovec *vector, int count);

  • int writev(int fd, const struct iovec *vector, int count);

(3)nl_send_iovec()将利用nl_msg填充msghdr结构体,并检查寻址状态,而后调用nl_sendmsg()继续发送数据

(4)nl_sendmsg()调用sendmsg()下发数据

通过netlink socket发送raw data:

int nl_sendto(struct nl_sock *sock, void *buf, size_t size);

nl_geterror

nl_cb_err

给nl_cb的nl_cb_kind(也就是某一个报错MSG) 增加错误回调函数

设置error message callback hook:

typedef int(* nl_recvmsg_err_cb_t)(struct sockaddr_nl *nla, struct nlmsgerr *nlerr, void *arg);

int nl_cb_err(struct nl_cb *cb, enum nl_cb_kind kind, nl_recvmsg_err_cb_t func, void * arg); //设置错误callback

nl_cb_set

给nl_cb的nl_cb_kind(也就是某一个NL MSG) 增加回调函数

设置callback:

typedef int (*nl_recvmsg_msg_cb_t)(struct nl_msg *msg, void *arg);

int nl_cb_set(struct nl_cb *cb, enum nl_cb_type type, enum nl_cb_kind kind, nl_recvmsg_msg_cb_t func, void *arg); //设置给定kind_type的callback

nl_recvmsgs

就是一个接收NLMSG的函数

libnl调用nl_recvmsgs_default()按照socket格式规定调用nl_recvmsgs() 接收数据。

nl_recvmsgs()在netlink message到来前保持阻塞,可以通过nl_cb_overwrite_recvmsgs()重写。nl_recvmsgs()调用recvmsgs()接收数据。

nl80211_nlmsg_clear

nlmsg_free

对应alloc的free

/*释放*/

void nlmsg_free(struct nl_msg *msg);

nla_put_u32

int nla_put_u32(struct nl_msg *msg, int attrtype, uint32_t value);

把value填充到nl msg里面

cpp 复制代码
struct nlattr *nla_reserve(struct nl_msg *msg, int attrtype, int len);

为netlink消息添加属性的接口是基于常规消息构造接口的。它假设消息头和最终协议头已经添加到消息中。函数nla_reserve()在消息的末尾添加一个属性头,并为len字节的有效负载保留空间。该函数返回一个指向消息内部属性有效负载部分的指针。在属性的末尾添加填充,以确保下一个属性正确对齐。

cpp 复制代码
int nla_put(struct nl_msg *msg, int attrtype, int attrlen, const void *data);

函数nla_put()以nla_reserve()为基础,但接受一个额外的指针数据,该指针数据指向包含属性有效负载的缓冲区。它将自动将缓冲区复制到消息中。

参考:

Netlink协议详解-CSDN博客

相关推荐
时光不负追梦人11 分钟前
谈谈对spring IOC的理解,原理和实现
java·后端·spring
程序猿ZhangSir20 分钟前
Redis 和 MySQL双写一致性的更新策略有哪些?常见面试题深度解答。
java·数据库·spring boot·redis·mysql·缓存·mybatis
黎明晓月23 分钟前
Linux 上使用 Docker 部署 Kafka 集群
linux·docker·kafka
flying jiang24 分钟前
HttpServletRequest
java·网络·websocket·http
清风~徐~来27 分钟前
【Linux】应用层协议 HTTP
linux·运维·http
my_realmy36 分钟前
蓝桥杯真题_小蓝和小桥的讨论
java·python·算法·职场和发展·蓝桥杯·intellij-idea
柳如烟@37 分钟前
LVS-NAT 负载均衡与共享存储配置
linux·负载均衡·lvs
weixin_4222013041 分钟前
在IDEA中快速注释所有console.log
java·ide·intellij-idea·console
hweiyu0041 分钟前
【IntelliJ IDEA导出WAR包教程】
java·ide·intellij-idea·idea·intellij idea
Forget_85501 小时前
Linux的例行性工作
linux·运维·服务器