【RDMA】22. RDMA之基于Socket API的QP间建链--未消化

转自:https://zhuanlan.zhihu.com/p/476407641  作者:Savir

本文欢迎非商业转载及引用,转载及引用请注明出处。

建链,即建立链接(或者连接)。在RDMA技术中,本端和对端的QP之间在建立连接之后才可以通过RDMA协议进行数据交换。我们可以认为建链是正式进行RDMA通信之前所必须进行的准备工作,在建链的过程中,两个节点间会通过某些方式交换一些用于之后的RDMA通信所必须的参数。

本文将向大家介绍RDMA建链的目的,并重点讲解基于Socket API的建链方式,包括建链流程、建链交互的内容等等。最后我们将做一个实验,通过基于libibverbs库编写的示例程序、结合抓包巩固本文所讲解的内容。

为什么QP间需要建链

假设有下面这种简单的场景:

QP间建链示意图

图中的节点A想要在QP2和节点B上的QP5之间建立RC服务类型的连接,这里的”连接“怎么理解?

当用户向某个QP下发WR之后,RDMA网卡会根据WR中的信息组装数据包,那么网卡会如何填写数据包中与目的地有关的信息,即会把这个数据包发给谁呢?网卡一定能够从某处获取目的地的信息。我们在“RDMA之QP”一文中介绍了QPC(Queue Pair Context)的概念,对于RC服务来说,QPC中不仅记录着关于本端QP的信息,还记录着跟这个QP对端QP的信息。而QPC中的对端信息,是通过Modify QP的Verbs接口实现的。

即RC QP之间的“连接”,指的是两个QP在QPC中储存了对端的信息。这个过程通常是在Modify QP的过程中(请参考"Verbs"一文)完成的。

大家需要注意,本文中所描述的QP间“建立连接”,和RC服务类型的“面向连接”不是同一个含义。“建立连接”指的是两端进行必须的协商和准备工作,掌握了彼此的信息,从而能够互相收发信息;而面向连接指的是QP上绑定了一种“一对一”的连接关系,一个QP只能和绑定了的QP相互通信。

所以说,虽然UD服务类型不是面向连接的,但是也需要本文中的“建立连接”的过程才能够与对端通信,毕竟总是要知道对端信息才能够发送消息。UD和RC服务类型的差异在于,对端的信息不是通过“控制路径”固定填入QPC中的,而是用户在“数据路径”中,也就是在Post Send过程中通过WR指定的。

从上面的描述我们可以知道,RDMA网卡是根据WR或者QPC中的信息组装成完整报文的,那么QPC或者WR中的对端信息又从哪里来?必然是从对端来。具体来说,是在RC的Modify QP或者UD的Post Send之前,通过建链流程获取的。

用户接口来区分,RDMA的应用程序常用的建链方式有基于Socket API和基于CM(Communication Manager/Management)两种。我们之所以强调“从用户接口上区分”,是因为CM建链具有一定的特殊性,对于Inifinband/RoCE来说,它是基于IB传输层 + IB规范所描述的CM协议实现的;而对于iWARP来说,它是基于TCP + IETF RFC 5044/6581所描述的流程所实现的,但是这两种实现的上层都使用着同一套用户接口。

其实建链的方式不拘泥于Socket和CM,如果两个节点的控制者在一个办公室,互相喊一嗓子:”我一会要用QP2“、”我一会要用QP5“,也可以达到建链的目的。

基于Socket API的QP建链

概述

Socket一般被翻译成”套接字“,直译为“插头”,是基于TCP/IP协议栈的一个中间层,为用户提供了一组标准编程接口。通过Socket API,用户可以在节点之间通过TCP/IP协议进行数据交互。

基于Socket API进行RDMA QP间的建链,指的就是先在两个节点间通过Socket API建立TCP/IP的连接,然后通过这条连接来交互双方的QP信息。这就要求节点间必须支持TCP/IP协议,那么运行Infiniband/RoCE v1的设备因为不是基于TCP/IP的,所以自然是不能直接使用这种建链方式了(如果两端有其他的通信方式,比如另一对互联的以太网卡,那么也可以用Socket);而iWARP协议的TCP/IP建链流程被封装到了CM API里面,因此只有RoCE v2协议的用户才可以选择直接使用基于Socket API建链方式

