Netlink编程-数据结构

本文转自:http://edsionte.com/techblog/archives/4131

内核态与用户态进行数据交互的方法很多,分为用户程序主动发起的消息交交互和内核主动发起的信息交互。通常我们所熟知的系统调用、对/proc进行读写操作、mmap和编写驱动程序等都属于前者,而由内核主动发起的信息交互方法比较少,最典型的方法为内核发送信号给当前进程。上述这些方法都是单向通信的,也就是说要么是用户态进程主动发起数据交互会话,要么是内核主动发起会话。有没有一种可以在用户态和内核态进行双向数据交互的方法呢?Netlink是一种在内核态和用户态可以进行双向数据交互的通信机制。在用户态,我们可以将netlink看作是一种特殊的socket,因此通过socket接口来就可以直接使用netlink;在内核态,则需要通过一组特殊的内核接口编写内核模块。如果初次编写netlink程序,可能会对它所涉及的数据结构感到困惑,本文将简单介绍一下netlink编程中遇到的基本数据结构。struct msghdr如果使用sendmsg()发送数据,那么必须使用msghdr结构,该结构内部封装了本次发送数据的一些参数:

struct msghdr {
   void        *msg_name;             /* optional address */
   socklen_t        msg_namelen;         /* size of address */
   struct iovec        *msg_iov;                 /* scatter/gather array */
   size_t        msg_iovlen;             /* # elements in msg_iov */
   void        *msg_control;           /* ancillary data, see below */
   size_t        msg_controllen;       /* ancillary data buffer len */
   int        msg_flags;               /* flags on received message */
};

netlink提供的是一种基于数据报的通信服务,因此与UDP通信协议类似,必须通过msg_name指明目的套接字地址结构,而msg_namelen则指明该地址结构的长度。msg_iov指定数据缓冲区数组,而msg_iovlen指明了该数组的元素个数。struct ioveciovec结构表示一个向量元素,它定义了一个标准的数据缓冲区格式。其包含两个字段:指向数据的指针和数据长度。

struct iovec
{
   void           *iov_base;        /* Pointer to data.  */
   size_t         iov_len;           /* Length of data.  */
};

当使用iovec结构类型的数组传递数据时,可以将多个消息通过一次系统调用进行发送。struct nlmsghdr如果使用sendmsg函数来发送netlink数据报,那么iovec结构中iov_base字段应该根据具体协议指向一个自定义的数据结构。一般这个自定义的数据结构如下所示:

struct req {
   struct nlmsghdr            *nlh;
   struct special_struct     *r;
};

其中nlmsghdr结构为必须所有的,它用来描述netlink消息头。

struct nlmsghdr {
   __u32         nlmsg_len;         /* Length of message including header. */
   __u16         nlmsg_type;       /* Type of message content. */
   __u16         nlmsg_flags;      /* Additional flags. */
   __u32         nlmsg_seq;        /* Sequence number. */
   __u32         nlmsg_pid;         /* PID of the sending process. */
};

对于special_struct结构则要根据具体需求而定。如果使用netlink现有的协议,比如NETLINK_INET_DIAG,那么special_struct结构在用户程序向内核发送消息时应该为inet_diag_req结构,如果当内核发送消息给用户进程那么special_struct结构又必须定义为inet_diag_msg。这些结构体都是事先定义好的,我们直接拿来用,按需获取这些结构中的某些字段的值即可。如果是自己定义的通信协议,那么则根据具体需求自定义数据结构即可。比如用户进程想通过pid在内核中获取该进程亲属的pid,那么用户进程在发送消息和接受消息时的special_struct分别可以按照如下定义:

struct get_pid_req {
   int    pid;
};
 
struct get_pid_msg {
   int    parent_pid;
   int    sibling_pid;
   int    child_pid;
}

为netlink消息确定好了结构以后,则将该结构赋值给iovec结构的iov_base字段。struct sockaddr_nlnetlink是一种特殊的无连接套接字,因此必须在msghdr结构的msg_name中指明目的netlink套接字的地址。套接字地址的通用结构为struct sockaddr,但对于具体类型的套接字则有不同的地址结构,比如TCP/IP协议族为sockaddr_in,对于netlink则为sockaddr_nl结构:

struct sockaddr_nl {
   sa_family_t         nl_family;      /* AF_NETLINK */
   unsigned short    nl_pad;        /* Zero. */
   pid_t                  nl_pid;          /* Process ID. */
   __u32                nl_groups;    /* Multicast groups mask. */
};

其中该结构中的nl_family字段填充为AF_NETLINK。以上这些结构在netlink编程中都会涉及到,但是通过上面描述的关系来看,它们最终都是与msghdr结构有关的。而msghdr结构作为参数通过sendmsg函数就可以netlink数据包发送至内核。