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.
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.