Linux Netlink 简介

2014-02-19 linux monitor

Netlink 机制是在 Linux 中作为内核态与用户态的一种通讯机制,它是基于 socket 的!!!怎么样,没有想到,除了 TCP/IP 协议外,这样也可以 ^_^

另外的一个特性是,面向数据报文的无连接消息子系统,有点类似于 UDP 协议。

简介

如果要为新特性添加系统调用,那么相比传统的 ioctl 和 procfs 来说,netlink 要简单的多,只需要将一个常量以及协议类型加入到 netlink.h 文件中即可。然后,内核模块和用户程序可以通过 socket 类型的 API 进行通信。

与其它基于 socket API 实现的通讯协议一样,Netlink 也是异步的,它通过一个 socket 队列来缓存突发的消息。

内核模块和用户测试程序

内核中的 Netlink 相关接口实际上一直在变化,最好的方式是参考内核中相关的实现,例如 TCP/IP 协议相关的诊断模块 sock_diag ,对应应用程序为 iproute2 包。

对于 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 。

和 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 的接口,偶然发现,没有仔细研究