分享

Netlink编程-使用NETLINK_INET_DIAG协议

 enchen008 2013-12-14

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 
7        struct inet_diag_sockid id;
8 
9        __u32   idiag_expires;
10        __u32   idiag_rqueue;
11        __u32   idiag_wqueue;
12        __u32   idiag_uid;
13        __u32   idiag_inode;
14};

每一次外层循环将接收到的数据存放在buf缓冲区中,该缓冲区中存放了多条消息,结构如下:

1struct nlhdrmsg struct inet_idiag_msg || struct nlhdrmsg struct inet_idiag_msg || ……

按照这样的数据存储方式,内层循环要做的就是依次获取这些数据结构。由于每条数据报都至少封装了nlmsghdr结构,因此具体的处理方法通过NLMSG_XXX宏即可完成。

1memset(buf, 0 ,sizeof(buf));
2iov.iov_base = buf;
3iov.iov_len = sizeof(buf);
4 
5while (1) {
6    int status;
7    struct nlmsghdr *h;
8 
9    msg = (struct msghdr) {
10        (void *)&dest_addr, sizeof(dest_addr),
11            &iov, 1, NULL, 0, 0
12    };
13    status = recvmsg(fd, &msg, 0);
14    if (status < 0) {
15        if (errno == EINTR)
16            continue;
17        eprint(__LINE__, errno, "recvmsg");
18        continue;
19    }
20 
21    if (status == 0) {
22        printf("EOF on netlink\n");
23        close(fd);
24        return 0;
25    }
26 
27    h = (struct nlmsghdr *)buf;
28 
29    while (NLMSG_OK(h, status)) {
30        struct inet_diag_msg *pkg = NULL;
31 
32        if (h->nlmsg_type == NLMSG_DONE) {
33            close(fd);
34            printf("NLMSG_DONE\n");
35            return 0;
36        }
37 
38        if (h->nlmsg_type == NLMSG_ERROR) {
39            struct nlmsgerr *err;
40            err = (struct nlmsgerr*)NLMSG_DATA(h);
41            fprintf(stderr, "%d Error %d:%s\n", __LINE__, -(err->error), strerror(-(err->error)));
42            close(fd);
43            printf("NLMSG_ERROR\n");
44            return 0;
45        }
46 
47        pkg = (struct inet_diag_msg *)NLMSG_DATA(h);
48        print_skinfo(pkg);
49        get_tcp_state(pkg->idiag_state);
50        h = NLMSG_NEXT(h, status);
51    }//while
52}//while
53close(fd);
54return 0;

NLMSG_OK宏每次判断buf中的数据是否读取完毕,NLMSG_DATA取当前netlink消息头结构紧邻的inet_diag_msg结构,NLMSG_NEXT则取下一个netlink消息头结构。

pkg指向当前获取到的消息,接下来具体需求处理各个字段即可。上述程序中,print_skinfo函数打印pkg中的各个字段,get_tcp_state则是打印每个套接字连接的状态。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多