管理组件传输协议 (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)。

Sockets 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 消息

使用 sendto()sendmsg()send() 系统调用之一传输 MCTP 消息。以 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 指定的情况下,在未连接的套接字上传输消息将导致分配标签,如果尚未为该目标分配任何有效标签。 (destination-eid,tag)元组充当隐式本地套接字地址,以允许套接字接收对此传出消息的响应。如果已执行任何先前的分配(针对不同的远程 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 的 address 参数填充了传入消息的远程地址,包括标签值(为了回复该消息,这是必需的)。

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

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 和 sock 列表。

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