← Documents Documentation/networking/rds.rst GitHub 원문 ↗

Linux 6.18.37 · Networking

RDS(Reliable Datagram Sockets)

Reliable ordered datagram의 socket API, protocol header, congestion·connection 처리와 multipath RDS 구조입니다.

Source pathDocumentation/networking/rds.rst
Source versionLinux v6.18.37
TranslationDUJINLABS 전문 번역 + 해설

요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.

1. 요약·해설

원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.

요약·해설

rds.rst:1-448

RDS는 cluster node pair 사이 reliable connection을 공유하면서 application에 ordered datagram socket을 제공합니다. General RDS layer가 BSD socket API, ACK·congestion·connection state를 맡고 IB 또는 TCP transport가 실제 전송을 수행합니다. Multipath RDS는 `rds_conn_path`별 TCP flow와 사전 path-count 협상으로 단일 flow bandwidth와 head-of-line blocking 한계를 줄입니다.

RDS 전체 경로
PF_RDS socketrds_sendmsgGeneral RDS connection·congestionIB 또는 TCP transportRemote RDS port
mprdsRDS_EXTHDR_NPATHS 협상여러 rds_conn_pathHash 기반 path 선택

Application datagram이 공통 계층과 선택한 transport를 거칩니다.

2. 영어 원문 전체

번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.

