Хабрахабр

[Из песочницы] Batfish. Введение

image

Множество правил фильтраций, политик обмена маршрутной информации, протоколов динамического роутинга делают сети запутанными и подверженными влиянию человеческого фактора. Одной из проблем современных сетей является их хрупкость. Нам определено не хватает инструмента, позволяющего оценить поведение сети с новой конфигурацией перед внесением изменений в продакшн. Авария на сети может произойти ненамеренно при внесении изменений в route-map или ACL (один, два). Каким маршрутом пойдут пакеты из сети C к серверу D, если на одном из транзитных линков я увеличу IGP метрику в два раза? Хочется точно знать, будет ли мне доступна сеть A, если я отфильтрую часть BGP-анонсов, полученных от провайдера B? Ответить на эти и многие другие вопросы нам поможет Batfish!

Обзор Batfish

Batfish – это инструмент для моделирования сети. Основным его назначением является тестирование конфигурационных изменений перед их внесением в рабочую сеть. Batfish так же можно использовать для анализа и проверки текущего состояния сети. Существующим CI/CD процессам в сетевом мире явно не хватает инструмента для тестирования новых конфигураций. Batfish позволяет решить эту проблему.

Batfish не требует непосредственного прямого доступа к действующему сетевому оборудованию, Batfish моделирует поведение сети на основе данных, содержащихся в конфигурационных файлах устройств.

Batfish может:

  • определить статус соседства протоколов динамической маршрутизации в сети (BGP, IS-IS, OSPF)
  • просчитать RIB каждого сетевого элемента
  • проверить настройки NTP, AAA, MTU
  • позволить определить, блокирует ли ACL прохождение сетевого трафика (аналог packet-tracer на Cisco ASA)
  • проверить наличие end-to-end связности между хостами внутри сети
  • показать путь прохождения трафика через сеть (виртуальная трассировка)

Поддерживаемые платформы:

  • Arista
  • Aruba
  • AWS (VPCs, Network ACLs, VPN GW, NAT GW, Internet GW, Security Groups)
  • Cisco (NX-OS, IOS, IOS-XE, IOS-XR и ASA)
  • Dell Force10
  • Foundry
  • iptables
  • Juniper (MX, EX, QFX, SRX, T-series, PTX)
  • MRV
  • Palo Alto Networks
  • Quagga / FRR
  • Quanta
  • VyOS

image

Для удобной работы с ним был написан Pybatfish — python SDK. Batfish – это Java приложение.

Я продемонстрирую Вам возможности Batfish на примере. Перейдем к практике.

Пример

Под нашим управлением находится две автономные системы: AS 41214 и AS 10631. В качестве IGP в AS 41214 используется IS-IS, в AS 10631 – OSPF. Внутри каждой AS используется IBGP-fullmesh. LDN-CORE-01 анонсирует своим соседям по BGP префикс 135.65.0.0/19, MSK-CORE-01 – 140.0.0.0/24. Обмен маршрутной информацией между автономными системами происходит на стыке HKI-CORE-01 — SPB-CORE-01.

HKI-CORE-01, STH-CORE-01 — Junos routers
LDN-CORE-01, AMS-CORE-01, SPB-CORE-01, MSK-CORE-01 — Cisco IOS routers

Установим контейнер с Batfish и python SDK:

docker pull batfish/allinone
docker run batfish/allinone
docker container exec -it <container> bash

Познакомимся с библиотекой через интерактивный режим python:

root@ea9a1559d88e:/# python3
--------------------
>>> from pybatfish.client.commands import bf_logger, bf_init_snapshot
>>> from pybatfish.question.question import load_questions
>>> from pybatfish.question import bfq
>>> import logging
>>> bf_logger.setLevel(logging.ERROR)
>>> load_questions()
>>> bf_init_snapshot('tmp/habr') 'ss_e8065858-a911-4f8a-b020-49c9b96d0381'

bf_init_snapshot('tmp/habr') — функция загружает конфигурационные файлы в Batfish и подготавливает их к анализу.

/tmp/habr – директория с конфигурационными файлами роутеров.

