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
32 as1border1 default 1.0.2.0/24 as1core1 1.0.1.2 dynamic ospf 2 110 None
40 as1border2 default 10.14.22.1/32 None AUTO/NONE(-1l) GigabitEthernet2/0 local 0 0 None
57 as1border2 default 10.13.22.0/24 None AUTO/NONE(-1l) GigabitEthernet0/0 connected 0 0 None
65 as1border2 default 1.10.1.1/32 as1core1 1.0.2.2 dynamic ospf 2 110 None
66 as1border2 default 1.0.2.0/24 None AUTO/NONE(-1l) GigabitEthernet1/0 connected 0 0 None
71 as1border1 default 10.12.11.1/32 None AUTO/NONE(-1l) GigabitEthernet1/0 local 0 0 None
96 as1border1 default 1.0.1.0/24 None AUTO/NONE(-1l) GigabitEthernet0/0 connected 0 0 None
100 as1border1 default 10.14.22.0/24 as1core1 1.0.1.2 dynamic ospfE2 20 110 None
101 as1border2 default 1.2.2.2/32 None AUTO/NONE(-1l) Loopback0 connected 0 0 None
118 as1border2 default 2.128.0.0/16 as2border1 10.12.11.2 dynamic ibgp 50 200 None
159 as1border2 default 10.13.22.1/32 None AUTO/NONE(-1l) GigabitEthernet0/0 local 0 0 None
174 as1border1 default 3.0.1.0/24 as3border2 10.13.22.3 dynamic ibgp 50 200 None
189 as1border2 default 1.0.1.0/24 as1core1 1.0.2.2 dynamic ospf 2 110 None
192 as1border1 default 3.0.2.0/24 as3border2 10.13.22.3 dynamic ibgp 50 200 None
193 as1border2 default 1.0.2.1/32 None AUTO/NONE(-1l) GigabitEthernet1/0 local 0 0 None
201 as1border1 default 1.2.2.2/32 as1core1 1.0.1.2 dynamic ospf 3 110 None
226 as1border1 default 10.13.22.0/24 as1core1 1.0.1.2 dynamic ospfE2 20 110 None
243 as1border2 default 3.0.2.0/24 as3border2 10.13.22.3 dynamic bgp 50 20 None
259 as1border1 default 2.128.0.0/16 as2border1 10.12.11.2 dynamic bgp 50 20 None
260 as1border2 default 10.14.22.0/24 None AUTO/NONE(-1l) GigabitEthernet2/0 connected 0 0 None
267 as1border1 default 10.12.11.0/24 None AUTO/NONE(-1l) GigabitEthernet1/0 connected 0 0 None
268 as1border2 default 10.12.11.0/24 as1core1 1.0.2.2 dynamic ospfE2 20 110 None
286 as1border1 default 1.10.1.1/32 as1core1 1.0.1.2 dynamic ospf 2 110 None
312 as1border1 default 1.1.1.1/32 None AUTO/NONE(-1l) Loopback0 connected 0 0 None
327 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
243 as1border2 default 3.0.2.0/24 as3border2 10.13.22.3 dynamic bgp 50 20 None
259 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
49 as3border1 default 2.128.0.0/16 as2border2 10.23.21.2 dynamic bgp 50 20 None
55 as2dist2 default 2.128.0.0/24 as2dept1 2.34.201.4 dynamic bgp 50 20 None
68 as2dept1 default 3.0.1.0/24 as2dist2 2.34.201.3 dynamic bgp 50 20 None
75 as2dist1 default 2.128.1.0/24 as2dept1 2.34.101.4 dynamic bgp 50 20 None
93 as2dept1 default 1.0.2.0/24 as2dist1 2.34.101.3 dynamic bgp 50 20 None
103 as2border1 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic bgp 50 20 None
108 as2border1 default 1.0.1.0/24 as1border1 10.12.11.1 dynamic bgp 50 20 None
120 as2dept1 default 3.0.2.0/24 as2dist1 2.34.101.3 dynamic bgp 50 20 None
127 as2dept1 default 3.0.1.0/24 as2dist1 2.34.101.3 dynamic bgp 50 20 None
149 as2dept1 default 1.0.2.0/24 as2dist2 2.34.201.3 dynamic bgp 50 20 None
152 as3border2 default 1.0.1.0/24 as1border2 10.13.22.1 dynamic bgp 50 20 None
175 as2dist2 default 2.128.1.0/24 as2dept1 2.34.201.4 dynamic bgp 50 20 None
210 as3border2 default 1.0.2.0/24 as1border2 10.13.22.1 dynamic bgp 50 20 None
243 as1border2 default 3.0.2.0/24 as3border2 10.13.22.3 dynamic bgp 50 20 None
259 as1border1 default 2.128.0.0/16 as2border1 10.12.11.2 dynamic bgp 50 20 None
266 as2dist1 default 2.128.0.0/24 as2dept1 2.34.101.4 dynamic bgp 50 20 None
279 as2dept1 default 1.0.1.0/24 as2dist1 2.34.101.3 dynamic bgp 50 20 None
293 as2dept1 default 3.0.2.0/24 as2dist2 2.34.201.3 dynamic bgp 50 20 None
321 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
32 as1border1 default 1.0.2.0/24 as1core1 1.0.1.2 dynamic ospf 2 110 None
66 as1border2 default 1.0.2.0/24 None AUTO/NONE(-1l) GigabitEthernet1/0 connected 0 0 None
83 as1core1 default 1.0.2.0/24 None AUTO/NONE(-1l) GigabitEthernet0/0 connected 0 0 None
93 as2dept1 default 1.0.2.0/24 as2dist1 2.34.101.3 dynamic bgp 50 20 None
103 as2border1 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic bgp 50 20 None
113 as3border1 default 1.0.2.0/24 as1border2 10.13.22.1 dynamic ibgp 50 200 None
122 as2border2 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
123 as2border2 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
148 as3core1 default 1.0.2.0/24 as1border2 10.13.22.1 dynamic ibgp 50 200 None
149 as2dept1 default 1.0.2.0/24 as2dist2 2.34.201.3 dynamic bgp 50 20 None
210 as3border2 default 1.0.2.0/24 as1border2 10.13.22.1 dynamic bgp 50 20 None
239 as2dist2 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
240 as2dist2 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
241 as2dist1 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
242 as2dist1 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
331 as2core2 default 1.0.2.0/24 as1border1 10.12.11.1 dynamic ibgp 50 200 None
335 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
30 host2 default 2.128.1.0/24 None AUTO/NONE(-1l) eth0 connected 0 0 None
59 host1 default 2.128.0.101/32 None AUTO/NONE(-1l) eth0 local 0 0 None
143 host2 default 2.128.1.101/32 None AUTO/NONE(-1l) eth0 local 0 0 None
180 host1 default 2.128.0.0/24 None AUTO/NONE(-1l) eth0 connected 0 0 None
188 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.