원문 전체 펼치기
1 .. SPDX-License-Identifier: GPL-2.0
2
3 ===
4 RDS
5 ===
6
7 Overview
8 ========
9
10 This readme tries to provide some background on the hows and whys of RDS,
11 and will hopefully help you find your way around the code.
12
13 In addition, please see this email about RDS origins:
14 http://oss.oracle.com/pipermail/rds-devel/2007-November/000228.html
15
16 RDS Architecture
17 ================
18
19 RDS provides reliable, ordered datagram delivery by using a single
20 reliable connection between any two nodes in the cluster. This allows
21 applications to use a single socket to talk to any other process in the
22 cluster - so in a cluster with N processes you need N sockets, in contrast
23 to N*N if you use a connection-oriented socket transport like TCP.
24
25 RDS is not Infiniband-specific; it was designed to support different
26 transports. The current implementation used to support RDS over TCP as well
27 as IB.
28
29 The high-level semantics of RDS from the application's point of view are
30
31 * Addressing
32
33 RDS uses IPv4 addresses and 16bit port numbers to identify
34 the end point of a connection. All socket operations that involve
35 passing addresses between kernel and user space generally
36 use a struct sockaddr_in.
37
38 The fact that IPv4 addresses are used does not mean the underlying
39 transport has to be IP-based. In fact, RDS over IB uses a
40 reliable IB connection; the IP address is used exclusively to
41 locate the remote node's GID (by ARPing for the given IP).
42
43 The port space is entirely independent of UDP, TCP or any other
44 protocol.
45
46 * Socket interface
47
48 RDS sockets work *mostly* as you would expect from a BSD
49 socket. The next section will cover the details. At any rate,
50 all I/O is performed through the standard BSD socket API.
51 Some additions like zerocopy support are implemented through
52 control messages, while other extensions use the getsockopt/
53 setsockopt calls.
54
55 Sockets must be bound before you can send or receive data.
56 This is needed because binding also selects a transport and
57 attaches it to the socket. Once bound, the transport assignment
58 does not change. RDS will tolerate IPs moving around (eg in
59 a active-active HA scenario), but only as long as the address
60 doesn't move to a different transport.
61
62 * sysctls
63
64 RDS supports a number of sysctls in /proc/sys/net/rds
65
66
67 Socket Interface
68 ================
69
70 AF_RDS, PF_RDS, SOL_RDS
71 AF_RDS and PF_RDS are the domain type to be used with socket(2)
72 to create RDS sockets. SOL_RDS is the socket-level to be used
73 with setsockopt(2) and getsockopt(2) for RDS specific socket
74 options.
75
76 fd = socket(PF_RDS, SOCK_SEQPACKET, 0);
77 This creates a new, unbound RDS socket.
78
79 setsockopt(SOL_SOCKET): send and receive buffer size
80 RDS honors the send and receive buffer size socket options.
81 You are not allowed to queue more than SO_SNDSIZE bytes to
82 a socket. A message is queued when sendmsg is called, and
83 it leaves the queue when the remote system acknowledges
84 its arrival.
85
86 The SO_RCVSIZE option controls the maximum receive queue length.
87 This is a soft limit rather than a hard limit - RDS will
88 continue to accept and queue incoming messages, even if that
89 takes the queue length over the limit. However, it will also
90 mark the port as "congested" and send a congestion update to
91 the source node. The source node is supposed to throttle any
92 processes sending to this congested port.
93
94 bind(fd, &sockaddr_in, ...)
95 This binds the socket to a local IP address and port, and a
96 transport, if one has not already been selected via the
97 SO_RDS_TRANSPORT socket option
98
99 sendmsg(fd, ...)
100 Sends a message to the indicated recipient. The kernel will
101 transparently establish the underlying reliable connection
102 if it isn't up yet.
103
104 An attempt to send a message that exceeds SO_SNDSIZE will
105 return with -EMSGSIZE
106
107 An attempt to send a message that would take the total number
108 of queued bytes over the SO_SNDSIZE threshold will return
109 EAGAIN.
110
111 An attempt to send a message to a destination that is marked
112 as "congested" will return ENOBUFS.
113
114 recvmsg(fd, ...)
115 Receives a message that was queued to this socket. The sockets
116 recv queue accounting is adjusted, and if the queue length
117 drops below SO_SNDSIZE, the port is marked uncongested, and
118 a congestion update is sent to all peers.
119
120 Applications can ask the RDS kernel module to receive
121 notifications via control messages (for instance, there is a
122 notification when a congestion update arrived, or when a RDMA
123 operation completes). These notifications are received through
124 the msg.msg_control buffer of struct msghdr. The format of the
125 messages is described in manpages.
126
127 poll(fd)
128 RDS supports the poll interface to allow the application
129 to implement async I/O.
130
131 POLLIN handling is pretty straightforward. When there's an
132 incoming message queued to the socket, or a pending notification,
133 we signal POLLIN.
134
135 POLLOUT is a little harder. Since you can essentially send
136 to any destination, RDS will always signal POLLOUT as long as
137 there's room on the send queue (ie the number of bytes queued
138 is less than the sendbuf size).
139
140 However, the kernel will refuse to accept messages to
141 a destination marked congested - in this case you will loop
142 forever if you rely on poll to tell you what to do.
143 This isn't a trivial problem, but applications can deal with
144 this - by using congestion notifications, and by checking for
145 ENOBUFS errors returned by sendmsg.
146
147 setsockopt(SOL_RDS, RDS_CANCEL_SENT_TO, &sockaddr_in)
148 This allows the application to discard all messages queued to a
149 specific destination on this particular socket.
150
151 This allows the application to cancel outstanding messages if
152 it detects a timeout. For instance, if it tried to send a message,
153 and the remote host is unreachable, RDS will keep trying forever.
154 The application may decide it's not worth it, and cancel the
155 operation. In this case, it would use RDS_CANCEL_SENT_TO to
156 nuke any pending messages.
157
158 ``setsockopt(fd, SOL_RDS, SO_RDS_TRANSPORT, (int *)&transport ..), getsockopt(fd, SOL_RDS, SO_RDS_TRANSPORT, (int *)&transport ..)``
159 Set or read an integer defining the underlying
160 encapsulating transport to be used for RDS packets on the
161 socket. When setting the option, integer argument may be
162 one of RDS_TRANS_TCP or RDS_TRANS_IB. When retrieving the
163 value, RDS_TRANS_NONE will be returned on an unbound socket.
164 This socket option may only be set exactly once on the socket,
165 prior to binding it via the bind(2) system call. Attempts to
166 set SO_RDS_TRANSPORT on a socket for which the transport has
167 been previously attached explicitly (by SO_RDS_TRANSPORT) or
168 implicitly (via bind(2)) will return an error of EOPNOTSUPP.
169 An attempt to set SO_RDS_TRANSPORT to RDS_TRANS_NONE will
170 always return EINVAL.
171
172 RDMA for RDS
173 ============
174
175 see rds-rdma(7) manpage (available in rds-tools)
176
177
178 Congestion Notifications
179 ========================
180
181 see rds(7) manpage
182
183
184 RDS Protocol
185 ============
186
187 Message header
188
189 The message header is a 'struct rds_header' (see rds.h):
190
191 Fields:
192
193 h_sequence:
194 per-packet sequence number
195 h_ack:
196 piggybacked acknowledgment of last packet received
197 h_len:
198 length of data, not including header
199 h_sport:
200 source port
201 h_dport:
202 destination port
203 h_flags:
204 Can be:
205
206 ============= ==================================
207 CONG_BITMAP this is a congestion update bitmap
208 ACK_REQUIRED receiver must ack this packet
209 RETRANSMITTED packet has previously been sent
210 ============= ==================================
211
212 h_credit:
213 indicate to other end of connection that
214 it has more credits available (i.e. there is
215 more send room)
216 h_padding[4]:
217 unused, for future use
218 h_csum:
219 header checksum
220 h_exthdr:
221 optional data can be passed here. This is currently used for
222 passing RDMA-related information.
223
224 ACK and retransmit handling
225
226 One might think that with reliable IB connections you wouldn't need
227 to ack messages that have been received. The problem is that IB
228 hardware generates an ack message before it has DMAed the message
229 into memory. This creates a potential message loss if the HCA is
230 disabled for any reason between when it sends the ack and before
231 the message is DMAed and processed. This is only a potential issue
232 if another HCA is available for fail-over.
233
234 Sending an ack immediately would allow the sender to free the sent
235 message from their send queue quickly, but could cause excessive
236 traffic to be used for acks. RDS piggybacks acks on sent data
237 packets. Ack-only packets are reduced by only allowing one to be
238 in flight at a time, and by the sender only asking for acks when
239 its send buffers start to fill up. All retransmissions are also
240 acked.
241
242 Flow Control
243
244 RDS's IB transport uses a credit-based mechanism to verify that
245 there is space in the peer's receive buffers for more data. This
246 eliminates the need for hardware retries on the connection.
247
248 Congestion
249
250 Messages waiting in the receive queue on the receiving socket
251 are accounted against the sockets SO_RCVBUF option value. Only
252 the payload bytes in the message are accounted for. If the
253 number of bytes queued equals or exceeds rcvbuf then the socket
254 is congested. All sends attempted to this socket's address
255 should return block or return -EWOULDBLOCK.
256
257 Applications are expected to be reasonably tuned such that this
258 situation very rarely occurs. An application encountering this
259 "back-pressure" is considered a bug.
260
261 This is implemented by having each node maintain bitmaps which
262 indicate which ports on bound addresses are congested. As the
263 bitmap changes it is sent through all the connections which
264 terminate in the local address of the bitmap which changed.
265
266 The bitmaps are allocated as connections are brought up. This
267 avoids allocation in the interrupt handling path which queues
268 messages on sockets. The dense bitmaps let transports send the
269 entire bitmap on any bitmap change reasonably efficiently. This
270 is much easier to implement than some finer-grained
271 communication of per-port congestion. The sender does a very
272 inexpensive bit test to test if the port it's about to send to
273 is congested or not.
274
275
276 RDS Transport Layer
277 ===================
278
279 As mentioned above, RDS is not IB-specific. Its code is divided
280 into a general RDS layer and a transport layer.
281
282 The general layer handles the socket API, congestion handling,
283 loopback, stats, usermem pinning, and the connection state machine.
284
285 The transport layer handles the details of the transport. The IB
286 transport, for example, handles all the queue pairs, work requests,
287 CM event handlers, and other Infiniband details.
288
289
290 RDS Kernel Structures
291 =====================
292
293 struct rds_message
294 aka possibly "rds_outgoing", the generic RDS layer copies data to
295 be sent and sets header fields as needed, based on the socket API.
296 This is then queued for the individual connection and sent by the
297 connection's transport.
298
299 struct rds_incoming
300 a generic struct referring to incoming data that can be handed from
301 the transport to the general code and queued by the general code
302 while the socket is awoken. It is then passed back to the transport
303 code to handle the actual copy-to-user.
304
305 struct rds_socket
306 per-socket information
307
308 struct rds_connection
309 per-connection information
310
311 struct rds_transport
312 pointers to transport-specific functions
313
314 struct rds_statistics
315 non-transport-specific statistics
316
317 struct rds_cong_map
318 wraps the raw congestion bitmap, contains rbnode, waitq, etc.
319
320 Connection management
321 =====================
322
323 Connections may be in UP, DOWN, CONNECTING, DISCONNECTING, and
324 ERROR states.
325
326 The first time an attempt is made by an RDS socket to send data to
327 a node, a connection is allocated and connected. That connection is
328 then maintained forever -- if there are transport errors, the
329 connection will be dropped and re-established.
330
331 Dropping a connection while packets are queued will cause queued or
332 partially-sent datagrams to be retransmitted when the connection is
333 re-established.
334
335
336 The send path
337 =============
338
339 rds_sendmsg()
340 - struct rds_message built from incoming data
341 - CMSGs parsed (e.g. RDMA ops)
342 - transport connection allocated and connected if not already
343 - rds_message placed on send queue
344 - send worker awoken
345
346 rds_send_worker()
347 - calls rds_send_xmit() until queue is empty
348
349 rds_send_xmit()
350 - transmits congestion map if one is pending
351 - may set ACK_REQUIRED
352 - calls transport to send either non-RDMA or RDMA message
353 (RDMA ops never retransmitted)
354
355 rds_ib_xmit()
356 - allocs work requests from send ring
357 - adds any new send credits available to peer (h_credits)
358 - maps the rds_message's sg list
359 - piggybacks ack
360 - populates work requests
361 - post send to connection's queue pair
362
363 The recv path
364 =============
365
366 rds_ib_recv_cq_comp_handler()
367 - looks at write completions
368 - unmaps recv buffer from device
369 - no errors, call rds_ib_process_recv()
370 - refill recv ring
371
372 rds_ib_process_recv()
373 - validate header checksum
374 - copy header to rds_ib_incoming struct if start of a new datagram
375 - add to ibinc's fraglist
376 - if completed datagram:
377 - update cong map if datagram was cong update
378 - call rds_recv_incoming() otherwise
379 - note if ack is required
380
381 rds_recv_incoming()
382 - drop duplicate packets
383 - respond to pings
384 - find the sock associated with this datagram
385 - add to sock queue
386 - wake up sock
387 - do some congestion calculations
388 rds_recvmsg
389 - copy data into user iovec
390 - handle CMSGs
391 - return to application
392
393 Multipath RDS (mprds)
394 =====================
395 Mprds is multipathed-RDS, primarily intended for RDS-over-TCP
396 (though the concept can be extended to other transports). The classical
397 implementation of RDS-over-TCP is implemented by demultiplexing multiple
398 PF_RDS sockets between any 2 endpoints (where endpoint == [IP address,
399 port]) over a single TCP socket between the 2 IP addresses involved. This
400 has the limitation that it ends up funneling multiple RDS flows over a
401 single TCP flow, thus it is
402 (a) upper-bounded to the single-flow bandwidth,
403 (b) suffers from head-of-line blocking for all the RDS sockets.
404
405 Better throughput (for a fixed small packet size, MTU) can be achieved
406 by having multiple TCP/IP flows per rds/tcp connection, i.e., multipathed
407 RDS (mprds). Each such TCP/IP flow constitutes a path for the rds/tcp
408 connection. RDS sockets will be attached to a path based on some hash
409 (e.g., of local address and RDS port number) and packets for that RDS
410 socket will be sent over the attached path using TCP to segment/reassemble
411 RDS datagrams on that path.
412
413 Multipathed RDS is implemented by splitting the struct rds_connection into
414 a common (to all paths) part, and a per-path struct rds_conn_path. All
415 I/O workqs and reconnect threads are driven from the rds_conn_path.
416 Transports such as TCP that are multipath capable may then set up a
417 TCP socket per rds_conn_path, and this is managed by the transport via
418 the transport private cp_transport_data pointer.
419
420 Transports announce themselves as multipath capable by setting the
421 t_mp_capable bit during registration with the rds core module. When the
422 transport is multipath-capable, rds_sendmsg() hashes outgoing traffic
423 across multiple paths. The outgoing hash is computed based on the
424 local address and port that the PF_RDS socket is bound to.
425
426 Additionally, even if the transport is MP capable, we may be
427 peering with some node that does not support mprds, or supports
428 a different number of paths. As a result, the peering nodes need
429 to agree on the number of paths to be used for the connection.
430 This is done by sending out a control packet exchange before the
431 first data packet. The control packet exchange must have completed
432 prior to outgoing hash completion in rds_sendmsg() when the transport
433 is multipath capable.
434
435 The control packet is an RDS ping packet (i.e., packet to rds dest
436 port 0) with the ping packet having a rds extension header option of
437 type RDS_EXTHDR_NPATHS, length 2 bytes, and the value is the
438 number of paths supported by the sender. The "probe" ping packet will
439 get sent from some reserved port, RDS_FLAG_PROBE_PORT (in <linux/rds.h>)
440 The receiver of a ping from RDS_FLAG_PROBE_PORT will thus immediately
441 be able to compute the min(sender_paths, rcvr_paths). The pong
442 sent in response to a probe-ping should contain the rcvr's npaths
443 when the rcvr is mprds-capable.
444
445 If the rcvr is not mprds-capable, the exthdr in the ping will be
446 ignored. In this case the pong will not have any exthdrs, so the sender
447 of the probe-ping can default to single-path mprds.
448
449

