enchen008 / Netlink / Netlink编程-使用NETLINK_INET_DIAG协议

0 0

   

Netlink编程-使用NETLINK_INET_DIAG协议

2013-12-14  enchen008

Netlink可以使得内核和用户进程进行双向通信,前文已经介绍过用户进程主动发起会话请求的例子。在那个示例程序中,必须同时编写用户态程序和内核模块,因为他们之间通信的协议是我们自己设定的,并没有使用netlink已有的通信协议。如果使用netlink已有的通信协议,那么我们无需编写内核模块,只需编写用户态程序即可。

本文将说明如何在用户态使用NETLINK_INET_DIAG协议。

1.创建netlink套接字

Netlink的使用方法与普通套接字并无太大差异,前文已经说明参数的差异,这里不再赘述。

1struct sk_req {
2    struct nlmsghdr nlh;
3    struct inet_diag_req r;
4};
5 
6int main(int argc, char **argv)
7{
8    int fd;
9    struct sk_req req;
10    struct sockaddr_nl dest_addr;
11    struct msghdr msg;
12    char buf[8192];
13    char src_ip[20];
14    char dest_ip[20];
15    struct iovec iov;
16 
17    if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_INET_DIAG)) < 0) {
18        eprint(__LINE__, errno, "socket");
19        return -1;
20    }

2.发送消息到内核

用户进程通过msghdr结构将消息发送到内核中,因此必须首先初始化msghdr类型的变量msg。该数据结构与iovec类型的变量iov和sockaddr_nl类型的变量dest_addr关联,iov指向数据缓冲区,dest_addr用于描述目的套接字地址。

这里需要将nlmsghdr结构中的nlmsg_type指定为TCPDIAG_GETSOCK,说明获取的是TCP套接字。同时需要将nlmsg_flags字段指定为NLM_F_REQUEST |  NLM_F_ROOT,NLM_F_REQUEST是所有向内核发出消息请求的用户进程所必须所设置的,NLM_F_ROOT则指明返回所有的套接字。

1req.nlh.nlmsg_len = sizeof(req);
2req.nlh.nlmsg_type = TCPDIAG_GETSOCK;
3req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
4req.nlh.nlmsg_pid = 0;
5 
6memset(&req.r, 0, sizeof(req.r));
7req.r.idiag_family = AF_INET;
8req.r.idiag_states = ((1 << TCP_CLOSING + 1) - 1);
9 
10iov.iov_base = &req;
11iov.iov_len = sizeof(req);
12 
13memset(&dest_addr, 0, sizeof(dest_addr));
14dest_addr.nl_family = AF_NETLINK;
15dest_addr.nl_pid = 0;
16dest_addr.nl_groups = 0;
17 
18memset(&msg, 0, sizeof(msg));
19msg.msg_name = (void *)&dest_addr;
20msg.msg_namelen = sizeof(dest_addr);
21msg.msg_iov = &iov;
22msg.msg_iovlen = 1;

数据缓冲区通过req结构来表示,它封装了两个数据结构nlmsghdr和inet_diag_req。前者用来表示netlink消息头,它是必须封装的数据结构。后者是NETLINK_INET_DIAG协议所特有的请求会话的数据结构,具体结构如下:

1struct inet_diag_req {
2        __u8    idiag_family;           /* Family of addresses. */
3        __u8    idiag_src_len;
4        __u8    idiag_dst_len;
5        __u8    idiag_ext;              /* Query extended information */
6 
7        struct inet_diag_sockid id;
8 
9        __u32   idiag_states;           /* States to dump */
10        __u32   idiag_dbs;              /* Tables to dump (NI) */
11};

这里需要特别注意的是inet_diag_req结构中的idiag_states字段,它用来表示内核将要反馈哪些状态的套接字到用户空间。用户空间通过一个枚举类型来表示套接字状态:

1enum
2{
3  TCP_ESTABLISHED = 1,
4  TCP_SYN_SENT,
5  TCP_SYN_RECV,
6  TCP_FIN_WAIT1,
7  TCP_FIN_WAIT2,
8  TCP_TIME_WAIT,
9  TCP_CLOSE,
10  TCP_CLOSE_WAIT,
11  TCP_LAST_ACK,
12  TCP_LISTEN,
13  TCP_CLOSING
14};

idiag_states字段的每一位表示一个状态,因此通过位偏移可以将具体某个状态位置1。上述的实例程序中,将表示所有状态的位都置1,因此内核将向用户进程反馈所有状态的套接字。

1if (sendmsg(fd, &msg, 0) < 0) {
2    eprint(__LINE__, errno, "sendmsg");
3    return -1;
4}

初始化相关的数据结构之后,接下来用户进程通过sendmsg函数发送消息到内核中。

3.用户进程接收消息

用户进程通过两层循环来接受并处理内核发送的消息。外层循环通过recvmsg函数不断接收内核发送的数据,在接收数据之前还要将新的数据缓冲区buf与iov进行绑定。内层循环将内核通过一次系统调用所发送的数据进行分批处理。对于本文所描述的NETLINK_INET_DIAG协议,内核每次向用户进程发送的消息通过inet_diag_msg结构描述:

1struct inet_diag_msg {
2        __u8    idiag_family;
3        __u8    idiag_state;
4        __u8    idiag_timer;
5        __u8    idiag_retrans;
6