使用 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 */
           };

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 */
           };

一些重要的描述信息:

       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.

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. 创建了一个 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

实现的功能:

向内核发送获取 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

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

可以看到接收到的消息中的目的地址的 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

返回消息中的 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

基础知识中提到——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

发送 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

消息的 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]

消息头中,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

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]

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........$...

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

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

同样,第一个 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/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值