Pull to refresh

Batfish. Введение

Reading time11 min
Views13K
image

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

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

Перейдем к практике. Я продемонстрирую Вам возможности 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.

В начале статьи я писал, что Batfish можно использовать для проверки конфигурационных изменений перед их внесением в «боевую» сеть. Теперь я предлагаю рассмотреть процесс проведения тестирования сети на базе RobotFramework. Для этого я написал небольшой модуль на основе 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  

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



Я был очень близок, но как показывает вывод теста, после внесенных изменений BGP соседство 192.168.30.0 – 192.168.30.1 находится не в состоянии Established -> как следствие, теряется IP связность между точками 135.65.0.1 <-> 140.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




Здесь мне необходимо затерминировать сеть 150.0.0.0/24 на роутере MSK-CORE-01 и обеспечить связность между точками 135.65.0.1 и 150.0.0.1

Добавляю следующие строки в тестовую конфигурацию маршрутизатора 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, так же добавлена дополнительная проверка связности



Связности между 135.65.0.1 и 150.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
Tags:
Hubs:
+18
Comments4

Articles

Change theme settings