Skip to content

bgp: support route reflector clients (RFC 4456) - #133

Open
gusbourg wants to merge 4 commits into
holo-routing:masterfrom
gusbourg:bgp-rr-server
Open

bgp: support route reflector clients (RFC 4456)#133
gusbourg wants to merge 4 commits into
holo-routing:masterfrom
gusbourg:bgp-rr-server

Conversation

@gusbourg

@gusbourg gusbourg commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Adds BGP route reflection (RFC 4456):

  • route-reflector neighbor config (client flag + configurable cluster-id, defaulting to the router-id) via the IETF ietf-bgp route-reflector container.
  • Reflection on advertisement: client-learned routes to all iBGP peers, non-client-learned routes to clients only.
  • ORIGINATOR_ID and CLUSTER_LIST set on reflected advertisements; received routes are rejected when either would form a reflection loop.
  • A route_reflector conformance topology.

🤖 Generated with Claude Code

gusbourg and others added 4 commits July 5, 2026 10:41
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Expose the IETF neighbor route-reflector container and use the client and cluster-id settings when disseminating iBGP routes. Reflected advertisements now carry ORIGINATOR_ID and CLUSTER_LIST, and received routes are rejected when ORIGINATOR_ID or CLUSTER_LIST would create a reflection loop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Use the route reflector server cluster ID when reflecting iBGP routes, including advertisements to non-client peers, and add a conformance topology covering client reflection, cluster-loop rejection, and non-client reflection scope.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@Paul-weqe Paul-weqe left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surprising this wasn't already supported for route reflectors...good catch. In any case, here's my review below.

I'll take a deeper look at the logic in BGP's event, rib, and neighbor files, but these were the comments that stood out to me first.

instance.state.schedule_decision_process(instance.tx);
}

fn schedule_decision_process_all_afs(instance: &mut Instance) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we have the calls to this moved to the EventQueue, we will probably end up changing &mut Instance to mut InstanceUpView<'_>, but that should come later.

@@ -0,0 +1,5 @@
{"RouterIdSub":{}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for including conformance tests here-super helpful.

One suggestion: the tests would be more meaningful with more than one device. With only rt-1 and route reflectors configured, we're mainly validating that the configuration parses correctly, not that route reflection actually works between routers.

output/northbound-state.json can help confirm the expected final state in a multi-device setup. There's also a way to generate these conformance tests automatically from the holo munet topologies, which should make this easier going forward.

I'll update the README to document that process.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README to holo-munet topologies has been updated:

https://github.com/holo-routing/holo-munet-topologies#conformance-tests


schedule_decision_process_all_afs(instance);
})
.delete_apply(|instance, args| {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick note: the YANG path

/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-bgp:bgp/neighbors/neighbor/route-reflector/client is a boolean, see here:

So it doesn't support the DELETE operation. Might be worth adjusting.

@Paul-weqe Paul-weqe Jul 10, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gusbourg
A PR helping us to catch this during compilation has been opened: #158

let nbr_addr = args.list_entry.into_neighbor().unwrap();
let nbr = instance.neighbors.get_mut(&nbr_addr).unwrap();

let cluster_id = args.dnode.get_string();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick suggestion on fetching cluster_id: could we use get_u32() directly and convert to Ipv4Addr, rather than going through a string parse first? Should avoid an unnecessary conversion step.

Also, a question on the cluster_id type. Would making it an Option<u32> work here? It'd sidestep the extra parsing either way, though happy to hear if there's a reason for the current approach.

let cluster_id = cluster_id.parse::<Ipv4Addr>().unwrap_or_else(|_| Ipv4Addr::from(cluster_id.parse::<u32>().unwrap()));
nbr.config.route_reflector.cluster_id = Some(cluster_id);

schedule_decision_process_all_afs(instance);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might fit better as an event added to the event queue. We could introduce a new variant in the Event enum, say ScheduleRRDecisionProcess and call schedule_decision_process_all_fs from there.
That would also keep it consistent with how holo handles events elsewhere in the configurations.


nbr.config.route_reflector.cluster_id = None;

schedule_decision_process_all_afs(instance);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above...this should be an event added to the event queue

Comment thread holo-protocol/src/lib.rs
// ===== global functions =====

pub fn spawn_protocol_task<P>(
name: String,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As had been proposed in #132 this can all be one function and have the "testing" feature handle the extra fields and additional logic.

Paul-weqe added a commit to Paul-weqe/holo that referenced this pull request Jul 10, 2026
Not every YANG node structurally supports every CRUD operation (e.g. a
leaf with a plain default and no when/case can't be deleted, list keys
can't be modified or deleted). This was only checked at daemon startup
(validate_callback), so an invalid registration like .delete_apply()
on such a node compiled fine and only surfaced as a runtime crash.

Make YangPath and CallbacksBuilder a typestate: yang_codegen now emits
a zero-sized Caps marker per node and implements SupportsCreate/
SupportsModify/SupportsDelete/SupportsLookup on it based on the same
CallbackOp::is_valid rules enforced at runtime today. CallbacksBuilder's
create_*/modify_*/delete_*/lookup methods are now gated on the matching
trait, so an invalid registration fails to compile instead of panicking
at startup.

Drop validate_callback, now fully superseded. validate_callbacks
(checks for missing, not invalid, callbacks) is unaffected. Basically
making sure invalid calls are caught at compile time not at runtime.

Concern raised when invalid callbacks were made in a boolean Yang Path:
PR: holo-routing#133
Comment: holo-routing#133 (comment)

Signed-off-by: Paul Wekesa <paul1tw1@gmail.com>
Paul-weqe added a commit to Paul-weqe/holo that referenced this pull request Jul 16, 2026
Not every YANG node structurally supports every CRUD operation (e.g. a
leaf with a plain default and no when/case can't be deleted, list keys
can't be modified or deleted). This was only checked at daemon startup
(validate_callback), so an invalid registration like .delete_apply()
on such a node compiled fine and only surfaced as a runtime crash.

Make YangPath and CallbacksBuilder a typestate: yang_codegen now emits
a zero-sized Caps marker per node and implements SupportsCreate/
SupportsModify/SupportsDelete/SupportsLookup on it based on the same
CallbackOp::is_valid rules enforced at runtime today. CallbacksBuilder's
create_*/modify_*/delete_*/lookup methods are now gated on the matching
trait, so an invalid registration fails to compile instead of panicking
at startup.

Drop validate_callback, now fully superseded. validate_callbacks
(checks for missing, not invalid, callbacks) is unaffected. Basically
making sure invalid calls are caught at compile time not at runtime.

Concern raised when invalid callbacks were made in a boolean Yang Path:
PR: holo-routing#133
Comment: holo-routing#133 (comment)

Signed-off-by: Paul Wekesa <paul1tw1@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants