Introduction to Route Computation and Analysis using Batfish

Network engineers routinely need to validate routing and forwarding in the network. They often do that by connecting to multiple network devices and executing a series of show route commands. This distributed debugging is highly complex even in a moderately-sized network. Batfish makes this task extremely simple by providing an easy-to-query, centralized view of routing tables in the network.

In this notebook, we will look at how you can extract routing information from Batfish.

Check out a video demo of this notebook here.

Analytics

[1]:
# Import packages and load questions
%run startup.py
load_questions()

Initializing the Network and Snapshot

SNAPSHOT_PATH below can be updated to point to a custom snapshot directory, see the Batfish instructions for how to package data for analysis. More example networks are available in the networks folder of the Batfish repository.

[2]:
# Initialize a network and snapshot
NETWORK_NAME = "example_network"
SNAPSHOT_NAME = "example_snapshot"

SNAPSHOT_PATH = "networks/example"

bf_set_network(NETWORK_NAME)
bf_init_snapshot(SNAPSHOT_PATH, name=SNAPSHOT_NAME, overwrite=True)
[2]:
'example_snapshot'

The network snapshot that we initialized above is illustrated below. You can download/view devices’ configuration files here.

example-network

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.

View Routing Tables for ALL devices and ALL VRFs

Batfish makes all routing tables in the network easily accessible. Let’s take a look at how you can retrieve the specific information you want.

[3]:
# Get routing tables for all nodes and VRFs
routes_all = bfq.routes().answer().frame()

We are not going to print this table as it has a large number of entries.

View Routing Tables for default VRF on AS1 border routers

There are 2 ways that we can get the desired subset of data:

Option 1) Only request that information from Batfish by passing in parameters into the routes() question. This is useful to do when you need to reduce the amount of data being returned, but is limited to regex filtering based on VRF, Node, Protocol and Network.

Option 2) Filter the output of the routes() question using the Pandas APIs.

[4]:
?bfq.routes
[5]:
# Get the routing table for the 'default' VRF on border routers of as1
# using BF parameters
routes_as1border = bfq.routes(nodes="/as1border/", vrfs="default").answer().frame()
[6]:
# Get the routing table for the 'default' VRF on border routers of as1
# using Pandas filtering
routes_as1border = routes_all[(routes_all['Node'].str.contains('as1border')) & (routes_all['VRF'] == 'default')]
routes_as1border
[6]:
Node VRF Network Next_Hop Next_Hop_IP Next_Hop_Interface Protocol Metric Admin_Distance Tag
0 as1border1 default 1.0.1.1/32 None AUTO/NONE(-1l) GigabitEthernet0/0 local 0 0 None
4 as1border2 default 3.0.1.0/24 as3border2 10.13.22.3 dynamic bgp 50 20 None
30 as1border1 default 1.0.2.0/24 as1core1 1.0.1.2 dynamic ospf 2 110 None
38 as1border2 default 10.14.22.1/32 None AUTO/NONE(-1l) GigabitEthernet2/0 local 0 0 None
55 as1border2 default 10.13.22.0/24 None AUTO/NONE(-1l) GigabitEthernet0/0 connected 0 0 None
63 as1border2 default 1.10.1.1/32 as1core1 1.0.2.2 dynamic ospf 2 110 None
64 as1border2 default 1.0.2.0/24 None AUTO/NONE(-1l) GigabitEthernet1/0 connected 0 0 None
69 as1border1 default 10.12.11.1/32 None AUTO/NONE(-1l) GigabitEthernet1/0 local 0 0 None
94 as1border1 default 1.0.1.0/24 None AUTO/NONE(-1l) GigabitEthernet0/0 connected 0 0 None
98 as1border1 default 10.14.22.0/24 as1core1 1.0.1.2 dynamic ospfE2 20 110 None
99 as1border2 default 1.2.2.2/32 None AUTO/NONE(-1l) Loopback0 connected 0 0 None
116 as1border2 default 2.128.0.0/16 as2border1 10.12.11.2 dynamic ibgp 50 200 None
157 as1border2 default 10.13.22.1/32 None AUTO/NONE(-1l) GigabitEthernet0/0 local 0 0 None
172 as1border1 default 3.0.1.0/24 as3border2 10.13.22.3 dynamic ibgp 50 200 None
187 as1border2 default 1.0.1.0/24 as1core1 1.0.2.2 dynamic ospf 2 110 None
190 as1border1 default 3.0.2.0/24 as3border2 10.13.22.3 dynamic ibgp 50 200 None
191 as1border2 default 1.0.2.1/32 None AUTO/NONE(-1l) GigabitEthernet1/0 local 0 0 None
199 as1border1 default 1.2.2.2/32 as1core1 1.0.1.2 dynamic ospf 3 110 None
224 as1border1 default 10.13.22.0/24 as1core1 1.0.1.2 dynamic ospfE2 20 110 None
241 as1border2 default 3.0.2.0/24 as3border2 10.13.22.3 dynamic bgp 50 20 None
257 as1border1 default 2.128.0.0/16 as2border1 10.12.11.2 dynamic bgp 50 20 None
258 as1border2 default 10.14.22.0/24 None AUTO/NONE(-1l) GigabitEthernet2/0 connected 0 0 None
265 as1border1 default 10.12.11.0/24 None AUTO/NONE(-1l) GigabitEthernet1/0 connected 0 0 None
266 as1border2 default 10.12.11.0/24 as1core1 1.0.2.2 dynamic ospfE2 20 110 None
288 as1border1 default 1.10.1.1/32 as1core1 1.0.1.2 dynamic ospf 2 110 None
314 as1border1 default 1.1.1.1/32 None AUTO/NONE(-1l) Loopback0 connected 0 0 None
326 as1border2 default 1.1.1.1/32 as1core1 1.0.2.2 dynamic ospf 3 110 None

