Getting Started with Batfish

This notebook uses pybatfish, a Python-based SDK for Batfish, to analyze a sample network. It shows how to submit your configurations and other network data for analysis and how to query its vendor-neutral network model. Other notebooks show how to use Batfish for different types of network validation tasks.

Check out a video demo of this notebook here.

Initializing a Network and Snapshot

A network is a logical group of routers and links. It can be your entire network or a subset of it. A snapshot is a collection of information (configuration files, routing data, up/down status of nodes and links) that represent the network state. Snapshots can contain the actual state of the network or candidate states (e.g, those corresponding to a planned change) that you want to analyze.

Analytics**Make sure that the Batfish service is running locally before running the cells below.**

[1]:
# Import packages
%run startup.py

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]:
# Assign a friendly name to your network and snapshot
NETWORK_NAME = "example_network"
SNAPSHOT_NAME = "example_snapshot"

SNAPSHOT_PATH = "networks/example"

# Now create the network and initialize the snapshot
bf_set_network(NETWORK_NAME)
bf_init_snapshot(SNAPSHOT_PATH, name=SNAPSHOT_NAME, overwrite=True)
[2]:
'example_snapshot'

If you used the example we provided, the network you initialized above is illustrated below. You can download/view devices’ configuration files here.

example-network


Querying the Network Model

Batfish creates a comprehensive vendor-neutral device and network model which can be queried for information about devices, interfaces, VRFs, routes, etc. It offers a set of questions to query this model.

[3]:
# Load all questions
load_questions()

# To see available questions, you can use
# list_questions()

# You can also use tab-completion on the Batfish question module - bfq. -> press TAB key,
# uncomment and try on the following line
# bfq.

# In IPython and Jupyter you can use the "?" shorthand to get help on a question
?bfq.nodeProperties

# help(bfq.nodeProperties) # in standard Python console

Getting status of parsed files

Batfish may ignore certain lines in the configuration. To retrieve the parsing status of snapshot files, use the fileParseStatus() question.

[4]:
parse_status = bfq.fileParseStatus().answer().frame()

answer() runs the question and returns the answer in a JSON format.

frame() wraps the answer as pandas dataframe.

[5]:
# View the parse status results
parse_status
[5]:
File_Name Status Nodes
0 configs/as1border1.cfg PASSED ['as1border1']
1 configs/as1border2.cfg PASSED ['as1border2']
2 configs/as1core1.cfg PASSED ['as1core1']
3 configs/as2border1.cfg PASSED ['as2border1']
4 configs/as2border2.cfg PASSED ['as2border2']
5 configs/as2core1.cfg PASSED ['as2core1']
6 configs/as2core2.cfg PASSED ['as2core2']
7 configs/as2dept1.cfg PASSED ['as2dept1']
8 configs/as2dist1.cfg PASSED ['as2dist1']
9 configs/as2dist2.cfg PASSED ['as2dist2']
10 configs/as3border1.cfg PASSED ['as3border1']
11 configs/as3border2.cfg PASSED ['as3border2']
12 configs/as3core1.cfg PASSED ['as3core1']
13 hosts/host1.json PASSED ['host1']
14 hosts/host2.json PASSED ['host2']
15 iptables/host1.iptables PASSED ['iptables/host1.iptables']
16 iptables/host2.iptables PASSED ['iptables/host2.iptables']

Additional post-processing can be done on this data, like filtering for values in one or multiple columns, reducing the number of columns, etc. using pandas. A tutorial on pandas-based filtering is here.

[6]:
# An example: use a filter on the returned dataframe to see which files failed to parse completely
parse_status[parse_status['Status'] != 'PASSED']  # change '!=' to '==' to get the files which passed
[6]:
File_Name Status Nodes
[7]:
# View details if some of the files were not parsed completely
parse_warning = bfq.parseWarning().answer().frame()

parse_warning
[7]:
Filename Line Text Parser_Context Comment

Extracting properties of network entities

Entities in the network refer to things like nodes, interfaces, routing processes, and VRFs. Batfish makes it trivial to extract configured properties of such entities in a vendor neutral manner.

Node properties

The nodeProperties question extracts information on nodes in the snapshot.

