一、概述
Generic Netlink 是内核专门为了扩展 netlink 协议簇而设计的“通用netlink协议簇”。由于 netlink 协议最多支持 32 个协议簇,目前 Linux4.1 的内核中已经使用其中 21 个,对于用户需要定制特殊的协议类型略显不够,而且用户还需自行在 include/linux/netlink.h 中添加簇定义,但有时不方便,为此 Linux 设计了这种通用 Netlink 协议簇,用户可在此之上定义更多类型的子协议。
Generic Netlink 使用 NETLINK_GENERIC 类型协议簇,同样基于 netlink 子系统。具体框架如下:
Genetlink 消息基于这个消息结构类型并定制化为如下结构:
其中 family 头对于 Genetlink 来说就是 Generic 消息头 genlmsghdr,接下来是可选的用户特定消息头,最后才是可选的有效载荷,即一个个消息属性实例。Genetlink 消息是命令驱动式的,即每一条消息的 genlmsghdr 中都指明了当前消息的 cmd 消息命令,这些消息 cmd 命令由用户自行定义。内核在接收到用户的 genl 消息后,首先会对命令 cmd 做判断,找到对应的消息处理结构(可能会执行 attr 有效性检查),然后才会去调用消息处理回调函数从消息载荷区中读取并处理其所需要的的 attr 属性载荷。
二、主要数据
2.1、struct nlmsghdr
struct nlmsghdr {
__u32 nlmsg_len; /* Length of message including header */
__u16 nlmsg_type; /* Message content */
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process port ID */
};
struct nlmsghdr 为 netlink socket 自己的消息头,netlink 的内核实现将利用这个消息头来多路复用和多路分解以及其它的一些控制,因此它也被称为netlink 控制块。因此,应用在发送 netlink 消息时必须提供该消息头。
(1)字段 nlmsg_len 指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小;
(2)字段 nlmsg_type 用于应用内部定义消息的类型,它对 netlink 内核实现是透明的,因此大部分情况下设置为 0;
字段 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 */
/* Modifiers to GET request */
#define NLM_F_ROOT 0x100 /* specify tree root */
#define NLM_F_MATCH 0x200 /* return all matching */
#define NLM_F_ATOMIC 0x400 /* atomic GET */
#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH)
/* Modifiers to NEW request */
#define NLM_F_REPLACE 0x100 /* Override existing */
#define NLM_F_EXCL 0x200 /* Do not touch, if it exists */
#define NLM_F_CREATE 0x400 /* Create, if it does not exist */
#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 指示在表末尾添加新的条目。
(3)内核需要读取和修改这些标志,对于一般的使用,用户把它设置为 0 就可以,只是一些高级应用(如 netfilter 和路由 daemon 需要它进行一些复杂的操作),字段 nlmsg_seq 和 nlmsg_pid 用于应用追踪消息,前者表示顺序号,后者为消息来源进程 ID。下面是一个示例:
#define MAX_MSGSIZE 1024
char buffer[] = "An example message";
struct nlmsghdr nlhdr;
nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));
strcpy(NLMSG_DATA(nlhdr),buffer);
nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer));
nlhdr->nlmsg_pid = getpid(); /* self pid */
nlhdr->nlmsg_flags = 0;
结构 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);
2.2、struct genlmsghdr
struct genlmsghdr {
__u8 cmd;
__u8 version;
__u16 reserved;
};
Generic Netlink 消息头结构。Generic Netlink 消息头比较简单,仅包含了两个字段。
cmd 表示消息命令,对于用户自己定义的每个子协议类型都需要定义特定的消息命令集,这里该字段表示当前消息的消息命令;
version 字段表示版本控制,可以在在不破坏向后兼容性的情况下修改消息的格式,可以不使用该字段;
reserved 字段保留。
2.3、struct nlattr
从 nlattr 类型定义来看,是非常简单,但是 OVS 使用该数据结构来定义 action,nlattr 自身是不存储数据,而使用来定义数据格式的,OVS 也会使用 skb 来存储真实数据。
nlattr 数据结构定义
struct nlattr {
uint16_t nla_len; //数据长度
uint16_t nla_type; //数据类型
};
使用 nlattr 构造的数据结构如下图所示:
相关函数的定义如下:
nla_len 函数
static inline int nla_len(const struct nlattr *nla) //返回数据的长度
{
return nla->nla_len - NLA_HDRLEN; //数据总长度减去头长度
}
nla_data 函数
static inline void *nla_data(const struct nlattr *nla) //返回数据的首地址
{
return (char *) nla + NLA_HDRLEN; //当前指针加上头长度
}
nla_next 函数
//返回下一个数据的首地址
static inline struct nlattr *nla_next(const struct nlattr *nla, int *remaining)
{
int totlen = NLA_ALIGN(nla->nla_len);
// //当remaining为0时,说明数据已经取完,返回值不能使用
*remaining -= totlen;<span style="white-space:pre"> </span>
return (struct nlattr *) ((char *) nla + totlen);
}
(SAW:Game Over!)