Linux 内核源码分析---内核 Netlink 套接字

linux 内核一直存在的一个严重问题就是内核态和用户态的交互的问题,对于这个问题内核大佬们一直在研究各种方法,想让内核和用户态交互能够安全高效的进行。如系统调用,proc,sysfs 等内存文件系统,但是这些方式一般都比较简单,只能在用户空间轮询访问内核的变化,内核的变化无法主动的推送出来

Netlink 套接字接口最初是 Linux 内核 2.2 引入的,名为AF_NETLINK套接字,提供一种更为灵活的用户空间进程与内核间通信方法,替代IOCTL。它运行模型非常简单,只需要使用套接字 API 打开并注册一个 Netlink 套接字,它就会处理与内核 Netlink 套接字的双向通信。

目前 netlink 的这种机制被广泛使用在各种场景中,在 Linux 内核中使用 netlink 进行应用与内核通信的应用很多;包括:路由 daemon(NETLINK_ROUTE),用户态 socket 协议(NETLINK_USERSOCK),防火墙(NETLINK_FIREWALL),netfilter 子系统(NETLINK_NETFILTER),内核事件向用户态通知(NETLINK_KOBJECT_UEVENT)等。

优势:
(1)使用 netlink 套接字时不需要轮询,用空间应用程序打开套接字,再调用 recvmsg(),如果没有来自内核的消息,就进入阻塞状态;
(2)内核可以主动向用户空间发送异步消息,而不需要用户空间来触发;
(3)netlink 套接字支持组播传输。
(4)要在用户空间中创建 netlink 套接字,可使用系统调用 socket(),netlink 套接字就可以是 SOCK_RAW 套接字,也可以是 SOCK_DGRAM 套接字。

为什么选择netlink套接字?

  • netlink 套接字支持多播,且一个进程可以将消息多播到netlink地址组。
  • netlink 提供BSD套接字样式的API。
  • netlink 套接字是异步的,并提供套接字的消息排队规则。
  • 要支持任何新的特征,只需要实现对应的协议类型。

Netlink 协议是一种在 RFC 3549 中定义的进程间通信机制,为用户空间和内核以及内核的有些部分之间提供双向通信信道,是对标准套接字实现的扩展。Netlink 协议实现基本是位于net/netlink 下,包含4个文件:
在这里插入图片描述

事实上最常用的是 af_netlink 模块,它提供 netink 内核套接字 APl,而 genetlink 模块提供了新的通用 netlink API。监视接口模块 diag(diag.c)提供的API用于读写有关的 netlink 套接字信息。
从理论原则来讲,netlink 套接字可用于在用户空间进程间通信(包括发送组播消息),但通常不这样做,而且这也不是开发 netlink 套接字初衷。UNIX 域套接字提供用于进程间通信(IPC) 的 API,被广泛用于两个用户空间进行间的通信。

一般来说用户空间和内核空间的通信方式有三种:/procioctlNetlink。而前两种都是单向的,而 Netlink 可以实现双工通信

内核和用户空间中创建 Netlink 套接字
在这里插入图片描述

netlink 数据结构
在这里插入图片描述

1、创建 netlink套接字源码、注册操作、数据包发送操作

在内核网络栈中,可创建 netlink 套接字如下:
在这里插入图片描述

使用 netlink 内核套接字,首先需要注册它:
在这里插入图片描述

数据包的实际发送工作是由通用 Netlink 方法nlmsg_notify()完成:
在这里插入图片描述

Netlink消息必须采用特定的格式,此格式是在RFC 3549中进行规定的。Netlink消息 的开头是长度固定的 Netlink报头,其后是有效载荷。
netlink 数据包的开头都是由结构 nlmsghdr 表示 netlink 消息报头,整个长度为16字节,包括5个字段如下
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
路由选择中添加/删除路由选择条目:
添加ip route add 参数
删除ip route del ip地址
使用 iproute2 命令ip来监视网络事件:ip monitor route

创建和发送通用 Netlink 消息:
通用 Netlink 消息的开头是一个 Netlink 报头,接下来是通用 Netlink 消息报头以及可选的用户特定报头,然后是可选的有效载荷,内核通用 Netlink 消息 genlmsghdr 结构源码如下
在这里插入图片描述
在这里插入图片描述

套接字监视接口
netlink 套接字sock_diag提供一个基于 netlink 子系统,可用于获取有关套接字的信息,在内核中添加它是指在Linux用户空间中支持检查点/恢复功能(CRIU)。要支持这项功能,还需要有关套接字的其他数据。创建netlink内核套接字sock_diag,具体源码如下
在这里插入图片描述

/****************************************
* Author: vico
* Date: 2021-06-26
* Filename: netlink_test.c
* Descript: netlink of kernel
* Kernel: 3.10.0-327.22.2.el7.x86_64
* Warning:
******************************************/

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>

