Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/ai/ibgp-design-revision.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Design: IBGP with Autonomous System

## Goal

Revise the current design of the ibgp layer and the `AutonomousSystem` class


## Design Principles

- Clearly separate the duties of `AutonomousSystem` class (AS) and the ibgp layers. Deciding the ibgp mode withing the AS and roles of routers should be done in the AS, not in the ibgp layer.
- Although IBGP might have many modes, we would like to stick to this principle: simple default model, clean extension points, minimal built-in special cases.
- Avoid adding special cases to core layers unless they represent major research or operational patterns.
- Every new API should have a small example and a test.


## Required Behavior


- Inside the Autonomous System class, set the following (use the first option as the default)
```
ibgp_mode = "full-mesh" | "route-reflector"
bgp_scope = "all-routers" | "edge-only"
core_forwarding = "plain-ip" | "mpls" | "sr" | "tunnel" | "redistribute"
```
- all-routers + full-mesh: All routers participate in ibgp using full-mesh peering
- all-routers + route-reflector: All routers participate in ibgp using route reflectors
- edge-only + full-mesh: Only edge routers participate in ibgp using full-mesh peerin; core routers do not participate in ibgp
- edge-only + route-reflector: Only edge routers participate in ibgp using route reflector; core routers do not participate in ibgp
44 changes: 44 additions & 0 deletions docs/ai/route-reflector-code-details.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# AS-level iBGP Mode Prompt

## Background

Original related code locations:

- `seedemu/core/AutonomousSystem.py`
- `createBgpCluster(address)` registers an RR cluster ID.
- `_aggregateBgpClusters()` aggregates cluster, RR, and client membership.
- The current implicit default cluster ID is hard-coded as `"10.0.0.0"`.
- `seedemu/core/Node.py`
- `Router.makeRouteReflector()` records whether a router is an RR.
- `Router.joinBgpCluster(cluster_id)` records the cluster a router belongs to.
- `seedemu/layers/Ibgp.py`
- `configure()` currently calls `asobj._aggregateBgpClusters()`.
- It currently uses `has_rr = any(len(rrs) > 0 ...)` to decide whether to use RR mode or full mesh mode.
- `_render_rr_mode()` writes RR/client session intent into routers.
- `_render_full_mesh_mode()` keeps the legacy full mesh behavior.
- `seedemu/layers/_bgp_metadata.py`
- `route_reflector_client` and `route_reflector_cluster_id` are BGP intent fields.
- BIRD renders `rr client` and `rr cluster id ...`.
- `seedemu/layers/Routing.py`
- FRR renders `bgp cluster-id ...` and `neighbor ... route-reflector-client`.

## Behavior Requirements

1. `Ibgp.configure()` must no longer infer the mode by itself using `has_rr`.
2. `Ibgp.configure()` should get the effective iBGP mode from `AutonomousSystem`:
- `"full-mesh"` calls `_render_full_mesh_mode()`.
- `"route-reflector"` calls `_render_rr_mode()`.
3. RR cluster aggregation, the default cluster ID, and default RR selection must all be completed inside `AutonomousSystem`.
4. For an AS to enter effective `"route-reflector"` mode, the user must explicitly call `setIbgpMode("route-reflector")`. Only after that should the following APIs be callable:
- `createBgpCluster()`.
- `makeRouteReflector(True)`.
- `joinBgpCluster(cluster_id)`.
5. If the user explicitly sets `"full-mesh"`, that setting should be respected unless RR-specific configuration already exists.
If there is a conflict, do not silently ignore it. Raise a clear error, for example:
`"AS2 has route-reflector cluster/router configuration but ibgp_mode is full-mesh"`.
6. Multi-cluster RR must still validate that every cluster has an RR and clients. The default RR is only used when the user selected `"route-reflector"` and did not provide any RR configuration.
7. Do not change the intent schema in `_bgp_metadata.py`. Continue using the existing fields
`route_reflector_client` and `route_reflector_cluster_id`.
8. Do not change BIRD/FRR rendering semantics. BIRD should still render RR configuration through `_bgp_metadata.py`, and FRR should still render cluster-id and route-reflector-client through `Routing.py`.
9. Existing old RR usage through `createBgpCluster()`, `joinBgpCluster()`, and `makeRouteReflector()` must not be broken.
10. `setIbgpMode()` must validate the input. Invalid values should directly raise `ValueError` or `AssertionError`, and the error message must include the valid values.
31 changes: 31 additions & 0 deletions docs/ai/route-reflector-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Design: Route Reflector

