VyOSXXX · BGP Route ReflectorsRoute reflectors
Cluster-id — the deduplication key, and why it decides how your clients must peer
What you'll learn
- Configure `set protocols bgp parameters cluster-id` on VyOS 1.5 LTS
- State when a route reflector stamps ORIGINATOR_ID and CLUSTER_LIST, and when it does not
- Explain why two RRs sharing a cluster-id must each peer with every client in the cluster
- Predict which copy of a reflected route a client installs, using the real tie-break order
- Diagnose a mismatched cluster-id and an accidental cluster-id collision, which fail in opposite directions
Prerequisites
Verified against VyOS 1.5.x LTS (circinus) · VyOS 1.4.x (sagitta) — legacy · FRRouting 10.x (VyOS 1.5) · Linux kernel 6.6 LTS (VyOS 1.5 base) · strongSwan 5.9.x (IPsec) · WireGuard 1.0.x (kernel module + userspace tooling) · 2026-08-19
The cluster-id is the loop-prevention key for a route reflector
cluster. A route reflector prepends it to the CLUSTER_LIST attribute
on every route it reflects, and discards any advertisement that
arrives with its own cluster-id already in that list.
That is the whole mechanism, and it is two sentences long. The interesting part is the design consequence, which is not obvious and which operators discover the hard way: two RRs that share a cluster-id throw away each other’s reflected routes, so every client in the cluster must have a session to both of them. Get that wrong and the network works — until the day a client’s only RR reboots.
Where the cluster-id lives on VyOS 1.5
set protocols bgp system-as 65001
set protocols bgp parameters router-id 10.255.0.5
set protocols bgp parameters cluster-id 10.255.255.1
Both the router-id and the cluster-id sit under parameters in the
1.4/1.5 tree. The local AS is declared once with system-as, and
neighbours hang off set protocols bgp neighbor ... without repeating
it — the 1.3 shape that embedded the AS number in every line
(set protocols bgp 65001 cluster-id ...) is rejected by commit on
1.5.
If you do not set a cluster-id, the RR uses its own router-id. That default is correct for a single-RR cluster and wrong for every redundant one, for a reason the next section makes concrete.
What a route reflector stamps, and when
An RR divides its internal peers into clients and non-clients, and reflects:
- a route learned from a client to all other clients and to all non-clients;
- a route learned from a non-client to all clients only.
On each of those reflections it adds two optional non-transitive attributes:
ORIGINATOR_ID— the BGP identifier of the router inside this AS that originated the route. Both RRs in a cluster set it to the same value, because it names the originating client, not the reflector.CLUSTER_LIST— the reflector prepends its own cluster-id.
Both are stripped before the route is advertised to an external peer; they are internal bookkeeping and mean nothing outside the AS.
The check on receipt is the loop-breaker: an RR that finds its own
cluster-id already in an advertisement’s CLUSTER_LIST ignores that
advertisement entirely. Not “prefers it less” — ignores it.
The canonical two-RR cluster
Two RRs, one cluster-id, every client peering with both:
# RR-1
set protocols bgp system-as 65001
set protocols bgp parameters router-id 10.255.0.5
set protocols bgp parameters cluster-id 10.255.255.1
set protocols bgp neighbor 10.0.0.1 remote-as 65001
set protocols bgp neighbor 10.0.0.1 address-family ipv4-unicast route-reflector-client
set protocols bgp neighbor 10.0.0.2 remote-as 65001
set protocols bgp neighbor 10.0.0.2 address-family ipv4-unicast route-reflector-client
set protocols bgp neighbor 10.255.0.6 remote-as 65001
# RR-2
set protocols bgp system-as 65001
set protocols bgp parameters router-id 10.255.0.6
set protocols bgp parameters cluster-id 10.255.255.1
set protocols bgp neighbor 10.0.0.1 remote-as 65001
set protocols bgp neighbor 10.0.0.1 address-family ipv4-unicast route-reflector-client
set protocols bgp neighbor 10.0.0.2 remote-as 65001
set protocols bgp neighbor 10.0.0.2 address-family ipv4-unicast route-reflector-client
set protocols bgp neighbor 10.255.0.5 remote-as 65001
Different router-ids, one shared cluster-id, and 10.255.0.5 /
10.255.0.6 peering with each other as ordinary non-client iBGP
neighbours. Note that route-reflector-client is a property of the
address family, not of the neighbour: it is set under
address-family ipv4-unicast, and a session that carries IPv4 and
IPv6 needs the flag set in each family you intend to reflect.
Now follow a prefix from client 10.0.0.1:
sequenceDiagram
participant A as Client 10.0.0.1
participant RR1 as RR-1 (cluster 10.255.255.1)
participant RR2 as RR-2 (cluster 10.255.255.1)
participant B as Client 10.0.0.2
A->>RR1: iBGP update, prefix X
A->>RR2: iBGP update, prefix X
RR1->>B: reflect X — Originator 10.0.0.1, Cluster list [10.255.255.1]
RR2->>B: reflect X — Originator 10.0.0.1, Cluster list [10.255.255.1]
RR1->>RR2: reflect X to non-client
RR2->>RR2: own cluster-id in CLUSTER_LIST — ignore
RR2->>RR1: reflect X to non-client
RR1->>RR1: own cluster-id in CLUSTER_LIST — ignore
Two things in that diagram are worth stopping on.
Client 10.0.0.2 receives two copies, one from each RR. The
cluster-id did not suppress a copy; both RRs reflected the route they
each learned directly from 10.0.0.1. This is the redundancy: if
RR-1 disappears, the copy from RR-2 is already in the client’s table
and the client re-runs best path locally.
The RR-to-RR advertisements are both discarded. Each RR sees its own cluster-id in the list and ignores the update. That is correct and intended — but it means the RR-to-RR session carries no reflected routes at all, which leads directly to the rule below.
Which copy the client installs
The client has two copies that are identical in every attribute the originating router set. The tie-break runs a long way down before it finds a difference, and knowing where it lands saves an argument:
- Weight, local-preference, AS-path length, origin, MED, eBGP over iBGP, IGP metric to the next hop — all identical, because both copies describe the same route from the same originator with the same next-hop.
- Router-id, with
ORIGINATOR_IDsubstituted when present. Both copies carry the sameORIGINATOR_ID— the originating client — so this ties too. This is where “the lowest router-id of the two RRs wins” would be true if the attribute were the reflector’s identity. It is not. CLUSTER_LISTlength. Both are length 1 in this topology, so this ties as well. It is the step that decides a multi-cluster topology, where one copy has been reflected twice.- The neighbour’s peer address. The lowest wins.
So in the canonical design the winner is decided by which RR has the
lower peering address — an outcome nobody designed and nobody should
depend on. If you need the clients to prefer a particular RR, express
it with local-preference or weight in an import policy, which are
compared at the top of the list rather than the bottom.
Reading it on the box
vyos@rr1:~$ show ip bgp 10.20.0.0/24BGP routing table entry for 10.20.0.0/24
Paths: (2 available, best #1, table default)
Advertised to non peer-group peers:
10.0.0.2
Local
10.0.0.1 from 10.0.0.1 (10.0.0.1)
Origin IGP, metric 0, localpref 100, valid, internal, best (Peer Address)
Last update: Tue Aug 19 09:14:22 2026
Local
10.0.0.1 from 10.255.0.6 (10.255.0.6)
Origin IGP, metric 0, localpref 100, valid, internal
Originator: 10.0.0.1, Cluster list: 10.255.255.1
Last update: Tue Aug 19 09:14:22 2026Illustrative output
Three details to read out of that:
- The
Originator:andCluster list:line appears only on the path that was reflected. The first path came straight from the client and carries neither. from 10.255.0.6 (10.255.0.6)is the peer that sent this copy; theOriginatoris who put the route into the AS. Confusing the two is the commonest misreading of this output.best (Peer Address)names the tie-break step that actually decided. When that reason isPeer Address, everything above it tied — which is the normal, expected outcome in a shared-cluster design and not a sign that anything is misconfigured.
The same fields are available from show ip bgp 10.20.0.0/24 json.
Read the key names off your own image rather than hard-coding them
into a parser: FRR’s JSON schema is stable in intent but has moved in
detail across releases, and a monitoring check that silently stops
finding a key reports health rather than an error.
Multi-cluster: different cluster-ids
Giving each RR its own cluster-id makes them separate clusters. The RR-to-RR advertisements are no longer discarded, so each RR re-reflects the other’s routes to its own clients:
# RR-1
set protocols bgp parameters cluster-id 10.255.255.1
# RR-2
set protocols bgp parameters cluster-id 10.255.255.2
A client of RR-2 now receives the prefix twice: once from RR-2’s own
reflection of the copy it learned directly, with CLUSTER_LIST
[10.255.255.2], and once from RR-2’s re-reflection of RR-1’s copy,
with [10.255.255.2, 10.255.255.1]. Step 3 of the tie-break —
CLUSTER_LIST length — is what separates them, and the singly-reflected
copy wins.
The honest trade-off between the two designs is not “redundancy versus load balancing”. It is this:
| Shared cluster-id | Distinct cluster-ids | |
|---|---|---|
| RR-to-RR reflections | Discarded | Accepted and re-reflected |
| Client peering requirement | Every client must peer with every RR in the cluster | A client may peer with one RR and still be reachable |
| Paths a client holds | One per RR it peers with | More — its own RRs’ copies plus re-reflections |
| State and update churn | Lower | Higher, and it grows with the number of clusters |
| Typical use | One site, one cluster, full client peering | Clusters that map to sites or regions |
Neither is more correct. Choosing the shared cluster-id and then attaching clients to a single RR is what is wrong, and it is a combination that looks tidy on a diagram.
Failure modes
The cluster-id is set on one RR and not the other
The intended design was a shared cluster. RR-1 has an explicit cluster-id; RR-2 fell back to its router-id. The two RRs are now separate clusters, and they accept and re-reflect each other’s routes.
There is no loop — each RR still discards anything carrying its own cluster-id — but every client holds more copies than the design predicted, and the update churn is higher than it should be.
The dangerous part is what it conceals. A client attached to only one RR works fine in this state, because the other RR re-reflects. The misconfiguration is discovered when somebody “corrects” the cluster-id to match, and the singly-attached clients go dark at that commit.
Diagnostic: show configuration commands | match cluster-id on every
RR, and compare. Before correcting it, audit client peering with
show ip bgp summary on both RRs.
Two RRs in different clusters share a cluster-id by accident
The inverse, and it is worse. A configuration copied between sites carries the cluster-id with it. The two clusters now discard each other’s reflected routes, and the two halves of the AS cannot see each other’s prefixes.
This one fails silently and asymmetrically: prefixes that reach both halves by another path still work, so the outage is partial and looks like a routing policy problem.
Diagnostic: a prefix that is present on one RR and absent on the other with no policy explaining the difference. Confirm by comparing cluster-ids across sites, not just within a site.
Fix: allocate cluster-ids centrally, one per cluster, from a documented range.
route-reflector-client set on the RR-to-RR session
The two RRs are configured as each other’s clients. A route learned from a client is now reflected to a peer that also treats it as a client relationship, and the topology no longer matches the one you drew.
Diagnostic: show configuration commands | match route-reflector-client
should list only client sessions. The other RR is a non-client; that
is the whole distinction.
The cluster-id was changed on a live cluster
Changing it re-stamps every reflected route with a new CLUSTER_LIST,
which means every client re-runs best-path selection over the whole
table. On a large table that is a measurable event, not a free one.
Treat it as a maintenance-window change, do it on one RR at a time
with commit-confirm, and confirm client tables have reconverged
before touching the second.
Rollback
compare
commit-confirm 5
comparebefore the commit, every time — a cluster-id change is one short line with a fleet-wide effect, and it is easy to type into the wrong RR.commit-confirm 5for any change made over a session that traverses the cluster you are editing.rollback N; commit; savereverts to a previous revision.load /config/archive/<known-good-file>; commit; saverestores a specific snapshot.
Rolling back a cluster-id costs a second full re-convergence, exactly like applying it did. Plan the window for two events, not one.
Production discipline
Cross-course references
The route-reflector lessons vyos-xxx-01-rr-concept and
vyos-xxx-02-rr-config cover the reflection model and the base
configuration this lesson extends; vyos-xxx-04-confederations covers
the alternative scaling mechanism, and vyos-xxx-06-rr-troubleshoot
walks the debugging of reflection failures. The BGP best-path lessons
in Part XXVI cover the full selection order that this lesson enters at
step 10. The OPNsense course’s XXX-OPNsense-DynamicRouting covers the
same FRR mechanism on the firewall side.
Quiz
Knowledge check · 4 questions
Q1. Two route reflectors serve one cluster. What cluster-id configuration does the canonical design use, and what does it require of the clients?
Q2. The CLUSTER_LIST attribute is propagated to eBGP peers so the upstream can see which cluster reflected the route.
Q3. The cluster-id was set on one RR and not the other. Explain what the network is actually doing, and why correcting it is more dangerous than leaving it.
RR-1 has `set protocols bgp parameters cluster-id 10.255.255.1`. RR-2 has no cluster-id configured, so it uses its router-id, 10.255.0.6. The design document describes a single cluster with two RRs. Clients hold more paths than the document predicts and the RRs' update counters are higher than the other sites'. Several clients, added over the past year, peer with only one of the two RRs. Everything currently works. A change ticket has been raised to 'align the cluster-id on RR-2 with RR-1'.
Q4. Two sites, each with its own RR cluster, cannot see each other's prefixes. The configurations look correct. Find the cause.
Two sites each run a two-RR cluster, and the four RRs are fully meshed with iBGP. Site A's clients cannot see Site B's prefixes and vice versa, although each site's own prefixes are fine. There is no import or export policy on the inter-site sessions, the sessions are Established, and prefix counters on those sessions are non-zero in the received direction. Site B was built by copying Site A's RR configuration and editing the addresses.
Passing score: 75%. Answers are checked in this browser.