• ethtool netlink 框架原理浅析


    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 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    我最近才从同事那里了解到这一变化,不由觉得自己看待问题少了些变化的角度。

    基础知识

    使用 strace、tcpdump、nlmon、wireshark 探索 ethtool netlink 框架的原理 这篇博客中,我通过 strace 等几个相关的强力工具描述了 ethtool 命令使用 ethtool netlink 获取信息的原理,本文探讨下内核部分的实现原理。

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    调用层次图示:
    在这里插入图片描述

    我将参考上图,自上而下描述内核中的实现原理。

    用户态使用 ethtool netlink 与内核交互的消息实例

    用户态发送给内核的消息:

    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]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    内核返回给用户态的消息:

    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]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    摘自 使用 strace、tcpdump、nlmon、wireshark 探索 ethtool netlink 框架的原理

    generic netlink 与 ethtool netlink

    generic 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 的协议号。

    genl_rcv 函数的主要逻辑

    调用 netlink_rcv_skb 函数依次解析 netlink 报文的头部、校验关键字段合法性、调用 genl_rcv_msg 函数、判断是否需要回复 ack、需要回复则回复

    genl_rcv_msg 函数的主要逻辑

    1. 根据 netlink headers 的 nlmsg_type 字段获取到注册的 genl_family 协议族结构
    2. 不支持并行调用时,获取互斥锁
    3. 调用 genl_family_rcv_msg 解析消息命令并执行
    4. 在不支持并行调用时,释放互斥锁

    genl_family_rcv_msg 的主要逻辑

    根据 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,
    	},
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    获取到 genl_ops 后,首先检查权限,权限检查通过后根据 netlink 头的 nlmsg_flags 配置,调用 generic netlink 的 dumpit 函数、doit 函数。

    对于 get_wol 消息,流程转向 genl_family_rcv_msg_doit

    genl_family_rcv_msg_doit 的功能

    1. 解析并创建 netlink attribute buff
    2. 调用协议族中的 pre_doit 函数
    3. 调用 genl_ops 中的 doit 函数
    4. 调用协议族中的 post_doit 函数
    5. 释放 netlink attribute buff

    genl_ops 中的 doit 函数调用在 ethtool netlink genl_ops 中均指向 ethnl_default_doit,此函数标志流程进入下一个层次。

    ethnl_default_doit 的功能

    1. 根据 generic netlink 头中的 cmd 字段查找 ethnl_default_requests表,得到一个 struct ethnl_request_op实例。get_wol 消息中对应 ethnl_wol_request_ops实例
    2. 解析 netlink attribute 并执行相关回调
    3. 依次调用 struct ethnl_request_op 结构中的 prepare_data、reply_size、fill_reply 方法获取数据并填充到回复消息中(回复消息直接使用源消息空间构造),然后调用 genlmsg_reply命令回复消息

    ethnl_wol_request_ops的 prepare_data 函数为 wol_prepare_data,此函数将流程推向网络设备驱动 ethtool_ops层。

    wol_prepare_data 函数

    1. 调用网络设备驱动 ethtool_ops 中的 begin 方法
    2. 调用网络设备驱动 ethtool_ops 中的 get_wol 方法(对应 r8169 驱动实现的 rtl8169_get_wol 函数)
    3. 调用网络设备驱动 ethtool_ops 中的 complete 方法

    此函数封装对网络设备驱动 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    netlink.c 为框架内容,ioctl.c 为 ioctl 方式实现,其它的每种类型 ethtool cmd 命令的解析与执行单独创建一个源文件实现。

    分层设计

    ethtool netlink 框架跨越了如下几个抽象层次:

    1. socket netlink
    2. generic netlink family
    3. ethtool generic netlink ops
    4. ethtool netlink request ops
    5. driver ethtool_ops

    ethtool netlink 消息有如下几个组成部分:

    1. nlmsghdr
    2. genlmsghdr
    3. attribute

    消息的不同组成部分在不同的层次中被使用,解析消息需要两层,适配 ethtool_ops 需要一层,这些层次都封装了一种类型功能的变化,有良好的扩展能力。

    提问

    1. 如何将 ethtool netlink 嵌入到内核现有的 netlink 框架中?
    2. ethtool netlink 如何找到底层的网络设备进而调用 ethtool_ops 中的方法?
    3. 为啥需要 genl_ops 与 ethnl_request_ops?两者能合并吗?
    4. 与传统 ioctl 接口获取信息,ethtool netlink 框架变化的是什么?不变的又是什么?
    5. 使用 ethtool netlink 框架的优势

    参考链接

    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

  • 相关阅读:
    本周四晚19:00知识赋能第六期第5课丨OpenHarmony WiFi子系统
    乾元通4G/5G多链路聚合设备在公共安全通信方面解决方案
    Android 休眠流程(三)
    react 安装教程
    【2 线性表】奇偶数划分先后,奇在前偶在后。
    C++使用nlohmann的简单示例
    欧拉定理公式(包括欧拉降幂)
    Mybiosource丨Mybiosource胃泌素细胞抗体ELISA试剂盒方案
    数据结构和算法(15):排序
    C语言-字符串替换
  • 原文地址:https://blog.csdn.net/Longyu_wlz/article/details/126315107