IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> netlink 读取数据流程 -> 正文阅读

[系统运维]netlink 读取数据流程

前言

本篇缘起是我要实现类似ss -i 的功能,通过netlink获取系统中的所有socket信息
代码如下

//发送tcpdiag的数据
sendto(netlinkdf, msg,...)
//sleep足够时间,使得内核处理
//因为每次都读取少部分数据,以为内核没来得及处理
sleep(10)

char buffer[10000]
len = recvfrom(buffer, sizeof(buffer), 0);

上面流程中,recvfrom 返回的字节大小,小于buffer大小,理应我们认为是收全了的,但是实际情况就是,buffer中的数据,实际上只有非常小的一部分socket信息

recvfrom/recvmsg

首先还是要看下recvmsg 做了哪些事情,注意recvfrom 最后还是调用了recvmsg

SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size,
		unsigned int, flags, struct sockaddr __user *, addr,
		int __user *, addr_len)
{
    ...
	msg.msg_control = NULL;
	msg.msg_controllen = 0;
	msg.msg_iovlen = 1;
	msg.msg_iov = &iov;
	iov.iov_len = size;
	iov.iov_base = ubuf;
	msg.msg_name = (struct sockaddr *)&address;
	msg.msg_namelen = sizeof(address);

	err = sock_recvmsg(sock, &msg, size, flags);

	if (err >= 0 && addr != NULL) {
		err2 = move_addr_to_user(&address,
					 msg.msg_namelen, addr, addr_len);
		if (err2 < 0)
			err = err2;
	}
	fput_light(sock->file, fput_needed);
out:
	return err;
}

上篇 讲过,netlink注册的一些函数调用栈是这样的
sock_recvmsg->netlink_recvmsg
核心就是netlink_recvmsg ,核心流程如下

static int netlink_recvmsg(struct kiocb *kiocb, struct socket *sock,
{
	struct sock *sk = sock->sk;
	struct netlink_sock *nlk = nlk_sk(sk);
	//从netlink的socket中,获取接收队列中的数据,这个队列其实就是socket信息
	//socket信息,首先是当你调用sendto(msg)发送tcp_diag请求的时候
	//就已经同步的放到了netlink的socket中了
	skb = skb_recv_datagram(sk, flags, noblock, &err);
	//copy到用户态
	err = skb_copy_datagram_iovec(data_skb, 0, msg->msg_iov, copied);
	//MSG_TRUNC告诉内核recvfrom的返回值大小,是实际skb的大小,而不是因为入参buffer太小从而返回buffer大小
	//
	if (flags & MSG_TRUNC)
		copied = data_skb->len;

	//释放skb
	skb_free_datagram(sk, skb);
	
	//核心在这里,接着获取剩余的socket信息
	if (nlk->cb && atomic_read(&sk->sk_rmem_alloc) <= sk->sk_rcvbuf / 2) {
		ret = netlink_dump(sk);
		if (ret) {
			sk->sk_err = ret;
			sk->sk_error_report(sk);
		}
	}
}

上面的流程有几个核心点,首先,对于tcp_diag流程而言,上篇 讲过当你sendto(msg)发送到内核时,实际调用了netlink_dump->tcp_diag_dump函数,来循环获取本机所有socket信息,这个过程是同步的。但是tcp_diag_dump之所以高性能很关键的一点就是因为它本身不会循环获取完所有的socket,这会导致加锁时间太长。所以,下一次获取剩余数据的时机 其实就是当用户调用recvfrom/recvmsg的时候。那么,怎么避免不重复获取socket,靠的是nlk->cb中保存了之前的信息。

所以,当我们执行一次len = recvfrom(buffer, sizeof(buffer), 0); ,如果tcp_diag模块还没循环完成,即nlk->cb还有值,那么虽然len返回的大小小于buffer的大小,但是实际上,本次recvfrom 操作之时,内核因为又跑了一遍netlink_dump->tcp_diag_dump,导致netlink的socket的接受队列里面,实际还是有数据的,而这些数据,并没有通过当前这次recvfrom反馈出来,这导致用户态的代码就很奇葩。

还有一点很关键,如果我们的用户态代码的buffer太小,例如buffer大小是1k,但是skb是3k,由于调用完这次recvfrom,skb就被释放了(没加MSG_PEEK的话),本次buffer是不全的,下次recvfrom 的数据实际上是新一次的dump出来的。从而连netlink格式都是错误的。

	for() {
		//循环多次,len返回一直有数据,但是大小可能一直小于buffer大小
		len = recvfrom(buffer, sizeof(buffer), 0);
	}

这简直是尿频尿不尽。
所以netlink接受数据的流程,其实是很奇葩的。那应用层该怎么办呢?实际上就是循环读,然后判断netlink的done数据包,然后停止读。以 iproute2 为例,rtnl_recvmsg->__rtnl_recvmsg封装了recvmsg函数

static int rtnl_recvmsg(int fd, struct msghdr *msg, char **answer)
{
	struct iovec *iov = msg->msg_iov;
	char *buf;
	int len;

	iov->iov_base = NULL;
	iov->iov_len = 0;
	//获取实际大小
	len = __rtnl_recvmsg(fd, msg, MSG_PEEK | MSG_TRUNC);
	if (len < 0)
		return len;

	if (len < 32768)
		len = 32768;
	//开辟实际大小
	buf = malloc(len);
	if (!buf) {
		fprintf(stderr, "malloc error: not enough buffer\n");
		return -ENOMEM;
	}

	iov->iov_base = buf;
	iov->iov_len = len;
	//消费一次skb,skb大小就是len
	len = __rtnl_recvmsg(fd, msg, 0);
	if (len < 0) {
		free(buf);
		return len;
	}

	if (answer)
		*answer = buf;
	else
		free(buf);

	return len;
}

而应用层是这么读的

static int rtnl_dump_filter_l(struct rtnl_handle *rth,
			      const struct rtnl_dump_filter_arg *arg)
{
	struct sockaddr_nl nladdr;
	struct iovec iov;
	struct msghdr msg = {
		.msg_name = &nladdr,
		.msg_namelen = sizeof(nladdr),
		.msg_iov = &iov,
		.msg_iovlen = 1,
	};
	char *buf;
	int dump_intr = 0;

	while (1) {
		int status;
		const struct rtnl_dump_filter_arg *a;
		int found_done = 0;
		int msglen = 0;

		status = rtnl_recvmsg(rth->fd, &msg, &buf);

			while (NLMSG_OK(h, msglen)) {
				//不断判断netlink messagede type,为done就break
				if (h->nlmsg_type == NLMSG_DONE) {
					err = rtnl_dump_done(h, a);

					found_done = 1;
					break; /* process next filter */
				}

				if (h->nlmsg_type == NLMSG_ERROR) {
					err = rtnl_dump_error(rth, h, a);
					if (err < 0) {
						free(buf);
						return -1;
					}

					goto skip_it;
				}

				if (!rth->dump_fp) {
					err = a->filter(h, a->arg1);
					if (err < 0) {
						free(buf);
						return err;
					}
				}

skip_it:
				h = NLMSG_NEXT(h, msglen);
			}

		//读到了 NLMSG_DONE 就退出
		if (found_done) {
			if (dump_intr)
				fprintf(stderr,
					"Dump was interrupted and may be inconsistent.\n");
			return 0;
		}

		if (msg.msg_flags & MSG_TRUNC) {
			fprintf(stderr, "Message truncated\n");
			continue;
		}
		if (msglen) {
			fprintf(stderr, "!!!Remnant of size %d\n", msglen);
			exit(1);
		}
	}
}
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-04-24 09:50:00  更:2022-04-24 09:50:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 20:08:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码