Linux内核(04)之netlink通信

Author:Onceday Date:2023年1月3日

漫漫长路,才刚刚开始…

参考文档:

  • netlink 机制 binarydady 阿里云开发者社区
  • linux中通用Netlink详解及使用剖析 binarydady 阿里云开发者社区
  • RFC 3549 Linux Netlink as an IP Services Protocol
  • Netlink interface for ethtool – The Linux Kernel documentation
  • networking:generic_netlink_howto [Wiki] (linuxfoundation.org)
  • Introduction to Netlink — The Linux Kernel documentation
  • Linux内核 | Netlink机制分析与使用 Liunx内核之旅 知乎
  • Linux内核和用户空间通信之netlink Arnold Lu 博客园
  • Linux内核与用户空间通信之netlink使用方法 明明是悟空 博客园

目录

  • Linux内核(04)之netlink通信
    • 1.概述
    • 2.基本机制介绍
      • 2.1 netlink子系统初始化
      • 2.2 Netlink套接字创建
      • 2.3 Netlink套接字操作
      • 2.4 socket创建
      • 2.5 socket绑定
      • 2.6 socket设置属性
      • 2.7 socket连接和发送消息
    • 3.netlink报文格式
      • 3.1 基本固定格式
      • 3.2 常用消息宏定义
      • 3.3 netlink消息格式
      • 3.4 常见的netlink消息函数
      • 3.5 常见netlink消息函数详解
      • 3.6 netlink消息属性函数
      • 3.7 常见netlink消息属性验证策略

1.概述

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通信方式,内核发送的数据会保存在接收进程的socket接收缓存中。
  • 支持多播(支持总线式通信,可实现消息订阅)。
  • 在内核端可用于进程上下文与间隔上下文,通过软件中断调用接收函数,无需新开内核线程。

通过自定义一种新的协议并加入协议族,即可通过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是默认固定的(要求原生套接字,原生类型即可)。

系统预定义了很多协议,主要的使用对象就是内核的网络类子系统,如下:

#<include/uapi/linux/netlink.h>#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)等等。

2.基本机制介绍

2.1 netlink子系统初始化

netlink作为一个内核子系统,需要在内核启动的时候首先进行初始化,即下面函数:

static int __init netlink_proto_init(void);  #net/netlink/af_netlink.c

其主要步骤如下:

  1. 通过proto_register()向内核注册名为NETLINK的套接字协议。

  2. 可选的BPF(伯克利包过滤)和PROC_FS(进程信息文件系统)支持。对应/proc/net/netlink文件。

  3. 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都将保存在这个表中。

  4. 添加一个虚拟的USERSOCK实体,让NETLINK USERSOCK协议能继续工作。

  5. 通过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负责完成。

  6. 通过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的消息。

  7. 最后调用rtnetlink_init()创建NETLINK_ROUTE协议类型的Netlink,用来传递网络路由子系统、邻居子系统、接口设置、防火墙等消息。整个Netlink子系统初始化完成。

    rtnetlink是在netlink上进一步封装的C库,主要针对网络相关的内核子系统,更加易用。

完成以上步骤后,整个Netlink子系统就初始化完成了。后续使用,将基于此处注册的套接字协议。

2.2 Netlink套接字创建

Netlink套接字可以是SOCK_RAW套接字,也可以是SOCK_DGRAM套接字。内核和用户空间都可以使用Netlink套接字,只是调用的方法不同,用户空间使用传统的socket系统调用,内核态使用__netlink_kernel_create函数。最终都会调用__netlink_create方法。

对于用户空间, 在调用socket()之后,便由netlink_create完成实际的创建过程。

  1. 判断套接字类型是否为SOCK_RAW或者SOCK_DGRAM
  2. 判断protocal是否超过MAX_LINKS=32
  3. netlink table获取对应protocal的互斥量、bind/unbind绑定和解绑函数指针。
  4. 调用__netlink_create函数。

__netlink_create()会完成实际的sock对象创建,如下:

  1. sk_alloc分配netlink协议族的sock对象内存。
  2. sock_init_data初始化sock对象。
  3. 互斥量处理和初始化等待队列netlink->wait
  4. 绑定sock_destruct析构函数。

socket是通用的伯克利套接字对象,而socksocket对象的具体网络层内容。

对于内核模块,创建时需要指定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函数,其内部主要操作如下:

  1. 创建新的socket对象,协议族为PF_NETLINK ,协议类型为SOCK_DGRAM
  2. __netlink_create()里面初始化实际的sock对象。
  3. 根据cfg->groups来申请listenersbitmap数组内存,默认8字节对齐,即groups对齐到64的倍数。
  4. 绑定input函数。
  5. 以默认portid=0sock对象插入到对应协议表项(netlink_table[protocal])的哈希表中。
  6. 更新对应协议的netlink_table[protocal]表项的各类属性值、绑定/解绑函数等。

同类协议,只有第一个注册的netlink kernel configuration配置会写入,后续再注册,并不会更改netlink_table[protocal]表项的值

2.3 Netlink套接字操作

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长度。

2.4 socket创建

使用netlink第一步便是创建套接字对象,如下:

/*Create netlink socket*/
skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);

2.1节 netlink子系统初始化可知,netlink套接字会按照不同网络命名空间单独存在,不会混杂通信。在用户空间,socket()是一个系统调用,最终根据不同架构,将进入不同的系统调用,一般有socketcallsocket两种系统调用,但最终调用的函数是相同的,网络相关系统调用所在源文件位置为:net/socket.c

对于socket调用,内核接口为:

int __sys_socket(int family, int type, int protocol);

注意,这里的family填写的是AF_NETLINK,但是前面2.1节提到,netlink子系统注册的是PF_NETLINK,明显两者的值是相等的AFaddress family的缩写,PFprotocol family的缩写,两者是一样的,PF在AF上封装一层宏定义。

**__sys_socket()**函数里做以下几件事情:

  • 调用__sys_socket_create在这个过程主要处理SOCK_CLOEXECSOCK_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_NETLINKcreate即上面的netlink_create函数。netlink_create函数的主要操作在2.2节已经介绍过了。此外,2.3节介绍的netlink操作也会以结构体指针的形式挂载到sock->ops上。

  • 在完成套接字创建后,会使用sock_map_fd函数将套接字映射到对应的socket文件中,并返回对应的文件句柄。

如果上述过程出现错误,那么会调用sock_release函数来释放已分配的套接字,该函数里也会调用sock->ops->release函数来释放套接字对象,即2.3节中的netlink_release()函数,此外也会释放已获得文件句柄

2.5 socket绑定

使用netlink第二步就是绑定套接字对象了:

//bind to skfd with saddr.
if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0) {error("xxx")};

对于struct sockaddr结构体,其是一种泛型结构体,第一个成员是指定的,但后面的成员是各协议族自己决定。比如对于netlink,其实际字段如下,需要指定port idgroups

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()函数,具体如下:

  • 判断saddr是否溢出和family是否对上AF_NETLINK
  • 权限检查,如果(NETLINK_ROUTE)协议不允许非root用户监听多播内容(即groups不为0),那么除了root等超级用户外,其他不具有超级管理权限用户的绑定过程将失败。
  • 如果具备监听多播内容的权限,那么将在netlink_realloc_groups函数里进行操作,如果对应protocol未注册,将创建失败。如果当前套接字的groups位图Size小于对应协议的groups位图Size,那么将分配对应内存,然后保存当前的groups位图Size。
  • 检查当前套接字对象是否绑定了port id,如果已绑定,那检查当前用户设置的port id和已绑定的port id是否相同。
  • 根据配置的groups值和对应协议(此协议指NETLINK ROUTE等协议)的netlink_bind函数来绑定对应的groups。
  • 最后再检查一遍groups情况,并且更新subscriptions情况。
2.6 socket设置属性

一般对应下面两个系统调用:

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
2.7 socket连接和发送消息

在用户空间,有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函数一起使用,面向连接对象,sendtorecvfrom面向无连接对象。Netlink的套接字类型包含SOCK_RAWSOCK_DGRAM两种套接字,所以一般使用sendtorecvfrom两个函数,不过Netlink也实现了connect函数

这些用户空间的函数,最终调用两个函数netlink_sendmsgnetlink_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 *buffvoid *ubuf,它们实际对应消息字段。该消息字段根据协议(NETLINK_ROUTE等)的不同,也会有不同的格式要求。

缓冲区指针(buff/ubuf)来到内核后,挂载在__sys_sendto()函数的栈变量struct msghdr下,在内核的处理过程中,msg包含所有相关信息,但最终存入(sock buffer)缓存区的还是缓冲区指针(buff/ubuf)内容。

对于netlink_sendmsg函数,主要根据分下面两种发送模式:

  • netlink_broadcast,组播,即groups不为0。
  • netlink_unicast,单播,单播和组播可共存,且不会重复发送同一个消息。

对于netlink_recvmsg函数,直接在缓存区收包,可以指定标识,从而收取所有网络命名空间的包。

3.netlink报文格式

3.1 基本固定格式

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;
};
3.2 常用消息宏定义
#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的长度
3.3 netlink消息格式

位于头文件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)---^
3.4 常见的netlink消息函数

下面是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期间序列号是否保持一致
3.5 常见netlink消息函数详解

构建新的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~3bytes

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等都会被设立。

3.6 netlink消息属性函数
 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() 遍历嵌套的属性
3.7 常见netlink消息属性验证策略

下面是标准的消息属性类型枚举符:

属性类型,用于指定验证策略 描述
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_policylen用来判断嵌套的属性数量,其值需要和给定的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)conditiontrue时会在编译时报错,通过对条件取反,因此__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)验证的是该二进制数的最小值,而并非是其位的最小长度。

linux内核之netlink通信相关推荐

  1. linux 内核中Netlink

    目录 套接字Netlink地址 sockaddr_nl 协议簇 常使用的宏 内核常用的函数 创建流程 Netlink套接字 uevent内核事件 套接字监视接口 demo Netlink套接字接口最初 ...

  2. Linux 网络之netlink 简介

    文章目录 前言 一.Netlink用户态应用的使用 1.1 Netlink socket 1.2 Netlink宏操作 二.Netlink对应数据结构 2.1 struct sockaddr_nl 2 ...

  3. Netlink通信机制

    linux用户空间与内核空间通信--Netlink通信机制 一般来说用户空间和内核空间的通信方式有三种:/proc.ioctl.Netlink.而前两种都是单向的,但是Netlink可以实现双工通信. ...

  4. Linux 内核态与用户态通信 netlink

    参考资料: https://blog.csdn.net/zqixiao_09/article/details/77131283 https://www.cnblogs.com/lopnor/p/615 ...

  5. linux kernel and user space通信机制,Linux内核与用户空间通信机制研究.pdf

    ISSN 1009-3044 E-mail:info@CCCC.net.CR ComputerKnowledgeandTechnology电脑知识与技术 http://www.dnzs.net.cn ...

  6. linux 内核与用户空间通信之netlink使用方法

    Linux中的进程间通信机制源自于Unix平台上的进程通信机制.Unix的两大分支AT&T Unix和BSD Unix在进程通信实现机制上的各有所不同,前者形成了运行在单个计算机上的Syste ...

  7. Openwrt按键检测分析-窥探Linux内核与用户空间通讯机制netlink使用

    首先看一下Openwrt系统中关于按键功能的使用和修改,以18.06版本为例 按键功能实现在脚本中, 比如18.06/package/base-files/files/etc/rc.button/re ...

  8. Linux内核态之间进程通信,内核态和用户态通信(二)--实现

    本文主要使用netlink套接字实现中断环境与用户态进程通信. 系统环境:基于linux 2.6.32.27 和 linux 3.16.36 Linux内核态和用户态进程通信方法的提出和实现 用户上下 ...

  9. linux kernel and user space通信机制,Linux内核空间与用户空间通信机制地研究.doc

    实用文案 标准文档 Linux内核空间与用户空间通信机制的研究 Linux kernel space and user space communication mechanism 摘 要 Linux ...

最新文章

  1. ArcGis License 启动失败
  2. ManagedObject this[oPropertyInfo._sMutator](oValue);
  3. poj2229 基础的动态规划算法 挑战程序设计竞赛
  4. 一步一步写算法(之 最大公约数、最小公倍数)
  5. Windows单机之Weblogic 12c受管服务器配置
  6. 网络安全与黑客工具TOP100
  7. Mac电脑没声音音量图标变灰色如何修复?
  8. IE脚本错误Automation服务器不能创建对象
  9. 蔡颖-《APS走向实践》书解读之二:需求驱动-预测、掌握、满足需求
  10. 2003-2021年高铁列车信息
  11. sql语句--模糊查询
  12. 基于vi构建强大的IDE
  13. 海上风电场对雷达性能的影响——绕射损耗
  14. 一次CPU过载的简单排查历程
  15. 一周学会linux实战笔记
  16. php tcpdf 嵌入字体,TCPDF如何设置中文字体为内嵌字体?
  17. 在AWS上部署一个网站
  18. 新人如何快速上手做(海外问卷)站点查?
  19. 2021年T电梯修理考试技巧及T电梯修理模拟考试题库
  20. EDIUS 视音频制作标准教程(第2版)21堂课高清1280X720 25Pmp4格式 含同步素材 edius初学者必备装备

热门文章

  1. ListNode链表结构超详细解析,LeetCode题解
  2. 电脑店 linux 安装盘,怎么用电脑店u盘装系统|电脑店u盘启动盘安装系统教程-系统城...
  3. 基于CUDA的卷积神经网络算法实现
  4. python画一颗拳头大的
  5. 3天时间,给你一个完整的APP设计模型
  6. 字节跳动-教育职位-高级后端开发面试
  7. 判断是否是微信中打开网页
  8. Unity如何判断屏幕被点击
  9. 千古绝唱---------《钗头凤》
  10. JMeter 常用的几种断言方法,你会了吗?