netlink

netlink定义

 用于在内核模块与在用户地址空间中的进程之间传递消息的。它包 含了用于用户进程的基于标准套接字的接口和用于内核模块的一个内部核心API。[--manual]
 Netlink是套接字家族中的一员,主要用内核与用户空间的进程间、用户进程间的通讯。然而它并不像网络套接字可以用于主机间通讯,Netlink只能用于同一主机上进程通讯,并通过PID来标识它们。[--wiki]
[
see more:RFC 3549]

netlink出现之前,linux是用ioctl接口控制设备参数。
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。

 (相对于系统调用,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

发送netlink消息
用户态应用使用标准的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 就能很容易地使用 netlink socket.

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

//int socket(int domain, int type, int protocol);
socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);


domain字段:指明是netlink协议 [see more:include/linux/socket.h]

type字段:由于缺少标准,SOCK_DGRAM和SOCK_RAW并不保证在每个Linux发行版(或其它操作系统)被声 明。有些源码中声明两种都可以使用,红帽的文档中声明SOCK_RAW一直可以被使用,而 iproute2交换使用两者。

protocol字段:netlink中哪种子协议[see more:include/linux/netlink.h]


 

函数 bind() 用于把一个打开的 netlink socket 与 netlink 源 socket 地址绑定在一起。

bind(fd, (struct sockaddr*)&nladdr, sizeof(struct sockaddr_nl));

netlink socket 的地址结构如下:

struct sockaddr_nl
{
  sa_family_t    nl_family; //必须设置为 AF_NETLINK 或着 PF_NETLINK
  unsigned short nl_pad;    // 当前没有使用,因此要总是设置为 0
  __u32          nl_pid;    // 推荐使用接收或发送消息的进程ID,如果希望内核处理消息或多播消息,就设置为0
  __u32          nl_groups; // 用于指定多播组
};
对于每一个netlink协议类型,可以有多达 32多播组,每一个多播组用一个位表示,netlink 的多播特性使得发送消息给同一个组仅需要一次系统调用,因而对于需要多拨消息的应用而言,大大地降低了系统调用的次数。

sendmsg之前,还需要引用结构 struct msghdr、struct nlmsghdr 和 struct iovec。

struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&(nladdr); //接受者的地址
msg.msg_namelen = sizeof(nladdr);


struct nlmsghdr 为 netlink socket 自己的消息头,这用于多路复用和多路分解 netlink 定义的所有协议类型以及其它一些控制,netlink 的内核实现将利用这个消息头来多路复用和多路分解已经其它的一些控制,因此它也被称为netlink 控制块。因此,应用在发送 netlink 消息时必须提供该消息头。
struct nlmsghdr
{
  __u32 nlmsg_len;   /* Length of message nlmsghdr+payload */
  __u16 nlmsg_type;  /* Message type*/
  __u16 nlmsg_flags; /* Additional flags */
  __u32 nlmsg_seq;   /* Sequence number */
  __u32 nlmsg_pid;   /* Sending process PID */
};

 字段 nlmsg_type: 大部分情况下设置为0,NETLINK_ROUTE提供路由和连接信息,对于这个 协议,Linux声明了大量的子消息:如链路层:RTM_NEWLINK, RTM_DELLINK, RTM_GETLINK, RTM_SETLINK。

字段 nlmsg_flags 用于设置消息标志,可用的标志包括:

/* Flags values */
#define NLM_F_REQUEST           1       /* It is request message.       */
#define NLM_F_MULTI             2       /* Multipart message, terminated by NLMSG_DONE */
#define NLM_F_ACK               4       /* Reply with ack, with zero or error code */
#define NLM_F_ECHO              8       /* Echo this request            */
......
内核需要读取和修改这些标志,对于一般的使用,用户把它设置为 0 就可以,只是一些高级应用(如 netfilter 和路由 daemon 需要它进行一些复杂的操作),字段 nlmsg_seq 和 nlmsg_pid 用于应用追踪消息,前者表示顺序号,后者为消息来源进程 ID。

结构 struct iovec 用于把多个消息通过一次系统调用来发送,下面是该结构使用示例:

struct iovec iov;
iov.iov_base = (void *)nlhdr;
iov.iov_len = nlh->nlmsg_len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

在完成以上步骤后,消息就可以通过下面语句直接发送:
sendmsg(fd, &msg, 0);


接收netlink消息
应用接收消息时需要首先分配一个足够大的缓存来保存消息头以及消息的数据部分,然后填充消息头,添完后就可以直接调用函数 recvmsg() 来接收。
#define MAX_NL_MSG_LEN 1024
struct sockaddr_nl nladdr;
struct msghdr msg;
struct iovec iov;
struct nlmsghdr * nlhdr;

