linux netlink

Linux 的 netlink 是一种内核与用户空间进程之间的通信机制,它允许用户空间程序接收来自内核的通知,或者向内核发送请求。netlink 是一种特殊的 socket 类型,它提供了比传统的系统调用(如 ioctl)更强大和灵活的接口,用于内核和用户空间之间的通信。

主要特点

  1. 双向通信netlink 支持从内核到用户空间的消息传递,也支持从用户空间到内核的消息传递。
  2. 异步通知:内核可以主动向用户空间发送消息,而不需要用户空间程序轮询。
  3. 类型化消息netlink 消息是类型化的,这允许内核和用户空间程序对消息进行明确的解析和处理。
  4. 多播:一个 netlink socket 可以配置为多播,这样多个用户空间程序可以监听来自内核的相同消息。

使用场景

  • 路由和网络配置netlink 被广泛用于路由和网络设备的配置和状态监控。
  • 防火墙和网络安全:通过 netlink,用户空间程序可以接收来自内核的网络安全事件,并作出相应处理。
  • 文件系统监控:某些文件系统(如 inotify)使用 netlink 来向用户空间报告文件系统的变化。
  • 内核模块和用户空间程序的交互netlink 提供了一种机制,允许内核模块和用户空间程序进行复杂的交互。

编程接口

在 Linux 用户空间中,netlink 编程通常涉及以下几个步骤:

  1. 创建 socket:使用 socket() 函数创建一个 AF_NETLINK 类型的 socket。
  2. 绑定 socket:使用 bind() 函数将 socket 绑定到一个特定的 netlink 协议族(也称为 netlink 组)。
  3. 发送和接收消息:使用 sendmsg() 和 recvmsg() 函数发送和接收消息。
  4. 关闭 socket:使用 close() 函数关闭 socket。

在内核中,netlink 的实现涉及注册 netlink 协议族、处理消息接收和发送等。

示例

由于 netlink 编程涉及复杂的内核和用户空间交互,这里不直接给出代码示例。但是,你可以通过查看 Linux 内核源代码中的 netlink 相关部分,以及用户空间工具(如 iproute2)的源代码,来了解 netlink 的具体使用方式。

$ cat nlkernel.c
/*
 *  Description : 内核netlink编程
 *  Date        :20180528
 *  Author      :mason
 *  Mail        : mrsonko@126.com
 *
 */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/netlink.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <net/ip.h>
#include "nlkernel.h"

 #include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kthread.h>

static struct task_struct *my_kthread;
static int gDstPid = 0;
static void netlink_sendto_userapp(unsigned int dst_pid);

static int my_kthread_fn(void *data)
{
        int count = 0;
    while (!kthread_should_stop()) {
        // 假设这里有某种“接收消息”的逻辑
        // 这里只是简单地打印消息
        printk(KERN_INFO "My kernel thread is running, dstPid:%d\n", gDstPid);


                if(gDstPid != 0){
                        netlink_sendto_userapp(gDstPid);
                }

        // 休眠一段时间
        set_current_state(TASK_INTERRUPTIBLE);
        schedule_timeout(HZ); // 休眠1秒
                count++;

    }

    return 0;
}

static int number = 0;
static struct sock *nlsock = NULL;  // netlink 套接字

// netlink消息回复,用于向应用层传递数据
static void netlink_sendto_userapp(unsigned int dst_pid) {
    struct sk_buff *skb = NULL;
    struct nlmsghdr *nlh = NULL;
    int datalen;
    int *pnum;
    datalen = NLMSG_SPACE(sizeof(int));

    skb = alloc_skb(datalen, GFP_KERNEL);
    if(!skb)
    {
        log("alloc skb error.\r\n");
        return ;
    }
    // 数据初始化
    nlh = nlmsg_put(skb, 0, 0, 0, sizeof(int), 0);
    nlh->nlmsg_pid = 0;
    nlh->nlmsg_type = NLKERNEL_GET;

    pnum = (int *)NLMSG_DATA(nlh);
    *pnum = number++;
    netlink_unicast(nlsock, skb, dst_pid, MSG_DONTWAIT);
    log("netlink send done \r\n");

    return;
}

/* netlink消息处理函数 */
static int netlink_kernel_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
    int *value = NULL;
    switch(nlh->nlmsg_type){
        // 设置
        case NLKERNEL_SET :
                        gDstPid = nlh->nlmsg_pid;
            log("kernel receive netlink set msg! dstPid:%d\r\n", gDstPid);
            value = (int *)NLMSG_DATA(nlh);
            number = *value;
            break;
        // 获取
        case NLKERNEL_GET :
                        gDstPid = nlh->nlmsg_pid;
            log("kernel receive netlink get msg! dstPid:%d\r\n", gDstPid);
            netlink_sendto_userapp(nlh->nlmsg_pid);
            break;
        default:
            log("unrecognized netlink message type : %u \r\n",nlh->nlmsg_type);
            break;
    }
    return 0;
}




