Skip to content
Thomas Mangin edited this page Mar 6, 2026 · 4 revisions

ExaBGP Design

ExaBGP's internal architecture is built around a single event loop managed by the reactor.

Reactor

The reactor shares time between network I/O and management of forked API processes. In ExaBGP 6.x, the reactor uses Python's asyncio event loop, while earlier versions used a custom generator-based loop. Both approaches achieve the same goal: non-blocking concurrent handling of multiple BGP peers.

Non-Blocking I/O

The code is non-blocking throughout, using co-routines (generators that yield instead of return). This allows producing and consuming data incrementally without large memory allocations — similar to Unix pipes.

Network Stack

The networking code has three layers:

  • Connection — non-blocking socket I/O layer using select(2). A forever-running generator that returns nothing when no data is available, or raw bytes when data arrives.
  • Protocol — wraps Connection to provide BGP message-level API. Returns NOP when no complete message is available, or a Message subclass (Open, Update, KeepAlive, Notification) when a full message is formed.
  • Peer — each BGP neighbor gets a Peer instance that drives the protocol state machine using co-routines.

The reactor manages all peers and dispatches messages between them and the forked API processes.

Error Handling

Peer code is wrapped in try/except. On error, a BGP NOTIFICATION is sent to the peer, the session is torn down, and re-established if configured.

Fair Scheduling

API processes can generate large volumes of data. The reactor runs each cycle for approximately one second:

  • Up to 0.5 seconds consuming data from API pipes
  • Remaining time handling network I/O (keepalives, updates)
  • If networking completes early, more pipe data is consumed
  • If idle, the reactor sleeps until the next second

This guarantees keepalive messages (heartbeats) are sent reliably even under heavy API load.

Route Processing Pipeline

When an API process sends a route announcement:

  1. The text/JSON command is parsed into a Change (single NLRI + attributes)
  2. The reactor adds the Change to the outgoing RIB of the target peer(s)
  3. Changes are grouped by attributes for efficient UPDATE message packing
  4. Each peer's RIB generates a co-routine consumed by the networking code
  5. The consumption is itself a co-routine — the entire pipeline is non-blocking

See also: Architecture for a more detailed component reference.

Clone this wiki locally