View BGP learnt routes for default VRF on AS1 border routers

[7]:
# Getting BGP routes in the routing table for the 'default' VRF on border routers of as1
# using BF parameters
routes_as1border_bgp = bfq.routes(nodes="/as1border/", vrfs="default", protocols="bgp").answer().frame()
[8]:
# Geting BGP routes in the routing table for the 'default' VRF on border routers of as1
# using Pandas filtering
routes_as1border_bgp = routes_all[(routes_all['Node'].str.contains('as1border')) & (routes_all['VRF'] == 'default') & (routes_all['Protocol'] == 'bgp')]
routes_as1border_bgp
[8]:
Node VRF Network Next_Hop Next_Hop_IP Next_Hop_Interface Protocol Metric Admin_Distance Tag
4 as1border2 default 3.0.1.0/24 as3border2 10.13.22.3 dynamic bgp 50 20 None
241 as1border2 default 3.0.2.0/24 as3border2 10.13.22.3 dynamic bgp 50 20 None
257 as1border1 default 2.128.0.0/16 as2border1 10.12.11.2 dynamic bgp 50 20 None

View BGP learnt routes for ALL VRFs on ALL routers with Metric >=50

We cannot pass in metric as a parameter to Batfish, so this task is best handled with the Pandas API.

[9]:
routes_filtered = routes_all[(routes_all['Protocol'] == 'bgp') & (routes_all['Metric'] >= 50)]
routes_filtered
[9]:
Node VRF Network Next_Hop Next_Hop_IP Next_Hop_Interface Protocol Metric Admin_Distance Tag
4 as1border2 default 3.0.1.0/24 as3border2 10.13.22.3 dynamic bgp 50 20 None
10 as2dept1 default 1.0.1.0/24 as2dist2 2.34.201.3 dynamic bgp 50 20 None
21 as2border2 default 3.0.1.0/24 as3border1 10.23.21.3 dynamic bgp 50 20 None
47 as3border1 default 2.128.0.0/16 as2border2 10.23.21.2 dynamic bgp 50 20 None
53 as2dist2 default 2.128.0.0/24 as2dept1 2.34.201.4 dynamic bgp 50 20 None
66 as2dept1 default 3.0.1.0/24 as2dist2 2.34.201.3 dynamic bgp 50 20 None
73 as2dist1 default 2.128.1.0/24 as2dept1 2.34.101.4 dynamic bgp 50 20 None
91 as2dept1 default 1.0.2.0/24 as2dist1 2.34.101.3 dynamic bgp 50 20 None
101 as2border1 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic bgp 50 20 None
106 as2border1 default 1.0.1.0/24 as1border1 10.12.11.1 dynamic bgp 50 20 None
118 as2dept1 default 3.0.2.0/24 as2dist1 2.34.101.3 dynamic bgp 50 20 None
125 as2dept1 default 3.0.1.0/24 as2dist1 2.34.101.3 dynamic bgp 50 20 None
147 as2dept1 default 1.0.2.0/24 as2dist2 2.34.201.3 dynamic bgp 50 20 None
150 as3border2 default 1.0.1.0/24 as1border2 10.13.22.1 dynamic bgp 50 20 None
173 as2dist2 default 2.128.1.0/24 as2dept1 2.34.201.4 dynamic bgp 50 20 None
208 as3border2 default 1.0.2.0/24 as1border2 10.13.22.1 dynamic bgp 50 20 None
241 as1border2 default 3.0.2.0/24 as3border2 10.13.22.3 dynamic bgp 50 20 None
257 as1border1 default 2.128.0.0/16 as2border1 10.12.11.2 dynamic bgp 50 20 None
264 as2dist1 default 2.128.0.0/24 as2dept1 2.34.101.4 dynamic bgp 50 20 None
281 as2dept1 default 1.0.1.0/24 as2dist1 2.34.101.3 dynamic bgp 50 20 None
295 as2dept1 default 3.0.2.0/24 as2dist2 2.34.201.3 dynamic bgp 50 20 None
322 as2border2 default 3.0.2.0/24 as3border1 10.23.21.3 dynamic bgp 50 20 None

