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 an earlier version 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.

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

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]:
# You can also use tab-completion on the Batfish question module - bf.q. -> press TAB key,
# uncomment and try on the following line
# bf.q.

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

# help(bf.q.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 = bf.q.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 File_Format Nodes
0 configs/as1border1.cfg PASSED CISCO_IOS ['as1border1']
1 configs/as1border2.cfg PASSED CISCO_IOS ['as1border2']
2 configs/as1core1.cfg PASSED CISCO_IOS ['as1core1']
3 configs/as2border1.cfg PASSED CISCO_IOS ['as2border1']
4 configs/as2border2.cfg PASSED CISCO_IOS ['as2border2']
5 configs/as2core1.cfg PASSED CISCO_IOS ['as2core1']
6 configs/as2core2.cfg PASSED CISCO_IOS ['as2core2']
7 configs/as2dept1.cfg PASSED CISCO_IOS ['as2dept1']
8 configs/as2dist1.cfg PASSED CISCO_IOS ['as2dist1']
9 configs/as2dist2.cfg PASSED CISCO_IOS ['as2dist2']
10 configs/as3border1.cfg PASSED CISCO_IOS ['as3border1']
11 configs/as3border2.cfg PASSED CISCO_IOS ['as3border2']
12 configs/as3core1.cfg PASSED CISCO_IOS ['as3core1']
13 hosts/host1.json PASSED HOST ['host1']
14 hosts/host2.json PASSED HOST ['host2']
15 iptables/host1.iptables PASSED IPTABLES ['iptables/host1.iptables']
16 iptables/host2.iptables PASSED IPTABLES ['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. We show a few examples of Pandas filtering below, some more filtering examples for Batfish answers are here, and a general tutorial 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 File_Format Nodes
[7]:
# View details if some of the files were not parsed completely
bf.q.parseWarning().answer().frame()
[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 = bf.q.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_Match_Exprs', 'Community_Set_Exprs',
       'Community_Set_Match_Exprs', 'Community_Sets', 'Configuration_Format',
       'DNS_Servers', 'DNS_Source_Interface', 'Default_Cross_Zone_Action',
       'Default_Inbound_Action', '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
bf.q.nodeProperties(nodes="/border/", properties="Domain_Name,NTP_Servers,Interfaces").answer().frame()
[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'] []

Interface properties

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

[11]:
# Fetch specific properties of Loopback interfaces
bf.q.interfaceProperties(interfaces="/loopback/", properties="Bandwidth,VRF,Primary_Address").answer().frame()
[11]:
Interface Bandwidth Primary_Address VRF
0 as1border1[Loopback0] 8000000000.0 1.1.1.1/32 default
1 as1border2[Loopback0] 8000000000.0 1.2.2.2/32 default
2 as1core1[Loopback0] 8000000000.0 1.10.1.1/32 default
3 as2border1[Loopback0] 8000000000.0 2.1.1.1/32 default
4 as2border2[Loopback0] 8000000000.0 2.1.1.2/32 default
5 as2core1[Loopback0] 8000000000.0 2.1.2.1/32 default
6 as2core2[Loopback0] 8000000000.0 2.1.2.2/32 default
7 as2dept1[Loopback0] 8000000000.0 2.1.1.2/32 default
8 as2dist1[Loopback0] 8000000000.0 2.1.3.1/32 default
9 as2dist2[Loopback0] 8000000000.0 2.1.3.2/32 default
10 as3border1[Loopback0] 8000000000.0 3.1.1.1/32 default
11 as3border2[Loopback0] 8000000000.0 3.2.2.2/32 default
12 as3core1[Loopback0] 8000000000.0 3.10.1.1/32 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.

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

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


Inspecting topologies

Nodes in a network form multiple types of topologies that are defined by edges at layer 3 (IP layer) or by routing protocols such as BGP or OSPF. Batfish has questions that return such edges. These questions take nodes and remoteNodes parameters that can limit the output to a subset of the nodes.

[13]:
# Get layer 3 edges
bf.q.layer3Edges(nodes="as1border1").answer().frame()
[13]:
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']
[14]:
# Get BGP edges
bf.q.bgpEdges(nodes="as1border1").answer().frame()
[14]:
Node IP Interface AS_Number Remote_Node Remote_IP Remote_Interface Remote_AS_Number
0 as1border1 10.12.11.1 None 1 as2border1 10.12.11.2 None 2
1 as1border1 1.1.1.1 None 1 as1core1 1.10.1.1 None 1

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.

[15]:
# Do a traceroute from host1 to 1.0.2.2
tr_frame = bf.q.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(Forwarded out interface: eth0 with resolved next-hop IP: 2.128.0.1, Routes: [static (Network: 0.0.0.0/0, Next Hop: interface eth0 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 2.34.101.3, 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 2.23.11.2, 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 2.12.11.1, 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 10.12.11.1, 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 1.0.1.2, Routes: [ospf (Network: 1.0.2.0/24, Next Hop: interface GigabitEthernet0/0 ip 1.0.1.2)])
  TRANSMITTED(GigabitEthernet0/0)
7. node: as1core1
  RECEIVED(GigabitEthernet1/0)
  ACCEPTED(GigabitEthernet0/0)

ACCEPTED
1. node: host1
  ORIGINATED(default)
  FORWARDED(Forwarded out interface: eth0 with resolved next-hop IP: 2.128.0.1, Routes: [static (Network: 0.0.0.0/0, Next Hop: interface eth0 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 2.34.101.3, 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(Forwarded out interface: GigabitEthernet1/0 with resolved next-hop IP: 2.23.21.2, 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(Forwarded out interface: GigabitEthernet1/0 with resolved next-hop IP: 2.12.12.1, 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 10.12.11.1, 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 1.0.1.2, Routes: [ospf (Network: 1.0.2.0/24, Next Hop: interface GigabitEthernet0/0 ip 1.0.1.2)])
  TRANSMITTED(GigabitEthernet0/0)
7. node: as1core1
  RECEIVED(GigabitEthernet1/0)
  ACCEPTED(GigabitEthernet0/0)

ACCEPTED
1. node: host1
  ORIGINATED(default)
  FORWARDED(Forwarded out interface: eth0 with resolved next-hop IP: 2.128.0.1, Routes: [static (Network: 0.0.0.0/0, Next Hop: interface eth0 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(Forwarded out interface: GigabitEthernet1/0 with resolved next-hop IP: 2.34.201.3, 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 2.23.22.2, 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(Forwarded out interface: GigabitEthernet1/0 with resolved next-hop IP: 2.12.12.1, 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 10.12.11.1, 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 1.0.1.2, Routes: [ospf (Network: 1.0.2.0/24, Next Hop: interface GigabitEthernet0/0 ip 1.0.1.2)])
  TRANSMITTED(GigabitEthernet0/0)
7. node: as1core1
  RECEIVED(GigabitEthernet1/0)
  ACCEPTED(GigabitEthernet0/0)

ACCEPTED
1. node: host1
  ORIGINATED(default)
  FORWARDED(Forwarded out interface: eth0 with resolved next-hop IP: 2.128.0.1, Routes: [static (Network: 0.0.0.0/0, Next Hop: interface eth0 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(Forwarded out interface: GigabitEthernet1/0 with resolved next-hop IP: 2.34.201.3, 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(Forwarded out interface: GigabitEthernet1/0 with resolved next-hop IP: 2.23.12.2, 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 2.12.11.1, 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 10.12.11.1, 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(Forwarded out interface: GigabitEthernet0/0 with resolved next-hop IP: 1.0.1.2, Routes: [ospf (Network: 1.0.2.0/24, Next Hop: interface GigabitEthernet0/0 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.

[16]:
# Fetch the routing table of all VRFs on all nodes in the snapshot
routes_all = bf.q.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 core routers, use nodes = “/core/”, and to restrict results to the prefix 90.90.90.0/24, use **network=90.90.90.0/24”.

[17]:
# Get all routes for the network 90.90.90.0/24 on core routers
bf.q.routes(nodes="/core/", network="90.90.90.0/24").answer().frame()
[17]:
Node VRF Network Next_Hop Next_Hop_IP Next_Hop_Interface Protocol Metric Admin_Distance Tag
0 as3core1 default 90.90.90.0/24 interface GigabitEthernet2/0 AUTO/NONE(-1l) GigabitEthernet2/0 connected 0 0 None
1 as3core1 default 90.90.90.0/24 interface GigabitEthernet3/0 AUTO/NONE(-1l) GigabitEthernet3/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.