static void netlink_kernel_rcv(struct sk_buff *skb)
{
    netlink_kernel_rcv_msg(skb, nlmsg_hdr(skb));
}




// 模块入口函数
static int __init nlkernel_init(void) {
    log("nlkernel init \r\n");
    my_kthread = kthread_run(my_kthread_fn, NULL, "my_kthread");
    if (IS_ERR(my_kthread)) {
        printk(KERN_ERR "Failed to create kernel thread\n");
        return PTR_ERR(my_kthread);
    }
    // 注册netlink协议
    struct netlink_kernel_cfg cfg = {
        .groups = 0,
        .input = netlink_kernel_rcv,
    };
        nlsock = netlink_kernel_create(&init_net, NETLINK_TEST_MODULE, &cfg);
    if (!nlsock) {
        log("netlink module init fail \r\n");
        return -1;
    }
    return 0;
}


// 模块退出函数
static void __exit nlkernel_exit(void) {
    kthread_stop(my_kthread);
    printk(KERN_INFO "My kernel thread has been stopped\n");
    // 注销netlink协议
        if(nlsock)
        {
        netlink_kernel_release(nlsock);
        nlsock = NULL;
        }
    log("nlkernel exit \r\n");
    return ;
}

module_init(nlkernel_init)
module_exit(nlkernel_exit)
MODULE_AUTHOR("mason");
MODULE_DESCRIPTION("netlink kernel test");
MODULE_LICENSE("GPL");


wmd@iZuf618w2ovmzml9l4vleyZ:~/work/code/demo$ cat nlkernel.h
#ifndef __NLKERNEL_H__
#define __NLKERNEL_H__
#define log(fmt, arg...)  printk(KERN_INFO"[bfd] %s:%d "fmt, __FUNCTION__, __LINE__, ##arg)

#ifndef NIPQUAD
#define NIPQUAD(addr) \
        ((unsigned char *)&addr)[0], \
        ((unsigned char *)&addr)[1], \
        ((unsigned char *)&addr)[2], \
        ((unsigned char *)&addr)[3]
#endif

#define NETLINK_TEST_MODULE         17      /* 定义 netlink 协议, */

typedef enum netlink_msg_type {             /* 定义 netlink 消息类型 */
    NLKERNEL_GET = NLMSG_MIN_TYPE +1,        /* value : 17 */
    NLKERNEL_SET,                            /* value : 18 */

    NLKERNEL_END,
}NETLINK_MSG_TYPE;

#endif
wmd@iZuf618w2ovmzml9l4vleyZ:~/work/code/demo$ cat Makefile
obj-m += nlkernel.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
wmd@iZuf618w2ovmzml9l4vleyZ:~/work/code/demo$ cat demo.c
/*
 *  Description : 应用层netlink编程
 *  Date        :20180529
 *  Author      :mason
 *  Mail        : mrsonko@126.com
 *
 */
#include <linux/netlink.h>
#include <linux/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include "demo.h"

void main() {
    int nlfd;
    int *value;
    int opt, arg;
    unsigned int len;
        struct iovec iov[1];
        struct msghdr msg;
        struct sockaddr_nl src, dst;
        struct nlmsghdr *nlh, *nlh1, *nlh2 = NULL;

    // 创建netlink套接字
    nlfd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_TEST_MODULE);
    if (nlfd == -1) {
        log("create netlink socket fail\n");
        return;
    }

    memset(&src, 0, sizeof(struct sockaddr_nl));
    memset(&dst, 0, sizeof(struct sockaddr_nl));
    memset(&msg, 0, sizeof(struct msghdr));

    // 设置本地地址
    src.nl_family = AF_NETLINK;
    src.nl_pid = getpid();
    src.nl_groups = 0;

    // 设置内核netlink地址
    dst.nl_family = AF_NETLINK;
    dst.nl_pid = 0;
    dst.nl_groups = 0;

    // 绑定本地地址
    bind(nlfd, (struct sockaddr*)&src, sizeof(struct sockaddr_nl));

    // 申请netlink消息头域
    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(int));

        arg = 1;
                memset(nlh, 0, NLMSG_SPACE(sizeof(int)));
                nlh->nlmsg_len = NLMSG_SPACE(sizeof(int));

                // 设置netlink 应用层的pid
                nlh->nlmsg_pid = getpid();

                // 设置消息类型
                nlh->nlmsg_type = NLKERNEL_SET;

                // 设置标志位
                nlh->nlmsg_flags = NLM_F_REQUEST;

                // 填充发送消息结构体
                iov[0].iov_base = (void *)nlh;
                iov[0].iov_len = nlh->nlmsg_len;
                value = (int *)NLMSG_DATA(nlh);
                *value = arg;
                msg.msg_name = (void *)&dst;
                msg.msg_namelen = sizeof(struct sockaddr_nl);
                msg.msg_iov = &iov[0];
                msg.msg_iovlen = 1;

                // 发送netlink 消息给内核
                sendmsg(nlfd, &msg, 0);
                log("send set init msg to kernel success \n\n");

