{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Introduction to BGP Analysis using Batfish" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Network engineers routinely need to validate BGP configuration and session status in the network. They often do that by connecting to multiple network devices and executing a series of `show ip bgp` commands. This distributed debugging is highly complex even in a moderately-sized network. And it is reactive, the configuration changes are already in the network. \n", "\n", "Batfish allows network engineers to proactively validate BGP configuration to ensure sessions are compatibly configured and will be established, thereby avoiding potential network outages.\n", "\n", "In this notebook, we will look at how you can extract BGP configuration and session status information from Batfish." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "# Import packages \n", "%run startup.py\n", "bf = Session(host=\"localhost\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Initializing the Network and Snapshot" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`SNAPSHOT_PATH` below can be updated to point to a custom snapshot directory, see the [Batfish instructions](https://github.com/batfish/batfish/wiki/Packaging-snapshots-for-analysis) for how to package data for analysis.
\n", "More example networks are available in the [networks](https://github.com/batfish/batfish/tree/master/networks) folder of the Batfish repository." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'example_snapshot'" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Initialize a network and snapshot\n", "NETWORK_NAME = \"example_network\"\n", "SNAPSHOT_NAME = \"example_snapshot\"\n", "\n", "SNAPSHOT_PATH = \"networks/example-bgp\"\n", "\n", "bf.set_network(NETWORK_NAME)\n", "bf.init_snapshot(SNAPSHOT_PATH, name=SNAPSHOT_NAME, overwrite=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The network snapshot that we initialized above is illustrated below. You can download/view devices' configuration files [here](https://github.com/batfish/pybatfish/tree/master/jupyter_notebooks/networks/example-bgp).\n", "\n", "![example-bgp-network](https://raw.githubusercontent.com/batfish/pybatfish/master/jupyter_notebooks/networks/example-bgp/example-bgp-network.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "All of the information we will show you in this notebook is dynamically computed by Batfish based on the configuration files for the network devices." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Batfish BGP related questions\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Batfish has a number of questions dedicated to extracting BGP configuration and state information. This notebook will show you how to use each of them to extract information you need to validate a configuration change or troubleshoot a BGP issue" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1) `bf.q.bgpProcessConfiguration` - This will return common information about the BGP process: router id, whether the device is acting as a route-reflector, multipath settings, list of neighbors, etc.\n", "\n", "2) `bf.q.bgpPeerConfiguration` - This will return the list of neighbors for the given node(s) and specific attributes for each configured neighbor such as: device local IP address, Local Interface (only use for BGP unnumbered sessions), remote-as, remote/peer/neighbor IP, peer-group, import and export policy, etc.\n", "\n", "3) `bf.q.bgpSessionCompatbility` - For each configured BGP peer, this question will return the `Configured_Status`. This is Batfish's view of whether or not a given BGP session is properly configured. Any peer that does not have `Configured_Status` equal to `UNIQUE_MATCH` or `DYNAMIC_MATCH` is misconfigured.\n", "\n", "4) `bf.q.bgpSessionStatus` - For each configured BGP peer, this question will return the `Established_Status`. This is the status of the session after Batfish has completed analyzing the configurations and building the routing and forwarding tables. Any BGP peer which has `Established_Status` other than `ESTABLISHED` indicates a misconfiguration. \n", "\n", "In addition to these 4 questions, we have created questions designed to test routing policies, which we will cover in a subsequent notebook." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### View BGP Configuration for ALL devices\n", "Batfish makes BGP Configuration settings in the network easily accessible. Let's take a look at how you can retrieve the specific information you want. Let's start with configuration attributes of the BGP process on all devices running BGP by using the question `bf.q.bgpProcessConfiguration`." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFRouter_IDConfederation_IDConfederation_MembersMultipath_EBGPMultipath_IBGPMultipath_Match_ModeNeighborsRoute_ReflectorTie_Breaker
0as1border1default1.1.1.1NoneNoneTrueTrueEXACT_PATH['3.2.2.2', '1.10.1.1', '5.6.7.8', '10.12.11.2']FalseARRIVAL_ORDER
1as1border2default1.2.2.2NoneNoneTrueTrueEXACT_PATH['10.14.22.4', '1.10.1.1', '10.13.22.3']FalseARRIVAL_ORDER
2as1core1default1.10.1.1NoneNoneTrueTrueEXACT_PATH['1.1.1.1', '1.2.2.2']TrueARRIVAL_ORDER
3as2border1default2.1.1.1NoneNoneTrueTrueEXACT_PATH['2.1.2.1', '2.1.2.2', '10.12.11.1']FalseARRIVAL_ORDER
4as2border2default2.1.1.2NoneNoneTrueTrueEXACT_PATH['2.1.2.1', '2.1.2.2', '10.23.21.3']FalseARRIVAL_ORDER
5as2core1default2.1.2.1NoneNoneTrueTrueEXACT_PATH['2.1.1.1', '2.1.3.1', '2.1.1.2', '2.1.3.2']TrueARRIVAL_ORDER
6as2core2default2.1.2.2NoneNoneTrueTrueEXACT_PATH['2.1.1.1', '2.1.3.1', '2.1.1.2', '2.1.3.2']TrueARRIVAL_ORDER
7as2dept1default2.1.4.1NoneNoneTrueTrueEXACT_PATH['2.34.101.3', '2.34.201.3']FalseARRIVAL_ORDER
8as2dist1default2.1.3.1NoneNoneTrueTrueEXACT_PATH['2.1.2.1', '2.1.2.2', '2.34.0.0/16']FalseARRIVAL_ORDER
9as2dist2default2.1.3.2NoneNoneTrueTrueEXACT_PATH['2.1.2.1', '2.1.2.2', '2.34.0.0/16']FalseARRIVAL_ORDER
10as3border1default3.1.1.1NoneNoneTrueTrueEXACT_PATH['10.23.21.2', '3.10.1.1']FalseARRIVAL_ORDER
11as3border2default3.2.2.2NoneNoneTrueTrueEXACT_PATH['3.10.1.1', '10.13.22.1']FalseARRIVAL_ORDER
12as3core1default3.10.1.1NoneNoneTrueTrueEXACT_PATH['3.1.1.1', '3.2.2.2']TrueARRIVAL_ORDER
\n", "
" ], "text/plain": [ " Node VRF Router_ID Confederation_ID Confederation_Members \\\n", "0 as1border1 default 1.1.1.1 None None \n", "1 as1border2 default 1.2.2.2 None None \n", "2 as1core1 default 1.10.1.1 None None \n", "3 as2border1 default 2.1.1.1 None None \n", "4 as2border2 default 2.1.1.2 None None \n", "5 as2core1 default 2.1.2.1 None None \n", "6 as2core2 default 2.1.2.2 None None \n", "7 as2dept1 default 2.1.4.1 None None \n", "8 as2dist1 default 2.1.3.1 None None \n", "9 as2dist2 default 2.1.3.2 None None \n", "10 as3border1 default 3.1.1.1 None None \n", "11 as3border2 default 3.2.2.2 None None \n", "12 as3core1 default 3.10.1.1 None None \n", "\n", " Multipath_EBGP Multipath_IBGP Multipath_Match_Mode \\\n", "0 True True EXACT_PATH \n", "1 True True EXACT_PATH \n", "2 True True EXACT_PATH \n", "3 True True EXACT_PATH \n", "4 True True EXACT_PATH \n", "5 True True EXACT_PATH \n", "6 True True EXACT_PATH \n", "7 True True EXACT_PATH \n", "8 True True EXACT_PATH \n", "9 True True EXACT_PATH \n", "10 True True EXACT_PATH \n", "11 True True EXACT_PATH \n", "12 True True EXACT_PATH \n", "\n", " Neighbors Route_Reflector \\\n", "0 ['3.2.2.2', '1.10.1.1', '5.6.7.8', '10.12.11.2'] False \n", "1 ['10.14.22.4', '1.10.1.1', '10.13.22.3'] False \n", "2 ['1.1.1.1', '1.2.2.2'] True \n", "3 ['2.1.2.1', '2.1.2.2', '10.12.11.1'] False \n", "4 ['2.1.2.1', '2.1.2.2', '10.23.21.3'] False \n", "5 ['2.1.1.1', '2.1.3.1', '2.1.1.2', '2.1.3.2'] True \n", "6 ['2.1.1.1', '2.1.3.1', '2.1.1.2', '2.1.3.2'] True \n", "7 ['2.34.101.3', '2.34.201.3'] False \n", "8 ['2.1.2.1', '2.1.2.2', '2.34.0.0/16'] False \n", "9 ['2.1.2.1', '2.1.2.2', '2.34.0.0/16'] False \n", "10 ['10.23.21.2', '3.10.1.1'] False \n", "11 ['3.10.1.1', '10.13.22.1'] False \n", "12 ['3.1.1.1', '3.2.2.2'] True \n", "\n", " Tie_Breaker \n", "0 ARRIVAL_ORDER \n", "1 ARRIVAL_ORDER \n", "2 ARRIVAL_ORDER \n", "3 ARRIVAL_ORDER \n", "4 ARRIVAL_ORDER \n", "5 ARRIVAL_ORDER \n", "6 ARRIVAL_ORDER \n", "7 ARRIVAL_ORDER \n", "8 ARRIVAL_ORDER \n", "9 ARRIVAL_ORDER \n", "10 ARRIVAL_ORDER \n", "11 ARRIVAL_ORDER \n", "12 ARRIVAL_ORDER " ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Get BGP process configuration information for ALL devices\n", "bgp_config = bf.q.bgpProcessConfiguration().answer().frame()\n", "bgp_config" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's drill into the configuration of a specific BGP session. Let's look at the sessions on `as2dept1`. To do this, we will use the `bf.q.bgpPeerConfiguration` question. " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFLocal_ASLocal_IPLocal_InterfaceConfederationRemote_ASRemote_IPDescriptionRoute_Reflector_ClientCluster_IDPeer_GroupImport_PolicyExport_PolicySend_CommunityIs_Passive
0as2dept1default650012.34.101.4NoneNone22.34.101.3NoneFalseNoneas2['as2_to_dept']['dept_to_as2']TrueFalse
1as2dept1default650012.34.201.4NoneNone22.34.201.3NoneFalseNoneas2['as2_to_dept']['dept_to_as2']TrueFalse
\n", "
" ], "text/plain": [ " Node VRF Local_AS Local_IP Local_Interface Confederation \\\n", "0 as2dept1 default 65001 2.34.101.4 None None \n", "1 as2dept1 default 65001 2.34.201.4 None None \n", "\n", " Remote_AS Remote_IP Description Route_Reflector_Client Cluster_ID \\\n", "0 2 2.34.101.3 None False None \n", "1 2 2.34.201.3 None False None \n", "\n", " Peer_Group Import_Policy Export_Policy Send_Community Is_Passive \n", "0 as2 ['as2_to_dept'] ['dept_to_as2'] True False \n", "1 as2 ['as2_to_dept'] ['dept_to_as2'] True False " ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Get all of the BGP peer configuration for as2dept1 devices\n", "bgp_peer_config = bf.q.bgpPeerConfiguration(nodes='as2dept1').answer().frame()\n", "bgp_peer_config" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### View BGP Session Status" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have seen the configuration for each peer on `as2dept1`, let's ensure that their configuration is compatible with their peers\n", "\n", "The `bgpSessionCompatibility` question allows you to ensure that BGP sessions are compatibly configured, so that if there is IP reachability between the peers the sessions will be established. Compatiblity checks that the remote-as matches up on both ends, the correct update source is specified, peer-ip addresses match-up, etc..." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFLocal_ASLocal_InterfaceLocal_IPRemote_ASRemote_NodeRemote_InterfaceRemote_IPAddress_FamiliesSession_TypeConfigured_Status
0as2dept1default65001None2.34.101.42as2dist1None2.34.101.3['IPV4_UNICAST']EBGP_SINGLEHOPUNIQUE_MATCH
1as2dept1default65001None2.34.201.42as2dist2None2.34.201.3['IPV4_UNICAST']EBGP_SINGLEHOPUNIQUE_MATCH
\n", "
" ], "text/plain": [ " Node VRF Local_AS Local_Interface Local_IP Remote_AS \\\n", "0 as2dept1 default 65001 None 2.34.101.4 2 \n", "1 as2dept1 default 65001 None 2.34.201.4 2 \n", "\n", " Remote_Node Remote_Interface Remote_IP Address_Families Session_Type \\\n", "0 as2dist1 None 2.34.101.3 ['IPV4_UNICAST'] EBGP_SINGLEHOP \n", "1 as2dist2 None 2.34.201.3 ['IPV4_UNICAST'] EBGP_SINGLEHOP \n", "\n", " Configured_Status \n", "0 UNIQUE_MATCH \n", "1 UNIQUE_MATCH " ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Check if the bgp Sessions on as2dept1 are properly configured\n", "bgpSessCompat = bf.q.bgpSessionCompatibility(nodes='as2dept1').answer().frame()\n", "bgpSessCompat" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Both of the configured BGP peers on `as2dept1` are compatible. We know that since the `Configured_Status` is `UNIQUE_MATCH`. So now let's check if they are established." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFLocal_ASLocal_InterfaceLocal_IPRemote_ASRemote_NodeRemote_InterfaceRemote_IPAddress_FamiliesSession_TypeEstablished_Status
0as2dept1default65001None2.34.101.42as2dist1None2.34.101.3['IPV4_UNICAST']EBGP_SINGLEHOPESTABLISHED
1as2dept1default65001None2.34.201.42as2dist2None2.34.201.3['IPV4_UNICAST']EBGP_SINGLEHOPESTABLISHED
\n", "
" ], "text/plain": [ " Node VRF Local_AS Local_Interface Local_IP Remote_AS \\\n", "0 as2dept1 default 65001 None 2.34.101.4 2 \n", "1 as2dept1 default 65001 None 2.34.201.4 2 \n", "\n", " Remote_Node Remote_Interface Remote_IP Address_Families Session_Type \\\n", "0 as2dist1 None 2.34.101.3 ['IPV4_UNICAST'] EBGP_SINGLEHOP \n", "1 as2dist2 None 2.34.201.3 ['IPV4_UNICAST'] EBGP_SINGLEHOP \n", "\n", " Established_Status \n", "0 ESTABLISHED \n", "1 ESTABLISHED " ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Check if the bgp Sessions on as2dept1 are ESTABLISHED\n", "bgpSessStat = bf.q.bgpSessionStatus(nodes='as2dept1').answer().frame()\n", "bgpSessStat" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Both sessions are established. Let's see if there are any configured BGP sessions, on any other device in the network, that are not established" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFLocal_ASLocal_InterfaceLocal_IPRemote_ASRemote_NodeRemote_InterfaceRemote_IPAddress_FamiliesSession_TypeEstablished_Status
0as1border1default1NoneNone666NoneNone3.2.2.2[]EBGP_SINGLEHOPNOT_COMPATIBLE
2as1border1default1NoneNone555NoneNone5.6.7.8[]EBGP_SINGLEHOPNOT_COMPATIBLE
4as1border2default1None10.14.22.14NoneNone10.14.22.4[]EBGP_SINGLEHOPNOT_COMPATIBLE
9as2border1default2None2.1.1.12as2core1None2.1.2.1['IPV4_UNICAST']IBGPNOT_ESTABLISHED
12as2border2default2None2.1.1.22as2core1None2.1.2.1['IPV4_UNICAST']IBGPNOT_ESTABLISHED
15as2core1default2None2.1.2.12as2border1None2.1.1.1['IPV4_UNICAST']IBGPNOT_ESTABLISHED
16as2core1default2None2.1.2.12as2dist1None2.1.3.1['IPV4_UNICAST']IBGPNOT_ESTABLISHED
17as2core1default2None2.1.2.12as2border2None2.1.1.2['IPV4_UNICAST']IBGPNOT_ESTABLISHED
18as2core1default2None2.1.2.12as2dist2None2.1.3.2['IPV4_UNICAST']IBGPNOT_ESTABLISHED
25as2dist1default2None2.1.3.12as2core1None2.1.2.1['IPV4_UNICAST']IBGPNOT_ESTABLISHED
28as2dist2default2None2.1.3.22as2core1None2.1.2.1['IPV4_UNICAST']IBGPNOT_ESTABLISHED
\n", "
" ], "text/plain": [ " Node VRF Local_AS Local_Interface Local_IP Remote_AS \\\n", "0 as1border1 default 1 None None 666 \n", "2 as1border1 default 1 None None 555 \n", "4 as1border2 default 1 None 10.14.22.1 4 \n", "9 as2border1 default 2 None 2.1.1.1 2 \n", "12 as2border2 default 2 None 2.1.1.2 2 \n", "15 as2core1 default 2 None 2.1.2.1 2 \n", "16 as2core1 default 2 None 2.1.2.1 2 \n", "17 as2core1 default 2 None 2.1.2.1 2 \n", "18 as2core1 default 2 None 2.1.2.1 2 \n", "25 as2dist1 default 2 None 2.1.3.1 2 \n", "28 as2dist2 default 2 None 2.1.3.2 2 \n", "\n", " Remote_Node Remote_Interface Remote_IP Address_Families Session_Type \\\n", "0 None None 3.2.2.2 [] EBGP_SINGLEHOP \n", "2 None None 5.6.7.8 [] EBGP_SINGLEHOP \n", "4 None None 10.14.22.4 [] EBGP_SINGLEHOP \n", "9 as2core1 None 2.1.2.1 ['IPV4_UNICAST'] IBGP \n", "12 as2core1 None 2.1.2.1 ['IPV4_UNICAST'] IBGP \n", "15 as2border1 None 2.1.1.1 ['IPV4_UNICAST'] IBGP \n", "16 as2dist1 None 2.1.3.1 ['IPV4_UNICAST'] IBGP \n", "17 as2border2 None 2.1.1.2 ['IPV4_UNICAST'] IBGP \n", "18 as2dist2 None 2.1.3.2 ['IPV4_UNICAST'] IBGP \n", "25 as2core1 None 2.1.2.1 ['IPV4_UNICAST'] IBGP \n", "28 as2core1 None 2.1.2.1 ['IPV4_UNICAST'] IBGP \n", "\n", " Established_Status \n", "0 NOT_COMPATIBLE \n", "2 NOT_COMPATIBLE \n", "4 NOT_COMPATIBLE \n", "9 NOT_ESTABLISHED \n", "12 NOT_ESTABLISHED \n", "15 NOT_ESTABLISHED \n", "16 NOT_ESTABLISHED \n", "17 NOT_ESTABLISHED \n", "18 NOT_ESTABLISHED \n", "25 NOT_ESTABLISHED \n", "28 NOT_ESTABLISHED " ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Find any BGP sessions in the network that are NOT ESTABLISHED\n", "bgpSessStat = bf.q.bgpSessionStatus().answer().frame()\n", "bgpSessStat[bgpSessStat['Established_Status'] != 'ESTABLISHED']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Looking at the `Established_Status` column we see that there are a lot of sessions that are configured, but not established. Let's dig into these issues. \n", "\n", "First, let's find all of the sessions that are not compatibly configured. For that we are looking for sessions which are not either a `UNIQUE_MATCH` or `DYNAMIC_MATCH`. The latter is for `dynamic` peers (these are peers configured to use a `listen-range`)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Debug NOT_COMPATIBLE sessions " ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFLocal_ASLocal_InterfaceLocal_IPRemote_ASRemote_NodeRemote_InterfaceRemote_IPAddress_FamiliesSession_TypeConfigured_Status
0as1border1default1NoneNone666NoneNone3.2.2.2[]EBGP_SINGLEHOPNO_LOCAL_IP
2as1border1default1NoneNone555NoneNone5.6.7.8[]EBGP_SINGLEHOPNO_LOCAL_IP
4as1border2default1None10.14.22.14NoneNone10.14.22.4[]EBGP_SINGLEHOPUNKNOWN_REMOTE
13as2border2default2NoneNone2NoneNone2.1.2.2[]IBGPLOCAL_IP_UNKNOWN_STATICALLY
\n", "
" ], "text/plain": [ " Node VRF Local_AS Local_Interface Local_IP Remote_AS \\\n", "0 as1border1 default 1 None None 666 \n", "2 as1border1 default 1 None None 555 \n", "4 as1border2 default 1 None 10.14.22.1 4 \n", "13 as2border2 default 2 None None 2 \n", "\n", " Remote_Node Remote_Interface Remote_IP Address_Families Session_Type \\\n", "0 None None 3.2.2.2 [] EBGP_SINGLEHOP \n", "2 None None 5.6.7.8 [] EBGP_SINGLEHOP \n", "4 None None 10.14.22.4 [] EBGP_SINGLEHOP \n", "13 None None 2.1.2.2 [] IBGP \n", "\n", " Configured_Status \n", "0 NO_LOCAL_IP \n", "2 NO_LOCAL_IP \n", "4 UNKNOWN_REMOTE \n", "13 LOCAL_IP_UNKNOWN_STATICALLY " ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Find BGP sessions that are not compatibly configured - i.e Batfish does not identify them as being a UNIQUE_MATCH or a DYNAMIC_MATCH\n", "bgpSessCompat = bf.q.bgpSessionCompatibility().answer().frame()\n", "bgpSessCompat[~bgpSessCompat['Configured_Status'].isin([\"UNIQUE_MATCH\", \"DYNAMIC_MATCH\"])]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We see 4 entries in this table even though we only saw 3 BGP session with thes status NOT_COMPATIBLE. This means that despite not having a `Configured_Status` of `UNIQUE_MATCH` or `DYNAMIC_MATCH` one of these sessions was indeed established. This typically occurs if the mis-configuration is such that the session can ONLY be established when initiated from one side, but not the other. The likely candidate in this output is the `as2border2` session to `2.1.2.2` that has status of `LOCAL_IP_UNKNOWN_STATICALLY`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Debug UNKNOWN_REMOTE peer on as1border2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Batfish deems the BGP peer `10.14.22.4` is not compatible because it cannot find a device in the snapshot that has that IP address configured on any interface. That is why the status is `UNKNOWN_REMOTE`.\n", "This will occur in most networks, since you will not have the configurations of the devices for your external peers (ISPs, content partners, etc...). We can easily verify this by checking the output of `bf.q.ipOwners`" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFInterfaceIPMaskActive
\n", "
" ], "text/plain": [ "Empty DataFrame\n", "Columns: [Node, VRF, Interface, IP, Mask, Active]\n", "Index: []" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# check if there is a node in the network that has the `10.14.22.4` on an interface\n", "ipOwn = bf.q.ipOwners().answer().frame()\n", "ipOwn[ipOwn['IP']=='10.14.22.4']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Debug LOCAL_IP_UNKNOWN_STATICALLY on as2border2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, let's dig into the sessions that are `LOCAL_IP_UNKNOWN_STATICALLY`.\n", "\n", "An iBGP session will have the status `LOCAL_IP_UNKNOWN_STATICALLY` if you are missing the `update-source` command.\n", "\n", "So, in this case, it is likely that the issue is that `as2border2` is missing the `update-source` command for the BGP session. This is needed for iBGP sessions to ensure the peers pick the correct IP address to use when trying to establish the TCP session. We can find out the target remote node by looking at the `bf.q.ipOwners` output" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFInterfaceIPMaskActive
22as2core2defaultLoopback02.1.2.232True
\n", "
" ], "text/plain": [ " Node VRF Interface IP Mask Active\n", "22 as2core2 default Loopback0 2.1.2.2 32 True" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "#find the device(s) that own these ip addresses\n", "ipOwn[ipOwn['IP'].isin(['2.1.2.2'])]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So this session is supposed to be between `as2border2` and `as2core2`. Let's see if this session is established." ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFLocal_ASLocal_InterfaceLocal_IPRemote_ASRemote_NodeRemote_InterfaceRemote_IPAddress_FamiliesSession_TypeEstablished_Status
13as2border2default2None2.1.1.22as2core2None2.1.2.2['IPV4_UNICAST']IBGPESTABLISHED
21as2core2default2None2.1.2.22as2border2None2.1.1.2['IPV4_UNICAST']IBGPESTABLISHED
\n", "
" ], "text/plain": [ " Node VRF Local_AS Local_Interface Local_IP Remote_AS \\\n", "13 as2border2 default 2 None 2.1.1.2 2 \n", "21 as2core2 default 2 None 2.1.2.2 2 \n", "\n", " Remote_Node Remote_Interface Remote_IP Address_Families Session_Type \\\n", "13 as2core2 None 2.1.2.2 ['IPV4_UNICAST'] IBGP \n", "21 as2border2 None 2.1.1.2 ['IPV4_UNICAST'] IBGP \n", "\n", " Established_Status \n", "13 ESTABLISHED \n", "21 ESTABLISHED " ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# check if either direction of the session can be established\n", "bgpSessStat[((bgpSessStat['Local_IP']=='2.1.1.2') & (bgpSessStat['Remote_IP']=='2.1.2.2')) | ((bgpSessStat['Local_IP']=='2.1.2.2') & (bgpSessStat['Remote_IP']=='2.1.1.2'))]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This confirms that our theory. We are missing the `update-source` command on `as2border2` which prevents it from initiating the BGP session. But since `as2core2` is properly configured, the session is established. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Debug NO_LOCAL_IP on as1border1" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFLocal_ASLocal_InterfaceLocal_IPRemote_ASRemote_NodeRemote_InterfaceRemote_IPAddress_FamiliesSession_TypeConfigured_Status
0as1border1default1NoneNone666NoneNone3.2.2.2[]EBGP_SINGLEHOPNO_LOCAL_IP
2as1border1default1NoneNone555NoneNone5.6.7.8[]EBGP_SINGLEHOPNO_LOCAL_IP
\n", "
" ], "text/plain": [ " Node VRF Local_AS Local_Interface Local_IP Remote_AS \\\n", "0 as1border1 default 1 None None 666 \n", "2 as1border1 default 1 None None 555 \n", "\n", " Remote_Node Remote_Interface Remote_IP Address_Families Session_Type \\\n", "0 None None 3.2.2.2 [] EBGP_SINGLEHOP \n", "2 None None 5.6.7.8 [] EBGP_SINGLEHOP \n", "\n", " Configured_Status \n", "0 NO_LOCAL_IP \n", "2 NO_LOCAL_IP " ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Identify the BGP sessions on as1border1 that are not compatible with Configured_Status of NO_LOCAL_IP\n", "bgpSessCompat[(bgpSessCompat['Configured_Status'] == 'NO_LOCAL_IP') & (bgpSessCompat['Node']=='as1border1')]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For an `EBGP_SINGLEHOP` session to have `CONFIGURED_STATUS` of `NO_LOCAL_IP` this typically means that no interface exists on the box which is in the same subnet as the BGP peer's IP address. This can easily be checked by looking at the IP addresses configued on `as1border1`." ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFInterfaceIPMaskActive
7as1border1defaultGigabitEthernet1/010.12.11.124True
14as1border1defaultGigabitEthernet0/01.0.1.124True
19as1border1defaultLoopback01.1.1.132True
\n", "
" ], "text/plain": [ " Node VRF Interface IP Mask Active\n", "7 as1border1 default GigabitEthernet1/0 10.12.11.1 24 True\n", "14 as1border1 default GigabitEthernet0/0 1.0.1.1 24 True\n", "19 as1border1 default Loopback0 1.1.1.1 32 True" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Identify the IP addresses that are owned by as1border1\n", "ipOwn[ipOwn['Node']=='as1border1']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see there are no interfaces with addresses that would be in the same subnet as `3.2.2.2` and `5.6.7.8`. There could be many explanations for this:\n", "\n", "1) BGP peers were configured before the physical connection to neighboring routers was up.\n", "\n", "2) The user simply configured the wrong BGP peer address.\n", "\n", "3) The interfaces used to exist but were decommissioned, but the BGP config was not cleaned up at the same time.\n", "\n", "4) The session is meant to be be an eBGP multi-hop session, but the user didn't add the `ebgp multihop` configuration option and specify an `update-source`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Batfish determines the Local_IP for each BGP session, either based on explicit configuration with `updates-source foo` (or equivalent non-IOS command) for iBGP sessions or eBGP multi-hop sessions,\n", "or by determining the interface the router will use to send packets towards the BGP peer. The latter method requires the route to the peer to be known.\n", "\n", "So, if there is no route to the configured peer, or the configured peer does not exist in the snapshot, you will see this status. We can check the output of `bf.q.routes` and `bf.q.ipOwners` questions to dig into this" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFInterfaceIPMaskActive
10as3border2defaultLoopback03.2.2.232True
\n", "
" ], "text/plain": [ " Node VRF Interface IP Mask Active\n", "10 as3border2 default Loopback0 3.2.2.2 32 True" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Find owner of IP addresses for the incompatible BGP sessions on as1border`\n", "bad_bgp_peer = ['3.2.2.2', '5.6.7.8']\n", "ipOwn[ipOwn['IP'].isin(bad_bgp_peer)]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So we can see that `5.6.7.8` does not exist in the network. This either means there is a mis-configuration, or the device is just not expected to be in the snapshot." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now let's dig into the peer `3.2.2.2`, which we know is the Loopback interface on `as3border2`" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFNetworkNext_HopNext_Hop_IPNext_Hop_InterfaceProtocolMetricAdmin_DistanceTag
\n", "
" ], "text/plain": [ "Empty DataFrame\n", "Columns: [Node, VRF, Network, Next_Hop, Next_Hop_IP, Next_Hop_Interface, Protocol, Metric, Admin_Distance, Tag]\n", "Index: []" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# retrieve the routing table entry for as3border2 loopback0 - 3.2.2.2/32 on as1border1\n", "routes = bf.q.routes(network='3.2.2.2/32').answer().frame()\n", "routes[routes['Node']=='as1border1']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The specific /32 is not present on `as1border1`. What about other routers?" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFNetworkNext_HopNext_Hop_IPNext_Hop_InterfaceProtocolMetricAdmin_DistanceTag
0as3border1default3.2.2.2/32interface GigabitEthernet0/0 ip 3.0.1.23.0.1.2GigabitEthernet0/0ospf3110None
1as3border2default3.2.2.2/32interface Loopback0AUTO/NONE(-1l)Loopback0connected00None
2as3core1default3.2.2.2/32interface GigabitEthernet0/0 ip 3.0.2.13.0.2.1GigabitEthernet0/0ospf2110None
\n", "
" ], "text/plain": [ " Node VRF Network Next_Hop \\\n", "0 as3border1 default 3.2.2.2/32 interface GigabitEthernet0/0 ip 3.0.1.2 \n", "1 as3border2 default 3.2.2.2/32 interface Loopback0 \n", "2 as3core1 default 3.2.2.2/32 interface GigabitEthernet0/0 ip 3.0.2.1 \n", "\n", " Next_Hop_IP Next_Hop_Interface Protocol Metric Admin_Distance Tag \n", "0 3.0.1.2 GigabitEthernet0/0 ospf 3 110 None \n", "1 AUTO/NONE(-1l) Loopback0 connected 0 0 None \n", "2 3.0.2.1 GigabitEthernet0/0 ospf 2 110 None " ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# retrieve the routing table entry for as3border2 loopback0 - 3.2.2.2/32 on ALL routers in the network\n", "routes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see that this route does not leave `as3`, which is why `as1border1` is unable to established the configured BGP session.\n", "This session should have been configured as an eBGP multi-hop session with static routes pointing to the appropriate interface and next-hop" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Debugging BGP sessions that are NOT_ESTABLISHED" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We have root-caused the `NOT_COMPATIBLE` sessions, now let's dig into the ones that are `NOT_ESTABLISHED`." ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFLocal_ASLocal_InterfaceLocal_IPRemote_ASRemote_NodeRemote_InterfaceRemote_IPAddress_FamiliesSession_TypeEstablished_Status
9as2border1default2None2.1.1.12as2core1None2.1.2.1['IPV4_UNICAST']IBGPNOT_ESTABLISHED
12as2border2default2None2.1.1.22as2core1None2.1.2.1['IPV4_UNICAST']IBGPNOT_ESTABLISHED
15as2core1default2None2.1.2.12as2border1None2.1.1.1['IPV4_UNICAST']IBGPNOT_ESTABLISHED
16as2core1default2None2.1.2.12as2dist1None2.1.3.1['IPV4_UNICAST']IBGPNOT_ESTABLISHED
17as2core1default2None2.1.2.12as2border2None2.1.1.2['IPV4_UNICAST']IBGPNOT_ESTABLISHED
18as2core1default2None2.1.2.12as2dist2None2.1.3.2['IPV4_UNICAST']IBGPNOT_ESTABLISHED
25as2dist1default2None2.1.3.12as2core1None2.1.2.1['IPV4_UNICAST']IBGPNOT_ESTABLISHED
28as2dist2default2None2.1.3.22as2core1None2.1.2.1['IPV4_UNICAST']IBGPNOT_ESTABLISHED
\n", "
" ], "text/plain": [ " Node VRF Local_AS Local_Interface Local_IP Remote_AS \\\n", "9 as2border1 default 2 None 2.1.1.1 2 \n", "12 as2border2 default 2 None 2.1.1.2 2 \n", "15 as2core1 default 2 None 2.1.2.1 2 \n", "16 as2core1 default 2 None 2.1.2.1 2 \n", "17 as2core1 default 2 None 2.1.2.1 2 \n", "18 as2core1 default 2 None 2.1.2.1 2 \n", "25 as2dist1 default 2 None 2.1.3.1 2 \n", "28 as2dist2 default 2 None 2.1.3.2 2 \n", "\n", " Remote_Node Remote_Interface Remote_IP Address_Families Session_Type \\\n", "9 as2core1 None 2.1.2.1 ['IPV4_UNICAST'] IBGP \n", "12 as2core1 None 2.1.2.1 ['IPV4_UNICAST'] IBGP \n", "15 as2border1 None 2.1.1.1 ['IPV4_UNICAST'] IBGP \n", "16 as2dist1 None 2.1.3.1 ['IPV4_UNICAST'] IBGP \n", "17 as2border2 None 2.1.1.2 ['IPV4_UNICAST'] IBGP \n", "18 as2dist2 None 2.1.3.2 ['IPV4_UNICAST'] IBGP \n", "25 as2core1 None 2.1.2.1 ['IPV4_UNICAST'] IBGP \n", "28 as2core1 None 2.1.2.1 ['IPV4_UNICAST'] IBGP \n", "\n", " Established_Status \n", "9 NOT_ESTABLISHED \n", "12 NOT_ESTABLISHED \n", "15 NOT_ESTABLISHED \n", "16 NOT_ESTABLISHED \n", "17 NOT_ESTABLISHED \n", "18 NOT_ESTABLISHED \n", "25 NOT_ESTABLISHED \n", "28 NOT_ESTABLISHED " ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Find all BGP sessions in the network that were compatible but NOT_ESTABLISHED\n", "bgpSessStat[bgpSessStat['Established_Status'] == 'NOT_ESTABLISHED']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The reasons for a session that is compatiable (`UNIQUE_MATCH` or `DYNAMIC_MATCH`) to not get established would be 1) missing routes or 2) some ACL in the path blocking traffic. Let's check the routing tables on `as2core1`" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFNetworkNext_HopNext_Hop_IPNext_Hop_InterfaceProtocolMetricAdmin_DistanceTag
0as2core1default2.1.1.1/32interface GigabitEthernet0/0 ip 2.12.11.12.12.11.1GigabitEthernet0/0ospf2110None
\n", "
" ], "text/plain": [ " Node VRF Network Next_Hop \\\n", "0 as2core1 default 2.1.1.1/32 interface GigabitEthernet0/0 ip 2.12.11.1 \n", "\n", " Next_Hop_IP Next_Hop_Interface Protocol Metric Admin_Distance Tag \n", "0 2.12.11.1 GigabitEthernet0/0 ospf 2 110 None " ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# retrieve routing table for as2core1 and check the route to the BGP peer 2.1.1.1 - as2border1\n", "routes = bf.q.routes(nodes='as2core1').answer().frame()\n", "routes[routes['Network']=='2.1.1.1/32']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see `as2core1` has a route to the configured neighbor `2.1.1.1`. What about the other routers?" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFNetworkNext_HopNext_Hop_IPNext_Hop_InterfaceProtocolMetricAdmin_DistanceTag
\n", "
" ], "text/plain": [ "Empty DataFrame\n", "Columns: [Node, VRF, Network, Next_Hop, Next_Hop_IP, Next_Hop_Interface, Protocol, Metric, Admin_Distance, Tag]\n", "Index: []" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# retrieve routing table for as2border1 to check if it has a route to as2core1 loopback0 - 2.1.2.1/32\n", "routes=bf.q.routes(nodes='as2border1').answer().frame()\n", "routes[routes['Network']=='2.1.2.1/32']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`as2border1` does not have a route to the loopback address `2.1.2.1` of `as2core1`. Let's also check `as2border2`." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
NodeVRFNetworkNext_HopNext_Hop_IPNext_Hop_InterfaceProtocolMetricAdmin_DistanceTag
\n", "
" ], "text/plain": [ "Empty DataFrame\n", "Columns: [Node, VRF, Network, Next_Hop, Next_Hop_IP, Next_Hop_Interface, Protocol, Metric, Admin_Distance, Tag]\n", "Index: []" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# retrieve routing table for as2border2 to check if it has a route to as2core1 loopback0 - 2.1.2.1/32\n", "routes=bf.q.routes(nodes='as2border2').answer().frame()\n", "routes[routes['Network']=='2.1.2.1/32']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Neither border router has a route to the loopback of `as2core1`. Since the loopback is supposed to be distributed via OSPF, next step is to look at the OSPF configuration on `as2core1`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From the snippet of the configuration of `as2core1`, we can see that the Loopback address isn't part of the OSPF process:\n", "```\n", "interface Loopback0\n", " ip address 2.1.2.1 255.255.255.255\n", "\n", "router ospf 1\n", " router-id 2.1.2.1\n", " !network 2.0.0.0 0.255.255.255 area 1\n", " network 2.12.0.0 0.0.255.255 area 1\n", " network 2.23.0.0 0.0.255.255 area 1\n", " ```\n", " \n", " This explains why the BGP session wasn't established.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "With that we have root-caused all of the BGP sessions that were `NOT_ESTABLISHED` or `NOT_COMPATIBLE`. " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Summary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "Batfish allows you to easily retrieve information about BGP configuration of all devices and peers, as well as status of each peer. With Batfish you can ensure that no change is pushed to the network that would cause a BGP session to not come up." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We hope you found this notebook useful and informative. Future notebooks will dive into more advanced topics like validating routing policy. Stay tuned! " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Want to learn more? Come find us on [Slack](https://join.slack.com/t/batfish-org/shared_invite/enQtMzA0Nzg2OTAzNzQ1LTUxOTJlY2YyNTVlNGQ3MTJkOTIwZT U2YjY3YzRjZWFiYzE4ODE5ODZiNjA4NGI5NTJhZmU2ZTllOTMwZDhjMzA) and [GitHub](https://github.com/batfish/batfish)" ] } ], "metadata": { "hide_input": false, "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.17" } }, "nbformat": 4, "nbformat_minor": 2 }