一,什么是netlink通信机制
Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。Netlink 是一种特殊的 socket,它是 Linux 所特有的。
Netlink 是一种在内核与用户应用间进行双向数据传输的非常好的方式,用户态应用使用标准的 socket API 就可以使用 netlink 提供的强大功能,内核态需要使用专门的内核 API 来使用 netlink。
二,netlink通信机制的特点
-
使用Netlink通过自定义一种新的协议并加入协议族即可通过socket API使用Netlink协议完成数据交换,而ioctl和proc文件系统均需要通过程序加入相应的设备或文件。
-
Netlink使用socket缓存队列,是一种异步通信机制,而ioctl是同步通信机制,如果传输的数据量较大,会影响系统性能。
-
Netlink支持多播,属于一个Netlink组的模块和进程都能获得该多播消息。
-
使用 netlink 的内核部分可以采用模块的方式实现,使用 netlink 的应用部分和内核部分没有编译时依赖
-
Netlink允许内核发起会话,而ioctl和系统调用只能由用户空间进程发起。
三,用户态常用结构体和接口
1,struct sockaddr_nl协议套接字
/*套接字结构体*/
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 */
};
nl_family 制定了协议族,netlink 有自己独立的值:AF_NETLINK,nl_pid 一般取为进程 pid。nl_groups 用以多播,当不需要多播时,该字段为 0。
nl_pid:该属性为发送或接收消息的进程ID,前面我们也说过,Netlink不仅可以实现用户-内核空间的通信还可使现实用户空间两个进程之间,或内核空间两个进程之间的通信。该属性为0时一般适用于如下两种情况:
第一,我们要发送的目的地是内核,即从用户空间发往内核空间时,我们构造的Netlink地址结构体中nl_pid通常情况下都置为0。这里有一点需要跟大家交代一下,在Netlink规范里,PID全称是Port-ID(32bits),其主要作用是用于唯一的标识一个基于netlink的socket通道。通常情况下nl_pid都设置为当前进程的进程号。
第二,从内核发出的多播报文到用户空间时,如果用户空间的进程处在该多播组中,那么其地址结构体中nl_pid也设置为0。
2,netlink的消息格式
Netlink消息由两部分组成:消息头和有效数据载荷,且整个Netlink消息是4字节对齐,一般按主机字节序进行传递。消息头为固定的16字节,消息体长度可变:
//netlink收发是以消息为单位的,每次收发可以包含一个或多个消息(msg)
------------------------------------------------------------------------
|单次sendto或者recvfrom 数据部分|
------------------------------------------------------------------------
|msg0|msg1|msgn|
------------------------------------------------------------------------
| nlmsghdr | data(携带的数据)| nlmsghdr | data(携带的数据)|....|
------------------------------------------------------------------------
3,netlink的消息头
消息头定义在文件里,由结构体nlmsghdr表示:
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 PID */
};
消息头中各成员属性的解释及说明:
nlmsg_len:整个消息的长度,按字节计算。包括了Netlink消息头本身。
nlmsg_type:消息的类型,即是数据还是控制消息。目前(内核版本2.6.21)Netlink仅支持四种类型的控制消息,如下:
NLMSG_NOOP-空消息,什么也不做;
NLMSG_ERROR-指明该消息中包含一个错误;
NLMSG_DONE-如果内核通过Netlink队列返回了多个消息,那么队列的最后一条消息的类型为NLMSG_DONE,其余所有消息的nlmsg_flags属性都被设置NLM_F_MULTI位有效。
NLMSG_OVERRUN-暂时没用到。
nlmsg_flags:附加在消息上的额外说明信息,如上面提到的NLM_F_MULTI。摘录如下:
NLM_F_REQUEST
如果消息中有该标记位,说明这是一个请求消息。所有从用户空间到内核空间的消息都要设置该位,否则内核将向用户返回一个EINVAL无效参数的错误
NLM_F_MULTI
消息从用户->内核是同步的立刻完成,而从内核->用户则需要排队。如果内核之前收到过来自用户的消息中有NLM_F_DUMP位为1的消息,那么内核就会向用户空间发送一个由多个Netlink消息组成的链表。除了最后个消息外,其余每条消息中都设置了该位有效。
NLM_F_ACK
该消息是内核对来自用户空间的NLM_F_REQUEST消息的响应
NLM_F_ECHO
如果从用户空间发给内核的消息中该标记为1,则说明用户的应用进程要求内核将用户发给它的每条消息通过单播的形式再发送给用户进程。和我们通常说的“回显”功能类似。
nlmsg_seq:消息序列号。因为Netlink是面向数据报的,所以存在丢失数据的风险,但是Netlink提供了如何确保消息不丢失的机制,让程序开发人员根据其实际需求而实现。消息序列号一般和NLM_F_ACK类型的消息联合使用,如果用户的应用程序需要保证其发送的每条消息都成功被内核收到的话,那么它发送消息时需要用户程序自己设置序号,内核收到该消息后对提取其中的序列号,然后在发送给用户程序回应消息里设置同样的序列号。有点类似于TCP的响应和确认机制。
注意:当内核主动向用户空间发送广播消息时,消息中的该字段总是为0。
nlmsg_pid:当用户空间的进程和内核空间的某个子系统之间通过Netlink建立了数据交换的通道后,Netlink会为每个这样的通道分配一个唯一的数字标识。其主要作用就是将来自用户空间的请求消息和响应消息进行关联。说得直白一点,假如用户空间存在多个用户进程,内核空间同样存在多个进程,Netlink必须提供一种机制用于确保每一对“用户-内核”空间通信的进程之间的数据交互不会发生紊乱。
4,用户态与内核态对数据处理的宏函数
/* 宏 NLMSG_ALIGN(len) 用于得到不小于len且字节对齐的最小数值 */
#define NLMSG_ALIGNTO 4U
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
/* Netlink 头部长度 */
#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/* 计算消息数据 len 的真实消息长度(消息体 + 消息头)*/
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
/* 宏 NLMSG_SPACE(len) 返回不小于 NLMSG_LENGTH(len) 且字节对齐的最小数值 */
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
/* 宏 NLMSG_DATA(nlh) 用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏 */
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
/* 宏 NLMSG_NEXT(nlh,len) 用于得到下一个消息的首地址, 同时 len 变为剩余消息的长度 */
#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 nlmsg_len - NLMSG_SPACE((len)))
5,创建套接字
应用层使用接口是标准的 socket API,与UDP通信类似。
int socket(int domain, int type, int protocol)
domain :使用netlink方式通信时配置为 AF_NETLINK
type :使用netlink方式通信时配置为 SOCK_RAW
protocol:自定义的通信协议
netlink 协议类型
#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
6,绑定套接字
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
addr :传参时要将转入的struct sockaddr_nl结构体指针变量强转为struct sockaddr *
7,收发送数据
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)
四,内核常用结构体和接口
1,struct sock结构体
套接字结构体
2,struct netlink_kernel_cfg 结构体
/* 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);
};
3, struct sk_buf 结构体
套接字缓存,作为网络数据包的存放地点,使得协议栈中每个层都可以对数据进行操作,从而实现了数据包自底向上的传递
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
}
struct sk_buff中head, end, data, tail字段的含义
4,netlink_kernel_create
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内核配置参数
5,单播netlink_unicast()
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),该函数在没有接收缓存可利用 定时睡眠
6,多播netlink_broadcast()
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
7, nlmsg_new()
分配一个新的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
8,nlmsg_put()
向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 对应
9,skb API
/**
* alloc_skb - allocate a network buffer
* @size: size to allocate
* @priority: allocation mask
*
* This function is a convenient wrapper around __alloc_skb().
*/
static inline struct sk_buff *alloc_skb(unsigned int size,
gfp_t priority)
{
return __alloc_skb(size, priority, 0, NUMA_NO_NODE);
}
static inline void *skb_put_data(struct sk_buff *skb, const void *data,
unsigned int len)
{
void *tmp = skb_put(skb, len);
memcpy(tmp, data, len);
return tmp;
}
五,netlink用户态和内核态交互过程
1,socket 通信主要有 2 个操作对象:server 端和 client 端
2,netlink_client – netlink_server 测试程序
user space接收kernel的广播消息,接收到消息后然后再发送消息到kernel。
test netlink client:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_EPOLL_EVENTS 64
#define NETLINK_T_MSG_LEN 2048
#define NETLINK_USER 31
#define MAX_PAYLOAD 1024 /* maximum payload size*/
int sk_fd = -1;
int mPollHandler = -1;
struct nlmsghdr *nlh = NULL;
int netlink_t_open_socket(int buf_sz, bool passcred) {
struct sockaddr_nl addr;
int on = passcred;
int buf_sz_readback = 0;
socklen_t optlen = sizeof(buf_sz_readback);
int s;
memset(&addr, 0x0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_pid = 0;
addr.nl_groups = 0xffffffff;
//s = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT);
s = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_USER);
if (s cmsg_type != SCM_CREDENTIALS) {
/* ignoring netlink message with no sender credentials */
goto out;
}
cred = (struct ucred*)CMSG_DATA(cmsg);
*uid = cred->uid;
if (addr.nl_pid != 0) {
/* ignore non-kernel */
goto out;
}
if (require_group && addr.nl_groups == 0) {
/* ignore unicast messages when requested */
goto out;
}
return n;
out:
/* clear residual potentially malicious data */
//bzero(buffer, length);
memset(buffer, 0 , length);
errno = EIO;
return -1;
}
static void signal_handler(int signum)
{
if (sk_fd > 0) {
close(sk_fd);
}
if (nlh != NULL) {
free(nlh);
nlh = NULL;
}
epoll_ctl(mPollHandler, EPOLL_CTL_DEL, sk_fd, NULL);
exit(EXIT_SUCCESS);
}
int main(int args, char *argv[])
{
struct epoll_event ev;
int i;
char msg[NETLINK_T_MSG_LEN + 2];
uid_t uid = -1;
int n;
//char *cp;
int ret;
struct msghdr msg_info; //msghdr includes: struct iovec * msg_iov;
struct sockaddr_nl dest_addr;
struct iovec iov;
int nevents;
struct epoll_event events[MAX_EPOLL_EVENTS];
signal(SIGINT, &signal_handler);
signal(SIGTERM, &signal_handler);
mPollHandler = epoll_create(MAX_EPOLL_EVENTS);
if (mPollHandler == -1) {
printf("Failed to initialize服务器托管网 Moto event loopern");
return false;
}
sk_fd = netlink_t_open_socket(64*1024, true);
if (sk_fd nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = getpid(); //self pid
nlh->nlmsg_flags = 0;
strcpy(NLMSG_DATA(nlh), "Hello this is a msg from userspace"); //put "Hello" into nlh
iov.iov_base = (void *)nlh; //iov -> nlh
iov.iov_len = nlh->nlmsg_len;
msg_info.msg_name = (void *)&dest_addr; //msg_name is Socket name: dest
msg_info.msg_namelen = sizeof(dest_addr);
msg_info.msg_iov = &iov; //msg -> iov
msg_info.msg_iovlen = 1;
ev.events = EPOLLIN | EPOLLPRI;
ev.data.fd = sk_fd;
if (epoll_ctl(mPollHandler, EPOLL_CTL_ADD, sk_fd, &ev) == -1) {
close(sk_fd);
printf("Failed to add epoll datan");
return false;
}
while (1) {
nevents = epoll_wait(mPollHandler, events, MAX_EPOLL_EVENTS, -1);
if (nevents == -1) {
if服务器托管网 (errno != EINTR)
printf("epoll wait failed %dn", errno);
continue;
}
//loop read
for (i = 0; i
test netlinkserver:
#include
#include
#include
#include
#include
#include
#define NETLINK_USER 31 //the user defined channel, the key factor
struct sock *nl_sk = NULL;
struct sk_buff *skb = NULL;
static ssize_t test_netlink_send_msg_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
const char *action_string = "netlink msg from kernel ";
const char *msg_info = "author william";
size_t len;
//char *scratch;
struct netlink_skb_parms *parms;
if (!buf || count creds.uid = GLOBAL_ROOT_UID;
parms->creds.gid = GLOBAL_ROOT_GID;
parms->dst_group = 1;
parms->portid = 0;
netlink_broadcast(nl_sk, skb_get(skb), 0 , 1, GFP_KERNEL);
consume_skb(skb);
return count;
}
/*
* ATTRIBUTES:
*
*/
static DEVICE_ATTR(send_msg, S_IWUSR | S_IRUGO,
NULL,
test_netlink_send_msg_store);
static struct attribute *test_netlink_attrs[] = {
&dev_attr_send_msg.attr,
NULL,
};
ATTRIBUTE_GROUPS(test_netlink);
static const struct of_device_id test_netlink_of_match[] = {
{ .compatible = "test-netlink", },
{ },
};
MODULE_DEVICE_TABLE(of, test_netlink_of_match);
static void test_netlink_recv_msg(struct sk_buff *skb)
{
struct nlmsghdr *nlh;
//for receiving...
nlh = (struct nlmsghdr*)skb->data;
printk("Netlink received msg payload: %sn",(char*)nlmsg_data(nlh));
}
static int test_netlink_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct netlink_kernel_cfg cfg = {
.input = test_netlink_recv_msg,
};
dev_info(dev, "%sn", __func__);
//allocate netlink socket
nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
if(!nl_sk)
{
dev_err(dev, "%s, Error creating socket.n", __func__);
return -1;
}
//if (!netlink_has_listeners(nl_sk, 1))
// return -1;
return 0;
}
static struct platform_driver netlink_device_driver = {
.probe = test_netlink_probe,
.driver = {
.name = "test-netlink",
.of_match_table = test_netlink_of_match,
.dev_groups = test_netlink_groups,
}
};
static int __init test_netlink_init(void)
{
return platform_driver_register(&netlink_device_driver);
}
static void __exit test_netlink_exit(void)
{
netlink_kernel_release(nl_sk);
platform_driver_unregister(&netlink_device_driver);
}
module_init(test_netlink_init);
module_exit(test_netlink_exit);
MODULE_LICENSE("GPL");
测试:
netlink client
netlink server
/sys/devices/platform/soc/soc:m_netlink_server # echo 1 > send_msg
参考链接:
内核通信之 Netlink 源码分析和实例分析-腾讯云开发者社区-腾讯云
Linux驱动-Netlink通信_linux netlink-CSDN博客
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
文章目录 题目链接 解题思路 解题代码 题目链接 15. 三数之和 给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + …