LC-trie implementation notesÂķ
Node typesÂķ
- leaf
An end node with data. This has a copy of the relevant key, along with âhlistâ with routing table entries sorted by prefix length. See
struct leafandstruct leaf_info.- trie node or tnode
An internal node, holding an array of child (leaf or tnode) pointers, indexed through a subset of the key. See Level Compression.
A few concepts explainedÂķ
- Bits (tnode)
The number of bits in the key segment used for indexing into the child array - the âchild indexâ. See Level Compression.
- Pos (tnode)
The position (in the key) of the key segment used for indexing into the child array. See Path Compression.
- Path Compression / skipped bits
Any given tnode is linked to from the child array of its parent, using a segment of the key specified by the parentâs âposâ and âbitsâ In certain cases, this tnodeâs own âposâ will not be immediately adjacent to the parent (pos+bits), but there will be some bits in the key skipped over because they represent a single path with no deviations. These âskipped bitsâ constitute Path Compression. Note that the search algorithm will simply skip over these bits when searching, making it necessary to save the keys in the leaves to verify that they actually do match the key we are searching for.
- Level Compression / child arrays
the trie is kept level balanced moving, under certain conditions, the children of a full child (see âfull_childrenâ) up one level, so that instead of a pure binary tree, each internal node (âtnodeâ) may contain an arbitrarily large array of links to several children. Conversely, a tnode with a mostly empty child array (see empty_children) may be âhalvedâ, having some of its children moved downwards one level, in order to avoid ever-increasing child arrays.
- empty_children
the number of positions in the child array of a given tnode that are NULL.
- full_children
the number of children of a given tnode that arenât path compressed. (in other words, they arenât NULL or leaves and their âposâ is equal to this tnodeâs âposâ+âbitsâ).
(The word âfullâ here is used more in the sense of âcompleteâ than as the opposite of âemptyâ, which might be a tad confusing.)
LockingÂķ
fib_lock is used for an RW-lock in the same way that this is done in fib_hash.
However, the functions are somewhat separated for other possible locking
scenarios. It might conceivably be possible to run trie_rebalance via RCU
to avoid read_lock in the fn_trie_lookup() function.
Main lookup mechanismÂķ
fn_trie_lookup() is the main lookup function.
The lookup is in its simplest form just like fib_find_node(). We descend the
trie, key segment by key segment, until we find a leaf. check_leaf() does
the fib_semantic_match in the leafâs sorted prefix hlist.
If we find a match, we are done.
If we donât find a match, we enter prefix matching mode. The prefix length, starting out at the same as the key length, is reduced one step at a time, and we backtrack upwards through the trie trying to find a longest matching prefix. The goal is always to reach a leaf and get a positive result from the fib_semantic_match mechanism.
Inside each tnode, the search for longest matching prefix consists of searching through the child array, chopping off (zeroing) the least significant â1â of the child index until we find a match or the child index consists of nothing but zeros.
At this point we backtrack (t->stats.backtrack++) up the trie, continuing to chop off part of the key in order to find the longest matching prefix.
At this point we will repeatedly descend subtries to look for a match, and there are some optimizations available that can provide us with âshortcutsâ to avoid descending into dead ends. Look for âHL_OPTIMIZEâ sections in the code.
To alleviate any doubts about the correctness of the route selection process,
a new netlink operation has been added. Look for NETLINK_FIB_LOOKUP, which
gives userland access to fib_lookup().
CommentsÂķ
We have tried to keep the structure of the code as close to fib_hash as possible to allow verification and help up reviewing.
A good start for understanding this code. This function implements a straightforward trie lookup.
Inserts a new leaf node in the trie. This is bit more complicated than
fib_find_node(). Inserting a new node means we might have to run the level compression algorithm on part of the trie.Looks up a key, deletes it and runs the level compression algorithm.
The key function for the dynamic trie after any change in the trie it is run to optimize and reorganize. It will walk the trie upwards towards the root from a given tnode, doing a
resize()at each step to implement level compression.Analyzes a tnode and optimizes the child array size by either inflating or shrinking it repeatedly until it fulfills the criteria for optimal level compression. This part follows the original paper pretty closely and there may be some room for experimentation here.
Doubles the size of the child array within a tnode. Used by
resize().Halves the size of the child array within a tnode - the inverse of
inflate(). Used byresize();The route manipulation functions. Should conform pretty closely to the corresponding functions in fib_hash.
This walks the full trie (using
nextleaf()) and searches for empty leaves which have to be removed.Dumps the routing table ordered by prefix length. This is somewhat slower than the corresponding fib_hash function, as we have to walk the entire trie for each prefix length. In comparison, fib_hash is organized as one âzoneâ/hash per prefix length.