[8]:
# Extract the properties of all nodes whose names contain 'border'
node_properties = bfq.nodeProperties(nodes="/border/").answer().frame()
[9]:
# View what columns (properties) are present in the answer
node_properties.columns
[9]:
Index(['Node', 'AS_Path_Access_Lists', 'Authentication_Key_Chains',
       'Community_Lists', 'Configuration_Format', 'DNS_Servers',
       'DNS_Source_Interface', 'Default_Cross_Zone_Action',
       'Default_Inbound_Action', 'Device_Type', 'Domain_Name', 'Hostname',
       'IKE_Phase1_Keys', 'IKE_Phase1_Policies', 'IKE_Phase1_Proposals',
       'IP6_Access_Lists', 'IP_Access_Lists', 'IPsec_Peer_Configs',
       'IPsec_Phase2_Policies', 'IPsec_Phase2_Proposals', 'Interfaces',
       'Logging_Servers', 'Logging_Source_Interface', 'NTP_Servers',
       'NTP_Source_Interface', 'PBR_Policies', 'Route6_Filter_Lists',
       'Route_Filter_Lists', 'Routing_Policies', 'SNMP_Source_Interface',
       'SNMP_Trap_Servers', 'TACACS_Servers', 'TACACS_Source_Interface',
       'VRFs', 'Zones'],
      dtype='object')
[10]:
# To extract only a subset of properties, use the properties parameter
node_properties_trunc = bfq.nodeProperties(nodes="/border/", properties="Domain_Name,NTP_Servers,Interfaces").answer().frame()

node_properties_trunc
[10]:
Node Domain_Name Interfaces NTP_Servers
0 as2border2 lab.local ['Ethernet0/0', 'GigabitEthernet0/0', 'GigabitEthernet1/0', 'GigabitEthernet2/0', 'Loopback0'] ['18.18.18.18']
1 as3border1 lab.local ['Ethernet0/0', 'GigabitEthernet0/0', 'GigabitEthernet1/0', 'Loopback0'] ['18.18.18.18', '23.23.23.23']
2 as3border2 lab.local ['Ethernet0/0', 'GigabitEthernet0/0', 'GigabitEthernet1/0', 'Loopback0'] ['18.18.18.18', '23.23.23.23']
3 as1border2 lab.local ['Ethernet0/0', 'GigabitEthernet0/0', 'GigabitEthernet1/0', 'GigabitEthernet2/0', 'Loopback0'] ['18.18.18.18', '23.23.23.23']
4 as2border1 lab.local ['Ethernet0/0', 'GigabitEthernet0/0', 'GigabitEthernet1/0', 'GigabitEthernet2/0', 'Loopback0'] ['18.18.18.18', '23.23.23.23']
5 as1border1 lab.local ['Ethernet0/0', 'GigabitEthernet0/0', 'GigabitEthernet1/0', 'Loopback0'] []

An alternative (client-side) way to restrict the list of columns displayed is to use pandas-based column filtering (pandas tutorial).

[11]:
# Let's remove the interfaces column from our result
node_properties_trunc = node_properties_trunc[["Node", "Domain_Name", "NTP_Servers"]]

node_properties_trunc
[11]:
Node Domain_Name NTP_Servers
0 as2border2 lab.local ['18.18.18.18']
1 as3border1 lab.local ['18.18.18.18', '23.23.23.23']
2 as3border2 lab.local ['18.18.18.18', '23.23.23.23']
3 as1border2 lab.local ['18.18.18.18', '23.23.23.23']
4 as2border1 lab.local ['18.18.18.18', '23.23.23.23']
5 as1border1 lab.local []

You can add additional filters to restrict entries based on values of columns.

[12]:
# View only nodes with **23.23.23.23** as one of the configured ntp-servers
node_properties_trunc[node_properties_trunc['NTP_Servers'].apply(lambda x:'23.23.23.23' in x)]
[12]:
Node Domain_Name NTP_Servers
1 as3border1 lab.local ['18.18.18.18', '23.23.23.23']
2 as3border2 lab.local ['18.18.18.18', '23.23.23.23']
3 as1border2 lab.local ['18.18.18.18', '23.23.23.23']
4 as2border1 lab.local ['18.18.18.18', '23.23.23.23']

Interface properties

To retrieve information about interfaces present and the properties of them, use the interfaceProperties question

[13]:
interface_properties = bfq.interfaceProperties(nodes="/border/", properties="Bandwidth,VRF,Primary_Address").answer().frame()

If you wanted to just find interfaces with the primary ip address in 10.12.0.0/16, you can filter the results as shown below.

na=False is required in order to ignore interfaces without any configured IP addresses, such as ethernet switchports.

[14]:
interface_properties[interface_properties['Primary_Address'].str.match("10.12", na=False)]
[14]:
Interface Bandwidth Primary_Address VRF
24 as1border1[GigabitEthernet1/0] 1e+09 10.12.11.1/24 default
26 as2border1[GigabitEthernet0/0] 1e+09 10.12.11.2/24 default

Similar questions extract properties of other entities (e.g., bgpProcessConfiguration() extracts properties of BGP processes).


Inspecting referential integrity of configuration structures

Network configuratons define and reference named structures like route maps, access control lists (ACLs), prefix lists, etc. Two common indicators of buggy configurations include references to structures that are not defined anywhere (which can lead to disastrous consequences on some platforms) or defined structures that are not referenced anywhere. Batfish makes it easy to flag such instances because it understand the underlying semantics of configuration.

[15]:
# List references to undefined structures
bfq.undefinedReferences().answer().frame()
[15]:
File_Name Struct_Type Ref_Name Context Lines
0 configs/as2core2.cfg route-map filter-bogons bgp inbound route-map configs/as2core2.cfg:[109]

The question for listing any unused structures is unusedStructures().


Inspecting physical and logical topologies

Nodes in a network form multiple types of topologies that are defined by edges at layer 1 or layer 3 (IP layer) or by different types of routing protocols such as BGP or OSPF. The edges() question in Batfish returns these different types of edges. Its edgeType parameter specifies the type of edges to retrieve, and the nodes and remoteNodes parameters can be used to limit the returned edges to a subset of the nodes.

[16]:
# Get edges of type layer 3 (IP layer)
bfq.edges(nodes="as1border1", edgeType="layer3").answer().frame()
[16]:
Interface IPs Remote_Interface Remote_IPs
0 as1border1[GigabitEthernet0/0] ['1.0.1.1'] as1core1[GigabitEthernet1/0] ['1.0.1.2']
1 as1border1[GigabitEthernet1/0] ['10.12.11.1'] as2border1[GigabitEthernet0/0] ['10.12.11.2']

Exploring Routing and Forwarding

Batfish computes routing and forwarding tables (aka RIBs and FIBs) of the network from snapshot data itself. These tables can be examined to understand the routing and forwarding behavior of the network.

One way to examine this behavior is using a virtual traceroute. Unlike the live-network traceroute, Batfish shows all possible flow paths in the network and identifies routing entries that cause each hop to be taken.

[17]:
# Do a traceroute from host1 to 1.0.2.2
tr_frame = bfq.traceroute(startLocation="host1", headers=HeaderConstraints(dstIps="1.0.2.2")).answer().frame()

# Display results using customizations to handle large string values

show(tr_frame)
Flow Traces TraceCount
0 Start Location: host1
Src IP: 2.128.0.101
Src Port: 49152
Dst IP: 1.0.2.2
Dst Port: 33434
IP Protocol: UDP
ACCEPTED
1. node: host1
  ORIGINATED(default)
  FORWARDED(ARP IP: 2.128.0.1, Output Interface: eth0, Routes: [static (Network: 0.0.0.0/0, Next Hop IP:2.128.0.1)])
  PERMITTED(filter::OUTPUT (EGRESS_FILTER))
  TRANSMITTED(eth0)
2. node: as2dept1
  RECEIVED(GigabitEthernet2/0)
  PERMITTED(RESTRICT_HOST_TRAFFIC_IN (INGRESS_FILTER))
  FORWARDED(ARP IP: 2.34.101.3, Output Interface: GigabitEthernet0/0, Routes: [bgp (Network: 1.0.2.0/24, Next Hop IP:2.34.101.3)])
  TRANSMITTED(GigabitEthernet0/0)
3. node: as2dist1
  RECEIVED(GigabitEthernet2/0)
  FORWARDED(ARP IP: 2.23.11.2, Output Interface: GigabitEthernet0/0, Routes: [ibgp (Network: 1.0.2.0/24, Next Hop IP:10.12.11.1)])
  TRANSMITTED(GigabitEthernet0/0)
4. node: as2core1
  RECEIVED(GigabitEthernet2/0)
  PERMITTED(blocktelnet (INGRESS_FILTER))
  FORWARDED(ARP IP: 2.12.11.1, Output Interface: GigabitEthernet0/0, Routes: [ibgp (Network: 1.0.2.0/24, Next Hop IP:10.12.11.1)])
  TRANSMITTED(GigabitEthernet0/0)
5. node: as2border1
  RECEIVED(GigabitEthernet1/0)
  FORWARDED(ARP IP: 10.12.11.1, Output Interface: GigabitEthernet0/0, Routes: [bgp (Network: 1.0.2.0/24, Next Hop IP:10.12.11.1)])
  PERMITTED(INSIDE_TO_AS1 (EGRESS_FILTER))
  TRANSMITTED(GigabitEthernet0/0)
6. node: as1border1
  RECEIVED(GigabitEthernet1/0)
  FORWARDED(ARP IP: 1.0.1.2, Output Interface: GigabitEthernet0/0, Routes: [ospf (Network: 1.0.2.0/24, Next Hop IP:1.0.1.2)])
  TRANSMITTED(GigabitEthernet0/0)
7. node: as1core1
  RECEIVED(GigabitEthernet1/0)
  ACCEPTED(GigabitEthernet0/0)

ACCEPTED
1. node: host1
  ORIGINATED(default)
  FORWARDED(ARP IP: 2.128.0.1, Output Interface: eth0, Routes: [static (Network: 0.0.0.0/0, Next Hop IP:2.128.0.1)])
  PERMITTED(filter::OUTPUT (EGRESS_FILTER))
  TRANSMITTED(eth0)
2. node: as2dept1
  RECEIVED(GigabitEthernet2/0)
  PERMITTED(RESTRICT_HOST_TRAFFIC_IN (INGRESS_FILTER))
  FORWARDED(ARP IP: 2.34.101.3, Output Interface: GigabitEthernet0/0, Routes: [bgp (Network: 1.0.2.0/24, Next Hop IP:2.34.101.3)])
  TRANSMITTED(GigabitEthernet0/0)
3. node: as2dist1
  RECEIVED(GigabitEthernet2/0)
  FORWARDED(ARP IP: 2.23.21.2, Output Interface: GigabitEthernet1/0, Routes: [ibgp (Network: 1.0.2.0/24, Next Hop IP:10.12.11.1)])
  TRANSMITTED(GigabitEthernet1/0)
4. node: as2core2
  RECEIVED(GigabitEthernet3/0)
  FORWARDED(ARP IP: 2.12.12.1, Output Interface: GigabitEthernet1/0, Routes: [ibgp (Network: 1.0.2.0/24, Next Hop IP:10.12.11.1)])
  TRANSMITTED(GigabitEthernet1/0)
5. node: as2border1
  RECEIVED(GigabitEthernet2/0)
  FORWARDED(ARP IP: 10.12.11.1, Output Interface: GigabitEthernet0/0, Routes: [bgp (Network: 1.0.2.0/24, Next Hop IP:10.12.11.1)])
  PERMITTED(INSIDE_TO_AS1 (EGRESS_FILTER))
  TRANSMITTED(GigabitEthernet0/0)
6. node: as1border1
  RECEIVED(GigabitEthernet1/0)
  FORWARDED(ARP IP: 1.0.1.2, Output Interface: GigabitEthernet0/0, Routes: [ospf (Network: 1.0.2.0/24, Next Hop IP:1.0.1.2)])
  TRANSMITTED(GigabitEthernet0/0)
7. node: as1core1
  RECEIVED(GigabitEthernet1/0)
  ACCEPTED(GigabitEthernet0/0)

ACCEPTED
1. node: host1
  ORIGINATED(default)
  FORWARDED(ARP IP: 2.128.0.1, Output Interface: eth0, Routes: [static (Network: 0.0.0.0/0, Next Hop IP:2.128.0.1)])
  PERMITTED(filter::OUTPUT (EGRESS_FILTER))
  TRANSMITTED(eth0)
2. node: as2dept1
  RECEIVED(GigabitEthernet2/0)
  PERMITTED(RESTRICT_HOST_TRAFFIC_IN (INGRESS_FILTER))
  FORWARDED(ARP IP: 2.34.201.3, Output Interface: GigabitEthernet1/0, Routes: [bgp (Network: 1.0.2.0/24, Next Hop IP:2.34.201.3)])
  TRANSMITTED(GigabitEthernet1/0)
3. node: as2dist2
  RECEIVED(GigabitEthernet2/0)
  FORWARDED(ARP IP: 2.23.22.2, Output Interface: GigabitEthernet0/0, Routes: [ibgp (Network: 1.0.2.0/24, Next Hop IP:10.12.11.1)])
  TRANSMITTED(GigabitEthernet0/0)
4. node: as2core2
  RECEIVED(GigabitEthernet2/0)
  FORWARDED(ARP IP: 2.12.12.1, Output Interface: GigabitEthernet1/0, Routes: [ibgp (Network: 1.0.2.0/24, Next Hop IP:10.12.11.1)])
  TRANSMITTED(GigabitEthernet1/0)
5. node: as2border1
  RECEIVED(GigabitEthernet2/0)
  FORWARDED(ARP IP: 10.12.11.1, Output Interface: GigabitEthernet0/0, Routes: [bgp (Network: 1.0.2.0/24, Next Hop IP:10.12.11.1)])
  PERMITTED(INSIDE_TO_AS1 (EGRESS_FILTER))
  TRANSMITTED(GigabitEthernet0/0)
6. node: as1border1
  RECEIVED(GigabitEthernet1/0)
  FORWARDED(ARP IP: 1.0.1.2, Output Interface: GigabitEthernet0/0, Routes: [ospf (Network: 1.0.2.0/24, Next Hop IP:1.0.1.2)])
  TRANSMITTED(GigabitEthernet0/0)
7. node: as1core1
  RECEIVED(GigabitEthernet1/0)
  ACCEPTED(GigabitEthernet0/0)

ACCEPTED
1. node: host1
  ORIGINATED(default)
  FORWARDED(ARP IP: 2.128.0.1, Output Interface: eth0, Routes: [static (Network: 0.0.0.0/0, Next Hop IP:2.128.0.1)])
  PERMITTED(filter::OUTPUT (EGRESS_FILTER))
  TRANSMITTED(eth0)
2. node: as2dept1
  RECEIVED(GigabitEthernet2/0)
  PERMITTED(RESTRICT_HOST_TRAFFIC_IN (INGRESS_FILTER))
  FORWARDED(ARP IP: 2.34.201.3, Output Interface: GigabitEthernet1/0, Routes: [bgp (Network: 1.0.2.0/24, Next Hop IP:2.34.201.3)])
  TRANSMITTED(GigabitEthernet1/0)
3. node: as2dist2
  RECEIVED(GigabitEthernet2/0)
  FORWARDED(ARP IP: 2.23.12.2, Output Interface: GigabitEthernet1/0, Routes: [ibgp (Network: 1.0.2.0/24, Next Hop IP:10.12.11.1)])
  TRANSMITTED(GigabitEthernet1/0)
4. node: as2core1
  RECEIVED(GigabitEthernet3/0)
  PERMITTED(blocktelnet (INGRESS_FILTER))
  FORWARDED(ARP IP: 2.12.11.1, Output Interface: GigabitEthernet0/0, Routes: [ibgp (Network: 1.0.2.0/24, Next Hop IP:10.12.11.1)])
  TRANSMITTED(GigabitEthernet0/0)
5. node: as2border1
  RECEIVED(GigabitEthernet1/0)
  FORWARDED(ARP IP: 10.12.11.1, Output Interface: GigabitEthernet0/0, Routes: [bgp (Network: 1.0.2.0/24, Next Hop IP:10.12.11.1)])
  PERMITTED(INSIDE_TO_AS1 (EGRESS_FILTER))
  TRANSMITTED(GigabitEthernet0/0)
6. node: as1border1
  RECEIVED(GigabitEthernet1/0)
  FORWARDED(ARP IP: 1.0.1.2, Output Interface: GigabitEthernet0/0, Routes: [ospf (Network: 1.0.2.0/24, Next Hop IP:1.0.1.2)])
  TRANSMITTED(GigabitEthernet0/0)
7. node: as1core1
  RECEIVED(GigabitEthernet1/0)
  ACCEPTED(GigabitEthernet0/0)
4

Another way to understand the routing behavior in detail is to examine the routing tables directly.

[18]:
# Fetch the routing table of all VRFs on all nodes in the snapshot
routes_df = bfq.routes().answer().frame()

(For a large network, the first time you run a question that needs the dataplane, fetching the answer can take a few minutes. Subsequent questions are quick as the generated dataplane is saved by Batfish.)

As used above, the routes() question can generate a lot of results. You may restrict the output using parameters to the question—to restrict the results to border routers, use nodes = “.*border.*”. Or, you can use pandas for client-side filtering as below.

[19]:
# View all routes for the network 90.90.90.0/24 with an AdminDistance of 0
routes_df[(routes_df['Network'] == "90.90.90.0/24") & (routes_df["Admin_Distance"] == 0)]
[19]:
Node VRF Network Next_Hop Next_Hop_IP Next_Hop_Interface Protocol Metric Admin_Distance Tag
273 as3core1 default 90.90.90.0/24 None AUTO/NONE(-1l) GigabitEthernet3/0 connected 0 0 None
318 as3core1 default 90.90.90.0/24 None AUTO/NONE(-1l) GigabitEthernet2/0 connected 0 0 None

That’s it for now! Feel free to explore further by adding cells and running other questions, or play with other notebooks.


Get involved with the Batfish community

Join our community on Slack and GitHub.