# MIB Catalogue and OID Resolution

## MIB catalogue and OID resolution

An OID like `.1.3.6.1.4.1.6574.2.1.1.5` means nothing on its own. Turning it into `diskTemperature`
requires a MIB, and on an air-gapped probe you cannot look one up.

Axiom Border therefore **ships the catalogue with the product**: over **12,500 MIB modules** in JSON
format covering the vendors you are likely to meet in the field. Symbolic names work anywhere numeric
OIDs work, and returned values come back with readable names attached.

| Directory | Contents | Used by |
| --- | --- | --- |
| `MIBS/JSON-FORMAT/` | ~12,500 `.json` modules | **Yes** — this is the catalogue the probe reads |
| `MIBS/ASN1-FORMAT/` | ~15,900 `.mib` modules | No — provided for the host's own SNMP tooling |

The JSON tree is what the probe uses, and its location is configurable through
`securityProbes.snmp.mibDir` (default `MIBS/JSON-FORMAT`). The ASN.1 tree is a convenience: copy it to
`/usr/share/snmp/mibs` if you want `snmpwalk` and friends on the host to resolve names too.

{{% notice style="tip" title="One operation is slower than the rest" icon="lightbulb" %}}
Listing vendors, inspecting a module and resolving a symbol are all fast, whatever the size of the
catalogue. The one operation that is noticeably slower is a **symbol search without a vendor**, because it
has to cover every module in the catalogue. Pass `vendor` whenever you know it.
{{% /notice %}}

## Vendors

Vendor is derived from the module name: everything before the first hyphen, lowercased. So
`SYNOLOGY-DISK-MIB` belongs to vendor `synology`, and `A3COM-HUAWEI-DEVICE-MIB` to `a3com`. A module
name with no hyphen becomes its own vendor. This is a heuristic, not metadata from the file — it works
because MIB naming conventions are near-universal, but do not expect it to be perfect on unusual
modules.

## Browsing the catalogue

### List vendors and modules

```bash
curl -s http://192.168.1.10:8083/security/mibs \
  -H "Authorization: Bearer <jwt-token>"
```

```json
{
  "root": "MIBS/JSON-FORMAT",
  "vendors": [
    { "vendor": "synology", "modules": ["SYNOLOGY-DISK-MIB", "SYNOLOGY-SYSTEM-MIB"], "moduleCount": 7 }
  ]
}
```

This is inexpensive regardless of catalogue size.

### Search for a symbol

```bash
curl -s 'http://192.168.1.10:8083/security/mibs/search?q=diskTemp&vendor=synology&limit=50' \
  -H "Authorization: Bearer <jwt-token>"
```

```json
{
  "query": "diskTemp",
  "vendor": "synology",
  "limit": 50,
  "matches": [
    {
      "vendor": "synology",
      "module": "SYNOLOGY-DISK-MIB",
      "name": "diskTemperature",
      "oid": "1.3.6.1.4.1.6574.2.1.1.6",
      "class": "objecttype",
      "description": "Temperature of the disk"
    }
  ]
}
```

`q` is mandatory and matched as a case-insensitive substring against object names. **Always pass
`vendor` when you can** — with it the search is limited to that vendor's modules; without it the search
covers the whole catalogue and takes considerably longer.

`limit` of zero or less means unlimited. Results are ordered by vendor, then module, then name.

### Inspect a module

```bash
curl -s http://192.168.1.10:8083/security/mibs/SYNOLOGY-DISK-MIB \
  -H "Authorization: Bearer <jwt-token>"
```

```json
{
  "module": "SYNOLOGY-DISK-MIB",
  "vendor": "synology",
  "objects": [
    { "name": "diskID", "oid": "1.3.6.1.4.1.6574.2.1.1.2", "class": "objecttype", "description": "..." }
  ]
}
```

Module names are **exact and case-sensitive** — they are the filename without the `.json` extension.
An unknown module returns `404`.

## How resolution works

Two directions matter, and they behave differently: symbols become OIDs before the request goes out, and
OIDs get names again on the way back.

### Inbound: symbols become OIDs before the request

When a scan request contains symbolic names, they are resolved **before** anything is sent on the wire:

1. A small built-in table of the universal system OIDs — `sysDescr`, `sysObjectID`, `sysName`,
   `sysLocation`, `sysContact`.
2. Anything already numeric passes through untouched.
3. Everything else is looked up in the vendor's modules.

```mermaid
flowchart TB
    S["Requested name<br>sysDescr"] --> C1{"Built-in<br>system OID?"}
    C1 -->|"no"| C2{"Already<br>numeric?"}
    C2 -->|"no"| C3{"In the vendor's<br>modules?"}
    C3 -->|"no"| FAIL["Execution fails<br>unresolved oids"]:::danger
    C1 -->|"yes"| OK["Numeric OID<br>sent on the wire"]
    C2 -->|"yes"| OK
    C3 -->|"yes"| OK
    classDef danger fill:#fff0ed,stroke:#ff664e,color:#101010
```

