netlink 通信(一):实战源码

233 阅读3分钟

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),确保内核和用户态使用相同的家族。