NETLINK_INET_DIAG:Socket Monitoring

来源:互联网 发布:midi电脑音乐制作软件 编辑:程序博客网 时间:2024/06/03 18:22

 

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 functionpointer to sock_diag_handler, and a diag_destroy functionpointer.  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 nlhnlh = 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);elseret = -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);elseerr = -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;elsesock_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获取Handlerhandler = 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);elseerr = -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 来找到对应的socketsk = inet_diag_find_one_icsk(net, hashinfo, req);if (IS_ERR(sk))return PTR_ERR(sk);        //create一个skbrep = 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_DESTROYstatic 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 ECONNABORTEDif (!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设计上还是扩展性非常强,框架写的好,扩展新功能也非常简单!













1 0
原创粉丝点击