建立Socket连接

简单来说,我们的应用程序通过按照一定的流程调用Socket API,就可以实现TCP/IP的三次握手、数据交互和四次挥手的流程。

关于如何建立Socket连接有不少很不错的文章,socket编程到底是什么? - 知乎 (zhihu.com),比如这个问题下的几个回答都写的非常好 ,本文中就不详细展开了。

建立好Socket连接之后还不能通信,因为只是有了通道,但是还没有交换QP间建链所必要的信息。

通过Socket连接交换信息

Socket建链之后,需要开发者自己设计交互的逻辑和内容。交互逻辑其实很简单,无非就是你发我收,我发你收;而用户在决定交互内容上的自由度很高,可以根据具体应用逻辑的来定。

但是,无论应用如何编写,总有一些必须要交换的信息,我们以最常见的两种服务类型为例:

QP间建链所需要交换的信息

我们来逐个解释一下这些内容:

GID

即全局ID,是Infiniband网络层(Network Layer)的地址,用于在跨子网时做路由。在Infiniband协议中,它的作用跟TCP/IP协议中的网络层地址,即IP地址的作用是一样的。

因为目前在市场占据绝对主流的RDMA软件栈——OFED中(见“RDMA之Verbs”一文),Infiniband/RoCE/iWARP共用一套代码,所以即使RoCE v2的网络层为IP协议,但是从RDMA的用户来讲,也使用GID来指定本端和对端的地址。

在RoCE v2的通信中,硬件需要知道要知道对端的地址信息,包括:目的GID(即目的IP)、目的MAC地址以及VLAN等等。这些信息都可以通过用户传入的DGID(Destination GID)获取到,驱动程序会将GID转换为IP地址,然后通过网络侧的邻居表获取MAC地址和VLAN ID。GID和IP地址间的转换其实很简单,我会在以后的文章中再做专门讲解。

综上,我们可以说通过用户指定的目的GID,就可以找到对端的节点。

QPN

我们在“RDMA之QP”一文中曾介绍过,QPN是一个节点本地某个QP的唯一编号。既然RDMA通信的基本单位是QP,自然只通过GID找到对端节点是不够的,还要能够找到指定的QP才能进行数据交换。

如下图所示,通过GID + QPN的组合,我们就能在网络中唯一确定一个QP了:

通过GID + QPN的组合定位目的QP

对于RC服务类型来说,在节点间通过基于Socket的数据交互掌握了彼此的GID + QPN之后,会通过Modify QP的动作将相关信息记录到QPC中。一切就绪之后,就可以进行Send/Recv双边操作了,但是如果想要进行单边的RDMA Write/Read操作,这些信息是还不够的。

VA

它指的是想要通过RDMA技术进行通信的进程的某个虚拟地址,这个地址上注册了MR,并且将会在注册过程中设置远端直接读、写,或者两者兼有的权限。

R_Key

它起到的是在RDMA Write/Read这种单边操作中,校验远端是否对于指定的MR具有读写权限。读者可以看一下“RDMA之MR”一文中对其作用的描述。

通过VA + R_Key来访问远端MR的内存

如上图所示,对于RDMA操作(Write/Read/Atomic),把VA + R_Key两者传给对端,对端就可以对本端的一部分内存进行安全的读写操作了。

Q_Key

Q_Key的概念我们是第一次提及,它是只在UD模式下使用的一种Key。我们知道,UD服务类型的特点就是无连接,我不用建立QP间的连接(区别于本文的建链)可以发送消息给任何QP。

那么就可能有这样一种情况:某个正常执行业务的UD QP的RQ中有一些WQE,其他几个合法的UD QP会时不时的向这个QP发送一些Send消息。正常情况下,这些Send中的Payload会按照顺序被硬件放入RQ WQE中指定的Buffer中,每个Send消息消耗一个RQ WQE。用户也会按照业务代码的节奏,定期的Post Receive WR到RQ中。

