文章目录
- 前言
- 一、内核态编程
- 二、用户态编程
- 三、代码测试结果
前言
请参考:Linux 网络之netlink 简介
一、内核态编程
用了 Netlink Socket 库中的函数和数据结构,通过 Netlink Socket 接收消息并将消息发送回用户空间进程。
代码说明:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/netlink.h>
#include <net/sock.h>#define NETLINK_TEST 25
#define MSG_SIZE 100static struct sock *nl_sk = NULL;int send_nl_message(struct sock *sock, int pid, char *message, int message_len)
{struct sk_buff *skb_out;struct nlmsghdr *nlh_out;// Allocate a new skb// 创建一个 sk_buff 缓冲区skb_out = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);if (!skb_out) {return -ENOMEM;}// Initialize the netlink message headernlh_out = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, message_len, 0);if (!nlh_out) {nlmsg_free(skb_out);return -ENOMEM;}// Set the destination PID and copy the message dataNETLINK_CB(skb_out).dst_group = 0; /* unicast */memcpy(NLMSG_DATA(nlh_out), message, message_len);// Send the message to the specified PID// 向指定的 Netlink Socket 发送消息if (nlmsg_unicast(sock, skb_out, pid) < 0) {nlmsg_free(skb_out);return -EFAULT;}return 0;
}static void nl_recv_msg(struct sk_buff *__skb)
{struct sk_buff *skb;char str[MSG_SIZE] = {0};struct nlmsghdr *nlh;if (__skb == NULL) {return;}skb = skb_get(__skb);if (skb->len < NLMSG_SPACE(0)) {return;}nlh = nlmsg_hdr(skb);memset(str, 0, sizeof(str));memcpy(str, NLMSG_DATA(nlh), sizeof(str));printk(KERN_INFO "receive message (pid:%d):%s\n", nlh->nlmsg_pid, str);// Send message back to user space processif (send_nl_message(nl_sk, nlh->nlmsg_pid, str, strlen(str)) < 0) {printk(KERN_ERR "Failed to send message to user space process\n");}
}static int __init netlink_init(void)
{struct netlink_kernel_cfg cfg = {.input = nl_recv_msg,};nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);if (!nl_sk) {printk(KERN_ALERT "Failed to create netlink socket.\n");return -ENOMEM;}printk(KERN_INFO "Netlink socket created.\n");return 0;
}static void __exit netlink_exit(void)
{netlink_kernel_release(nl_sk);printk(KERN_INFO "Netlink socket released.\n");
}module_init(netlink_init);
module_exit(netlink_exit);MODULE_LICENSE("GPL");
(1)send_nl_message: 该函数用于向指定的 Netlink Socket 发送消息。它创建了一个 sk_buff 缓冲区,并使用 nlmsg_put 函数初始化了消息头部。然后,它将目标 PID 设置为指定的 PID,并将消息数据复制到消息数据缓冲区中。最后,它使用 nlmsg_unicast 函数将消息发送到指定的 Netlink Socket。
(2)nl_recv_msg: 该函数是 Netlink Socket 的回调函数,当内核收到消息时,会自动调用该函数。它接收到一个 sk_buff 缓冲区,并从中提取出消息数据。然后,它打印出消息数据和发送者的 PID,并使用 send_nl_message 函数将消息发送回用户空间进程。
(3)netlink_init: 该函数是内核模块的初始化函数。它使用 netlink_kernel_create 函数创建了一个 Netlink Socket,并将回调函数指定为 nl_recv_msg 函数。如果创建失败,该函数将返回一个负数错误码。
(4)netlink_exit: 该函数是内核模块的清理函数。它使用 netlink_kernel_release 函数释放了之前创建的 Netlink Socket。
在模块初始化时,我们使用 netlink_kernel_create 函数创建了一个 Netlink Socket。该函数接收一个 netlink_kernel_cfg 结构体参数,其中包含了一个回调函数指针。我们将回调函数指定为 nl_recv_msg 函数,以便在内核接收到消息时自动调用该函数。
在 nl_recv_msg 函数中,我们从 sk_buff 缓冲区中提取出消息数据,并将其打印到内核日志中。然后,我们使用 send_nl_message 函数将消息发送回用户空间进程。
在清理函数中,我们使用 netlink_kernel_release 函数释放之前创建的 Netlink Socket。
二、用户态编程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/netlink.h>#define NETLINK_TEST 25
#define MSG_SIZE 100#define MAX_PAYLOAD 1024int netlink_recv_message(int sock_fd, unsigned char *message, int *len)
{if(message == NULL|| len == NULL) {return -1;}//create messagestruct nlmsghdr *nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));if( !nlh ) {perror("malloc");return -1;}struct sockaddr_nl src_addr;socklen_t addrlen = sizeof(struct sockaddr_nl);memset(&src_addr, 0, addrlen);if(recvfrom(sock_fd, nlh, NLMSG_SPACE(MAX_PAYLOAD), 0, (struct sockaddr *)&src_addr, (socklen_t *)&addrlen) < 0 ) {printf("recvmsg error!\n");free(nlh);return -1;}*len = nlh->nlmsg_len - NLMSG_SPACE(0);memcpy(message, (unsigned char *)NLMSG_DATA(nlh), *len);free(nlh);return 0;
}int main() {int sock_fd, len;struct sockaddr_nl src_addr, dst_addr;struct nlmsghdr *nlh;char msg[MSG_SIZE] = {0};sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);if (sock_fd < 0) {perror("socket");exit(EXIT_FAILURE);}memset(&src_addr, 0, sizeof(struct sockaddr_nl));src_addr.nl_family = AF_NETLINK;src_addr.nl_pid = getpid();bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(struct sockaddr_nl));memset(&dst_addr, 0, sizeof(struct sockaddr_nl));dst_addr.nl_family = AF_NETLINK;dst_addr.nl_pid = 0; /* send to kernel */dst_addr.nl_groups = 0; /* unicast */nlh = (struct nlmsghdr*)malloc(NLMSG_SPACE(MSG_SIZE));memset(nlh, 0, NLMSG_SPACE(MSG_SIZE));nlh->nlmsg_len = NLMSG_SPACE(MSG_SIZE);nlh->nlmsg_pid = getpid();nlh->nlmsg_flags = 0;char buf[MAX_PAYLOAD] = {0};while(1){printf("Enter message: ");fgets(msg, MSG_SIZE, stdin);len = strlen(msg);msg[len-1] = '\0';if(strncmp(msg, "quit", 4) == 0){break;}memcpy(NLMSG_DATA(nlh), msg, len);if (sendto(sock_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr*)&dst_addr, sizeof(struct sockaddr_nl)) < 0) {perror("sendto");exit(EXIT_FAILURE);}if(netlink_recv_message(sock_fd, buf, &len) == 0 ) {printf("recv:%s len:%d\n", buf, len);}memset(buf, 0, MAX_PAYLOAD);}free(nlh);close(sock_fd);return 0;
}
它创建了一个 Netlink Socket,并使用 sendto 函数向内核发送消息。而且还使用 recvfrom 函数从内核接收消息。
在主函数中,我们使用 socket 函数创建了一个 AF_NETLINK 类型的套接字,并将其绑定到当前进程的 PID 上。然后,我们准备发送消息,定义了一个 nlmsghdr 结构体并将其初始化。将消息数据写入 nlmsghdr 结构体中,然后使用 sendto 函数向内核发送消息。
接下来,我们使用 recvfrom 函数从内核接收消息。该函数从套接字中读取一个 nlmsghdr 结构体,然后从 nlmsghdr 结构体中提取出消息数据。最后,将消息数据存储到指定的缓冲区中。
在循环中,我们读取用户输入的消息,并将其发送到内核。如果用户输入 quit,则退出循环。
三、代码测试结果
加载内核模块驱动,然后运行应用层程序:
# dmesg -w
[15492.193242] receive message (pid:5561):abcd
[15495.152386] receive message (pid:5561):sddffg
[15496.999417] receive message (pid:5561):qweqweqweq
[15499.649971] receive message (pid:5561):asdasdasd
[15505.720225] receive message (pid:5561):zsdqweq
# ./a.out
Enter message: abcd
recv:abcd len:4
Enter message: sddffg
recv:sddffg len:6
Enter message: qweqweqweq
recv:qweqweqweq len:10
Enter message: asdasdasd
recv:asdasdasd len:9
Enter message: zsdqweq
recv:zsdqweq len:7
Enter message: quit