while(1)
{

                memset(nlh, 0, NLMSG_SPACE(sizeof(int)));
                nlh->nlmsg_len = NLMSG_SPACE(sizeof(int));
                iov[0].iov_base = (void *)nlh;
                iov[0].iov_len = nlh->nlmsg_len;
                msg.msg_iov = &iov[0];
                msg.msg_iovlen = 1;

                // 接收netlink内核端消息
                len = recvmsg(nlfd, &msg, 0);
                value = (int *)NLMSG_DATA(nlh);
                log("kernel return : %d \r\n\n", *value);

}

    while(1) {
        log("input option :\n"

"1 : set \n"
            "2 : get \n"
            "default : quit\n");
        scanf("%d", &opt);
        switch (opt) {
            // set
            case 1 :
                log("input value to set:");
                scanf("%d", &arg);
                memset(nlh, 0, NLMSG_SPACE(sizeof(int)));
                nlh->nlmsg_len = NLMSG_SPACE(sizeof(int));

                // 设置netlink 应用层的pid
                nlh->nlmsg_pid = getpid();

                // 设置消息类型
                nlh->nlmsg_type = NLKERNEL_SET;

                // 设置标志位
                nlh->nlmsg_flags = NLM_F_REQUEST;

                // 填充发送消息结构体
                iov[0].iov_base = (void *)nlh;
                iov[0].iov_len = nlh->nlmsg_len;
                value = (int *)NLMSG_DATA(nlh);
                *value = arg;
                msg.msg_name = (void *)&dst;
                msg.msg_namelen = sizeof(struct sockaddr_nl);
                msg.msg_iov = &iov[0];
                msg.msg_iovlen = 1;

                // 发送netlink 消息给内核
                sendmsg(nlfd, &msg, 0);
                log("send set msg to kernel success \n\n");
                break;

            // get
            case 2 :
                memset(nlh, 0, NLMSG_SPACE(int));
                nlh->nlmsg_len = NLMSG_SPACE(int);
                nlh->nlmsg_pid = getpid();

                // 设置netlink消息类型
                nlh->nlmsg_type = NLKERNEL_GET;
                nlh->nlmsg_flags = NLM_F_REQUEST;
                iov[0].iov_base = (void *)nlh;
                iov[0].iov_len = nlh->nlmsg_len;
                msg.msg_name = (void *)&dst;
                msg.msg_namelen = sizeof(struct sockaddr_nl);
                msg.msg_iov = &iov[0];
                msg.msg_iovlen = 1;

                // 发送消息
                log("send get msg to kernel success \n");
                sendmsg(nlfd, &msg, 0);

                memset(nlh, 0, NLMSG_SPACE(sizeof(int)));
                nlh->nlmsg_len = NLMSG_SPACE(sizeof(int));
                iov[0].iov_base = (void *)nlh;
                iov[0].iov_len = nlh->nlmsg_len;
                msg.msg_iov = &iov[0];
                msg.msg_iovlen = 1;

                // 接收netlink内核端消息
                len = recvmsg(nlfd, &msg, 0);
                value = (int *)NLMSG_DATA(nlh);
                log("kernel return : %d \r\n\n", *value);

            break;
            default :
                goto end;
        }
    }

 end:
    close(nlfd);
    if (nlh)
        free(nlh);

    return ;
}
$ cat demo.h
#ifndef _NLCLIENT_H
#define _NLCLIENT_H
#define log(fmt,arg...) \
        printf("[nlclient] "fmt,##arg)

#define NETLINK_TEST_MODULE         17      /* 抓包 netlink 协议 */

typedef enum netlink_msg_type {
    NLKERNEL_GET = NLMSG_MIN_TYPE +1,        /* value : 17 */
    NLKERNEL_SET,                            /* value : 18 */

    NLKERNEL_END,
}NETLINK_MSG_TYPE;


#endif

结论

netlink 是 Linux 系统中一种强大的内核与用户空间通信机制,它提供了丰富的功能和灵活性,被广泛应用于网络、文件系统、安全等多个领域。通过学习和掌握 netlink 编程,你可以开发出更加高效和强大的系统级应用程序。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注