Add GoBGP full-table feed container (roadmap E1)
New gobgp service: GoBGP peers eBGP-multihop with the AS57355 lab route server (Bromirski) for the full real IPv4 + IPv6 Internet table and BMP-exports it to the OpenBMP collector, landing in ip_rib as a monitored peer. Config follows the route server's published peering spec: local AS 65001, no password, keepalive 3600 / hold-time 7200, IPv4 feed on the v4 session and IPv6 feed on the v6 session. gobgp/mrt-refresh.sh is a cron-safe fallback that injects RouteViews MRT RIB dumps when the live session is down. The live BGP session is not started here — bringing gobgp up establishes the external session and loads ~1M routes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d60c582ff6
commit
88a5546e29
@ -419,6 +419,22 @@ services:
|
||||
ports:
|
||||
- "5053:5053"
|
||||
|
||||
# GoBGP -- pulls the full real Internet routing table (roadmap E1) from the
|
||||
# AS57355 lab route server and BMP-exports it to the OpenBMP collector, where
|
||||
# it lands in PostgreSQL ip_rib as a monitored peer. Config + MRT fallback
|
||||
# script live in ./gobgp (see gobgp/README.md). Receive-only, local AS 65001.
|
||||
gobgp:
|
||||
restart: unless-stopped
|
||||
container_name: obmp-gobgp
|
||||
image: jauderho/gobgp:v4.5.0
|
||||
depends_on:
|
||||
- collector
|
||||
# gobgpd reads /config/gobgpd.conf; the same mount carries mrt-refresh.sh
|
||||
# and the cached MRT dumps it downloads.
|
||||
volumes:
|
||||
- ./gobgp:/config
|
||||
command: ["gobgpd", "-f", "/config/gobgpd.conf", "-t", "toml"]
|
||||
|
||||
whois:
|
||||
restart: unless-stopped
|
||||
container_name: obmp-whois
|
||||
|
||||
99
gobgp/README.md
Normal file
99
gobgp/README.md
Normal file
@ -0,0 +1,99 @@
|
||||
# GoBGP global Internet table feed (roadmap E1)
|
||||
|
||||
This service runs [GoBGP](https://github.com/osrg/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.
|
||||
|
||||
```sh
|
||||
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
|
||||
|
||||
```sh
|
||||
# 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.
|
||||
|
||||
## 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)
|
||||
|
||||
```cron
|
||||
0 */2 * * * docker exec obmp-gobgp /config/mrt-refresh.sh >> /var/log/gobgp-mrt.log 2>&1
|
||||
```
|
||||
|
||||
Run it once manually to verify:
|
||||
|
||||
```sh
|
||||
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.
|
||||
70
gobgp/gobgpd.conf
Normal file
70
gobgp/gobgpd.conf
Normal file
@ -0,0 +1,70 @@
|
||||
# GoBGP daemon configuration -- OpenBMP "global Internet table" feed (roadmap E1)
|
||||
#
|
||||
# Pulls the full real Internet routing table (IPv4 ~1M + IPv6 ~200k routes)
|
||||
# from Lukasz Bromirski's lab route server (AS57355) and BMP-exports every
|
||||
# received route to the OpenBMP collector, where it lands in PostgreSQL ip_rib.
|
||||
# Peering spec: https://lukasz.bromirski.net/post/bgp-w-labie-3/
|
||||
#
|
||||
# Receive-only: we announce NOTHING -- AS57355 explicitly asks peers not to
|
||||
# send prefixes. Local AS is 65001 (the value the route server expects).
|
||||
# Per the spec: eBGP multihop, no password, keepalive 3600 / hold-time 7200.
|
||||
# TOML syntax targets GoBGP v3.x / v4.x.
|
||||
|
||||
[global]
|
||||
[global.config]
|
||||
as = 65001
|
||||
router-id = "10.40.40.250"
|
||||
# Listen for inbound BGP on the standard port. We only originate
|
||||
# outbound sessions, but the daemon still needs a listen port.
|
||||
port = 179
|
||||
|
||||
# --- Neighbor: route server, IPv4 feed --------------------------------------
|
||||
# The IPv4 transport session carries the full IPv4 table only.
|
||||
[[neighbors]]
|
||||
[neighbors.config]
|
||||
neighbor-address = "85.232.240.179"
|
||||
peer-as = 57355
|
||||
description = "AS57355 Bromirski lab route-server (IPv4 feed)"
|
||||
[neighbors.timers.config]
|
||||
keepalive-interval = 3600
|
||||
hold-time = 7200
|
||||
[neighbors.ebgp-multihop.config]
|
||||
enabled = true
|
||||
multihop-ttl = 64
|
||||
[neighbors.transport.config]
|
||||
# we initiate the session; no local-address pinning
|
||||
passive-mode = false
|
||||
[[neighbors.afi-safis]]
|
||||
[neighbors.afi-safis.config]
|
||||
afi-safi-name = "ipv4-unicast"
|
||||
|
||||
# --- Neighbor: route server, IPv6 feed --------------------------------------
|
||||
# The IPv6 transport session carries the full IPv6 table only.
|
||||
[[neighbors]]
|
||||
[neighbors.config]
|
||||
neighbor-address = "2001:1a68:2c:2::179"
|
||||
peer-as = 57355
|
||||
description = "AS57355 Bromirski lab route-server (IPv6 feed)"
|
||||
[neighbors.timers.config]
|
||||
keepalive-interval = 3600
|
||||
hold-time = 7200
|
||||
[neighbors.ebgp-multihop.config]
|
||||
enabled = true
|
||||
multihop-ttl = 64
|
||||
[neighbors.transport.config]
|
||||
passive-mode = false
|
||||
[[neighbors.afi-safis]]
|
||||
[neighbors.afi-safis.config]
|
||||
afi-safi-name = "ipv6-unicast"
|
||||
|
||||
# --- BMP export to the OpenBMP collector ------------------------------------
|
||||
# GoBGP connects OUT to the collector. "obmp-collector" resolves on the shared
|
||||
# compose network; port 5000 is the collector's BMP listener.
|
||||
# route-monitoring-policy = "pre-policy" exports the Adj-RIB-In (received
|
||||
# routes, pre import-policy) -- consistent with the rest of the OpenBMP fleet.
|
||||
[[bmp-servers]]
|
||||
[bmp-servers.config]
|
||||
address = "obmp-collector"
|
||||
port = 5000
|
||||
route-monitoring-policy = "pre-policy"
|
||||
statistics-timeout = 3600
|
||||
104
gobgp/mrt-refresh.sh
Executable file
104
gobgp/mrt-refresh.sh
Executable file
@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# mrt-refresh.sh -- MRT full-table fallback loader for the OpenBMP GoBGP feed.
|
||||
#
|
||||
# Roadmap E1. The live route server (AS57355) is a single volunteer-run host
|
||||
# with no SLA. When it is unreachable, the global table in PostgreSQL ip_rib
|
||||
# would otherwise age out. This script downloads the latest RouteViews full
|
||||
# MRT RIB dump and injects it into the running gobgpd so the table stays warm.
|
||||
#
|
||||
# Designed to be idempotent and cron-safe at a 2-hour cadence:
|
||||
# - it only downloads a dump it does not already have,
|
||||
# - it only injects when the live route server session is NOT established,
|
||||
# - concurrent runs are guarded by a flock.
|
||||
#
|
||||
# Run it INSIDE the gobgp container (it shells out to the local `gobgp` CLI):
|
||||
# docker exec obmp-gobgp /config/mrt-refresh.sh
|
||||
#
|
||||
# Example crontab entry on the docker host (every 2 hours):
|
||||
# 0 */2 * * * docker exec obmp-gobgp /config/mrt-refresh.sh >> /var/log/gobgp-mrt.log 2>&1
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# --- tunables ---------------------------------------------------------------
|
||||
MRT_DIR="${MRT_DIR:-/config/mrt}"
|
||||
RV_BASE="${RV_BASE:-https://archive.routeviews.org/route-views/bgpdata}"
|
||||
GOBGP="${GOBGP:-gobgp}"
|
||||
LOCKFILE="${LOCKFILE:-/tmp/gobgp-mrt-refresh.lock}"
|
||||
# RouteViews publishes a full RIB dump every 2 hours; dumps land a few minutes
|
||||
# after the even hour, so we look back a safe margin.
|
||||
LOOKBACK_HOURS="${LOOKBACK_HOURS:-4}"
|
||||
|
||||
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*"; }
|
||||
|
||||
# --- single-instance guard --------------------------------------------------
|
||||
exec 9>"${LOCKFILE}"
|
||||
if ! flock -n 9; then
|
||||
log "another mrt-refresh run is in progress; exiting"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "${MRT_DIR}"
|
||||
|
||||
# --- skip if the live route server is up ------------------------------------
|
||||
# If any AS57355 neighbor is Established, the live feed is authoritative and
|
||||
# we must NOT inject a stale MRT dump on top of it.
|
||||
if ${GOBGP} neighbor 2>/dev/null | grep -qiE 'Establ'; then
|
||||
log "a BGP session is Established; live feed is healthy, skipping MRT inject"
|
||||
exit 0
|
||||
fi
|
||||
log "no Established BGP session; proceeding with MRT fallback"
|
||||
|
||||
# --- locate the most recent available RIB dump -----------------------------
|
||||
# RouteViews RIB dumps:
|
||||
# <RV_BASE>/YYYY.MM/RIBS/rib.YYYYMMDD.HHMM.bz2
|
||||
# RIB dumps are taken at even hours (00,02,04,...,22) UTC.
|
||||
found_url=""
|
||||
found_file=""
|
||||
now_epoch="$(date -u +%s)"
|
||||
for ((h = 0; h <= LOOKBACK_HOURS; h++)); do
|
||||
ts_epoch=$(( now_epoch - h * 3600 ))
|
||||
hh="$(date -u -d "@${ts_epoch}" +%H)"
|
||||
# only even hours carry RIB dumps
|
||||
if (( 10#${hh} % 2 != 0 )); then
|
||||
continue
|
||||
fi
|
||||
ym="$(date -u -d "@${ts_epoch}" +%Y.%m)"
|
||||
ymd="$(date -u -d "@${ts_epoch}" +%Y%m%d)"
|
||||
fname="rib.${ymd}.${hh}00.bz2"
|
||||
url="${RV_BASE}/${ym}/RIBS/${fname}"
|
||||
if curl -fsI --max-time 30 "${url}" >/dev/null 2>&1; then
|
||||
found_url="${url}"
|
||||
found_file="${fname}"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -z "${found_url}" ]]; then
|
||||
log "ERROR: no RouteViews RIB dump found within ${LOOKBACK_HOURS}h lookback"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dest="${MRT_DIR}/${found_file}"
|
||||
|
||||
# --- download (idempotent) --------------------------------------------------
|
||||
if [[ -s "${dest}" ]]; then
|
||||
log "already have ${found_file}; reusing cached copy"
|
||||
else
|
||||
log "downloading ${found_url}"
|
||||
tmp="${dest}.partial.$$"
|
||||
curl -fsSL --max-time 600 -o "${tmp}" "${found_url}"
|
||||
mv -f "${tmp}" "${dest}"
|
||||
log "downloaded $(du -h "${dest}" | cut -f1) -> ${dest}"
|
||||
fi
|
||||
|
||||
# --- inject into the running gobgpd -----------------------------------------
|
||||
# `gobgp mrt inject global` reads the bz2 dump directly and installs every
|
||||
# prefix into the global RIB; BMP export to the collector follows automatically.
|
||||
log "injecting ${found_file} into gobgpd global RIB"
|
||||
${GOBGP} mrt inject global "${dest}"
|
||||
log "MRT inject complete"
|
||||
|
||||
# --- housekeeping: keep only the 4 most recent dumps ------------------------
|
||||
( cd "${MRT_DIR}" && ls -1t rib.*.bz2 2>/dev/null | tail -n +5 | xargs -r rm -f )
|
||||
log "done"
|
||||
Loading…
x
Reference in New Issue
Block a user