Skip to content

route reflection

Thomas Mangin edited this page Jul 25, 2026 · 4 revisions

Pre-Alpha. This page describes behavior that may change.

Ze operates as both a route server (RFC 7947) and an iBGP route reflector (RFC 4456). Route serving is the bgp-rs plugin driven by process bindings; it forwards every received route to every other peer with zero-copy wire optimization when peers share an encoding context. iBGP route reflection is a session-level option (session/route-reflector-client plus session/cluster-id) that turns an iBGP peer into a reflector client and applies the RFC 4456 rules (ORIGINATOR_ID, CLUSTER_LIST, client vs non-client forwarding).

This page covers both. Pick the mode that matches the deployment: a route server at an IXP or transit exchange, or a route reflector inside an autonomous system.

iBGP route reflection (RFC 4456)

An iBGP peer is turned into a reflector client by setting session/route-reflector-client true; on it. Ze will then apply the RFC 4456 forwarding rules to routes learned from that peer: routes from a client are forwarded to all other peers (clients and non-clients), routes from a non-client are forwarded to clients only, and the ORIGINATOR_ID plus CLUSTER_LIST attributes are handled automatically.

bgp {
    router-id 10.0.0.254;
    session {
        asn { local 65000; }
        cluster-id 10.0.0.254;          # optional; defaults to router-id
    }

    peer client-a {
        connection { remote { ip 10.0.0.1; } }
        session {
            asn { remote 65000; }        # iBGP: same AS
            route-reflector-client true;
            family { ipv4/unicast { prefix { maximum 1000000; } } }
        }
    }

    peer client-b {
        connection { remote { ip 10.0.0.2; } }
        session {
            asn { remote 65000; }
            route-reflector-client true;
            family { ipv4/unicast { prefix { maximum 1000000; } } }
        }
    }
}

What Ze does on reflected routes

  • ORIGINATOR_ID. Set to the source peer's Router ID if the attribute is absent on ingress. If it is already present (the route has been reflected before), the existing value is preserved.
  • CLUSTER_LIST. Prepended with the local cluster ID (from session/cluster-id, falling back to bgp/router-id). If CLUSTER_LIST is absent on ingress it is created. Existing values are preserved.
  • Loop detection. On ingress, if ORIGINATOR_ID matches the local Router ID, or the local cluster ID is in CLUSTER_LIST, the route is treated as a loop and dropped. This is the RFC 4456 Section 8 check and runs as part of the built-in loop-detection filter (see route filters).
  • CLUSTER_LIST cluster-id. If you set session/cluster-id on the peer, the same value is used for egress prepending AND ingress loop detection. Ze keeps the two in sync automatically, so you only need to configure it once.

Client vs non-client

Source peer Destination peer Forwarded?
Client Any iBGP peer (client or non-client) Yes
Non-client Client Yes
Non-client Non-client No (RFC 4456 — non-clients rely on the full iBGP mesh to exchange among themselves)
eBGP Any iBGP peer Yes

A peer with route-reflector-client false (the default) is a non-client. Mixing clients and non-clients on the same reflector is the normal setup for a partial-mesh deployment.

Multiple reflectors in a cluster

Set the same cluster-id on every reflector in the cluster. Ze's CLUSTER_LIST prepend uses that value, and the CLUSTER_LIST loop check on ingress uses the same value, so routes bounce between cluster members only when the cluster ID really is different.

Route server (RFC 7947)

The route server model uses the bgp-rs plugin, bound per peer via process. It forwards every received route to every other peer with no best-path selection, which is what IXP members expect: the server is a transparent bus and the members run their own policy downstream.

# bgp-rs and bgp-adj-rib-in are internal plugins; no separate "plugin { }" block is
# needed unless you want to override defaults.

bgp {
    router-id 10.0.0.254;
    session { asn { local 65000; } }

    peer client-a {
        connection { remote { ip 10.0.0.1; } }
        session {
            asn { remote 65001; }
            family { ipv4/unicast { prefix { maximum 1000000; } } }
        }
        process bgp-rs         { receive [ update ]; send [ update ]; }
        process bgp-adj-rib-in { receive [ update state ]; }
    }

    peer client-b {
        connection { remote { ip 10.0.0.2; } }
        session {
            asn { remote 65002; }
            family { ipv4/unicast { prefix { maximum 1000000; } } }
        }
        process bgp-rs         { receive [ update ]; send [ update ]; }
        process bgp-adj-rib-in { receive [ update state ]; }
    }
}

Every peer gets both plugin bindings. bgp-rs does the forwarding. bgp-adj-rib-in stores received routes so they can be replayed when a peer reconnects.

The forward-all model

The route server forwards every received route to every other peer. There is no best-path selection. This is RFC 7947 and it is what IXP members expect: the IXP is a transparent bus that propagates routes between members, and the members run their own policy.

The combination of the route server model and ADD-PATH is what lets every downstream make its own best-path decision. Without ADD-PATH you can only forward one path per prefix to each client; with ADD-PATH every path is available. See ADD-PATH.

Zero-copy forwarding

When two peers negotiate identical capabilities (same ADD-PATH mode, same ASN format, same extended-message support), they share the same encoding context, identified by a ContextID. Routes between peers with matching contexts are forwarded as raw wire bytes: no parse, no rebuild, no allocation. The reactor looks up the cached message, confirms the contexts match, and ships the bytes straight back out.

This is the fast path and the reason a route server with many identically-configured clients runs well on Ze. When contexts differ (for example, one peer without ADD-PATH forwarding to one with ADD-PATH), a progressive build walks the cached attributes and re-encodes for the destination.

Forwarding workers and congestion

Each destination peer has its own long-lived forwarding worker goroutine with a buffered channel. When a destination peer is slower than the update rate, the channel absorbs short bursts (default capacity 4096 items). If the channel fills, items go into a per-worker overflow buffer, and the worker fires a congestion event visible in logs and Prometheus.

The overflow uses a two-tier pool. Per-peer pools (64 slots) absorb steady-state traffic. A shared MixedBufMux pool, auto-sized from peer prefix maximums or overridable via ze.fwd.pool.size, bounds overflow memory across every peer. Routes are never dropped: missing a route update is worse than using extra memory. When a peer catches up and the channel drains below 25 %, congestion clears.

The Prometheus metrics that show this behaviour:

Metric Meaning
ze_bgp_pool_used_ratio Global overflow pool utilisation.
ze_bgp_overflow_items{peer} Items in a per-destination overflow buffer.
ze_bgp_overflow_ratio{source} Per-source overflow ratio.

If ze_bgp_pool_used_ratio is above 0.8 in steady state, you are close to congestion teardown and should investigate which peer is lagging.

Convergent replay

When a peer reconnects, the route server replays every stored route from the other peers. The replay is three phases:

  1. Full snapshot replay from the Adj-RIB-In.
  2. Delta loop that catches routes received during the replay.
  3. End-of-RIB sent when the replay has caught up.

A batch of bug fixes in 2026 closed three latent bugs in this path (wrong-direction dispatch for sent events, nil WireUpdate on reflected UPDATEs, and wrong format when the cached route came from the text codec). The event delivery on replay is now event-driven rather than a fixed sleep, and graceful restart, LLGR, and refresh-on-reconnect tests that had flaky timeouts all pass reliably.

The plugin bindings in detail

bgp-rs plugin. Needs receive [ update ] (so it sees routes coming in) and send [ update ] (so it can forward routes back out). Every peer that participates in the route server must have these bindings.

bgp-adj-rib-in plugin. Stores received routes for replay on peer reconnect. Needs receive [ update state ]. If you do not bind it, reconnecting peers will not get a replay and convergence will be slower.

Cache commands

Routes in transit are exposed through the cache surface.

cache list
cache forward <id> <peer>
cache release <id>

Use these when you want to inspect or reroute a specific cached message. They are more of a debugging surface than a day-2 operator tool, but they are the mechanism that lets a plugin take control of forwarding decisions for a specific UPDATE.

Without bgp-rs

When bgp-rs is not loaded, routes are not forwarded between peers. The bgp-rib plugin (if loaded) stores routes and runs best-path selection, but it does not re-advertise them. Ze becomes a passive route collector in that mode. Load bgp-rs when you want forwarding.

Route reflection vs route server

Aspect Route reflector (RFC 4456) Route server (RFC 7947)
Intended topology iBGP inside an AS eBGP between members at an IXP
Best-path selection Yes, then forward best path per prefix (or all paths with ADD-PATH) No, forward every path
Modifies NEXT_HOP By default yes on eBGP → iBGP borders (routable inside AS) No, forward unchanged (RFC 7947)
Adds ORIGINATOR_ID / CLUSTER_LIST Yes No
Config knob session/route-reflector-client, session/cluster-id process bgp-rs binding, process bgp-adj-rib-in binding
Plugin Built-in reactor logic bgp-rs plugin

Pick based on intent: if you want to distribute routes inside an AS without a full iBGP mesh, it is a route reflector. If you want a transparent route exchange where members build their own RIBs from every available path, it is a route server.

See also

Adapted from main/docs/guide/route-reflection.md.

Home

About

First Steps

Configuration

Operation

Interfaces

Plugins

Plugin Development

Chaos Testing

Blueprints

Development

Reference

Clone this wiki locally