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);
nlk->netlink_rcv(skb);
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;
}
}
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,
};
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;
}
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;
}
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 sock_diag_destroy(sk, ECONNABORTED);
}
#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,
};
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;
}
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();
}