root@ea9a1559d88e:/tmp/habr# tree
.
`-- configs |-- AMS-CORE-01.cfg |-- HKI-CORE-01.cfg |-- LDN-CORE-01.cfg |-- MSK-CORE-01.cfg |-- SPB-CORE-01.cfg `-- STH-CORE-01.cfg 1 directory, 6 files

Теперь давайте определим статус BGP-сессий на роутере LDN-CORE-01:


>>> bgp_peers = bfq.bgpSessionStatus(nodes='LDN-CORE-01').answer().frame()
>>> bgp_peers
Node VRF Local_AS Local_IP Remote_AS Remote_Node Remote_IP Session_Type Est_Status
0 ldn-core-01 default 41214 172.20.20.1 41214 sth-core-01 172.20.20.2 IBGP EST
1 ldn-core-01 default 41214 172.20.20.1 41214 ams-core-01 172.20.20.3 IBGP EST
2 ldn-core-01 default 41214 172.20.20.1 41214 hki-core-01 172.20.20.4 IBGP EST

Ну, как? Похоже на правду?


LDN-CORE-01#show ip bgp summary

Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
172.20.20.2 4 41214 629 669 9 0 0 00:56:51 0
172.20.20.3 4 41214 826 827 9 0 0 01:10:18 0
172.20.20.4 4 41214 547 583 9 0 0 00:49:24 1

Теперь давайте посмотрим, какие IS-IS маршруты есть в RIB на маршрутизаторе HKI-CORE-01 по мнению Batfish:


>>> isis_routes = bfq.routes(nodes='HKI-CORE-01', protocols='isis').answer().frame()
>>> isis_routes
Node VRF Network Next_Hop Next_Hop_IP Protocol Admin_Distance Metric Tag
0 hki-core-01 default 172.20.20.3/32 ams-core-01 10.0.0.6 isisL2 18 20 None
1 hki-core-01 default 172.20.20.1/32 ams-core-01 10.0.0.6 isisL2 18 30 None
2 hki-core-01 default 172.20.20.2/32 sth-core-01 10.0.0.4 isisL2 18 10 None
3 hki-core-01 default 172.20.20.1/32 sth-core-01 10.0.0.4 isisL2 18 30 None
4 hki-core-01 default 10.0.0.0/31 sth-core-01 10.0.0.4 isisL2 18 20 None
5 hki-core-01 default 10.0.0.2/31 ams-core-01 10.0.0.6 isisL2 18 20 None

В командной строке:


