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.

[1]:
# Import packages
%run startup.py
bf = Session(host="localhost")

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 = bf.q.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]:
?bf.q.routes
[5]:
# Get the routing table for the 'default' VRF on border routers of as1
# using BF parameters
routes_as1border = bf.q.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.0/24 interface GigabitEthernet0/0 AUTO/NONE(-1l) GigabitEthernet0/0 connected 0 0 None
1 as1border1 default 1.0.1.1/32 interface GigabitEthernet0/0 AUTO/NONE(-1l) GigabitEthernet0/0 local 0 0 None
2 as1border1 default 1.0.2.0/24 interface GigabitEthernet0/0 ip 1.0.1.2 1.0.1.2 GigabitEthernet0/0 ospf 2 110 None
3 as1border1 default 1.1.1.1/32 interface Loopback0 AUTO/NONE(-1l) Loopback0 connected 0 0 None
4 as1border1 default 1.2.2.2/32 interface GigabitEthernet0/0 ip 1.0.1.2 1.0.1.2 GigabitEthernet0/0 ospf 3 110 None
5 as1border1 default 1.10.1.1/32 interface GigabitEthernet0/0 ip 1.0.1.2 1.0.1.2 GigabitEthernet0/0 ospf 2 110 None
6 as1border1 default 2.128.0.0/16 ip 10.12.11.2 10.12.11.2 dynamic bgp 50 20 None
7 as1border1 default 3.0.1.0/24 ip 10.13.22.3 10.13.22.3 dynamic ibgp 50 200 None
8 as1border1 default 3.0.2.0/24 ip 10.13.22.3 10.13.22.3 dynamic ibgp 50 200 None
9 as1border1 default 10.12.11.0/24 interface GigabitEthernet1/0 AUTO/NONE(-1l) GigabitEthernet1/0 connected 0 0 None
10 as1border1 default 10.12.11.1/32 interface GigabitEthernet1/0 AUTO/NONE(-1l) GigabitEthernet1/0 local 0 0 None
11 as1border1 default 10.13.22.0/24 interface GigabitEthernet0/0 ip 1.0.1.2 1.0.1.2 GigabitEthernet0/0 ospfE2 20 110 None
12 as1border1 default 10.14.22.0/24 interface GigabitEthernet0/0 ip 1.0.1.2 1.0.1.2 GigabitEthernet0/0 ospfE2 20 110 None
13 as1border2 default 1.0.1.0/24 interface GigabitEthernet1/0 ip 1.0.2.2 1.0.2.2 GigabitEthernet1/0 ospf 2 110 None
14 as1border2 default 1.0.2.0/24 interface GigabitEthernet1/0 AUTO/NONE(-1l) GigabitEthernet1/0 connected 0 0 None
15 as1border2 default 1.0.2.1/32 interface GigabitEthernet1/0 AUTO/NONE(-1l) GigabitEthernet1/0 local 0 0 None
16 as1border2 default 1.1.1.1/32 interface GigabitEthernet1/0 ip 1.0.2.2 1.0.2.2 GigabitEthernet1/0 ospf 3 110 None
17 as1border2 default 1.2.2.2/32 interface Loopback0 AUTO/NONE(-1l) Loopback0 connected 0 0 None
18 as1border2 default 1.10.1.1/32 interface GigabitEthernet1/0 ip 1.0.2.2 1.0.2.2 GigabitEthernet1/0 ospf 2 110 None
19 as1border2 default 2.128.0.0/16 ip 10.12.11.2 10.12.11.2 dynamic ibgp 50 200 None
20 as1border2 default 3.0.1.0/24 ip 10.13.22.3 10.13.22.3 dynamic bgp 50 20 None
21 as1border2 default 3.0.2.0/24 ip 10.13.22.3 10.13.22.3 dynamic bgp 50 20 None
22 as1border2 default 10.12.11.0/24 interface GigabitEthernet1/0 ip 1.0.2.2 1.0.2.2 GigabitEthernet1/0 ospfE2 20 110 None
23 as1border2 default 10.13.22.0/24 interface GigabitEthernet0/0 AUTO/NONE(-1l) GigabitEthernet0/0 connected 0 0 None
24 as1border2 default 10.13.22.1/32 interface GigabitEthernet0/0 AUTO/NONE(-1l) GigabitEthernet0/0 local 0 0 None
25 as1border2 default 10.14.22.0/24 interface GigabitEthernet2/0 AUTO/NONE(-1l) GigabitEthernet2/0 connected 0 0 None
26 as1border2 default 10.14.22.1/32 interface GigabitEthernet2/0 AUTO/NONE(-1l) GigabitEthernet2/0 local 0 0 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 = bf.q.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
6 as1border1 default 2.128.0.0/16 ip 10.12.11.2 10.12.11.2 dynamic bgp 50 20 None
20 as1border2 default 3.0.1.0/24 ip 10.13.22.3 10.13.22.3 dynamic bgp 50 20 None
21 as1border2 default 3.0.2.0/24 ip 10.13.22.3 10.13.22.3 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
6 as1border1 default 2.128.0.0/16 ip 10.12.11.2 10.12.11.2 dynamic bgp 50 20 None
20 as1border2 default 3.0.1.0/24 ip 10.13.22.3 10.13.22.3 dynamic bgp 50 20 None
21 as1border2 default 3.0.2.0/24 ip 10.13.22.3 10.13.22.3 dynamic bgp 50 20 None
40 as2border1 default 1.0.1.0/24 ip 10.12.11.1 10.12.11.1 dynamic bgp 50 20 None
41 as2border1 default 1.0.2.0/24 ip 10.12.11.1 10.12.11.1 dynamic bgp 50 20 None
106 as2border2 default 3.0.1.0/24 ip 10.23.21.3 10.23.21.3 dynamic bgp 50 20 None
107 as2border2 default 3.0.2.0/24 ip 10.23.21.3 10.23.21.3 dynamic bgp 50 20 None
182 as2dept1 default 1.0.1.0/24 ip 2.34.101.3 2.34.101.3 dynamic bgp 50 20 None
183 as2dept1 default 1.0.1.0/24 ip 2.34.201.3 2.34.201.3 dynamic bgp 50 20 None
184 as2dept1 default 1.0.2.0/24 ip 2.34.101.3 2.34.101.3 dynamic bgp 50 20 None
185 as2dept1 default 1.0.2.0/24 ip 2.34.201.3 2.34.201.3 dynamic bgp 50 20 None
195 as2dept1 default 3.0.1.0/24 ip 2.34.101.3 2.34.101.3 dynamic bgp 50 20 None
196 as2dept1 default 3.0.1.0/24 ip 2.34.201.3 2.34.201.3 dynamic bgp 50 20 None
197 as2dept1 default 3.0.2.0/24 ip 2.34.101.3 2.34.101.3 dynamic bgp 50 20 None
198 as2dept1 default 3.0.2.0/24 ip 2.34.201.3 2.34.201.3 dynamic bgp 50 20 None
226 as2dist1 default 2.128.0.0/24 ip 2.34.101.4 2.34.101.4 dynamic bgp 50 20 None
227 as2dist1 default 2.128.1.0/24 ip 2.34.101.4 2.34.101.4 dynamic bgp 50 20 None
261 as2dist2 default 2.128.0.0/24 ip 2.34.201.4 2.34.201.4 dynamic bgp 50 20 None
262 as2dist2 default 2.128.1.0/24 ip 2.34.201.4 2.34.201.4 dynamic bgp 50 20 None
271 as3border1 default 2.128.0.0/16 ip 10.23.21.2 10.23.21.2 dynamic bgp 50 20 None
281 as3border2 default 1.0.1.0/24 ip 10.13.22.1 10.13.22.1 dynamic bgp 50 20 None
282 as3border2 default 1.0.2.0/24 ip 10.13.22.1 10.13.22.1 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 = bf.q.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
2 as1border1 default 1.0.2.0/24 interface GigabitEthernet0/0 ip 1.0.1.2 1.0.1.2 GigabitEthernet0/0 ospf 2 110 None
14 as1border2 default 1.0.2.0/24 interface GigabitEthernet1/0 AUTO/NONE(-1l) GigabitEthernet1/0 connected 0 0 None
29 as1core1 default 1.0.2.0/24 interface GigabitEthernet0/0 AUTO/NONE(-1l) GigabitEthernet0/0 connected 0 0 None
41 as2border1 default 1.0.2.0/24 ip 10.12.11.1 10.12.11.1 dynamic bgp 50 20 None
77 as2border2 default 1.0.2.0/24 ip 10.12.11.1 10.12.11.1 dynamic ibgp 50 200 None
113 as2core1 default 1.0.2.0/24 ip 10.12.11.1 10.12.11.1 dynamic ibgp 50 200 None
148 as2core2 default 1.0.2.0/24 ip 10.12.11.1 10.12.11.1 dynamic ibgp 50 200 None
184 as2dept1 default 1.0.2.0/24 ip 2.34.101.3 2.34.101.3 dynamic bgp 50 20 None
185 as2dept1 default 1.0.2.0/24 ip 2.34.201.3 2.34.201.3 dynamic bgp 50 20 None
200 as2dist1 default 1.0.2.0/24 ip 10.12.11.1 10.12.11.1 dynamic ibgp 50 200 None
235 as2dist2 default 1.0.2.0/24 ip 10.12.11.1 10.12.11.1 dynamic ibgp 50 200 None
270 as3border1 default 1.0.2.0/24 ip 10.13.22.1 10.13.22.1 dynamic ibgp 50 200 None
282 as3border2 default 1.0.2.0/24 ip 10.13.22.1 10.13.22.1 dynamic bgp 50 20 None
294 as3core1 default 1.0.2.0/24 ip 10.13.22.1 10.13.22.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
309 host1 default 0.0.0.0/0 interface eth0 ip 2.128.0.1 2.128.0.1 eth0 static 0 1 None
310 host1 default 2.128.0.0/24 interface eth0 AUTO/NONE(-1l) eth0 connected 0 0 None
311 host1 default 2.128.0.101/32 interface eth0 AUTO/NONE(-1l) eth0 local 0 0 None
312 host2 default 0.0.0.0/0 interface eth0 ip 2.128.1.1 2.128.1.1 eth0 static 0 1 None
313 host2 default 2.128.1.0/24 interface eth0 AUTO/NONE(-1l) eth0 connected 0 0 None
314 host2 default 2.128.1.101/32 interface eth0 AUTO/NONE(-1l) eth0 local 0 0 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.