NETLINK_INET_DIAG:Socket Monitoring

 

Kernel Version: 4.6.6

     本文分析一下netlink中的一種協議NETLINK_INET_DIAG的實現方法,這裏基本沒有提及userspace的使用方法哦,主要是講解kernel的實現方法。在Android中,會使用到這個netlink socket,對相關的socket進行monitor,會有什麼場景應用到這個功能呢? 一般情況下,如果我們在使用wifi在看視頻的時候,數據包會從server端源源不斷的書送過來,然後送到上層APP,但是如果中途wifi斷掉時候,這個網絡連接會發生什麼呢,繼續接收數據,但是收不到任何數據,但是這個時候不能無限的等下去吧,一般的軟件都會設定一個超時的時間,時間到了,就會收到ETIMEOUT的錯誤,在Android中也做了一些優化,試想一下網絡斷了,其實就意味着這個鏈接沒什麼用了,即使重連之後,也許Wifi的IP地址會變了,這個鏈接就不可用了,所以在斷線的時候,Android網絡管理程序就會去dump當前源地址對應的socket,然後destroy掉。

      此功能需要kernel支持Patch:Subject: net:diag:Add the ability to destroy a socket

      這個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了。

Code的目錄kernel-4.6/net/ipv4/inet_diag.c,調用的是inet_diag_handler_cmd,這個函數基本直接執行到inet_diag_cmd_exact,這裏的設計有將AF_INET協議抽象了一層,弄了個inet_diag_table,然後TCP/UDP根據協議號存放到數組中!

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);
}

看下內容:根據原地址 目的地址等connection的信息來找到對應的sk信息,然後反饋回userspace!

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設計上還是擴展性非常強,框架寫的好,擴展新功能也非常簡單!













發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章