{
"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",
""
]
},
{
"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",
" Node | \n",
" VRF | \n",
" Router_ID | \n",
" Confederation_ID | \n",
" Confederation_Members | \n",
" Multipath_EBGP | \n",
" Multipath_IBGP | \n",
" Multipath_Match_Mode | \n",
" Neighbors | \n",
" Route_Reflector | \n",
" Tie_Breaker | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" as1border1 | \n",
" default | \n",
" 1.1.1.1 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['3.2.2.2', '1.10.1.1', '5.6.7.8', '10.12.11.2'] | \n",
" False | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
" | 1 | \n",
" as1border2 | \n",
" default | \n",
" 1.2.2.2 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['10.14.22.4', '1.10.1.1', '10.13.22.3'] | \n",
" False | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
" | 2 | \n",
" as1core1 | \n",
" default | \n",
" 1.10.1.1 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['1.1.1.1', '1.2.2.2'] | \n",
" True | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
" | 3 | \n",
" as2border1 | \n",
" default | \n",
" 2.1.1.1 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['2.1.2.1', '2.1.2.2', '10.12.11.1'] | \n",
" False | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
" | 4 | \n",
" as2border2 | \n",
" default | \n",
" 2.1.1.2 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['2.1.2.1', '2.1.2.2', '10.23.21.3'] | \n",
" False | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
" | 5 | \n",
" as2core1 | \n",
" default | \n",
" 2.1.2.1 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['2.1.1.1', '2.1.3.1', '2.1.1.2', '2.1.3.2'] | \n",
" True | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
" | 6 | \n",
" as2core2 | \n",
" default | \n",
" 2.1.2.2 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['2.1.1.1', '2.1.3.1', '2.1.1.2', '2.1.3.2'] | \n",
" True | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
" | 7 | \n",
" as2dept1 | \n",
" default | \n",
" 2.1.4.1 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['2.34.101.3', '2.34.201.3'] | \n",
" False | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
" | 8 | \n",
" as2dist1 | \n",
" default | \n",
" 2.1.3.1 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['2.1.2.1', '2.1.2.2', '2.34.0.0/16'] | \n",
" False | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
" | 9 | \n",
" as2dist2 | \n",
" default | \n",
" 2.1.3.2 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['2.1.2.1', '2.1.2.2', '2.34.0.0/16'] | \n",
" False | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
" | 10 | \n",
" as3border1 | \n",
" default | \n",
" 3.1.1.1 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['10.23.21.2', '3.10.1.1'] | \n",
" False | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
" | 11 | \n",
" as3border2 | \n",
" default | \n",
" 3.2.2.2 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['3.10.1.1', '10.13.22.1'] | \n",
" False | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
" | 12 | \n",
" as3core1 | \n",
" default | \n",
" 3.10.1.1 | \n",
" None | \n",
" None | \n",
" True | \n",
" True | \n",
" EXACT_PATH | \n",
" ['3.1.1.1', '3.2.2.2'] | \n",
" True | \n",
" ARRIVAL_ORDER | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Local_AS | \n",
" Local_IP | \n",
" Local_Interface | \n",
" Confederation | \n",
" Remote_AS | \n",
" Remote_IP | \n",
" Description | \n",
" Route_Reflector_Client | \n",
" Cluster_ID | \n",
" Peer_Group | \n",
" Import_Policy | \n",
" Export_Policy | \n",
" Send_Community | \n",
" Is_Passive | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" as2dept1 | \n",
" default | \n",
" 65001 | \n",
" 2.34.101.4 | \n",
" None | \n",
" None | \n",
" 2 | \n",
" 2.34.101.3 | \n",
" None | \n",
" False | \n",
" None | \n",
" as2 | \n",
" ['as2_to_dept'] | \n",
" ['dept_to_as2'] | \n",
" True | \n",
" False | \n",
"
\n",
" \n",
" | 1 | \n",
" as2dept1 | \n",
" default | \n",
" 65001 | \n",
" 2.34.201.4 | \n",
" None | \n",
" None | \n",
" 2 | \n",
" 2.34.201.3 | \n",
" None | \n",
" False | \n",
" None | \n",
" as2 | \n",
" ['as2_to_dept'] | \n",
" ['dept_to_as2'] | \n",
" True | \n",
" False | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Local_AS | \n",
" Local_Interface | \n",
" Local_IP | \n",
" Remote_AS | \n",
" Remote_Node | \n",
" Remote_Interface | \n",
" Remote_IP | \n",
" Address_Families | \n",
" Session_Type | \n",
" Configured_Status | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" as2dept1 | \n",
" default | \n",
" 65001 | \n",
" None | \n",
" 2.34.101.4 | \n",
" 2 | \n",
" as2dist1 | \n",
" None | \n",
" 2.34.101.3 | \n",
" ['IPV4_UNICAST'] | \n",
" EBGP_SINGLEHOP | \n",
" UNIQUE_MATCH | \n",
"
\n",
" \n",
" | 1 | \n",
" as2dept1 | \n",
" default | \n",
" 65001 | \n",
" None | \n",
" 2.34.201.4 | \n",
" 2 | \n",
" as2dist2 | \n",
" None | \n",
" 2.34.201.3 | \n",
" ['IPV4_UNICAST'] | \n",
" EBGP_SINGLEHOP | \n",
" UNIQUE_MATCH | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Local_AS | \n",
" Local_Interface | \n",
" Local_IP | \n",
" Remote_AS | \n",
" Remote_Node | \n",
" Remote_Interface | \n",
" Remote_IP | \n",
" Address_Families | \n",
" Session_Type | \n",
" Established_Status | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" as2dept1 | \n",
" default | \n",
" 65001 | \n",
" None | \n",
" 2.34.101.4 | \n",
" 2 | \n",
" as2dist1 | \n",
" None | \n",
" 2.34.101.3 | \n",
" ['IPV4_UNICAST'] | \n",
" EBGP_SINGLEHOP | \n",
" ESTABLISHED | \n",
"
\n",
" \n",
" | 1 | \n",
" as2dept1 | \n",
" default | \n",
" 65001 | \n",
" None | \n",
" 2.34.201.4 | \n",
" 2 | \n",
" as2dist2 | \n",
" None | \n",
" 2.34.201.3 | \n",
" ['IPV4_UNICAST'] | \n",
" EBGP_SINGLEHOP | \n",
" ESTABLISHED | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Local_AS | \n",
" Local_Interface | \n",
" Local_IP | \n",
" Remote_AS | \n",
" Remote_Node | \n",
" Remote_Interface | \n",
" Remote_IP | \n",
" Address_Families | \n",
" Session_Type | \n",
" Established_Status | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" as1border1 | \n",
" default | \n",
" 1 | \n",
" None | \n",
" None | \n",
" 666 | \n",
" None | \n",
" None | \n",
" 3.2.2.2 | \n",
" [] | \n",
" EBGP_SINGLEHOP | \n",
" NOT_COMPATIBLE | \n",
"
\n",
" \n",
" | 2 | \n",
" as1border1 | \n",
" default | \n",
" 1 | \n",
" None | \n",
" None | \n",
" 555 | \n",
" None | \n",
" None | \n",
" 5.6.7.8 | \n",
" [] | \n",
" EBGP_SINGLEHOP | \n",
" NOT_COMPATIBLE | \n",
"
\n",
" \n",
" | 4 | \n",
" as1border2 | \n",
" default | \n",
" 1 | \n",
" None | \n",
" 10.14.22.1 | \n",
" 4 | \n",
" None | \n",
" None | \n",
" 10.14.22.4 | \n",
" [] | \n",
" EBGP_SINGLEHOP | \n",
" NOT_COMPATIBLE | \n",
"
\n",
" \n",
" | 9 | \n",
" as2border1 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.1.1 | \n",
" 2 | \n",
" as2core1 | \n",
" None | \n",
" 2.1.2.1 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 12 | \n",
" as2border2 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.1.2 | \n",
" 2 | \n",
" as2core1 | \n",
" None | \n",
" 2.1.2.1 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 15 | \n",
" as2core1 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.2.1 | \n",
" 2 | \n",
" as2border1 | \n",
" None | \n",
" 2.1.1.1 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 16 | \n",
" as2core1 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.2.1 | \n",
" 2 | \n",
" as2dist1 | \n",
" None | \n",
" 2.1.3.1 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 17 | \n",
" as2core1 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.2.1 | \n",
" 2 | \n",
" as2border2 | \n",
" None | \n",
" 2.1.1.2 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 18 | \n",
" as2core1 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.2.1 | \n",
" 2 | \n",
" as2dist2 | \n",
" None | \n",
" 2.1.3.2 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 25 | \n",
" as2dist1 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.3.1 | \n",
" 2 | \n",
" as2core1 | \n",
" None | \n",
" 2.1.2.1 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 28 | \n",
" as2dist2 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.3.2 | \n",
" 2 | \n",
" as2core1 | \n",
" None | \n",
" 2.1.2.1 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Local_AS | \n",
" Local_Interface | \n",
" Local_IP | \n",
" Remote_AS | \n",
" Remote_Node | \n",
" Remote_Interface | \n",
" Remote_IP | \n",
" Address_Families | \n",
" Session_Type | \n",
" Configured_Status | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" as1border1 | \n",
" default | \n",
" 1 | \n",
" None | \n",
" None | \n",
" 666 | \n",
" None | \n",
" None | \n",
" 3.2.2.2 | \n",
" [] | \n",
" EBGP_SINGLEHOP | \n",
" NO_LOCAL_IP | \n",
"
\n",
" \n",
" | 2 | \n",
" as1border1 | \n",
" default | \n",
" 1 | \n",
" None | \n",
" None | \n",
" 555 | \n",
" None | \n",
" None | \n",
" 5.6.7.8 | \n",
" [] | \n",
" EBGP_SINGLEHOP | \n",
" NO_LOCAL_IP | \n",
"
\n",
" \n",
" | 4 | \n",
" as1border2 | \n",
" default | \n",
" 1 | \n",
" None | \n",
" 10.14.22.1 | \n",
" 4 | \n",
" None | \n",
" None | \n",
" 10.14.22.4 | \n",
" [] | \n",
" EBGP_SINGLEHOP | \n",
" UNKNOWN_REMOTE | \n",
"
\n",
" \n",
" | 13 | \n",
" as2border2 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" None | \n",
" 2 | \n",
" None | \n",
" None | \n",
" 2.1.2.2 | \n",
" [] | \n",
" IBGP | \n",
" LOCAL_IP_UNKNOWN_STATICALLY | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Interface | \n",
" IP | \n",
" Mask | \n",
" Active | \n",
"
\n",
" \n",
" \n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Interface | \n",
" IP | \n",
" Mask | \n",
" Active | \n",
"
\n",
" \n",
" \n",
" \n",
" | 22 | \n",
" as2core2 | \n",
" default | \n",
" Loopback0 | \n",
" 2.1.2.2 | \n",
" 32 | \n",
" True | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Local_AS | \n",
" Local_Interface | \n",
" Local_IP | \n",
" Remote_AS | \n",
" Remote_Node | \n",
" Remote_Interface | \n",
" Remote_IP | \n",
" Address_Families | \n",
" Session_Type | \n",
" Established_Status | \n",
"
\n",
" \n",
" \n",
" \n",
" | 13 | \n",
" as2border2 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.1.2 | \n",
" 2 | \n",
" as2core2 | \n",
" None | \n",
" 2.1.2.2 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" ESTABLISHED | \n",
"
\n",
" \n",
" | 21 | \n",
" as2core2 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.2.2 | \n",
" 2 | \n",
" as2border2 | \n",
" None | \n",
" 2.1.1.2 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" ESTABLISHED | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Local_AS | \n",
" Local_Interface | \n",
" Local_IP | \n",
" Remote_AS | \n",
" Remote_Node | \n",
" Remote_Interface | \n",
" Remote_IP | \n",
" Address_Families | \n",
" Session_Type | \n",
" Configured_Status | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" as1border1 | \n",
" default | \n",
" 1 | \n",
" None | \n",
" None | \n",
" 666 | \n",
" None | \n",
" None | \n",
" 3.2.2.2 | \n",
" [] | \n",
" EBGP_SINGLEHOP | \n",
" NO_LOCAL_IP | \n",
"
\n",
" \n",
" | 2 | \n",
" as1border1 | \n",
" default | \n",
" 1 | \n",
" None | \n",
" None | \n",
" 555 | \n",
" None | \n",
" None | \n",
" 5.6.7.8 | \n",
" [] | \n",
" EBGP_SINGLEHOP | \n",
" NO_LOCAL_IP | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Interface | \n",
" IP | \n",
" Mask | \n",
" Active | \n",
"
\n",
" \n",
" \n",
" \n",
" | 7 | \n",
" as1border1 | \n",
" default | \n",
" GigabitEthernet1/0 | \n",
" 10.12.11.1 | \n",
" 24 | \n",
" True | \n",
"
\n",
" \n",
" | 14 | \n",
" as1border1 | \n",
" default | \n",
" GigabitEthernet0/0 | \n",
" 1.0.1.1 | \n",
" 24 | \n",
" True | \n",
"
\n",
" \n",
" | 19 | \n",
" as1border1 | \n",
" default | \n",
" Loopback0 | \n",
" 1.1.1.1 | \n",
" 32 | \n",
" True | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Interface | \n",
" IP | \n",
" Mask | \n",
" Active | \n",
"
\n",
" \n",
" \n",
" \n",
" | 10 | \n",
" as3border2 | \n",
" default | \n",
" Loopback0 | \n",
" 3.2.2.2 | \n",
" 32 | \n",
" True | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Network | \n",
" Next_Hop | \n",
" Next_Hop_IP | \n",
" Next_Hop_Interface | \n",
" Protocol | \n",
" Metric | \n",
" Admin_Distance | \n",
" Tag | \n",
"
\n",
" \n",
" \n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Network | \n",
" Next_Hop | \n",
" Next_Hop_IP | \n",
" Next_Hop_Interface | \n",
" Protocol | \n",
" Metric | \n",
" Admin_Distance | \n",
" Tag | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" as3border1 | \n",
" default | \n",
" 3.2.2.2/32 | \n",
" interface GigabitEthernet0/0 ip 3.0.1.2 | \n",
" 3.0.1.2 | \n",
" GigabitEthernet0/0 | \n",
" ospf | \n",
" 3 | \n",
" 110 | \n",
" None | \n",
"
\n",
" \n",
" | 1 | \n",
" as3border2 | \n",
" default | \n",
" 3.2.2.2/32 | \n",
" interface Loopback0 | \n",
" AUTO/NONE(-1l) | \n",
" Loopback0 | \n",
" connected | \n",
" 0 | \n",
" 0 | \n",
" None | \n",
"
\n",
" \n",
" | 2 | \n",
" as3core1 | \n",
" default | \n",
" 3.2.2.2/32 | \n",
" interface GigabitEthernet0/0 ip 3.0.2.1 | \n",
" 3.0.2.1 | \n",
" GigabitEthernet0/0 | \n",
" ospf | \n",
" 2 | \n",
" 110 | \n",
" None | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Local_AS | \n",
" Local_Interface | \n",
" Local_IP | \n",
" Remote_AS | \n",
" Remote_Node | \n",
" Remote_Interface | \n",
" Remote_IP | \n",
" Address_Families | \n",
" Session_Type | \n",
" Established_Status | \n",
"
\n",
" \n",
" \n",
" \n",
" | 9 | \n",
" as2border1 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.1.1 | \n",
" 2 | \n",
" as2core1 | \n",
" None | \n",
" 2.1.2.1 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 12 | \n",
" as2border2 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.1.2 | \n",
" 2 | \n",
" as2core1 | \n",
" None | \n",
" 2.1.2.1 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 15 | \n",
" as2core1 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.2.1 | \n",
" 2 | \n",
" as2border1 | \n",
" None | \n",
" 2.1.1.1 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 16 | \n",
" as2core1 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.2.1 | \n",
" 2 | \n",
" as2dist1 | \n",
" None | \n",
" 2.1.3.1 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 17 | \n",
" as2core1 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.2.1 | \n",
" 2 | \n",
" as2border2 | \n",
" None | \n",
" 2.1.1.2 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 18 | \n",
" as2core1 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.2.1 | \n",
" 2 | \n",
" as2dist2 | \n",
" None | \n",
" 2.1.3.2 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 25 | \n",
" as2dist1 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.3.1 | \n",
" 2 | \n",
" as2core1 | \n",
" None | \n",
" 2.1.2.1 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
" | 28 | \n",
" as2dist2 | \n",
" default | \n",
" 2 | \n",
" None | \n",
" 2.1.3.2 | \n",
" 2 | \n",
" as2core1 | \n",
" None | \n",
" 2.1.2.1 | \n",
" ['IPV4_UNICAST'] | \n",
" IBGP | \n",
" NOT_ESTABLISHED | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Network | \n",
" Next_Hop | \n",
" Next_Hop_IP | \n",
" Next_Hop_Interface | \n",
" Protocol | \n",
" Metric | \n",
" Admin_Distance | \n",
" Tag | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" as2core1 | \n",
" default | \n",
" 2.1.1.1/32 | \n",
" interface GigabitEthernet0/0 ip 2.12.11.1 | \n",
" 2.12.11.1 | \n",
" GigabitEthernet0/0 | \n",
" ospf | \n",
" 2 | \n",
" 110 | \n",
" None | \n",
"
\n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Network | \n",
" Next_Hop | \n",
" Next_Hop_IP | \n",
" Next_Hop_Interface | \n",
" Protocol | \n",
" Metric | \n",
" Admin_Distance | \n",
" Tag | \n",
"
\n",
" \n",
" \n",
" \n",
"
\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",
" Node | \n",
" VRF | \n",
" Network | \n",
" Next_Hop | \n",
" Next_Hop_IP | \n",
" Next_Hop_Interface | \n",
" Protocol | \n",
" Metric | \n",
" Admin_Distance | \n",
" Tag | \n",
"
\n",
" \n",
" \n",
" \n",
"
\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
}