Netlink

简介: Netlink

netlink

netlink是用户空间和内核空间通信的一种机制,是一种特殊的 socket,它是 Linux 所特有的。

由于传送的消息是暂存在socket接收缓存中,并不被接收者立即处理,所以netlink是一种异步通信机制。

而系统调用和ioctl 则是同步通信机制。

在一些网关产品中,使用netfilter把数据包hook住,然后通过netlink传到用户态,用户态的app对这些数据包识别,或做控制,或做安全扫描,然后通过再传到内核态发送出去。

netlink允许内核主动发消息给用户态。

用户空间进程可以通过标准socket API来实现消息的发送、接收,在Linux中,有很多用户空间和内核空间的交互都是通过Netlink机制完成的,比如:路由管理,ss,iptable

netlink套接字创建

fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETLINKTEST);

netlink地址

netlink中表示地址的数据结构是struct sockaddr_nl,定义如下:

struct sockaddr_nl {
        __kernel_sa_family_t    nl_family;      /* AF_NETLINK   */
        unsigned short  nl_pad;         /* zero         */
        __u32           nl_pid;         /* port ID      */
        __u32           nl_groups;      /* multicast groups mask */
};

struct sockaddr_nl中包含四项信息:

1. nl_family:地址类型,固定为AF_NETLINK。

2. nl_pad:这是一个填充字段,不用管它。

3. nl_pid:这是最主要的字段,表示通信地址,你可以随便设置源端nl_pid,就像普通socket中可以随便设置源端口号一样,目的端的nl_pid就是你想通信的那个netlink socket的nl_pid。为了防止nl_pid冲突,最简单的做法是将进程的pid作为nl_pid,内核的nl_pid固定为0。

4. nl_groups:netlink具有组通信功能,你可以向一个netlink组发送数据,这样属于这个netlink组的所有socket都可以接收到消息。

netlink报文

netlink socket中报文也由两部分构成 netlink header + payload,但是你需要自己处理netlink header,但是不用担心 netlink header也不算复杂,结构如下:

struct nlmsghdr {
        // 这是报文长度: netlink header + payload
        __u32           nlmsg_len;      /* Length of message including header */
        // 消息类型
        __u16           nlmsg_type;     /* Message content */
        // 一些标志
        __u16           nlmsg_flags;    /* Additional flags */
        // 当前报文的序号
        __u32           nlmsg_seq;      /* Sequence number */
        // 源端 port id
        __u32           nlmsg_pid;      /* Sending process port ID */
};

这里需要说明的是nlmsg_type,netlink消息可以定义成不同的类型,其中编号小于0x10的是预定义类型,有特殊用途,从x010开始供各个模块使用。

每个模块都可以定义不同类型的消息,比如0x10表示创建table,0x11表示创建chain,0x12表示创建rule,等等。

内核空间创建netlink套接字

struct netlink_kernel_cfg cfg = {
        .input  = nltest_rcv,
    };
    netlink_kernel_create(&init_net, NETLINK_NETLINKTEST, &cfg);

这个函数需要三个参数:

1. net:这是命令空间,直接使用init_net就可以了。

2. unit:这是模块编号,用来区分使用netlink的各个模块。

3. cfg:这个参数保存了这个模块的处理函数,每个模块可以定义自己的处理函数,struct netlink_kernel_cfg结构如下:

struct netlink_kernel_cfg {
        unsigned int    groups;
        unsigned int    flags;
        void            (*input)(struct sk_buff *skb);
        struct mutex    *cb_mutex;
        void            (*bind)(int group);
        bool            (*compare)(struct net *net, struct sock *sk);
}

每个模块都向netlink注册了一个socket,内核接收到netlink消息后,会根据用户程序调用socket(2)时设置的第三个参数查找socket,然后调用相应的input()函数,由input()函数处理接收到的netlink消息。

helloworld(原始套接字)

server端

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
#include <net/netlink.h>
#include <net/net_namespace.h>
MODULE_AUTHOR("test");
MODULE_LICENSE("GPL");
#define NETLINK_NETLINKTEST 22
static struct sock *nltest_sock;
// make -C  /lib/modules/`uname -r`/build M=$PWD'
static int nltest_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
{
    char    *data;
    data = nlmsg_data(nlh);
    printk(KERN_INFO"Receive msg from userspace: %s\n", data);
    return 0;
}
static void nltest_rcv(struct sk_buff *skb)
{
    netlink_rcv_skb(skb, &nltest_rcv_msg);
}
static int __init netlinktest_init(void)
{
    struct netlink_kernel_cfg cfg = {
        .input  = nltest_rcv,
    };
    nltest_sock = netlink_kernel_create(&init_net, NETLINK_NETLINKTEST, &cfg);
    if (!nltest_sock)
        return -ENOMEM;
    printk(KERN_INFO"Enter netlink test module.\n");
    return 0;
}
static void __exit netlinktest_exit(void)
{
    printk(KERN_INFO"Leave netlink test module.\n");
    netlink_kernel_release(nltest_sock);
}
module_init(netlinktest_init);
module_exit(netlinktest_exit);

编译和运行:

ifneq ($(KERNELRELEASE),)
        obj-m += nl_server1.o
else
        KERNELDIR ?= /lib/modules/$(shell uname -r)/build
all:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) clean
endif
make -C  /lib/modules/`uname -r`/build M=$PWD''
sudo insmod nl_server1.ko

client端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <malloc.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#define NETLINK_NETLINKTEST     22
#define NETLINK_TEST_ECHO   0x10
int main(int argc, char *argv[])
{
    int fd;
    int res;
    int len;
    struct nlmsghdr *nlh = NULL;
    struct sockaddr_nl  saddr, daddr;
    char    *msg = "hello world";
    len = strlen(msg);
    fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETLINKTEST);
    if (!fd) {
        printf("socket() error.\n");
        return -1;
    }
    memset(&saddr, 0, sizeof(saddr));
    saddr.nl_family = AF_NETLINK;
    saddr.nl_pid = getpid();
    memset(&daddr, 0, sizeof(daddr));
    daddr.nl_family = AF_NETLINK;
    daddr.nl_pid = 0;
    res = bind(fd, (const struct sockaddr *)&saddr, sizeof(saddr));
    if (res == -1) {
        printf("bind() error.\n");
        goto error;
    }
    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(len + 1));
    if (!nlh) {
        printf("malloc() error.\n");
        goto error;
    }
    nlh->nlmsg_type = NETLINK_TEST_ECHO;
    nlh->nlmsg_flags = NLM_F_REQUEST;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = getpid();
    strcpy(NLMSG_DATA(nlh), msg);
    nlh->nlmsg_len = NLMSG_LENGTH(len + 1);
    res = sendto(fd, nlh, NLMSG_SPACE(len + 1), 0, (struct sockaddr *)&daddr, sizeof(daddr));
    if (res == -1)
        printf("send() error.\n");
error:
    free(nlh);
    close(fd);
    return res;
}

编译和运行:

gcc nl_client1.c -o nl_client
 ./nl_client

通过dmesg可以看到结果:

[36371.489892] Enter netlink test module.
[36393.360358] Receive msg from userspace: hello world

libnl

libnl封装了netlink编程接口,使得netlink编程更方便。

为了避免冗余链接,libnl按功能分成4个子库:libnl,libnl-route,libnl-genl,libnl-nf

image.png

库的具体使用参考

https://www.infradead.org/~tgr/libnl/

源码的tests下面有一些有趣的例子。

相关文章
|
存储 Kubernetes 负载均衡
Kubeadm 初始化高可用 kubernetes v1.17.2集群 | 学习笔记
快速学习 Kubeadm 初始化高可用 kubernetes v1.17.2集群
Kubeadm 初始化高可用 kubernetes v1.17.2集群 | 学习笔记
|
6月前
|
IDE 自动驾驶 Linux
深度解析 CAN 总线:从底层物理层到 SocketCAN 编程实战
CAN总线是工业通信的关键技术,以其高可靠性和实时性广泛应用于自动驾驶、轨道交通等领域。其核心技术包括差分信号传输(物理层)和非破坏性逐位仲裁机制(数据链路层),确保在极端环境下稳定工作。CAN协议支持标准帧(11位ID)和扩展帧(29位ID),并通过严密的错误检测(5种机制)和节点健康管理(TEC/REC计数器)实现自我修复。进阶的CAN FD技术提升了数据传输能力(64字节负载,5Mbps速率)。Linux环境下可通过SocketCAN实现CAN通信模拟。
1576 8
|
11月前
|
缓存 监控 Ubuntu
Ubuntu操作系统下清除系统缓存与无用文件的方法
通过上述步骤断行综合性地对Ubuntu进行优化与整洁可显著改善其性能表现及响应速度。然而,请注意在执行某些操作前确保充分了解其潜在影响;例如,在移除旧内核之前确认新内核稳定运行无问题;而对于关键配置更改则需确保备份好相关设置以便恢复原状态。
2647 0
|
存储 人工智能 自然语言处理
ACE++:输入想法就能完成图像创作和编辑!阿里通义推出新版自然语言驱动的图像生成与编辑工具
ACE++ 是阿里巴巴通义实验室推出的升级版图像生成与编辑工具,支持多种任务,如高质量人物肖像生成、主题一致性保持和局部图像编辑。
1204 8
|
网络安全 数据安全/隐私保护 C++
VS Code 的SSH连接不成功问题分析与解决
VS Code 的SSH连接不成功问题分析与解决
|
JavaScript
el-table 使用了 修改行使用了v-model双向绑定 如何记录修改前的数据
el-table 使用了 修改行使用了v-model双向绑定 如何记录修改前的数据
544 2
|
Unix Linux 开发者
在Linux中,什么是GPL、GNU,自由由软件?
在Linux中,什么是GPL、GNU,自由由软件?
|
人工智能 搜索推荐 机器人
ai教育
【8月更文挑战第4天】ai教育
913 0
|
存储 网络协议 网络架构
|
NoSQL Linux 开发工具
【core analyzer】core analyzer的介绍和安装详情
【core analyzer】core analyzer的介绍和安装详情
781 5

热门文章

最新文章