Author:Onceday Date:2023年1月3日
漫漫长路,才刚刚开始…
参考文档:
Unfortunately the protocol has evolved over the years, in an organic and undocumented fashion, making it hard to coherently explain. To make the most practical sense this document starts by describing netlink as it is used today and dives into more “historical” uses in later sections.
这一段话来自The Linux Kernel Documentation的Introduction to Netlink,它描述了一个事实:netlink缺乏系统的文档描述。这也是本文撰写的动机所在,如果不能了解netlink
的”历史用途“,那么很容易陷入片面的观点中,从而导致各种意外发生。
Netlink是一种基于网络的机制,允许在内核内部以及内核与用户层之间进行通信。最早在内核2.2引入,旨在替代笨拙的IOCTL,IOCTL不能从内核向用户空间发送异步消息,而且必须定义IOCTL号。
当前活动的netlink连接信息可以从/proc/net/netlink
中看到。
代码位于内核源码路径net/netlink
。
Netlink运行机制可用下图来概括:
相比去其他IPC(进程间通信)机制,Netlink突出的优点如下:
通过自定义一种新的协议并加入协议族,即可通过socket API使用Netlink协议完成进程间数据交换。
socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); //Create a socket using user defined protocol NETLINK_TEST.
domain
使用AF_NETLINK
,协议类型对应内核系统的不同系统组件,这里NETLINK_TEST
是用户自定义的协议类型,最大支持32种协议类型,每个协议都对应一个多播组,SOCK_RAW
是默认固定的(要求原生套接字,原生类型即可)。
系统预定义了很多协议,主要的使用对象就是内核的网络类子系统,如下:
#
#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 /* Unused number, formerly ip_queue */
#define NETLINK_SOCK_DIAG 4 /* 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 NETLINK_SMC 22 /* SMC monitoring */
#define NETLINK_INET_DIAG NETLINK_SOCK_DIAG
#define MAX_LINKS 32
上述这些协议已经为不同的系统应用所使用,每种不同的应用都有特有的传输数据的格式,因此如果用户不使用这些协议,需要加入自己定义的协议号。对于每一个Netlink协议类型,可以有多达 32多播组,每一个多播组用一个位表示,Netlink 的多播特性使得发送消息给同一个组仅需要一次系统调用,因而对于需要多播消息的应用而言,大大地降低了系统调用的次数。
自从2005年Generic Netlink
出来之后,Classic Netlink
(NETLINK ROUTE等)便不再准备新增定义了,因此目前有大量的子系统使用Generic Netlink
,但同时,Netlink ROUTE/XFRM/AUDIT/...
等旧定义(协议)依旧在被使用。
在用户空间,使用netlink
很简单,就和socket一模一样:
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
/* format the request */
send(fd, &request, sizeof(request));
n = recv(fd, &response, RSP_BUFFER_SIZE);
/* interpret the response */
虽然使用简单,但netlink
涉及的内容非常多,比如内核空间和用户空间、General Netlink和Classic Netlink、不同Netlink协议的格式、二次封装的库(如RTNL(Linux IP setting and routing))、高级语言库(如pyroute2)等等。
netlink作为一个内核子系统,需要在内核启动的时候首先进行初始化,即下面函数:
static int __init netlink_proto_init(void); #net/netlink/af_netlink.c
其主要步骤如下:
通过proto_register()
向内核注册名为NETLINK
的套接字协议。
可选的BPF(伯克利包过滤)和PROC_FS(进程信息文件系统)支持。对应/proc/net/netlink
文件。
netlink table
数组分配空间和初始化。netlink table
是Size为32的数组,每一个成员节点对应NETLINK ROUTE
这样的具体协议,最多32个。成员节点组成如下:
struct netlink_table {
struct rhashtable hash;
struct hlist_head mc_list;
struct listeners __rcu *listeners;
unsigned int flags;
unsigned int groups;
struct mutex *cb_mutex;
struct module *module;
int (*bind)(struct net *net, int group);
void (*unbind)(struct net *net, int group);
bool (*compare)(struct net *net, struct sock *sock);
int registered;
};
这个表(netlink table
数组)是整个netlink实现的关键一步,每种协议类型占数组中的一项,后续内核中创建的不同种协议类型的netlink都将保存在这个表中。
添加一个虚拟的USERSOCK
实体,让NETLINK USERSOCK
协议能继续工作。
通过sock_register
注册协议处理函数,将netlink的socket
创建处理函数注册到内核中。后续应用层创建netlink类型的socket
时将会调用该协议处理函数。如下:
static const struct net_proto_family netlink_family_ops = {
.family = PF_NETLINK,
.create = netlink_create,
.owner = THIS_MODULE, /* for consistency 8) */
};
在用户空间创建netlink时,因此socket(PF_NETLINK,...)
里面应该填写PF_NETLINK
,并且socket()
调用处理过程由netlink_create
负责完成。
通过register_pernet_subsys()
来初始化网络子系统(network namespace subsystem),对于不同的网络命名空间,都会调用下面结构体定义的操作。
static struct pernet_operations __net_initdata netlink_net_ops = {
.init = netlink_net_init,
.exit = netlink_net_exit,
};
netlink_net_init()
会在文件系统中为每个网络命名空间创建一个proc入口,而netlink_net_exit()
就是则销毁。
此外,还会进一步调用下面结构体的操作:
static struct pernet_operations netlink_tap_net_ops = {
.init = netlink_tap_init_net,
.id = &netlink_tap_net_id,
.size = sizeof(struct netlink_tap_net),
};
这个tap
类功能是用来过滤和分割不同network namespace
的消息。
最后调用rtnetlink_init()
创建NETLINK_ROUTE
协议类型的Netlink,用来传递网络路由子系统、邻居子系统、接口设置、防火墙等消息。整个Netlink子系统初始化完成。
rtnetlink
是在netlink
上进一步封装的C库,主要针对网络相关的内核子系统,更加易用。
完成以上步骤后,整个Netlink子系统就初始化完成了。后续使用,将基于此处注册的套接字协议。
Netlink套接字可以是SOCK_RAW套接字,也可以是SOCK_DGRAM套接字。内核和用户空间都可以使用Netlink套接字,只是调用的方法不同,用户空间使用传统的socket系统调用,内核态使用__netlink_kernel_create
函数。最终都会调用__netlink_create
方法。
对于用户空间, 在调用socket()
之后,便由netlink_create
完成实际的创建过程。
SOCK_RAW
或者SOCK_DGRAM
。protocal
是否超过MAX_LINKS=32
。netlink table
获取对应protocal
的互斥量、bind/unbind
绑定和解绑函数指针。__netlink_create
函数。在__netlink_create()
会完成实际的sock
对象创建,如下:
sk_alloc
分配netlink
协议族的sock
对象内存。sock_init_data
初始化sock
对象。netlink->wait
。sock_destruct
析构函数。socket
是通用的伯克利套接字对象,而sock
是socket
对象的具体网络层内容。
对于内核模块,创建时需要指定netlink kernel configuration
配置,其字段如下:
/* optional Netlink kernel configuration parameters */
struct netlink_kernel_cfg {
unsigned int groups;
unsigned int flags;
void (*input)(struct sk_buff *skb);
struct mutex *cb_mutex;
int (*bind)(struct net *net, int group);
void (*unbind)(struct net *net, int group);
bool (*compare)(struct net *net, struct sock *sk);
};
然后调用__netlink_kernel_create
函数,其内部主要操作如下:
socket
对象,协议族为PF_NETLINK
,协议类型为SOCK_DGRAM
。__netlink_create()
里面初始化实际的sock对象。cfg->groups
来申请listeners
的bitmap
数组内存,默认8字节对齐,即groups对齐到64的倍数。input
函数。portid=0
将sock
对象插入到对应协议表项(netlink_table[protocal]
)的哈希表中。netlink_table[protocal]
表项的各类属性值、绑定/解绑函数等。同类协议,只有第一个注册的netlink kernel configuration
配置会写入,后续再注册,并不会更改netlink_table[protocal]
表项的值。
socket套接字协议族有一批固定的抽象接口函数,但netlink只实现了其中某一些操作,如下:
参见:net/netlink/af_netlink.c .... line:2820.
socket协议标准操作接口 | netlink协议操作接口 | 描述 |
---|---|---|
release() | netlink_release() | 释放套接字 |
bind() | netlink_bind() | 绑定套接字 |
connect() | netlink_connect() | 连接套接字 |
socketpair() | sock_no_socketpair() | 未实现,返回错误代码 |
accept() | sock_no_accept() | 未实现,返回错误代码 |
getname() | netlink_getname() | 获取套接字名字 |
poll() | datagram_poll() | 数据报轮询,默认函数 |
ioctl() | netlink_ioctl() | 未实现,返回错误代码 |
compat_ioctl() | \ | 未实现 |
gettstamp() | \ | 未实现 |
listen() | sock_no_listen() | 未实现,返回错误代码 |
shutdown() | sock_no_shutdown() | 未实现,返回错误代码 |
setsockopt() | netlink_setsockopt() | 设置端口属性 |
getsockopt() | netlink_getsockopt() | 获取端口属性 |
show_fdinfo() | \ | 未实现 |
sendmsg() | netlink_sendmsg() | 通过套接字发送信息 |
recvmsg() | netlink_recvmsg() | 通过套接字获取信息 |
mmap() | sock_no_mmap() | 未实现,返回错误代码 |
sendpage() | sock_no_sendpage() | 未实现,返回错误代码 |
splice_read() | \ | 未实现 |
set_peek_off() | \ | 未实现 |
peek_len() | \ | 未实现 |
read_sock() | \ | 未实现 |
read_skb() | \ | 未实现 |
sendpage_locked() | \ | 未实现 |
sendmsg_locked() | \ | 未实现 |
set_rcvlowat() | \ | 未实现 |
上述各类netlink操作以函数指针的形式存放在结构体中,如下:
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 = datagram_poll,
.ioctl = netlink_ioctl,
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = netlink_setsockopt,
.getsockopt = netlink_getsockopt,
.sendmsg = netlink_sendmsg,
.recvmsg = netlink_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
该结构体在__netlink_create
函数中挂载到每一个创建的套接字的ops
字段上。
sock->ops = &netlink_ops;
所以一般在用户空间可以使用的函数如下:
int socket(int domain, int type, int protocol);
该函数用来创建一个套接字,并返回一个描述符,该描述符可以用来访问该套接字。protocol参数设置为0表示使用默认协议。
int bind( int socket, const struct sockaddr *address, size_t address_len);
把通过socket()创建的套接字命名,即绑定到具体的地址上,从而让它可以被其他的进程使用。
int sendto(int sockfd, void *buffer, size_t len, int flags, struct sockaddr *to, socklen_t tolen);
把缓冲区buffer中的信息送给指定的IP端口程序,buffer存放将要发送的数据,len是buffer长度,to是要发送数据到的程序IP端口,tolen是to参数长度。
int recvfrom(int sockfd, void *buffer, size_t len,int flags, struct sockaddr *src_from, socklen_t *src_len);
把发送给程序的信息存储在缓冲区buffer中,并记录数据来源的程序IP端口。buffer存放接收的数据,len是buffer长度,src_from是数据来源程序IP端口,src_len是src_from长度。
使用netlink第一步便是创建套接字对象,如下:
/*Create netlink socket*/
skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
由2.1节 netlink子系统初始化可知,netlink套接字会按照不同网络命名空间单独存在,不会混杂通信。在用户空间,socket()是一个系统调用,最终根据不同架构,将进入不同的系统调用,一般有socketcall
和socket
两种系统调用,但最终调用的函数是相同的,网络相关系统调用所在源文件位置为:net/socket.c
。
对于socket调用,内核接口为:
int __sys_socket(int family, int type, int protocol);
注意,这里的family填写的是AF_NETLINK
,但是前面2.1节提到,netlink子系统注册的是PF_NETLINK
,明显两者的值是相等的。AF
是address family
的缩写,PF
是protocol family
的缩写,两者是一样的,PF在AF上封装一层宏定义。
**__sys_socket()**函数里做以下几件事情:
调用__sys_socket_create
,在这个过程主要处理SOCK_CLOEXEC
和SOCK_NONBLOCK
这两个标识,前者用来在fork子进程时自动关闭父进程的文件描述符。后者用来创建非阻塞IO。
然后调用__sock_create
,会在当前进程上下文的网络命名空间里创建套接字。
使用sock_alloc
函数分配通用伯克利套接字的结构体空间,然后赋值套接字类型(type = SOCK_RAW)。
通过family
参数在net_families
数组查找对应协议族注册表项,此处对应2.1节 sock_register注册过程,注册表项包含以下信息:
static const struct net_proto_family netlink_family_ops = {
.family = PF_NETLINK,
.create = netlink_create,
.owner = THIS_MODULE, /* for consistency 8) */
};
接下来调用对应协议族的create函数。对于family=AF_NETLINK
,create
即上面的netlink_create
函数。netlink_create
函数的主要操作在2.2节已经介绍过了。此外,2.3节介绍的netlink操作也会以结构体指针的形式挂载到sock->ops
上。
在完成套接字创建后,会使用sock_map_fd
函数将套接字映射到对应的socket
文件中,并返回对应的文件句柄。
如果上述过程出现错误,那么会调用sock_release
函数来释放已分配的套接字,该函数里也会调用sock->ops->release
函数来释放套接字对象,即2.3节中的netlink_release()函数,此外也会释放已获得文件句柄。
使用netlink第二步就是绑定套接字对象了:
//bind to skfd with saddr.
if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) {error("xxx")};
对于struct sockaddr
结构体,其是一种泛型结构体,第一个成员是指定的,但后面的成员是各协议族自己决定。比如对于netlink
,其实际字段如下,需要指定port id
和groups
。
struct sockaddr {
sa_family_t sa_family; /* address family, AF_xxx */
union {
char sa_data_min[14]; /* Minimum 14 bytes of protocol address */
DECLARE_FLEX_ARRAY(char, sa_data);
};
};
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 */
};
因此实际代码一般如下:
struct sockaddr_nl saddr;
saddr.nl_family = AF_NETLINK; //AF_NETLINK
saddr.nl_pid = USER_PORT; //netlink portid, same as kernel.
saddr.nl_groups = 0;
//bind to skfd with saddr.
if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) {error("xxxx")};
bind
对应的系统调用函数为__sys_bind
,首先会尝试根据skfd
获取对应的套接字,然后调用sock->ops->bind
函数来执行实际的处理过程,即netlink_bind()
函数,具体如下:
AF_NETLINK
。netlink_realloc_groups
函数里进行操作,如果对应protocol未注册,将创建失败。如果当前套接字的groups位图Size小于对应协议的groups位图Size,那么将分配对应内存,然后保存当前的groups位图Size。groups
值和对应协议(此协议指NETLINK ROUTE等协议)的netlink_bind
函数来绑定对应的groups。一般对应下面两个系统调用:
getsockopt(int fd, int level, int optname, char *optval, int *optlen);
setsockopt(int fd, int level, int optname, char *optval, int optlen);
它们最终使用2.3节操作中的netlink_setsockopt、netlink_getsockopt函数,目前支持以下设置选项:
set选项名称 | get选项名称 | 描述 |
---|---|---|
NETLINK_PKTINFO | NETLINK_PKTINFO | |
NETLINK_ADD_MEMBERSHIP | ||
NETLINK_DROP_MEMBERSHIP | ||
NETLINK_LIST_MEMBERSHIPS | ||
NETLINK_BROADCAST_ERROR | NETLINK_BROADCAST_ERROR | |
NETLINK_NO_ENOBUFS | NETLINK_NO_ENOBUFS | |
NETLINK_LISTEN_ALL_NSID | ||
NETLINK_CAP_ACK | NETLINK_CAP_ACK | |
NETLINK_EXT_ACK | NETLINK_EXT_ACK | |
NETLINK_GET_STRICT_CHK | NETLINK_GET_STRICT_CHK |
在用户空间,有7个函数可以使用,如:
connect(int fd, struct sockaddr *uservaddr, int addrlen);
send(int fd, void *buff, size_t len, unsigned int flags);
sendmsg(int fd, struct user_msghdr *msg, unsigned int flags);
sendto(int fd, void *buff, size_t len, unsigned int flags, struct sockaddr *addr, int addr_len);
recv(int fd, void *ubuf, size_t size, unsigned int flags);
recvmsg(int fd, struct user_msghdr *msg, unsigned int flags);
recvfrom(int fd, void *ubuf, size_t size, unsigned int flags, struct sockaddr addr, int *addr_len);
connect()
一般和send/recv
函数一起使用,面向连接对象,sendto
和recvfrom
面向无连接对象。Netlink的套接字类型包含SOCK_RAW
和SOCK_DGRAM
两种套接字,所以一般使用sendto
和recvfrom
两个函数,不过Netlink也实现了connect
函数。
这些用户空间的函数,最终调用两个函数netlink_sendmsg
和netlink_recvmsg
,需要声明一个目标地址:
struct sockaddr_nl daddr;
daddr.nl_family = AF_NETLINK; //AF_NETLINK
daddr.nl_pid = USER_PORT; //netlink portid, same as kernel.
daddr.nl_groups = 0;
关键的三个参数如上,需要指定,否则会导致发送失误。
此外,需要着重注意的地方是void *buff
和void *ubuf
,它们实际对应消息字段。该消息字段根据协议(NETLINK_ROUTE等)的不同,也会有不同的格式要求。
缓冲区指针(buff/ubuf)来到内核后,挂载在__sys_sendto()
函数的栈变量struct msghdr
下,在内核的处理过程中,msg包含所有相关信息,但最终存入(sock buffer)缓存区的还是缓冲区指针(buff/ubuf)内容。
对于netlink_sendmsg
函数,主要根据分下面两种发送模式:
netlink_broadcast
,组播,即groups不为0。netlink_unicast
,单播,单播和组播可共存,且不会重复发送同一个消息。对于netlink_recvmsg
函数,直接在缓存区收包,可以指定标识,从而收取所有网络命名空间的包。
netlink报文的头部具有固定格式,大小为16个字节,如下:
struct nlmsghdr {
__u32 nlmsg_len; /* Length of message including header */
__u16 nlmsg_type; /* Message content type */
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process port ID */
};
整个消息的长度用nlmsg_len
来指定,包括首部自身和任何需要的填充字节。nlmsg_pid
是发送消息的进程的端口ID。nlmsg_seq
表示序列号,用于排列消息,并不是必须的。
nlmsg_type有以下几种类型,也可以填为0
,但是其值不能大于NLMSG_MIN_TYPE
:
#define NLMSG_NOOP 0x1 /* Nothing. */-----不执行任何动作,必须将该消息丢弃。
#define NLMSG_ERROR 0x2 /* Error */-----消息发生错误。
#define NLMSG_DONE 0x3 /* End of a dump */-----标识分组消息的末尾。
#define NLMSG_OVERRUN 0x4 /* Data lost */-----缓冲区溢出,表示某些消息已经丢失。
#define NLMSG_MIN_TYPE x10 /* < 0x10: reserved control messages */
nlmsg_flags有以下的可选值,不同值之间可以搭配,组合值将有新的含义:
/* Flags values */
#define NLM_F_REQUEST 0x01 /* It is request message. */
#define NLM_F_MULTI 0x02 /* Multipart message, terminated by NLMSG_DONE */
#define NLM_F_ACK 0x04 /* Reply with ack, with zero or error code */
#define NLM_F_ECHO 0x08 /* Echo this request */
#define NLM_F_DUMP_INTR 0x10 /* Dump was inconsistent due to sequence change */
#define NLM_F_DUMP_FILTERED 0x20 /* Dump was filtered as requested */
/* 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 */
netlink的消息头后面跟着的是消息的有效载荷部分,它采用的是格式为“类型—长度—值”,简写TLV。其中类型和长度使用属性头nlattr
来表示。其中nla_len
表示属性长度;nla_type
表示属性类型,其值定义在include\net\netlink.h
中。
/*
* <------- NLA_HDRLEN ------> <-- NLA_ALIGN(payload)-->
* +---------------------+- - -+- - - - - - - - - -+- - -+
* | Header | Pad | Payload | Pad |
* | (struct nlattr) | ing | | ing |
* +---------------------+- - -+- - - - - - - - - -+- - -+
* <-------------- nlattr->nla_len -------------->
*/
struct nlattr {
__u16 nla_len;
__u16 nla_type;
};
/*
* nla_type (16 bits)
* +---+---+-------------------------------+
* | N | O | Attribute Type |
* +---+---+-------------------------------+
* N := Carries nested attributes
* O := Payload stored in network byte order
*
* Note: The N and O flag are mutually exclusive.
*/
#define NLA_F_NESTED (1 << 15)
#define NLA_F_NET_BYTEORDER (1 << 14)
#define NLA_TYPE_MASK ~(NLA_F_NESTED | NLA_F_NET_BYTEORDER)
#define NLA_ALIGNTO 4
#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
#define NLA_HDRLEN ((int) NLA_ALIGN(sizeof(struct nlattr)))
对于netlink消息,其负载有一个枚举类型的数据,定义如下:
/* Generic 32 bitflags attribute content sent to the kernel.
*
* The value is a bitmap that defines the values being set
* The selector is a bitmask that defines which value is legit
*
* Examples:
* value = 0x0, and selector = 0x1
* implies we are selecting bit 1 and we want to set its value to 0.
*
* value = 0x2, and selector = 0x2
* implies we are selecting bit 2 and we want to set its value to 1.
*
*/
struct nla_bitfield32 {
__u32 value;
__u32 selector;
};
#define NLMSG_ALIGNTO 4U
#define NLMSG_ALIGN(len) (((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1))
// 向上对齐到4字节大小
#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
// netlink报文头部大小
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
// 计算netlink消息的真实长度,消息体+消息头
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
// 向上对齐到4字节,且大于NLMSG_LENGTH(len)字节
#define NLMSG_DATA(nlh) ((void *)(((char *)nlh) + NLMSG_HDRLEN))
// 取得netlink消息的数据部分的首地址
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
(struct nlmsghdr *)(((char *)(nlh)) + \
NLMSG_ALIGN((nlh)->nlmsg_len)))
// 用于得到下一个消息的首地址,同时len变为剩余消息的长度
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len <= (len))
// 判断netlink消息长度是否OK
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
// 返回netlink消息负载payload的长度
位于头文件include/net/netlink.h
中,netlink报文格式可参考如下:
Message Format:
<--- nlmsg_total_size(payload) --->
<-- nlmsg_msg_size(payload) ->
+----------+- - -+-------------+- - -+-------- - -
| nlmsghdr | Pad | Payload | Pad | nlmsghdr
+----------+- - -+-------------+- - -+-------- - -
nlmsg_data(nlh)---^ ^
nlmsg_next(nlh)-----------------------+
Payload Format:
<---------------------- nlmsg_len(nlh) --------------------->
<------ hdrlen ------> <- nlmsg_attrlen(nlh, hdrlen) ->
+----------------------+- - -+--------------------------------+
| Family Header | Pad | Attributes |
+----------------------+- - -+--------------------------------+
nlmsg_attrdata(nlh, hdrlen)---^
下面是netlink消息构建函数:
函数名 | 功能 |
---|---|
nlmsg_new() | 创建一个新的netlink消息 |
nlmsg_put() | 添加一个新的netlink消息到sock buffer中 |
nlmsg_put_answer() | 基于nlmsg_put()的回调函数 |
nlmsg_end() | 最终确定下来netlink消息 |
nlmsg_get_pos() | 返回netlink消息中目前的位置 |
nlmsg_trim() | 对netlink消息中部分内容进行裁剪 |
nlmsg_cancel() | 终止netlink消息的构造 |
nlmsg_free() | 释放一个netlink消息 |
nlmsg_append() | 在一个存在的nlmsg消息后面继续添加数据 |
下面是常见的消息发送函数:
函数名 | 功能 |
---|---|
nlmsg_multicast() | 对一些groups广播消息 |
nlmsg_unicast() | 对一个socket单播消息 |
nlmsg_notify() | 发送通告消息 |
下面是常见的消息长度计算函数:
函数名 | 功能 |
---|---|
nlmsg_msg_size(payload) | length of message w/o padding,不包含结尾填充 |
nlmsg_total_size(payload) | length of message w/ padding,包含结尾填充 |
nlmsg_padlen(payload) | length of padding at tail,结尾填充长度 |
下面是常见的消息负载访问函数:
函数名 | 功能 |
---|---|
nlmsg_data(nlh) | 消息负载的头部地址 |
nlmsg_len(nlh) | 消息负载的长度 |
nlmsg_attrdata(nlh, hdrlen) | 负载的属性数据的头地址 |
nlmsg_attrlen(nlh, hdrlen) | 属性数据的长度 |
下面是常见的消息解析函数:
函数名 | 功能 |
---|---|
nlmsg_ok(nlh, remaining) | 在剩余的字节里是否存在合适的netlink消息 |
nlmsg_next(nlh, remaining) | 获取下一个netlink消息 |
nlmsg_parse() | 解析一个消息的属性 |
nlmsg_find_attr() | 找到一个消息里的属性 |
nlmsg_for_each_msg() | 遍历所有的消息 |
nlmsg_validate() | 验证一个netlink消息 |
nlmsg_for_each_attr() | 遍历所有的属性 |
nlmsg_report() | 检测是否需要返回应答 |
nlmsg_seq() | 返回一个消息的序列号 |
nl_dump_check_consistent() | 检查在dump期间序列号是否保持一致 |
构建新的netlink消息需要分配内存空间,其函数定义如下:
/**
* nlmsg_new - Allocate a new netlink message
* @payload: size of the message payload
* @flags: the type of memory to allocate.
*
* Use NLMSG_DEFAULT_SIZE if the size of the payload isn't known
* and a good default is needed.
*/
static inline struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
{
return alloc_skb(nlmsg_total_size(payload), flags);
}
默认的NLMSG_DEFAULT_SIZE一般是对应8192bytes,即PageSize,和设备的具体架构相关,一般使用默认值即可。flags是内存属性,一般在内核里都使用GFP_KERNEL
,具体如下:
// include/linux/gfp_types.h line:262
/**
* DOC: Useful GFP flag combinations
*
* Useful GFP flag combinations
* ----------------------------
*
* Useful GFP flag combinations that are commonly used. It is recommended
* that subsystems start with one of these combinations and then set/clear
* %__GFP_FOO flags as necessary.
*
* %GFP_ATOMIC users can not sleep and need the allocation to succeed. A lower
* watermark is applied to allow access to "atomic reserves".
* The current implementation doesn't support NMI and few other strict
* non-preemptive contexts (e.g. raw_spin_lock). The same applies to %GFP_NOWAIT.
*
* %GFP_KERNEL is typical for kernel-internal allocations. The caller requires
* %ZONE_NORMAL or a lower zone for direct access but can direct reclaim.
*
* %GFP_KERNEL_ACCOUNT is the same as GFP_KERNEL, except the allocation is
* accounted to kmemcg.
*
* %GFP_NOWAIT is for kernel allocations that should not stall for direct
* reclaim, start physical IO or use any filesystem callback.
*
* %GFP_NOIO will use direct reclaim to discard clean pages or slab pages
* that do not require the starting of any physical IO.
* Please try to avoid using this flag directly and instead use
* memalloc_noio_{save,restore} to mark the whole scope which cannot
* perform any IO with a short explanation why. All allocation requests
* will inherit GFP_NOIO implicitly.
*
* %GFP_NOFS will use direct reclaim but will not use any filesystem interfaces.
* Please try to avoid using this flag directly and instead use
* memalloc_nofs_{save,restore} to mark the whole scope which cannot/shouldn't
* recurse into the FS layer with a short explanation why. All allocation
* requests will inherit GFP_NOFS implicitly.
*
* %GFP_USER is for userspace allocations that also need to be directly
* accessibly by the kernel or hardware. It is typically used by hardware
* for buffers that are mapped to userspace (e.g. graphics) that hardware
* still must DMA to. cpuset limits are enforced for these allocations.
*
* %GFP_DMA exists for historical reasons and should be avoided where possible.
* The flags indicates that the caller requires that the lowest zone be
* used (%ZONE_DMA or 16M on x86-64). Ideally, this would be removed but
* it would require careful auditing as some users really require it and
* others use the flag to avoid lowmem reserves in %ZONE_DMA and treat the
* lowest zone as a type of emergency reserve.
*
* %GFP_DMA32 is similar to %GFP_DMA except that the caller requires a 32-bit
* address. Note that kmalloc(..., GFP_DMA32) does not return DMA32 memory
* because the DMA32 kmalloc cache array is not implemented.
* (Reason: there is no such user in kernel).
*
* %GFP_HIGHUSER is for userspace allocations that may be mapped to userspace,
* do not need to be directly accessible by the kernel but that cannot
* move once in use. An example may be a hardware allocation that maps
* data directly into userspace but has no addressing limitations.
*
* %GFP_HIGHUSER_MOVABLE is for userspace allocations that the kernel does not
* need direct access to but can use kmap() when access is required. They
* are expected to be movable via page reclaim or page migration. Typically,
* pages on the LRU would also be allocated with %GFP_HIGHUSER_MOVABLE.
*
* %GFP_TRANSHUGE and %GFP_TRANSHUGE_LIGHT are used for THP allocations. They
* are compound allocations that will generally fail quickly if memory is not
* available and will not wake kswapd/kcompactd on failure. The _LIGHT
* version does not attempt reclaim/compaction at all and is by default used
* in page fault path, while the non-light is used by khugepaged.
*/
#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
#define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT)
#define GFP_NOWAIT (__GFP_KSWAPD_RECLAIM)
#define GFP_NOIO (__GFP_RECLAIM)
#define GFP_NOFS (__GFP_RECLAIM | __GFP_IO)
#define GFP_USER (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
#define GFP_DMA __GFP_DMA
#define GFP_DMA32 __GFP_DMA32
#define GFP_HIGHUSER (GFP_USER | __GFP_HIGHMEM)
#define GFP_HIGHUSER_MOVABLE (GFP_HIGHUSER | __GFP_MOVABLE | \
__GFP_SKIP_KASAN_POISON | __GFP_SKIP_KASAN_UNPOISON)
#define GFP_TRANSHUGE_LIGHT ((GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
__GFP_NOMEMALLOC | __GFP_NOWARN) & ~__GFP_RECLAIM)
#define GFP_TRANSHUGE (GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM)
可以看到,使用了nlmsg_total_size
函数来计算payload
,如下:
/**
* nlmsg_total_size - length of netlink message including padding
* @payload: length of message payload
*/
static inline int nlmsg_total_size(int payload)
{
return NLMSG_ALIGN(nlmsg_msg_size(payload));
}
NLMSG_ALIGN是对齐宏,在3.2节中已介绍,其实际操作为向上4字节对齐。nlmsg_msg_size
函数如下:
/**
* nlmsg_msg_size - length of netlink message not including padding
* @payload: length of message payload
*/
static inline int nlmsg_msg_size(int payload)
{
return NLMSG_HDRLEN + payload;
}
NLMSG_HDRLEN是netlink消息头部部分的大小,为NLMSG_ALIGN(sizeof(struct nlmsghdr))
,在3.1节中,有该结构体的定义,其大小为(4 + 2 + 2 + 4 + 4 )= 12 bytes,已4字节对齐。
nlmsg_padlen用于计算尾部填充字节大小,直接用nlmsg_total_size - nlmsg_msg_size
即可,其范围为0~3
bytes。
static inline int nlmsg_padlen(int payload)
{
return nlmsg_total_size(payload) - nlmsg_msg_size(payload);
// 等价于 NLMSG_ALIGN(payload) - payload
}
nlmsg_put
,添加一个netlink消息到sock buffer中,如果空间不足,就会失败,并返回NULL。
static inline struct nlmsghdr *nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
int type, int payload, int flags)
{
if (unlikely(skb_tailroom(skb) < nlmsg_total_size(payload)))
return NULL;
return __nlmsg_put(skb, portid, seq, type, payload, flags);
}
__nlmsg_put
函数在net/netlink/af_netlink.c Line:2191
,其主要功能是在sock buffer中分配一个netlink消息,然后赋值相关属性。根据size的情况,对尾部填充字节(向上4字节对齐产生)进行置0操作,此处针对size为常量时有优化操作。
struct nlmsghdr * __nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, int type, int len, int flags)
{
struct nlmsghdr *nlh;
int size = nlmsg_msg_size(len);
nlh = skb_put(skb, NLMSG_ALIGN(size));
nlh->nlmsg_type = type;
nlh->nlmsg_len = size;
nlh->nlmsg_flags = flags;
nlh->nlmsg_pid = portid;
nlh->nlmsg_seq = seq;
if (!__builtin_constant_p(size) || NLMSG_ALIGN(size) - size != 0)
memset(nlmsg_data(nlh) + len, 0, NLMSG_ALIGN(size) - size);
return nlh;
}
nlmsg_put_answer
,向skb(sock buffer)中添加一个新的基于netlink回调的消息。
static inline struct nlmsghdr *nlmsg_put_answer(struct sk_buff *skb,
struct netlink_callback *cb,
int type, int payload,
int flags)
{
return nlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
type, payload, flags);
}
nlmsg_end
,构建netlink消息的最终处理,即更正netlink消息头部的长度字段信息:
static inline void nlmsg_end(struct sk_buff *skb, struct nlmsghdr *nlh)
{
nlh->nlmsg_len = skb_tail_pointer(skb) - (unsigned char *)nlh;
}
nlmsg_get_pos
,返回netlink消息当前的位置,即sock buffer的最后写入处:
static inline void *nlmsg_get_pos(struct sk_buff *skb)
{
return skb_tail_pointer(skb);
}
nlmsg_trim
,裁剪netlink 消息到提供的标记处,即改变sock buffer尾部写入指针值:
static inline void nlmsg_trim(struct sk_buff *skb, const void *mark)
{
if (mark) {
WARN_ON((unsigned char *) mark < skb->data);
skb_trim(skb, (unsigned char *) mark - skb->data);
}
}
nlmsg_cancel
,从sock buffer中移除整个netlink消息,包含其具有的所有属性。本质使用裁剪函数实现。
static inline void nlmsg_cancel(struct sk_buff *skb, struct nlmsghdr *nlh)
{
nlmsg_trim(skb, nlh);
}
nlmsg_free
,释放一个netlink消息的内存,本质是释放sock buffer。
static inline void nlmsg_free(struct sk_buff *skb)
{
kfree_skb(skb);
}
nlmsg_multicast
,在提供的套接字上广播一个netlink消息,指定自身的portid,避免发送给自己。
static inline int nlmsg_multicast(struct sock *sk, struct sk_buff *skb,
u32 portid, unsigned int group, gfp_t flags)
{
int err;
NETLINK_CB(skb).dst_group = group;
err = netlink_broadcast(sk, skb, portid, group, flags);
if (err > 0)
err = 0;
return err;
}
nlmsg_unicast
,在提供的套接字上发送一个单播消息到指定的portid
上。
static inline int nlmsg_unicast(struct sock *sk, struct sk_buff *skb, u32 portid)
{
int err;
err = netlink_unicast(sk, skb, portid, MSG_DONTWAIT);
if (err > 0)
err = 0;
return err;
}
nlmsg_notify
,发送一个netlink消息通告,可以指定目标groups和report属性,report可以向目标端口发送该消息通告,而groups是群发通告(会排除目标端口,确保不会发送两次消息通告)。
nlmsg_data
,返回消息头部负载的首地址,这里的NLMSG_HDRLEN
是已经向上4字节对齐的地址:
static inline void *nlmsg_data(const struct nlmsghdr *nlh)
{
return (unsigned char *) nlh + NLMSG_HDRLEN;
}
nlmsg_len
,返回消息负载的长度,这里的消息负载长度算上了尾部填充字节(0~3bytes):
static inline int nlmsg_len(const struct nlmsghdr *nlh)
{
return nlh->nlmsg_len - NLMSG_HDRLEN;
}
nlmsg_attrdata
,返回消息属性数据的首地址 ,需要计算偏移,这部分偏移来自payload里面的family header
,然后才是属性数据:
static inline struct nlattr *nlmsg_attrdata(const struct nlmsghdr *nlh, int hdrlen)
{
unsigned char *data = nlmsg_data(nlh);
return (struct nlattr *) (data + NLMSG_ALIGN(hdrlen));
}
nlmsg_attrlen
,返回属性数据的长度:
static inline int nlmsg_attrlen(const struct nlmsghdr *nlh, int hdrlen)
{
return nlmsg_len(nlh) - NLMSG_ALIGN(hdrlen);
}
nlmsg_ok
,检查剩余字节数里面是否可以放下一个netlink 消息。
static inline int nlmsg_ok(const struct nlmsghdr *nlh, int remaining)
{
return (remaining >= (int) sizeof(struct nlmsghdr) &&
nlh->nlmsg_len >= sizeof(struct nlmsghdr) &&
nlh->nlmsg_len <= remaining);
}
nlmsg_next
,返回消息流里的下一个消息,并且减少当前剩下的字节数:
static inline struct nlmsghdr * nlmsg_next(const struct nlmsghdr *nlh, int *remaining)
{
int totlen = NLMSG_ALIGN(nlh->nlmsg_len);
*remaining -= totlen;
return (struct nlmsghdr *) ((unsigned char *) nlh + totlen);
}
nlmsg_parse
,解析属性流里的属性到属性数组里面,然后可以通过属性类型来访问,超过最大类型值的属性将会被拒绝,验证策略需要指定,属性将会以给定的严格程度被验证。
__nlmsg_parse - parse attributes of a netlink message
@nlh: netlink message header
@hdrlen: length of family specific header
@tb: destination array with maxtype+1 elements
@maxtype: maximum attribute type to be expected
@policy: validation policy
@validate: validation strictness
@extack: extended ACK report struct
__nla_parse(tb, maxtype, nlmsg_attrdata(nlh, hdrlen), nlmsg_attrlen(nlh, hdrlen), policy, validate, extack);
解析之后的属性按照tb[type]
索引来记录属性消息在sock buffer中的首地址,每次解析可解析多个消息属性。但属性嵌套有上限,在给定的验证策略下,会检查嵌套程度,其默认上限为MAX_POLICY_RECURSION_DEPTH
,当前值为10。
消息验证的严格程度可以指定为以下值include/net/netlink.h Line:493
:
消息验证的严格程度:枚举位图 | 描述 |
---|---|
NL_VALIDATE_LIBERAL = 0 | Old-style “be liberal” validation, not caring about extra data at the end of the message, attributes being longer than extra data at the end of the message, attributes being longer than they should be, or unknown attributes being present。 |
NL_VALIDATE_TRAILING = BIT(0) | Reject junk data encountered after attribute parsing. |
NL_VALIDATE_MAXTYPE = BIT(1) | Reject attributes > max type; Together with _TRAILING this is equivalent to the old nla_parse_strict()/nlmsg_parse_strict(). |
NL_VALIDATE_UNSPEC = BIT(2) | Reject attributes with NLA_UNSPEC in the policy. This can safely be set by the kernel when the given policy has no NLA_UNSPEC anymore, and can thus be used to ensure policy entries are enforced going forward. |
NL_VALIDATE_STRICT_ATTRS = BIT(3) | strict attribute policy parsing (e.g. U8, U16, U32 must have exact size, etc.) |
NL_VALIDATE_NESTED = BIT(4) | Check that NLA_F_NESTED is set for NLA_NESTED(_ARRAY) and unset for other policies. |
对于目前最严格的验证策略程度,上述的NL_VALIDATE_TRAILING、NL_VALIDATE_MAXTYPE、NL_VALIDATE_UNSPEC 、NL_VALIDATE_STRICT_ATTRS 、NL_VALIDATE_NESTED等都会被设立。
Attribute Format:
<------- nla_total_size(payload) ------->
<---- nla_attr_size(payload) ----->
+----------+- - -+- - - - - - - - - +- - -+-------- - -
| Header | Pad | Payload | Pad | Header
+----------+- - -+- - - - - - - - - +- - -+-------- - -
<- nla_len(nla) -> ^
nla_data(nla)----^ |
nla_next(nla)-----------------------------'
下面是常见属性构造函数:
函数名 | 功能 |
---|---|
nla_reserve(skb, type, len) | 为一个属性保留空间 |
nla_reserve_nohdr(skb, len) | 为一个没有头部的属性保留空间 |
nla_put(skb, type, len, data) | 添加一个属性到sock buffer(skb)中 |
nla_put_nohdr(skb, len, data) | 添加一个没有头部的属性负载数据到sock buffer(skb)中 |
nla_append(skb, len, data) | 在sock buffer中添加数据 |
下面是常见的基本类型构造函数:
函数名 | 功能 |
---|---|
nla_put_u8(skb, type, value) | 添加一个u8属性到sock buffer中 |
nla_put_u16(skb, type, value) | 添加一个u16属性到sock buffer中 |
nla_put_u32(skb, type, value) | 添加一个u32属性到sock buffer中 |
nla_put_u64_64bit(skb, type, value, padattr) | 添加一个u64属性到sock buffer中,可以额外指定对齐到8字节 |
nla_put_s8(skb, type, value) | 添加一个s8属性到sock buffer中 |
nla_put_s16(skb, type, value) | 添加一个s16属性到sock buffer中 |
nla_put_s32(skb, type, value) | 添加一个s32属性到sock buffer中 |
nla_put_s64(skb, type, value, padattr) | 添加一个s64属性到sock buffer中,可以额外指定对齐到8字节 |
nla_put_string(skb, type, str) | 添加一个string属性到sock buffer中,字符串带有尾部NULL |
nla_put_flag(skb, type) | 添加一个flag属性到sock buffer中,flag没有负载数据。 |
nla_put_msecs(skb, type, jiffies,padattr) | 添加一个msecs属性到sock buffer中,从jiffies换算到ms毫秒 |
nla_put_in_addr(skb, type, addr) | 添加一个IPv4属性到sock buffer中 |
nla_put_in6_addr(skb, type, addr) | 添加一个IPv6属性到sock buffer中 |
nla_put_net16/32(skb, attr, value) | 添加一个网络字节序的整数属性到sock buffer中 |
nla_put_bitfield32(skb, attr, value, selector) | 添加一个32位的位图属性到sock buffer中 |
下面是常见的嵌套属性构建函数:
函数名 | 功能 |
---|---|
nla_nest_start(skb, type) | 开始一个嵌套的属性,设立NLA_F_NESTED标志(header.attr->type第15位值),目前推荐使用 |
nla_nest_end(skb, nla) | 最终确定一个嵌套的属性,即更新头部里面的长度字段信息 |
nla_nest_cancel(skb, nla) | 终止嵌套属性的构建,将sock buffer的写入指针移到属性头地址处 |
nla_nest_start_noflag(skb, type) | 开始一个嵌套的属性,但是不设立NLA_F_NESTED标志,这是兼容旧版本 |
下面是常见的属性长度计算函数:
函数名 | 功能 |
---|---|
nla_attr_size(payload) | 不带尾部填充的属性长度,但属性头部已向上4字节对齐 |
nla_total_size(payload) | 带尾部填充的属性长度,即头部和负载皆已向上4字节对齐 |
nla_padlen(payload) | 尾部填充的长度,取值0~3字节 |
下面是常见的属性负载访问函数:
函数名 | 功能 |
---|---|
nla_data(nla) | 属性负载的头部地址 |
nla_len(nla) | 属性负载的长度 |
nla_type(nla) | 返回头部的type值(16位),需要去掉高处两位,它们表示是否嵌套和网络字节序。 |
下面是常见基本类型的属性负载访问函数:
函数名 | 功能 |
---|---|
nla_get_u8(nla) | 从一个u8属性上获取负载 |
nla_get_u16(nla) | 从一个u16属性上获取负载 |
nla_get_u32(nla) | 从一个u32属性上获取负载 |
nla_get_u64(nla) | 从一个u64属性上获取负载 |
nla_get_s8(nla) | 从一个s8属性上获取负载 |
nla_get_s16(nla) | 从一个s16属性上获取负载 |
nla_get_s32(nla) | 从一个s32属性上获取负载 |
nla_get_s64(nla) | 从一个s64属性上获取负载 |
nla_get_flag(nla) | 如果nla不为NULL,则返回1,否则返回0 |
nla_get_msecs(nla) | 从一个msecs属性上获取负载,msecs是毫秒,返回换算后的jiffies值 |
nla_get_in_addr(nla) | 返回一个IPv4地址属性的负载数据 |
nla_get_in6_addr(nla) | 返回一个IPv6地址属性的负载数据 |
nla_get_bitfield32(nla) | 返回一个32位位段属性的负载数据 |
下面是常见的杂类函数:
函数名字 | 功能 |
---|---|
nla_memcpy(dest, nla, count) | 复制属性到内存中 |
nla_memcmp(nla, data, size) | 比较消息和内存中的属性 |
nla_strscpy(dst, nla, size) | 复制属性到一个给定sized的字符串中 |
nla_strcmp(nla, str) | 和字符串中的属性进行比较 |
nla_need_padding_for_64bit(skb) | 检查skb尾部写入指针是否对齐到8字节处 |
下面是常见的属性解析函数:
函数名 | 功能 |
---|---|
nla_ok(nla, remaining) | 在剩下的字节中是否存在一个合适的属性 |
nla_next(nla, remaining) | 获取下一个netlink属性 |
nla_validate() | 验证属性流 |
nla_validate_nested() | 验证嵌套属性流 |
nla_find() | 在属性流中找到一个属性 |
nla_find_nested() | 在嵌套属性中找到一个属性 |
nla_parse() | 解析和验证属性流 |
nla_parse_nested() | 解析嵌套属性流,需要属性type中含有嵌套标识NLA_F_NESTED,并且默认执行严格的验证策略 |
nla_for_each_attr() | 遍历所有的属性 |
nla_for_each_nested() | 遍历嵌套的属性 |
下面是标准的消息属性类型枚举符:
属性类型,用于指定验证策略 | 描述 |
---|---|
NLA_UNSPEC | 未指定类型,主要是保持旧版本的兼容 |
NLA_U8 | 8位无符号数 |
NLA_U16 | 16位无符号数 |
NLA_U32 | 32位无符号数 |
NLA_U64 | 64位无符号数 |
NLA_STRING | 字符串 |
NLA_FLAG | 标志值,即该消息不携带数据,仅用于通信。 |
NLA_MSECS | 毫秒值 |
NLA_NESTED | 嵌套属性 |
NLA_NESTED_ARRAY | 嵌套属性数组 |
NLA_NUL_STRING | 带NULL的字符串 |
NLA_BINARY | 二进制值 |
NLA_S8 | 8位有符号数 |
NLA_S16 | 16位有符号数 |
NLA_S32 | 32位有符号数 |
NLA_S64 | 64位有符号数 |
NLA_BITFIELD32 | 32位位图,会检查设置位是否存在含义 |
NLA_REJECT | 拒绝验证包含该属性的值 |
NLA_BE16 | 大端编码的16位数 |
NLA_BE32 | 大端编码的32位数 |
__NLA_TYPE_MAX | 最大的TYPE枚举值,用于验证时进行参数判断 |
下面是标准的验证类型枚举符:
验证类型枚举符 | 描述 |
---|---|
NLA_VALIDATE_NONE | 无 |
NLA_VALIDATE_RANGE | 验证属性值范围,包含最小值和最大值 |
NLA_VALIDATE_RANGE_WARN_TOO_LONG | 验证二进制值(NLA_BINARY)的位长度 |
NLA_VALIDATE_MIN | 验证属性值的最小值 |
NLA_VALIDATE_MAX | 验证属性值的最大值 |
NLA_VALIDATE_MASK | 验证位图(NLA_BITFIELD32)的有效设置位 |
NLA_VALIDATE_RANGE_PTR | 使用指针指向的结构体成员值来任意验证最大/最小值 |
NLA_VALIDATE_FUNCTION | 使用验证函数来任意验证属性数据,是指针的替代品。 |
上述枚举类型用于填充指定结构体,如下:
struct nla_policy {
u8 type;
u8 validation_type;
u16 len;
union {
u16 strict_start_type;
/* private: use NLA_POLICY_*() to set */
const u32 bitfield32_valid;
const u32 mask;
const char *reject_message;
const struct nla_policy *nested_policy;
struct netlink_range_validation *range;
struct netlink_range_validation_signed *range_signed;
struct {
s16 min, max;
};
int (*validate)(const struct nlattr *attr,
struct netlink_ext_ack *extack);
};
};
一般,该结构体定义成一个数组,其索引值为policy策略对应的枚举值。针对不同的type
,其len
字段含义不同。
下面是len
字段在不同的属性类型(type)下的含义:
属性类型(Type) | 描述 |
---|---|
NLA_STRING | Maximum length of string |
NLA_NUL_STRING | Maximum length of string (excluding NUL) |
NLA_FLAG | Unused |
NLA_BINARY | Maximum length of attribute payload (but see also below with the validation type) |
NLA_NESTED,NLA_NESTED_ARRAY | Length verification is done by checking len of nested header (or empty); len field is used if nested_policy is also used, for the max attr number in the nested policy. |
NLA_{U,S}{8,16,32,64},NLA_BE{16,32},NLA_MSECS | Leaving the length field zero will verify the given type fits, using it verifies minimum length |
NLA_BITFIELD32 | Unused |
NLA_REJECT | Unused |
All other(其他未列出来的类型) | Minimum length of attribute payload |
下面是validation union
联合字段在不同的属性类型(type)下的含义:
属性类型(Type) | 联合字段含义(union)描述 |
---|---|
NLA_BITFIELD32 | This is a 32-bit bitmap/bitselector attribute and `bitfield32_valid’ is the u32 value of valid flags |
NLA_REJECT | This attribute is always rejected and ‘reject_message’ may point to a string to report as the error instead of the generic one in extended ACK. |
NLA_NESTED | ‘nested_policy’ to a nested policy to validate, must also set ‘len’ to the max attribute number. Use the provided NLA_POLICY_NESTED() macro. Note that nla_parse() will validate, but of course not parse, the nested sub-policies. |
NLA_NESTED_ARRAY | ‘nested_policy’ points to a nested policy to validate, must also set ‘len’ to the max attribute number. Use the provided NLA_POLICY_NESTED_ARRAY() macro. The difference to NLA_NESTED is the structure: NLA_NESTED has the nested attributes directly inside while an array has the nested attributes at another level down and the attribute types directly in the nesting don’t matter. |
NLA_{U,S}{8,16,32,64},NLA_BE{16,32} | The ‘min’ and ‘max’ fields are used depending on the validation_type field, if that is min/max/range then the min, max or both are used (respectively) to check the value of the integer attribute. Note that in the interest of code simplicity and struct size both limits are s16, so you cannot enforce a range that doesn’t fall within the range of s16 - do that as usual in the code instead. Use the NLA_POLICY_MIN(), NLA_POLICY_MAX() and NLA_POLICY_RANGE() macros. |
NLA_U{8,16,32,64} | If the validation_type field instead is set to NLA_VALIDATE_RANGE_PTR, `range’ must be a pointer to a struct netlink_range_validation that indicates the min/max values. Use NLA_POLICY_FULL_RANGE(). |
NLA_S{8,16,32,64} | If the validation_type field instead is set to NLA_VALIDATE_RANGE_PTR, ‘range_signed’ must be a pointer to a struct netlink_range_validation_signed that indicates the min/max values.Use NLA_POLICY_FULL_RANGE_SIGNED(). |
NLA_BINARY | If the validation type is like the ones for integers above, then the min/max length (not value like for integers) of the attribute is enforced. |
All other(其他未列出来的类型) | Unused - but note that it’s a union |
上述的组合仅仅是验证方式中的一部分,实际情况更复杂,并且具有一定的自由度。
接下来看看与其相关的宏定义:
#define _NLA_POLICY_NESTED(maxattr, policy) \
{ .type = NLA_NESTED, .nested_policy = policy, .len = maxattr }
#define _NLA_POLICY_NESTED_ARRAY(maxattr, policy) \
{ .type = NLA_NESTED_ARRAY, .nested_policy = policy, .len = maxattr }
#define NLA_POLICY_NESTED(policy) \
_NLA_POLICY_NESTED(ARRAY_SIZE(policy) - 1, policy)
#define NLA_POLICY_NESTED_ARRAY(policy) \
_NLA_POLICY_NESTED_ARRAY(ARRAY_SIZE(policy) - 1, policy)
上面是和嵌套属性相关的宏定义,其使用的联合字段为nested_policy
,len
用来判断嵌套的属性数量,其值需要和给定的policy
数组大小匹配。
#define NLA_POLICY_BITFIELD32(valid) \
{ .type = NLA_BITFIELD32, .bitfield32_valid = valid }
上面指定一个验证位图的策略,bitfield32_valid
存储的是位图中已使用的有效位。
对于整数类型,有非常多的判断选项:
#define __NLA_IS_UINT_TYPE(tp) \
(tp == NLA_U8 || tp == NLA_U16 || tp == NLA_U32 || tp == NLA_U64)
#define __NLA_IS_SINT_TYPE(tp) \
(tp == NLA_S8 || tp == NLA_S16 || tp == NLA_S32 || tp == NLA_S64)
#define __NLA_IS_BEINT_TYPE(tp) \
(tp == NLA_BE16 || tp == NLA_BE32)
#define __NLA_ENSURE(condition) BUILD_BUG_ON_ZERO(!(condition))
#define NLA_ENSURE_UINT_TYPE(tp) \
(__NLA_ENSURE(__NLA_IS_UINT_TYPE(tp)) + tp)
#define NLA_ENSURE_UINT_OR_BINARY_TYPE(tp) \
(__NLA_ENSURE(__NLA_IS_UINT_TYPE(tp) || \
tp == NLA_MSECS || \
tp == NLA_BINARY) + tp)
#define NLA_ENSURE_SINT_TYPE(tp) \
(__NLA_ENSURE(__NLA_IS_SINT_TYPE(tp)) + tp)
#define NLA_ENSURE_INT_OR_BINARY_TYPE(tp) \
(__NLA_ENSURE(__NLA_IS_UINT_TYPE(tp) || \
__NLA_IS_SINT_TYPE(tp) || \
__NLA_IS_BEINT_TYPE(tp) || \
tp == NLA_MSECS || \
tp == NLA_BINARY) + tp)
#define NLA_ENSURE_NO_VALIDATION_PTR(tp) \
(__NLA_ENSURE(tp != NLA_BITFIELD32 && \
tp != NLA_REJECT && \
tp != NLA_NESTED && \
tp != NLA_NESTED_ARRAY) + tp)
#define NLA_ENSURE_BEINT_TYPE(tp) \
(__NLA_ENSURE(__NLA_IS_BEINT_TYPE(tp)) + tp)
BUILD_BUG_ON_ZERO(condition)
在condition
为true
时会在编译时报错,通过对条件取反,因此__NLA_ENSURE(condition)
在条件不为真时编译报错,从而确保指定条件一定为真。
NLA_ENSURE_NO_VALIDATION_PTR
是用来替代指针的验证方式,如果一种数据类型尚未使用指针结构体进行验证,那么就可以使用任意验证函数进行验证。
下面是整型相关的结构体验证策略组成:
#define NLA_POLICY_RANGE(tp, _min, _max) { \
.type = NLA_ENSURE_INT_OR_BINARY_TYPE(tp), \
.validation_type = NLA_VALIDATE_RANGE, \
.min = _min, \
.max = _max \
}
#define NLA_POLICY_MIN(tp, _min) { \
.type = NLA_ENSURE_INT_OR_BINARY_TYPE(tp), \
.validation_type = NLA_VALIDATE_MIN, \
.min = _min, \
}
#define NLA_POLICY_MAX(tp, _max) { \
.type = NLA_ENSURE_INT_OR_BINARY_TYPE(tp), \
.validation_type = NLA_VALIDATE_MAX, \
.max = _max, \
}
可以看到,只要类型是(有/无符号)整数、大端编码整数、二进制数和时间数,都可以进行范围验证。
#define NLA_POLICY_FULL_RANGE(tp, _range) { \
.type = NLA_ENSURE_UINT_OR_BINARY_TYPE(tp), \
.validation_type = NLA_VALIDATE_RANGE_PTR, \
.range = _range, \
}
#define NLA_POLICY_FULL_RANGE_SIGNED(tp, _range) { \
.type = NLA_ENSURE_SINT_TYPE(tp), \
.validation_type = NLA_VALIDATE_RANGE_PTR, \
.range_signed = _range, \
}
对于(有/无符号)整数、二进制数和时间数,还可以进行大范围值的验证,policy结构体的min/max字段只是16位整数,其大小有限,而rang(_signed)
指针指向的结构体,其max/min
字段值为64位。
#define NLA_POLICY_MASK(tp, _mask) { \
.type = NLA_ENSURE_UINT_TYPE(tp), \
.validation_type = NLA_VALIDATE_MASK, \
.mask = _mask, \
}
对于无符号整数,还可以使用掩码来进行验证。
#define NLA_POLICY_VALIDATE_FN(tp, fn, ...) { \
.type = NLA_ENSURE_NO_VALIDATION_PTR(tp), \
.validation_type = NLA_VALIDATE_FUNCTION, \
.validate = fn, \
.len = __VA_ARGS__ + 0, \
}
对NLA_BITFIELD32、NLA_REJECT、NLA_NESTED、NLA_NESTED_ARRAY
之外的其他类型,可以使用任意函数进行验证,此处len
字段并无实际含义。
#define NLA_POLICY_EXACT_LEN(_len) NLA_POLICY_RANGE(NLA_BINARY, _len, _len)
#define NLA_POLICY_EXACT_LEN_WARN(_len) { \
.type = NLA_BINARY, \
.validation_type = NLA_VALIDATE_RANGE_WARN_TOO_LONG, \
.min = _len, \
.max = _len \
}
#define NLA_POLICY_MIN_LEN(_len) NLA_POLICY_MIN(NLA_BINARY, _len)
针对二进制数,会特别验证其位的长度,即NLA_VALIDATE_RANGE_WARN_TOO_LONG
标识。但没有这个标识时,如NLA_POLICY_MIN(NLA_BINARY, _len)
验证的是该二进制数的最小值,而并非是其位的最小长度。