 Hello, everyone. My name is Jukkaris, and I work in the Intel Open Source Technology Center in Espo Finland. Our GOMS Team Develops Communication Technologies for Linux and Zephyr OS. This presentation describes new and native internet protocol stack for Zephyr. Currently, Zephyr has a Contiki micro IP-based networking stack. This new native stack will replace the legacy stack and initially provide same functionality as the old one. Native stack in this context means that the IP stack is fully integrated to Zephyr. It's using Zephyr coding style and is fully optimized to be used in Zephyr. So you may wonder why would we need the native stack. The Contiki-based IP stack initially provided suitable features for experimenting. But new feature requests were created and the limitations of the legacy stack were realized. And there's a list of some features that are missing in the legacy stack. So there's no possibility to have a dual stack IPv4 and IPv6. IPv6 is the more important one for IoT use cases as it's using heavily in 1504 and Bluetooth networks. Use of IPv4 is still important, but it's only used in Ethernet and Wi-Fi networks. Also, the legacy stack can support only one network technology at the time. This means that one cannot have Bluetooth and 15.4 networks connected at the same time. Related to this, there cannot be multiple network interfaces at the same time. For example, you cannot have multiple 15.4 radios running at the same time or having like a sleep or Ethernet connectivity at the same time. The legacy stack uses so-called big memory buffers. Its buffer is 1,280 bytes long in order to be able to receive one full IPv6 packet. This is quite suboptimal as typically the network packets in IoT protocols like co-op are very small way under 100 bytes. As the server is also multi-threaded operating system, using micro IP in this environment is a bit cumbersome, because micro IP stack is basically a one big loop that running in server in this way is very suboptimal. Also as these IoT devices just need to work, testing the functionality is really important. So we need the way to verify that each new commit in the networking code does not cause any regression. For these purposes, we are creating a unit and component and functionality testing. And these tests need to run after each commit to the source tree. With legacy stack this is a bit difficult as the code base already exists, and it's slightly easier to create these tests for a new stack. So why not port some existing stacks to server? There's a lot of third-party stacks available. Initially we used Kondike-based stack when we were creating IP support for server. It worked, but it had limitations as seen in the previous slide. The initial port tried to make possible to merge upstream fixes into the stack, but that became later impossible because the changes we needed to make to the code in order to be it being usable in server was just too much. There's also one big issue if one uses to use existing third-party stack as a library. It requires use of adaptation layers to adapt the library to the host operating system. For example, FNET and lightweight IP both provides hooks to enable them to be used in different environments. The extra adaptation layer scores is some memory overhead, because we need to map the buffering in these libraries to the native buffering, what is available in server. If we don't have this adaptation layer, meaning that we are deeply integrating this third-party stack into the operating system, I can server, then usually the code needs to be heavily modified. So instead we can just create the native stack instead. But this doesn't mean that we could not reuse the code from third-party library. For example, we have a TCP implementation that is based on FNET. And it's just that we cannot push back changes back to FNET or take patches easily from it, because the code has been changed so much. What this means in practice that the native stack is now the upstream stack. Having a unified look and feel of the code is also important. It is much easier to read the code and create the patches, if the code looks unified throughout the code base. This is very obvious if you have ever looked contiki-based code in the server legacy IP stack. One big improvement in the native stack will be the automatic utilization of the unit testing. For each commit we will run several unit tests automatically. It is certainly possible to create unit tests for third-party stacks, but this usually requires more work than creating these tests while developing the stack. So what exactly have we done with the native stack? First of all, we created IPv6 and IPv4 stacks so that they can go exist at the same time. The IP3 layer uses the common L2 layer that helps to abstract the device driver away. There's a picture in the following slides. This way it's easier to implement network interface support, so that we can support multiple technologies at the same time. We took relevant code from existing stacks like TCP from Fnet and Ripple from Contiki. We needed to heavily modify the code in order to support the server natively. We made also the network buffers much smaller in the native stack. The big buffers are gone, although if you really need those you can still create such buffers, if you don't have memory constraints. Now the smaller buffers are chained together to create support for a larger amount of data. With the native stack we can also now send data larger than 1,280 bytes. As I already mentioned we created the functionality testing framework that verify the native stack is working as expected. This is done in order to catch the regression in the code. The key features in the new native stack are dual IPv6 and IPv4 support. We have effective memory management. We have a possibility to have a minimal copy data path, but this is not always possible. For example the IPv6 header compression requires some memory copy. We also support thread requirements, but the stack does not support thread. Compared to the legacy stack all configuration can be done via kconfig. In legacy stack some config options were required to be changed in header file. As I already mentioned one really important key feature is the utilization of testing harness. In the right hand side there is a high level picture of the network data path. We have one RX fiber or thread, these are called fibers in Zephyr. That receives data from the device driver. The device driver is abstracted as a network interface. The RX fiber runs the protocol dispatcher logic. So depending on the data like UDP or TCP the dispatcher checks if there are any applications listening corresponding destination board. Then it passes the data to the correct application for further processing. Any control traffic like ICMP messages are handled in the course stack. But it is possible to hook into the system and get this directed to the application if really needed. In the TX side we have one TX thread for each network interface. And applications write their data to the TX5O that is then read by corresponding TX fiber. The data is then checked very minimally and passed to the device driver for sending. If the packet sending is okay the device driver will release the network buffer that was sent just now. And if the packet sending fails the upper stack may retry or discard the packet. In the lower levels we have the L2 abstraction layer which is very thin. Its purpose is to provide lower level services for various technologies. For example IPv4 ARP for Ethernet is handled here. It is possible to share L2 code between same type of technology device drivers like if you have multiple Ethernet interfaces or if you have a sleep and Ethernet at the same time they both share the same L2 code. The 15.4 stack has been enhanced in the native L2 layer. In legacy stack the 15.4 support was very simple and not fully spec compliant. In the legacy stack we are missing for example scanning and association and disassociation. The network management API can be used to start a network scan. It can also send events when important things happen in the stack. Like when a new IP address is added to the system. Applications can re-listen these events or activate some commands to initiate this scanning for example. As I already mentioned the automatic testing is one of the key features of the native stack. It's very crucial that we do this properly. We need to verify that any change to the code does not cause any regression. Creating these tests is not a trivial task but we need to do it. While building the stack it's a bit easier than writing them from scratch to the existing. If you want to see the code it's there they are in the tests slash net. For conformance testing we are currently investigating Tahi that is used for IPv6 testing. If you have any good suggestions for tools that help conformance testing we would be grateful to hear. Then some lower level detail to the stack. This is one part that is very important that the stack will perform optimally. Many third party stacks have a similar network buffer management scheme implemented. But it's not really possible to combine them with the separate network buffer implementation. This means that the third party stacks like Fnet would need to be changed to use the separate network buffers. The network buffers are allocated from pools and only the available memory limits the number of pools you can have. Although individual network buffer elements in one pool must have the same size. It is possible to chain these network pools, network buffers together. The chain can have different size elements in it. And this way we can actually receive larger amount of data than this 1,280 bytes. That is limiting factor in the legacy stack. Typically the size of the fragment in the chain should be selected according to the lower level network technology MTU. For example in 15.04 the buffer size should be 128 bytes in order to avoid any extra memory copy in the L2 driver. This L2 driver prepares the packet to be sent. So it doesn't need to fragment or reassemble the packets in the lower levels. The network buffer can have a user part combined with it. This means that the protocol specific data can be attached to the individual network buffer. For example we store IPv6 specific data in the user data part in order to speed up the parsing of the network packet. Typically we do it so that the first fragment of the head of the buffer chain contains this user specific data and all the network data is in the subsequent fragments. And all aspects of the network buffers can be configured via K-config. Some lower level details also continues. If the system is configured optimally the size of the network buffer is selected so that each buffer can have a link layer headers and MTU size data in it. So it's possible to reserve some extra space for link layer headers in each fragment. This is optional but recommended. So if we allocate these headers beforehand we don't need to copy data in the lower part of the stack when the link layer headers are put in this network data. But anyway the network buffer memory is not linear so it must be partitioned properly when applications are sending data. We have an API for doing that so applications do not really need to know about this. The link layer information is automatically added by corresponding L2 driver when sending to the network. In the receiving side the same principle applies. The data needs to be read in chunks by the application. But we have a helper API for doing that. Currently the code can be found in the Zephyr OS Git 3 under the net branch. And the current plan is to merge it to the 1.7 release. So the plan is that when 1.6 is merge windows opens we merge this code into the master questions. Yes, yes, there is... Did you talk about the API? No, no. We have a new API that resembles the BSD socket API. It's not really identical to it but it maps to the BSD socket API. And provides both asynchronous and synchronous APIs for the applications. No extra shim layers for these purposes. All the sample applications will be kind of work with the new stack. But any extra external applications need to be changed. You can add new fragments into this list if needed. Yeah, that's very easy. It's just to duplicate. What do you mean by duplicating it? There are several protocols running on the same error. And they need to modify the same errors. Somehow you will be testing it to duplicate packet. And you can change it based on... Do I need to duplicate full packet in this case or can I just duplicate? Yeah, I think you need to duplicate the whole packet. How does the new stack go for performance wise? Only ad hoc testing so far, so I don't have any measurements. But it's faster and smaller memory footprint. And one big reason is that we have less fibers, which means that we have less stack in use. And also the original kod contained a lot of extra stuff that was not really compiled out properly. So now we only have in the code what really is required for the functionality. We have a DHCP4 client already merged and DNS client also is available. Well, the Wi-Fi, we don't support Wi-Fi at the moment. But it's work to be done in the future. We have the management APIs for doing various management things. At the moment we just merged it in, so there's not much functionality yet. But we are adding right now scanning support and association to a pan. Well, at the moment the code is not yet there, but we are adding it. We are investigating this. There's no support for that yet. Yeah, there are a lot of tools available, but not many are open sourced. There are commercial tools. Yeah, of course we need for this kind of complex scenarios, we need a proper testing environment and real hardware and so on. We have this in QMO, we are doing functionality testing. So we are checking that we are sending some packet and is it correct and so on. It's kind of inside one QMO. Yeah, that's very interesting. Oh, okay. All right, good. All right, if that's it. Thank you.