Validating Network-as-Code with Batfish: How to Stop Guessing and Start Proving

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

The Shift from Manual CLI to Network-as-Code

I’ve spent too many 2 AM maintenance windows staring at a blinking cursor, hoping a 10-line ACL change wouldn’t trigger a SEV-1 outage. We’ve all been there: typing show ip bgp summary for the tenth time, praying a simple prefix-list update didn’t just blackhole a regional branch. Standard labs like GNS3 or EVE-NG are great for learning, but they are resource hogs. Trying to replicate a 500-router production backbone in a virtual lab is a recipe for a crashed workstation.

After six months of running Batfish on a 200-node backbone, I’ve stopped crossing my fingers. Batfish isn’t a simulator or an emulator. It’s a configuration analysis tool. It parses your raw device configs—Cisco IOS-XE, Juniper JunOS, Arista EOS, or Palo Alto—and builds a rigorous mathematical model of the entire network. You can query your network state like a SQL database without spinning up a single virtual machine.

Transitioning from a traditional Network Engineer to a NetDevOps pro requires this shift in mindset. In a Network-as-Code (NaC) workflow, your configurations are source code. They must pass automated unit tests before they ever touch a production serial number.

Setting Up the Batfish Environment

Batfish uses a client-server architecture. The heavy lifting happens inside a Docker container (the engine), while you interact with it using Pybatfish, a Python library. This setup is lean and integrates perfectly into modern CI/CD pipelines.

1. Launch the Batfish Service

Pull the official Docker image to get started. It’s roughly 1GB, so it’s much lighter than a full suite of vendor VMs.

docker pull batfish/allinone
docker run -d --name batfish -p 9997:9997 -p 9996:9996 batfish/allinone

2. Install Pybatfish

Install the Python client on your local machine. Use a virtual environment to avoid version conflicts with other automation tools.

python3 -m venv batfish-env
source batfish-env/bin/activate
pip install pybatfish

Configuring Snapshots and Python Integration

Batfish organizes data into “snapshots.” A snapshot is just a folder containing your configuration files. You don’t need to tell Batfish how things are connected. It figures out the topology by analyzing interface IPs and routing protocols within those text files.

Organize your directory like this:

network_snapshot/
├── configs/
│   ├── border-router-01.cfg
│   ├── leaf-01.cfg
│   └── fw-dmz-01.cfg
└── hosts/ (optional for server simulation)

Now, use a Python script to initialize the environment. This step uploads your configs to the Docker container for parsing.

from pybatfish.client.commands import bf_session, bf_init_snapshot
from pybatfish.question import bfq

# Connect to the local Batfish engine
bf_session.host = "localhost"

# Initialize the snapshot
SNAPSHOT_PATH = './network_snapshot'
bf_init_snapshot(SNAPSHOT_PATH, name='prod-network', overwrite=True)

Verifying Routing and Security Policies

Once the model is built, Batfish understands your topology. It knows which interfaces are neighbors based on LLDP data or subnet matching. Now you can run queries that would normally take hours of manual show commands.

Finding Hidden Errors

Is your configuration clean? You can check for “undefined references,” such as a BGP neighbor referencing a route-map that doesn’t actually exist. In a large config file with 5,000+ lines, these typos are easy to miss but can cause silent failures.

# Find typos like missing prefix-lists or route-maps
undefined_refs = bfq.undefinedReferences().answer().frame()
print(undefined_refs)

Testing Firewall Reachability

This is the feature that saved my team the most time. Instead of guessing if an ACL works, you can simulate a specific packet flow. Let’s say you need to verify that VLAN 100 (10.10.1.0/24) can reach the Postgres_DB (192.168.50.10) on port 5432.

# Define the flow reachability query
reachability = bfq.reachability(
    pathConstraints=bfq.pathConstraints(startLocation="leaf-01"),
    headers=bfq.headerConstraints(
        srcIps="10.10.1.0/24", 
        dstIps="192.168.50.10", 
        ipProtocols=["tcp"], 
        dstPorts="5432"
    )
).answer().frame()

print(reachability)

If the flow is blocked, Batfish won’t just say “Deny.” It will point you to the exact line in the ACL or firewall policy that dropped the traffic. No more packet-tracer commands on ten different boxes.

Simulating Changes with Impact Analysis

The true power of Network-as-Code is the “Differential Analysis.” Before I push a change to an Arista spine, I create a temporary snapshot with my proposed edits. I then compare it against the current production snapshot.

The “Blast Radius” Test

The differentialReachability question shows exactly what flows will be added or broken. If you are decommissioning an old subnet, this test ensures you aren’t accidentally killing traffic for a service you forgot existed.

# Compare the 'current' vs 'proposed' snapshots
comparison = bfq.differentialReachability(
    baseSnapshot='prod-network',
    snapshot='proposed-change'
).answer().frame()

if not comparison.empty:
    print("Alert: This change impacts existing traffic flows!")
    print(comparison)

Continuous Validation in GitLab

In my current setup, these scripts run inside a GitLab CI pipeline. Every time a teammate opens a Merge Request for a config change, the pipeline triggers automatically. It spins up Batfish, loads the new config, and runs a battery of tests: no routing loops, no leaked internal routes to the ISP, and SSH access restricted to the management subnet.

This workflow has effectively eliminated human error outages in our data center. We don’t guess if a change is safe anymore; we use math to prove it. While Pybatfish has a slight learning curve—especially if you aren’t used to Pandas DataFrames—the peace of mind it provides during a change window is worth the effort.

If your network spans more than 20 devices, manual verification is no longer enough. Build a Batfish model and let the tool do the auditing for you.

Share: