netlink socket是一种用于用户态进程和内核态进程之间的通信机制。它通过为内核模块提供一组特殊的API,并为用户程序提供了一组标准的socket接口的方式,实现了全双工的通讯连接。
内核版本中,已经有许多内核模块使用netlink 机制,其中驱动模型中使用的uevent 就是基于netlink 实现。目前 netlink 协议族支持32种协议类型,它们定义在 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
#define NETLINK_CTRL 17
/* 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</span>
在4.1.x 的内核版本中已经定义了22种协议类型,其中NETLINK_ROUTE是用于设置和查询路由表等网络核心模块的,NETLINK_KOBJECT_UEVENT是用于uevent消息通信
以上这几种专用的协议类型无法满足,这时可以在不超过最大32种类型的基础之上自行添加。但是一般情况下这样做有些不妥,于是内核开发者就设计了一种通用netlink 协议类型(Generic Netlink)NETLINK_GENERIC,它就是一个Netlink复用器,便于用户自行扩展子协议类型
struct sockaddr_nl
{
sa_family_t nl_family;
unsigned short nl_pad;
__u32 nl_pid;
__u32 nl_groups;
};
struct nlmsghdr
{
__u32 nlmsg_len; /* Length of message */
__u16 nlmsg_type; /* Message type*/
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process PID */
};
/* 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 */
/* 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 */
在linux/netlink.h中定义了一些方便对消息进行处理的宏,这些宏包括:
宏NLMSG_DATA(nlhdr) 返回指向消息的数据部分的指针
#define NLMSG_ALIGNTO 4
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
宏NLMSG_ALIGN(len)用于将 len 参数按 4 字节对齐。
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
宏NLMSG_LENGTH(len)用于计算整个 Netlink 消息的长度,包括头部和数据部分len。
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
宏NLMSG_SPACE(len)返回计算整个 Netlink 消息的空间大小,包括头部和数据部分。该宏将 len 参数按 4 字节对齐,并加上头部长度和尾部填充字节,得到整个 Netlink 消息的空间大小。
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
宏NLMSG_DATA(nlh)用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏。
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
(struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
宏NLMSG_NEXT(nlh,len)用于得到下一个消息的首地址,同时len也减少为剩余消息的总长度,该宏一般在一个消息被分成几个部分发送或接收时使用。
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \
(nlh)->nlmsg_len <= (len))
宏NLMSG_OK(nlh,len)用于判断消息是否有len这么长。
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
宏NLMSG_PAYLOAD(nlh,len)用于返回payload的长度。
#define NLMSG_TAIL(nmsg) \
((struct rtattr*)(((void*)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
宏NLMSG_TAIL(nlh) 返回指向 Netlink 消息数据部分结尾的指针,可以用于将数据添加到 Netlink 消息的末尾。
#define NLMSG_FOREACH(nmsg, len) \
for ((nmsg) = (struct nlmsghdr*)(void*)(buf); \
NLMSG_OK((nmsg), (len)); \
(nmsg) = NLMSG_NEXT((nmsg), (len)))
宏NLMSG_FOREACH(nlh, len) 用于遍历一个包含多个 Netlink 消息的序列,它接受一个指向第一个 Netlink 消息的指针 nlh,以及序列的总长度 len,然后在每个循环迭代中将指针移动到下一个 Netlink 消息
struct nlattr 是 Netlink 协议中的一个重要结构体,用于表示 Netlink 消息的属性
struct nlattr {
uint16_t nla_len; // 属性长度
uint16_t nla_type; // 属性类型
};
nla_len
表示属性的长度,包括头部和数据部分
nla_type
表示属性的类型,不同类型的属性具有不同的含义。属性的类型是一个 16 位的无符号整数,由用户空间和内核空间之间的协商确定
struct nlattr
结构体通常被嵌入在 Netlink 消息的数据部分中,作为一个属性的头部,多个属性按照顺序依次排列,形成一个属性列表。在属性列表中,可以通过 nla_len
和 nla_type
字段来确定每个属性的长度和类型。
由于属性的长度和类型都是固定长度的整数,因此可以通过指针运算来遍历属性列表。在 Linux 内核中,可以使用 nla_for_each_attr
宏来遍历属性列表,例如:
struct nlattr *attr;
int rem = nla_len;
nla_for_each_attr(attr, nlh, rem) {
// 处理每个属性
}
其中,attr
是指向当前属性的指针,nla_for_each_attr
宏会在每个循环迭代中将指针移动到下一个属性。在循环体中,可以使用 nla_len
和 nla_type
字段来访问当前属性的长度和类型。
struct rtattr 是 Linux 内核中路由相关数据结构中的一个重要结构体,用于表示一条路由的属性。
struct rtattr {
unsigned short rta_len; // 属性长度
unsigned short rta_type; // 属性类型
};
rta_len
表示属性的长度,包括头部和数据部分;
rta_type
表示属性的类型,不同类型的属性具有不同的含义。属性的类型是一个 16 位的无符号整数,由用户空间和内核空间之间的协商确定
struct rtattr
结构体通常被嵌入在一个路由表项的数据部分中,作为一个属性的头部。多个属性按照顺序依次排列,形成一个属性列表。在属性列表中,可以通过 rta_len
和 rta_type
字段来确定每个属性的长度和类型。
由于属性的长度和类型都是固定长度的整数,因此可以通过指针运算来遍历属性列表。在 Linux 内核中,可以使用 RTA_OK
和 RTA_NEXT
宏来遍历属性列表,例如
struct rtattr *attr;
int len = rtmsg.rtm_msglen - NLMSG_ALIGN(sizeof(struct rtmsg));
for (attr = RTM_RTA(rtmsg); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
// 处理每个属性
}
其中,attr
是指向当前属性的指针,RTM_RTA
宏返回指向第一个属性的指针,RTA_OK
宏判断当前属性是否合法,RTA_NEXT
宏返回下一个属性的指针。在循环体中,可以使用 rta_len
和 rta_type
字段来访问当前属性的长度和类型。
在路由相关的数据结构中,struct rtattr
结构体通常与 struct rtmsg
结构体一起使用,struct rtmsg
结构体用于表示一条路由的基本信息,而 struct rtattr
结构体用于表示路由的属性
RTA中涉及到的宏:
#define RTA_DATA(rta) \
((void*)(((char*)(rta)) + RTA_HDRLEN))
#define RTA_NEXT(rta, len) \
((len) -= RTA_ALIGN((rta)->rta_len), \
(struct rtattr*)(((char*)(rta)) + RTA_ALIGN((rta)->rta_len)))
RTA_NEXT 宏返回下一个属性的指针
#define RTA_OK(rta, len) \
((len) >= (ssize_t)sizeof(struct rtattr) && \
(rta)->rta_len >= sizeof(struct rtattr) && \
(rta)->rta_len <= (len))
RTA_OK 宏判断当前属性是否合法
#define RTA_FOREACH(rta, len, attr) \
for ((rta) = (struct rtattr*)(void*)(attr); \
RTA_OK((rta), (len)); \
(rta) = RTA_NEXT((rta), (len)))
#define RTA_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct rtattr)))
struct rtattr
结构体通常与 struct rtmsg
结构体一起使用,struct rtmsg
结构体用于表示一条路由的基本信息,而 struct rtattr
结构体用于表示路由的属性
struct rtmsg
是 Linux 内核中路由相关数据结构中的一个重要结构体,用于表示一条路由的基本信息。
struct rtmsg {
unsigned char rtm_family; // 地址族,如 AF_INET、AF_INET6
unsigned char rtm_dst_len; // 目标地址的掩码长度
unsigned char rtm_src_len; // 源地址的掩码长度
unsigned char rtm_tos; // 服务类型字段
unsigned char rtm_table; // 路由表标识
unsigned char rtm_protocol; // 路由的协议标识
unsigned char rtm_scope; // 路由的可见性范围
unsigned char rtm_type; // 路由的类型
unsigned int rtm_flags; // 路由的标志位
};
其中,各个字段的含义如下:
rtm_family
:地址族,表示地址的类型,例如 IPv4 或 IPv6。rtm_dst_len
:目标地址的掩码长度。rtm_src_len
:源地址的掩码长度。rtm_tos
:服务类型字段,用于标识 QoS(Quality of Service)信息。rtm_table
:路由表标识,用于区分不同的路由表。rtm_protocol
:路由的协议标识,用于标识路由的来源,例如 RIP、OSPF 等。rtm_scope
:路由的可见性范围,用于标识路由的作用域,例如本地路由、全局路由等。rtm_type
:路由的类型,用于标识路由的作用类型,例如主机路由、网关路由等。rtm_flags
:路由的标志位,用于表示路由的属性,例如是否是默认路由等。
struct rtmsg
结构体通常用于表示一条路由的基本信息,例如目标地址、掩码长度、下一跳地址等。在读取或设置路由信息时,可以使用 struct rtmsg
结构体来表示路由的基本属性,以便与 struct rtattr
结构体一起表示完整的路由信息
struct iovec
struct msghdr