Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。Netlink 是一种特殊的 socket,它是 Linux 所特有的。
Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink。
//netlink收发是以消息为单位的,每次收发可以包含一个或多个消息(msg)
------------------------------------------------------------------------
| 单次sendto或者recvfrom 数据部分 |
------------------------------------------------------------------------
| msg0 | msg1 | msgn |
------------------------------------------------------------------------
| nlmsghdr | data(携带的数据) | nlmsghdr | data(携带的数据) | .... |
------------------------------------------------------------------------
/*套接字结构体*/
struct sockaddr_nl {
__kernel_sa_family_t nl_family; /* AF_NETLINK (跟AF_INET对应)*/
unsigned short nl_pad; /* zero */
__u32 nl_pid; /* port ID (通信端口号)*/
__u32 nl_groups; /* multicast groups mask */
};
/*struct nlmsghdr 是netlink消息头*/
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 */
};
nlmsg_len :指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小
nlmsg_type :用于应用内部定义消息的类型,它对 netlink 内核实现是透明的,因此大部分情况下设置为 0
/* Type values */
#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 */
/*NLMSG_NOOP:不执行任何动作,必须将该消息丢弃;
NLMSG_ERROR:消息发生错误;
NLMSG_DONE:标识分组消息的末尾;
NLMSG_OVERRUN:缓冲区溢出,表示某些消息已经丢失。
NLMSG_MIN_TYPEK:预留 */
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 */
nlmsg_seq: 消息序列号,用以将消息排队,有些类似TCP协议中的序号(不完全一样),但是netlink的这个字段是可选的,不强制使用;
nlmsg_pid:发送端口的ID号,对于内核来说该值就是0,对于用户进程来说就是其socket所绑定的ID号
应用层使用接口是标准的 socket API,与UDP通信类似
int socket(int domain, int type, int protocol)
domain :使用netlink方式通信时配置为 AF_NETLINK
type :使用netlink方式通信时配置为 SOCK_RAW
protocol:自定义的通信协议
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
addr :传参时要将转入的struct sockaddr_nl结构体指针变量强转为struct sockaddr *
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen)
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen)
套接字结构体
/* optional Netlink kernel configuration parameters */
struct netlink_kernel_cfg {
unsigned int groups;
unsigned int flags;
void (*input)(struct sk_buff skb); / *input 回调函数 */一
struct mutex *cb_mutex;
void (*bind)(int group);
bool (*compare)(struct net *net, struct sock *sk);
};
套接字缓存,作为网络数据包的存放地点,使得协议栈中每个层都可以对数据进行操作,从而实现了数据包自底向上的传递
struct sk_buff
{
struct sk_buff *next;
struct sk_buff *prev;
struct sock *sock ; //struct sock是socket在网络层的表示,其中存放了网络层的信息
unsigned int len; //表示当前协议数据包的长度。它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。
unsigned int data_len; //和len不同,data_len只计算分片中数据的长度
__u16 mac_len ; //数路链路层的头长度
__u16 hdr_len ; //writable header length of cloned skb
unsigned int truesize ; //socket buffer(套接字缓存区的大小)
atomic_t users ; //对当前的struct sk_buff结构体的引用次数;
__u32 priority ; //这个struct sk_buff结构体的优先级
sk_buff_data_t transport_header ; //传输层头部的偏移量
sk_buff_data_t network_header ; //网络层头部的偏移量
sk_buff_data_t mac_header ; //数据链路层头部的偏移量
char *data ; //socket buffer中数据的起始位置;
sk_buff_data_t tail ; //socket buffer中数据的结束位置;
char *head ; //socket buffer缓存区的起始位置;
sk_buffer_data_t end ; //socket buffer缓存区的终止位置;
struct net_device *dev; //将要发送struct sk_buff结构体的网络设备或struct sk_buff的接收网络设备
int iif; //网络设备的接口索引号;
struct timeval tstamp ; //用于存放接受的数据包的到达时间;
__u8 local_df : 1 , //allow local fragmentaion;
cloned : 1 , // head may be cloned
;
__u8 pkt_type : 3 , //数据包的类型;
fclone : 2, // struct sk_buff clone status
}
netlink_kernel_create内核函数用于创建 内核socket用用户态通信
static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
net: net指向所在的网络命名空间, 一般默认传入的是&init_net(不需要定义); 定义在net_namespace.c(extern struct net init_net);
unit:netlink协议类型,对应用户态创建套接字时的protocol参数,两者需保持一致
cfg: cfg存放的是netlink内核配置参数
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
ssk: netlink socket
skb: skb buff 指针
portid: 通信的端口号,对应用态的端口号
nonblock:表示该函数是否为非阻塞,如果为1(MSG_DONTWAIT),该函数将在没有接收缓存可利用时立即返回,而如果为0(MSG_WAITALL),该函数在没有接收缓存可利用 定时睡眠
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid,
__u32 group, gfp_t allocation);
ssk: 同上(对应netlink_kernel_create 返回值)、
skb: 内核skb buff
portid: 通信的端口号,对应用态的端口号
group: 是所有目标多播组对应掩码的"OR"操作的合值。
allocation: 指定内核内存分配方式,通常GFP_ATOMIC用于中断上下文,而GFP_KERNEL用于其他场合。
这个参数的存在是因为该API可能需要分配一个或多个缓冲区来对多播消息进行clone
获取netlink消息的头部指针
分配一个新的netlink消息
struct sk_buff *nlmsg_new(size_t payload, gfp_t flags)
payload : 分配的大小
flags:
进程上下文,可以睡眠:GFP_KERNEL
进程上下文,不可以睡眠:GFP_ATOMIC
中断处理程序:GFP_ATOMIC
软中断:GFP_ATOMIC
Tasklet:GFP_ATOMIC
用于DMA的内存,可以睡眠:GFP_DMA | GFP_KERNEL
用于DMA的内存,不可以睡眠:GFP_DMA |GFP_ATOMIC
向skb缓冲区中获取消息头空间并且初始化netlink消息头,入参中的第5个参数为netlink消息头的总空间
struct nlmsghdr *nlmsg_put(struct sk_buff *skb, u32 portid, u32 seq,
int type, int payload, int flags)
portid:与 netlink消息头 中的 nlmsg_pid 对应
seq:与 netlink消息头 中的 nlmsg_seq 对应
type:与 netlink消息头 中的 nlmsg_type 对应
payload:与 netlink消息头 中的 nlmsg_len 对应
flags:与 netlink消息头 中的 nlmsg_flags 对应
1、NLMSG_LENGTH(len)
根据消息数据部分长度,返回消息长度,即头长度+len,但是这个长度不是对齐的(消息头和整个消息都是4字节对其的;
len:消息数据部分长度
2、NLMSG_SPACE(len)
根据消息数据部分长度,返回消息实际占用空间,也就是在NLMSG_LENGTH(len)基础上进行4字节对齐。
len:消息数据部分长度
-----------------------------------------------------------
| nlmsghdr | data(携带的数据) |
-----------------------------------------------------------
| |<----- len --->| |
| <------- NLMSG_LENGTH(len) ---->|<--4字节对齐-->|
| <------- NLMSG_SPACE(len) ---->|
3、NLMSG_DATA(nlh)
根据消息内存地址,返回数据部分地址.
nlh:消息头地址,消息头地址和消息地址是一样。
-----------------------------------------------------
| nlmsghdr | data(携带的数据) |
-----------------------------------------------------
| |
| |
--->这里就是nlh地址 |
|
---> 这里就是返回值,也就是数据部分地址(与TCP/IP协议很像吧, 先是数据头再是数据)
4、NLMSG_NEXT(nlh,len)
根据当前消息地址,返回下一个消息地址
nlh: 当前消息地址
len:剩余消息部分长度,比如当前recvfrom收到100字节数据,包含4个长度为25字节的消息,存储在buf中:
5、NLMSG_OK(nlh,len)
检查nlh地址是否是一条完整的消息
nlh: 通过recvfrom收到数据的缓冲区地址
len: 收到数据的长度
6、NLMSG_PAYLOAD(nlh,len)
消息数据部分剩余长度
nlh: 消息的首地址
len: 数据部分已经使用的长度
|<------------------100字节-------------------------->|
-----------------------------------------------------
| nlmsghdr | data(携带的数据) |
-----------------------------------------------------
| |<--------len------|<----+---->|
| |
| |
--> nlh +---> 这里就是返回值的含义