Batfish: Validate Network Config Changes Offline - 夜莺博客

Batfish: Validate Network Config Changes Offline

Batfish parses real device configurations into a model of the network and answers questions about forwarding, ACLs and routing without touching a live device. That turns change review from "read the diff carefully" into "prove that the traffic path we care about still works". Because it is offline, it also fits into a CI pipeline where a config pull request can be blocked before it reaches the rack. This guide covers the snapshot workflow and the two questions that catch most regressions: reachability and differential reachability.

How Batfish models a network

Configurations go through parsing, extraction into a vendor-independent representation, conversion to a model, then post-processing. After that, Batfish computes the data plane (routes and forwarding), the topology, and packet-flow outcomes. The practical consequence: ACLs and routing are evaluated together, so a change that is safe in isolation but breaks a path in combination is still caught.

pip install pybatfish
# or run the service
docker run -d -p 9997:9997 -p 9996:9996 batfish/batfish

The snapshot workflow

# Layout of a snapshot directory
networks/base/configs/
    core1.cfg
    core2.cfg
    dist1.cfg
    dist2.cfg
    edge1.cfg
networks/base/hosts/        # optional host definitions (IP, gateway)
from pybatfish.client.session import Session

bf = Session(host="localhost")
bf.set_network("dc-validate")
bf.init_snapshot("networks/base", name="base", overwrite=True)

# Did every configuration parse cleanly?
bf.q.fileParseStatus().answer().frame()
bf.q.initIssues().answer().frame()

Always check parse status first — a device that failed to parse is simply absent from the model and every later answer will be quietly wrong.

Question 1: does specific traffic still get delivered?

bf.q.reachability(
    pathConstraints={"startLocation": "@enter(dist1)"},
    headers={"dstIps": "10.20.30.10", "srcIps": "10.10.10.50", "ipProtocol": "TCP", "dstPorts": 443}
).answer().frame()

# Broad sweep: what breaks if core1 goes away?
bf.q.reachability(
    pathConstraints={"startLocation": "/dist[0-9]+/"},
    headers={"dstIps": "10.20.30.0/24", "ipProtocol": "TCP", "dstPorts": 443}
).answer().frame()

Question 2: what changes between snapshots?

bf.init_snapshot("networks/change1", name="change1", overwrite=True)

bf.q.differentialReachability(
    headers={"dstIps": "10.20.30.0/24", "ipProtocol": "TCP", "dstPorts": 443}
).answer().frame()

The result lists flows that are accepted in one snapshot but not the other. An entry appearing in the "not accepted / no route" columns of the change snapshot is the regression you were about to deploy.

Wiring it into review

#!/usr/bin/env bash
set -euo pipefail
rm -rf networks/change && mkdir -p networks/change
cp -r configs_from_git/* networks/change/configs/    # proposed configs
python validate.py --fail-on-regression || exit 1    # exit non-zero blocks the merge

Keep three snapshots in the pipeline: base (what is running), change (the PR), and baseline-plus-other-prs when two changes ship together. Testing only the individual change misses interactions between simultaneous edits.

What Batfish does not do

  • It does not validate live state — no ARP tables, no BGP session uptime, no hardware faults.
  • It does not model every vendor extension; unsupported lines raise warnings that must be reviewed, not ignored.
  • It is not a substitute for a maintenance window when the change touches power, optics or firmware.

Related reading: Netmiko Python network automation, NAPALM getters and configuration diff, and Ansible Jinja2 network configuration templating.

原文链接:https://batfish.readthedocs.io/en/latest/notebooks/linked/introduction-to-forwarding-change-validation.html