但是如果有一个恶意的用户不停的给这个UD QP发送Send消息,如果不加以防备的话,这个UD QP就会不断消耗RQ WQE用于接收这些报文,从而导致无法处理合法的Send(类似于DoS——Denial of Service攻击)。

所以IB规范中定义了Queue Key,本端用户在创建UD之后,会通过Modify QP将指定的Q_Key写入到QPC中,然后通过建链将Q_Key传递给对方。对端用户在WR中携带对端UD QP的Q_Key,硬件解析WQE时会将其中的Q_Key放到报文中。这样本端的UD QP在接收报文时,就可以把这个Q_Key和QPC中的Q_Key做校验了,如果校验不通过就会默认丢弃这个报文。

Q_Key的作用

除此之外Q_Key还有一些特殊的用法,为了避免引入与本文无关的内容,以后再单独讲吧。

上面只列举常见的服务类型和操作类型下所必须的信息。大家编写的应用逻辑不同,建链要交换的内容也会有差异,比如我们可以把Packet Sequence Number、Path MTU、甚至是服务类型和操作类型也加到Socket API建链交换的内容中。更完整的一个版本可以参考RDMAmojo上面的这篇文章,但也只是作为参考即可。

需要注意的是,上述这些信息不是在流程的一开始就可以交换的。比如QPN是在Create QP之后产生的,而R_Key是在注册MR之后产生的,所以这些信息必须要在对应的Verbs成功返回之后才能交换。具体时机没有硬性要求,可以根据程序的逻辑而定。

通过Socket连接做同步

Socket连接不是在交换完QP建链所需的信息之后就没用了,我们还可以利用它来做两端的同步,因为双方在程序流程上可能会有相互依赖。

比如Send/Recv操作中,如果收端的程序还没有Post Receive WR的情况下,发端就通过下发Post Send WR触发硬件发送了报文,这时就会发生RNR(Receive Not Ready)错误,即接收端还没有准备好接收报文。

RNR错误

因此我们需要确保在接收端下发RQ WR前,发送端不会发送报文过来。这一过程如下图所示,如果Client端等不到同步消息,就不会触发Post Send流程。

Post Send前等待同步

程序中可能还有一些时间点需要做同步,这取决于具体的逻辑。我们也可以通过合理安排交换QP建链信息的时机来实现同步,比如下文实验中的例子,就只通过一次交互实现了信息的交换和流程的同步。

实验

为了让大家有一个更直观更具体的认识,下面我们通过例子来总结一下本文的内容。

准备工作

前面的文章中我们做抓包实验的时候大多数用的是perftest,但是对于本文的内容来说,其逻辑有些过于复杂了。我们在“RDMA之Verbs”一文中曾经介绍过libibverbs,也就是Verbs的用户态库,其中包含了几个基于C语言的简单示例程序,下面就用它们做实验。

安装完毕后,我们将能够执行ibv_rc_pingpong和ibv_ud_pingpong这两个程序,其源码位于rdma-core仓库的libibverbs/examples下面。它们整体流程非常简单,感兴趣的读者可以通过阅读源码获得更深入的了解。

顾名思义,这两个程序分别执行的是基于RC和UD服务类型的双端测试,操作类型都是Send。需要注意它们通过Socket API交换的内容和本文有所差异,比如rc_pingpong中交换了LID(Local Identifier),这个是IB链路层的地址,我们RoCE v2是不需要的,值为0;又比如ud_pingpong.c中,Server端(接收方)QPC中的Q_Key值和Client端(发送方)的WQE中的Q_Key值是固定写为0x11111111,而不是通过建链交换得到的。

实验环境和“RDMA之RoCE & Soft-RoCE”一文中的相同,读者可以参考这篇文章进行配置。

实验拓扑示意图

执行

假设大家已经配置好虚拟机的网卡以及Soft RoCE设备,Server端执行:

ibv_rc_pingpong -d rxe_0 -g 2

我的Server端的IP为192.168.217.128,在Client端执行:

ibv_rc_pingpong -d rxe_0 -g 2 192.168.217.128

