netlink 通信(一):实战源码
Netlink 作为一种用于内核与用户空间之间通信的机制,适用于传递控制信息和数据。
下边是一个完整的示例代码。
1. 内核态源码及脚本
1. 内核源码文件 kernetlink.c 内容如下:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>
#define MY_MOD_NETLINK_TOKEN 27
#define MY_MOD_MSG_LEN 64
// 自定义数据结构体
typedef struct _msg_info
{
uint16_t type;
char msg[MY_MOD_MSG_LEN];
} msg_info;
struct sock *nlsk = NULL;
static void my_mod_input(struct sk_buff *__skb)
{
struct nlmsghdr *nlh = NULL;
uint32_t pid = 0, seq = 0;
struct sk_buff *skb = NULL;
unsigned char *receive_data = NULL;
msg_info *pmsg = NULL;
skb = skb_get(__skb);
if (!skb) {
pr_debug("get skb failed\n");
return;
}
if (skb->len < NLMSG_SPACE(0)) {
pr_debug("my_mod_input msg_space < 0\n");
return;
}
nlh = nlmsg_hdr(skb);
pid = nlh->nlmsg_pid;
seq = nlh->nlmsg_seq;
receive_data = (char *)NLMSG_DATA(nlh);
pmsg = (msg_info*)receive_data;
printk(KERN_INFO "Recv type:%d, msg:%s\n", pmsg->type, pmsg->msg);
/*
* 做一些其他工作
*/
kfree_skb(skb);
}
struct netlink_kernel_cfg cfg = {
.groups = 0,
.input = my_mod_input,
};
static int my_mod_init(void)
{
printk(KERN_INFO "my_mod_init\n");
nlsk = (struct sock *)netlink_kernel_create(&init_net, MY_MOD_NETLINK_TOKEN, &cfg);
if (nlsk == NULL) {
printk(KERN_ALERT "netlink_kernel_create error !\n");
return -1;
}
return 0;
}
static void my_mod_exit(void)
{
if (NULL != nlsk) {
netlink_kernel_release(nlsk);
nlsk = NULL;
}
printk(KERN_INFO "my_mod_exit!\n");
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Intel-Momentum");
MODULE_DESCRIPTION("My module for testing.");
module_init(my_mod_init);
module_exit(my_mod_exit);
2. 编译脚本 Makefile 内容如下:
obj-m := kernetlink.o
PWD:=$(shell pwd)
KDIR:=/lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
3. 编译及模块加载与卸载
# 编译
make
# 查看模块信息
modinfo kernetlink.ko
# 模块插入
sudo insmod kernetlink.ko
# 查看内核是否成功加载模块
lsmod | grep kernetlink
# 查看内核支持
dmesg -T | tail -n 10
# 卸载模块
rmmod kernetlink
2. 用户态代码及脚本
1. 用户态源码文件 usertool.c 内容如下:
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <linux/netlink.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define MY_MOD_NETLINK_TOKEN 27
#define MY_MOD_MSG_LEN 64
#define MY_PROTO_NETLINK_ADD 0x1001
#define MY_PROTO_NETLINK_DEL 0x1002
// 自定义数据结构体
typedef struct _msg_info
{
uint16_t type;
char msg[MY_MOD_MSG_LEN];
} msg_info;
int send_msg_to_kernel(const char* msg, const int msglen)
{
int sockfd = -1, on = 0, ret = 0;;
struct sockaddr_nl saddr;
struct sockaddr_nl daddr;
struct nlmsghdr *message = NULL;
message = (struct nlmsghdr*)malloc(NLMSG_SPACE(msglen));
if (!message) {
perror("malloc error:");
return -1;
}
memset(message, 0x0, NLMSG_SPACE(msglen));
message->nlmsg_len = NLMSG_SPACE(msglen);
message->nlmsg_pid = getpid();
memcpy(NLMSG_DATA(message), msg, msglen);
sockfd = socket(PF_NETLINK, SOCK_DGRAM, MY_MOD_NETLINK_TOKEN);
if (-1 == sockfd) {
perror("create socket error:");
free(message);
return -1;
}
if ((setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0) {
perror("setsockopt error:");
close(sockfd);
free(message);
return -1;
}
memset(&saddr, 0x0, sizeof(saddr));
saddr.nl_family = AF_NETLINK;
saddr.nl_pid = getpid();
saddr.nl_groups = 0;
if (0 != bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr))) {
perror("bind() error:");
close(sockfd);
free(message);
return -1;
}
memset(&daddr, 0x0, sizeof(daddr));
daddr.nl_family = AF_NETLINK;
daddr.nl_pid = 0;
daddr.nl_groups = 0;
ret = sendto(sockfd, message, message->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
if (ret < 0) {
perror("sendto error:");
close(sockfd);
free(message);
return -1;
}
close(sockfd);
free(message);
return ret;
}
int user_netlink_send_msg(const char* msg)
{
if (NULL == msg) {
printf("user_netlink_send_msg parameter invalid.\n");
return -1;
}
msg_info message;
memset(&message, 0x0, sizeof(message));
message.type = MY_PROTO_NETLINK_ADD;
memcpy(message.msg, msg, strlen(msg));
return send_msg_to_kernel((char*)&message, sizeof(msg_info));
}
int main()
{
int ret = user_netlink_send_msg("Hello kernel.");
printf("user_netlink_send_msg ret:%d\n", ret);
return 0;
}
2. 编译脚本 Makefile 如下:
CC=gcc
CFLAGS += -Wall -g
usertool: usertool.c
$(CC) $(CFLAGS) $^ -o $@
clean:
rm -f usertool
3. 编译及运行
# 编译
make
# 运行(先插入内核模块,否则会报错)
./usertool
# 查看与内核模块的通信情况
dmesg -T | tail -n 10
代码说明
- 内核模块:
- 使用 netlink_kernel_create 创建 Netlink 套接字。
- 定义回调函数 my_mod_input 处理用户态发送的消息。
- 用户态程序:
- 创建 Netlink 套接字并与内核通信。
- 使用 sendto 发送消息。
- Netlink 家族:
- 使用自定义家族 (MY_MOD_NETLINK_TOKEN 27),确保内核和用户态使用相同的家族。