您的当前位置:首页正文

NETLINK_INET_DIAG:Socket Monitoring

2024-12-01 来源:个人技术集锦

 

Kernel Version: 4.6.6

      此功能需要kernel支持Patch:

      这个patch主要实现了destroy的功能!

Subject: net: diag: Add the ability to destroy a socket.
This patch adds a SOCK_DESTROY operation, a destroy function
pointer to sock_diag_handler, and a diag_destroy function
pointer.  It does not include any implementation code.


在逐步分析之前,先看下如下的时序图,基本上就是这样调用的,当然后面的inet_diag完全可以替换成其他协议,这个地方就是使用inet来作为一个实例,看了大体流程是不是觉得很简单,OK,开始细细分析!

1. sock_diag 初始化

Kernel部分 主要集中在  kernel3.18/net/sock_diag.c

这个module的初始化的时候,会使用netlink_kernel_create创建一个协议为NETLINK_SOCK_DIAG的函数,接收函数为sock_diag_rcv

static int __net_init diag_net_init(struct net *net)
{
	struct netlink_kernel_cfg cfg = {
		.input	= sock_diag_rcv,
	};

	net->diag_nlsk = netlink_kernel_create(net, NETLINK_SOCK_DIAG, &cfg);
	return net->diag_nlsk == NULL ? -ENOMEM : 0;
}

不过要注意一点在create socket的时候会有这样的操作,就是把input给关联起来..

	sk->sk_data_ready = netlink_data_ready;
	if (cfg && cfg->input)
		nlk_sk(sk)->netlink_rcv = cfg->input;

2. 传递消息到kernel

         netlink 主要是userspace和kernel space的交互使用的,从上面来看user和kernel的socket都创建完成之后,如果user有消息发送给kernel的话,也是会走netlink的通用的flow到:netlink_sendmsg,这个函数的过程在这里不做具体分析,直接到最后调用到netlink_unicast,到目前为止,实际上从协议上去区分,userspace发送的kernel是有目的的,专门发送到某个socket,这里要根据NETLINK_SOCK_DIAG来区分,结合上面初始化的和现在netlink_rev skb,这样其实我们就直接走到了sock_diag_rcv的函数,已经朝着目的地迈进一大步了!

static int netlink_unicast_kernel(struct sock *sk, struct sk_buff *skb,
				  struct sock *ssk)
{
	int ret;
	struct netlink_sock *nlk = nlk_sk(sk);

	ret = -ECONNREFUSED;
	if (nlk->netlink_rcv != NULL) {
		ret = skb->len;
		netlink_skb_set_owner_r(skb, sk);
		NETLINK_CB(skb).sk = ssk;
		netlink_deliver_tap_kernel(sk, ssk, skb);
		<span style="color:#ff0000;">nlk->netlink_rcv(skb);</span>
		consume_skb(skb);
	} else {
		kfree_skb(skb);
	}
	sock_put(sk);
	return ret;
}
接着来看 sock_diag_rcv, 上锁处理解锁的流程,从netlink_rcv_skb的参数来看,肯定是中间会执行到sock_diag_rcv_msg这个callback的函数

static void sock_diag_rcv(struct sk_buff *skb)
{
	mutex_lock(&sock_diag_mutex);
	netlink_rcv_skb(skb, &sock_diag_rcv_msg);
	mutex_unlock(&sock_diag_mutex);
}
ok,下面来说下netlink_rcv_skb这个函数了,其实任何一种协议的数据包都会走这个函数,kernel分层的思想比比皆是,这个函数的重要工作其实就是在解析skb的信息,然后去调用协议接收函数,这里就是指的sock_diag_rcv_msg

int netlink_rcv_skb(struct sk_buff *skb, int (*cb)(struct sk_buff *,
						     struct nlmsghdr *))
{
	struct nlmsghdr *nlh;
	int err;
	while (skb->len >= nlmsg_total_size(0)) {
		int msglen;
                 
                //先取出header nlh
		nlh = nlmsg_hdr(skb);
		err = 0;
                //合法性的检查,如果小于HDRLEN或者skb->len异常 就退出不处理
		if (nlh->nlmsg_len < NLMSG_HDRLEN || skb->len < nlh->nlmsg_len)
			return 0;

		/* Only requests are handled by the kernel */
		if (!(nlh->nlmsg_flags & NLM_F_REQUEST))
			goto ack;

		/* Skip control messages */
		if (nlh->nlmsg_type < NLMSG_MIN_TYPE)
			goto ack;
                //这个地方调用协议相关的函数
		err = cb(skb, nlh);
		if (err == -EINTR)
			goto skip;

ack:
                //如果user需要有回复,那么这个地方调用netlink_ack进行答复动作
		if (nlh->nlmsg_flags & NLM_F_ACK || err)
			netlink_ack(skb, nlh, err);

skip:
		msglen = NLMSG_ALIGN(nlh->nlmsg_len);
		if (msglen > skb->len)
			msglen = skb->len;
		skb_pull(skb, msglen);
	}