nlhdr = (struct nlmsghdr *)malloc(MAX_NL_MSG_LEN);
iov.iov_base = (void *)nlhdr;
iov.iov_len = MAX_NL_MSG_LEN;
msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

recvmsg(fd, &msg, 0);




在消息接收后,nlhdr指向接收到的消息的消息头,nladdr保存了接收到的消息的目标地址,宏NLMSG_DATA(nlhdr)返回指向消息的数据部分的指针。
在linux/netlink.h中定义了一些方便对消息进行处理的宏,这些宏包括:[see more: man  netlink & include/linux/netlink.h]
#define NLMSG_ALIGNTO   4
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
宏NLMSG_ALIGN(len)用于得到不小于len且字节对齐的最小数值。
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
宏NLMSG_LENGTH(len)用于计算数据部分长度为len时实际的消息长度。它一般用于分配消息缓存。
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
宏NLMSG_SPACE(len)返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值,它也用于分配消息缓存。
#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
宏NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏。
#define NLMSG_NEXT(nlh,len)      ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
                      (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
宏NLMSG_NEXT(nlh,len)用于得到下一个消息的首地址,同时len也减少为剩余消息的总长度,该宏一般在一个消息被分成几个部分发送或接收时使用。
#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这么长。
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
宏NLMSG_PAYLOAD(nlh,len)用于返回payload的长度。



函数close用于关闭打开的netlink socket。
close(fd);

库:libnl

#####Connection Management#####

int 	nl_connect (struct nl_sock *sk, int protocol)
Create file descriptor and bind socket. 

void 	nl_close (struct nl_sock *sk)
Close Netlink socket. 

#####Send#####

int 	nl_sendto (struct nl_sock *sk, void *buf, size_t size)
Transmit raw data over Netlink socket. 

int 	nl_sendmsg (struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr)
Transmit Netlink message using sendmsg() 

int 	nl_send_iovec (struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen)
Transmit Netlink message (taking IO vector) 

int 	nl_send (struct nl_sock *sk, struct nl_msg *msg)
Transmit Netlink message. 

void 	nl_complete_msg (struct nl_sock *sk, struct nl_msg *msg)
Finalize Netlink message. 

int 	nl_send_auto (struct nl_sock *sk, struct nl_msg *msg)
Finalize and transmit Netlink message. 

int 	nl_send_sync (struct nl_sock *sk, struct nl_msg *msg)
Finalize and transmit Netlink message and wait for ACK or error message. 

int 	nl_send_simple (struct nl_sock *sk, int type, int flags, void *buf, size_t size)
Construct and transmit a Netlink message. 

#####Receive#####

int 	nl_recv (struct nl_sock *sk, struct sockaddr_nl *nla, unsigned char **buf, struct ucred **creds)
Receive data from netlink socket. 

int 	nl_recvmsgs_report (struct nl_sock *sk, struct nl_cb *cb)
Receive a set of messages from a netlink socket and report parsed messages. 

int 	nl_recvmsgs (struct nl_sock *sk, struct nl_cb *cb)
Receive a set of messages from a netlink socket. 

int 	nl_recvmsgs_default (struct nl_sock *sk)
Receive a set of message from a netlink socket using handlers in nl_sock. 

int 	nl_wait_for_ack (struct nl_sock *sk)
Wait for ACK. 

int 	nl_pickup (struct nl_sock *sk, int(*parser)(struct nl_cache_ops *, struct sockaddr_nl *, struct nlmsghdr *, struct nl_parser_param *), struct nl_object **result)
Pickup netlink answer, parse is and return object. 

#####Deprecated#####

void 	nl_auto_complete (struct nl_sock *sk, struct nl_msg *msg)

int 	nl_send_auto_complete (struct nl_sock *sk, struct nl_msg *msg)


[see more:http://www.carisma.slowglass.com/~tgr/libnl/doc/api/index.html]

[source code: libnl,iproute2, iw,wpa_supplicant/hostapd]

内核实现

内核接收netlink message流程,以generic netlink为例
genl_init()-->register_pernet_subsys(&genl_pernet_ops)-->.init=genl_pernet_init-->netlink_kernel_create(..., genl_rcv, ...)-->netlink_rcv_skb(..., &genl_rcv_msg)
详略,[see more: linux source code]



构造nlmsg
#include <netlink/msg.h>

struct nlmsghdr *hdr;
struct nl_msg *msg;
struct myhdr {
        uint32_t foo1, foo2;
} my_hdr = { 10, 20 };


/* Allocate a message with the default maximum message size */
msg = nlmsg_alloc();

/*
 * Add header with message type MY_MSGTYPE, the flag NLM_F_CREATE,
 * let library fill port and sequence number, and reserve room for
 * struct myhdr
 */
hdr = nlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, MY_MSGTYPE, sizeof(hdr), NLM_F_CREATE);

/* Copy own header into newly reserved payload section */
memcpy(nlmsg_data(hdr), &my_hdr, sizeof(my_hdr));
/*
 * The message will now look like this:
 *     +-------------------+- - -+----------------+- - -+
 *     |  struct nlmsghdr  | Pad |  struct myhdr  | Pad |
 *     +-------------------+-----+----------------+- - -+
 * nlh -^                        /                \
 *                              +--------+---------+
 *                              |  foo1  |  foo2   |
 *                              +--------+---------+
 */

//之后可能还需要对齐等操作


数据解析:

从buffer得到的数据格式:

     <----------- nlmsg_total_size(len) ------------>
     <----------- nlmsg_size(len) ------------>

    +-------------------+- - -+- - - - - - - - +- - -+-------------------+- - -
    |  struct nlmsghdr  | Pad |     Payload    | Pad |  struct nlsmghdr  |
    +-------------------+- - -+- - - - - - - - +- - -+-------------------+- - -

     <---- NLMSG_HDRLEN -----> <- NLMSG_ALIGN(len) -> <---- NLMSG_HDRLEN ---

两种方式得到nlmsg:

#include <netlink/msg.h>

void my_parse(void *stream, int length)
{
        struct nlmsghdr *hdr = stream;

        while (nlmsg_ok(hdr, length)) {
                // Parse message here
                hdr = nlmsg_next(hdr, &length);
        }
}
or

#include <netlink/msg.h>

struct nlmsghdr *hdr;

nlmsg_for_each(hdr, stream, length) {
        /* do something with message */
}



得到payload:
                               <--- nlmsg_datalen(nlh) --->
    +-------------------+- - -+----------------------------+- - -+
    |  struct nlmsghdr  | Pad |           Payload          | Pad |
    +-------------------+- - -+----------------------------+- - -+
nlmsg_data(nlh) ---------------^                                  ^
nlmsg_tail(nlh) --------------------------------------------------^

解析attributes:

                               <---------------------- payload ------------------------->
                               <----- hdrlen ---->       <- nlmsg_attrlen(nlh, hdrlen) ->
    +-------------------+- - -+-----  ------------+- - -+--------------------------------+- - -+
    |  struct nlmsghdr  | Pad |  Protocol Header  | Pad |           Attributes           | Pad |
    +-------------------+- - -+-------------------+- - -+--------------------------------+- - -+
nlmsg_attrdata(nlh, hdrlen) -----------------------------^

attributes结构:

     <----------- nla_total_size(payload) ----------->
     <---------- nla_size(payload) ----------->
    +-----------------+- - -+- - - - - - - - - +- - -+-----------------+- - -
    |  struct nlattr  | Pad |     Payload      | Pad |  struct nlattr  |
    +-----------------+- - -+- - - - - - - - - +- - -+-----------------+- - -
     <---- NLA_HDRLEN -----> <--- NLA_ALIGN(len) ---> <---- NLA_HDRLEN ---

Netlink Attribute Header

顺序解析attribute:

#include <netlink/msg.h>
#include <netlink/attr.h>

struct nlattr *hdr = nlmsg_attrdata(msg, 0);
int remaining = nlmsg_attrlen(msg, 0);

while (nla_ok(hdr, remaining)) {
        /* parse attribute here */
        hdr = nla_next(hdr, &remaining);
};

or a easer way:

#include <netlink/attr.h>

int nla_parse(struct nlattr **attrs, int maxtype, struct nlattr *head,
              int len, struct nla_policy *policy);


定位参数:

#include <netlink/attr.h>

struct nlattr *nla_find(struct nlattr *head, int len, int attrtype);

#include <netlink/msg.h>

struct nlattr *nlmsg_find_attr(struct nlmsghdr *hdr, int hdrlen, int attrtype);

构造attr:

int nla_put(struct nl_msg *msg, int attrtype, int attrlen, const void *data);


关于attr的详尽讨论略.

[see more: http://www.carisma.slowglass.com/~tgr/libnl/doc/core.html#_socket_structure_struct_nl_sock]


netlink内核编程例程:

1)内核模块程序,从 netfilter 的 NF_IP_PRE_ROUTING 点截获的 ICMP 数据报,在将数据报的相关信息传递到一个用户态进程,由用户态进程将信息打印在终端上:

2)如何添加一个自定义的netlink协议类型:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值