3. 한국어 전문 번역

영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.

RDS architecture와 application semantics

1-66

이 문서는 RDS가 어떤 이유로 어떤 방식으로 설계되었는지 배경을 제공하고 kernel code를 탐색할 출발점을 제시합니다. RDS의 기원은 원문에 연결된 2007년 `rds-devel` email에서도 확인할 수 있습니다.

RDS는 cluster의 node 두 개 사이에 신뢰할 수 있는 connection 하나를 유지하면서 application에는 reliable하고 ordered인 datagram delivery를 제공합니다. Application process 하나는 socket 하나로 cluster의 모든 다른 process와 통신할 수 있습니다. Process가 N개라면 socket도 N개면 되며, TCP 같은 connection-oriented socket transport에서 endpoint pair마다 필요할 수 있는 N*N개와 대비됩니다.

RDS는 Infiniband 전용 protocol이 아니며 여러 transport를 지원하도록 계층화되었습니다. 구현은 RDS over TCP와 RDS over IB를 지원해 왔습니다. Application endpoint는 IPv4 address와 독립적인 16-bit RDS port로 식별하고 kernel과 userspace 사이 주소 전달에는 보통 `struct sockaddr_in`을 사용합니다.

IPv4 address를 쓴다고 underlying transport가 IP 기반이어야 하는 것은 아닙니다. RDS over IB는 reliable IB connection을 사용하고 IP address는 ARP를 통해 remote node의 GID를 찾는 데만 씁니다. RDS port namespace는 UDP, TCP 및 다른 protocol의 port 공간과 완전히 독립적입니다.

RDS socket I/O는 대체로 BSD socket과 같고 표준 API로 수행합니다. Zerocopy 같은 추가 기능은 control message로, 다른 extension은 `getsockopt()`와 `setsockopt()`로 제공합니다. 송수신 전에 반드시 `bind()`해야 하는 이유는 local address뿐 아니라 transport도 선택해 socket에 고정하기 때문입니다. Bind 후 transport는 바뀌지 않으며 IP 이동은 같은 transport 안에서만 허용됩니다. Active-active HA에서 주소가 옮겨가더라도 다른 transport로 넘어가면 안 됩니다.

RDS 관련 sysctl은 `/proc/sys/net/rds` 아래에 있습니다.

