2019 年 11 月我写了 ethtool 的工作原理 这篇博客,描述了 ethtool 使用 ioctl 获取信息的方式。谁曾想在 2019 年 12 月的时候内核已经开始支持 ethtool netlink 框架。
相关的 commit 如下:
commit 2b4a8990b7df55875745a80a609a1ceaaf51f322
Author: Michal Kubecek
Date: Fri Dec 27 15:55:18 2019 +0100
ethtool: introduce ethtool netlink interface
Basic genetlink and init infrastructure for the netlink interface, register
genetlink family "ethtool". Add CONFIG_ETHTOOL_NETLINK Kconfig option to
make the build optional. Add initial overall interface description into
Documentation/networking/ethtool-netlink.rst, further patches will add more
detailed information.
Signed-off-by: Michal Kubecek
Reviewed-by: Florian Fainelli
Reviewed-by: Andrew Lunn
Signed-off-by: David S. Miller
我最近才从同事那里了解到这一变化,不由觉得自己看待问题少了些变化的角度。
在 使用 strace、tcpdump、nlmon、wireshark 探索 ethtool netlink 框架的原理 这篇博客中,我通过 strace 等几个相关的强力工具描述了 ethtool 命令使用 ethtool netlink 获取信息的原理,本文探讨下内核部分的实现原理。
在 debian11 系统中,我使用 ftrace 跟踪执行 ethtool 命令时 r8169 驱动中的 get_wol 调用,得到如下内核调用栈信息:
ethtool-8117 [000] ..... 17227.018504: rtl8169_get_wol <-wol_prepare_data
ethtool-8117 [000] ..... 17227.018534: <stack trace>
=> 0xffffffffc0c94083
=> rtl8169_get_wol
=> wol_prepare_data
=> ethnl_default_doit
=> genl_family_rcv_msg_doit
=> genl_rcv_msg
=> netlink_rcv_skb
=> genl_rcv
=> netlink_unicast
=> netlink_sendmsg
=> sock_sendmsg
=> __sys_sendto
=> __x64_sys_sendto
=> do_syscall_64
=> entry_SYSCALL_64_after_hwframe
调用层次图示:
我将参考上图,自上而下描述内核中的实现原理。
用户态发送给内核的消息:
Frame 52: 52 bytes on wire (416 bits), 52 bytes captured (416 bits)
Linux netlink (cooked header)
Link-layer address type: Netlink (824)
Family: Generic (0x0010)
Linux Generic Netlink protocol
Netlink message header (type: 0x0014)
Length: 36
Family ID: 0x14 (ethtool)
Flags: 0x0005
Sequence: 2
Port ID: 0
Command: 4
Family Version: 1
Reserved
Data (32 bytes)
Data: 14000500020000000000000004010000100001800b000200656e703173300000
[Length: 32]
内核返回给用户态的消息:
Frame 53: 432 bytes on wire (3456 bits), 432 bytes captured (3456 bits)
Linux netlink (cooked header)
Link-layer address type: Netlink (824)
Family: Generic (0x0010)
Linux Generic Netlink protocol
Netlink message header (type: 0x0014)
Length: 416
Family ID: 0x14 (ethtool)
Flags: 0x0000
Sequence: 2
Port ID: 45683
Command: 4
Family Version: 1
Reserved
Data (412 bytes)
Data: 140000000200000073b20000040100001800018008000100020000000b000200656e7031…
[Length: 412]
摘自 使用 strace、tcpdump、nlmon、wireshark 探索 ethtool netlink 框架的原理。
框架代码位于内核源码树 net/netlink/genetlink.c
文件中,此框架为每个 namespace 的 net 结构创建一个名为 genl_sock 的 struct sock
结构,并将实例化的 netlink sock 结构的 netlink_rcv 收包函数设置为 genl_rcv
。
当用户态通过 generic netlink socket 发送 netlink 消息时,内核中调用到 netlink_unicast 接口时会调用注册的 netlink_rcv 函数接收消息,在 generic netlink 框架中就对应 genl_rcv
函数。
同时 generic netlink 框架提供一套接口,允许用户注册、解除注册以struct genl_family
结构标识的 generic netlink 内部协议族。每个协议族通过 name 区分,内核会为每个协议族分配一个唯一的 family id。
上文用户态发送给内核的 netlink 消息中的 Family ID 为 0x14,0x14 就是 ethtool netlink 的协议号。
调用 netlink_rcv_skb 函数依次解析 netlink 报文的头部、校验关键字段合法性、调用 genl_rcv_msg 函数、判断是否需要回复 ack、需要回复则回复
根据 netlink headers 获取消息的 payload 并解析为一个 generic netlink 消息头,然后通过消息头中的 cmd 字段从内部协议族中获取到一个 struct genl_ops 实例。get_wol 消息对应如下实现:
{
.cmd = ETHTOOL_MSG_WOL_GET,
.flags = GENL_UNS_ADMIN_PERM,
.doit = ethnl_default_doit,
.start = ethnl_default_start,
.dumpit = ethnl_default_dumpit,
.done = ethnl_default_done,
.policy = ethnl_wol_get_policy,
.maxattr = ARRAY_SIZE(ethnl_wol_get_policy) - 1,
},
获取到 genl_ops 后,首先检查权限,权限检查通过后根据 netlink 头的 nlmsg_flags 配置,调用 generic netlink 的 dumpit 函数、doit 函数。
对于 get_wol 消息,流程转向 genl_family_rcv_msg_doit
。
genl_ops 中的 doit 函数调用在 ethtool netlink genl_ops 中均指向 ethnl_default_doit
,此函数标志流程进入下一个层次。
ethnl_default_requests
表,得到一个 struct ethnl_request_op
实例。get_wol 消息中对应 ethnl_wol_request_ops
实例genlmsg_reply
命令回复消息ethnl_wol_request_ops
的 prepare_data 函数为 wol_prepare_data
,此函数将流程推向网络设备驱动 ethtool_ops
层。
此函数封装对网络设备驱动 ethtool_ops 实例方法的调用过程。
.
├── bitset.c
├── bitset.h
├── cabletest.c
├── channels.c
├── coalesce.c
├── common.c
├── common.h
├── debug.c
├── eee.c
├── features.c
├── ioctl.c
├── linkinfo.c
├── linkmodes.c
├── linkstate.c
├── Makefile
├── netlink.c
├── netlink.h
├── pause.c
├── privflags.c
├── rings.c
├── strset.c
├── tsinfo.c
├── tunnels.c
└── wol.c
netlink.c 为框架内容,ioctl.c 为 ioctl 方式实现,其它的每种类型 ethtool cmd 命令的解析与执行单独创建一个源文件实现。
ethtool netlink 框架跨越了如下几个抽象层次:
ethtool netlink 消息有如下几个组成部分:
消息的不同组成部分在不同的层次中被使用,解析消息需要两层,适配 ethtool_ops 需要一层,这些层次都封装了一种类型功能的变化,有良好的扩展能力。
https://www.kernel.org/doc/html/latest/networking/ethtool-netlink.html
https://lwn.net/Articles/783633/
https://blog.csdn.net/Longyu_wlz/article/details/126216625