View the routing entries for network 1.0.2.0/24 on ALL routers in ALL VRFs

[10]:
# grab the route table entry for network 1.0.2.0/24 from all routers in all VRFs
# using BF parameters
routes_filtered = bfq.routes(network="1.0.2.0/24").answer().frame()
[11]:
# grab the route table entry for network 1.0.2.0/24 from all routers in all VRFs
# using Pandas filtering
routes_filtered = routes_all[routes_all['Network'] == "1.0.2.0/24"]
routes_filtered
[11]:
Node VRF Network Next_Hop Next_Hop_IP Next_Hop_Interface Protocol Metric Admin_Distance Tag
30 as1border1 default 1.0.2.0/24 as1core1 1.0.1.2 dynamic ospf 2 110 None
64 as1border2 default 1.0.2.0/24 None AUTO/NONE(-1l) GigabitEthernet1/0 connected 0 0 None
81 as1core1 default 1.0.2.0/24 None AUTO/NONE(-1l) GigabitEthernet0/0 connected 0 0 None
91 as2dept1 default 1.0.2.0/24 as2dist1 2.34.101.3 dynamic bgp 50 20 None
101 as2border1 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic bgp 50 20 None
111 as3border1 default 1.0.2.0/24 as1border2 10.13.22.1 dynamic ibgp 50 200 None
120 as2border2 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
121 as2border2 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
146 as3core1 default 1.0.2.0/24 as1border2 10.13.22.1 dynamic ibgp 50 200 None
147 as2dept1 default 1.0.2.0/24 as2dist2 2.34.201.3 dynamic bgp 50 20 None
208 as3border2 default 1.0.2.0/24 as1border2 10.13.22.1 dynamic bgp 50 20 None
237 as2dist2 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
238 as2dist2 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
239 as2dist1 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
240 as2dist1 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
330 as2core2 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
334 as2core1 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None

Using Panda’s filtering it is easy to retrieve the list of nodes which have the network in the routing table for at least 1 VRF. This type of processing should always be done using the Pandas APIs.

[12]:
# Get the list of nodes that have the network 1.0.2.0/24 in at least 1 VRF
# the .unique function removes duplicate entries that would have been returned if the network was in multiple VRFs on a node or there were
# multiple route entries for the network (ECMP)
print(sorted(routes_filtered["Node"].unique()))
['as1border1', 'as1border2', 'as1core1', 'as2border1', 'as2border2', 'as2core1', 'as2core2', 'as2dept1', 'as2dist1', 'as2dist2', 'as3border1', 'as3border2', 'as3core1']

Now we will retrieve the list of nodes that do NOT have this prefix in their routing table. This is easy to do with the Pandas groupby and filter functions.

[13]:
# Group all routes by Node and filter for those that don't have '1.0.2.0/24'
routes_filtered = routes_all.groupby('Node').filter(lambda x: all(x['Network'] != '1.0.2.0/24'))

# Get the unique node names and sort the list
print(sorted(routes_filtered["Node"].unique()))
['host1', 'host2']

The only devices that do not have a route to 1.0.2.0/24 are the 2 hosts in the snapshot. This is expected, as they should just have a default route. Let’s verify that.

[14]:
routes_all[routes_all['Node'].str.contains('host')]
[14]:
Node VRF Network Next_Hop Next_Hop_IP Next_Hop_Interface Protocol Metric Admin_Distance Tag
28 host2 default 2.128.1.0/24 None AUTO/NONE(-1l) eth0 connected 0 0 None
57 host1 default 2.128.0.101/32 None AUTO/NONE(-1l) eth0 local 0 0 None
141 host2 default 2.128.1.101/32 None AUTO/NONE(-1l) eth0 local 0 0 None
178 host1 default 2.128.0.0/24 None AUTO/NONE(-1l) eth0 connected 0 0 None
186 host2 default 0.0.0.0/0 as2dept1 2.128.1.1 eth0 static 0 1 None
316 host1 default 0.0.0.0/0 as2dept1 2.128.0.1 eth0 static 0 1 None

With Batfish and Pandas you can easily retrieve the exact information you are looking for from the routing tables on ANY or ALL network devices for ANY or ALL VRFs.

This concludes the notebook. To recap, in this notebook we covered the foundational tasks for route analysis:

  1. How to get routes at all nodes in the network or only at a subset of them

  2. How to retrieve routing entries that match a specific protocol or metric

  3. How to find which nodes have an entry for a prefix or which ones do not

We hope you found this notebook useful and informative. Future notebooks will dive into more advanced topics like path analysis, debugging ACLs and firewall rules, validating routing policy, etc.. so stay tuned!

Want to know more?

Reach out to us through Slack or GitHub to learn more, or send feedback.