管理组件传输协议 (MCTP)

net/mctp/ 包含对 MCTP 的协议支持,由 DMTF 标准 DSP0236 定义。物理接口驱动程序(规范中的“绑定”)在 drivers/net/mctp/ 中提供。

核心代码通过 AF_MCTP、SOCK_DGRAM 套接字提供基于套接字的接口来发送和接收 MCTP 消息。

结构:接口和网络

内核通过两个项目对本地 MCTP 拓扑进行建模:接口和网络。

接口(或“链路”)是 MCTP 物理传输绑定(由 DSP0236 第 3.2.47 节定义)的一个实例,可能连接到特定的硬件设备。这表示为 struct netdevice

网络通过端点 ID(由 DSP0236 第 3.2.31 节描述)定义 MCTP 端点的唯一地址空间。网络具有用户可见的标识符,以便允许来自用户空间的引用。路由定义特定于一个网络。

接口与一个网络相关联。一个网络可以与一个或多个接口相关联。

如果存在多个网络,则每个网络可能包含其他网络上也存在的端点 ID (EID)。

套接字 API

协议定义

MCTP 使用 AF_MCTP / PF_MCTP 作为地址族和协议族。由于 MCTP 是基于消息的,因此仅支持 SOCK_DGRAM 套接字。

int sd = socket(AF_MCTP, SOCK_DGRAM, 0);

protocol 参数的唯一(当前)值为 0。

与所有套接字地址族一样,源地址和目标地址都使用 sockaddr 类型指定,其中包含单字节端点地址

typedef __u8                mctp_eid_t;

struct mctp_addr {
        mctp_eid_t          s_addr;
};

struct sockaddr_mctp {
        __kernel_sa_family_t smctp_family;
        unsigned int         smctp_network;
        struct mctp_addr     smctp_addr;
        __u8                 smctp_type;
        __u8                 smctp_tag;
};

#define MCTP_NET_ANY        0x0
#define MCTP_ADDR_ANY       0xff

系统调用行为

以下部分描述了标准套接字系统调用的 MCTP 特定行为。选择这些行为是为了与现有套接字 API 紧密映射。

bind():设置本地套接字地址

接收传入请求数据包的套接字将使用 bind() 系统调用绑定到本地地址。

struct sockaddr_mctp addr;

addr.smctp_family = AF_MCTP;
addr.smctp_network = MCTP_NET_ANY;
addr.smctp_addr.s_addr = MCTP_ADDR_ANY;
addr.smctp_type = MCTP_TYPE_PLDM;
addr.smctp_tag = MCTP_TAG_OWNER;

int rc = bind(sd, (struct sockaddr *)&addr, sizeof(addr));

这将建立套接字的本地地址。与网络、地址和消息类型匹配的传入 MCTP 消息将由此套接字接收。这里“传入”的引用很重要;绑定的套接字将仅接收设置了 TO 位的消息,以指示传入请求消息,而不是响应。

smctp_tag 值将配置从此套接字远程端接受的标签。鉴于以上所述,唯一有效的值是 MCTP_TAG_OWNER,这将导致远程“拥有”的标签被路由到此套接字。由于设置了 MCTP_TAG_OWNER,因此不使用 smctp_tag 的 3 个最低有效位;调用者必须将它们设置为零。

MCTP_NET_ANYsmctp_network 值将配置套接字以接收来自任何本地连接网络的传入数据包。特定的网络值将导致套接字仅接收来自该网络的传入消息。

smctp_addr 字段指定要绑定的本地地址。MCTP_ADDR_ANY 的值将配置套接字以接收发送到任何本地目标 EID 的消息。

smctp_type 字段指定要接收的消息类型。仅在传入消息上匹配类型的低 7 位(即,最高有效 IC 位不是匹配的一部分)。这导致套接字接收带有和不带有消息完整性检查页脚的数据包。

sendto()sendmsg()send():传输 MCTP 消息

MCTP 消息使用 sendto()sendmsg()send() 系统调用之一进行传输。使用 sendto() 作为主要示例

struct sockaddr_mctp addr;
char buf[14];
ssize_t len;

/* set message destination */
addr.smctp_family = AF_MCTP;
addr.smctp_network = 0;
addr.smctp_addr.s_addr = 8;
addr.smctp_tag = MCTP_TAG_OWNER;
addr.smctp_type = MCTP_TYPE_ECHO;

/* arbitrary message to send, with message-type header */
buf[0] = MCTP_TYPE_ECHO;
memcpy(buf + 1, "hello, world!", sizeof(buf) - 1);

len = sendto(sd, buf, sizeof(buf), 0,
                (struct sockaddr_mctp *)&addr, sizeof(addr));

addr 的网络和地址字段定义要发送到的远程地址。如果 smctp_tag 具有 MCTP_TAG_OWNER,则内核将忽略在 MCTP_TAG_VALUE 中设置的任何位,并生成适合目标 EID 的标签值。如果未设置 MCTP_TAG_OWNER,则将使用指定的标签值发送消息。如果无法分配标签值,则系统调用将报告 EAGAIN 的 errno。

应用程序必须将消息类型字节作为传递给 sendto() 的消息缓冲区的第一个字节提供。如果要将消息完整性检查包括在传输的消息中,则也必须在消息缓冲区中提供它,并且消息类型字节的最高有效位必须为 1。

