sam 2634aada24 Parameterize HOST_IP everywhere -- portable to another lab host
Removes hardcoded 10.40.40.202 references so a fresh clone + .env-only
edit can stand the stack up on a new compute node.

  * docker-compose.yml: rib-poller PG_DSN now uses ${HOST_IP:-...}.
  * obmp-rib-poller/poller.py: default PG_DSN host falls back to
    ${HOST_IP} env (compose passes it; manual runs honour $HOST_IP too).
  * cml/gobgp_peering_config.py: GOBGP_IP read from $HOST_IP or the
    HOST_IP= line in repo-root .env, with a small _env_default helper.
  * cml/proxmox_bmp_config.py: COLLECTOR_HOST resolved the same way.

For gobgp/gobgpd.conf and gobgp-evpn/gobgpd.conf -- jauderho/gobgp is
distroless (no shell), so we can't sed-substitute at container start.
Pattern instead:

  * gobgpd.conf is now gobgpd.conf.tmpl with __HOST_IP__ placeholders
    (committed). The rendered gobgpd.conf is gitignored.
  * setup.sh renders the .tmpl(s) to .conf using $HOST_IP from .env.
  * compose `command` stays the simple `gobgpd -f /config/gobgpd.conf`.

After cloning on a new host:  cp .env.example .env  -> edit HOST_IP ->
./setup.sh -> docker compose up -d. Verified locally by force-recreating
gobgp; all 6 sessions (4 cores + 2 Bromirski) re-established in <60s.

Known portability gaps still to address (separate work):
  * Hardcoded lab-router inventories in cml/*.py and
    obmp-rib-poller/poller.py.
  * The /etc/cron.d/openbmp */5 -> */15 edit inside obmp-psql-app is
    not persistent (regenerated by config_cron on every container start).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 18:34:51 -07:00
..

GoBGP global Internet table feed (roadmap E1)

This service runs GoBGP to pull the full real Internet routing table (IPv4 ~1M + IPv6 ~200k routes) from Łukasz Bromirski's lab route server (AS57355) and BMP-export every received route to the OpenBMP collector. The table lands in PostgreSQL ip_rib as a monitored peer.

  • Image: jauderho/gobgp:v4.5.0 — community-maintained, multi-arch, tracks upstream GoBGP releases (rebuilt within an hour of each release). Chosen because the official osrg/gobgp image is published less consistently.
  • Local AS: 65001 (private). Router-id: 10.40.40.250.
  • The session is receive-only — we announce nothing to the route server.

Files

File Purpose
gobgpd.conf GoBGP daemon config (global, neighbors, BMP export). TOML.
mrt-refresh.sh MRT full-table fallback loader (cron-driven).
mrt/ Created at runtime; cached RouteViews RIB dumps.

Bring it up

The gobgp service is defined in the repo docker-compose.yml, on the same default compose network as collector, and depends_on it.

docker compose config            # validate compose is well-formed
docker compose up -d gobgp       # start (collector must be running)
docker logs -f obmp-gobgp

The live BGP cutover is performed by a human — bringing the container up is all that is needed; GoBGP initiates the eBGP-multihop sessions automatically.

Confirm the session and route count

# session state — expect both neighbors in "Establ"
docker exec obmp-gobgp gobgp neighbor

# received route counts — expect ~1M IPv4, ~200k IPv6
docker exec obmp-gobgp gobgp global rib summary -a ipv4
docker exec obmp-gobgp gobgp global rib summary -a ipv6

How the data appears in OpenBMP

GoBGP opens an outbound BMP session to obmp-collector:5000 with route-monitoring-policy = "pre-policy" (Adj-RIB-In, pre import-policy — consistent with the rest of the OpenBMP fleet).

In OpenBMP / PostgreSQL the source is identified by the BMP router, which GoBGP reports using its router-id (10.40.40.250) and local-as (65001):

  • routers table — a row with ip_address / name derived from 10.40.40.250.
  • bgp_peers table — two peer rows for 85.232.240.179 and 2001:1a68:2c:2::179, both peer_as = 57355.
  • ip_rib — every prefix from the global table, attributed to those peers.