showroute@HKI-CORE-01# run show route table inet.0 protocol isis
inet.0: 18 destinations, 18 routes (18 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both
10.0.0.0/31 *[IS-IS/18] 00:51:25, metric 20 > to 10.0.0.4 via ge-0/0/0.0
10.0.0.2/31 *[IS-IS/18] 00:51:45, metric 20 > to 10.0.0.6 via ge-0/0/1.0
172.20.20.1/32 *[IS-IS/18] 00:51:25, metric 30 to 10.0.0.4 via ge-0/0/0.0 > to 10.0.0.6 via ge-0/0/1.0
172.20.20.2/32 *[IS-IS/18] 00:51:25, metric 10 > to 10.0.0.4 via ge-0/0/0.0
172.20.20.3/32 *[IS-IS/18] 00:51:45, metric 20 > to 10.0.0.6 via ge-0/0/1.0

Отлично! Полагаю, Вам стало яснее, что есть Batfish.

Теперь я предлагаю рассмотреть процесс проведения тестирования сети на базе RobotFramework. В начале статьи я писал, что Batfish можно использовать для проверки конфигурационных изменений перед их внесением в «боевую» сеть. Для этого я написал небольшой модуль на основе PyBatfish, позволяющий выполнять следующие проверки:

  • Определять статус BGP-сессий в сети
  • Определять состояние IS-IS соседей
  • Проверять наличие end-to-end связности между узлами в сети с демонстрацией трассировки
  • Определять размер RIB на роутере для определенного протокола динамической маршрутизации

LibraryBatfish.py

import logging from pybatfish.client.commands import bf_logger, bf_init_snapshot
from pybatfish.question.question import load_questions, list_questions
from pybatfish.question import bfq
from pybatfish.datamodel.flow import HeaderConstraints, PathConstraints
from robot.api import logger class LibraryBatfish(object): def __init__(self, snapshot): bf_logger.setLevel(logging.ERROR) load_questions() bf_init_snapshot(snapshot) def check_bgp_peers(self): not_established_peers = list() bgp_peers = bfq.bgpSessionStatus().answer() for peer in bgp_peers.rows: if peer.get('Established_Status') != 'ESTABLISHED': not_established_peers.append(dict.fromkeys(peer.get('Local_IP').split(), peer.get('Remote_IP').get('value'))) if len(not_established_peers) == 0: return 1 else: logger.warn('BGP neighbors are not in an established state:') for neighborship in not_established_peers: for peer in neighborship: logger.warn(' - {}'.format(peer, neighborship.get(peer))) return 0 def check_routes(self, node, protocol): routes = bfq.routes(nodes=node, protocols=protocol).answer() return len(routes.rows) def check_isis_neighbors(self, description): not_isis_enabled_links = list() for link in self._get_isis_enabled_links(description): if link not in self._get_isis_neighbors(): not_isis_enabled_links.append(link) if len(not_isis_enabled_links) == 0: return 1 else: for link in not_isis_enabled_links: logger.warn('{} {} has no IS-IS neighbor'.format(link.get('hostname'), link.get('interface'))) return 0 def ping(self, source_ip, destination_ip): ip_owners = bfq.ipOwners().answer() traceroute = self._get_traceroute_status(source_ip, destination_ip, ip_owners) reverse_traceroute = self._get_traceroute_status(destination_ip, source_ip, ip_owners) if traceroute == True and reverse_traceroute == True: self._show_trace(source_ip, destination_ip, ip_owners) return 1 else: logger.warn('Ping {} -> {} failed'.format(source_ip, destination_ip)) return 0 def _get_traceroute_status(self, source_ip, destination_ip, addresses): tracert = self._unidirectional_virtual_traceroute(source_ip, destination_ip, addresses) isAccepted = True if tracert != None: for trace in tracert.rows[0].get('Traces'): if trace.get('disposition') != 'ACCEPTED': isAccepted = False if isAccepted == True: return True else: return False def _get_paths(self, source_ip, destination_ip, addresses): tracert = self._unidirectional_virtual_traceroute(source_ip, destination_ip, addresses) traces = tracert.rows[0].get('Traces') paths = dict() path_number = 1 for trace in traces: if trace.get('disposition') == 'ACCEPTED': path = list() for hop in trace.get('hops'): path.append(hop.get('node').get('name')) paths[path_number] = path path_number += 1 return paths def _unidirectional_virtual_traceroute(self, source_ip, destination_ip, addresses): for address in addresses.rows: if address.get('IP') == source_ip: node = address.get('Node').get('name') int = address.get('Interface') headers = HeaderConstraints(srcIps=source_ip, dstIps=destination_ip, ipProtocols=['ICMP']) try: tracert = bfq.traceroute(startLocation="{}[{}]".format(node,int), headers=headers).answer() return tracert except: logger.warn('{} address has not been found'.format(source_ip)) def _get_isis_enabled_links(self, description='core-link'): isis_enabled_links = list() interfaces = bfq.interfaceProperties().answer() for int in interfaces.rows: if int.get('Description') != None and description in int.get('Description'): isis_enabled_links.append({'hostname' : int.get('Interface').get('hostname'), 'interface' : int.get('Interface').get('interface')}) return isis_enabled_links def _get_isis_neighbors(self): isis_neighbors = list() isis_adjacencies = bfq.edges(edgeType='isis').answer() for neighbor in isis_adjacencies.rows: isis_neighbors.append(neighbor.get('Interface')) return isis_neighbors def _show_trace(self, source_ip, destination_ip, addresses): logger.console('\nTraceroute to {} from {}'.format(destination_ip, source_ip)) paths = self._get_paths(source_ip, destination_ip, addresses) path_num = 1 for path in paths: n = 1 logger.console('\n Path N{}'.format(path_num)) for hop in paths.get(path): logger.console(' {} {}'.format(n, hop)) n += 1 path_num += 1

batfish-test.robot

image

Сценарий N1

Допустим, мне требуется привести в порядок фильтры на границе AS 41214 и AS 10631 и заблокировать на стыке пакеты, содержащие в source или destination ip адреса из диапазона BOGONS. Под моим управлением находится все та же сеть.

Запускаем тест до внесения изменений.

image

Тесты пройдены.

Внесем изменения в тестовую конфигурацию роутера HKI-CORE-01 — /tmp/habr/configs/HKI-CORE-01.cfg:


set firewall family inet filter BOGONS term TERM010 from address 0.0.0.0/8
set firewall family inet filter BOGONS term TERM010 from address 10.0.0.0/8
set firewall family inet filter BOGONS term TERM010 from address 100.64.0.0/10
set firewall family inet filter BOGONS term TERM010 from address 127.0.0.0/8
set firewall family inet filter BOGONS term TERM010 from address 169.254.0.0/16
set firewall family inet filter BOGONS term TERM010 from address 172.16.0.0/12
set firewall family inet filter BOGONS term TERM010 from address 192.0.2.0/24
set firewall family inet filter BOGONS term TERM010 from address 192.88.99.0/24
set firewall family inet filter BOGONS term TERM010 from address 192.168.0.0/16
set firewall family inet filter BOGONS term TERM010 from address 198.18.0.0/15
set firewall family inet filter BOGONS term TERM010 from address 198.51.100.0/24
set firewall family inet filter BOGONS term TERM010 from address 203.0.113.0/24
set firewall family inet filter BOGONS term TERM010 from address 224.0.0.0/4
set firewall family inet filter BOGONS term TERM010 from address 240.0.0.0/4
set firewall family inet filter BOGONS term TERM010 then discard
set firewall family inet filter BOGONS term PERMIT-IP-ANY-ANY then accept set interfaces ge-0/0/2.0 family inet filter input BOGONS set interfaces ge-0/0/2.0 family inet filter output BOGONS

Запускаем тест.

168. Я был очень близок, но как показывает вывод теста, после внесенных изменений BGP соседство 192. 0 – 192. 30. 30. 168. 65. 1 находится не в состоянии Established -> как следствие, теряется IP связность между точками 135. 1 <-> 140. 0. 0. 0. Что же не так? 1. Смотрим внимательно в конфигурацию HKI-CORE-01 и видим, что eBGP пиринг установлен на приватных адресах:


showroute@HKI-CORE-01# show interfaces ge-0/0/2 | display set set interfaces ge-0/0/2 description SPB-CORE-01
set interfaces ge-0/0/2 unit 0 family inet filter input BOGONS
set interfaces ge-0/0/2 unit 0 family inet filter output BOGONS
set interfaces ge-0/0/2 unit 0 family inet address 192.168.30.0/31

Вывод: необходимо поменять адреса на стыке или добавить в исключение подсеть 192.168.30.0/31.

Добавлю сеть на стыке в исключение, вновь обновлю /tmp/habr/configs/HKI-CORE-01.cfg:


set firewall family inet filter BOGONS term TERM005 from address 192.168.0.0/31 set firewall family inet filter BOGONS term TERM005 then accept

Запускаем тест.

Можно смело вносить изменения, не опасаясь последствий. Теперь нежелательный трафик не пройдет через ebgp стык AS 41214 – AS 10631.

Сценарий N2

0. Здесь мне необходимо затерминировать сеть 150. 0/24 на роутере MSK-CORE-01 и обеспечить связность между точками 135. 0. 0. 65. 0. 1 и 150. 1 0.

Добавляю следующие строки в тестовую конфигурацию маршрутизатора MSK-CORE-01 — tmp/habr/configs/MSK-CORE-01.cfg:


interface Loopback2 ip address 150.0.0.1 255.255.255.255
!
ip route 150.0.0.0 255.255.255.0 Null0
!
router bgp 10631 ! address-family ipv4 network 150.0.0.0 mask 255.255.255.0
!

Изменяю тестовый сценарий и запускаю проверку:


git diff HEAD~
diff --git a/batfish-robot.robot b/batfish-robot.robot
index 8d963c5..ce8cb6a 100644
--- a/batfish-robot.robot
+++ b/batfish-robot.robot
@@ -5,7 +5,7 @@ Library LibraryBatfish.py tmp/habr ${ISIS-ENABLED-LINK-DESCRIPTION} ISIS-LINK ${NODE} HKI-CORE-01 ${PROTOCOL} ebgp
-${RIB-SIZE} 1
+${RIB-SIZE} 2 *** Test Cases *** ISIS
@@ -27,3 +27,8 @@ Ping [Documentation] Test end-to-end ICMP connectivity & show traceroute ${result}= Ping 135.65.0.1 140.0.0.1 Should Be Equal As Integers ${result} 1
+
+Ping2
+ [Documentation] Test end-to-end ICMP connectivity & show traceroute
+ ${result}= Ping 135.65.0.1 150.0.0.1
+ Should Be Equal As Integers ${result} 1

теперь я ожидаю увидеть два eBGP маршрута на роутере HKI-CORE-01, так же добавлена дополнительная проверка связности

65. Связности между 135. 1 и 150. 0. 0. 0. 1 нет, к тому же на маршрутизаторе HKI-CORE-01 всего один eBGP маршрут, вместо двух.

Проверяем содержание RIB на HKI-CORE-01 при добавлении новой конфигурации на роутер MSK-CORE-01:


showroute@HKI-CORE-01# run show route table inet.0 protocol bgp inet.0: 20 destinations, 20 routes (19 active, 0 holddown, 1 hidden)
+ = Active Route, - = Last Active, * = Both 135.65.0.0/19 *[BGP/170] 02:25:38, MED 0, localpref 100, from 172.20.20.1 AS path: I, validation-state: unverified > to 10.0.0.4 via ge-0/0/0.0 to 10.0.0.6 via ge-0/0/1.0
140.0.0.0/24 *[BGP/170] 01:38:02, localpref 100 AS path: 10631 I, validation-state: unverified > to 192.168.30.1 via ge-0/0/2.0 showroute@HKI-CORE-01# run show route table inet.0 protocol bgp hidden detail inet.0: 20 destinations, 20 routes (19 active, 0 holddown, 1 hidden)
150.0.0.0/24 (1 entry, 0 announced) BGP /-101 Next hop type: Router, Next hop index: 563 Address: 0x940f43c Next-hop reference count: 4 Source: 192.168.30.1 Next hop: 192.168.30.1 via ge-0/0/2.0, selected Session Id: 0x9 State: <Hidden Ext> Local AS: 41214 Peer AS: 10631 Age: 1:42:03 Validation State: unverified Task: BGP_10631.192.168.30.1+179 AS path: 10631 I Localpref: 100 Router ID: 10.68.1.1 Hidden reason: rejected by import policy

Обратите внимание на политику импорта префиксов, полученных от SPB-CORE-01:


set protocols bgp group AS10631 import FROM-AS10631
set protocols bgp group AS10631 neighbor 192.168.30.1 description SPB-CORE-01
set protocols bgp group AS10631 neighbor 192.168.30.1 peer-as 10631
set policy-options policy-statement FROM-AS10631 term TERM010 from route-filter 140.0.0.0/24 exact
set policy-options policy-statement FROM-AS10631 term TERM010 then accept
set policy-options policy-statement FROM-AS10631 term DENY then reject

Не хватает правила, разрешающего 150.0.0.0/24. Добавляем его в тестовую конфигурацию и запускаем проверку:


showroute@HKI-CORE-01# show | compare
[edit policy-options policy-statement FROM-AS10631 term TERM010 from] route-filter 140.0.0.0/24 exact { ... }
+ route-filter 150.0.0.0/24 exact; [edit]

Значить можно внести данные изменения в работу «боевой» сети. Отлично, связность между сетями есть, все тесты пройдены!

Заключение

На мой взгляд, Batfish — это мощнейщий инструмент с огромным потенциалом. Попробуйте и убедитесь в этом сами.

Если данная тема Вам интересна — присоединяйтесь в slack чат, разработчики Batfish с удовольсвтием отвечают на любые вопросы и быстро правят баги.

batfish-org.slack.com

Благодарю за внимание.

Ссылки

www.batfish.org

www.youtube.com/channel/UCA-OUW_3IOt9U_s60KvmJYA

github.com/batfish/batfish

media.readthedocs.org/pdf/pybatfish/latest/pybatfish.pdf

github.com/showroute/batfish-habr

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть