文章目录
- 前言
- 一、Netlink用户态应用的使用
- 1.1 Netlink socket
- 1.2 Netlink宏操作
- 二、Netlink对应数据结构
- 2.1 struct sockaddr_nl
- 2.2 struct nlmsghdr
- 2.3 struct msghdr
- 三、用户层实例
- 参考资料
前言
The Netlink socket family 是一个 Linux 内核接口,用于内核和用户空间进程之间以及不同用户空间进程之间的进程间通信 (IPC),其方式类似于 Unix domain sockets。 与 Unix domain sockets类似,但与 INET sockets不同,Netlink 通信不能跨越主机边界。Unix domain sockets使用文件系统名称空间,Netlink进程通常由进程标识符(pid)寻址。
Netlink 设计用于在内核空间和用户空间进程之间传输各种网络信息。 网络实用程序,例如 iproute2 工具包,使用 Netlink 从用户空间与 Linux 内核进行通信。 Netlink 为用户空间进程提供了一个标准的基于套接字的接口,以及一个供内核模块内部使用的内核端 API。 Netlink 使用 AF_NETLINKsocket family。
Netlink 主要是用来进行内核和用户空间进程之间的通信,用户空间进程之间的进程间通信 (IPC)一般不用 Netlink。
Netlink 支持双工通信,是一种异步通信机制,采用数据报信息(SOCK_DGRAM)格式传送数据。在内核与用户态应用之间传递的消息保存在socket缓存队列中,发送消息只是把消息保存在接收者的socket的接收队列,而不需要等待接收者收到消息。
该机制一个非常重要的用户是通用设备模型,使用netlink套接字将各种关于内核内部事务的状态信息传递到用户层,其中包括新设备的注册和移除、硬件层次上发生的特别事件等等。
一、Netlink用户态应用的使用
1.1 Netlink socket
以下命令查看netlink套接字相关信息:
man 7 netlink
Netlink是一个面向数据报的服务。用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,AF_NETLINK 系列提供多个协议子集。 每个接口都连接到不同的内核组件并具有不同的消息传递子集。 该子集由套接字调用中的 the protocol 字段引用:
/* Create a new socket of type TYPE in domain DOMAIN, using
protocol PROTOCOL. If PROTOCOL is zero, one is chosen automatically.
Returns a file descriptor for the new socket, or -1 for errors. */
extern int socket (int __domain, int __type, int __protocol) __THROW;
int socket(AF_NETLINK, SOCK_DGRAM or SOCK_RAW, protocol)
第一个参数必须是PF_NETLINK或者AF_NETLINK(这两者等价)。
对于第二个参数,由于缺乏标准,SOCK_DGRAM 和 SOCK_RAW 不能保证在给定的 Linux(或其他操作系统)版本中实现。 一些消息来源指出这两个选项都是合理的,Red Hat 推荐使用 SOCK_RAW 作为参数。 但是,iproute2 工具包中可以互换使用两者。
对于第三个参数,netlink提供“协议”来标示通信实体,在创建socket的时候,需要指定netlink的通信协议号。每个协议号代表一种“应用”,上层可以用内核已经定义的协议和内核进行通信,获得内核已经提供的信息。其中protocol的选项可以是:
#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_INET_DIAG NETLINK_SOCK_DIAG
#define MAX_LINKS 32
NETLINK_ROUTE:提供路由和链接信息, 此信息主要用于用户空间路由守护程序。
NETLINK_FIREWALL:为用户空间应用程序提供接收防火墙数据包的接口。
NETLINK_NFLOG:提供了一个用于Netfilter(内核模块)和iptables(用户空间工具包)之间通信的接口。
NETLINK_ARPD:提供从用户空间管理 ARP 表的接口。
NETLINK_AUDIT:为 Linux 内核版本 2.6.6 及更高版本中的审计子系统提供接口。
NETLINK_IP6_FW:提供一个接口将数据包从 netfilter 传输到用户空间。
NETLINK_XFRM:用于发送和接受有关IPSec的信息。
NETLINK_KOBJECT_UEVENT:提供内核广播 uevent 的接口,通常由 udev 使用,是内核通用模型向用户层发送信息所采用的协议(内核热插拔机制的基础)。
NETLINK_GENERIC:Netlink 协议的缺点之一是协议族的数量被限制为 32 (MAX_LINKS)。这是创建通用 Netlink 族的主要原因之一 – 为增加更多的 families 提供支持。它充当一个Netlink多路复用器,并与单个Netlink families NETLINK_GENERIC一起工作。通用Netlink协议基于Netlink协议并使用它的API
由此可见在Linux内核中,使用nertlink进行应用与内核通信的应用很多。
对于上述协议的用途,比如用来创建一个上层应用,通过使用NETLINK_ROUTE协议通信,可以获得内核的路由信息。当然我们也可以自定义通信协议,但不能使用已有的协议编号,也不能超过MAX_LINKS,因此我们我们自定义netlink通信协议编号可以 22 - 32
1.2 Netlink宏操作
Netlink消息由一个字节流和一个或多个nlmsghdr头以及相关的有效负载组成。只能使用标准的NLMSG_*宏访问字节流。
<linux/netlink.h> 定义了几个标准宏来访问或创建 netlink 数据报。 应该只使用这些宏来访问传入和传出 netlink 套接字的缓冲区。
#define NLMSG_ALIGNTO 4U
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
(struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len <= (len))
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
NLMSG_ALIGN()
Round the length of a netlink message up to align it properly.
NLMSG_LENGTH()
Given the payload length, len, this macro returns the aligned length to store in the nlmsg_len field of the nlmsghdr.
NLMSG_SPACE()
Return the number of bytes that a netlink message with payload of len would occupy.
NLMSG_DATA()
Return a pointer to the payload associated with the passed nlmsghdr.
NLMSG_NEXT()
Get the next nlmsghdr in a multipart message. The caller must check if the current nlmsghdr didn't have the NLMSG_DONE set—this function doesn't return NULL on end. The len argument
is an lvalue containing the remaining length of the message buffer. This macro decrements it by the length of the message header.
NLMSG_OK()
Return true if the netlink message is not truncated and is in a form suitable for parsing.
NLMSG_PAYLOAD()
Return the length of the payload associated with the nlmsghdr.
二、Netlink对应数据结构
2.1 struct sockaddr_nl
struct sockaddr_nl是netlink套接字通信地址:
struct sockaddr_nl {
__kernel_sa_family_t nl_family; /* AF_NETLINK */
unsigned short nl_pad; /* zero */
__u32 nl_pid; /* port ID unicast address */
__u32 nl_groups; /* multicast groups mask */
};
nl_pid 是 netlink 套接字的单播地址,一个约定的通信端口,用户态使用的时候需要用一个非0的数字,一般来说可以直接采用上层应用的进程ID(不用进程ID号码也没事,只要系统中不冲突的一个数字即可使用)。对于内核的地址,该值必须用0,也就是说,如果上层通过sendto向内核发送netlink消息,peer addr中nl_pid必须填写0。
nl_pid 标识的是 netlink 套接字,而不是进程。本质上,nl_pid就是netlink的通信地址。
通过 protocol 和 nl_pid 组成Netlink的通信方式。
编写netlink通信时,需要源struct sockaddr_nl结构体和目的地 struct sockaddr_nl结构体。
2.2 struct nlmsghdr
Netlink的报文由消息头和消息体构成,struct nlmsghdr即为消息头:
struct nlmsghdr {
__u32 nlmsg_len; /* Length of message including header */
__u16 nlmsg_type; /* Message content */
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process port ID */
};
(1) nlmsg_len:整个消息的长度,按字节计算。包括了Netlink消息头本身。
(2) nlmsg_type:消息的类型,即是数据还是控制消息。
(3) nlmsg_flags:附加在消息上的额外说明信息。
其中的nlmsg_flags:
/* 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 */
/* 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 socketd的type是SOCK_DGRAM ,所以Netlink是一个不可靠的协议。它尽最大努力将消息传递到目的地,但当内存不足的情况或其他错误发生时,可能会丢弃消息。为了实现可靠的传输,发送方可以通过设置NLM_F_ACK标志向接收方请求确认。确认是一个 NLMSG_ERROR 数据包,错误字段设置为 0。应用程序必须为接收到的消息本身生成确认。内核尝试为每个失败的包发送一个NLMSG_ERROR消息。用户进程也应该遵循这个约定。
#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 0x10 /* < 0x10: reserved control messages */
2.3 struct msghdr
用户层调用sendmsg和recvmsg是所需要的结构体:
/* Send a message described MESSAGE on socket FD.
Returns the number of bytes sent, or -1 for errors.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t sendmsg (int __fd, const struct msghdr *__message,
int __flags);
/* Receive a message as described by MESSAGE from socket FD.
Returns the number of bytes read or -1 for errors.
This function is a cancellation point and therefore not marked with
__THROW. */
extern ssize_t recvmsg (int __fd, struct msghdr *__message, int __flags);
/* Structure describing messages sent by
`sendmsg' and received by `recvmsg'. */
struct msghdr
{
void *msg_name; /* Address to send to/receive from. */
socklen_t msg_namelen; /* Length of address data. */
struct iovec *msg_iov; /* Vector of data to send/receive into. */
size_t msg_iovlen; /* Number of elements in the vector. */
void *msg_control; /* Ancillary data (eg BSD filedesc passing). */
size_t msg_controllen; /* Ancillary data buffer length.
!! The type should be socklen_t but the
definition of the kernel is incompatible
with this. */
int msg_flags; /* Flags on received message. */
};
其中 struct iovec :
/* Structure for scatter/gather I/O. */
struct iovec
{
void *iov_base; /* Pointer to data. */
size_t iov_len; /* Length of data. */
};
iov_base: iov_base指向数据包缓冲区,即参数buff,iov_len是buff的长度。msghdr中允许一次传递多个buff,以数组的形式组织在 msg_iov中,msg_iovlen就记录数组的长度 (即有多少个buff)。
iov_base指向如下数据结构(包含了消息头 struct nlmsghdr ):
以上三种关系如下图所示:
图片来自于:linux用户空间与内核空间通信——Netlink通信机制
三、用户层实例
NETLINK_ROUTE:接收路由和链路更新,并可用于修改路由表(IPv4和IPv6)、IP地址、链路参数、邻居设置、排队规则、流分类和包分类。
/****
* General form of address family dependent message.
****/
struct rtgenmsg {
unsigned char rtgen_family;
};
/*****************************************************************
* Link layer specific messages.
****/
/* struct ifinfomsg
* passes link level specific information, not dependent
* on network protocol.
*/
struct ifinfomsg {
unsigned char ifi_family;
unsigned char __ifi_pad;
unsigned short ifi_type; /* ARPHRD_* */
int ifi_index; /* Link index */
unsigned ifi_flags; /* IFF_* flags */
unsigned ifi_change; /* IFF_* change mask */
};
/********************************************************************
/*
Generic structure for encapsulation of optional route information.
It is reminiscent of sockaddr, but with sa_family replaced
with attribute type.
*/
struct rtattr {
unsigned short rta_len;
unsigned short rta_type;
};
/* Macros to handle rtattributes */
#define RTA_ALIGNTO 4
#define RTA_ALIGN(len) ( ((len)+RTA_ALIGNTO-1) & ~(RTA_ALIGNTO-1) )
#define RTA_OK(rta,len) ((len) >= (int)sizeof(struct rtattr) && \
(rta)->rta_len >= sizeof(struct rtattr) && \
(rta)->rta_len <= (len))
#define RTA_NEXT(rta,attrlen) ((attrlen) -= RTA_ALIGN((rta)->rta_len), \
(struct rtattr*)(((char*)(rta)) + RTA_ALIGN((rta)->rta_len)))
#define RTA_LENGTH(len) (RTA_ALIGN(sizeof(struct rtattr)) + (len))
#define RTA_SPACE(len) RTA_ALIGN(RTA_LENGTH(len))
#define RTA_DATA(rta) ((void*)(((char*)(rta)) + RTA_LENGTH(0)))
#define RTA_PAYLOAD(rta) ((int)((rta)->rta_len) - RTA_LENGTH(0))
enum {
IFLA_UNSPEC,
IFLA_ADDRESS,
IFLA_BROADCAST,
IFLA_IFNAME,
IFLA_MTU,
IFLA_LINK,
IFLA_QDISC,
IFLA_STATS,
......
}
获取网络接口的名字:
/*
* Display all network interface names
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#define BUFSIZE 8192
struct nl_req_s {
struct nlmsghdr hdr;
struct rtgenmsg gen;
};
void rtnl_print_link(struct nlmsghdr * h)
{
struct ifinfomsg * iface;
struct rtattr * attr;
int len;
iface = NLMSG_DATA(h);
len = RTM_PAYLOAD(h);
/* loop over all attributes for the NEWLINK message */
for (attr = IFLA_RTA(iface); RTA_OK(attr, len); attr = RTA_NEXT(attr, len))
{
switch (attr->rta_type)
{
case IFLA_IFNAME:
printf("Interface %d : %s\n", iface->ifi_index, (char *)RTA_DATA(attr));
break;
default:
break;
}
}
}
int main(void)
{
struct sockaddr_nl src_addr;
int s, end=0, len;
struct msghdr msg;
struct nl_req_s req;
struct iovec io;
char buf[BUFSIZE];
//build src_addr netlink address
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_groups = 0;
//create a Netlink socket
if ((s=socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0){
perror("socket");
return -1;
}
//build netlink message
memset(&req, 0, sizeof(req));
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
req.hdr.nlmsg_type = RTM_GETLINK;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.hdr.nlmsg_seq = 1;
req.hdr.nlmsg_pid = getpid();
req.gen.rtgen_family = AF_INET;
memset(&io, 0, sizeof(io));
io.iov_base = &req;
io.iov_len = req.hdr.nlmsg_len;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &io;
msg.msg_iovlen = 1;
msg.msg_name = &src_addr;
msg.msg_namelen = sizeof(src_addr);
//send the message
if (sendmsg(s, &msg, 0) < 0)
{
perror("sendmsg");
}
//parse reply
while (!end){
memset(buf, 0, BUFSIZE);
msg.msg_iov->iov_base = buf;
msg.msg_iov->iov_len = BUFSIZE;
if ((len=recvmsg(s, &msg, 0)) < 0){
perror("recvmsg");
}
for (struct nlmsghdr * msg_ptr = (struct nlmsghdr *)buf;
NLMSG_OK(msg_ptr, len); msg_ptr = NLMSG_NEXT(msg_ptr, len)){
switch (msg_ptr->nlmsg_type)
{
case NLMSG_DONE:
end++;
break;
case RTM_NEWLINK:
rtnl_print_link(msg_ptr);
break;
default:
printf("Ignored msg: type=%d, len=%d\n", msg_ptr->nlmsg_type, msg_ptr->nlmsg_len);
break;
}
}
}
close(s);
return 0;
}
[root@localhost netlink]# ./a.out
Interface 1 : lo
Interface 2 : enp1s0
Interface 3 : virbr0
Interface 4 : virbr0-nic
参考资料
https://zhuanlan.zhihu.com/p/269141945
https://www.jianshu.com/p/073bcd9c3b08
https://gist.github.com/cl4u2/5204374