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


    前言

    ethtool 的工作原理 这篇文章中我描述了 ethtool 使用 ioctl 获取信息的流程,最近却发现新版本内核与 ethtool 命令已经支持通过 netlink 来交互,又刷新了认知,本文通过使用现有的一些工具来探索此交互的细节。

    环境信息与准备工作

    环境信息

    linux 系统版本:debian11
    内核版本:5.15.0
    ethtool 命令版本:5.9
    测试网卡驱动:r8169

    准备工作

    1. 安装 tcpdump、wireshark、ethtool 命令
    2. 加载 nlmon 驱动并配置
      配置方式可参考 tcpdump 抓取 netlink 报文 这篇博客

    一些基础知识

    netlink 消息的一些标志

    NLM_F_REQUEST Must be set on all request messages.
    NLM_F_ACK Request for an acknowledgment on success.

    NLM_F_ACK 标志请求接收者回复 ack 消息。netlink 中的 ack 消息的含义是一个 NLMSG_ERROR 报文且 error 字段设置为 0。

    netlink 消息头的结构:

               struct nlmsghdr {
                   __u32 nlmsg_len;    /* Length of message including header */
                   __u16 nlmsg_type;   /* Type of message content */
                   __u16 nlmsg_flags;  /* Additional flags */
                   __u32 nlmsg_seq;    /* Sequence number */
                   __u32 nlmsg_pid;    /* Sender port ID */
               };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    netlink 消息地址的结构:

              struct sockaddr_nl {
                   sa_family_t     nl_family;  /* AF_NETLINK */
                   unsigned short  nl_pad;     /* Zero */
                   pid_t           nl_pid;     /* Port ID */
                   __u32           nl_groups;  /* Multicast groups mask */
               };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    一些重要的描述信息:

           nl_pid  is the unicast address of netlink socket.  It's always 0 if the destination is in the kernel.  For a user-space process, nl_pid is usually the PID of the
           process owning the destination socket.  However, nl_pid identifies a netlink socket, not a process.  If a process owns several netlink sockets, then  nl_pid  can
           be  equal to the process ID only for at most one socket.  There are two ways to assign nl_pid to a netlink socket.  If the application sets nl_pid before calling
           bind(2), then it is up to the application to make sure that nl_pid is unique.  If the application sets it to 0, the kernel takes care of assigning it.  The  ker‐
           nel assigns the process ID to the first netlink socket the process opens and assigns a unique nl_pid to every netlink socket that the process subsequently creates.
    
    • 1
    • 2
    • 3
    • 4
    • 5

    nl_pid 是 netlink 套接字的单播地址,当目标地址是内核时其值为 0。对于一个用户态进程来说,nl_pid 通常是某个 netlink 套接字拥有者的进程 pid。需要注意的是 nl_pid 是标识一个 netlink 套接字而不是一个进程。如果一个进程同时拥有多个 netlink 套接字,此时最多只有一个 netlink socket 的 nl_pid 会与此进程的 pid 相同。

    有两种方法可以将 nl_pid 绑定到一个 netlink 套接字上:

    1. 应用在调用 bind 前设置了 nl_pid 时(非 0 值),由应用自己保证 nl_pid 一一映射到单个 netlink socket
    2. 应用调用 bind 时没有设定 nl_pid、将 nl_pid 设置为 0,内核负责分配多个 netlink socket 的 nl_pid。内核会将第一个 netlink socket 的 nl_pid 设置为进程的 pid,其它的 netlink socket 单独生成唯一的 nl_pid。

    第二种方法比较方便,可是由于 nl_pid 是由内核分配的,而 bind 系统调用并不会返回内核分配的 nl_pid,故而需要在 bind 后单独查询 nl_pid(可以调用 getsockname 获取)。

    备注:本节信息摘自自 man 7 netlink

    从系统调用角度观测

    在一个终端中执行 sudo tcpdump -v -i nlmon0 -w netlink.pcap命令开始抓包,在另外一个终端中执行 sudo strace ethtool enp1s0 2>/tmp/strace.log触发 netlink 消息并使用 strace 跟踪系统调用。

    注意事项:

    最终抓到的 netlink 消息中不只包含 ethtool 使用的 netlink 消息,还包含其它的 netlink 消息(route 等)

    netlink 套接字的创建与 bind

    执行的系统调用:

    socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC) = 3
    setsockopt(3, SOL_NETLINK, NETLINK_EXT_ACK, [1], 4) = 0
    bind(3, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 0
    getsockname(3, {sa_family=AF_NETLINK, nl_pid=45683, nl_groups=00000000}, [12]) = 0
    
    • 1
    • 2
    • 3
    • 4

    实现的功能:

    1. 创建了一个 NETLINK_GENERIC 类型的 netlink
    2. 设置此套接字的 NETLINK_EXT_ACK 属性,支持 ACK 功能
    3. 使用 bind 绑定端口,使用上文描述的第二种设置 nl_pid 的方法(由内核将进程的 pid 赋值给第一个 netlink socket 的 nl_pid 字段)
    4. 使用 getsockname 获取地址信息,目标是获取 nl_pid,获取到的值为 45683 与进程的 pid 相同

    使用 nlctrl 类型 netlink 请求消息获取 ethtool netlink 的协议号

    执行的系统调用:

    sendto(3, {{len=32, type=nlctrl, flags=NLM_F_REQUEST|NLM_F_ACK, seq=1, pid=0}, "\x03\x01\x00\x00\x0c\x00\x02\x00\x65\x74\x68\x74\x6f\x6f\x6c\x00"}, 32, 0, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 32
    
    • 1

    实现的功能:

    向内核发送获取 ethtool 协议号的 netlink 消息,目的地址的 nl_pid 为 0 表示目的地址为内核。

    wireshark 解析的报文内容:

    Frame 49: 48 bytes on wire (384 bits), 48 bytes captured (384 bits)
    Linux netlink (cooked header)
        Link-layer address type: Netlink (824)
        Family: Generic (0x0010)
    Linux Generic Netlink protocol
        Netlink message header (type: 0x0010)
        Command: CTRL_CMD_GETFAMILY (3)
        Family Version: 1
        Reserved
        Attribute: CTRL_ATTR_FAMILY_NAME: ethtool
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    netlink 消息头中设定 type 为 0x10 表示 nl_ctrl 类型,Command 为 CTRL_CMD_GETFAMILY 表示获取协议号的命令,Attribute 中设置待获取的 netlink 协议号的名称为 ethtool

    这一步获取到的 ethtool netlink 的协议号会在 ethtool 向内核发送 ethtool netlink 命令的时候填充到【消息头的 type 字段】中。

    注意事项:

    wireshark 打印的捕获报文大小为 48-bytes,这是消息的长度(32bytes)+ 消息头的长度 16bytes(sizeof(struct nlmsghdr)) 的结果。

    从内核接收返回的消息,得到 ethtool netlink 的协议号

    执行的系统调用:

    recvmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base={{len=756, type=nlctrl, flags=0, seq=1, pid=45683}, "\x01\x02\x00\x00\x0c\x00\x02\x00\x65\x74\x68\x74\x6f\x6f\x6c\x00\x06\x00\x01\x00\x14\x00\x00\x00\x08\x00\x03\x00\x01\x00\x00\x00"...}, iov_len=65536}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 756
    
    • 1

    可以看到接收到的消息中的目的地址的 nl_pid 为 45683,表明这是一条内核发往用户态的点对点消息。

    wireshark 解析的报文内容:

    Frame 50: 772 bytes on wire (6176 bits), 772 bytes captured (6176 bits)
    Linux netlink (cooked header)
        Link-layer address type: Netlink (824)
        Family: Generic (0x0010)
    Linux Generic Netlink protocol
        Netlink message header (type: 0x0010)
        Command: CTRL_CMD_NEWFAMILY (1)
        Family Version: 2
        Reserved
        Attribute: CTRL_ATTR_FAMILY_NAME: ethtool
        Attribute: CTRL_ATTR_FAMILY_ID: 0x14
        Attribute: CTRL_ATTR_VERSION: 1
        Attribute: CTRL_ATTR_HDRSIZE: 0
        Attribute: CTRL_ATTR_MAXATTR: 0
        Attribute: CTRL_ATTR_OPS
        Attribute: CTRL_ATTR_MCAST_GROUPS
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    返回消息中的 Attribute 字段保存获取到的数据,CTRL_ATTR_FAMILY_ID 的值为 0x14 表示 ethtool netlink 的协议号为 0x14。

    获取内核返回的 ack 消息

    执行的系统调用:

    recvmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base={{len=36, type=NLMSG_ERROR, flags=NLM_F_CAPPED, seq=1, pid=45683}, {error=0, msg={len=32, type=nlctrl, flags=NLM_F_REQUEST|NLM_F_ACK, seq=1, pid=0}}}, iov_len=65536}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 36
    
    • 1

    基础知识中提到——netlink 中的 ack 消息的含义是一个 NLMSG_ERROR 报文且 error 字段设置为 0。上述系统调用中,接收到的 netlink 消息符合 ack 消息的特征,同时 ack 消息中也带了原消息的 netlink 头部,用以区分 ack 的目标。

    wireshark 解析的报文内容:

    Frame 51: 52 bytes on wire (416 bits), 52 bytes captured (416 bits)
    Linux netlink (cooked header)
        Link-layer address type: Netlink (824)
        Family: Generic (0x0010)
    Netlink message
        Netlink message header (type: Error)
            Length: 36
            Message type: Error (0x0002)
            Flags: 0x0100
            Sequence: 1
            Port ID: 45683
        Error code: Success (0)
        Netlink message header (type: 0x0010)
            Length: 32
            Message type: Protocol-specific (0x0010)
            Flags: 0x0005
            Sequence: 1
            Port ID: 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    发送 ethtool 查询消息获取接口信息

    sendto(3, {{len=36, type=ethtool, flags=NLM_F_REQUEST|NLM_F_ACK, seq=2, pid=0}, "\x04\x01\x00\x00\x10\x00\x01\x80\x0b\x00\x02\x00\x65\x6e\x70\x31\x73\x30\x00\x00"}, 36, 0, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 36
    
    • 1

    消息的 type 修改为了 ethool,seq 递增为 2,目标地址的 nl_pid 为 0 表明这时一个发往内核的消息。

    wireshark 解析的报文内容:

    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

    消息头中,type 字段为 0x14,这就是 ethtool netlink 的内核协议号,Data 中封装了请求消息的内容。

    获取内核回复的 netlink 消息得到获取的结果

    sendto(3, {{len=36, type=ethtool, flags=NLM_F_REQUEST|NLM_F_ACK, seq=2, pid=0}, "\x04\x01\x00\x00\x10\x00\x01\x80\x0b\x00\x02\x00\x65\x6e\x70\x31\x73\x30\x00\x00"}, 36, 0, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 36
    
    • 1

    wireshark 解析的报文内容:

    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

    wireshark 解析的 Data 部分内容:

    0000   00 04 03 38 00 00 00 00 00 00 00 00 00 00 00 10   ...8............
    .........................................................................
    0030   0b 00 02 00 65 6e 70 31 73 30 00 00 05 00 02 00   ....enp1s0......
    .........................................................................
    0060   11 00 02 00 31 30 62 61 73 65 54 2f 48 61 6c 66   ....10baseT/Half
    0070   00 00 00 00 04 00 03 00 24 00 01 80 08 00 01 00   ........$.......
    0080   01 00 00 00 11 00 02 00 31 30 62 61 73 65 54 2f   ........10baseT/
    0090   46 75 6c 6c 00 00 00 00 04 00 03 00 24 00 01 80   Full........$...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    Data 中表示获取到的 ethtool dump 数据,能够明显识别出来它包含支持的链路模式(10M 半、全双工)。

    获取内核返回的 ack 消息

    recvmsg(3, {msg_name={sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, msg_namelen=12, msg_iov=[{iov_base={{len=36, type=NLMSG_ERROR, flags=NLM_F_CAPPED, seq=2, pid=45683}, {error=0, msg={len=36, type=ethtool, flags=NLM_F_REQUEST|NLM_F_ACK, seq=2, pid=0}}}, iov_len=65536}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, 0) = 36
    sendto(3, {{len=36, type=ethtool, flags=NLM_F_REQUEST|NLM_F_ACK, seq=3, pid=0}, "\x02\x01\x00\x00\x10\x00\x01\x80\x0b\x00\x02\x00\x65\x6e\x70\x31\x73\x30\x00\x00"}, 36, 0, {sa_family=AF_NETLINK, nl_pid=0, nl_groups=00000000}, 12) = 36
    
    • 1
    • 2

    wireshark 解析的报文内容:

    Frame 54: 52 bytes on wire (416 bits), 52 bytes captured (416 bits)
    Linux netlink (cooked header)
        Link-layer address type: Netlink (824)
        Family: Generic (0x0010)
    Netlink message
        Netlink message header (type: Error)
            Length: 36
            Message type: Error (0x0002)
            Flags: 0x0100
            Sequence: 2
            Port ID: 45683
        Error code: Success (0)
        Netlink message header (type: 0x0014)
            Length: 36
            Message type: Protocol-specific (0x0014)
            Flags: 0x0005
            Sequence: 2
            Port ID: 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    同样,第一个 Netlink message header 标识 ack 消息,第二个 Netlink message header 标识 ack 的目标。

    问题联想

    1. 为啥需要获取 ethtool netlink 的协议号?
      Netlink message header 头中的 type 为数字,发送消息时需要填充,其值为内核维护,内核使用它来映射到对应的协议族 ops。
    2. ack 为啥不是相互的?用户态为啥不回复 ack?
      上面的交互过程有些奇怪,内核收到消息后先发送执行结果消息然后再发送 ack,且用户态收到消息后不会发送 ack,与 tcp ack 的过程完全不同。
      我觉得主要的问题是 tcp ack 完全隐藏在协议栈内部,netlink 却涉及内核与用户态的交互,用户态也需要回复 ack 时,每个使用的应用都要编码相关功能。同时用户态明确知道自己需要获取的数据的格式的规范,可以自己做合法性校验。

    参考链接

    https://www.kernel.org/doc/html/latest/networking/ethtool-netlink.html
    https://lwn.net/Articles/783633/

  • 相关阅读:
    原来还可以客户端负载均衡
    MaxCompute远程连接,上传、下载数据文件操作
    什么是营销自动化工具?简单的营销自动化流程如何设计?
    【LeetCode每日一题】——771.宝石与石头
    Django+vue自动化测试平台(24)-- 接口自动化之处理变量
    相机图像质量研究(35)常见问题总结:图像处理对成像的影响--运动噪声
    2113_扫雷 蓝桥杯
    【C++】继承和多态常见的问题
    通过Nacos配置刷新进行RabbitMQ消费者在线启停
    Android入门第37天-在子线程中调用Handler
  • 原文地址:https://blog.csdn.net/Longyu_wlz/article/details/126216625