MIB sources and patches

mib-sources.json records the publisher and the retrieval method for every module under src/. This page describes that record, why it exists, and how the tooling checks it.

For most of this repository’s life the provenance was not written down. MIBs arrived in bulk, from the snmplabs collection this repository began as and from librenms, and were hand-repaired whenever one would not compile. The repair went into the file, next to the vendor’s own text, and after that the two were indistinguishable. A module carrying a deliberate one-line fix and a module eight years behind its vendor look exactly alike on disk. Nothing could tell us which modules had fallen behind, because nothing recorded what they were supposed to match.

mib-sources.json records it, using the shape pysmi already uses for the base MIBs it bundles:

checked-in text  ==  the publisher's text  +  our patch

The vendor’s own bytes are the baseline. Our change, where we have one, sits beside it in scripts/mib-patches/ where it can be read, reviewed and re-applied to a newer revision. A module with a publisher and no patch must match that publisher byte for byte.

Once that holds, “has the vendor revised this?” becomes a question a machine can ask, and the monthly workflow asks it.

Why each patch exists

A diff says what was changed and never why. That matters the month a publisher moves under a patch and it stops applying, because a maintainer then has to decide between two cases. If the vendor fixed the defect, the patch should go. If the vendor changed something unrelated, the patch should be re-cut. That decision needs the original defect written down.

So every patch opens with the defect it repairs, in pysmi’s header format:

Defect: SMI-UNPUBLISHED-MODULE https://pysnmp.github.io/pysmi/stable/mib-defects.html#smi-unpublished-module

The module imports igmpInterfaceEntry FROM IGMP-MIB. No publisher ships
a module by that name: RFC 2933 publishes the IGMP MIB as IGMP-STD-MIB.

--- a/CISCO-IPMCAST-MIB
+++ b/CISCO-IPMCAST-MIB
@@ ...

The identifier links to pysmi’s catalogue, which says what the defect is and which rule it breaks, so the note here only has to say what is particular to this module. Everything above the first diff line is header: it is not applied to anything, and --adopt carries it over when it re-cuts the diff, so a regenerated patch does not lose its reason.

Where the archaeology does not resolve, the honest note is that it does not. “Cause not recovered; the diff is what makes it compile” is worth more to the next reader than an invented reason.

What this does not recover

Two things, and neither is a gap to be closed later.

A module whose publisher serves nothing fetchable has no baseline. Plenty of vendors publish MIBs only behind a support login, or only inside a firmware image, or not at all. Those modules stay unmanaged. --report counts them, so the size of the gap stays visible rather than being excluded from a green check without notice.

An edit made before the file reached us is invisible. The snmplabs and librenms collections were themselves hand-repaired for years, and those repairs arrived here inside the imported file. Our git history starts at the import; it cannot see behind it. So adopting a source turns silent patching into visible patching only for edits made since the import. Anything older stays silent, and appears only as an unexplained difference.

--explain is honest about this. It reports which differing lines a commit of ours wrote, and says plainly when the rest trace no further back than the import. Those may be a repair made here in 2015, or a revision the vendor has published since. This repository’s history cannot distinguish them, and the tool reports that rather than guessing. A maintainer reads the two texts.

Using it

# Does the manifest still describe the tree? Offline, so ordinary CI
# runs it on every pull request.
uv run python scripts/update_vendor_mibs.py --validate

# What the monthly workflow runs. Re-fetches everything with a
# publisher and reports what no longer matches.
uv run python scripts/update_vendor_mibs.py --check

# How much of src/ has a publisher behind it at all.
uv run python scripts/update_vendor_mibs.py --report

# Why does our copy of this module differ from the vendor's?
uv run python scripts/update_vendor_mibs.py --explain src/vendor/cisco/CISCO-ENTITY-ALARM-MIB

# It is a fix of ours: keep it, as a patch against the vendor's text.
# Nothing on disk changes -- the patch reproduces the file exactly.
uv run python scripts/update_vendor_mibs.py --adopt src/vendor/cisco/CISCO-ENTITY-ALARM-MIB

# It was only stale: take the vendor's text, patches re-applied.
uv run python scripts/update_vendor_mibs.py --update src/vendor/cisco/CISCO-BGP4-MIB

# Adopt a vendor directory wholesale: everything that already matches
# the publisher is recorded, and everything that does not is listed for
# a maintainer to --explain.
uv run python scripts/update_vendor_mibs.py --discover cisco-mibs-v2 src/vendor/cisco

--update is the only mode that rewrites a MIB, and it refuses partway: a half-refreshed vendor directory compiles into a tree that is neither the old set nor the new one.

What --check treats as failure

Not everything it reports is a problem to fix this month.

A module that no longer matches its publisher, a patch that no longer applies, and a source that has stopped serving the module it should all fail the run. So does a recorded divergence that has since been resolved: a note left after the condition clears stops being read.

A divergence already recorded does not fail the run. It is listed every month under “known divergences, awaiting review”, and that backlog is the honest state of this repository: 52 modules that differ from their vendor for reasons not yet established. Failing on them every month would train everybody to ignore the one month the sweep says something new.

An unreachable publisher exits 2 rather than 1. An outage says nothing about our MIBs, and the exit code should not claim otherwise.

Adding a publisher

A publisher belongs in mib-sources.json when it serves the vendor’s own MIB text at a URL that can be fetched without a login and that keeps working. Two kinds are supported:

  • file: one URL per module, with {module} standing in for the name.

  • archive: one zip holding many modules, with member naming the path inside it.

Pin a live URL, not a dated release artefact. A frozen URL would make the copy here unfalsifiable: the fetch would compare equal forever while the vendor moved on, which is exactly the blindness this exists to end.

Then run --discover and read what it could not place.

Sources that do not work

Recorded here so that it need not be rediscovered:

Vendor

What happens

Arista

arista.com/assets/data/docs/MIBs/ answers 200 with a bot-check HTML page, not MIB text

MikroTik

the download link is an opaque file-share URL serving an HTML wrapper

Juniper, Huawei, HPE/Comware, Fortinet, Nokia

MIB bundles are behind a support login

Cisco Small Business (CISCOSB-*), UCS

not in cisco/cisco-mibs; published only inside firmware images

The --check sweep guards against the first two specifically: a fetch that succeeds but does not contain the module it should is reported as not-a-module rather than being adopted as the vendor’s text. Without that, a vendor putting its downloads behind a bot check would silently replace real MIBs with an error page.