内核Netlink接口剖析

当开发linux内核特性的时候,将必要的内核子系统的详细信息暴露给用户空间的程序是一个比较好的习惯,因为这增强了内核的扩展性。通常来说,软件开发者必须面对这样一个任务:寻找一种好的方法使得用户空间和内核空间进行交互。

NetLinux作为内核接口的一种,担任了这样的功能。维基百科对于它的解释,:

Netlink socketfamily is a Linux
kernel interface
 usedfor inter-process
communication
 (IPC)between the kernel and user-space processes,as
well as between user processes (e.g. Unix domain sockets) or amixture
of both types. However, unlike INET sockets, it cannot traversehost boundaries,
because it addresses processes by their (inherently local) PIDs.

在linux内核2.0系列的时候被并入,2000年之前,当时用来替代Ioctl。不过直到现在这个ioctl也还存在,只不过它比较陈旧,rtnetlink和它完成同样的功能。(rtnetlink或许就是netlink最原本的目的,可能是后来随着netlink的壮大,才慢慢的出现了别的netlink,比如generic netlink)。

设计和实用性

主要是linux系统的跨平台的使用,和linux的设计技巧。有些内核子系统,比如网络和设备需要从用户空间来进行配置和管理。用户态程序需要获得资源,服务和信息,同时配置监控内核子系统(后面将要分析lldpad和DCB子系统的关系)。

内核接口是操作系统非常重要的一部分,提供灵活的内核接口对于内核的体验简直太棒了(不然要想要很多功能,内核就会变得非常臃肿),一方面内核可以保持比较小,另一方面又使得用户空间程序可以进行想要的操作。

netlink有很多好处,比如强大的可扩展性和支持基于事件的通知机制。要理解netlink,主要还是要理解c语言编程,要理解内核的一些基本知识,和BSD sockets。

linux内核接口

linux本身有很多内核和用户接口,但是哪个更好,哪个更加适合在什么场合下使用,还是很值得我们深思的。内核接口应该提供的几个特性有可扩展性、架构可移植、事件通知机制和大数据传输特性。

系统调用,是一个在多种操作系统上(包括Windows)非常通用和标准的接口。然而,linux内核开发者非常不情愿在linux上面添加系统调用,它也不能给每个子模块添加特别的调用,针对的是一些通用的接口,没有针对特殊模块的特殊接口。使用固定的layout格式的接口:ioctl;每一个配置都有一个唯一的ioctl号来识别。因为在内核和用户程序之间需要传递消息,为了让这个消息被识别,这个消息一般会用一个固定格式的数据结构(ioctl调用时,会有一个指向这个结构的指针)来表示,不过呢,如果新的配置需要去改变信息的格式,就会需要使用一个新的ioctl调用了。

虚拟文件系统

主要是对于字符和块设备驱动接口来说,内核给用户程序提供了一种像访问文件一样的方式来和这个驱动进行通信。有/proc和sysfs等,这些接口基本上都是为内核设备的驱动留的。那么proc文件系统呢?原本是设计用来存储进程信息的,缺点是有很多信息不能满足,没有事件通知,不支持大文件传输(限制为一页)。对于应用程序来说并不实际,因为这些信息很难convert成为想要的消息啦。sysfs呢?只是接口(文本)对于应用程序来说,也还是不太实用。

BSD socket

最先开始的socket选项是用AF_INET选项,然后调用相关的ioctl来进行通信。socket选项还有AF_RAW,比如用在内核防火墙子系统里面。而Netlink的好处更是表现在可扩展性(格式不固定,可以任意增加属性)。然后,还有事件通知机制等好处,可有效的传输大数据。

上面提到的所有内核接口的设计特性如下图所示:

Netlink

Netlink家族协议


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

#define NETLINK_UNUSED               1       /* Unused number                              */

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

#define NETLINK_FIREWALL    3       /* Firewalling hook                              */

#define NETLINK_INET_DIAG 4       /* INET socket monitoring                          */

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

#define NETLINK_XFRM           6       /* ipsec */

#define NETLINK_SELINUX                7       /* SELinux event notifications */

#define NETLINK_ISCSI             8       /* Open-iSCSI */

#define NETLINK_AUDIT          9       /* auditing */

#define NETLINK_FIB_LOOKUP        10

#define NETLINK_CONNECTOR       11

#define NETLINK_NETFILTER  12     /* netfilter subsystem */

#define NETLINK_IP6_FW                 13

#define NETLINK_DNRTMSG            14     /* DECnet routing messages */

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

#define NETLINK_GENERIC              16

/* leave room for NETLINK_DM (DM Events) */

#define NETLINK_SCSITRANSPORT 18     /* SCSI Transports */

#define NETLINK_ECRYPTFS    19

#define NETLINK_RDMA          20

#define NETLINK_CRYPTO                21     /* Crypto layer */

#define MAX_LINKS 32

struct sockaddr_nl {

__kernel_sa_family_t      nl_family;          /* AF_NETLINK         */

unsigned short         nl_pad;              /* zero               */

__u32                nl_pid;               /* port ID          */

__u32                nl_groups;        /* multicast groups mask */

};

netlink总线

内核最多支持32根netlink总线,比如nfnetlink和rtnetlink分别是两个不同的总线。rtnetlink总线一般负责网络设备的管理,路由和排队机制等。nfnetlink总线被linux下面所有的防火墙子系统模块使用。

传输方式和单播和多播两种方式。

消息的格式

16B,安装4字节对齐。

学过计算机网络的人都知道,每一种协议的包都有她自己的格式,比如说以太网包,IP包,UDP或者TCP包,我们一直致力于研究FCoE包,FCoE包也有FCoE包的格式(不过FCoE作为存储网的特性,可不是用socket来进行通信的,而是通过文件系统或者块设备啦啦啦)。

Netlink也是通过socket来进行通信的,那么他的包类型是怎么样的呢?

不像TCP协议的头部标志和目的地址是自动产生的,NETLINK因为是SOCK_RAW的模式,他的头部必须由调用者来构造。


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 */

};


Bit offset


0–15


16–31


0


Message length


32


Type


Flags


64


Sequence number


96


PID


128+


Data


/* 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                 */

#define NLM_F_DUMP_INTR           16     /* Dump was inconsistent due to sequence change */

? Message type (16 bits): the type of this message. There are twosorts, data and controlmessages. Data messages depend on the set of actionsthat the given kernel-spacesubsystem allows. Control messages are common to allNetlink subsystems, there arecurrently
four types of control messages, althoughthere are 16 slots reserved (seeNLM MIN TYPE constant in linux/netlink.h). Theexisting control types are:– NLMSG NOOP: no operation, this can be used to implement a Netlinkping utilityto know if a given Netlink bus is
available.– NLMSGERROR: this message contains an error.– NLMSG DONE: this isthe trailing message that is part of a multi-part message. Amulti-part messageis composed of a set of messages all with the NLM F MULTI

??ag set.– NLMSG OVERRUN: this control message type is currently unused.

用户空间是怎么和内核子系统通信的呢?大概可以在这里看的出来。

比如DCB子系统和lldpad程序。

DCB子系统注册了

rtnl_register(PF_UNSPEC, RTM_GETDCB,dcb_doit, NULL, NULL);

rtnl_register(PF_UNSPEC,RTM_SETDCB, dcb_doit, NULL, NULL);

lldpad中就是通过RTM_GETDCB和RTM_SETDCB这两种消息类型来寻址的:


static struct nlmsghdr *start_msg(__u16 msg_type, __u8 arg)

{

struct nlmsghdr *nlh;

struct dcbmsg *d;

struct ifinfomsg *ifi;

/* nlh needs to be free‘d by send_msg() */

nlh = (struct nlmsghdr *)malloc(MAX_MSG_SIZE);

if (NULL == nlh)

return NULL;

memset((void *)nlh, 0, MAX_MSG_SIZE);

nlh->nlmsg_type = msg_type;

         nlh->nlmsg_flags = NLM_F_REQUEST;

nlh->nlmsg_seq = next_rtseq();

nlh->nlmsg_pid = getpid();

switch (msg_type) {

case RTM_GETDCB:

         case RTM_SETDCB:

                   nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct dcbmsg));

                   d = NLMSG_DATA(nlh);

                   d->cmd = arg;

                   d->dcb_family = AF_UNSPEC;

                   d->dcb_pad = 0;

                   break;

case RTM_GETLINK:

nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));

ifi = NLMSG_DATA(nlh);

ifi->ifi_family = AF_UNSPEC;

ifi->ifi_index = arg;

ifi->ifi_change = 0xffffffff;

break;

default:

free(nlh);

nlh = NULL;

break;

}

return nlh;

}

这个函数被调用的情况:


Lldp_dcbx_nl.c:static struct nlmsghdr *start_msg(__u16 msg_type, __u8 arg)

Lldp_dcbx_nl.c:/* free‘s nlh which was allocated by start_msg */

Lldp_dcbx_nl.c:        nlh = start_msg(RTM_GETDCB, DCB_CMD_GSTATE);

Lldp_dcbx_nl.c:        nlh = start_msg(RTM_SETDCB, DCB_CMD_SSTATE);

Lldp_dcbx_nl.c:        nlh = start_msg(RTM_SETDCB, DCB_CMD_PFC_SCFG);

Lldp_dcbx_nl.c:        nlh = start_msg(RTM_SETDCB, DCB_CMD_PFC_SSTATE);

Lldp_dcbx_nl.c:        nlh = start_msg(RTM_SETDCB, cmd);

Lldp_dcbx_nl.c:        nlh = start_msg(RTM_SETDCB, DCB_CMD_SDCBX);

Lldp_dcbx_nl.c:        nlh = start_msg(RTM_GETDCB, DCB_CMD_GCAP);

Lldp_dcbx_nl.c:        nlh = start_msg(RTM_GETDCB, DCB_CMD_GNUMTCS);

Lldp_dcbx_nl.c:        nlh = start_msg(RTM_SETDCB, DCB_CMD_SAPP);

Lldp_dcbx_nl.c:        nlh = start_msg(RTM_SETDCB, DCB_CMD_SET_ALL);

Nltest.c (test):static struct nlmsghdr *start_msg(__u16 msg_type, __u8 arg)

Nltest.c (test):          nlh = start_msg(RTM_SETDCB, DCB_CMD_SSTATE);

Nltest.c (test):          nlh = start_msg(RTM_GETDCB, DCB_CMD_GSTATE);

Nltest.c (test):          nlh = start_msg(RTM_GETDCB, DCB_CMD_PFC_GCFG);

Nltest.c (test):          nlh = start_msg(RTM_GETDCB, DCB_CMD_PFC_GSTATE);

Nltest.c (test):          nlh = start_msg(RTM_GETDCB, cmd);

Nltest.c (test):          nlh = start_msg(RTM_GETDCB, DCB_CMD_GPERM_HWADDR);

Nltest.c (test):          nlh = start_msg(RTM_GETDCB, DCB_CMD_GCAP);

Nltest.c (test):          nlh = start_msg(RTM_SETDCB, DCB_CMD_SNUMTCS);

Nltest.c (test):          nlh = start_msg(RTM_GETDCB, DCB_CMD_GNUMTCS);

Nltest.c (test):          nlh = start_msg(CMD, DCB_CMD_SET_ALL);

Nltest.c (test):          nlh = start_msg(RTM_GETDCB, DCB_CMD_BCN_GCFG);

Nltest.c (test):          nlh = start_msg(RTM_SETDCB, DCB_CMD_BCN_SCFG);

Nltest.c (test):          nlh = start_msg(RTM_GETDCB, DCB_CMD_GAPP);

Nltest.c (test):          nlh = start_msg(RTM_SETDCB, DCB_CMD_SAPP);

Nltest.c (test):          nlh = start_msg(RTM_SETDCB, DCB_CMD_IEEE_SET);

Nltest.c (test):          nlh = start_msg(RTM_GETDCB, DCB_CMD_IEEE_GET);

Nltest.c (test):                   printf("start_msg failed\n");

Nltest.c (test):          nlh = start_msg(RTM_GETLINK, ifindex);

序列号:这是一个消息的序列号。这个和flag的#define NLM_F_ACK在一起使用的话才非常有用(用户程序想要验证请求正确的发出去了)。

端口ID:这是Netlink分配的一个ID,不同的值代表不同的socket通道,默认的值是进程PID。在某些情况下,这个值被设置为0:比如消息来自内核空间,或者想要Netlink来设置这个值。

一些现有的linux内核子系统也可以增加一些额外的固定大小的头部在Netlink头部的后面,使得Netlink bus的多路复用。这就是GeNetlink.

Netlink message的负载时由很多的属性组成的,使用TLV结构呢,哈哈哈。每一个netlink的属性头部使用struct nlattr定义,包括T,L,V字段。TLV(type-length-value)在8914的FIP帧里面就封装了这个。这个结构使得可以创建新的属性,而不必打破内核接口的后向兼容性的优势。


struct nlattr {

__u16           nla_len;

__u16           nla_type;

};

是否使用TLV,取决于程序员,不过使用了就明显更灵活啦。只要在内核模块中添加属性,然后更新用户程序的特性就可以了。

为了更方便的使用,TLV还有内嵌结构呢。。。

参考文献

http://en.wikipedia.org/wiki/Netlink

http://qos.ittc.ku.edu/netlink/netlink.pdf

http://1984.lsi.us.es/~pablo/docs/spae.pdf

Pablo Neira Ayuso?,?1 , Rafael M.Gasca1 and Laurent Lefevre. Communicating between the kernel and user-space in Linuxusing Netlink sockets.2010.

Linux3.2的内核源码

p.s:由于时间和经历关系,此文描述得还不够通透,有时间再补充。

时间: 2024-10-31 13:40:15

内核Netlink接口剖析的相关文章

(升级版)Spark从入门到精通(Scala编程、案例实战、高级特性、Spark内核源码剖析、Hadoop高端)

本课程主要讲解目前大数据领域最热门.最火爆.最有前景的技术——Spark.在本课程中,会从浅入深,基于大量案例实战,深度剖析和讲解Spark,并且会包含完全从企业真实复杂业务需求中抽取出的案例实战.课程会涵盖Scala编程详解.Spark核心编程.Spark SQL和Spark Streaming.Spark内核以及源码剖析.性能调优.企业级案例实战等部分.完全从零起步,让学员可以一站式精通Spark企业级大数据开发,提升自己的职场竞争力,实现更好的升职或者跳槽,或者从j2ee等传统软件开发工程

Linux Kernel(Android) 加密算法总结(三)-应用程序调用内核加密算法接口

在Linux Kernel(Android) 加密算法总结(cipher.compress.digest)文章中,介绍了如何在内核中加入三种不同类型的内核加密算法, 并给出了在内核模块中如何调用他们的实例. 本文将主要介绍,如何在应用程序空间中(user space) 调用内核空间(kernel space)加密模块提供的加密算法API. 方法一:通过调用crypto: af_alg - User-space interface for Crypto API, Herbert Xu <[emai

深入解析Linux内核I/O剖析(open,write实现)

Linux内核将一切视为文件,那么Linux的文件是什么呢?其既可以是事实上的真正的物理文件,也可以是设备.管道,甚至还可以是一块内存.狭义的文件是指文件系统中的物理文件,而广义的文件则可以是Linux管理的所有对象.这些广义的文件利用VFS机制,以文件系统的形式挂载在Linux内核中,对外提供一致的文件操作接口. 从数值上看,文件描述符是一个非负整数,其本质就是一个句柄,所以也可以认为文件描述符就是一个文件句柄.那么何为句柄呢?一切对于用户透明的返回值,即可视为句柄.用户空间利用文件描述符与内

Linux 系统调用 —— fork 内核源码剖析

系统调用流程简述 fork() 函数是系统调用对应的 API,这个系统调用会触发一个int 0x80 的中断: 当用户态进程调用 fork() 时,先将 eax(寄存器) 的值置为 2(即 __NR_fork 系统调用号): ? 执行 int $0x80,cpu 进入内核态: ? 执行 SAVE_ALL,保存所有寄存器到当前进程内核栈中: ? 进入 sys_call,将 eax 的值压栈,根据系统调用号查找 system_call_table ,调用对应的函数: ? 函数返回,执行 RESTOR

oom-kill内核源码剖析

开场白:历史已经成为过去,而过去的却已经埋藏在看不见的沙子里面,成为不了历史 代码存放:/mm/oom_kill.c 主函数: void out_of_memory(int gfp_mask) 主要功能如下:     1)调用select_bad_process(void)函数返回选择删除的进程的task_struct结构体     2)调用oom_kill_process(struct task_struct *p)函数对进程发出终止信号 辅助函数说明:select_bad_process(v

linux内核源码剖析-tcp/ip实现--阅读1

这本书一共有33章,从底层开始往上讲,论述了tcp/ip的实现过程: 数据链层->网络层->socket->传输层: 打算从后面开始学,从传输层开始! 传输层-tcp+udp 网络层(IP,ICMP,IGMP,路由以及邻居子系统和ip组播)

Netlink 内核实现分析(二):通信

在前一篇博文<Netlink 内核实现分析(一):创建>中已经较为具体的分析了Linux内核netlink子系统的初始化流程.内核netlink套接字的创建.应用层netlink套接字的创建和绑定流程,本文来具体的分析一下内核是怎样实现netlink消息在内核和应用进程之间全双工异步通信的. 一.netlink通信数据结构 1.netlink消息报头:struct nlmsghdr struct nlmsghdr { __u32 nlmsg_len; /* Length of message

Linux0.11内核剖析–内核体系结构 &#169;Fanwu

Linux0.11内核剖析–内核体系结构 ©Fanwu <Linux内核完全注释>下载:http://files.cnblogs.com/files/HanBlogs/linux-kernel.pdf(进入pdf后要点击右下角保存喔^_^) 一个完整可用的操作系统主要由 4 部分组成:硬件.操作系统内核.操作系统服务和用户应用程序,如下图所示: 用户应用程序是指那些字处理程序. Internet 浏览器程序或用户自行编制的各种应用程序: 操作系统服务程序是指那些向用户所提供的服务被看作是操作系

wpa_supplicant与内核nl80211通信之Generic Netlink

代码位置: kernel/net/netlink/genetlink.c kernel/include/net/genetlink.h GENL简介 netlink仅支持32种协议类型,这在实际应用中可能并不足够.因此产生了generic netlink(以下简称为genl). generic netlink支持1023(前10个保留不用)个子协议号,弥补了netlink协议类型较少的缺陷. 1 架构及工作原理: Generic Netlink是以自定义的genl_family为基本单位的,每一