To find it in Grafana/SQL, filter on peer_as = 57355 or the router-id above.

Fleet-wide full-table feed into the CML lab (stress test)

GoBGP additionally re-advertises the full table to the two CML core routers (CORE-01/CORE-02, AS65020). As route reflectors the cores propagate it to all seven R9K clients, so every lab router carries and BMP-exports a full table — an intentional stress test of the OpenBMP ingestion/storage path (the database grows toward ~55-65 GB).

  • GoBGP sidegobgpd.conf neighbors 10.100.0.100 / 10.100.0.200 (peer-as 65020, eBGP-multihop, IPv4+IPv6, prefix-limit caps). The route-server sessions carry default-export-policy = "reject-route" so the lab's own routes can never leak back to AS57355.
  • Router sidecml/gobgp_peering_config.py adds the neighbor 10.40.40.202 config (with maximum-prefix 1.5M/400k caps) to both cores. GoBGP is host-networked, so it sources BGP TCP from the host IP 10.40.40.202, not its router-id 10.40.40.250 — the cores peer with the host IP.

Apply

python3 cml/gobgp_peering_config.py             # configure both cores
docker compose up -d --force-recreate gobgp     # load gobgpd.conf changes

A volume-mounted config change does NOT trigger a recreate on its own — --force-recreate is required for GoBGP to re-read gobgpd.conf.

Rollback

Emergency stop (fastest — feed off within seconds, no router change):

docker compose stop gobgp

Stopping GoBGP drops the eBGP sessions; the cores withdraw the full table and the withdrawal propagates to every client. The ip_rib rows are marked withdrawn and aged out by the existing TimescaleDB retention.

Full revert (also removes the router-side config):

python3 cml/gobgp_peering_config.py --remove    # delete neighbor from cores
docker compose stop gobgp

To keep the Bromirski feed running but drop only the lab injection, delete the two 10.100.0.x [[neighbors]] blocks from gobgpd.conf and docker compose up -d --force-recreate gobgp.

What to watch during convergence

docker exec obmp-gobgp gobgp neighbor                        # 4 sessions Establ
docker logs --tail 20 obmp-psql-app                          # consumer lag
docker exec obmp-psql psql -U openbmp -d openbmp -c \
  "SELECT count(*) FROM ip_rib WHERE iswithdrawn = false;"   # row growth

If psql-app consumer lag climbs without draining, or PostgreSQL CPU/IO saturates, use the emergency stop above.

MRT fallback

AS57355 is a single volunteer-run host with no SLA — it can and does go away. mrt-refresh.sh keeps the global table in ip_rib warm when the live feed is down:

  1. If any AS57355 session is Established, the script does nothing — the live feed is authoritative and must not be overwritten with a stale dump.
  2. Otherwise it downloads the latest full RIB dump from RouteViews (https://archive.routeviews.org/route-views/bgpdata/YYYY.MM/RIBS/rib.YYYYMMDD.HHMM.bz2, published every 2 hours UTC) and runs gobgp mrt inject global <file>, which installs every prefix into the running daemon. BMP export to the collector then happens automatically.

The script is idempotent (re-uses an already-downloaded dump), guarded by a flock against overlapping runs, and prunes to the 4 most recent dumps.

Schedule it (host crontab, 2-hour cadence)

0 */2 * * * docker exec obmp-gobgp /config/mrt-refresh.sh >> /var/log/gobgp-mrt.log 2>&1

Run it once manually to verify:

docker exec obmp-gobgp /config/mrt-refresh.sh

Caveats

  • No SLA. AS57355 is a volunteer lab route server; treat the live feed as best-effort and rely on the MRT fallback for continuity.
  • eBGP-multihop TTL is set to 64 — the route server is many hops away.
  • A full table is ~1M+ prefixes; expect a noticeable load spike in the collector and PostgreSQL when the session first establishes or an MRT dump is injected.