	return 0;
}

接下来再看sock_diag_rcv_msg: 这个函数其实主要是就是对四中msg type进行解析,并执行相关的函数,我们这里主要关心2中类型的消息

#define SOCK_DIAG_BY_FAMILY 20
#define SOCK_DESTROY 21

其中SOCK_DIAG_BY_FAMILY 是用于dump相关的socket,SOCK_DESTROY是用来destroy相关socket

static int sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
	int ret;

	switch (nlh->nlmsg_type) {
	case TCPDIAG_GETSOCK:
	case DCCPDIAG_GETSOCK:
		if (inet_rcv_compat == NULL)
			request_module("net-pf-%d-proto-%d-type-%d", PF_NETLINK,
					NETLINK_SOCK_DIAG, AF_INET);

		mutex_lock(&sock_diag_table_mutex);
		if (inet_rcv_compat != NULL)
			ret = inet_rcv_compat(skb, nlh);
		else
			ret = -EOPNOTSUPP;
		mutex_unlock(&sock_diag_table_mutex);

		return ret;
	/*目前主要是处理这两种类型的消息,一个是Dump相关的connection的socket info,一个是Destroy相关的
	 * Socket connect*/
	case SOCK_DIAG_BY_FAMILY:
	case SOCK_DESTROY:
		return __sock_diag_cmd(skb, nlh);
	default:
		return -EINVAL;
	}
}

继续分析__sock_diag_cmd这个函数,这个函数主要是根据传入的数据也就是sock_diag_req中的协议类型在注册的sock_diag_handlers数组中找到对应的sock_diag_handler,  然后再判断cmd的类型SOCK_DIAG_BY_FAMILY 执行dump钩子函数,SOCK_DESTROY 执行destroy钩子函数!
static int __sock_diag_cmd(struct sk_buff *skb, struct nlmsghdr *nlh)
{
	int err;
	struct sock_diag_req *req = nlmsg_data(nlh);
	const struct sock_diag_handler *hndl;

	if (nlmsg_len(nlh) < sizeof(*req))
		return -EINVAL;

	if (req->sdiag_family >= AF_MAX)
		return -EINVAL;

	if (sock_diag_handlers[req->sdiag_family] == NULL)
		request_module("net-pf-%d-proto-%d-type-%d", PF_NETLINK,
				NETLINK_SOCK_DIAG, req->sdiag_family);

	mutex_lock(&sock_diag_table_mutex);
	hndl = sock_diag_handlers[req->sdiag_family];
	if (hndl == NULL)
		err = -ENOENT;
	else if (nlh->nlmsg_type == SOCK_DIAG_BY_FAMILY)
		err = hndl->dump(skb, nlh);
	else if (nlh->nlmsg_type == SOCK_DESTROY && hndl->destroy)
		err = hndl->destroy(skb, nlh);
	else
		err = -EOPNOTSUPP;
	mutex_unlock(&sock_diag_table_mutex);

	return err;
}


3.重要的结构体

      在进入执行cmd之前,先来看下几个重要的结构体, 这个是一个 全局静态的结构体数组指针

static const struct sock_diag_handler *sock_diag_handlers[AF_MAX];

这个结构体,包含3个钩子和一个family的变量,其中family就是指的协议的类型了比如AF_INET,AF_INET6等,方法有destroy,dump,get_info 3个方法,可想而知每一个协议族需要使用sock_diag的功能的时候,需要提前注册到sock_diag_handlers中,就拿AF_INET来讲,会有一个inet_diag_handler,

static const struct sock_diag_handler inet_diag_handler = {
	.family = AF_INET,
	.dump = inet_diag_handler_cmd,
	.get_info = inet_diag_handler_get_info,
	.destroy = inet_diag_handler_cmd,
};

当inet_diag 初始化的时候就会调用sock_diag_register,将传入的inet_diag_handler给注册到sock_diag_handlers中,就是按照family放到之前提到的那个全局数据组!

int sock_diag_register(const struct sock_diag_handler *hndl)
{
	int err = 0;

	if (hndl->family >= AF_MAX)
		return -EINVAL;

	mutex_lock(&sock_diag_table_mutex);
	if (sock_diag_handlers[hndl->family])
		err = -EBUSY;
	else
		sock_diag_handlers[hndl->family] = hndl;
	mutex_unlock(&sock_diag_table_mutex);

	return err;
}

协议的类型很多,但是注册形式都是一样的,如果对于这个协议下还有其他的协议,还可以继续按照这种分层的思想继续分下去,例如tcp_diag_handler!


4.Dump命令分析

          到此处,在回到之前的__sock_diag_cmd函数,可以看到这个函数会先根据协议的类型family来找到对于的handler,在根据对应的command id来调用handler的钩子函数,好,假设上层的参数是AF_INET,我们来看下相关flow. 如果是AF_INET,handler肯定是刚才讲的inet_diag_handler,如果cmd是SOCK_DIAG_BY_FAMILY的话,那么接下来走调用dump的hook了。

static int inet_diag_cmd_exact(int cmd, struct sk_buff *in_skb,
			       const struct nlmsghdr *nlh,
			       const struct inet_diag_req_v2 *req)
{
	const struct inet_diag_handler *handler;
	int err;
        //继续从inet_diag_table数组从跟进协议TCP/UDP获取Handler
	handler = inet_diag_lock_handler(req->sdiag_protocol);
	if (IS_ERR(handler))
		err = PTR_ERR(handler);
	//然后再根据cmd,调用相关的执行函数
	else if (cmd == SOCK_DIAG_BY_FAMILY)
		err = handler->dump_one(in_skb, nlh, req);
	else if (cmd == SOCK_DESTROY && handler->destroy)
		err = handler->destroy(in_skb, req);
	else
		err = -EOPNOTSUPP;
	inet_diag_unlock_handler(handler);

	return err;
}

如果是TCP协议的话,也就意味着调用到了tcp_diag中的tcp_diag_dump_one

static const struct inet_diag_handler tcp_diag_handler = {
	.dump		 = tcp_diag_dump,
	.dump_one	 = tcp_diag_dump_one,
	.idiag_get_info	 = tcp_diag_get_info,
	.idiag_type	 = IPPROTO_TCP,
	.idiag_info_size = sizeof(struct tcp_info),
#ifdef CONFIG_INET_DIAG_DESTROY
	.destroy	 = tcp_diag_destroy,
#endif
};

查看tcp_diag_dump_one函数,实际上就是从tcp_hashinfo中读取相关的socket,这个地方只能一次读一个哦,one....

static int tcp_diag_dump_one(struct sk_buff *in_skb, const struct nlmsghdr *nlh,
			     const struct inet_diag_req_v2 *req)
{
	return inet_diag_dump_one_icsk(&tcp_hashinfo, in_skb, nlh, req);
}

int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
			    struct sk_buff *in_skb,
			    const struct nlmsghdr *nlh,
			    const struct inet_diag_req_v2 *req)
{
	struct net *net = sock_net(in_skb->sk);
	struct sk_buff *rep;
	struct sock *sk;
	int err;
        
        //此处根据req信息中的,dport/dst sport/src if 来找到对应的socket
	sk = inet_diag_find_one_icsk(net, hashinfo, req);
	if (IS_ERR(sk))
		return PTR_ERR(sk);
        //create一个skb
	rep = nlmsg_new(inet_sk_attr_size(), GFP_KERNEL);
	if (!rep) {
		err = -ENOMEM;
		goto out;
	}
        //填写相关的req信息
	err = sk_diag_fill(sk, rep, req,
			   sk_user_ns(NETLINK_CB(in_skb).sk),
			   NETLINK_CB(in_skb).portid,
			   nlh->nlmsg_seq, 0, nlh);
	if (err < 0) {
		WARN_ON(err == -EMSGSIZE);
		nlmsg_free(rep);
		goto out;
	}
	//将dump出的信息,传入
	err = netlink_unicast(net->diag_nlsk, rep, NETLINK_CB(in_skb).portid,
			      MSG_DONTWAIT);
	if (err > 0)
		err = 0;

out:
	if (sk)
		sock_gen_put(sk);

	return err;
}


5. Destroy 分析

  分析完dump之后,再来看下destroy函数,其实我们也能够看到flow都是基本一样的了,只不过最后根据cmd不同会选择到tcp_diag_destroy,仍然也是根据req2的信息,先找到对应的socket,然后调用sock_diag_destroy,我们看到传入的参数是sk 和 ECONNABORTED

#ifdef CONFIG_INET_DIAG_DESTROY
static int tcp_diag_destroy(struct sk_buff *in_skb,
			    const struct inet_diag_req_v2 *req)
{
	struct net *net = sock_net(in_skb->sk);
	struct sock *sk = inet_diag_find_one_icsk(net, &tcp_hashinfo, req);

	if (IS_ERR(sk))
		return PTR_ERR(sk);

	return <span style="color:#ff0000;">sock_diag_destroy(sk, ECONNABORTED)</span>;
}
#endif
继续看下sock_diag_destroy,这个函数就是check权限,然后找到sk_prot中的diag_destroy,执行这个hook函数

int sock_diag_destroy(struct sock *sk, int err)
{
	if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN))
		return -EPERM;

	if (!sk->sk_prot->diag_destroy)
		return -EOPNOTSUPP;

	return sk->sk_prot->diag_destroy(sk, err);
}