命令中-d后面接的是RDMA设备名称,我们来看一下-g参数指的是什么:

ibv_rc_pingpong --help

ibv_rc_pingpong的帮助信息

帮助信息中解释的是“本地端口的GID索引”。前文中我们介绍过,目的GID(即IP)是通过建链交换的,但是没有解释源GID(IP)是哪来的。设备的一个端口上可能有多个可以用于通信的GID,这些在本地是有一张表来储存的,我们称为GID表。对于RoCE v2来说,RDMA框架会根据网口设备的每一个IP地址自动生成对应的GID放到表里。

ibv_devinfo -d rxe_0 -v

上述命令会打印RDMA设备的相关信息,其中在最后会打印出各个端口的有效GID,可以看到,这个Soft-RoCE设备有一个端口,它共有三个GID:

Soft-RoCE设备的GID

对比网口设备的信息,很容易看出来这几个GID是如何生成的:

RXE绑定的网络设备的地址信息

更多关于GID的内容,我们会在以后专门的文章中介绍,此处不展开讲了。总之,我们指定了两端都使用index为2的GID作为RDMA通信的源地址 ,这个地址是IPv6生成的,故报文的网络层也会基于IPv6。

执行之后的结果如下图所示:

实验执行结果

结果中包含了建链交换的两端信息(LID、QPN、PSN和GID),以及带宽和时延。

报文分析

我们依然使用Wireshark来进行报文分析,取最前面的一系列报文,它们可以被鲜明的分成下图中的四个部分:

实验的最前一部分报文

其中TCP的握手和挥手是标准行为,不予关注。我们来看一下Socket交换信息的部分。

TCP/IP

展开34号报文,可以看到它是Client端(192.168.217.130)发给Server端(192.168.217.128)的IPv4报文:

Client发给Server的建链信息

其TCP层Payload的内容是:

0000:000011:46d9ab:fe800000000000004b92470ab6f183

其被冒号分成了四个部分,正好对应上面执行结果截图中的LID、QPN、PSN和GID。所以这个报文的目的是Client端把自己建链的信息发给了Server端。

而Server端在回复了ACK之后,于36号报文发出了自己的信息:

Server发给Client的建链信息

Client端在38号回复了”done”。

Client端回复的完成信息

之后的39-42号报文,双方就断开了TCP连接,说明这个程序在交换完上述信息后没有再用Socket API交换信息,执行的都是RDMA业务了。

RoCE v2

后面就都是RoCE v2的报文了,我们看Client端发给Server端的第一个Send报文,可以看到IP层的版本是v6,这个是因为我们指定的GID是IPv6生成的。如果我们使用index为1,也就是IPv4生成的GID,那么这里就会是v4。

RoCE v2报文中的对端信息

我之所以指定IPv6的GID来跑这个例子,是想告诉读者,执行ibv_rc_pingpong时Client端指定的IP地址,指的是Socket建链使用的IP地址,而不是用于RoCE v2报文的IP地址,这两者是可以不同的。甚至执行程序时指定的IP,可以是另一个网卡的IP,只要通路没问题都可以(所以我们前文才说Infiniband设备也可能通过其他网卡提供的TCP/IP服务进行Socket建链)。

另外,我们可以看到Destination Queue Pair域段,也就是目的QPN,是0x11,而PSN是4643243,转换成16进制是0x46D9AB。也就是说,通过Socket API交换的信息,体现到了报文中。因为本专栏中还没有讲解RoCE v2的报文,其他域段就不展开说了。

总结一下,上面的整个流程如下图所示。通过Modify QP将对端信息写入QPC,以及下发WR等动作,是在TCP/IP部分和RoCE v2部分的交界处完成的。

ibv_ud_pingpong的流程类似,就不展开讲了,感兴趣的读者可以自行验证。

好了,Socket建链就介绍到这里,下一篇计划给大家介绍一下CM建链。

参考文档

  1. Connecting Queue Pairs - RDMAmojo RDMAmojo

posted on 2022-10-04 01:21  bdy  阅读(137)  评论(0编辑  收藏  举报

导航