#define NETLINK_TEST     30
#define MSG_LEN            125
#define USER_PORT        100

MODULE_LICENSE("GPL");
MODULE_AUTHOR("vico");
MODULE_DESCRIPTION("netlink protocol example");

struct sock *nlsk = NULL;
extern struct net init_net;

int send_usrmsg(char *pbuf, uint16_t len)
{
    struct sk_buff *nl_skb;
    struct nlmsghdr *nlh;

    int ret;

    /* 创建sk_buff 空间 */
    nl_skb = nlmsg_new(len, GFP_ATOMIC);
    if(!nl_skb)
    {
        printk("\nError:netlink alloc failure.\n\n");
        return -1;
    }

    /* 设置netlink消息头部 */
    nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
    if(nlh == NULL)
    {
        printk("\nError:nlmsg_put failaure. \n\n");
        nlmsg_free(nl_skb);
        return -1;
    }

    /* 拷贝数据发送 */
    memcpy(nlmsg_data(nlh), pbuf, len);
    ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);

    return ret;
}

static void netlink_rcv_msg(struct sk_buff *skb)
{
    struct nlmsghdr *nlh = NULL;
    char *umsg = NULL;
    char *kmsg = "Hello vico users program.";

    if(skb->len >= nlmsg_total_size(0))
    {
        nlh = nlmsg_hdr(skb);
        umsg = NLMSG_DATA(nlh);
        if(umsg)
        {
            printk("kernel recv from user: %s\n", umsg);
            send_usrmsg(kmsg, strlen(kmsg));
        }
    }
}

struct netlink_kernel_cfg cfg = {
        .input  = netlink_rcv_msg, /* set recv callback */
};

int test_netlink_init(void)
{
    /* create netlink socket */
    nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
    if(nlsk == NULL)
    {
        printk("\nError:netlink_kernel_create error !\n");
        return -1;
    }
    printk("\ntest_netlink_init\n");

    return 0;
}

void test_netlink_exit(void)
{
    if (nlsk){
        netlink_kernel_release(nlsk); /* release ..*/
        nlsk = NULL;
    }
    printk("test_netlink_exit!\n");
}

module_init(test_netlink_init);
module_exit(test_netlink_exit);
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>

#define NETLINK_TEST    30
#define MSG_LEN         125
#define MAX_PLOAD       125

typedef struct _user_msg_info
{
    struct nlmsghdr hdr;
    char  msg[MSG_LEN];
} user_msg_info;

int main(int argc, char **argv)
{
    int skfd;
    int ret;
    user_msg_info u_info;
    socklen_t len;
    struct nlmsghdr *nlh = NULL;
    struct sockaddr_nl saddr, daddr;
    char *umsg = "Hello Netlink protocol.";

    /* 创建NETLINK socket */
    skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
    if(skfd == -1)
    {
        perror("\nError:Create socket error.\n");
        return -1;
    }

    memset(&saddr, 0, sizeof(saddr));
    saddr.nl_family = AF_NETLINK; //AF_NETLINK
    saddr.nl_pid = 100;  //端口号(port ID)
    saddr.nl_groups = 0;
    if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0)
    {
        perror("\nError:bind() error.\n");
        close(skfd);
        return -1;
    }

    memset(&daddr, 0, sizeof(daddr));
    daddr.nl_family = AF_NETLINK;
    daddr.nl_pid = 0; // to kernel
    daddr.nl_groups = 0;

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
    memset(nlh, 0, sizeof(struct nlmsghdr));
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
    nlh->nlmsg_flags = 0;
    nlh->nlmsg_type = 0;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = saddr.nl_pid; //self port

    memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));
    ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
    if(!ret)
    {
        perror("\nError:sendto error.\n");
        close(skfd);
        exit(-1);
    }
    printf("\nApplication-->Send kernel:%s\n\n", umsg);

    memset(&u_info, 0, sizeof(u_info));
    len = sizeof(struct sockaddr_nl);
    ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
    if(!ret)
    {
        perror("\nError:recv form kernel error.\n");
        close(skfd);
        exit(-1);
    }

    printf("\nApplication-->From kernel:%s\n\n", u_info.msg);
    close(skfd);

    free((void *)nlh);
    return 0;
}

首先将编译出来的Netlink内核模块插入到系统当中(insmod netlink_test.ko):insmod netlink_test.ko

报错:insmod: ERROR: could not insert module netlink_test.ko: Operation not permitted
解决方案:http://t.csdnimg.cn/UhMSF

接着运行应用程序:./a.out

https://mp.weixin.qq.com/s/cJ8asff7eCVeFm2L4B3tLw
https://www.cnblogs.com/x_wukong/p/5920437.html
https://zhuanlan.zhihu.com/p/487961245
https://zhuanlan.zhihu.com/p/694289016

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飞大圣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值