Netlink Socket

原文出处:http://blog.csdn.net/accp_2008123456/article/details/5818194

 

简介
Netlink
是一种特殊的 socket,它是 Linux 所特有的,类似于 BSD 中的AF_ROUTE 但又远比它的功能强大,目前在最新的 Linux 内核(2.6.14)中使用netlink 进行应用与内核通信的应用很多,包括:

路由 daemonNETLINK_ROUTE

1-wire 子系统(NETLINK_W1

用户态 socket 协议(NETLINK_USERSOCK

防火墙(NETLINK_FIREWALL

socket 监视(NETLINK_INET_DIAG

netfilter 日志(NETLINK_NFLOG

ipsec 安全策略(NETLINK_XFRM

SELinux 事件通知(NETLINK_SELINUX

iSCSI 子系统(NETLINK_ISCSI

进程审计(NETLINK_AUDIT

转发信息表查询(NETLINK_FIB_LOOKUP

netlink connector(NETLINK_CONNECTOR)

netfilter 子系统(NETLINK_NETFILTER

IPv6 防火墙(NETLINK_IP6_FW

DECnet 路由信息(NETLINK_DNRTMSG

内核事件向用户态通知(NETLINK_KOBJECT_UEVENT

通用 netlinkNETLINK_GENERIC)。

Netlink
是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink

Netlink
相对于系统调用,ioctl 以及 /proc 文件系统而言具有以下优点:

1.
为了使用 netlink,用户仅需要在 include/linux/netlink.h 中增加一个新类型的 netlink 协议定义即可, #define NETLINK_MYTEST 17 然后,内核和用户态应用就可以立即通过 socket API 使用该 netlink 协议类型进行数据交换。但系统调用需要增加新的系统调用,ioctl 则需要增加设备或文件,那需要不少代码,proc 文件系统则需要在 /proc 下添加新的文件或目录,那将使本来就混乱的 /proc 更加混乱。

2. Netlink
是一种异步通信机制,在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息,但系统调用与 ioctl 则是同步通信机制,如果传递的数据太长,将影响调度粒度。

3.
使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖,但系统调用就有依赖,而且新的系统调用的实现必须静态地连接到内核中,它无法在模块中实现,使用新系统调用的应用在编译时需要依赖内核。

4. Netlink
支持多播,内核模块或应用可以把消息多播给一个netlink组,属于该neilink 组的任何内核模块或应用都能接收到该消息,内核事件向用户态的通知机制就使用了这一特性,任何对内核事件感兴趣的应用都能收到该子系统发送的内核事件,在后面的文章中将介绍这一机制的使用。

5.
内核可以使用 netlink 首先发起会话,但系统调用和 ioctl 只能由用户应用发起调用。

6. Netlink
使用标准的 socket API,因此很容易使用,但系统调用和 ioctl则需要专门的培训才能使用。


用户态使用 netlink

用户态应用使用标准的socket APIs, socket(), bind(), sendmsg(), recvmsg() close() 就能很容易地使用 netlink socket,查询手册页可以了解这些函数的使用细节,本文只是讲解使用 netlink 的用户应该如何使用这些函数。注意,使用 netlink 的应用必须包含头文件 linux/netlink.h。当然 socket 需要的头文件也必不可少,sys/socket.h

为了创建一个 netlink socket,用户需要使用如下参数调用 socket():

Cpp代码

1.#define NETLINK_ROUTE 0 /* Routing/device hook */

2.#define NETLINK_W1 1 /* 1-wire subsystem */

3.#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */

4.#define NETLINK_FIREWALL 3 /* Firewalling hook */

5.#define NETLINK_INET_DIAG 4 /* INET socket monitoring */

6.#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */

7.#define NETLINK_XFRM 6 /* ipsec */

8.#define NETLINK_SELINUX 7 /* SELinux event notifications */

9.#define NETLINK_ISCSI 8 /* Open-iSCSI */

10.  #define NETLINK_AUDIT 9 /* auditing */

11.  #define NETLINK_FIB_LOOKUP 10

12.  #define NETLINK_CONNECTOR 11

13.  #define NETLINK_NETFILTER 12 /* netfilter subsystem */

14.  #define NETLINK_IP6_FW 13

15.  #define NETLINK_DNRTMSG 14 /* DECnet routing messages */

16.  #define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */

17.  #define NETLINK_GENERIC 16


对于每一个netlink协议类型,可以有多达 32多播组,每一个多播组用一个位表示,netlink 的多播特性使得发送消息给同一个组仅需要一次系统调用,因而对于需要多拨消息的应用而言,大大地降低了系统调用的次数。

函数 bind() 用于把一个打开的 netlink socket netlink socket 地址绑定在一起。netlink socket 的地址结构如下:

Cpp代码

1.pthread_self() << 16 | getpid();

 

因此字段 nl_pid 实际上未必是进程 ID,它只是用于区分不同的接收者或发送者的一个标识,用户可以根据自己需要设置该字段。函数 bind 的调用方式如下:

Cpp代码

1.struct msghdr msg;

2.memset(&msg, 0, sizeof(msg));

3.msg.msg_name = (void *)&(nladdr);

4.msg.msg_namelen = sizeof(nladdr);

其中 nladdr 为消息接收者的 netlink 地址。

struct nlmsghdr
netlink socket 自己的消息头,这用于多路复用和多路分解 netlink 定义的所有协议类型以及其它一些控制,netlink 的内核实现将利用这个消息头来多路复用和多路分解已经其它的一些控制,因此它也被称为netlink 控制块。因此,应用在发送 netlink 消息时必须提供该消息头。

Cpp代码

1./* Flags values */

2.#define NLM_F_REQUEST 1 /* It is request message */

3.#define NLM_F_MULTI 2 /* Multipart message, terminated by NLMSG_DONE */

4.#define NLM_F_ACK 4 /* Reply with ack, with zero or error code */

5.#define NLM_F_ECHO 8 /* Echo this request */

6. 

7./* Modifiers to GET request */

8.#define NLM_F_ROOT 0x100 /* specify tree root */

9.#define NLM_F_MATCH 0x200 /* return all matching */

10.  #define NLM_F_ATOMIC 0x400 /* atomic GET */

11.  #define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH)

12.   

13.  /* Modifiers to NEW request */

14.  #define NLM_F_REPLACE 0x100 /* Override existing */

15.  #define NLM_F_EXCL 0x200 /* Do not touch, if it exists */

16.  #define NLM_F_CREATE 0x400 /* Create, if it does not exist */

17.  #define NLM_F_APPEND 0x800 /* Add to end of list */


标志NLM_F_REQUEST用于表示消息是一个请求,所有应用首先发起的消息都应设置该标志。

标志NLM_F_MULTI 用于指示该消息是一个多部分消息的一部分,后续的消息可以通过宏NLMSG_NEXT来获得。

NLM_F_ACK表示该消息是前一个请求消息的响应,顺序号与进程ID可以把请求与响应关联起来。

标志NLM_F_ECHO表示该消息是相关的一个包的回传。

标志NLM_F_ROOT 被许多 netlink 协议的各种数据获取操作使用,该标志指示被请求的数据表应当整体返回用户应用,而不是一个条目一个条目地返回。有该标志的请求通常导致响应消息设置NLM_F_MULTI标志。注意,当设置了该标志时,请求是协议特定的,因此,需要在字段 nlmsg_type 中指定协议类型。

标志 NLM_F_MATCH 表示该协议特定的请求只需要一个数据子集,数据子集由指定的协议特定的过滤器来匹配。

标志 NLM_F_ATOMIC 指示请求返回的数据应当原子地收集,这预防数据在获取期间被修改。

标志 NLM_F_DUMP 未实现。

标志 NLM_F_REPLACE 用于取代在数据表中的现有条目。

标志 NLM_F_EXCL_ 用于和 CREATE APPEND 配合使用,如果条目已经存在,将失败。

标志 NLM_F_CREATE 指示应当在指定的表中创建一个条目。

标志 NLM_F_APPEND 指示在表末尾添加新的条目。

内核需要读取和修改这些标志,对于一般的使用,用户把它设置为 0 就可以,只是一些高级应用(如 netfilter 和路由 daemon 需要它进行一些复杂的操作),字段 nlmsg_seq nlmsg_pid 用于应用追踪消息,前者表示顺序号,后者为消息来源进程 ID。下面是一个示例:

Cpp代码

1.struct iovec iov;

2.iov.iov_base = (void *)nlhdr;

3.iov.iov_len = nlh->nlmsg_len;

4.msg.msg_iov = &iov;

5.msg.msg_iovlen = 1;

在完成以上步骤后,消息就可以通过下面语句直接发送:

Cpp代码

1.#define MAX_NL_MSG_LEN 1024

2.struct sockaddr_nl nladdr;

3.struct msghdr msg;

4.struct iovec iov;

5.struct nlmsghdr * nlhdr;

6.nlhdr = (struct nlmsghdr *)malloc(MAX_NL_MSG_LEN);

7.iov.iov_base = (void *)nlhdr;

8.iov.iov_len = MAX_NL_MSG_LEN;

9.msg.msg_name = (void *)&(nladdr);

10.  msg.msg_namelen = sizeof(nladdr);

11.  msg.msg_iov = &iov;

12.  msg.msg_iovlen = 1;

13.  recvmsg(fd, &msg, 0);


注意:fd socket 调用打开的 netlink socket 描述符。

在消息接收后,nlhdr指向接收到的消息的消息头,nladdr保存了接收到的消息的目标地址,宏NLMSG_DATA(nlhdr)返回指向消息的数据部分的指针。

linux/netlink.h中定义了一些方便对消息进行处理的宏,这些宏包括:

Cpp代码

1.#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))

NLMSG_LENGTH(len)用于计算数据部分长度为len时实际的消息长度。它一般用于分配消息缓存。

Cpp代码

1.#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏。

Cpp代码

1.#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && (nlh)->nlmsg_len <= (len))

NLMSG_OK(nlh,len)用于判断消息是否有len这么长。

Cpp代码

1.#define NETLINK_MYTEST 17


只要增加这个定义之后,用户就可以在内核的任何地方引用该协议。

在内核中,为了创建一个netlink socket用户需要调用如下函数:

Cpp代码

1.void input (struct sock *sk, int len)

2.{

3.struct sk_buff *skb;

4.struct nlmsghdr *nlh = NULL;

5.u8 *data = NULL;

6.while ((skb = skb_dequeue(&sk->receive_queue)) != NULL)

7.{

8./* process netlink message pointed by skb->data */

9.nlh = (struct nlmsghdr *)skb->data;

10.  data = NLMSG_DATA(nlh);

11.  /* process netlink message with header pointed by

12.  * nlh and data pointed by data

13.  */

14.  }

15.  }


函数input()会在发送进程执行sendmsg()时被调用,这样处理消息比较及时,但是,如果消息特别长时,这样处理将增加系统调用sendmsg()的执行时间,对于这种情况,可以定义一个内核线程专门负责消息接收,而函数input的工作只是唤醒该内核线程,这样sendmsg将很快返回。

函数skb = skb_dequeue(&sk->receive_queue)用于取得socket sk的接收队列上的消息,返回为一个struct sk_buff的结构,skb->data指向实际的netlink消息。

函数skb_recv_datagram(nl_sk)也用于在netlink socket nl_sk上接收消息,与skb_dequeue的不同指出是,如果socket的接收队列上没有消息,它将导致调用进程睡眠在等待队列nl_sk->sk_sleep,因此它必须在进程上下文使用,刚才讲的内核线程就可以采用这种方式来接收消息。

下面的函数input就是这种使用的示例:

Cpp代码

1.#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))


来方便消息的地址设置。下面是一个消息地址设置的例子:

Cpp代码

1.int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);


参数sk为函数netlink_kernel_create()返回的socket,参数skb存放消息,它的data字段指向要发送的netlink消息结构,而skb的控制块保存了消息的地址信息,前面的宏NETLINK_CB(skb)就用于方便设置该控制块,参数pid为接收消息进程的pid,参数nonblock表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用时睡眠。

内核模块或子系统也可以使用函数netlink_broadcast来发送广播消息:

Cpp代码

1.void sock_release(struct socket * sock);


注意函数netlink_kernel_create()返回的类型为struct sock,因此函数sock_release应该这种调用:

Cpp代码

1.sock_release(sk->sk_socket);


sk
为函数netlink_kernel_create()的返回值。

源代码包(见附件)中给出了一个使用 netlink 的示例,它包括一个内核模块 netlink-exam-kern.c 和两个应用程序 netlink-exam-user-recv.c, netlink-exam-user-send.c。内核模块必须先插入到内核,然后在一个终端上运行用户态接收程序,在另一个终端上运行用户态发送程序,发送程序读取参数指定的文本文件并把它作为 netlink 消息的内容发送给内核模块,内核模块接受该消息保存到内核缓存中,它也通过proc接口出口到 procfs,因此用户也能够通过 /proc/netlink_exam_buffer 看到全部的内容,同时内核也把该消息发送给用户态接收程序,用户态接收程序将把接收到的内容输出到屏幕上。

 

你可能感兴趣的:(netlink)