sk_prot对于tcp协议来讲就是tcp_abort函数了

kernel-4.6/net/ipv4/tcp_ipv4.c

struct proto tcp_prot = {
	.name			= "TCP",
	.owner			= THIS_MODULE,
	.close			= tcp_close,
	.connect		= tcp_v4_connect,
	.disconnect		= tcp_disconnect,
	.accept			= inet_csk_accept,
	.ioctl			= tcp_ioctl,
	.init			= tcp_v4_init_sock,
	.destroy		= tcp_v4_destroy_sock,
	.shutdown		= tcp_shutdown,
	.setsockopt		= tcp_setsockopt,
	.getsockopt		= tcp_getsockopt,
	.recvmsg		= tcp_recvmsg,
	.sendmsg		= tcp_sendmsg,
	.sendpage		= tcp_sendpage,
	.backlog_rcv		= tcp_v4_do_rcv,
	.release_cb		= tcp_release_cb,
	.hash			= inet_hash,
	.unhash			= inet_unhash,
	.get_port		= inet_csk_get_port,
	.enter_memory_pressure	= tcp_enter_memory_pressure,
	.stream_memory_free	= tcp_stream_memory_free,
	.sockets_allocated	= &tcp_sockets_allocated,
	.orphan_count		= &tcp_orphan_count,
	.memory_allocated	= &tcp_memory_allocated,
	.memory_pressure	= &tcp_memory_pressure,
	.sysctl_mem		= sysctl_tcp_mem,
	.sysctl_wmem		= sysctl_tcp_wmem,
	.sysctl_rmem		= sysctl_tcp_rmem,
	.max_header		= MAX_TCP_HEADER,
	.obj_size		= sizeof(struct tcp_sock),
	.slab_flags		= SLAB_DESTROY_BY_RCU,
	.twsk_prot		= &tcp_timewait_sock_ops,
	.rsk_prot		= &tcp_request_sock_ops,
	.h.hashinfo		= &tcp_hashinfo,
	.no_autobind		= true,
#ifdef CONFIG_COMPAT
	.compat_setsockopt	= compat_tcp_setsockopt,
	.compat_getsockopt	= compat_tcp_getsockopt,
#endif
	.diag_destroy		= tcp_abort,
};

tcp_abort基本就是将这个sk给destroy掉了,然后置位一个ECONNABORTED错误,通过sk_error_report通知上层的user

int tcp_abort(struct sock *sk, int err)
{
        //Error ECONNABORTED
	if (!sk_fullsock(sk)) {
		if (sk->sk_state == TCP_NEW_SYN_RECV) {
			struct request_sock *req = inet_reqsk(sk);

			local_bh_disable();
			inet_csk_reqsk_queue_drop_and_put(req->rsk_listener,
							  req);
			local_bh_enable();
			return 0;
		}
		sock_gen_put(sk);
		return -EOPNOTSUPP;
	}

	/* Don't race with userspace socket closes such as tcp_close. */
	lock_sock(sk);

	if (sk->sk_state == TCP_LISTEN) {
		tcp_set_state(sk, TCP_CLOSE);
		inet_csk_listen_stop(sk);
	}

	/* Don't race with BH socket closes such as inet_csk_listen_stop. */
	local_bh_disable();
	bh_lock_sock(sk);

	if (!sock_flag(sk, SOCK_DEAD)) {
	        
	       //实际上就是指了一个ECONNABORTED的错误
		sk->sk_err = err;
		/* This barrier is coupled with smp_rmb() in tcp_poll() */
		smp_wmb();
		//Error report 
		sk->sk_error_report(sk);
		
		//如果需要发reset的,发RST 包
		if (tcp_need_reset(sk->sk_state))
			tcp_send_active_reset(sk, GFP_ATOMIC);
		//关闭一些socket的状态
		tcp_done(sk);
	}

	bh_unlock_sock(sk);
	local_bh_enable();
	release_sock(sk);
	sock_put(sk);
	return 0;
}

看下error report,就是在唤醒等待队列的sk了,这个时候在使用select或者poll方法的sk会被唤醒,然后上层收到ECONNABORTED的错误!

static void sock_def_error_report(struct sock *sk)
{
	struct socket_wq *wq;

	rcu_read_lock();
	wq = rcu_dereference(sk->sk_wq);
	if (skwq_has_sleeper(wq))
		wake_up_interruptible_poll(&wq->wait, POLLERR);
	sk_wake_async(sk, SOCK_WAKE_IO, POLL_ERR);
	rcu_read_unlock();
}

到这里就把socket monitoring的dump和destroy功能就介绍完了,sock diag的功能还是蛮强大的,不光AF_INET,任何协议的socket都可以扩展支持,换句话说Linux kernel设计上还是扩展性非常强,框架写的好,扩展新功能也非常简单!













显示全文