Я обнаружил баг в ROS BGP, из-за которого возникает рассогласование состояния между двумя BGP-пирами, и выкладываю это для осведомленности сообщества.
Тестовая среда: Router OS версии 6.34 на платформе CHR, запущенный в Virtualbox в GNS3.
Баг: В некоторых случаях маршрут может менять статус так, что он уже не проходит через фильтр маршрутов. В этом случае исходный роутер удаляет префикс из списка объявляемых маршрутов, но при этом не отправляет сообщение об отзыве (withdrawal update). После этого никакие мягкие обновления (refresh) не могут убрать маршрут из таблицы маршрутов соседа. Маршрут должен в итоге истечь по TTL, но иначе избавиться от ошибочного маршрута у удалённого пира можно только полной остановкой и запуском сессии пира или повторным объявлением маршрута с последующей блокировкой его другим правилом фильтрации.
Как воспроизвести это поведение: Не все переходы статусных изменений accept->discard затрагиваются багом. Я не знаю, в чём причина, но вот такой сценарий его вызывает.
Я настроил систему, где фильтры маршрутов применяют community к локально-генерированным маршрутам, а затем фильтруют исходящие объявления, основываясь на этой community. В работе участвуют два фильтра: global-out и peer-out.
R1 (маршрутизатор, который объявляет маршруты) сопоставляет локально-сгенерированные маршруты и добавляет к ним community 1:1 [global-out], затем фильтрует маршруты для пира R2 так, чтобы отправлялись только маршруты с community 1:1 [peer-out].
Конфигурация R1:
/ip route
add distance=254 dst-address=192.168.1.0/24 type=blackhole
add distance=254 dst-address=192.168.2.0/24 type=blackhole
/routing filter
add action=accept bgp-communities=1:1 chain=peer-out
add action=discard chain=peer-out
add action=accept append-bgp-communities=1:1 chain=global-out locally-originated-bgp=yes
/routing bgp instance
set default out-filter=bgp-global-out as=65530
/routing bgp network
add network=192.168.1.0/24 synchronize=yes
add network=192.168.2.0/24 synchronize=yes
/routing bgp peer
add name=R2 out-filter=peer-out remote-address=10.1.2.2 remote-as=65520 ttl=default
Конфигурация R2 тривиальна — он принимает все маршруты от R1 по eBGP сессии.
Если в /routing bgp network создать третий маршрут, например 192.168.3.0/24 с synchronize=no, то фильтр global-out не применит community к этому префиксу, так как он не считается «локально-сгенерированным». Следовательно, фильтр peer-out его заблокирует от отправки R2 (я тестировал именно это поведение и так нашёл этот баг).
Однако, если изменить существующий префикс (192.168.2.0/24), поменяв synchronize с yes на no, роутер внутренне удалит community 1:1 с этого префикса и затем заблокирует его в списке объявлений. Но при этом роутер не отправит BGP Update с уведомлением об отзыве маршрута для R2.
/routing bgp advertisements> print
PEER PREFIX NEXTHOP AS-PATH ORIGIN LOCAL-PREF
R2 192.168.1.0/24 10.1.2.1 igp
/ip route> print where bgp
Flags: X - отключенный, A - активный, D - динамический,
C - соединение, S - статический, r - rip, b - bgp, o - ospf, m - mme,
B - blackhole, U - недоступен, P - запрещён
# DST-ADDRESS PREF-SRC GATEWAY DISTANCE
0 ADb 192.168.1.0/24 10.1.2.1 20
1 ADb 192.168.2.0/24 10.1.2.1 20
Wireshark показал, что сообщение об отзыве маршрута от R1 не отправлялось. Если R2 или R1 выполнит refresh, то R1 отправит только префикс 192.168.1.0/24 — R2 же оставит 192.168.2.0/24 в своей таблице, поскольку не получил никакой новой информации по этому маршруту.
Я понимаю, что звучит как крайний случай, но ситуация неприятная: фильтр блокирует маршрут, роутер считает, что отозвал его, а на самом деле этого не произошло.
Есть ещё одна проблема с фильтрами маршрутов — изменение порядка правил, перетаскивая их в цепочке, не заставляет фильтр переоценивать маршруты. Изменение существующего правила (даже просто комментария) заставляет фильтр сделать переоценку правильно.
И ещё — если использовать один фильтр (без глобального фильтра на уровне инстанса), поведение меняется: BGP matcher будет считать локально-генерированные маршруты даже если synchronize=no, и тогда баг не проявляется.
Тестовая среда: Router OS версии 6.34 на платформе CHR, запущенный в Virtualbox в GNS3.
Баг: В некоторых случаях маршрут может менять статус так, что он уже не проходит через фильтр маршрутов. В этом случае исходный роутер удаляет префикс из списка объявляемых маршрутов, но при этом не отправляет сообщение об отзыве (withdrawal update). После этого никакие мягкие обновления (refresh) не могут убрать маршрут из таблицы маршрутов соседа. Маршрут должен в итоге истечь по TTL, но иначе избавиться от ошибочного маршрута у удалённого пира можно только полной остановкой и запуском сессии пира или повторным объявлением маршрута с последующей блокировкой его другим правилом фильтрации.
Как воспроизвести это поведение: Не все переходы статусных изменений accept->discard затрагиваются багом. Я не знаю, в чём причина, но вот такой сценарий его вызывает.
Я настроил систему, где фильтры маршрутов применяют community к локально-генерированным маршрутам, а затем фильтруют исходящие объявления, основываясь на этой community. В работе участвуют два фильтра: global-out и peer-out.
R1 (маршрутизатор, который объявляет маршруты) сопоставляет локально-сгенерированные маршруты и добавляет к ним community 1:1 [global-out], затем фильтрует маршруты для пира R2 так, чтобы отправлялись только маршруты с community 1:1 [peer-out].
Конфигурация R1:
/ip route
add distance=254 dst-address=192.168.1.0/24 type=blackhole
add distance=254 dst-address=192.168.2.0/24 type=blackhole
/routing filter
add action=accept bgp-communities=1:1 chain=peer-out
add action=discard chain=peer-out
add action=accept append-bgp-communities=1:1 chain=global-out locally-originated-bgp=yes
/routing bgp instance
set default out-filter=bgp-global-out as=65530
/routing bgp network
add network=192.168.1.0/24 synchronize=yes
add network=192.168.2.0/24 synchronize=yes
/routing bgp peer
add name=R2 out-filter=peer-out remote-address=10.1.2.2 remote-as=65520 ttl=default
Конфигурация R2 тривиальна — он принимает все маршруты от R1 по eBGP сессии.
Если в /routing bgp network создать третий маршрут, например 192.168.3.0/24 с synchronize=no, то фильтр global-out не применит community к этому префиксу, так как он не считается «локально-сгенерированным». Следовательно, фильтр peer-out его заблокирует от отправки R2 (я тестировал именно это поведение и так нашёл этот баг).
Однако, если изменить существующий префикс (192.168.2.0/24), поменяв synchronize с yes на no, роутер внутренне удалит community 1:1 с этого префикса и затем заблокирует его в списке объявлений. Но при этом роутер не отправит BGP Update с уведомлением об отзыве маршрута для R2.
/routing bgp advertisements> print
PEER PREFIX NEXTHOP AS-PATH ORIGIN LOCAL-PREF
R2 192.168.1.0/24 10.1.2.1 igp
/ip route> print where bgp
Flags: X - отключенный, A - активный, D - динамический,
C - соединение, S - статический, r - rip, b - bgp, o - ospf, m - mme,
B - blackhole, U - недоступен, P - запрещён
# DST-ADDRESS PREF-SRC GATEWAY DISTANCE
0 ADb 192.168.1.0/24 10.1.2.1 20
1 ADb 192.168.2.0/24 10.1.2.1 20
Wireshark показал, что сообщение об отзыве маршрута от R1 не отправлялось. Если R2 или R1 выполнит refresh, то R1 отправит только префикс 192.168.1.0/24 — R2 же оставит 192.168.2.0/24 в своей таблице, поскольку не получил никакой новой информации по этому маршруту.
Я понимаю, что звучит как крайний случай, но ситуация неприятная: фильтр блокирует маршрут, роутер считает, что отозвал его, а на самом деле этого не произошло.
Есть ещё одна проблема с фильтрами маршрутов — изменение порядка правил, перетаскивая их в цепочке, не заставляет фильтр переоценивать маршруты. Изменение существующего правила (даже просто комментария) заставляет фильтр сделать переоценку правильно.
И ещё — если использовать один фильтр (без глобального фильтра на уровне инстанса), поведение меняется: BGP matcher будет считать локально-генерированные маршруты даже если synchronize=no, и тогда баг не проявляется.