## Goal

Revise the implemenation of the route reflector in the emulator


## Design Principles

- Keep the configuration of route reflector structure inside the `AutonomousSystem` class, not in the ibgp layer.


## API design

- Introduce an API called `completeIbgpSetup()` inside the `AutonomousSystem` class. It inspects the `AutonomousSystem` object and all the routers inside this AS, finding any place where ibgp setup is incomplete, and complete it.
- The reason for this API: Users might not set up the route reflector structure inside the AS completely, we need to automatically complete the setup.
- Who invokes this API? When `ibgp` starts rendering for an AS, it first calls this API to complete the ibgp setup within the AS.
- Principle: We choose to do this within the AS class, not at the ibgp layer, because we want all the ibgp-related setup to be done inside the AS, and the ibgp layer's job only focuses on rendering, i.e., turning the setup into actual system setup.



## Required Behavior

- Ensure at least one cluster ID is created for the AS. If users do not set one, a deterministic default cluster ID must be created. The first created cluster ID is set as the default.
- Each cluster must have at least one route reflector, if users do not set one, a determininistic router is selected as the RR. If the user does not specify a route reflector for the default single-cluster setup, the emulator deterministically selects one router as the default route reflector. The default rule is: Select the first router in this AS after sorting router names in ascending order.(A cluster may contain multiple route reflectors. Among them, one route reflector is treated as the default route reflector of that cluster.)
- Each router must join one cluster ID; if users do not set one, the router joins the default one of the AS.
- Each router must have one RR; if users do not set one, the default RR in the cluster is used.
- A cluster allows multiple route reflectors, with one being the default. All the route reflectors within the same cluster must peer with one another.
- The default cluster ID must be decided by the AS. Use an ASN-derived IPv4-style string, for example `"10.{asn}.0.1"`, and ensure it does not conflict.
- If the user configures multiple cluster IDs within the same AS, the emulator assumes that the user intends to define a more advanced route reflector topology. In this case, the emulator should not create an additional default cluster ID or automatically select a default route reflector. Instead, the user must explicitly provide all required route reflector information:Every router must explicitly join one cluster ID. Every configured cluster ID must have at least one route reflector. Each router’s cluster ID must exist. Each router must be associated with a valid route reflector in its cluster, unless the router itself is a route reflector. All route reflectors inside the same cluster must peer with one another. If any required information is missing, completeIbgpSetup() should raise a clear error. Examples of error cases: Router r3 in AS 150 is missing a cluster ID; Cluster 10.150.0.2 in AS 150 has no route reflector.

1 change: 1 addition & 0 deletions examples/basic/A62_route_reflector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ marking the RR router:

```python
as12 = base.getAutonomousSystem(12)
as12.setIbgpMode("route-reflector")
as12.createBgpCluster("10.12.0.1")
as12.getRouter("r101").joinBgpCluster("10.12.0.1").makeRouteReflector()
as12.getRouter("r104").joinBgpCluster("10.12.0.1")
Expand Down
2 changes: 2 additions & 0 deletions examples/basic/A62_route_reflector/route_reflector.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,14 @@ def build_mini_internet(emu: Emulator, base: Base, ebgp: Ebgp, hosts_per_as: int

# AS12 demonstrates a single Route Reflector and one client.
as12 = base.getAutonomousSystem(12)
as12.setIbgpMode("route-reflector")
as12.createBgpCluster(AS12_CLUSTER_ID)
as12.getRouter("r101").joinBgpCluster(AS12_CLUSTER_ID).makeRouteReflector()
as12.getRouter("r104").joinBgpCluster(AS12_CLUSTER_ID)

# AS3 demonstrates two RR clusters plus an RR-to-RR mesh.
as3 = base.getAutonomousSystem(3)
as3.setIbgpMode("route-reflector")
as3.createBgpCluster(AS3_WEST_CLUSTER_ID)
as3.createBgpCluster(AS3_EAST_CLUSTER_ID)
as3.getRouter("r100").joinBgpCluster(AS3_WEST_CLUSTER_ID).makeRouteReflector()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,24 @@ def build_route_server_slice(emu: Emulator, base: Base, ebgp: Ebgp, web: WebServ
ebgp.addRsPeer(100, asn)


def build_frr_route_server_slice(emu: Emulator, base: Base, ebgp: Ebgp, web: WebService) -> None:
"""Exercise a FRR route-server IX without changing the legacy BIRD RS default."""

ix = base.createInternetExchange(107)
ix.getRouteServerNode().setRoutingBackend("frr")

for asn in [157, 158]:
current_as = base.createAutonomousSystem(asn)
current_as.createNetwork("net0")
current_as.createRouter("router0").joinNetwork("net0").joinNetwork("ix107")
current_as.createHost("web").joinNetwork("net0")

vnode = "web{}".format(asn)
web.install(vnode)
emu.addBinding(Binding(vnode, filter=Filter(nodeName="web", asn=asn)))
ebgp.addRsPeer(107, asn)


def build_mixed_backend_slice(emu: Emulator, base: Base, ebgp: Ebgp, web: WebService) -> None:
"""Exercise one transit AS with BIRD and FRR routers from shared BGP/OSPF intent."""

Expand Down Expand Up @@ -94,6 +112,7 @@ def build_frr_route_reflector_slice(base: Base, ebgp: Ebgp) -> None:

as3 = base.createAutonomousSystem(3)
as3.createNetwork("net0")
as3.setIbgpMode("route-reflector")
as3.createBgpCluster(AS3_CLUSTER_ID)
as3.createRouter("rr", routingBackend="frr").joinNetwork("net0").joinNetwork("ix103").joinBgpCluster(AS3_CLUSTER_ID).makeRouteReflector()
as3.createRouter("client", routingBackend="frr").joinNetwork("net0").joinBgpCluster(AS3_CLUSTER_ID)
Expand Down Expand Up @@ -166,6 +185,7 @@ def build_emulator() -> Emulator:
web = WebService()

build_route_server_slice(emu, base, ebgp, web)
build_frr_route_server_slice(emu, base, ebgp, web)
build_mixed_backend_slice(emu, base, ebgp, web)
build_frr_route_reflector_slice(base, ebgp)
build_exabgp_slice(base, exabgp, emu)
Expand Down
21 changes: 21 additions & 0 deletions examples/basic/A63_control_plane_regression/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ def main() -> int:
rs100 = test.require_service(100, "ix100", "IX100 BIRD route server is generated")
as150_router = test.require_service(150, "router0")
as151_router = test.require_service(151, "router0")
rs107 = test.require_service(107, "ix107", "IX107 FRR route server is generated")
as157_router = test.require_service(157, "router0")
as158_router = test.require_service(158, "router0")
as2_r1 = test.require_service(2, "r1")
as2_r2 = test.require_service(2, "r2")
as152_router = test.require_service(152, "router0")
Expand Down Expand Up @@ -82,6 +85,24 @@ def main() -> int:
if as151_router:
test.exec_check("AS151 learns AS150 route through route server", as151_router, "birdc show route | grep -q '10.150.0.0/24'", retries=15)

if rs107:
require_backend(test, rs107, "frr")
test.exec_check("IX107 route server starts FRR bgpd", rs107, "pgrep -x bgpd >/dev/null")
test.exec_check("IX107 route server does not start BIRD", rs107, "! pgrep -x bird >/dev/null")
test.exec_check("IX107 route server renders AS157 as RS client", rs107, "grep -q 'neighbor 10.107.0.157 route-server-client' /etc/frr/frr.conf")
test.exec_check("IX107 route server renders AS158 as RS client", rs107, "grep -q 'neighbor 10.107.0.158 route-server-client' /etc/frr/frr.conf")
test.exec_check("IX107 route server sees AS157 BGP session", rs107, "vtysh -c 'show ip bgp summary' | grep -q '10.107.0.157'", retries=15)
test.exec_check("IX107 route server sees AS158 BGP session", rs107, "vtysh -c 'show ip bgp summary' | grep -q '10.107.0.158'", retries=15)

if as157_router:
require_backend(test, as157_router, "bird")
test.exec_check("AS157 route-server session is established", as157_router, "birdc show protocols | grep -q 'p_rs107.*Established'", retries=15)

if as158_router:
require_backend(test, as158_router, "bird")
test.exec_check("AS158 route-server session is established", as158_router, "birdc show protocols | grep -q 'p_rs107.*Established'", retries=15)
test.exec_check("AS158 learns AS157 route through FRR route server", as158_router, "birdc show route | grep -q '10.157.0.0/24'", retries=15)

if as2_r1:
require_backend(test, as2_r1, "bird")
test.exec_check("AS2 r1 starts BIRD", as2_r1, "pgrep -x bird >/dev/null")
Expand Down
Loading
Loading