本贴记录Netlink 协议和 uevent 机制开发进度以及当前正在解决的问题
当前进度
已经手搓完成了 Netlink 协议和 Uvenet 机制的大部分代码,初步进入测试阶段,正在通过调试尝试发现和解决实际运行过程中的问题
当前正在解决的问题
-
8.10: socket_handle() todo!
socket trait 中基于smoltcp 的 socket 结构体构造了一个 GlobalSocketHandle 类型的变量 handle 作为数据成员。在 handle 初始化的时候需要该 socket 结构体对象传入;而由于 NetlinkSock 不包含 smoltcp 的 socket 结构体,无法直接通过添加 handle 字段并初始化的方式来实现该方法,从而引发了 panic。 -
8.9: (已解决)metadata() 和 posix_item() 会被调用并由于未实现而发生panic
在 Netlinksock 中添加 metadata 和 posix_item 字段并实现这两个函数即可。 -
8.8:(已解决)bind 系统调用 和 netlink_bind 函数的调用问题
SYS_BIND 的具体执行函数没法执行 NetlinkSock 实现的 bind 方法,抛出 ENOSYS。- 解决方法:SocketInode 的 SYS_BIND中最终调用 bind 函数的 socket 类型是根据传给它的 Socket_type 决定的,在 new_socket() 中模式匹配部分指定对应的 new 函数即可。NetlinkSocket 一般使用的socket_type 为 RAW 和 DGRAM(Datagram)
-
8.4:(已解决)用户空间传递了错误的参数给 bind 系统调用
用户空间使用 nix 库创建套接字并尝试 bind 的时候,内核中通过打日志发现到SYS_BIND的时候,addr.family 不是预期的16(Netlink 的AddrFamily 为16)而是2(INet = 2)
- 解决方法:NetlinkAddr 才是和内核的 SockAddr 结构体相兼容的,而不是 nix 库的SockAddr。用户空间传参的时候转换一下即可。
- 解决方法:NetlinkAddr 才是和内核的 SockAddr 结构体相兼容的,而不是 nix 库的SockAddr。用户空间传参的时候转换一下即可。
-
7.30:(已解决)NetlinkSocket 和 Socket 的类型转换
由于设计的原因,当前有一个 NetlinkSocket trait 和一个NetlinkSock 的 struct,分别用于实现Linux 6.1.9 中的netlink_socket 和 sock。bind 系统调用中使用的套接字接口是 Socket,但是该trait 更像是一个函数操作集而不同于 Linux 6.1.9 中的Sock结构体,因此当初实现的时候选择构造了一个实现了 Socket trait 的 NetlinkSocket trait。如今由于netlink_bind 函数接受的是 NetlinkSocket ,而 Socket 似乎是无法转换为 NetlinkSocket 的。
Netlink 协议和 uevent 机制的作用原理与设计实现解析
Netlink 协议
在用户空间使用 netlink 套接字和内核通信
在用户空间使用 netlink 套接字和内核通信,和传统的套接字有所区别。相同的都是需要首先使用 socket 系统调用,创建用户空间套接字,不同的是内核也要创建对应的内核套接字,两者通过 nl_table 链表进行绑定; nl_table 是 netlink 机制的核心数据结构,围绕此结构的内核活动有:
- 用户空间应用程序使用 socket 系统调用创建用户空间的套接字,然后在 bind 系统调用时,内核 netlink_bind 函数将调用 netlink_insert (sk, portid) 将此用户态套接字和应用程序的进程 pid 插入 nl_table,这里参数 portid 就是进程 pid;
- 创建内核套接字时,调用 netlink_insert(sk, 0) 将此用户态套接字插入 nl_table(因为是内核套接字,这里 portid 是0);
- 用户空间向内核发送 netlink 消息时,调用 netlink_lookup 函数,根据协议簇和 portid 在nl_table 快速查找对应的内核套接字对象;
- 当内核空间向用户空间发送 netlink 消息时,调用调用 netlink_lookup 函数,根据协议簇和 portid 在 nl_table 快速查找对应的用户套接字对象.
uevent 机制
uevent
机制是 Linux 内核用于通知用户空间有关设备事件(如设备添加、移除或更改)的机制。它是 Linux 内核设备模型的一部分,通过 uevent
,内核可以将设备事件发送到用户空间,用户空间的进程可以监听这些事件并作出相应的处理。
工作原理
- 设备事件生成:
- 当内核中的设备状态发生变化(如设备插入、移除或属性变化)时,内核会生成一个
uevent
事件。
- 通过 netlink 发送事件:
- 内核通过
netlink
套接字将uevent
事件发送到用户空间。netlink
是一种用于内核和用户空间进程之间通信的机制。
- 用户空间处理:
- 用户空间的进程(如
udevd
或其他监听uevent
事件的进程)接收到uevent
事件后,可以根据事件类型和内容执行相应的操作,如加载驱动程序、创建设备节点或更新设备配置。
关键组件
- sysfs:
sysfs
是一个虚拟文件系统,提供了内核对象的视图。设备的属性和状态可以通过sysfs
文件系统进行访问和修改。
- netlink:
netlink
是一种 IPC(进程间通信)机制,允许内核和用户空间进程之间进行双向通信。uevent
事件通过netlink
套接字发送到用户空间。
- uevent 文件:
- 每个设备在
sysfs
中都有一个uevent
文件,当写入该文件时,可以触发uevent
事件。
- 每个设备在
目前设计实现的具体方案
trait 和 struct
trait
NetlinkSocketStruct
NetlinkSockStruct
NetlinkTable
Linux 6.1.9 中 hash 字段使用的是自己实现的 rhashtable,由于自己实现 rhashtable 以及相关接口比较繁杂,暂时使用了功能近似的 hashmap 替代。- NL_TABLE: RwLock<Vec>
Struct
SkBuff
SkBuff 第一版实现,原本使用的是 smoltcp 库中的 PacketBuffer<'a> 封装,但由于生命周期标注的存在,会影响到上层很多其他结构的生命周期,并且由于缺少很多必要的 trait 和 属性,最终参照 aya_ebpf 的 __sk_buff 实现了现有的 SkBuff 。struct
UeventSock
function
enum 和 const
备注
相关讨论
- inet没有实现网络协议族的操作集。https://code.dragonos.org.cn/xref/linux-6.1.9/include/net/sock.h#1230
对比Linux 6.1.9,未实现的部分功能
struct
Net:网络命名空间
未来拓展
- netlink 协议族有其他的类别,例如 route 和 generic netlink
- classic netlink 还是 generic netlink?
- generic netlink 使用 classic netlink 的API。generic netlink 是 netlink 的一种扩展,它支持1023个子协议号,弥补了 netlink 协议类型较少的缺陷。generic netlink 基于 netlink,但是在内核中,generic netlink 的接口与 netlink 并不相同。generic netlink 的API是通过netlink 的API来实现的,因此 generic netlink 使用 classic netlink 的API。
- 经典 Netlink 和通用 Netlink 之间的主要区别是子系统标识符的动态分配和自省的可用性。 在理论上,协议没有显著的区别,然而,在实践中,经典 Netlink 实验了通用 Netlink 中被放弃的概念(实际上,它们通常只在一个单一子系统的一小角落中使用)。