{{% notice style="warning" title="An unresolved symbol fails the whole execution" icon="triangle-exclamation" %}}
If any requested symbol cannot be resolved, the execution ends as `failed` with
`snmp: unresolved oids for vendor=<vendor>: <list>` — it does not silently skip the unknown ones and
query the rest. Verify symbols with `/security/mibs/search` before relying on them, or pass numeric OIDs.
{{% /notice %}}

### The vendor hint

Symbol resolution needs to know which vendor's modules to search. Axiom Border works it out in order:

1. The `mib` field of the scan request.
2. `securityProbes.snmp.mib` from configuration.
3. **Inferred from the host's `manufacturer`** as recorded by a previous nmap scan.

The third is the useful one: run a network scan first, and the manufacturer discovered from the MAC
address becomes the vendor hint automatically. Explicit rules exist for common vendors — Synology,
Cisco, HP, Huawei, Juniper, D-Link — with a fallback to the first token of the manufacturer name.

Note that the hint is only consulted when a request actually contains non-numeric symbols. All-numeric
requests need no vendor at all.

### Outbound: OIDs become names on the response

Every OID that comes back is put through the same four steps, in order, and the first match wins:

1. The built-in table of universal system OIDs.
2. The vendor's modules, **trimming up to two trailing segments** — which is how indexed OIDs such as
   `ifDescr.3` resolve to `ifDescr`.
3. The SNMPv2 module.
4. A set of generic modules — `if`, `ip`, `tcp`, `udp`, `host`, `entity`, `sysappl`, `ucd`, `net` — by
   exact match.

If nothing matches, the `name` field of the result is literally `unknown`. The value is still returned —
only the label is missing.

{{% notice style="note" title="Ambiguity yields no name rather than a wrong one" icon="lightbulb" %}}
When trimming produces **more than one** candidate match, resolution stops and returns no name. This is
deliberate: an OID labelled with a plausible-but-wrong symbol is worse than an unlabelled one, because
it silently misleads whoever reads the report.
{{% /notice %}}

## Using symbols in scans

Symbolic names work in both SNMP operations. A GET with symbols:

```bash
curl -s -X POST http://192.168.1.10:8083/security/scan/snmp \
  -H "Authorization: Bearer <jwt-token>" \
  -H 'Content-Type: application/json' \
  -d '{
        "target": "192.168.1.50",
        "mib": "synology",
        "oids": ["sysDescr", "sysName", "diskTemperature"]
      }'
```

And a walk from a symbolic root:

```bash
curl -s -X POST http://192.168.1.10:8083/security/scan/snmp/walk \
  -H "Authorization: Bearer <jwt-token>" \
  -H 'Content-Type: application/json' \
  -d '{"target": "192.168.1.50", "mib": "synology", "walkRoot": "diskTable"}'
```

A `walkRoot` symbol that does not resolve fails that target with
`snmp: walkRoot symbol not found vendor=<vendor> walkRoot=<root>`.

{{% notice style="warning" title="`oids` always wins over `walkRoot`" icon="triangle-exclamation" %}}
If a request contains both `oids` and `walkRoot`, the **`walkRoot` is silently ignored** and only the
listed OIDs are fetched. A request must contain at least one of the two, or it fails with
`snmp: no oids or walkRoot provided`. If you meant to walk, do not send `oids`.
{{% /notice %}}

## Values in results

Each SNMP result entry carries the numeric `oid`, the resolved `name` (or `unknown`), the formatted
`value` and a `status`.

Values are formatted by type: numbers as numbers, IP addresses in dotted form, and byte strings as
**text when they are printable UTF-8, otherwise as `0x<hex>`** — so binary values are legible rather
than mangled. The `status` is `up`, or `error` carrying the SNMP condition name for
`NoSuchObject`, `NoSuchInstance` and `EndOfMibView`.

{{% notice style="note" title="Walks mark disappearances, GETs do not" icon="lightbulb" %}}
A **walk** covers a whole subtree, so an OID that was previously known and is now absent is genuinely
gone, and gets marked as down. A **GET** only asks about specific OIDs, so absence proves nothing and
nothing is marked. This is why change detection over SNMP inventory should use walks.
{{% /notice %}}

## Extending the catalogue

To add a vendor's MIB, drop its JSON module into the configured `mibDir`. New modules are picked up
automatically, and changing `mibDir` reloads the catalogue from the new location.

If the catalogue fails to load — a bad path, unreadable files — the probe logs a warning and continues
with an empty catalogue. It does not refuse to start. Symbol resolution will then fail for everything
except the built-in system OIDs, which is a good signature for this particular misconfiguration: numeric
OIDs work, `sysDescr` works, everything else does not.