RDS cluster socket model
Process A의 PF_RDS socketNode A↔Node B reliable connectionProcess B의 RDS port
Process A의 같은 socketNode A↔Node C reliable connectionProcess C의 RDS port

N개 process가 endpoint pair별 socket 없이 공통 RDS connection을 공유합니다.

RDS addressing
항목RDS 의미
IPv4 addressRemote node 식별; IB에서는 GID 탐색용
16-bit portUDP/TCP와 독립적인 RDS port space
sockaddr_inKernel↔userspace 주소 전달
Transportbind 시 선택되어 socket에 고정

Application 주소와 실제 transport 식별을 구분합니다.

.. SPDX-License-Identifier: GPL-2.0

===
RDS
===

Overview
========

This readme tries to provide some background on the hows and whys of RDS,
and will hopefully help you find your way around the code.

In addition, please see this email about RDS origins:
http://oss.oracle.com/pipermail/rds-devel/2007-November/000228.html

RDS Architecture
================

RDS provides reliable, ordered datagram delivery by using a single
reliable connection between any two nodes in the cluster. This allows
applications to use a single socket to talk to any other process in the
cluster - so in a cluster with N processes you need N sockets, in contrast
to N*N if you use a connection-oriented socket transport like TCP.

RDS is not Infiniband-specific; it was designed to support different
transports.  The current implementation used to support RDS over TCP as well
as IB.

The high-level semantics of RDS from the application's point of view are

 *        Addressing

        RDS uses IPv4 addresses and 16bit port numbers to identify
        the end point of a connection. All socket operations that involve
        passing addresses between kernel and user space generally
        use a struct sockaddr_in.

        The fact that IPv4 addresses are used does not mean the underlying
        transport has to be IP-based. In fact, RDS over IB uses a
        reliable IB connection; the IP address is used exclusively to
        locate the remote node's GID (by ARPing for the given IP).

        The port space is entirely independent of UDP, TCP or any other
        protocol.

 *        Socket interface

        RDS sockets work *mostly* as you would expect from a BSD
        socket. The next section will cover the details. At any rate,
        all I/O is performed through the standard BSD socket API.
        Some additions like zerocopy support are implemented through
        control messages, while other extensions use the getsockopt/
        setsockopt calls.

        Sockets must be bound before you can send or receive data.
        This is needed because binding also selects a transport and
        attaches it to the socket. Once bound, the transport assignment
        does not change. RDS will tolerate IPs moving around (eg in
        a active-active HA scenario), but only as long as the address
        doesn't move to a different transport.

 *        sysctls

        RDS supports a number of sysctls in /proc/sys/net/rds

Socket 생성, buffer, bind와 send·receive

67-126

RDS socket domain은 `AF_RDS` 또는 `PF_RDS`이고 RDS 전용 socket option level은 `SOL_RDS`입니다. `socket(PF_RDS, SOCK_SEQPACKET, 0)`은 아직 bind되지 않은 RDS socket을 만듭니다.

RDS는 `SOL_SOCKET`의 send·receive buffer 크기 option을 따릅니다. `SO_SNDSIZE`보다 많은 byte를 socket send queue에 둘 수 없습니다. `sendmsg()`가 호출되면 message가 queue에 들어가고 remote system이 도착을 acknowledge할 때 queue에서 제거됩니다.

`SO_RCVSIZE`는 최대 receive queue length를 제어하지만 hard limit가 아닌 soft limit입니다. Queue가 limit를 넘어도 incoming message를 계속 받아 쌓는 대신 해당 port를 `congested`로 표시하고 source node에 congestion update를 보냅니다. Source node는 이 port로 보내는 process를 throttle해야 합니다.

`bind(fd, &sockaddr_in, ...)`는 local IP address와 port에 socket을 묶고, `SO_RDS_TRANSPORT`로 미리 선택하지 않았다면 transport도 선택합니다. `sendmsg()`는 지정한 recipient로 message를 보내며 underlying reliable connection이 아직 없으면 kernel이 투명하게 설정합니다.

Message 하나가 `SO_SNDSIZE`를 넘으면 `-EMSGSIZE`를 반환합니다. Message를 추가했을 때 전체 queued byte가 `SO_SNDSIZE` threshold를 넘으면 `EAGAIN`, destination이 congested로 표시되어 있으면 `ENOBUFS`를 반환합니다.

`recvmsg()`는 socket receive queue의 message를 받고 queue accounting을 갱신합니다. 원문은 queue length가 `SO_SNDSIZE` 아래로 내려가면 port를 uncongested로 표시하고 모든 peer에 update를 보낸다고 설명합니다. Application은 `struct msghdr`의 `msg.msg_control` buffer에서 congestion update 도착이나 RDMA operation 완료 같은 kernel notification도 control message로 받을 수 있으며 정확한 형식은 manpage에 정의됩니다.

RDS sendmsg 결과
조건결과
단일 message > SO_SNDSIZE-EMSGSIZE
추가 후 queued bytes > SO_SNDSIZEEAGAIN
Destination port congestedENOBUFS
Connection 없음Kernel이 연결한 뒤 전송

크기, queue와 congestion 조건별 결과입니다.

RDS receive back-pressure
Receive queue 증가SO_RCVSIZE 도달·초과Port congested 표시Peer에 congestion updateSender throttle

Soft receive limit가 sender throttling으로 이어집니다.

Socket Interface
================

  AF_RDS, PF_RDS, SOL_RDS
        AF_RDS and PF_RDS are the domain type to be used with socket(2)
        to create RDS sockets. SOL_RDS is the socket-level to be used
        with setsockopt(2) and getsockopt(2) for RDS specific socket
        options.

  fd = socket(PF_RDS, SOCK_SEQPACKET, 0);
        This creates a new, unbound RDS socket.

  setsockopt(SOL_SOCKET): send and receive buffer size
        RDS honors the send and receive buffer size socket options.
        You are not allowed to queue more than SO_SNDSIZE bytes to
        a socket. A message is queued when sendmsg is called, and
        it leaves the queue when the remote system acknowledges
        its arrival.

        The SO_RCVSIZE option controls the maximum receive queue length.
        This is a soft limit rather than a hard limit - RDS will
        continue to accept and queue incoming messages, even if that
        takes the queue length over the limit. However, it will also
        mark the port as "congested" and send a congestion update to
        the source node. The source node is supposed to throttle any
        processes sending to this congested port.

  bind(fd, &sockaddr_in, ...)
        This binds the socket to a local IP address and port, and a
        transport, if one has not already been selected via the
        SO_RDS_TRANSPORT socket option

  sendmsg(fd, ...)
        Sends a message to the indicated recipient. The kernel will
        transparently establish the underlying reliable connection
        if it isn't up yet.

        An attempt to send a message that exceeds SO_SNDSIZE will
        return with -EMSGSIZE

        An attempt to send a message that would take the total number
        of queued bytes over the SO_SNDSIZE threshold will return
        EAGAIN.

        An attempt to send a message to a destination that is marked
        as "congested" will return ENOBUFS.

  recvmsg(fd, ...)
        Receives a message that was queued to this socket. The sockets
        recv queue accounting is adjusted, and if the queue length
        drops below SO_SNDSIZE, the port is marked uncongested, and
        a congestion update is sent to all peers.

        Applications can ask the RDS kernel module to receive
        notifications via control messages (for instance, there is a
        notification when a congestion update arrived, or when a RDMA
        operation completes). These notifications are received through
        the msg.msg_control buffer of struct msghdr. The format of the
        messages is described in manpages.

