BMP Watcher¶
BMP Watcher приносит в Topolograph управляющую плоскость BGP: сессии пиринга, маршруты, которые по ним передаются, контекст VPN и все изменения того и другого.
Это пассивная станция BMP. Маршрутизаторы сами открывают к ней TCP-сессию и передают свой Adj-RIB-In. Watcher не говорит на BGP, не поднимает пиринг и никогда не подключается к маршрутизатору сам, поэтому не добавляет состояния BGP в наблюдаемую сеть.
Состояние BGP хранится отдельно от графа IGP
Сессия BGP - это отношение управляющей плоскости, а не линк передачи данных. BGP хранится как отдельный граф со своим жизненным циклом и привязывается к графам OSPF и IS-IS, но никогда не смешивается с ними. Граф BGP работает и сам по себе, вообще без графа IGP.
Что собирается¶
Семейства адресов¶
| Семейство | AFI | SAFI | Тип события |
|---|---|---|---|
| IPv4 unicast | 1 | 1 | prefix |
| IPv6 unicast | 2 | 1 | prefix |
| VPNv4 | 1 | 128 | l3vpn |
| VPNv6 | 2 | 128 | l3vpn |
| EVPN | 25 | 70 | evpn |
VPN-маршрут уникален только вместе со своим Route Distinguisher, поэтому RD входит в его идентичность. У маршрута EVPN префикса нет вовсе - он идентифицируется компонентами NLRI по RFC 7432: тип маршрута, Ethernet Segment ID, Ethernet Tag, MAC, IP.
Сообщения BMP¶
| Сообщение BMP | Что с ним делает Topolograph |
|---|---|
| Route Monitoring | строит таблицу и все последующие изменения маршрутов |
| Peer Up | состояние сессии и BGP Identifier пира - Router ID, на который относятся события |
| Peer Down | разрыв сессии и withdraw на каждый маршрут, который нёс этот пир |
| Initiation / Termination | жизненный цикл сессии коллектора |
| Statistics Report | игнорируется - счётчики не являются состоянием маршрутизации |
Потоки политик и уровень доказательности¶
Оба потока Adj-RIB-In хранятся раздельно, а там, где спикер поддерживает RFC 9069, поток Loc-RIB сохраняется как третье, отдельное наблюдение:
| Поток | Evidence | Что означает |
|---|---|---|
pre / out-pre |
pre_policy |
пир анонсировал маршрут; роутер мог его отбросить |
post / out-post |
post_policy |
роутер принял маршрут - это кандидат |
loc-rib |
loc_rib |
собственный выбор роутера - установленный лучший путь |
fib |
fib |
присутствует в таблице форвардинга |
Они никогда не сливаются. Маршрут, увиденный только в pre-policy, никогда не показывается как выбранный или установленный - ради этого различия оба потока и существуют.
Наблюдения, а не подсети¶
Один и тот же префикс сохраняется отдельно для каждого спикера, каждого пира, каждого path ID и каждого потока политик. Все копии сохраняются, потому что «кто, кому и что анонсировал» - это ровно тот вопрос, на который отвечает мониторинг таблицы.
Установка коллектора¶
Коллектор - bmpwatcher, станция BMP на Go, которая отделяет первоначальную выгрузку таблицы от последующих изменений. В его README описаны сборка, запуск в Docker и настройка BMP на стороне маршрутизатора для FRR, IOS-XR, Junos и SR OS.
Минимальный запуск со снимком и потоком событий:
bmpwatcher \
--bmp-port=11019 \
--source-id=pe1 \
--watcher-name=bmp-dc1 \
--events=/var/log/bmpwatcher/events.jsonl \
--topolograph-topology-url=https://topolograph.com/api/watcher/bgp
Аутентификация в коллекторе ещё не реализована
/api/watcher/bgp требует Authorization: Bearer sk-..., а коллектор
пока не добавляет этот заголовок - прямая отправка получает 401. До
выхода этой возможности сохраняйте документ локально через
--topolograph-topology-file и отправляйте его сами (пример с curl
ниже).
Токен создаётся в Settings → API Tokens → Create token. Рабочее пространство определяется по токену на сервере и никогда не берётся из тела запроса.
API приёма данных¶
POST /api/watcher/bgp - снимок топологии¶
Коллектор собирает всю таблицу за своё окно сбора и отправляет её одним документом.
curl -sS -X POST https://topolograph.com/api/watcher/bgp \
-H "Authorization: Bearer $TOPOLOGRAPH_API_TOKEN" \
-H "Content-Type: application/json" \
--data @topolograph-topology.json
{
"time": "2026-08-17T09:12:03Z",
"user": "bmp-dc1",
"srcid": "pe1",
"sesid": "b4f1c8e2",
"topology": {
"nodes": [
{"name": "10.0.0.1", "asn": "65001", "role": "speaker", "router_ip": "10.0.0.1"},
{"name": "10.0.0.2", "asn": "65002", "role": "peer"}
],
"edges": [
{"source": "10.0.0.1", "target": "10.0.0.2",
"peer_ip": "10.0.0.2", "local_ip": "10.0.0.1", "asn": "65002",
"peer_type": 0, "policies": ["pre", "post"], "families": ["1/1", "1/128"]}
],
"networks": [
{"subnet": "192.0.2.0/24", "type": "1", "subtype": 1,
"bmp_source": "10.0.0.1", "peer_ip": "10.0.0.2",
"policy": ["post"], "path_id": 0, "nexthop": "10.0.0.2",
"vpn_rd": "65001:100", "rt": "65001:100",
"labels": [24001], "data": {}}
]
}
}
| Поле | Значение |
|---|---|
time |
время снимка, ISO 8601 - оно же ключ проверки на устаревание |
srcid |
экземпляр коллектора |
sesid |
один запуск коллектора; меняется при каждом рестарте |
nodes[].role |
speaker - сообщает; peer - о нём только сообщили |
edges[] |
одна сессия BGP, а не одна пара маршрутизаторов |
networks[] |
одно наблюдение маршрута |
networks[].type / subtype |
AFI строкой, SAFI числом |
networks[].data |
исходная запись коллектора, чтобы не потерять непереведённые в поля атрибуты |
Ответ
graph_time - публичный идентификатор для всех методов чтения ниже, в том же
формате, что и у графов IGP.
Порядок и повторные отправки. sesid создаётся при старте коллектора - ровно тогда, когда спикеры заново выгружают свои таблицы. Внутри одного
sesid побеждает наибольшее time; более старое или равное отклоняется с
400 stale snapshot. Периодическая полная переотправка с тем же sesid
считается сверочной контрольной точкой, а не новым графом: возвращается
checkpoint: true, это подтверждает, что источник жив в тихой сети, и
исправляет текущее представление, если оно разошлось. Новый sesid заменяет
предыдущий запуск.
Все Route Target извлекаются из data.base_attrs.ext_community_list, а не
только из продвинутого поля rt - маршрут с несколькими RT остаётся видимым
при поиске по любому из них.
POST /api/watcher/bgp/events - поток изменений¶
Принимает один объект события или их список.
curl -sS -X POST https://topolograph.com/api/watcher/bgp/events \
-H "Authorization: Bearer $TOPOLOGRAPH_API_TOKEN" \
-H "Content-Type: application/json" \
-d '[{
"srcid": "pe1", "sesid": "b4f1c8e2", "seq": 41,
"watcher_time": "2026-08-17T09:14:11Z",
"event_name": "prefix", "event_status": "withdraw",
"event_object": "192.0.2.0/24", "event_detected_by": "10.0.0.2",
"bmp_source": "10.0.0.1", "policy": "post",
"afi": 1, "safi": 1, "prefix": "192.0.2.0", "prefix_len": 24,
"family_data": {"peer_ip": "10.0.0.2"}
}]'
| Поле | Значение |
|---|---|
event_name |
prefix, l3vpn, evpn, peer |
event_status |
add, change, withdraw (маршруты); up, down (пиры) |
event_detected_by |
маршрутизатор, о котором изменение |
bmp_source |
спикер, который сообщил - на рефлектированной сессии это другой роутер |
seq |
монотонный в пределах sesid; точная дедупликация и обнаружение пропусков |
watcher_time |
часы коллектора - упорядочивают поток |
bmp_timestamp |
часы маршрутизатора - только для корреляции, не для порядка |
replay_suspect |
возможно, хвост выгрузки, а не живое изменение |
Событие, чья пара (srcid, sesid) не совпадает с сохранённым снимком,
отклоняется - сначала отправьте топологию, потом включайте поток событий.
Повторный seq считается дубликатом и отбрасывается; пропуск в seq
логируется как потерянное сообщение.
Событие up/down пира влияет только на отображение. Коллектор и так выдаёт обычный withdraw на каждый префикс, который нёс упавший пир, поэтому само событие пира никогда не меняет состояние маршрутов.
POST /api/watcher/vrfs - инвентарь VRF¶
Route Distinguisher идентифицирует VPN-маршруты, но имя VRF и его импортируемые/экспортируемые Route Target знает только устройство. Отправка инвентаря позволяет искать по имени VRF, а не по RD.
curl -sS -X POST https://topolograph.com/api/watcher/vrfs \
-H "Authorization: Bearer $TOPOLOGRAPH_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"router_id": "10.0.0.1",
"observed_at": "2026-08-17T09:10:00Z",
"vrfs": [{
"name": "Red",
"families": [{
"afi": "ipv4", "safi": "unicast",
"route_distinguisher": "65001:100",
"import_route_targets": ["65001:100", "65001:999"],
"export_route_targets": ["65001:100"]
}]
}]
}'
Каждое наблюдение сохраняется со своей меткой времени, а не затирает
предыдущее, поэтому старый граф по-прежнему может восстановить состояние VRF
таким, каким оно было тогда. Уникальность - (рабочее пространство, router_id,
rd); неизменившаяся переотправка ничего не пишет.
Привязка к графам IGP¶
После каждого сохранения BGP и каждого сохранения IGP Topolograph заново вычисляет, каким графам OSPF или IS-IS принадлежит граф BGP. Кандидаты ранжируются по пересечению Router ID жадным покрытием множества, поэтому граф BGP, охватывающий два домена IGP, привязывается к обоим.
| Состояние | Значение |
|---|---|
bound |
пересечение Router ID ≥ 80 %, однозначно |
needs_mapping |
ниже порога или два равных кандидата - ждёт подтверждения |
Совпадение Router ID - свидетельство, а не требование. BGP Router ID и OSPF Router ID обычно совпадают, но Topolograph на этом не настаивает: неоднозначные результаты остаются видимыми для подтверждения вручную.
# к чему привязан граф BGP
curl -sS ".../api/bgp-graph/17Aug2026_09h12m03s_6_hosts/bindings" -H "Authorization: Bearer $T"
# подтвердить привязку вручную
curl -sS -X PUT ".../api/bgp-graph/<bgp_graph_time>/<igp_graph_time>/binding" -H "Authorization: Bearer $T"
# удалить
curl -sS -X DELETE ".../api/bgp-graph/<bgp_graph_time>/<igp_graph_time>/binding" -H "Authorization: Bearer $T"
Для IS-IS нужен настоящий Router ID
Внутри узел IS-IS называется псевдо-Router ID, который придумал парсер и которого нет нигде в сети. Идентичностью считается только TE Router ID, анонсированный самим устройством. Устройство, которое его не анонсирует, не вносит вклада в оценку пересечения - это честный результат, а не ошибка. Включите TE на устройстве или задайте Router ID вручную на странице сопоставления имён хостов; дальше он переносится на следующие графы так же, как имя хоста.
Чтение данных¶
Графы, узлы и сессии¶
GET /api/bgp-graphs?page=1&per_page=50
GET /api/bgp-graph/{bgp_graph_time}
GET /api/bgp-graph/{bgp_graph_time}/nodes?role=rr&asn=65001
GET /api/bgp-graph/{bgp_graph_time}/sessions?igp_relation=inter-domain&bgp_session_type=ebgp
Каждая сессия классифицируется, как только известны привязки:
igp_relation |
Значение |
|---|---|
intra-domain |
оба конца в одном привязанном графе IGP |
inter-domain |
концы в двух разных привязанных графах IGP |
external |
хотя бы один конец не входит ни в один привязанный граф |
bgp_session_type - ibgp или ebgp, определяется по ASN сессии
относительно собственного ASN спикера, а не по AS_PATH[0], который на
рефлектированном маршруте вводил бы в заблуждение.
Поиск маршрутов¶
GET /api/bgp-graph/{bgp_graph_time}/routes?prefix=192.0.2.0/24
GET /api/bgp-graph/{bgp_graph_time}/node/{router_id}/routes?evidence=loc_rib
| Параметр | Поведение |
|---|---|
prefix=192.0.2.0/24 |
точное совпадение по полному префиксу |
prefix=192.0.2.5 |
вхождение - все маршруты, покрывающие адрес |
prefix=192.0.2.0/24&lpm=1 |
самый длинный совпадающий префикс, одна строка |
afi / safi |
числовое семейство |
rd |
Route Distinguisher |
vrf |
имя VRF, раскрывается в его RD через инвентарь |
rt |
любой Route Target маршрута |
policy / evidence |
исходный поток либо pre_policy / post_policy / loc_rib / fib |
as_path_contains |
подстрока в любом месте AS_PATH |
community / large_community / extended_community |
поиск по community |
origin, local_pref, med, originator_id, label |
фильтры по атрибутам |
peer_ip, nexthop, bmp_source |
кто анонсировал и как достигается |
page, per_page |
постраничный вывод (per_page не больше 500) |
Каждый маршрут несёт VRF/RD/RT, AFI/SAFI, политику и evidence, path ID, community, next hop, метки и origin.
История и сравнение¶
# состояние таблицы сейчас или на момент времени
GET /api/bgp-graph/{bgp_graph_time}/routes/state
GET /api/bgp-graph/{bgp_graph_time}/routes/state?at=2026-08-17T09:20:00Z
# что изменилось между двумя моментами
GET /api/bgp-graph/{bgp_graph_time}/routes/compare?t0=...&t1=...
# лента событий и дорожки таймлайна мониторинга
GET /api/bgp-graph/{bgp_graph_time}/events?last_minutes=60&event_name=peer
GET /api/bgp-graph/{bgp_graph_time}/events/timeline
state без at читает постоянно поддерживаемое текущее представление,
поэтому стоит размера ответа, а не проигрывания всего журнала событий. Явный
at проигрывает изменения от базового снимка до этого момента. Границы
интервала включаются с обеих сторон.
compare возвращает по строке на изменение: added, withdrawn или
changed с состоянием до и после.
На таймлайне мониторинга bgp_peer даёт по маркеру на каждое поднятие или
падение сессии - событий мало, и важно каждое, - а bgp_route
кластеризуется, поэтому всплеск маршрутного шума рисуется одним маркером со
счётчиком, а не тысячами точек.
Route lookup¶
Route lookup отвечает на вопрос «что этот маршрутизатор реально сделает с этим назначением», в отличие от чистого SPF по топологии.
{
"prefix": "192.0.2.0/24",
"start_node": "10.0.0.1",
"route_source": "BGP",
"admin_distance": 200,
"nexthop": "10.0.0.9",
"resolution_chain": ["192.0.2.0/24", "10.0.0.9/32"],
"path_segments": [{"domain": "17Aug2026_09h05m00s_6_hosts",
"path": ["10.0.0.1", "10.0.0.4", "10.0.0.9"]}],
"warning": null
}
Порядок принятия решения задан намеренно:
- Самый длинный совпадающий префикс в выбранной таблице или VRF.
- Выбор лучшего пути BGP - один путь на префикс, по LOCAL_PREF, длине AS_PATH, ORIGIN и MED, прежде чем что-либо начнёт сравнивать протоколы. Наблюдение Loc-RIB завершает сравнение: это собственный выбор роутера.
- Административная дистанция между оставшимися кандидатами разных протоколов.
- Рекурсивное разрешение next hop с защитой от петель и по глубине.
- Транспорт IGP SPF/CSPF до этого next hop, при необходимости через подходящие LSP-шорткаты.
| Протокол | AD |
|---|---|
| connected | 0 |
| static | 1 |
| eBGP | 20 |
| OSPF | 110 |
| IS-IS | 115 |
| iBGP | 200 |
Административная дистанция принадлежит маршрутам, а не рёбрам топологии.
Метрики OSPF, IS-IS и BGP никогда не сравниваются между собой - метрика имеет
смысл только внутри своего протокола. iBGP или eBGP определяется сессией, по
которой маршрут выучен, а не по AS_PATH[0].
Кандидаты ограничены тем, что реально видит стартовый узел: его собственная сообщённая таблица плюс таблицы его непосредственных соседей по сессиям. Маршрутизатор без BGP не наследует ничего.
Хранение¶
Topolograph хранит последние графы BGP по каждому источнику (srcid),
поэтому установка с двумя коллекторами сохраняет полное окно для каждого.
Когда эпоха выпадает из окна, вместе с ней удаляются её маршруты и привязки.
Периодические переотправки с тем же sesid - контрольные точки, окно они не
расходуют.
Текущие ограничения¶
- Коллектор пока не добавляет API-токен; временно отправляйте снимок через
curl. - Запускайте по одному коллектору на BMP-спикер и задавайте
--source-id. Несколько спикеров в один коллектор смешают свои наблюдения. - Маршруты EVPN собираются и сохраняются, но таблица маршрутов и route lookup ориентированы на префиксы; EVPN пока не является полноценным объектом поиска.
- Router ID, который законно присутствует в двух привязанных доменах IGP, при классификации сессий относится к одному из них.
Смотрите также¶
- bmpwatcher на GitHub
- OSPF Watcher · IS-IS Watcher
- События, таймлайн и статус
- Сессия BGP-LS - BGP-LS переносит топологию IGP, это другой предмет, чем состояние маршрутизации BGP на этой странице