DEV Community 👩‍💻👨‍💻

felixfaisal
felixfaisal

Posted on

Rust VPP API Bindings | LFX Mentorship Project

Hey Everyone, This article is about Rust VPP API Bindings project which was a part of LFX Mentorship. I had this amazing opportunity to work under the guidance of Andrew Yourtchenko on this project. So let's get started

What is VPP 🤔

FD.IO is an Open Source Dataplane developed by Cisco that runs in user space. At the heart of FD.IO is VPP which stands for Vector Packet Processor. So emphasis on Vector as opposed to Scalar Packet Processor wherein a single packet is processed at a time, VPP processes a vector of packets at a time which results in high performance.

It's like moving a load of marbles with a wheelbarrow instead of spoon

There are many exciting applications of VPP in Cloud Native and Networking space, I will be leaving links here for you to explore 🚀
Project Calico/VPP
VPP Case Study

Architecture 🧐

Here's the architecture of our project
alt text

VPP-API-Transport

This crate is responsible for establishing a connection to a running VPP process either via a socket or a shared memory interface. It also holds various functions for sending data via the connection.

VPP-API-Encoding

Encoding library holds various encapsulated data structures that are responsible for interacting with VPP. The library majorly ensures the deserializing and serializng of data structures are compatible with VPP. We are using serde+bincode for all serializing and deserializing to binary.

VPP-API-Driver

Driver library consists of functions that makes use of ingredients of Transport and Encoding which allows sending messages and receiving them easier. There are 3 types of messages that can be sent to VPP. (Referenced from Developer Documentation of VPP)

  1. Request/Reply The client sends a request message and the server replies with a single reply message. The convention is that the reply message is named as method_name + Reply. Eg: SwInterfaceAddDelAddressReply
  2. Dump/Detail The client sends a “bulk” request message to the server, and the server replies with a set of detail messages. These messages may be of different type. A dump/detail call must be enclosed in a control ping block (Otherwise the client will not know the end of the bulk transmission). The method name must end with method + Dump, the reply message should be named method + Details. The exception here is for the methods that return multiple message types (e.g. SwInterfaceDump). The Dump/Detail methods are typically used for acquiring bulk information, like the complete FIB table or details of all the existing Interfaces.
  3. Events The client can register for getting asynchronous notifications from the server. This is useful for getting interface state changes, and so on. This a type of message that is not yet supported.

Here is a function that is responsible of sending Request/Reply message

pub fn send_recv_msg<'a, T: Serialize + Deserialize<'a>, TR: Serialize + DeserializeOwned>(
    name: &str,
    m: &T,
    t: &mut dyn VppApiTransport,
    reply_name: &str,
) -> TR {
    let vl_msg_id = t.get_msg_index(name).unwrap();
    let reply_vl_msg_id = t.get_msg_index(reply_name).unwrap();
    let enc = get_encoder();
    let mut v = enc.serialize(&vl_msg_id).unwrap();
    let enc = get_encoder();
    let msg = enc.serialize(&m).unwrap();

    v.extend_from_slice(&msg);
    println!("MSG[{} = 0x{:x}]: {:?}", name, vl_msg_id, &v);
    t.write(&v);
    loop {
        let res = t.read_one_msg_id_and_msg();
        // dbg!(&res);
        if let Ok((msg_id, data)) = res {
            println!("id: {} data: {:x?}", msg_id, &data);
            if msg_id == reply_vl_msg_id {
                let res = get_encoder()
                    .allow_trailing_bytes()
                    .deserialize::<TR>(&data)
                    .unwrap();
                return res;
            } else {
            }
        } else {
            panic!("Result is an error: {:?}", &res);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

VPP-API-Gen

This is the most important part of Rust VPP, As it ingests Binary APIs and generates Rust bindings as a separate package which is nothing but the VPP-API.
alt text

VPP provides a binary API scheme to allow a wide variety of client codes to program data-plane tables. As of this writing, there are hundreds of binary APIs. Messages are defined in *.api files. However we are using Messages defined in *.api.json* which are self contained api files written in JSON format.

VPP-API-Macros

Macro library consists of procedural macros that make the generated rust bindings more ergonomic and provide sophisticated abstractions. The VPPMessage macro is a builder macro that allows you to initialize VPP messages in a less tiring fashion and also implements a derive for message name and crc. Another macro VPPUnionIdent creates accessor methods for the Union which makes it easier for the users to initialize it without having to fill the entire union.

Here is an example of using builder

let create_host_interface: CliInbandReply = send_recv_msg(
        &CliInband::get_message_name_and_crc(),
        &CliInband::builder()
            .client_index(t.get_client_index())
            .context(0)
            .cmd("create host-interface name vpp1out".try_into().unwrap())
            .build()
            .unwrap(),
        &mut *t,
        &CliInbandReply::get_message_name_and_crc(),
    );
Enter fullscreen mode Exit fullscreen mode

Future and Testing

  • The integration tests currently only ensure if the messages are sent to VPP and received back without checking if it's the reply we are expecting, It has been added into the CI of the repository using GitHub Actions.
  • As mentioned earlier we do not support events messages as of now but hoping to develop it in the future.

Pull Requests 🤓

Here are all my pull requests:

Things I learnt 😎

This project is the best project I've worked on so far, I got to explore and implement many Rust features

  • Rust Macros: I learnt and implemented procedural macros using Rust and created VPP-api-macros which proved to be very useful in making the code abstracted.
  • Serde: I explored Serde which is a crate for serializing and deserializing, I was able to implement custom serialization and deserialization for EnumFlags and Unions.
  • VPP: It took me a while to get started and learn about VPP but by the end of the first half, I was able to grasp what VPP is and what it does.
  • Golang: I learnt Go to explore GoVPP bindings and understand their philosophy in generating bindings and possibly using some of it in Rust VPP.
  • Traits: Before the mentorship I was still struggling with properly understanding traits but during the course of the project I became much more confident about my understanding of traits
  • Functional Programming in Rust: Parts of the project has been written using concepts of Functional Programming, I sort of had to learn a bit of functional programming as I had mostly programmed using OOP.

Thank you 🥳

Shoutout to the Rust Foundation Discord Server for always helping me out with my queries be it how silly they were. Jon Gjengset for creating extensive videos on difficult parts of Rust, Linux Foundation for this amazing program that allowed me to work with Rust and lastly my mentor Andrew for guiding me at every step of the project.

Top comments (0)

Now it's your turn.

 
Join DEV and share your story.