 Hello everybody, welcome back to the evolving technology track. And now we'll have, now we'll have Daiki Waena speaking about the understanding QIC by examples. And if you have any questions, please leave them in the chat. We're going to address them in the Q&A at the end of the talk. Daiki, the stage is all yours. Okay, thanks for the introduction. So let's get started. So QIC, as many of you already know about it, we're actually using it. So QIC has been a hot topic in the networking area for the last couple of years. It's now published as RFC. QIC is a transport protocol, primarily designed for HTTP-3. It is based on UDP. It is a connection-oriented protocol like TCP. QIC connections can be multiplexed into multiple streams and packets are protected by encryption using TLS. Okay, but it is a complex protocol and I've had several misunderstandings. Someone said HTTP-3 is QIC and vice versa is not true. Another person said it's full of layer violation. I think it's not really. Someone also thought TLS handshake happens for each stream, but it only happens per connection. Another person said TLS libraries should implement QIC, just as they implement DTLS, but in my opinion, it's not feasible. So I'll try to explain why in the later slides. So the goal of this talk is to explain important aspects of QIC by going through the source code of a simple application program. And secondly, I would like to clear up the previously mentioned confusion, hopefully. So application. So we use a simple echo server and client application as an example. I mentioned file synchronization application in the talk description, but it turned out to be too complex for this short talk. So I switched to a simpler application. Sorry about that. The echo application actually quite simple. The client needs data from the standard input and sends it to the server and the server sends the received data back to the client. So we use C because it would make it easy to compare the QIC API with POSIX networking API. So as a TLS library, we use VNU TLS because it provides minimal support for QIC and packages are already available in major Linux distributions. NZTCP2 is the most important part here. It is a quick stack. Fortunately, it has not officially released yet. So we embed it as a Git sub-module. The Maison build system allows us to easily embed such Git sub-modules as a sub-project. So finally we use GRIF and some specific Linux functions for other facilities. Okay, NZTCP2. So it is a very flexible quick library. It's flexible because the library itself does not do any system IO itself like networking and timer management. And also it does not depend on any specific TLS library. You can use it with any TLS library if you can provide the necessary callbacks. So code setup. So the code is available on my Git repository. So there are several files. Maison build is a build file. Client.c and sub.c are the main part of the application. Connection.c and stream.c are minimal abstraction of quick connections and streams. And when you see this group and utils, basically just provide generic helper functions. And in the sub-projects directory we embed NZTCP2. So let's break down the program behavior. So both client and server take four steps. Firstly, it resolves the host address and create a sub-project. Secondly, it does handshake to establish a connection. And thirdly, it opens the stream. And finally it starts sending and receiving data on the stream. So preparation. So this can be... I think this is a family code, I think. It just resolves host address with get address info and create a socket. It's a common pattern taken from the manual page. So let's move on to the handshake phase. The figure on the left-hand side is a three-way handshake in TCP. The figure on the right-hand side is a typical quick handshake. As for the number of roundtrips, they are mostly the same. The server still needs to send a handshake-down message. But the client can start sending the data earlier in its second flight. So quick handshake actually just wraps TLS handshake messages in three packet types. The important point here is that packets with different types are encrypted in different encryption levels with different keys. So let's take a look at the server side code. Since quick uses UDP, it's a bit tricky to accept connection. So in TCP, we can simply use listen to create a listening socket and use accept to create a socket for incoming connection. But in quick, there is no distinction between listening and connected sockets. So we need to inspect the received packets to identify its connection. So to identify connections, quick uses identifier called connection ID. Connection IDs are typically random bytes, and that means they do not necessarily correspond to the actual network address. So using connection ID, it is even possible to migrate the existing connection to a different network. Conceptually, each endpoint uses two connection IDs, source connection ID and destination connection ID. They are negotiated here in handshake in these four steps. So first the client randomly generates SCID and DCID and sends them to the server. The server also generates its own SCID. And in the end, both client server will have the same pair of SCID and DCID. So in the code, accepting connection looks like this. First, we receive a packet from the client using receive message. Then pass it to the NZTCP2 decode function to extract SCID and DCID. Then we check if the DCID matches any of the existing connection. And if not found, we create a new connection logically with NZTCP2 accept action. So stream opening. So after the function is established, we can open streams. Quick streams are implicitly opened upon writing. So that means there is no dedicated packets or frames sent out to just open stream. So this code opens a bi-directional stream, but it's only logically. So now we can send some data on the stream. So this writeable stream function encodes the data along with the pending output from the NZTCP2 library. So it writes out the output into buff and it can be sent as a UDP payload directly with send message. So far we haven't looked at how quick packets are organized. Quick packets are embedded in UDP packets and a single UDP packet may contain multiple quick packets. And each quick packet may contain multiple quick frames. Frames are smaller that transfer in it. They can be streamed later or acts and so on. So since UDPs are unreliable, so frames may be lost. So quickly this acknowledgement for loss detection and recovery. Most of the frames trigger the receiver to generate acts. In quick, they are called act A-citing frames. So that acts a packet, not a pair of frames, and a single frame can include multiple acknowledgement as a range. So okay, that's basically about the high-level overview. But to make the application work, we need to add some boilerplate code. The first is to implement the application's main loop. We use E-Poll system code for both client and server. So we wanted to get with E-Poll's edge trigger interface for scalability. I think this is also a family code. Buffering. So for loss detection and recovery, the application needs to keep the same data until they are actually worked. So in the example application, buffering is implemented at the application level, parsley, using a glib cube. So when stream data arrives, the server does not directly write it back to the socket. But instead, it pushes it between the application's buffer. And then the buffer data will be dispatched in the E-Poll group. So timers. So in addition to acts, quick also uses timers for loss detection. So this NCCP2 does not have access to the system clock. So timer management is the application S1-CT. So in the example, we use enux-timefd with monotonic clock. To check the next expiry time, we need to call NCCP2 get expiry function and set the timer. So when the timer fires, we tell NCCP2 with 100 expiry function. So let me show some demo. So hopefully it works. So I'll use three different scenarios. First, I'll send a single frame in a single stream packet. Secondly, I'll send multiple frames in a single packet using a processing option. Finally, I'll send frames through multiple streams with a string section. So the traffic can be captured using the Tshark command. OK. So here Tshark is already running. So capture it on the loopwork device. And let's start the server. So it's started. Let's start the client as well. So something has been exchanged. You see the handshake term is here. So handshake is completed. So now I can send some data. So AAA is sent on the stream number 0. And I get the same string from the stream number 0. And looking at the packet actually sent. So you see 616161. So that means AAA. Yeah, I see. But this is a short header packet. It contains ACK and stream data. So it's basically a single packet. Let's pack the multiple packets in a single packet. Multiple frames in a single packet. This is not sent yet. It's just buffered. So now three strings are sent as a single packet, I think. So now this packet actually includes three stream frames. Right? So AAA, BBB, something like that. So now we send strings, multiple strings. So as you see, they are coming from different strings. 0, 4, 8. So the difference is just a stream ID in the stream frame. So there is no dedicated action to open the stream. Okay. So hopefully we have time to discuss advanced topics. But let me briefly touch them. So first is flow control and congestion control. So those are different concepts and shouldn't be confused. Flow control is a mechanism to prevent receiver being overwhelmed. On the other hand, congestion control is a mechanism to prevent network being congested. So in quick flow control works by advertising the limits. The limits are negotiated during the handshake, but can be changed later as well. We have six minutes in the initial parameters, three full times to control the limits later. Congestion control. Quick use of multiple algorithms for congestion control. The first is ECN explicit congestion negotiations. This means for the intermediate networking nodes such as switches or nodes, switches or routers to notify the endpoints that congestion is happening. So when the congestion is detected, like switches becoming overloaded, it marks ECN flag in the IP flag, IP header, and for the packet to the receiver. Then the receiver mirrors the fact in the upper layer protocol packet, in the next packet being sent to the server, sent to the sender. Then the sender takes appropriate action to avoid the congestion. So writing on the reading ECN flag in the IP header can be done with socket options and control messages. To tell NGCTP2 about the presence of ECN flag, we can set the ECN field in NGCTP2 packet link structure. So congestion control algorithms. They are a bit difficult, so I don't go into the details, but these algorithms basically define packet sending strategies when congestion is detected. So RENAPS provides multiple algorithms for ECP. The default is cubic. NGCTP2 also supports Reno, cubic, and BBR. We can also create custom implementation through the callback. So UDT, GSO, generic segmentation of road, is a kernel mechanism to send multiple UDT packets with a single send message system call. So the size of each UDT packet can be get through the socket option for control message. It's kind of significant to affect the performance. Okay, so we are near the end of the presentation. I think the major takeaways are that quick is magical but not magic. Basically the magic logic is in NGCTP2. Quick design is largely influenced by TCP. So writing a quick based application is a great way to learn or refresh your TCP IP knowledge. So that's the end of presentation. So thank you for your attention. The slides are available on my digital page where you can also find the code. Also I would like to thank Tatsuhiro Tsuchikawa for creating NGCTP2 and reviewing the slides. So any questions? Yeah, so far I cannot see any questions but if anybody has questions please do ask them. We'll give it 10 more seconds. Yeah, there are also some useful resources. NGCTP2 has an online document. There are four RFC. The main part is 9000. It covers all the aspects. Also 90002 was detection is also interesting. So there is an ongoing draft to define the applicability of this protocol. But I guess since we don't have any questions. Thank you so much Daki for your presentation and talk.