Source Code Guide

From Ouroboros
Jump to navigation Jump to search

Under contruction This page is under construction  

The main git repository can be browsed online. We have mirrors on github or bitbucket if you prefer.
For bug reports, feature requests or contributions (including git guidelines), see the contact page.

Directory structure

  • cmake: This directory contains some CMake additions for building O7s.
  • doc: This directory holds some documentation, most notably the man pages.
  • include: Includes public O7s headers for implementing Ouroboros programs and management tools, and some internal headers that expose functions of the library needed by other O7s components.
  • src: The main implementation, which holds
    • ipcpd: Reference implementation of the different IPCPs.
      • common: Elements common to Broadcast and Unicast IPCP
        • connmgr: Connection manager
        • enroll: Enrollment
      • broadcast: Broadcast IPCP
      • unicast: The unicast IPCP implementation has the following components:
        • main: The main entry point into the IPCP.
        • addr-auth: Address authority. This component is used to assign the addresses to the IPCPs. This is an relic from the RINA implementation and will be redesigned to reflect more accurately that addresses are composed of names of forwarding elements.
        • ca: Congestion avoidance module allowing to select different congestion avoidance strategies. Has multiple policies.
        • connmgr: Connection manager, it is uses to allocate data transfer (and management) flows between IPCPS.
          • Data transfer flows carry N + 1 traffic and some internal traffic for the directory and flow allocator.
          • Management flows are used for the routing component. Will be removed as management flows should be implemented by N - 1 broadcast layers. This component is shared with the broadcast IPCP.
        • dir: Directory module that is backed by a key-value store mapping hashed N + 1 application names to N addresses. A Distributed Hash Table (dht) is the currently implemented policy.
        • dt: The data transfer component creates and manages the actual forwarding elements.
        • fa: The flow allocator, which contains the logic and state needed to create, manage and destroy N + 1 flows.
        • pff: The packet forwarding function that selects the outgoing flow(s) on which to forward an incoming DT packet. Currently implemented policies allow for a simple, loop-free alternate or multipath capable forwarding table (PFT).
        • psched: The packet schedulers that schedule packets coming from N - 1 or N + 1 flows, prioritizing them to be handled by the data transfer component.
        • routing: The component that implements the mechanism to populate the PFTs. Currently holds a link-state routing policy to build an adjacency graph of the layer.
      • eth: Ethernet IPCs, tunneling O7s over Ethernet DIX or Ethernet 802.2 (LLC).
      • udp: UDP IPCP, tunneling O7s over UDP/IPv4.
      • local: A loopback IPCP for local IPC, used for testing.
    • irmd: Reference implementation of a basic IRMd.
    • lib: Implementation of various library functions.
    • tools: Some tools, such as small test programs and the IPC Resource Management CLI for managing O7s.

Variables

Ouroboros index of commonly used variable names
type name Description
int fd flow descriptor. It's the flow analogue to a file descriptor.
struct fcrti * frcti State associated with the flow and retransmission control task, the logic that implements the FRCP protocol.
size_t idx This idx is an index in the packet buffer (rdrbuff) and used to calculate the location of the packet in reference the base pointer at which the rdrbuff is mapped into each process' memory space.
struct shm_du_buff * sdb shared memory (shm) data unit (du) buffer. The ouroboros 'flavored' skb. An sdb is a (fixed size) buffer that contains the actual packet data and spare space for (future) packet headers (and packet tails such as CRC), in addition to some metadata for managing the buffer (head and tail offset to the start of the packet, total size of the buffer, ...). The term 'data unit' stems from the OSI SDU (service data unit) and PDU (protocol data unit), which were adopted by RINA.
struct shm_rbuff * tx_rb, rx_rb, rb ring buffer. These (small) ring buffers are shared between 2 processes, used to efficiently move packets through the local pipeline. They pass an index that points to an sdb in the rdrbuff, and are the entry and exit structures with which the application (either a user application or an IPCP) interacts with the end-to-end flow. Writing happens to the transmit ring buffer tx_rb, reading from the receive ring buffer rx_rb.
struct shm_rdrbuff * rdrb random deletion ring buffer, a quick-and-dirty memory allocator. The block of memory in which Ouroboros stores actual packet data (and metadata) in a (ring buffer) of sdbs. Packets are allocated at the head of the ringbuffer and removed from the tail. Packets are marked on deletion, and deleting the tail causes a cleanup operation of all marked packets. Needs to be replaced with a more efficient memory allocator at some point.

Coding Guidelines

Coding guidelines

The coding guidelines of the main Ouroboros stack are similar as those of the Linux kernel (https://www.kernel.org/doc/html/latest/process/coding-style.html) with the following exceptions:

  • Soft tabs are to be used instead of hard tabs
  • A space is to be inserted between a pointer and its object name upon declaration or in function signatures. Example:


   int * a;

instead of

   int *a;

Don’t explicitly cast malloc, but do

   ptr = malloc(sizeof(*ptr) * len);

When checking for invalid pointers use

   if (ptr == NULL)

instead of

   if (!ptr)

When in doubt, just browse the code a bit.

Component lifespan

All (sub)components have a similar structure to them.

First their static objects are allocated. If the base object is allocated on the stack, the functions are called init (some internals may be allocated on the heap). If the base object is allocated on the heap (malloc) the functions are called create.

If the object has active components (e.g. spawns internal threads), they will activate as part of a start() call. Do not start threads in an init or create call!

At the end of the object's lifetime, first the threads are stopped (stop()) call, and the object is destroyed via a fini() call if it was living on the stack, or a destroy() call if it was allocated on the heap.

   init() / create()
   
   start()
 
   stop()

   fini() / destroy()

Library lifespan

Ouroboros is provided as a shared library [1] to dynamically link against. When a program dynamically linking to ouroboros-dev (like e.g. oecho) is started, it automatically loads this shared library at load time. The Ouroboros dynamic library has some .init and .fini sections [2] containing the init()/fini() functions, which are run when (un)loaded.

Interesting to know is that this init() function will exit the process if the IRMd is not reachable. Thus, the process is stopped at load time, meaning that even a simple oecho --help will not work. This is intended, because ultimately, the IRMd is considered part of the always present Operating System (and no threads should be actively waiting for this invariant). The --help case is unusual enough for networked applications that we don't want to design against the actual goals/principles [3].

A workaround is loading the shared library at runtime, only when needed to start the communication. This uses e.g. dlopen()/dlclose()/... . In fact, an early implementation required a call to ouroboros_init() and ouroboros_fini(), but this is not ergonomic nor aligned with the goals[3]. [4]

Notes

  1. the *.so files on Linux, in the ELF format
  2. Actually, for O7s they are .init_array/.fini_array on most platforms, but the idea remains the same.
  3. 3.0 3.1 At some point, we want to get rid of the POSIX sockets and bootstrap/run all IPC over O7s, and when that is the case, the IRMd needs to be up at init. That is one of the promises of the prototype: unification of all IPC and networking.
  4. Another possibility for the O7s library is to hide this in e.g. flow_alloc(), but it would be inefficient to check this each time.