Exercises: DB and RIB

README for the 'RIB' exercise
Login

README for the 'RIB' exercise

Back to main README

Excercise 2: RIB (C)

Write down a small library in C language to store an IP routing table, for both IPv4 and IPv6.
The library should allow performing longest-­prefix matching operations.
Start by defining the API, then the data structures and, finally, the implementation.
For longest-prefix operations, you do not have to provide a true implementation but only pseudo­code.
Report the references employed, if any.

Solution to the RIB excercise

For this exercise, the RIB table entries has been implemented as a linked list.
This is due to simplicity, as it is not an optimal solution.
An example of a more sophisticated solution, especially for IPv6, is Fast IP Routing with LC-Tries (source code available here), which implements the RIB as a level-compressed trie.

This implementation is considered a "fake" RIB in the sense that added/delete/modified table entries will modify the linked list, but not the kernel RIB.
The test program rib is used to test the RIB library.
To keep the state between executions of rib, the RIB will be saved to/loaded from a file.
Even if the RIB is "fake", the initially retrieved values (Destination, Gateway, Iface, etc.) are "real", obtained from the kernel RIB.

The API

The API - overview

To get an idea how to design the API, use these commands and their output as a base:

    ip -4 route

        default via 10.0.2.2 dev eth0  proto static  metric 100
        10.0.2.0/24 dev eth0  proto kernel  scope link  src 10.0.2.15
        10.0.2.2 dev eth0  proto dhcp  scope link  src 10.0.2.15  metric 1024
        169.254.0.0/16 dev eth0  scope link  metric 1000

    ip -6 route

        fe80::/64 dev eth0  proto kernel  metric 256  pref medium

For this exercise, skip fields which are less relevant to this task.
That reduces the output to:

    IPv4:

        default via 10.0.2.2 dev eth0
        10.0.2.0/24 dev eth0
        10.0.2.2 dev eth0
        169.254.0.0/16 dev eth0

    IPv6:

        fe80::/64 dev eth0

If the keyword "via" is not present for an entry, a gateway is not set for that entry.
Internally, the fields will be stored like this:

        Destination     Prefix   Gateway         Iface
        0.0.0.0         0        10.0.2.2        eth0
        10.0.2.0        24       0.0.0.0         eth0
        10.0.2.2        32       0.0.0.0         eth0
        169.254.0.0     16       0.0.0.0         eth0
        fe80::          64       ::              eth0


The rib and rib6 programs

The command options for rib and rib6 have much in common with the ip route command, as described in ip-route(8).

    ip route { add | del | change | append | replace | monitor } ROUTE

    ./rib [show] [FILE]
    ./rib { add | del | mod } ROUTE [FILE]
    ./rib match IP [FILE]

If the rib program is run without arguments, it looks by default for the file rib.txt to load the RIB, and then shows the loaded RIB.
If rib.txt does not exist, the kernel RIB is loaded and shown instead (the kernel RIB is never modified, though).
Running rib with show subcommand or without it is equivalent. Use FILE to load a RIB from another file.

Any changes to the loaded RIB (add/del/mod) are saved by default to rib.txt , or to FILE.

The match subcommand is used to test any arbitrary IP to print a matching route in the RIB (if any).
If more than one route match the IP, longest-prefix matching is applied to get the correct route.

The rib program only treats IPv4 addresses.
The rib6 program is the equivalent for treats IPv6, and uses rib6.txt by default.

The API reflects the subcommands add, del, mod, match, show.

The API - details

The rib_data_t and rib_entry_t are data structures described in detail below:

    rib_entry_t  = {Destination Gateway Mask Flags Iface}  (the RIB columns discussed above)
    rib_entry_list_t  = {linked list of rib_entry_t entries }


    int librib_load_list(const char *input_file, rib_entry_t **list, int ip_version);

    /*
      Load a RIB from file or, if file not found, from the 'ip -4 r' or 'ip -6 r' command.
      The lines are validated and stored into a linked list.
      (always called by the 'rib' program)
      Returns non-zero on failure (I/O error, unexpected error).
    */


    int librib_save_list(const char *output_file, rib_entry_t *list, int ip_version);
    /*
      Save a RIB to file (called by the 'rib add/del/mod' subcommands).
      Returns non-zero on failure (I/O error, unexpected error).
    */
    int librib_show_list(rib_entry_t *list, int ip_version);
    /*
      Print a list of RIB entries (called by 'rib show').
      Returns non-zero on failure (unexpected error).
    */
    int librib_delete_list(rib_entry_t *list);
    /*
      Delete all RIB entries.
      Returns non-zero on failure (unexpected error).
    */
    int librib_add_entry(rib_entry_t **list, rib_data_t *new_data);
    /*
      Add a new entry to the RIB.
      One or more rib_entry_t values may be set.
      Passing 0 to Destination sets the default gateway.
      Returns non-zero on failure (unexpected error).
    */
    int librib_del_entry(rib_entry_t **list, rib_data_t *matching_data);
    /*
      Delete an existing entry in the RIB that matches the provided struct.
      One or more rib_entry_t values may be set.
      Passing a NULL struct o 0 to Destination deletes the default gateway.
      Only one entry is deleted, even if several RIB entries match the provided struct.
      Returns non-zero on failure (no matching entry to delete, unexpected error).
    */
    int librib_mod_entry(rib_entry_t **list, rib_data_t *matching_and_modified_data);
    /*
      Modify an existing entry in the RIB that matches the provided struct.
      One or more rib_entry_t values may be set.
      Only one entry is modified, even if several RIB entries match the provided struct.
      Returns non-zero on failure (no matching entry to modify, unexpected error).
    */
    rib_entry_t rib_get_entry(rib_entry_t **list, rib_data_t *matching_data);
    /*
      Get the first RIB entry which matches matching_entry.
      Returns a pointer to a rib_entry_t struct.
      This function is called by other functions with a "matching entry" as argument: rib_del_entry(), rib_mod_entry()
      Returns NULL on failure (no match, unexpected error).
    */

Data structures

    /* IPv4 and IPv6 addresses may share memory space, as they are not used simultaneously */
    typedef union ip_addr_t
    {
      struct in_addr ipv4;
      struct in6_addr ipv6;
    } ip_addr_t;


    /* RIB entry data */
    typedef struct rib_data_t
    {
      ip_addr_t dst; /* Destination (IP address)*/
      ip_addr_t gw; /* Gateway */
      uint8_t prefix; /* Destination CIDR Prefix: 0-32 (IPv4), 0-128 (IPv6), used to calculate mask */
      char iface[IF_NAMESIZE];
    } rib_data_t;

    /* RIB entry in linked list */
    typedef struct rib_entry_t
    {
      rib_data_t data;
      struct rib_entry_t* next;
    } rib_entry_t;

The implementation

Prerequisites

This code has been compiled and tested on Linux (Ubuntu 16.04).
Only make and gcc should suffice to compile the source code.
No additional third-party software should be required.

Portability has not been considered for this task.
The code may not work on other Linux distributions, and will probably not do so on other platforms (i.e. Free/Open/NetBSD, MacOSX, Win32, CygWin, MSYS2).

Compile and run

    make clean all run

Refer to the Makefile for build details.

Conclusions

This RIB implementation is focused on simplicity, not on efficiency.
As mentioned before, an improved version would be to replace the linked-list data structre with a level-compressed trie, as described in Fast IP Routing with LC-Tries.

References

https://en.wikipedia.org/wiki/Routing_table
https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_blocks
https://www.geeksforgeeks.org/computer-networks-longest-prefix-matching-in-routers/
http://olegkutkov.me/2019/03/24/getting-linux-routing-table-using-netlink/
https://oroboro.com/linux-routing-tables-in-c/
https://www.geeksforgeeks.org/program-for-ip-forwarding-table-lookup/
http://www.drdobbs.com/cpp/fast-ip-routing-with-lc-tries/184410638
https://www.nada.kth.se/~snilsson/software/router/