poll, 전송 취소와 transport 선택

127-171

RDS는 asynchronous I/O를 위한 `poll()`을 지원합니다. Incoming message가 socket에 queue되어 있거나 pending notification이 있으면 `POLLIN`을 알립니다.

`POLLOUT`은 destination별 congestion 때문에 더 복잡합니다. RDS socket은 어느 destination으로든 보낼 수 있으므로 send queue에 여유가 있으면 항상 `POLLOUT`을 알립니다. 그러나 선택한 destination이 congested이면 kernel은 message를 거부합니다. 따라서 `poll()` 결과만 의존하면 계속 writable로 깨어난 뒤 `sendmsg()`가 실패하는 loop에 빠질 수 있습니다. Application은 congestion notification을 사용하고 `sendmsg()`의 `ENOBUFS`도 반드시 처리해야 합니다.

`setsockopt(SOL_RDS, RDS_CANCEL_SENT_TO, &sockaddr_in)`는 이 socket에서 특정 destination으로 queue한 모든 message를 폐기합니다. Remote host가 unreachable인 경우 RDS는 기본적으로 계속 재시도하므로 application timeout 정책상 더 기다릴 가치가 없을 때 outstanding message를 취소하는 용도입니다.

`SO_RDS_TRANSPORT`는 RDS packet을 감쌀 underlying transport를 integer로 설정하거나 읽습니다. 설정값은 `RDS_TRANS_TCP` 또는 `RDS_TRANS_IB`이고, 아직 bind되지 않은 socket을 조회하면 `RDS_TRANS_NONE`을 돌려줍니다. 이 option은 `bind(2)` 전에 정확히 한 번만 설정할 수 있습니다.

Transport가 이미 `SO_RDS_TRANSPORT`로 명시적으로 붙었거나 `bind(2)`로 암묵적으로 붙은 socket에서 다시 설정하면 `EOPNOTSUPP`입니다. `RDS_TRANS_NONE`으로 설정하려는 시도는 항상 `EINVAL`입니다.

RDS poll·option 주의점
Interface동작·오류
POLLINMessage 또는 notification 대기
POLLOUTSend queue 여유만 반영; destination congestion은 별도
RDS_CANCEL_SENT_TO특정 destination pending message 전부 취소
SO_RDS_TRANSPORT 재설정EOPNOTSUPP
RDS_TRANS_NONE 설정EINVAL

비동기 처리와 transport 고정 규칙입니다.

  poll(fd)
        RDS supports the poll interface to allow the application
        to implement async I/O.

        POLLIN handling is pretty straightforward. When there's an
        incoming message queued to the socket, or a pending notification,
        we signal POLLIN.

        POLLOUT is a little harder. Since you can essentially send
        to any destination, RDS will always signal POLLOUT as long as
        there's room on the send queue (ie the number of bytes queued
        is less than the sendbuf size).

        However, the kernel will refuse to accept messages to
        a destination marked congested - in this case you will loop
        forever if you rely on poll to tell you what to do.
        This isn't a trivial problem, but applications can deal with
        this - by using congestion notifications, and by checking for
        ENOBUFS errors returned by sendmsg.

  setsockopt(SOL_RDS, RDS_CANCEL_SENT_TO, &sockaddr_in)
        This allows the application to discard all messages queued to a
        specific destination on this particular socket.

        This allows the application to cancel outstanding messages if
        it detects a timeout. For instance, if it tried to send a message,
        and the remote host is unreachable, RDS will keep trying forever.
        The application may decide it's not worth it, and cancel the
        operation. In this case, it would use RDS_CANCEL_SENT_TO to
        nuke any pending messages.

  ``setsockopt(fd, SOL_RDS, SO_RDS_TRANSPORT, (int *)&transport ..), getsockopt(fd, SOL_RDS, SO_RDS_TRANSPORT, (int *)&transport ..)``
        Set or read an integer defining  the underlying
        encapsulating transport to be used for RDS packets on the
        socket. When setting the option, integer argument may be
        one of RDS_TRANS_TCP or RDS_TRANS_IB. When retrieving the
        value, RDS_TRANS_NONE will be returned on an unbound socket.
        This socket option may only be set exactly once on the socket,
        prior to binding it via the bind(2) system call. Attempts to
        set SO_RDS_TRANSPORT on a socket for which the transport has
        been previously attached explicitly (by SO_RDS_TRANSPORT) or
        implicitly (via bind(2)) will return an error of EOPNOTSUPP.
        An attempt to set SO_RDS_TRANSPORT to RDS_TRANS_NONE will
        always return EINVAL.

RDMA·congestion 문서와 rds_header

172-223

RDS의 RDMA interface 세부 사항은 `rds-tools`에 포함된 `rds-rdma(7)` manpage에 있고 congestion notification API는 `rds(7)` manpage에 있습니다.

Wire message header는 `rds.h`의 `struct rds_header`입니다. `h_sequence`는 packet별 sequence number, `h_ack`는 마지막으로 받은 packet에 piggyback한 acknowledgement, `h_len`은 header를 제외한 data 길이입니다. `h_sport`와 `h_dport`는 source·destination RDS port입니다.

`h_flags`의 `CONG_BITMAP`은 congestion update bitmap임을, `ACK_REQUIRED`는 receiver가 이 packet을 acknowledge해야 함을, `RETRANSMITTED`는 이전에 전송된 packet임을 표시합니다. `h_credit`은 상대에게 추가 send room, 즉 사용 가능한 receive credit이 생겼음을 알립니다.

`h_padding[4]`는 미래 사용을 위한 미사용 공간이고 `h_csum`은 header checksum입니다. `h_exthdr`에는 optional data를 넣을 수 있으며 현재는 RDMA 관련 정보를 전달하는 데 사용합니다.

struct rds_header
Field의미
h_sequencePer-packet sequence
h_ack마지막 수신 packet ACK piggyback
h_lenHeader 제외 data 길이
h_sport / h_dportSource / destination RDS port
h_flagsCONG_BITMAP, ACK_REQUIRED, RETRANSMITTED
h_credit상대에게 새 send credit 통지
h_padding[4]미사용, future use
h_csumHeader checksum
h_exthdrRDMA 등 optional data

Message header field와 역할입니다.

RDMA for RDS
============

  see rds-rdma(7) manpage (available in rds-tools)


Congestion Notifications
========================

  see rds(7) manpage


RDS Protocol
============

  Message header

    The message header is a 'struct rds_header' (see rds.h):

    Fields:

      h_sequence:
          per-packet sequence number
      h_ack:
          piggybacked acknowledgment of last packet received
      h_len:
          length of data, not including header
      h_sport:
          source port
      h_dport:
          destination port
      h_flags:
          Can be:

          =============  ==================================
          CONG_BITMAP    this is a congestion update bitmap
          ACK_REQUIRED   receiver must ack this packet
          RETRANSMITTED  packet has previously been sent
          =============  ==================================

      h_credit:
          indicate to other end of connection that
          it has more credits available (i.e. there is
          more send room)
      h_padding[4]:
          unused, for future use
      h_csum:
          header checksum
      h_exthdr:
          optional data can be passed here. This is currently used for
          passing RDMA-related information.

ACK, credit flow control과 congestion bitmap

224-275

Reliable IB connection을 사용해도 RDS message ACK가 필요합니다. IB hardware는 message를 memory로 DMA하기 전에 hardware ACK를 만들 수 있습니다. ACK 뒤 DMA·처리 전에 HCA가 비활성화되고 다른 HCA로 failover하면 message가 손실될 수 있기 때문입니다.

매 message마다 즉시 ACK를 보내면 sender가 send queue에서 message를 빨리 해제할 수 있지만 ACK traffic이 과도해집니다. RDS는 가능한 한 data packet에 ACK를 piggyback합니다. ACK-only packet은 한 번에 하나만 flight 상태가 되도록 제한하고 sender의 send buffer가 차기 시작할 때만 ACK를 요청합니다. 모든 retransmission은 반드시 acknowledge합니다.

RDS IB transport는 credit 기반 flow control로 peer receive buffer에 data를 더 받을 공간이 있는지 확인합니다. 이 mechanism은 connection에서 hardware retry가 필요하지 않게 합니다.

Receive socket queue의 payload byte만 `SO_RCVBUF` 값에 대해 accounting합니다. Queued byte가 `rcvbuf` 이상이면 socket은 congested가 되고 해당 address로의 send는 block하거나 `-EWOULDBLOCK`을 반환해야 합니다. 잘 조정된 application에서는 이 back-pressure가 매우 드물어야 하며 문서는 이를 자주 겪는 application을 bug로 간주합니다.

각 node는 bind된 address에서 어느 port가 congested인지 나타내는 bitmap을 유지합니다. Bitmap이 바뀌면 그 local address에서 끝나는 모든 connection으로 전체 bitmap을 보냅니다. Connection을 올릴 때 미리 bitmap을 할당해 socket에 message를 queue하는 interrupt path에서는 allocation하지 않습니다.

Dense bitmap 전체를 변경 때마다 보내는 방식은 per-port congestion을 세밀한 message로 전달하는 것보다 구현이 단순하면서도 충분히 효율적입니다. Sender는 전송 직전 아주 저렴한 bit test 한 번으로 destination port가 congested인지 판단합니다.

RDS ACK 정책
Packet 수신Outgoing data에 ACK piggyback
Send buffer 차기 시작ACK_REQUIREDACK-only 최대 1개 in flight
Retransmitted packet항상 ACK

Data piggyback을 우선하고 필요한 경우에만 ACK-only packet을 제한적으로 사용합니다.

Congestion bitmap
Payload bytes >= SO_RCVBUFPort bit set모든 관련 connection에 bitmapSender bit testBlock 또는 -EWOULDBLOCK

Receive queue 상태가 cluster peer의 send decision으로 전달됩니다.

  ACK and retransmit handling

      One might think that with reliable IB connections you wouldn't need
      to ack messages that have been received.  The problem is that IB
      hardware generates an ack message before it has DMAed the message
      into memory.  This creates a potential message loss if the HCA is
      disabled for any reason between when it sends the ack and before
      the message is DMAed and processed.  This is only a potential issue
      if another HCA is available for fail-over.

      Sending an ack immediately would allow the sender to free the sent
      message from their send queue quickly, but could cause excessive
      traffic to be used for acks. RDS piggybacks acks on sent data
      packets.  Ack-only packets are reduced by only allowing one to be
      in flight at a time, and by the sender only asking for acks when
      its send buffers start to fill up. All retransmissions are also
      acked.

  Flow Control

      RDS's IB transport uses a credit-based mechanism to verify that
      there is space in the peer's receive buffers for more data. This
      eliminates the need for hardware retries on the connection.

  Congestion

      Messages waiting in the receive queue on the receiving socket
      are accounted against the sockets SO_RCVBUF option value.  Only
      the payload bytes in the message are accounted for.  If the
      number of bytes queued equals or exceeds rcvbuf then the socket
      is congested.  All sends attempted to this socket's address
      should return block or return -EWOULDBLOCK.

      Applications are expected to be reasonably tuned such that this
      situation very rarely occurs.  An application encountering this
      "back-pressure" is considered a bug.

      This is implemented by having each node maintain bitmaps which
      indicate which ports on bound addresses are congested.  As the
      bitmap changes it is sent through all the connections which
      terminate in the local address of the bitmap which changed.

      The bitmaps are allocated as connections are brought up.  This
      avoids allocation in the interrupt handling path which queues
      messages on sockets.  The dense bitmaps let transports send the
      entire bitmap on any bitmap change reasonably efficiently.  This
      is much easier to implement than some finer-grained
      communication of per-port congestion.  The sender does a very
      inexpensive bit test to test if the port it's about to send to
      is congested or not.

Transport 계층과 kernel 핵심 구조체

276-319

RDS code는 transport 독립적인 general RDS layer와 transport-specific layer로 나뉩니다. General layer는 socket API, congestion handling, loopback, statistics, user memory pinning과 connection state machine을 담당합니다. Transport layer는 실제 transport 세부 사항을 처리하며 IB implementation은 queue pair, work request, CM event handler와 기타 Infiniband 동작을 맡습니다.

`struct rds_message`는 `rds_outgoing`이라고도 볼 수 있는 outgoing datagram 표현입니다. General layer가 socket API로 받은 data를 복사하고 header field를 채운 뒤 connection queue에 넣으면 해당 connection transport가 전송합니다.

`struct rds_incoming`은 transport가 general code에 넘길 incoming data를 가리키는 공통 구조체입니다. General code는 socket을 깨우는 동안 이를 queue하고, 실제 copy-to-user 단계에서는 다시 transport code에 넘깁니다.

