RxRPC 网络协议¶
RxRPC 协议驱动程序在 UDP 之上提供可靠的两阶段传输,可用于执行 RxRPC 远程操作。这是通过 AF_RXRPC 系列的套接字完成的,使用 sendmsg() 和 recvmsg() 以及控制数据来发送和接收数据、中止和错误。
本文档内容
概述。
RxRPC 协议概述。
AF_RXRPC 驱动程序模型。
控制消息。
套接字选项。
安全性。
客户端示例用法。
服务器示例用法。
AF_RXRPC 内核接口。
可配置参数。
概述¶
RxRPC 是一个两层协议。有一个会话层,它使用 UDP over IPv4(或 IPv6)作为传输层,提供可靠的虚拟连接,但实现了一个真正的网络协议;还有一个表示层,它使用 XDR 将结构化数据呈现为二进制 blob,然后再反转(就像 SunRPC 一样)
+-------------+
| Application |
+-------------+
| XDR | Presentation
+-------------+
| RxRPC | Session
+-------------+
| UDP | Transport
+-------------+
AF_RXRPC 提供
RxRPC 工具的一部分,用于内核和用户空间应用程序,通过使其会话部分成为 Linux 网络协议 (AF_RXRPC)。
一个两阶段协议。客户端传输一个 blob(请求),然后接收一个 blob(回复),服务器接收请求,然后传输回复。
保留为一次调用设置的传输系统的可重用位,以加快后续调用。
一个安全协议,使用 Linux 内核的密钥保留工具来管理客户端的安全。服务器端必须在安全协商中更加主动。
AF_RXRPC 不提供 XDR 编组/表示工具。这留给应用程序处理。AF_RXRPC 只处理 blob。即使操作 ID 也只是请求 blob 的前四个字节,因此超出了内核的兴趣范围。
AF_RXRPC 系列的套接字
创建为 SOCK_DGRAM 类型;
提供它们将要使用的底层传输类型的协议 - 目前仅支持 PF_INET。
Andrew 文件系统 (AFS) 是一个使用此功能的应用程序示例,它同时具有内核(文件系统)和用户空间(实用程序)组件。
RxRPC 协议概述¶
RxRPC 协议概述
RxRPC 位于另一个网络协议(目前唯一选择是 UDP)之上,并使用它来提供网络传输。例如,UDP 端口提供传输端点。
RxRPC 支持来自任何给定传输端点的多个虚拟“连接”,从而允许共享端点,即使是到同一个远程端点。
每个连接都转到特定的“服务”。一个连接不能转到多个服务。可以将服务视为 RxRPC 等效的端口号。AF_RXRPC 允许多个服务共享一个端点。
客户端发起的数据包被标记,因此可以在客户端和服务器连接之间共享传输端点(连接具有方向)。
在一个本地传输端点和一个远程端点上的一个服务之间,最多可以同时支持十亿个连接。一个 RxRPC 连接由七个数字描述
Local address } Local port } Transport (UDP) address Remote address } Remote port } Direction Connection ID Service ID每个 RxRPC 操作都是一个“调用”。一个连接最多可以进行 40 亿次调用,但一个连接上一次最多只能进行四次调用。
调用是两阶段的,并且是不对称的:客户端发送其请求数据,服务接收这些数据;然后服务传输客户端接收的回复数据。
数据 blob 的大小不确定,阶段的结束用数据包中的标志标记。但是,组成一个 blob 的数据包数量可能不超过 40 亿,因为这会导致序列号回绕。
请求数据的前四个字节是服务操作 ID。
安全是基于每个连接协商的。连接由其上到达的第一个数据包发起。如果请求安全,服务器会发出“质询”,然后客户端回复“响应”。如果响应成功,则为该连接的生命周期设置安全,并且之后对其进行的所有后续调用都使用相同的安全。如果服务器在客户端之前让连接失效,如果客户端再次使用该连接,则会重新协商安全。
调用使用 ACK 数据包来处理可靠性。数据包也按每次调用显式排序。
有两种类型的肯定确认:硬 ACK 和软 ACK。硬 ACK 向远端指示已接收并处理到某个点为止的所有数据;软 ACK 指示已接收到数据,但可能仍会被丢弃并重新请求。发送方在它们被硬 ACK 之前不得丢弃任何可传输的数据包。
接收到回复数据包会隐式地硬 ACK 构成请求的所有数据包。
当请求已发送、回复已收到并且回复的最后一个数据包的最终硬 ACK 已到达服务器时,调用完成。
在完成之前,任何一方都可以在任何时候中止调用。
AF_RXRPC 驱动程序模型¶
关于 AF_RXRPC 驱动程序
AF_RXRPC 协议透明地使用传输协议的内部套接字来表示传输端点。
AF_RXRPC 套接字映射到 RxRPC 连接捆绑包。实际的 RxRPC 连接是透明处理的。一个客户端套接字可以用于对同一服务进行多次并发调用。一个服务器套接字可以处理来自许多客户端的调用。
将启动额外的并行客户端连接以支持额外的并发调用,直至可调整的限制。
每个连接在当前使用它的最后一个调用完成后都会保留一段时间 [可调整],以防新调用可以重用它。
每个内部 UDP 套接字在最后一个使用它的连接被丢弃后都会保留一段时间 [可调整],以防新连接可以使用它。
仅当客户端调用具有描述其安全的相同密钥结构(并假设调用会以其他方式共享连接)时,才会在调用之间共享客户端连接。不安全的调用也能够相互共享连接。
如果客户端表示它是共享的,则共享服务器端连接。
ACK 处理由协议驱动程序自动处理,包括 ping 回复。
SO_KEEPALIVE 自动 ping 另一方以保持连接活动 [TODO]。
如果收到 ICMP 错误,所有受该错误影响的调用都将中止,并通过 recvmsg() 传递适当的网络错误。
与 RxRPC 套接字的用户交互
通过绑定具有非零服务 ID 的地址,将套接字制作为服务器套接字。
在客户端中,发送请求是通过一个或多个 sendmsg 实现的,然后通过一个或多个 recvmsg 接收回复。
从客户端发送的请求的第一个 sendmsg 包含一个标签,该标签将用于与该调用关联的所有其他 sendmsg 或 recvmsg 中。该标签在控制数据中携带。
connect() 用于为客户端套接字提供默认目标地址。可以通过向调用的第一个 sendmsg() 提供备用地址 (struct msghdr::msg_name) 来覆盖此地址。
如果在未绑定的客户端上调用 connect(),则在操作发生之前将绑定一个随机的本地端口。
服务器套接字也可以用于进行客户端调用。为此,调用的第一个 sendmsg() 必须指定目标地址。服务器的传输端点用于发送数据包。
一旦应用程序收到了与调用关联的最后一个消息,保证不会再看到该标签,因此可以用它来固定客户端资源。然后可以发起具有相同标签的新调用,而不用担心干扰。
在服务器中,通过一个或多个 recvmsg 接收请求,然后通过一个或多个 sendmsg 传输回复,然后通过最后一个 recvmsg 接收最终的 ACK。
在为调用发送数据时,如果有更多数据要发送给该调用,则为 sendmsg 提供 MSG_MORE。
在为调用接收数据时,如果有更多数据要发送给该调用,则 recvmsg 会标记 MSG_MORE。
在为调用接收数据或消息时,recvmsg 会标记 MSG_EOR,以指示该调用的终止消息。
可以通过向控制数据添加中止控制消息来中止调用。发出中止会终止内核对该调用标签的使用。该调用的接收队列中等待的任何消息都将被丢弃。
中止、繁忙通知和质询数据包由 recvmsg 传递,控制数据消息将设置为指示上下文。接收到中止或繁忙消息会终止内核对该调用标签的使用。
msghdr 结构的控制数据部分用于许多事情
目标或受影响调用的标签。
发送或接收错误、中止和繁忙通知。
传入调用的通知。
发送调试请求和接收调试回复 [TODO]。
当内核已接收并设置传入调用时,它会向服务器应用程序发送消息,以告知它有一个新调用正在等待接受 [recvmsg 报告一个特殊的控制消息]。然后,服务器应用程序使用 sendmsg 为新调用分配一个标签。完成此操作后,请求数据的第一部分将由 recvmsg 传递。
服务器应用程序必须为服务器套接字提供一个密钥环,其中包含与其允许的安全类型相对应的密钥。设置安全连接时,内核会在密钥环中查找相应的密钥,然后向客户端发送质询数据包并接收响应数据包。然后,内核检查数据包的授权,并中止连接或设置安全。
客户端将用于保护其通信的密钥的名称由套接字选项指定。
关于 sendmsg 的注释
可以设置 MSG_WAITALL 来告诉 sendmsg 如果对等方正在合理的时间内接受数据包,从而使我们能够将所有数据排队等待传输,则忽略信号。这要求客户端在每个 2*RTT 时间段内至少接受一个数据包。
如果未设置此选项,sendmsg() 将立即返回,如果未消耗任何内容,则返回 EINTR/ERESTARTSYS,否则返回消耗的数据量。
关于 recvmsg 的说明
如果接收队列中有一系列属于特定调用的数据消息,那么 recvmsg 将会继续处理它们,直到
遇到该调用接收数据的末尾,
遇到一个非数据消息,
遇到属于不同调用的消息,或者
填满用户缓冲区。
如果在阻塞模式下调用 recvmsg,它将保持睡眠状态,等待进一步数据的接收,直到满足上述四个条件之一。
MSG_PEEK 的操作类似,但如果它已将任何数据放入缓冲区,它将立即返回,而不是睡眠直到它可以填满缓冲区。
如果一个数据消息在填充用户缓冲区时仅被部分消耗,那么该消息的剩余部分将保留在队列的前面,供下一个接收者使用。MSG_TRUNC 将永远不会被标记。
如果一个调用还有更多数据可用(它尚未复制该阶段中最后一个数据消息的最后一个字节),则 MSG_MORE 将被标记。
控制消息¶
AF_RXRPC 在 sendmsg() 和 recvmsg() 中使用控制消息来复用调用,调用某些操作并报告某些条件。它们是:
消息 ID
SRT
数据
含义
RXRPC_USER_CALL_ID
sr-
用户 ID
应用程序的调用说明符
RXRPC_ABORT
srt
中止代码
发出/接收的中止代码
RXRPC_ACK
-rt
不适用
接收到最终 ACK
RXRPC_NET_ERROR
-rt
错误号
调用时的网络错误
RXRPC_BUSY
-rt
不适用
调用被拒绝(服务器繁忙)
RXRPC_LOCAL_ERROR
-rt
错误号
遇到本地错误
RXRPC_NEW_CALL
-r-
不适用
接收到新调用
RXRPC_ACCEPT
s--
不适用
接受新调用
RXRPC_EXCLUSIVE_CALL
s--
不适用
进行独占客户端调用
RXRPC_UPGRADE_SERVICE
s--
不适用
客户端调用可以升级
RXRPC_TX_LENGTH
s--
数据长度
Tx 数据的总长度
(SRT = 可在 Sendmsg 中使用 / 由 Recvmsg 传递 / 终端消息)
RXRPC_USER_CALL_ID
这用于指示应用程序的调用 ID。这是一个无符号长整型,应用程序在客户端通过将其附加到第一个数据消息中指定,或者在服务器端通过将其与 RXRPC_ACCEPT 消息关联传递。recvmsg() 会将其与除 RXRPC_NEW_CALL 消息之外的所有消息一起传递。
RXRPC_ABORT
应用程序可以使用此项通过将其传递给 sendmsg 来中止调用,或者可以通过 recvmsg 传递此项来指示收到了远程中止。无论哪种方式,都必须将其与 RXRPC_USER_CALL_ID 关联以指定受影响的调用。如果要发送中止,则如果不存在具有该用户 ID 的调用,将返回错误 EBADSLT。
RXRPC_ACK
此项传递给服务器应用程序,以指示已从客户端收到调用的最终 ACK。它将与 RXRPC_USER_CALL_ID 关联,以指示现在已完成的调用。
RXRPC_NET_ERROR
此项传递给应用程序,以指示在尝试与对等方通信的过程中遇到了 ICMP 错误消息。控制消息数据中将包含一个 errno 类整数值,指示问题,并且 RXRPC_USER_CALL_ID 将指示受影响的调用。
RXRPC_BUSY
此项传递给客户端应用程序,以指示由于服务器繁忙,调用被服务器拒绝。它将与 RXRPC_USER_CALL_ID 关联,以指示被拒绝的调用。
RXRPC_LOCAL_ERROR
此项传递给应用程序,以指示遇到了本地错误,并且由于此错误,调用已中止。控制消息数据中将包含一个 errno 类整数值,指示问题,并且 RXRPC_USER_CALL_ID 将指示受影响的调用。
RXRPC_NEW_CALL
此项传递给服务器应用程序,以指示已收到新调用并等待接受。由于必须通过执行 RXRPC_ACCEPT 来随后分配用户 ID,因此没有与此关联的用户 ID。
RXRPC_ACCEPT
服务器应用程序使用此项来尝试接受调用并为其分配用户 ID。它应与 RXRPC_USER_CALL_ID 关联,以指示要分配的用户 ID。如果没有要接受的调用(可能已超时、已中止等),则 sendmsg 将返回错误 ENODATA。如果用户 ID 已被另一个调用使用,则将返回错误 EBADSLT。
RXRPC_EXCLUSIVE_CALL
此项用于指示应在一次性连接上进行客户端调用。一旦调用终止,连接将被丢弃。
RXRPC_UPGRADE_SERVICE
此项用于进行客户端调用以探测服务器是否可以升级指定的服务 ID。调用方必须检查 recvmsg() 返回的 msg_name 中实际使用的服务 ID。探测的操作必须是在两个服务中采用相同参数的操作。
一旦此项已用于建立服务器的升级能力(或缺乏升级能力),则应将返回的服务 ID 用于与该服务器的所有未来通信,并且不应再设置 RXRPC_UPGRADE_SERVICE。
RXRPC_TX_LENGTH
此项用于通知内核调用将要传输的数据总量(无论是客户端请求还是服务响应)。如果给定此项,它允许内核直接从用户空间缓冲区加密到数据包缓冲区,而不是复制到缓冲区然后再就地加密。此项只能与为调用提供数据的第一个 sendmsg() 一起给出。如果实际给出的数据量不同,将生成 EMSGSIZE。
此项采用 __s64 类型的参数,指示将要传输的数量。此项不能小于零。
符号 RXRPC__SUPPORTED 定义为比支持的最高控制消息类型大 1。在运行时,可以通过 RXRPC_SUPPORTED_CMSG 套接字选项(见下文)查询此值。
套接字选项¶
AF_RXRPC 套接字在 SOL_RXRPC 级别支持一些套接字选项
RXRPC_SECURITY_KEY
此项用于指定要使用的密钥的描述。密钥使用 request_key() 从调用进程的密钥环中提取,并且应为“rxrpc”类型。
optval 指针指向描述字符串,optlen 指示字符串的长度,不包括 NUL 终止符。
RXRPC_SECURITY_KEYRING
与上述类似,但指定要使用的服务器密钥环(密钥类型“keyring”)。请参阅“安全性”部分。
RXRPC_EXCLUSIVE_CONNECTION
此项用于请求应为此套接字上后续进行的每个调用使用新连接。optval 应为 NULL,optlen 应为 0。
RXRPC_MIN_SECURITY_LEVEL
此项用于指定此套接字上的调用所需的最低安全级别。optval 必须指向一个包含以下值之一的 int
RXRPC_SECURITY_PLAIN
仅加密校验和。
RXRPC_SECURITY_AUTH
加密校验和加上数据包填充和加密的数据包的前八个字节 - 其中包括实际数据包长度。
RXRPC_SECURITY_ENCRYPT
加密校验和加上整个数据包填充和加密,包括实际数据包长度。
RXRPC_UPGRADEABLE_SERVICE
此项用于指示具有两个绑定的服务套接字可以根据客户端的请求将一个绑定的服务升级到另一个。optval 必须指向一个包含两个无符号短整型的数组。第一个是要从中升级的服务 ID,第二个是要升级到的服务 ID。
RXRPC_SUPPORTED_CMSG
这是一个只读选项,它将一个 int 写入缓冲区,指示支持的最高控制消息类型。
安全性¶
目前,仅实现了 kerberos 4 等效协议(安全索引 2 - rxkad)。这需要加载 rxkad 模块,并且在客户端上,需要从 AFS kaserver 或 kerberos 服务器获取适当类型的票证并将其安装为“rxrpc”类型密钥。这通常使用 klog 程序完成。可以在以下位置找到一个简单的 klog 示例程序:
在客户端上提供给 add_key() 的有效负载应采用以下形式:
struct rxrpc_key_sec2_v1 {
uint16_t security_index; /* 2 */
uint16_t ticket_length; /* length of ticket[] */
uint32_t expiry; /* time at which expires */
uint8_t kvno; /* key version number */
uint8_t __pad[3];
uint8_t session_key[8]; /* DES session key */
uint8_t ticket[0]; /* the encrypted ticket */
};
其中票证 blob 只是附加到上述结构。
对于服务器,必须使类型为“rxrpc_s”的密钥可用于服务器。它们的描述为“<serviceID>:<securityIndex>”(例如:AFS VL 服务的 rxkad 密钥为“52:2”)。创建此类密钥时,应将服务器的密钥作为实例化数据给出(请参见下面的示例)。
add_key(“rxrpc_s”, “52:2”, secret_key, 8, keyring);
通过在 sockopt 中命名密钥环,将其传递到服务器套接字。然后,服务器套接字在建立安全传入连接时在此密钥环中查找服务器密钥。可以在以下位置找到一个示例程序,其中可以看到这一点:
客户端使用示例¶
客户端将通过以下方式发出操作:
通过以下方式设置 RxRPC 套接字:
client = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);其中第三个参数指示所使用的传输套接字的协议族 - 通常是 IPv4,但也可以是 IPv6 [待办事项]。
可以选择绑定本地地址
struct sockaddr_rxrpc srx = { .srx_family = AF_RXRPC, .srx_service = 0, /* we're a client */ .transport_type = SOCK_DGRAM, /* type of transport socket */ .transport.sin_family = AF_INET, .transport.sin_port = htons(7000), /* AFS callback */ .transport.sin_address = 0, /* all local interfaces */ }; bind(client, &srx, sizeof(srx));这指定了要使用的本地 UDP 端口。如果未给出,将使用随机的非特权端口。一个 UDP 端口可以在几个不相关的 RxRPC 套接字之间共享。安全是基于每个 RxRPC 虚拟连接处理的。
设置安全:
const char *key = "AFS:cambridge.redhat.com"; setsockopt(client, SOL_RXRPC, RXRPC_SECURITY_KEY, key, strlen(key));这将发出 request_key() 来获取表示安全上下文的密钥。可以设置最低安全级别:
unsigned int sec = RXRPC_SECURITY_ENCRYPT; setsockopt(client, SOL_RXRPC, RXRPC_MIN_SECURITY_LEVEL, &sec, sizeof(sec));然后可以指定要联系的服务器(或者可以通过 sendmsg 完成此操作):
struct sockaddr_rxrpc srx = { .srx_family = AF_RXRPC, .srx_service = VL_SERVICE_ID, .transport_type = SOCK_DGRAM, /* type of transport socket */ .transport.sin_family = AF_INET, .transport.sin_port = htons(7005), /* AFS volume manager */ .transport.sin_address = ..., }; connect(client, &srx, sizeof(srx));然后应使用一系列 sendmsg() 调用将请求数据发布到服务器套接字,每个调用都附加以下控制消息:
RXRPC_USER_CALL_ID
指定此调用的用户 ID
应在请求的除最后一部分之外的所有部分上在 msghdr::msg_flags 中设置 MSG_MORE。可以同时发出多个请求。
还可以在第一次 sendmsg() 调用中指定 RXRPC_TX_LENGTH 控制消息。
如果调用旨在转到通过 connect() 指定的默认目标之外的目标,则应在该调用的第一个请求消息上设置 msghdr::msg_name。
然后将回复数据发布到服务器套接字,以供 recvmsg() 接收。如果特定调用有更多回复数据要读取,则 recvmsg() 将标记 MSG_MORE。将在调用的终端读取时设置 MSG_EOR。
所有数据都将附加以下控制消息传递:
RXRPC_USER_CALL_ID - 指定此调用的用户 ID
如果发生中止或错误,则将在控制数据缓冲区中返回此消息,并且将标记 MSG_EOR 以指示该调用的结束。
客户端可以请求它知道的服务 ID,并通过在调用的第一个 sendmsg() 上提供 RXRPC_UPGRADE_SERVICE 来请求将其升级到更好的服务(如果可用)。然后,客户端应检查 recvmsg() 填充的 msg_name 中的 srx_service。如果服务忽略了升级请求,则 srx_service 将保留与提供给 sendmsg() 相同的值 - 否则,它将被更改以指示服务器升级到的服务 ID。请注意,升级的服务 ID 由服务器选择。调用方必须等待,直到在回复中看到服务 ID,然后才能发送任何其他调用(在探测结束之前,将阻止对同一目标的进一步调用)。
服务器使用示例¶
服务器的设置方式应为接受以下操作
通过以下方式创建 RxRPC 套接字
server = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);其中第三个参数指示所使用的传输套接字的地址类型,通常为 IPv4。
如果需要,可以通过为套接字提供一个包含服务器密钥的密钥环来设置安全性
keyring = add_key("keyring", "AFSkeys", NULL, 0, KEY_SPEC_PROCESS_KEYRING); const char secret_key[8] = { 0xa7, 0x83, 0x8a, 0xcb, 0xc7, 0x83, 0xec, 0x94 }; add_key("rxrpc_s", "52:2", secret_key, 8, keyring); setsockopt(server, SOL_RXRPC, RXRPC_SECURITY_KEYRING, "AFSkeys", 7);密钥环在提供给套接字后可以被操作。这允许服务器在运行时添加更多密钥、替换密钥等。
然后必须绑定一个本地地址
struct sockaddr_rxrpc srx = { .srx_family = AF_RXRPC, .srx_service = VL_SERVICE_ID, /* RxRPC service ID */ .transport_type = SOCK_DGRAM, /* type of transport socket */ .transport.sin_family = AF_INET, .transport.sin_port = htons(7000), /* AFS callback */ .transport.sin_address = 0, /* all local interfaces */ }; bind(server, &srx, sizeof(srx));只要传输参数相同,一个套接字可以绑定多个服务 ID。当前限制为两个。为此,应该调用两次 bind()。
如果需要服务升级,则必须先绑定两个服务 ID,然后必须设置以下选项
unsigned short service_ids[2] = { from_ID, to_ID }; setsockopt(server, SOL_RXRPC, RXRPC_UPGRADEABLE_SERVICE, service_ids, sizeof(service_ids));如果连接请求升级,这将自动将服务 from_ID 上的连接升级到服务 to_ID。当请求数据传递到用户空间时,这将反映在通过 recvmsg() 获取的 msg_name 中。
然后将服务器设置为侦听传入的调用
listen(server, 100);内核通过为每个传入连接发送消息来通知服务器。这是通过服务器套接字上的 recvmsg() 接收的。它没有数据,并且附加了一条无数据的控制消息
RXRPC_NEW_CALL此时可以通过 recvmsg() 传回的地址应被忽略,因为发出消息的调用可能在它被接受时已经过去 - 在这种情况下,将接受队列中仍在等待的第一个调用。
然后服务器通过发出一个带有两个控制数据且没有实际数据的 sendmsg() 来接受新调用
RXRPC_ACCEPT
表示连接接受
RXRPC_USER_CALL_ID
为此调用指定用户 ID
然后第一个请求数据包将被发布到服务器套接字以供 recvmsg() 拾取。此时,可以从 msghdr 结构中的地址字段读取调用的 RxRPC 地址。
后续的请求数据将发布到服务器套接字以供 recvmsg() 在到达时收集。除了最后一部分请求数据外,所有其他部分都将带有 MSG_MORE 标志。
所有数据都将附加以下控制消息传递:
RXRPC_USER_CALL_ID
指定此调用的用户 ID
然后应使用一系列 sendmsg() 调用将回复数据发布到服务器套接字,每个调用都附加了以下控制消息
RXRPC_USER_CALL_ID
指定此调用的用户 ID
对于特定调用的所有消息,除了最后一条消息外,都应在 msghdr::msg_flags 中设置 MSG_MORE。
当收到来自客户端的最终 ACK 时,它将被发布以供 recvmsg() 检索。它将采用带有两条附加控制消息的无数据消息的形式
RXRPC_USER_CALL_ID
指定此调用的用户 ID
RXRPC_ACK
指示最终 ACK(无数据)
将标记 MSG_EOR 以指示这是此调用的最后一条消息。
在发送回复数据的最后一个数据包之前,可以通过使用带有以下控制消息的无数据消息调用 sendmsg() 来中止调用
RXRPC_USER_CALL_ID
指定此调用的用户 ID
RXRPC_ABORT
指示中止代码(4 字节数据)
如果发出此命令,则将丢弃套接字接收队列中等待的任何数据包。
请注意,特定服务的所有通信都通过一个服务器套接字进行,使用 sendmsg() 和 recvmsg() 上的控制消息来确定受影响的调用。
AF_RXRPC 内核接口¶
AF_RXRPC 模块还为内核实用程序(如 AFS 文件系统)提供了一个接口。这允许此类实用程序
在一个套接字上的单个客户端调用上直接使用不同的密钥,而不是必须打开一大堆套接字,每个密钥一个。
避免让 RxRPC 在发出调用或打开套接字时调用 request_key()。相反,实用程序负责在适当的时候请求密钥。例如,AFS 将在 VFS 操作(如 open() 或 unlink())期间执行此操作。然后在启动调用时传递密钥。
请求使用 GFP_KERNEL 以外的其他内容来分配内存。
避免使用 recvmsg() 调用的开销。可以在将 RxRPC 消息放入套接字 Rx 队列之前拦截它们,并直接操作套接字缓冲区。
要使用 RxRPC 功能,内核实用程序仍然必须打开一个 AF_RXRPC 套接字,根据需要绑定地址,并在需要作为服务器套接字时进行侦听,然后将其传递给内核接口函数。
内核接口函数如下
开始新的客户端调用
struct rxrpc_call * rxrpc_kernel_begin_call(struct socket *sock, struct sockaddr_rxrpc *srx, struct key *key, unsigned long user_call_ID, s64 tx_total_len, gfp_t gfp, rxrpc_notify_rx_t notify_rx, bool upgrade, bool intr, unsigned int debug_id);这将分配用于进行新的 RxRPC 调用的基础结构并分配调用和连接号。调用将在套接字绑定的 UDP 端口上进行。除非提供替代方案(srx 为非 NULL),否则调用将转到已连接的客户端套接字的目标地址。
如果提供了密钥,则将使用该密钥来保护调用,而不是使用 RXRPC_SECURITY_KEY sockopt 绑定到套接字的密钥。以这种方式保护的调用仍将尽可能共享连接。
user_call_ID 等同于控制数据缓冲区中提供给 sendmsg() 的 ID。完全可以使用它来指向内核数据结构。
tx_total_len 是调用者打算通过此调用传输的数据量(如果此时未知,则为 -1)。设置数据大小允许内核直接加密到数据包缓冲区,从而节省副本。该值不得小于 -1。
notify_rx 是一个指向函数的指针,当发生传入数据包或远程中止等事件时,将调用该函数。
如果客户端操作应请求服务器将服务升级到更好的服务,则应将 upgrade 设置为 true。结果服务 ID 由 rxrpc_kernel_recv_data() 返回。
如果调用应该是可中断的,则应将 intr 设置为 true。如果未设置此选项,则此函数可能不会返回,直到分配了通道;如果设置了此选项,则该函数可能会返回 -ERESTARTSYS。
debug_id 是用于跟踪的调用调试 ID。可以通过原子方式递增 rxrpc_debug_id 来获得。
如果此函数成功,则返回对 RxRPC 调用的不透明引用。调用者现在持有对此的引用,并且必须正确结束它。
关闭客户端调用
void rxrpc_kernel_shutdown_call(struct socket *sock, struct rxrpc_call *call);用于关闭先前开始的调用。user_call_ID 将从 AF_RXRPC 的知识中删除,并且不会再次与指定的调用关联。
释放客户端调用的引用
void rxrpc_kernel_put_call(struct socket *sock, struct rxrpc_call *call);用于释放调用者对 rxrpc 调用的引用。
通过调用发送数据
typedef void (*rxrpc_notify_end_tx_t)(struct sock *sk, unsigned long user_call_ID, struct sk_buff *skb); int rxrpc_kernel_send_data(struct socket *sock, struct rxrpc_call *call, struct msghdr *msg, size_t len, rxrpc_notify_end_tx_t notify_end_rx);用于提供客户端调用的请求部分或服务器调用的回复部分。msg.msg_iovlen 和 msg.msg_iov 指定要使用的数据缓冲区。msg_iov 不得为 NULL,并且必须专门指向内核中的虚拟地址。如果此调用将有后续数据发送,则可以为 msg.msg_flags 提供 MSG_MORE。
msg 不得指定目标地址、控制数据或 MSG_MORE 以外的任何标志。len 是要传输的数据总量。
notify_end_rx 可以为 NULL,也可以用于指定当调用状态更改为结束 Tx 阶段时要调用的函数。调用此函数时将保持自旋锁,以防止在函数返回之前传输最后一个 DATA 数据包。
从调用接收数据
int rxrpc_kernel_recv_data(struct socket *sock, struct rxrpc_call *call, void *buf, size_t size, size_t *_offset, bool want_more, u32 *_abort, u16 *_service) This is used to receive data from either the reply part of a client call or the request part of a service call. buf and size specify how much data is desired and where to store it. *_offset is added on to buf and subtracted from size internally; the amount copied into the buffer is added to *_offset before returning. want_more should be true if further data will be required after this is satisfied and false if this is the last item of the receive phase. There are three normal returns: 0 if the buffer was filled and want_more was true; 1 if the buffer was filled, the last DATA packet has been emptied and want_more was false; and -EAGAIN if the function needs to be called again. If the last DATA packet is processed but the buffer contains less than the amount requested, EBADMSG is returned. If want_more wasn't set, but more data was available, EMSGSIZE is returned. If a remote ABORT is detected, the abort code received will be stored in ``*_abort`` and ECONNABORTED will be returned. The service ID that the call ended up with is returned into *_service. This can be used to see if a call got a service upgrade.中止调用??
void rxrpc_kernel_abort_call(struct socket *sock, struct rxrpc_call *call, u32 abort_code);用于在调用仍处于可中止状态时中止调用。指定的中止代码将放置在发送的 ABORT 消息中。
拦截接收到的 RxRPC 消息
typedef void (*rxrpc_interceptor_t)(struct sock *sk, unsigned long user_call_ID, struct sk_buff *skb); void rxrpc_kernel_intercept_rx_messages(struct socket *sock, rxrpc_interceptor_t interceptor);这会在指定的 AF_RXRPC 套接字上安装一个拦截器函数。否则最终将进入套接字 Rx 队列的所有消息都将重定向到此函数。请注意,必须小心按正确的顺序处理消息,以保持 DATA 消息的顺序性。
拦截器函数本身提供套接字的地址和处理传入消息,内核实用程序分配给调用的 ID 以及包含消息的套接字缓冲区。
skb->mark 字段指示消息的类型
标记
含义
RXRPC_SKB_MARK_DATA
数据消息
RXRPC_SKB_MARK_FINAL_ACK
为传入的调用接收的最终 ACK
RXRPC_SKB_MARK_BUSY
客户端调用因服务器繁忙而被拒绝
RXRPC_SKB_MARK_REMOTE_ABORT
调用被对等方中止
RXRPC_SKB_MARK_NET_ERROR
检测到网络错误
RXRPC_SKB_MARK_LOCAL_ERROR
遇到本地错误
RXRPC_SKB_MARK_NEW_CALL
等待接受的新传入调用
可以使用 rxrpc_kernel_get_abort_code() 探测远程中止消息。可以使用 rxrpc_kernel_get_error_number() 探测两个错误消息。可以使用 rxrpc_kernel_accept_call() 接受新调用。
可以使用通常的一组套接字缓冲区操作函数提取数据消息的内容。可以使用 rxrpc_kernel_is_data_last() 确定数据消息是否是序列中的最后一条。当数据消息被用完后,应在其上调用 rxrpc_kernel_data_consumed()。
应将消息处理到 rxrpc_kernel_free_skb() 以进行处置。可以获取所有类型消息的额外引用以供以后释放,但这可能会锁定调用的状态,直到最终释放该消息。
接受传入的调用
struct rxrpc_call * rxrpc_kernel_accept_call(struct socket *sock, unsigned long user_call_ID);用于接受传入的调用并为其分配调用 ID。此函数类似于 rxrpc_kernel_begin_call(),并且必须以相同的方式结束接受的调用。
如果此函数成功,则返回对 RxRPC 调用的不透明引用。调用者现在持有对此的引用,并且必须正确结束它。
拒绝传入的调用
int rxrpc_kernel_reject_call(struct socket *sock);用于使用 BUSY 消息拒绝套接字队列中的第一个传入调用。如果没有传入的调用,则返回 -ENODATA。如果调用已中止 (-ECONNABORTED) 或已超时 (-ETIME),则可能会返回其他错误。
分配用于执行匿名安全的空密钥
struct key *rxrpc_get_null_key(const char *keyname);用于分配一个空的 RxRPC 密钥,该密钥可用于指示特定域的匿名安全。
获取调用的对等地址
void rxrpc_kernel_get_peer(struct socket *sock, struct rxrpc_call *call, struct sockaddr_rxrpc *_srx);用于查找调用的远程对等地址。
设置调用上的总传输数据大小
void rxrpc_kernel_set_tx_length(struct socket *sock, struct rxrpc_call *call, s64 tx_total_len);这将设置调用方打算在调用上传输的数据量。它旨在用于设置回复大小,因为应在开始调用时设置请求大小。tx_total_len 不得小于零。
获取调用 RTT
u64 rxrpc_kernel_get_rtt(struct socket *sock, struct rxrpc_call *call);获取调用正在使用的对等方的 RTT 时间。返回的值以纳秒为单位。
检查调用是否仍然有效
bool rxrpc_kernel_check_life(struct socket *sock, struct rxrpc_call *call, u32 *_life); void rxrpc_kernel_probe_life(struct socket *sock, struct rxrpc_call *call);第一个函数在
*_life
中传回一个数字,该数字在收到来自对等方的 ACK 时更新(尤其是包括 PING RESPONSE ACK,我们可以通过发送 PING ACK 来查看服务器上是否仍然存在调用)。调用方应比较两个调用的数字,以查看在等待适当的间隔后调用是否仍然有效。只要调用尚未达到完成状态,它也会返回 true。这允许调用方确定服务器是否仍然可联系,以及在等待服务器处理客户端操作时调用在服务器上是否仍然有效。
第二个函数会导致传输一个 ping ACK 以尝试促使对等方做出响应,然后这将导致第一个函数返回的值发生更改。请注意,必须在 TASK_RUNNING 状态下调用此函数。
获取远程客户端纪元
u32 rxrpc_kernel_get_epoch(struct socket *sock, struct rxrpc_call *call)允许查询传入的客户端调用的数据包中包含的纪元。返回此值。如果调用仍在进行中,则该函数始终成功。一旦调用过期,则不应调用它。请注意,在本地客户端调用上调用此函数只会返回本地纪元。
此值可用于确定远程客户端是否已重新启动,否则不应更改。
设置调用的最大寿命
void rxrpc_kernel_set_max_life(struct socket *sock, struct rxrpc_call *call, unsigned long hard_timeout)将调用的最大寿命设置为 hard_timeout(以 jiffies 为单位)。如果发生超时,则将中止调用并返回 -ETIME 或 -ETIMEDOUT。
从内核内部将 RXRPC_MIN_SECURITY_LEVEL sockopt 应用于套接字
int rxrpc_sock_set_min_security_level(struct sock *sk, unsigned int val);这指定了此套接字调用所需的最低安全级别。
可配置参数¶
RxRPC协议驱动程序有许多可通过 /proc/net/rxrpc/ 中的 sysctl 调整的可配置参数。
req_ack_delay
在接收到设置了请求-确认标志的数据包后,我们才接受该标志并实际发送请求的确认的时间(以毫秒为单位)。
通常,另一方在广告的接收窗口填满(最多 255 个数据包)之前不会停止发送数据包,因此延迟 ACK 允许一次 ACK 多个数据包。
soft_ack_delay
在接收到新数据包后,我们生成软 ACK 以告知发送方无需重新发送的时间(以毫秒为单位)。
idle_ack_delay
在接收队列中的所有数据包都被消耗完后,我们生成硬 ACK 以告知发送方可以释放其缓冲区的时间(以毫秒为单位),假设没有其他原因导致我们发送 ACK。
resend_timeout
在传输数据包后,我们再次传输它之前的时间(以毫秒为单位),假设没有从接收方收到告知他们已收到的 ACK。
max_call_lifetime
在我们将一个调用抢占式终止之前,该调用可能处于进行中的最长时间(以秒为单位)。
dead_call_expiry
在我们将一个已死的调用从调用列表中删除之前的时间(以秒为单位)。已死的调用会保留一小段时间,目的是重复 ACK 和 ABORT 数据包。
connection_expiry
在上次使用连接后,我们将其从连接列表中删除之前的时间(以秒为单位)。当连接存在时,它充当协商安全性的占位符;当它被删除时,必须重新协商安全性。
transport_expiry
在上次使用传输后,我们将其从传输列表中删除之前的时间(以秒为单位)。当传输存在时,它用于锚定对等数据并保留连接 ID 计数器。
rxrpc_rx_window_size
接收窗口的大小(以数据包为单位)。这是我们愿意为任何特定调用在内存中保存的最大未消耗的接收数据包数量。
rxrpc_rx_mtu
我们愿意接收的最大数据包 MTU 大小(以字节为单位)。这向对等方表明我们是否愿意接受巨型数据包。
rxrpc_rx_jumbo_max
我们愿意在一个巨型数据包中接受的最大数据包数量。巨型数据包中的非终端数据包必须包含一个四字节头加上正好 1412 字节的数据。终端数据包必须包含一个四字节头加上任意数量的数据。无论如何,巨型数据包的大小不得超过 rxrpc_rx_mtu。