+++ title = "Architecture Overview" description = "Describes the internal components of Ferrix and how a route is processed from reception to kernel installation and advertisement." weight = 1 [extra] nav = "Architecture" +++ This topic describes the internal components of Ferrix and how they process a route. It is intended for developers who work on the source code. Figure 1 shows the components and the path of a route through them.
Route processing in Ferrix UPDATE messages to neighbors, through the session output queue Neighbors TCP, port 179 Sessions one task each Import policy ยท RPKI Routing table shards best paths journal Exporters one per session Kernel netlink Status, API HTTP, metrics RTR client RPKI caches
Figure 1: Route processing in Ferrix
## Design Principles Ferrix is built on the following principles: - **Memory safety.** The workspace lints forbid `unsafe` code in every crate. - **No central routing table task.** Each session task applies the routes of an UPDATE message to the routing table directly, and locks only the shards that contain the affected prefixes. Sessions that update different shards do not wait for each other. A session that must wait is slowed down, which provides back-pressure without an unbounded queue in front of the routing table. - **Independent consumers.** The components that act on best path changes (the export task of each session, kernel route installation, and the status page) read the changes from a journal at their own rate. A slow consumer does not delay the routing table or other consumers. - **Fault isolation.** A panic ends only the task in which it occurs. If a session task panics, Ferrix closes the session, notifies the neighbor, and removes the neighbor's routes. Shared tables use locks that are not poisoned by a panic. The daemon exits only if it loses a component that it cannot operate without. - **Resistance to neighbor-triggered load.** Ferrix rate-limits log messages that a neighbor can trigger, closes connections from unconfigured addresses before reading from them, and places flapping neighbors in hold-down. ## Inbound Route Processing ### Connection Management The dialer (`server::dialer`) initiates a connection to each configured neighbor and retries with exponential backoff. The listener accepts connections from the addresses of configured neighbors only. The collision module (`server::collision`) retains one connection for each neighbor when both speakers initiate a connection (RFC 4271 Section 6.8). ### BGP Finite State Machine The BGP finite state machine (`server::fsm`) implements RFC 4271 Section 8 without input or output. It receives events and returns the actions to perform, so that each state transition can be tested with a function call. The connection driver (`server::connection`) converts received bytes and timer expirations into events, and performs the returned actions. ### Message Decoding The `packet` crate decodes and encodes BGP messages. Errors in UPDATE messages are handled as specified in RFC 7606: depending on the error, the attribute is discarded or the routes are treated as withdrawn. All parsers are fuzz-tested. ### Import Processing The `server::routes` module applies the routes of an UPDATE message to the routing table, according to the neighbor's import policy: - `server::policy` evaluates each route against the import policy. - `server::rpki` validates the route against the data that the RTR client (`rpki` crate) receives. - `server::reach` determines whether the next hop is resolvable. A route that is rejected for a reason that can change without a new UPDATE (RPKI invalid, ASPA invalid, or an unresolvable next hop) is stored as a held route. Ferrix reevaluates held routes when the RPKI data or the interfaces change. ### Routing Table The routing table (`rib::table`) combines the Adj-RIBs-In and the Loc-RIB (RFC 4271 Section 3.2) in one structure, organized by prefix. The entry for a prefix contains the routes from all neighbors and identifies the best path. After a change, best path selection examines only the entry for the affected prefix. The routing table is designed for hundreds of full routing tables: - A route occupies 16 bytes: a pointer to its path attribute set, and one word that encodes the neighbor and the first steps of best path selection as a single comparable value. - Path attribute sets are stored once and shared by all routes with identical attributes. Unused sets are removed periodically. - The table is divided into shards by a hash of the prefix. Each shard has its own lock. ## Outbound Route Processing ### Change Journal Each shard of the routing table maintains a journal of the prefixes whose best path changed. Each consumer reads the journal through its own reader. The journal records that the best path of a prefix changed, not the new best path; the reader retrieves the current best path when it processes the entry. A reader that falls too far behind compares its state with the routing table instead. Changes made together carry the same table-wide sequence stamp, so that a reader processes them together and they are advertised in the same UPDATE messages. ### Route Advertisement The `server::advertise` module runs an export task for each session. The task advertises the originated routes and, if the export policy enables `learned_routes`, the best paths learned from other neighbors, as the `server::export` module permits for each route. The task records the routes that it has advertised, groups routes with identical attributes into one UPDATE message, and waits when the session's output queue is full. ### Kernel Route Installation The `fib` crate reads best path changes and maintains the kernel routing table over rtnetlink, with one route for each prefix through the next hop of its best path. It also receives the router's interfaces and addresses from the kernel, which determine whether a next hop is resolvable. ### HTTP Interface The `web` crate provides read-only access to the state owned by the server. It does not store routing state. ## Crates Table 1 lists the crates of the workspace. | Crate | Contents | |---|---| | `crates/primitives` | Prefixes, address families, and parsing utilities | | `crates/packet` | BGP message encoding and decoding | | `crates/rib` | The routing table, best path selection, and the change journal | | `crates/rpki` | The RTR client, route origin validation, and ASPA verification | | `crates/fib` | Kernel route installation over netlink | | `crates/server` | Sessions, the finite state machine, policy, export, and configuration | | `crates/web` | The status page, HTTP API, and metrics | | `crates/integration-tests` | Tests that run the server against in-process BGP peers | | `crates/bench` | Benchmarks, with a synthetic Internet routing table | | `bin/bgp` | The daemon | The API documentation of each crate (`cargo doc --open`) describes its modules. Module-level comments document the reasons for each design.