`struct rds_socket`은 socket별 정보, `struct rds_connection`은 connection별 정보, `struct rds_transport`는 transport-specific function pointer를 보관합니다. `struct rds_statistics`는 transport 독립 statistics이고 `struct rds_cong_map`은 raw congestion bitmap과 rbnode, wait queue 등을 감쌉니다.

RDS kernel structures
구조체역할
rds_messageOutgoing data와 header, connection send queue
rds_incomingTransport와 general receive code 사이 공통 data
rds_socketPer-socket state
rds_connectionPer-connection state
rds_transportTransport function pointers
rds_statisticsTransport-independent statistics
rds_cong_mapCongestion bitmap + rbnode + waitq

General layer와 transport 사이 data·state object입니다.

RDS 계층
BSD socket APIGeneral RDS layerrds_transport operationsIB queue pair / TCP socket

Application API에서 실제 transport resource까지의 책임 분리입니다.

RDS Transport Layer
===================

  As mentioned above, RDS is not IB-specific. Its code is divided
  into a general RDS layer and a transport layer.

  The general layer handles the socket API, congestion handling,
  loopback, stats, usermem pinning, and the connection state machine.

  The transport layer handles the details of the transport. The IB
  transport, for example, handles all the queue pairs, work requests,
  CM event handlers, and other Infiniband details.


RDS Kernel Structures
=====================

  struct rds_message
    aka possibly "rds_outgoing", the generic RDS layer copies data to
    be sent and sets header fields as needed, based on the socket API.
    This is then queued for the individual connection and sent by the
    connection's transport.

  struct rds_incoming
    a generic struct referring to incoming data that can be handed from
    the transport to the general code and queued by the general code
    while the socket is awoken. It is then passed back to the transport
    code to handle the actual copy-to-user.

  struct rds_socket
    per-socket information

  struct rds_connection
    per-connection information

  struct rds_transport
    pointers to transport-specific functions

  struct rds_statistics
    non-transport-specific statistics

  struct rds_cong_map
    wraps the raw congestion bitmap, contains rbnode, waitq, etc.

Connection state와 영구 재연결

320-335

RDS connection은 `UP`, `DOWN`, `CONNECTING`, `DISCONNECTING`, `ERROR` 상태 중 하나입니다. RDS socket이 어떤 node로 처음 data를 보내려 할 때 connection을 할당하고 연결합니다.

한 번 만들어진 connection은 계속 유지됩니다. Transport error가 발생하면 connection을 내렸다가 다시 설정합니다. 이때 queue에 있거나 일부만 전송된 datagram은 connection이 복구된 뒤 retransmit됩니다.

RDS connection lifecycle
첫 sendCONNECTINGUP
UPTransport errorDOWN / ERROR재연결Queued·partial datagram retransmitUP

Error 뒤에도 logical connection과 queued datagram delivery를 유지합니다.

Connection management
=====================

  Connections may be in UP, DOWN, CONNECTING, DISCONNECTING, and
  ERROR states.

  The first time an attempt is made by an RDS socket to send data to
  a node, a connection is allocated and connected. That connection is
  then maintained forever -- if there are transport errors, the
  connection will be dropped and re-established.

  Dropping a connection while packets are queued will cause queued or
  partially-sent datagrams to be retransmitted when the connection is
  re-established.

Send path와 receive path

336-392

Send path는 `rds_sendmsg()`에서 시작합니다. Userspace data로 `struct rds_message`를 만들고 RDMA operation 같은 CMSG를 parse합니다. 필요한 transport connection을 할당·연결한 뒤 message를 send queue에 넣고 send worker를 깨웁니다.

`rds_send_worker()`는 queue가 빌 때까지 `rds_send_xmit()`을 호출합니다. `rds_send_xmit()`은 pending congestion map을 먼저 보낼 수 있고 필요하면 `ACK_REQUIRED`를 설정한 뒤 transport에 일반 또는 RDMA message 전송을 요청합니다. RDMA operation은 retransmit하지 않습니다.

IB의 `rds_ib_xmit()`은 send ring에서 work request를 할당하고 새 receive credit을 `h_credits`로 peer에 알립니다. `rds_message`의 scatter-gather list를 map하고 ACK를 piggyback한 뒤 work request를 채워 connection queue pair에 post합니다.

Receive path의 `rds_ib_recv_cq_comp_handler()`는 completion을 확인하고 receive buffer를 device에서 unmap합니다. 오류가 없으면 `rds_ib_process_recv()`를 부른 뒤 receive ring을 refill합니다.

`rds_ib_process_recv()`는 header checksum을 검증하고 새 datagram 시작이면 header를 `rds_ib_incoming`에 복사한 뒤 fragment list에 추가합니다. Datagram이 완성되면 congestion update인 경우 congestion map을 갱신하고, 아니면 `rds_recv_incoming()`을 호출하며 ACK 필요 여부도 기록합니다.

`rds_recv_incoming()`은 duplicate packet을 버리고 ping에 응답하며 datagram에 대응하는 socket을 찾아 socket queue에 추가하고 깨운 뒤 congestion 계산을 합니다. 마지막으로 `rds_recvmsg()`가 data를 user iovec으로 복사하고 CMSG를 처리해 application으로 반환합니다.

RDS send path
rds_sendmsgrds_message + CMSGSend queuerds_send_workerrds_send_xmitrds_ib_xmitQueue pair post

Socket call에서 IB queue pair post까지입니다.

RDS receive path
rds_ib_recv_cq_comp_handlerrds_ib_process_recvDatagram assemblerds_recv_incomingSocket queue + wakeuprds_recvmsgUser iovec

Completion에서 userspace copy까지입니다.

The send path
=============

  rds_sendmsg()
    - struct rds_message built from incoming data
    - CMSGs parsed (e.g. RDMA ops)
    - transport connection allocated and connected if not already
    - rds_message placed on send queue
    - send worker awoken

  rds_send_worker()
    - calls rds_send_xmit() until queue is empty

  rds_send_xmit()
    - transmits congestion map if one is pending
    - may set ACK_REQUIRED
    - calls transport to send either non-RDMA or RDMA message
      (RDMA ops never retransmitted)

  rds_ib_xmit()
    - allocs work requests from send ring
    - adds any new send credits available to peer (h_credits)
    - maps the rds_message's sg list
    - piggybacks ack
    - populates work requests
    - post send to connection's queue pair

The recv path
=============

  rds_ib_recv_cq_comp_handler()
    - looks at write completions
    - unmaps recv buffer from device
    - no errors, call rds_ib_process_recv()
    - refill recv ring

  rds_ib_process_recv()
    - validate header checksum
    - copy header to rds_ib_incoming struct if start of a new datagram
    - add to ibinc's fraglist
    - if completed datagram:
         - update cong map if datagram was cong update
         - call rds_recv_incoming() otherwise
         - note if ack is required

  rds_recv_incoming()
    - drop duplicate packets
    - respond to pings
    - find the sock associated with this datagram
    - add to sock queue
    - wake up sock
    - do some congestion calculations
  rds_recvmsg
    - copy data into user iovec
    - handle CMSGs
    - return to application

