Netlink 机制是在 Linux 中作为内核态与用户态的一种通讯机制,它是基于 socket 的!!!怎么样,没有想到,除了 TCP/IP 协议外,这样也可以 ^_^
另外的一个特性是,面向数据报文的无连接消息子系统,有点类似于 UDP 协议。
简介
如果要为新特性添加系统调用,那么相比传统的 ioctl 和 procfs 来说,netlink 要简单的多,只需要将一个常量以及协议类型加入到 netlink.h 文件中即可。然后,内核模块和用户程序可以通过 socket 类型的 API 进行通信。
与其它基于 socket API 实现的通讯协议一样,Netlink 也是异步的,它通过一个 socket 队列来缓存突发的消息。
内核模块和用户测试程序
内核中的 Netlink 相关接口实际上一直在变化,最好的方式是参考内核中相关的实现,例如 TCP/IP 协议相关的诊断模块 sock_diag ,对应应用程序为 iproute2 包。
对于 Netlink,不仅可以实现用户-内核空间的通信还可使现实用户空间两个进程之间,或内核空间两个进程之间的通信。
Netlink 消息格式
消息体包括了两部分:消息头和有效数据载荷;其中,消息头固定为 16 字节,消息体长度可变。另外,整个 Netlink 消息是 4 字节对齐,很多与消息体相关的宏操作。
|<====== 16B ======>|<======================= 2^32-16B =============================>|
+-------------------+----------------------------------------------------------------+
| msg header | msg body |
+-------------------+----------------------------------------------------------------+
| | |
| |<--------------------- NLMSG_SPACE() -------------------------->|
| |
|<------------------------------- NLMSG_LENGTH() ----------------------------------->|
对于上述的 Netlink 消息,可以通过几个宏的进行操作:
NLMSG_SPACE(MAX_PAYLOAD)、NLMSG_LENGTH(LEN)
在申请内存时使用,两个宏都会返回 4 字节对齐的最小长度值,前者的入参为 body 大小,而后者为 header+body 的长度。NLMSG_DATA(nlh)
返回 Netlink 消息中数据部分的首地址,写入和读取消息数据部分时会使用。
其中消息头定义通过 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 PID */
};
消息头结构体中各成员详细解释如下:
nlmsg_len
包括消息头在内的整个消息长度,按字节计算。nlmsg_type
消息的类型,即是数据还是控制消息。目前类型有 4 中:A) NLMSG_NOOP 空操作;B) NLMSG_ERROR 错误消息;C) NLMSG_DONE 若内核返回多个消息,最后一条为该类型,其它的消息的 nlmsg_flags 属性被设置 NLM_F_MULTI 位有效;D) NLMSG_OVERRUN 数据被覆盖。nlmsg_flags
消息中的额外说明信息,通常用 NLM_F_XXX 表示,其中常用的如下:- NLM_F_MULTI,消息从用户到内核是同步的立刻完成,而从内核用户则需要排队。如果用户发送的请求中有NLM_F_DUMP标志位,那么内核就会向用户发送多个 Netlink 消息,除了最后的消息外,其余都设置了该位。
- NLM_F_REQUEST,标示请求消息。所有从用户到内核的消息都要设置该位,否则内核将返回一个 EINVAL 无效参数的错误。
- NLM_F_ACK,内核对来自用户空间的 NLM_F_REQUEST 消息的响应。
- NLM_F_ECHO,用户发送内核发送时置位,则说明用户要求内核以单播的方式再发送给用户,也就是 “回显” 。
nlmsg_seq
消息序号。类似于 UDP,Netlink 也可能会丢失数据,如果用户要保证其发送的每条消息都能成功被内核收到,那么在发送请求时需要设置序号,然后检查返回结果的该值是否相同。对于内核向用户发送的广播消息,该字段为 0 。nlmsg_pid
Netlink 会为用户空间和内核空间建立的链接分配一个唯一标示,用于确保内核返回给用户时的进程 ID 。
Netlink 地址结构体
和 TCP/IP 协议中的地址结构体和标准结构体相似,Netlink 的结构体如下:
----- sock标准结构体 TCP/IP结构体 Netlink结构体
struct sockaddr; struct sockaddr_in; struct sockaddr_nl;
+------------------+ +------------------+ +------------------+
2B | sa_family | 2B | sin_family | 2B | nl_family |
+------------------+ +------------------+ +------------------+
14B | sa_data[14] | 2B | sin_port | 2B | nl_pad |
+------------------+ +------------------+ +------------------+
4B | sin_addr | 4B | nl_pid |
+------------------+ +------------------+
8B | __pad | 4B | nl_groups |
+------------------+ +------------------+
对于 Netlink 地址结构体,详细内容如下:
struct sockaddr_nl {
sa_family_t nl_family; // 总是为AF_NETLINK
unsigned short nl_pad; // 目前未用到,填充为0
__u32 nl_pid; // process pid
__u32 nl_groups; // multicast groups mask
};
内核实现
其中内核实现在 net/netlink/af_netlink.c
文件中。
初始化
Netlink 相关的内容通过 core_initcall()
指定,其初始化函数为 netlink_proto_init()
,简化后的内容如下。
static const struct net_proto_family netlink_family_ops = {
.family = PF_NETLINK,
.create = netlink_create,
.owner = THIS_MODULE, /* for consistency 8) */
};
static struct pernet_operations __net_initdata netlink_net_ops = {
.init = netlink_net_init,
.exit = netlink_net_exit,
};
static int __init netlink_proto_init(void)
{
... ...
sock_register(&netlink_family_ops);
register_pernet_subsys(&netlink_net_ops);
... ...
}
core_initcall(netlink_proto_init);
其中在创建命名空间时,会调用 netlink_net_init()
函数,而 netlink_family_ops
变量则定义了 sock 相关的函数,也就是如何创建 Netlink socket 。
而 netlink_net_init()
函数就非常简单了,就是在 procfs
中创建 /proc/net/netlink
文件。
创建 Socket
如上述 netlink_family_ops
变量中的定义,创建 Socket 时最终会调用到 netlink_create()
函数。
static const struct proto_ops netlink_ops = {
.family = PF_NETLINK,
.owner = THIS_MODULE,
.release = netlink_release,
.bind = netlink_bind,
.connect = netlink_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = netlink_getname,
.poll = netlink_poll,
.ioctl = sock_no_ioctl,
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = netlink_setsockopt,
.getsockopt = netlink_getsockopt,
.sendmsg = netlink_sendmsg,
.recvmsg = netlink_recvmsg,
.mmap = netlink_mmap,
.sendpage = sock_no_sendpage,
};
static struct proto netlink_proto = {
.name = "NETLINK",
.owner = THIS_MODULE,
.obj_size = sizeof(struct netlink_sock),
};
static int __netlink_create(struct net *net, struct socket *sock,
struct mutex *cb_mutex, int protocol)
{
... ...
sock->ops = &netlink_ops;
sk = sk_alloc(net, PF_NETLINK, GFP_KERNEL, &netlink_proto);
sock_init_data(sock, sk);
init_waitqueue_head(&nlk->wait);
... ..
}
也就是创建 struct sock 并初始化,其中最重要的是 netlink_ops 变量,定义了 sock 相关的操作函数,如常见的 sendmsg()、connect() 等操作。
也就是说,接下来我们可以针对不同的接口进行分析。
参考
Netlink 相关内容可以参考内核中的文档,以及 www.linuxfoundation.org 中关于 netlink 协议的基本介绍。
用户端的实现还可以参考 Netlink Protocol Library Suite (libnl),一个不错的实现,还包括了 Python 的接口,偶然发现,没有仔细研究