sendmsg() 系统调用允许更紧凑的参数接口,并将消息缓冲区指定为分散/收集列表。目前,未定义任何辅助消息类型(用于传递给 sendmsg()msg_control 数据)。

在未连接的套接字上使用指定的 MCTP_TAG_OWNER 传输消息将导致分配一个标签,如果没有为该目标分配有效的标签。 (目标 eid,标签)元组充当隐式本地套接字地址,以允许套接字接收对此传出消息的响应。如果已执行任何先前的分配(对于不同的远程 EID),则该分配将丢失。

套接字将仅接收它们已发送的请求的响应(TO=1),并且可能仅响应它们已接收的请求(TO=0)。

recvfrom()recvmsg()recv():接收 MCTP 消息

应用程序可以使用 recvfrom()recvmsg()recv() 系统调用之一来接收 MCTP 消息。使用 recvfrom() 作为主要示例

struct sockaddr_mctp addr;
socklen_t addrlen;
char buf[14];
ssize_t len;

addrlen = sizeof(addr);

len = recvfrom(sd, buf, sizeof(buf), 0,
                (struct sockaddr_mctp *)&addr, &addrlen);

/* We can expect addr to describe an MCTP address */
assert(addrlen >= sizeof(buf));
assert(addr.smctp_family == AF_MCTP);

printf("received %zd bytes from remote EID %d\n", rc, addr.smctp_addr);

recvfromrecvmsg 的地址参数填充有传入消息的远程地址,包括标签值(这将需要回复消息)。

消息缓冲区的第一个字节将包含消息类型字节。如果完整性检查在消息之后,则它将包含在接收的缓冲区中。

recv() 系统调用的行为类似,但不向应用程序提供远程地址。因此,仅当已知远程地址或消息不需要回复时,这些调用才有用。

与发送调用一样,套接字将仅接收它们已发送的请求的响应(TO=1),并且可能仅响应它们已接收的请求(TO=0)。

ioctl(SIOCMCTPALLOCTAG)ioctl(SIOCMCTPDROPTAG)

这些标签通过显式分配(和删除)标签值,而不是在 sendmsg() 时由内核自动分配每个消息的标签,从而为应用程序提供对 MCTP 消息标签的更多控制。

通常,仅当您的 MCTP 协议不符合通常的请求/响应模型时,才需要使用这些 ioctl。例如,如果您需要在多个请求中持久保存标签,或者一个请求可能生成多个响应。在这些情况下,ioctl 允许您将标签分配(和释放)与各个消息发送和接收操作分离。

两个 ioctl 都传递指向 struct mctp_ioc_tag_ctl 的指针

struct mctp_ioc_tag_ctl {
    mctp_eid_t      peer_addr;
    __u8            tag;
    __u16           flags;
};

SIOCMCTPALLOCTAG 为特定的对等方分配一个标签,应用程序可以在将来的 sendmsg() 调用中使用该标签。应用程序使用远程 EID 填充 peer_addr 成员。其他字段必须为零。

返回时,将使用分配的标签值填充 tag 成员。分配的标签将设置以下标签位

  • MCTP_TAG_OWNER: 只有当你作为标签所有者时,分配标签才有意义。

  • MCTP_TAG_PREALLOC: 向 sendmsg() 表明这是一个预分配的标签。

  • ... 以及实际的标签值,位于最低有效的三位(MCTP_TAG_MASK)中。 请注意,零是一个有效的标签值。

标签值应按原样用于 struct sockaddr_mctpsmctp_tag 成员。

SIOCMCTPDROPTAG 释放先前由 SIOCMCTPALLOCTAG ioctl 分配的标签。 peer_addr 必须与分配时使用的相同,并且 tag 值必须与分配返回的标签完全匹配(包括 MCTP_TAG_OWNERMCTP_TAG_PREALLOC 位)。 flags 字段必须为零。

内核内部机制

MCTP 协议栈中存在几种可能的包流:

  1. 本地 TX 到远程端点,消息 <= MTU

    sendmsg()
     -> mctp_local_output()
        : route lookup
        -> rt->output() (== mctp_route_output)
           -> dev_queue_xmit()
    
  2. 本地 TX 到远程端点,消息 > MTU

    sendmsg()
    -> mctp_local_output()
        -> mctp_do_fragment_route()
           : creates packet-sized skbs. For each new skb:
           -> rt->output() (== mctp_route_output)
              -> dev_queue_xmit()
    
  3. 远程 TX 到本地端点,单包消息

    mctp_pkttype_receive()
    : route lookup
    -> rt->output() (== mctp_route_input)
       : sk_key lookup
       -> sock_queue_rcv_skb()
    
  4. 远程 TX 到本地端点,多包消息

    mctp_pkttype_receive()
    : route lookup
    -> rt->output() (== mctp_route_input)
       : sk_key lookup
       : stores skb in struct sk_key->reasm_head
    
    mctp_pkttype_receive()
    : route lookup
    -> rt->output() (== mctp_route_input)
       : sk_key lookup
       : finds existing reassembly in sk_key->reasm_head
       : appends new fragment
       -> sock_queue_rcv_skb()
    

关键引用计数

  • 键被以下内容引用:

    • 一个 skb:在路由输出期间,存储在 skb->cb 中。

    • netns 和套接字列表。

  • 键可以与设备关联,在这种情况下,它们会持有对设备 (通过 key->dev 设置,通过 dev->key_count 计数) 的引用。 多个键可以引用同一个设备。