Multipath RDS(mprds)

393-448

`mprds`는 multipathed RDS이며 주 대상은 RDS-over-TCP지만 개념은 다른 transport에도 확장할 수 있습니다. 고전적인 RDS-over-TCP는 두 endpoint 사이의 여러 `PF_RDS` socket을 두 IP address 사이 TCP socket 하나로 demultiplex합니다. 여기서 endpoint는 IP address와 port의 조합입니다.

이 방식은 여러 RDS flow를 TCP flow 하나에 몰아 단일-flow bandwidth가 상한이 되고, 한 flow의 지연이 모든 RDS socket을 막는 head-of-line blocking을 일으킵니다. 고정된 작은 packet size와 MTU에서 connection마다 여러 TCP/IP flow를 두면 throughput을 높일 수 있습니다. 각 TCP/IP flow가 RDS/TCP connection의 path가 됩니다.

RDS socket은 local address와 RDS port number 같은 값을 hash해 path에 붙고, 해당 socket의 packet은 그 path의 TCP를 통해 segmentation·reassembly됩니다. 구현은 `struct rds_connection`을 모든 path가 공유하는 부분과 path별 `struct rds_conn_path`로 나눕니다. I/O workqueue와 reconnect thread는 `rds_conn_path`에서 구동합니다.

TCP처럼 multipath-capable한 transport는 `rds_conn_path`마다 TCP socket을 만들 수 있고 transport-private `cp_transport_data` pointer로 관리합니다. Transport는 RDS core에 등록할 때 `t_mp_capable` bit를 설정해 capability를 알립니다. 이 경우 `rds_sendmsg()`가 `PF_RDS` socket의 bind local address와 port를 바탕으로 outgoing traffic을 여러 path에 hash합니다.

양 peer가 모두 multipath를 지원한다는 보장은 없고 지원 path 수가 다를 수도 있으므로 첫 data packet 전에 사용할 path 수를 control packet으로 합의해야 합니다. Multipath transport에서는 `rds_sendmsg()`의 outgoing hash가 완료되기 전에 이 exchange가 끝나야 합니다.

Control packet은 destination RDS port 0으로 보내는 RDS ping입니다. Ping extension header에는 type `RDS_EXTHDR_NPATHS`, length 2 byte, sender가 지원하는 path 수를 넣습니다. Probe ping은 `<linux/rds.h>`의 reserved port `RDS_FLAG_PROBE_PORT`에서 전송됩니다.

Receiver는 `RDS_FLAG_PROBE_PORT`에서 온 ping을 받으면 `min(sender_paths, rcvr_paths)`를 즉시 계산합니다. Receiver도 mprds-capable이면 pong에 자신의 `npaths`를 넣습니다. 지원하지 않으면 ping extension을 무시하고 pong에도 extension이 없으므로 probe sender는 single-path mprds로 fallback합니다.

Classical RDS/TCP와 mprds
구성TCP flow결과
Classical RDS/TCPEndpoint pair당 1개Single-flow bandwidth, HOL blocking
mprdsrds_conn_path마다 여러 개Hash 분산, path별 I/O·reconnect

Multipath가 해결하는 병목입니다.

mprds path 협상
RDS_FLAG_PROBE_PORTPort 0 probe pingRDS_EXTHDR_NPATHS (2 bytes)Receiver min(sender, receiver)npaths 포함 pongHash traffic across paths
Non-mprds receiverExtension 없는 pongSingle-path fallback

첫 data와 outgoing hash보다 먼저 path 수를 합의합니다.

Multipath RDS (mprds)
=====================
  Mprds is multipathed-RDS, primarily intended for RDS-over-TCP
  (though the concept can be extended to other transports). The classical
  implementation of RDS-over-TCP is implemented by demultiplexing multiple
  PF_RDS sockets between any 2 endpoints (where endpoint == [IP address,
  port]) over a single TCP socket between the 2 IP addresses involved. This
  has the limitation that it ends up funneling multiple RDS flows over a
  single TCP flow, thus it is
  (a) upper-bounded to the single-flow bandwidth,
  (b) suffers from head-of-line blocking for all the RDS sockets.

  Better throughput (for a fixed small packet size, MTU) can be achieved
  by having multiple TCP/IP flows per rds/tcp connection, i.e., multipathed
  RDS (mprds).  Each such TCP/IP flow constitutes a path for the rds/tcp
  connection. RDS sockets will be attached to a path based on some hash
  (e.g., of local address and RDS port number) and packets for that RDS
  socket will be sent over the attached path using TCP to segment/reassemble
  RDS datagrams on that path.

  Multipathed RDS is implemented by splitting the struct rds_connection into
  a common (to all paths) part, and a per-path struct rds_conn_path. All
  I/O workqs and reconnect threads are driven from the rds_conn_path.
  Transports such as TCP that are multipath capable may then set up a
  TCP socket per rds_conn_path, and this is managed by the transport via
  the transport private cp_transport_data pointer.

  Transports announce themselves as multipath capable by setting the
  t_mp_capable bit during registration with the rds core module. When the
  transport is multipath-capable, rds_sendmsg() hashes outgoing traffic
  across multiple paths. The outgoing hash is computed based on the
  local address and port that the PF_RDS socket is bound to.

  Additionally, even if the transport is MP capable, we may be
  peering with some node that does not support mprds, or supports
  a different number of paths. As a result, the peering nodes need
  to agree on the number of paths to be used for the connection.
  This is done by sending out a control packet exchange before the
  first data packet. The control packet exchange must have completed
  prior to outgoing hash completion in rds_sendmsg() when the transport
  is multipath capable.

  The control packet is an RDS ping packet (i.e., packet to rds dest
  port 0) with the ping packet having a rds extension header option  of
  type RDS_EXTHDR_NPATHS, length 2 bytes, and the value is the
  number of paths supported by the sender. The "probe" ping packet will
  get sent from some reserved port, RDS_FLAG_PROBE_PORT (in <linux/rds.h>)
  The receiver of a ping from RDS_FLAG_PROBE_PORT will thus immediately
  be able to compute the min(sender_paths, rcvr_paths). The pong
  sent in response to a probe-ping should contain the rcvr's npaths
  when the rcvr is mprds-capable.

  If the rcvr is not mprds-capable, the exthdr in the ping will be
  ignored.  In this case the pong will not have any exthdrs, so the sender
  of the probe-ping can default to single-path mprds.