Перейти к содержанию

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": "17Aug2026_09h12m03s_6_hosts", "checkpoint": false, "routes": 1428}

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 возможно, хвост выгрузки, а не живое изменение
{"accepted": 1, "duplicates": 0}

Событие, чья пара (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 по топологии.

GET /api/graph/{graph_time}/route-lookup/{start_node}?destination=192.0.2.5&vrf=Red&with_lsps=1
{
  "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
}

Порядок принятия решения задан намеренно:

  1. Самый длинный совпадающий префикс в выбранной таблице или VRF.
  2. Выбор лучшего пути BGP - один путь на префикс, по LOCAL_PREF, длине AS_PATH, ORIGIN и MED, прежде чем что-либо начнёт сравнивать протоколы. Наблюдение Loc-RIB завершает сравнение: это собственный выбор роутера.
  3. Административная дистанция между оставшимися кандидатами разных протоколов.
  4. Рекурсивное разрешение next hop с защитой от петель и по глубине.
  5. Транспорт 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, при классификации сессий относится к одному из них.

Смотрите также