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.
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:
How to get routes at all nodes in the network or only at a subset of them
How to retrieve routing entries that match a specific protocol or metric
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.