SNMP Discovery
The SNMP discovery backend leverages SNMP (Simple Network Management Protocol) to connect to network devices and collect network information.
This backend works with any SNMPv1/v2c/v3 capable device. For the list of vendors with bundled device model lookup coverage, see SNMP Discovery - Supported Platforms.
Diode Entities
The SNMP discovery backend uses Diode Go SDK to ingest the following entities:
- Device
- Interface
- IP Address
- Mac Address
- Platform
- Manufacturer
- Site
- VLAN
- VirtualChassis
- Module
- ModuleBay
When a target is a switch stack / Virtual Chassis (NetBox VirtualChassis), snmp-discovery emits one VirtualChassis entity plus one Device per stack member, and routes each interface/IP to the member that physically owns it - see Switch stacks / Virtual Chassis below. Standalone switches and devices not in stack mode fall through to the existing single-Device path with no change in behaviour.
When a target is a modular chassis and the discover_modules policy option is enabled, snmp-discovery additionally emits Module and ModuleBay entities for each chassis slot (and, in full mode, each transceiver sub-bay) - see Modules / ModuleBays below. Defaults to off, so existing operators see zero behaviour change unless they explicitly opt in.
When the discover_vrfs policy option is enabled, snmp-discovery emits a VRF entity for each VRF reported by the device's VRF MIB tables and attaches it to the IP addresses of the VRF's member interfaces - see VRFs below. Defaults to false, so existing operators see zero behaviour change.
When the discover_asset_tags policy option is enabled, snmp-discovery reads ENTITY-MIB::entPhysicalAssetID and populates asset_tag on each emitted device - including per-member tags on virtual-chassis stacks. Defaults to false, so existing operators see zero behaviour change. Note that NetBox asset_tag values are unique and act as the highest-precedence device matcher during ingestion: enable this only if the tags provisioned on your devices are trustworthy and unique.
When the emit_device_name policy option is set to false, snmp-discovery still walks sysName but does not emit Device.name on the matched device. Use this with a target netbox_id / metadata.source_match so Diode matches the existing NetBox record without proposing a hostname rename when the device's sysName differs from the NetBox name. The name is suppressed only when the device carries a matcher that also travels on its nested references - source_match (netbox_id) or asset_tag; if neither is present the name is kept and a warning is logged. (Matching by serial or primary_ip alone does not enable omission: serial is not a unique NetBox matcher, and primary_ip is stripped from the nested device stubs.) The name is suppressed across every representation of the device that reaches the payload: the device itself, the shared virtual-chassis master reference, the nested device stubs on interfaces, and the device reference embedded in its own primary_ip4/primary_ip6. On a virtual-chassis stack, member names and the virtual-chassis name are unaffected. Defaults to true.
Interfaces that are members of a link aggregation carry a lag reference to their aggregate interface, read from the standard IEEE8023-LAG-MIB - see LAG membership below. On by default; set emit_lag_membership: false to opt out.
When a device exposes the relevant MIBs, interfaces also carry their switching configuration: mode (access / tagged / tagged-all / unset for routed), the untagged (access/native) VLAN, and the list of tagged VLANs. VLANs referenced on an interface but not present in the device's VLAN database are auto-emitted as VLAN entities so the association is complete in NetBox; this behavior can be disabled via the create_unknown_vlans option (see below). Auto-emitted stubs use the placeholder name VLAN<vid> (e.g. VLAN42) because NetBox's ipam.vlan.name is required - operators or sibling switches can later overwrite the placeholder via the same vid+group matcher. VLAN discovery uses Q-BRIDGE-MIB (RFC 4363) as the generic source, a Cisco-specific overlay (CISCO-VLAN-MEMBERSHIP-MIB, CISCO-VOICE-VLAN-MIB) on Cisco devices that don't fully implement Q-BRIDGE, a Juniper overlay that resolves internal VLAN indices to real tags on the Junos platforms that use them, and the HUAWEI-VLAN-MIB catalog on Huawei platforms (such as SmartAX OLTs) that implement no Q-BRIDGE-MIB at all - see SNMP Discovery - Supported Platforms for which device classes are covered.
The switchport a membership lands on is the physical port (or aggregate), not the logical unit some platforms index their bridge ports by: on pre-ELS Junos dot1dBasePortIfIndex resolves to xe-0/0/17.0 where the configuration belongs to xe-0/0/17. See Switchport placement for how units are resolved, combined, and when they are left alone.
Note: when a switchport is converted to a routed (L3) interface between discovery cycles, prior mode/untagged-VLAN/tagged-VLAN associations are NOT automatically cleared in NetBox; operators must clear them manually. This is a current limitation of the Diode plugin's PATCH semantics and is tracked separately. The same caveat applies on device-discovery.
Configuration
The snmp_discovery backend uses the diode settings specified in the common subsection to forward discovery results. Optional backend-level settings tune ingest throughput and memory use (see Backend below). Per-policy behavior is configured separately under Policy.
orb:
backends:
snmp_discovery:
ingest_buffer_size: 512
common:
diode:
target: grpc://127.0.0.1:8080/diode
client_id: ${DIODE_CLIENT_ID}
client_secret: ${DIODE_CLIENT_SECRET}
agent_name: agent01Backend
Backend-level settings apply to the snmp_discovery process as a whole (distinct from per-policy config).
| Parameter | Type | Required | Description |
|---|---|---|---|
| ingest_buffer_size | integer | no | Capacity of the buffered queue that serializes Diode ingest calls. Defaults to 512. |
Diode ingest runs through a single-consumer queue so concurrent crawl jobs finishing at once do not trigger concurrent OAuth token refresh storms. Increase ingest_buffer_size when large subnet bursts may enqueue many payloads before the consumer drains them; decrease it if memory is a concern - each queued request retains its entity payload until processed.
Policy
SNMP discovery policies are broken down into two subsections: config and scope.
Config Section
| Parameter | Type | Required | Description |
|---|---|---|---|
| schedule | cron format | no | Cron expression for scheduling (e.g., "*/5 * * * *") |
| timeout | integer | no | Timeout for whole policy in seconds (defaults to 120) |
| snmp_timeout | integer | no | Timeout for SNMP operations in seconds for SNMP operations (defaults to 5) |
| snmp_probe_timeout | integer | no | Timeout for SNMP probe operations in seconds (defaults to 1) |
| retries | integer | no | Number of retries for SNMP operations (defaults to 0) |
| lookup_extensions_dir | string | no | Directory containing device model lookup files |
| defaults | map | no | Default values for entities (description, comments, tags, etc.) |
| options | map | no | Per-policy behavior toggles (see Options Parameters) |
Options Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| create_unknown_vlans | bool | no | Auto-emit a VLAN entity for any VID referenced on an interface but absent from the device's dot1qVlanStaticTable. Stubs inherit attributes from defaults.vlan for stable matching. Defaults to true. Set false to drop unknown VIDs from interface associations entirely (requires every referenced VLAN to already exist in NetBox). |
| discover_asset_tags | bool | no | When true, walks ENTITY-MIB::entPhysicalAssetID and populates each device's asset_tag from its chassis row - standalone devices get the chassis tag; each virtual-chassis member gets its own per-member tag. An operator-supplied defaults.asset_tag (literal or OID reference) always takes precedence on the target device. Values that are empty, non-printable, well-known placeholders (UNKNOWN, N/A, None, 0, …), longer than NetBox's 50-character limit, or duplicated across chassis rows of the same target are skipped with a warning - asset_tag is unique in NetBox and is the highest-precedence device matcher, so a duplicated tag would merge two devices into one record. The same protection applies across targets of one policy: the first target to report a tag owns it for the lifetime of the policy, and other targets reporting the same value (vendor-cloned EEPROM data) are skipped with a warning. Defaults to false - the column is not even walked when off. |
| discover_modules | string | no | Controls emission of Module / ModuleBay entities on modular chassis. One of off (default - no modules emitted, zero behaviour change), linecards (one Module per chassis slot - line cards and supervisors; PSU / fan recognised by the PID classifier but never emitted), or full (linecards plus one Module per transceiver sub-bay; interfaces are linked to the optic or line module holding them when the device populates entAliasMappingTable). Detection is vendor-neutral via ENTITY-MIB::entPhysicalTable - see the supported platforms page. See Modules / ModuleBays for the emission shape and current sub-bay rendering trade-off. |
| discover_vrfs | bool | no | When true, discovers VRFs from the device's VRF MIB tables and attaches them to the IP addresses of each VRF's member interfaces (matched by ifIndex). A discovered VRF takes precedence over the vrf / vrf_ipv4 / vrf_ipv6 defaults for those interfaces; other addresses keep the configured defaults. Defaults to false - the VRF tables are not even walked when off. See VRFs for the MIB tiers, route-distinguisher handling, and limitations. |
| emit_prefixes | bool | no | Derive one Prefix entity per unique (network, VRF) from the discovered IP addresses, matching device-discovery's behavior. Defaults to true - set false to opt out. See Prefixes. |
| emit_host_prefixes | bool | no | Derive a Prefix from IPv4 /32 and IPv6 /128 addresses. Defaults to false (the opposite of emit_prefixes): a host prefix only restates the address, which is already emitted as an IPAddress entity. Set true to derive them anyway, e.g. when loopback /32s are deliberately tracked as prefixes in NetBox. IPv6 link-local prefixes (fe80::/10) are never derived and are unaffected by this option. See Prefixes. |
| emit_prefix_vlan | string | no | Associate a derived Prefix with the VLAN of the SVI-style interface the contributing address lives on. One of off (default) or svi-name. An unrecognized or misspelled value normalizes to off rather than erroring, so a typo disables the feature instead of writing a guess into NetBox. See Prefixes. |
| emit_lag_membership | bool | no | Defaults to true. Read link-aggregation membership from IEEE8023-LAG-MIB::dot3adAggPortTable and set lag on each member port to its aggregate interface. Set false to leave lag unset and skip the table walk. See LAG membership. |
| emit_device_name | bool | no | Defaults to true. Set false to stop emitting Device.name (from sysName) on the matched device, so continual discovery under Assurance does not propose a hostname rename when sysName differs from the NetBox name. sysName is still walked; only the emitted field is suppressed. Intended for use with a target netbox_id / metadata.source_match. Takes effect only when the device is matchable by a field that also travels on its nested references - source_match (netbox_id) or asset_tag; if neither is present the name is kept and a warning is logged, to avoid emitting an unmatchable device. Matching by serial (not unique in NetBox) or primary_ip alone does not enable omission. Does not affect virtual-chassis member names. |
| interface_name_source | string | no | Source for the NetBox interface name. One of auto (default - ifDescr preferred, ifName used when ifDescr is empty or looks like a hardware description; zero behaviour change), ifname (force SNMP ifName), or ifdescr (force ifDescr). Each forced mode falls back to the other field when its primary is empty for an interface. An unrecognized value is warned once and treated as auto. ⚠️ Changing this on an existing deployment renames interfaces - see Interface Name Selection. |
| propagate_defaults_to_prefix_scope | bool | no | When true AND no explicit defaults.prefix.scope_* is set, defaults.site cascades to the Prefix scope site and defaults.location to the scope location (location wins, carrying the site). Defaults to false. Any explicit defaults.prefix.scope_* skips the cascade wholesale. |
Defaults Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| tags | list | no | List of tags to apply to all discovered entities |
| site | string | no | Default site name for discovered devices |
| location | string | no | Default location for discovered devices. Accepts a literal name or an SNMP OID reference (see Default values from SNMP OIDs) |
| rack | string | no | NetBox rack for discovered devices, by name. Always a literal, never an OID reference. In a target's override_defaults it replaces the policy value. See Rack placement |
| position | number | no | The U the target's device sits at in its rack, e.g. 40 or 40.5 for a half U. Only in a target's override_defaults, together with face. See Rack placement |
| face | string | no | The rack face the target's device is mounted on: front or rear, in any case. Only in a target's override_defaults, together with position. See Rack placement |
| asset_tag | string | no | Default asset tag for discovered devices. Accepts a literal value or an SNMP OID reference (see Default values from SNMP OIDs). NetBox enforces a 50-character limit; resolved values longer than 50 characters are warn-logged and skipped |
| role | string | no | Default role for discovered devices |
| stack_member_name_template | string | no | Template for non-master virtual-chassis member device names. Placeholders: {name} (the stack name, from sysName) and {id} (the device-reported member id). Defaults to {name}-{id}, which reproduces the naming emitted before this option existed. See Member naming. |
| tenant | string | map | no | Default tenant for discovered devices. Accepts a bare tenant name or a map with name + optional group / description / comments / tags (see the tenant map below). Applies to Device entities only; IP address, prefix, and VLAN tenants keep their own per-entity defaults (ip_address.tenant, prefix.tenant, vlan.tenant). Virtual-chassis members inherit the master's tenant. In a per-target override_defaults, a tenant with another name, or with a group other than the policy's (including one the policy lacks), replaces the policy's as a whole, keeping the policy's name when the override gives none, so give the group there too; otherwise it refines the policy's field by field |
| interface_patterns | list | no | User-defined interface type patterns (see Interface Type Matching) |
| interface_exclude_patterns | list | no | Regex patterns to exclude interfaces (and their IPs) from ingestion (see Interface Exclusion) |
| vlan_interface_name_prefix | string | no | Sends VLAN interfaces that a switch names by the bare VLAN ID (Cisco small business, Eltex MES 21xx/23xx, UniFi) as this prefix followed by the VLAN ID, for example Vlan (5 -> Vlan5) or vlan (vlan 5). Unset keeps the device's names. ⚠️ Setting, changing or removing it renames interfaces. See VLAN interfaces named by their VLAN ID. |
Nested Defaults
| Parameter | Type | Description |
|---|---|---|
| device | map | Device-specific defaults |
| ├─ description | string | Device description |
| ├─ comments | string | Device comments |
| ├─ model | string | Override the auto-discovered device model (see Device Model Lookup) |
| ├─ manufacturer | string | Override the auto-discovered manufacturer name |
| ├─ platform | string | Override the auto-discovered platform name |
| interface | map | Interface-specific defaults |
| ├─ description | string | Interface description |
| ├─ if_type | string | Interface type (e.g. "1000base-t", "other") |
| ip_address | map | IP address-specific defaults |
| ├─ role | string | IP address role |
| ├─ tenant | string | map | IP address tenant, in the same form as the top-level tenant (see the tenant map below) |
| ├─ description | string | IP address description |
| ├─ vrf | string | map | IP address VRF name, or a VRF map (see the vrf map below). Used for both address families unless an AF-specific override is set. |
| ├─ vrf_ipv4 | string | map | IPv4-specific VRF override (same shape as vrf). When set, IPv4 addresses use this VRF; IPv6 still uses vrf. The override replaces vrf wholesale for its family - it does not inherit vrf.name. |
| ├─ vrf_ipv6 | string | map | IPv6-specific VRF override (same shape as vrf). When set, IPv6 addresses use this VRF; IPv4 still uses vrf. |
| prefix | map | Prefix-specific defaults applied to derived Prefix entities (see Prefixes) |
| ├─ description | string | Prefix description |
| ├─ comments | string | Prefix comments |
| ├─ tags | list | Prefix tags |
| ├─ role | string | Prefix role |
| ├─ tenant | string | map | Prefix tenant, in the same form as the top-level tenant |
| ├─ vrf | string | map | Prefix VRF (same vrf map shape; independent of ip_address.vrf) |
| ├─ vrf_ipv4 | string | map | IPv4-specific prefix VRF override |
| ├─ vrf_ipv6 | string | map | IPv6-specific prefix VRF override |
| ├─ scope_site | string | Prefix scope site. Setting any explicit scope skips the propagate_defaults_to_prefix_scope cascade wholesale. |
| ├─ scope_location | string | Prefix scope location (wins over scope_site on the wire, carrying the site for NetBox's per-site location uniqueness) |
| vrf | map | VRF-specific defaults (used within ip_address and prefix, incl. the vrf_ipv4 / vrf_ipv6 overrides) |
| ├─ name | string | VRF name |
| ├─ rd | string | Route distinguisher (e.g. 65000:100) |
| ├─ tenant | string | map | Tenant the VRF belongs to, in the same form as the top-level tenant. Never taken from another tenant default (see the vrf map below) |
| ├─ description | string | VRF description, written to the VRF on every run |
| ├─ comments | string | VRF comments, written to the VRF on every run |
| ├─ tags | list | VRF tags, added to the VRF's existing tags |
| vlan | map | VLAN-specific defaults |
| ├─ description | string | VLAN description |
| ├─ tags | list | Per-VLAN tags. Merged with the top-level tags list on each emitted VLAN entity, mirroring the device/interface/ip_address defaults pattern. |
| ├─ group | string | map | VLAN group. A bare name attaches every emitted VLAN to an ipam.vlangroup scoped to defaults.site. The map form takes name plus one optional scope: scope_site, scope_site_group, scope_region or scope_location (see the VLAN group map below). In a per-target override_defaults, the group replaces the policy value as a whole |
| ├─ tenant | string | map | VLAN tenant, in the same form as the top-level tenant |
| ├─ status | string | VLAN status override (active, reserved, deprecated). When unset, status is derived from dot1qVlanStaticRowStatus: active(1) → active, notInService(2) → reserved. |
Tenant Map
Every tenant default (the top-level tenant, ip_address.tenant, prefix.tenant, vlan.tenant, and a VRF map's tenant) accepts either a bare string (tenant name) or a map:
| Parameter | Type | Description |
|---|---|---|
| name | string | Tenant name |
| group | string | Tenant group name |
| description | string | Tenant description |
| comments | string | Tenant comments |
| tags | list | Tenant tags |
VRF Map
vrf, vrf_ipv4 and vrf_ipv6 accept either a bare string (VRF name) or a map with the keys in the vrf rows above. Any other key in the map, or in its tenant map, is refused, so a misspelt key such as rd, tenant or group is not silently dropped.
In a per-target override_defaults, a VRF map that names another VRF replaces the policy's as a whole, keeping only the policy's name when it gives none; a tenant it gives is still resolved against the policy VRF's tenant, by the tenant rule below. A policy VRF with neither a name nor an rd is a template that each override refines. Diode finds a VRF with an RD by the RD alone and one without by its name and tenant, so a VRF map names another VRF when it gives another name, an rd other than the policy's (including one the policy lacks), a tenant other than the policy VRF's, or, with no rd on either side, a tenant the policy VRF lacks. Otherwise it refines the policy's field by field, for example a tenant added to a VRF that has an rd. A tenant default names another tenant when it gives another name, or a group other than the policy's (including one the policy lacks), and is then replaced as a whole the same way. A tenant named in another group is only a separate tenant once it exists in NetBox; until then Diode can match the existing one by its slug and move it between groups.
A VRF tenant needs a name, and a VRF map that sets a tenant needs a VRF name in the defaults a target ends up with; otherwise the policy is refused rather than the VRF being sent without its tenant, or dropped.
The ip_address, prefix and top-level tenant defaults do not set the VRF's tenant. Diode matches a VRF without an RD by its name and tenant, so when the VRF belongs to a tenant in NetBox, name that tenant under vrf. Otherwise Diode creates a second VRF with the same name and no tenant.
When the VRF has an RD in NetBox, set rd too. Diode then finds the VRF by its RD alone and writes the policy's VRF name, and tenant when set, onto it, so both must match what NetBox holds.
Every tenant default reaches Diode in full on each run. Diode trims names and, when a tenant's name and group match no tenant, falls back to its slug whatever its group, so names with the same slug, for example ones that differ only in case or accents, or by a space against a hyphen, are one tenant to it. When a VRF's tenant, or an ip_address, prefix or vlan tenant written as a map with more than a name, names the same tenant as another tenant default, write the two identically, for example with a YAML anchor. If they disagree on its name, group, description, comments, tags or the order of its tags, Diode refuses the objects carrying both, rewrites the tenant on every run or creates a second tenant of that name, depending on what NetBox already holds, so such a policy is refused, as is one that writes a tenant group two ways. Each target is checked with the defaults it ends up with, its override_defaults merged in; copies in different targets, or in different policies, are not compared, so keep those consistent yourself. Pairs that could be written before VRF tenants and tenant maps existed, such as a top-level tenant map against a bare ip_address.tenant, are not compared either; a grouped and an ungrouped copy of one tenant can still create a second tenant of that name on an empty NetBox.
If an earlier run already created the extra tenant-less VRF, discovered addresses and prefixes are created again in the tenant's VRF once the policy names its tenant. Reassign or delete the objects left in the extra VRF, then delete that VRF.
VRFs discovered with discover_vrfs carry no tenant, so this applies only to the configured vrf, vrf_ipv4 and vrf_ipv6 defaults.
defaults:
tenant: &owner
name: "Example Tenant GmbH"
group: "Example Group"
ip_address:
tenant: *owner
vrf: &example-vrf
name: "Example VRF"
tenant: *owner
prefix:
vrf: *example-vrfVLAN Group Map
Set a site and a group. Diode matches a VLAN that has no group on its VID alone, so such a VLAN never matches VLANs already scoped to a group in NetBox: ingestion duplicates them, and across several sites the same VID collides on a single record where the last writer's name wins.
vlan.groupalone is not enough - with nodefaults.sitethe group is scoped to the placeholder siteundefined, which cannot match your group of the same name under a real site, so you get a second group and the same duplicates. Set both. The agent logs a warning the first time it sends VLANs it cannot match. Note a site on the VLAN itself is not a substitute for the group: it leaves the duplication untouched, and NetBox has deprecated assigning a VLAN directly to a site.
Diode matches a VLAN group on its name and scope, so the group must be scoped the way it is in NetBox. With a bare name the group is scoped to defaults.site. When VLANs are shared across several sites, scope the group to the site group, region or location that holds them instead:
defaults:
site: "mysite01"
vlan:
group:
name: "Brussels VLAN Group"
scope_site_group: "Brussels"| Parameter | Type | Description |
|---|---|---|
| name | string | VLAN group name (required in the map form) |
| scope_site | string | Scope the group to this site instead of defaults.site |
| scope_site_group | string | Scope the group to a site group |
| scope_region | string | Scope the group to a region |
| scope_location | string | Scope the group to a location. Locations are unique per site in NetBox, so defaults.site is sent with it |
Only one scope_* may be set; a map with none behaves like the bare name. Rack and cluster scopes are not supported.
Scope Section
| Parameter | Type | Required | Description |
|---|---|---|---|
| targets | list | yes | List of SNMP targets to discover. Supports subnets (e.g. 192.168.1.0/28), IP ranges (192.168.0.1-192.168.0.10 or 192.168.0.1-10), and per-target authentication. |
| authentication | map | conditional | Policy-level SNMP authentication settings (required unless all targets have their own authentication) |
Target Parameters
Each target in the targets list can include:
| Parameter | Type | Required | Description |
|---|---|---|---|
| host | string | yes | Target hostname, IP address, subnets or IP ranges |
| port | integer | no | SNMP port (defaults to 161) |
| authentication | map | no | Target-specific authentication (overrides policy-level authentication) |
| override_defaults | map | no | Allows overriding of any defaults for a specific target in the scope. position and face are set here only (see Rack placement) |
| netbox_id | integer | no | NetBox device primary key. When set, the diode plugin matches the device by PK instead of by name. Ignored when host is a subnet or IP range. |
Subnet and range scanning
A subnet excludes its network and broadcast addresses, so 192.168.1.0/24 is
scanned as 254 addresses, .1 through .254. A /31 is a point-to-point link
and a /32 a single host, so neither has a pair to exclude. A range excludes
nothing, because it is an operator enumerating addresses rather than naming a
subnet: 192.168.1.0-255 is 256 addresses, .0 and .255 included.
Each address is probed for reachability before any discovery job is scheduled, and only addresses that answer are discovered.
What the probe puts on the wire. With SNMPv3 the probe presents a placeholder user and does not authenticate, so neither the configured user nor any passphrase reaches an address that has not answered. Presence comes from the credential-free engine discovery exchange the protocol performs before any authenticated request.
With SNMPv1 and SNMPv2c there is no equivalent, and the probe carries the community string to every address in the subnet or range. The community is the credential in those versions and is sent in cleartext, so scanning a range with v1 or v2c puts it in front of anything listening on the scanned port across that range. A conformant agent silently discards a request bearing the wrong community, so there is no substitute value the probe could send instead without turning every device into a false negative. Use SNMPv3 for range scanning where the segment is not trusted, or name targets individually.
Rack placement
rack places discovered devices in a NetBox rack. It is the rack's name, used
as written, and can be set in defaults or in a target's override_defaults.
The rack is sent with the device's site and, when the device has one, its
location, so the device goes into the rack of that name in its own location.
position and face place one target's device at a U of that rack. They are
accepted only in a target's override_defaults, and a policy is refused
unless:
positionandfaceare set together, and the target has a rack, from its ownoverride_defaultsor from the policydefaults.faceisfrontorrear, in any case.positionis 1 or more, in steps of 0.5 (40.5is a half U). Whether it fits the rack's height is left to NetBox.- The target's
hostis a single address or hostname. A range or subnet would place every device it finds at the same U.rackalone is fine on a range or subnet. - No two targets are placed at the same U and face of one rack (the same site,
location and rack name). Two half-depth devices can share a U on opposite
faces. A device sent without a location (none on the target or in the
policy
defaults) clashes with a target at the same U and face of a rack of that name in any location of the site, because a rack sent without a location can bind to any rack of that name in the site. A target whoselocationis an OID reference is left out of this check: its location is only known when the device is scanned. Two targets reaching the same host (one address or name, written as itself, a/32or a one-address range, on the sameport), or with the samenetbox_idor literalasset_tag, update one device, so when they send a rack they must send the same rack, position and face (a rack without a position counts too), and in a literal location, since one read from an OID cannot be compared. Anasset_tagin the policydefaultsreaches every target, so it makes all of them one device; a tag read from an OID is only known at scan time and is not compared. A target is matched by its strongest identifier, in the order Diode matches on (netbox_id, thenasset_tag, then the host), so a shared identifier ties two targets only when it is the strongest of at least one: two targets with differentnetbox_ids are two devices even when they send the same tag or reach the same host. Two targets count as one device at a U only when they share the strongest identifier of both. A target without a rack that shares a device with a racked one must send that target's site and location, or no location: NetBox refuses a device whose rack is in another site or location. Anetbox_idis ignored on any subnet or range syntax, a/32or a one-address range included, so it does not tie such a target to the device with that id.
Quote a numeric rack name (rack: "01"). The agent passes the policy on
through YAML, so an unquoted 01 would arrive as the number 1, and 010 as
8; a rack that is not text is refused rather than guessed at.
config:
defaults:
site: "DC1"
location: "Hall 1"
rack: "R12"
scope:
targets:
- host: "192.0.2.10"
override_defaults:
position: 40
face: "front"
- host: "192.0.2.11"
override_defaults:
rack: "R14"
position: 12.5
face: "rear"
- host: "192.0.2.32/28" # every device found goes in R12, at no particular U
authentication:
protocol_version: "SNMPv2c"
community: "public"How NetBox and Diode handle a placement:
- A rack name that does not exist in the site is created by Diode, like any
other referenced object. Use the exact NetBox name, and set
locationwhen racks in different locations share a name. - When NetBox cannot accept a placement (the U is taken, the device does not fit, or the position is beyond the rack's height), NetBox rejects the device's own record that cycle, and its reason appears in the Diode ingestion logs. Its interfaces and addresses are separate records and still go in.
- A device that is not in NetBox yet, sent to a U another device already occupies, updates that other device, because Diode matches devices by rack, position and face. Make sure the U is free before setting it.
- The position is applied again on every run, so a device moved in NetBox moves back on the next run unless its override is updated.
- On a stack, only the master device is
placed. A stack can span racks, so the other members are sent no rack,
position or face: NetBox keeps whatever it has for them, and a new member is
created without a rack. When a rack is sent, members get no
locationeither, since NetBox refuses a device location that differs from the location of the device's rack. Without a rack, members take the location as before.
Authentication Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| protocol_version | string | yes | SNMP protocol version ("SNMPv1", "SNMPv2c", or "SNMPv3") |
| community | string | yes* | SNMP community string for v1/v2c authentication |
| username | string | no | SNMPv3 username |
| security_level | string | no | SNMPv3 security level ("noAuthNoPriv", "authNoPriv", "authPriv") |
| auth_protocol | string | no | SNMPv3 authentication protocol (see SNMPv3 auth/priv protocols) |
| auth_passphrase | string | no | SNMPv3 authentication passphrase |
| priv_protocol | string | no | SNMPv3 privacy protocol (see SNMPv3 auth/priv protocols) |
| priv_passphrase | string | no | SNMPv3 privacy passphrase |
| context_name | string | no | SNMPv3 context name (equivalent to snmpwalk -n). Required by devices that expose MIB data in a named context; such devices return an empty walk when it is omitted. Rejected for SNMPv1/v2c. |
*Required for SNMPv1/v2c, optional for SNMPv3
Note: Authentication can be specified at the policy level (under scope.authentication) as a fallback, or per-target (under each target's authentication field). Targets without authentication use the policy-level authentication - this is a wholesale replacement, not a field-level merge: a target with its own authentication block does not inherit any individual field, such as context_name, from the policy-level block. Environment variables are supported using ${VAR} syntax for community, username, auth_passphrase, priv_passphrase, and context_name fields.
SNMPv3 auth/priv protocols
Values are case-sensitive and must be passed as one of the strings in the tables below.
auth_protocol:
| Value | Algorithm |
|---|---|
NoAuth | No authentication |
MD5 | HMAC-MD5-96 |
SHA | HMAC-SHA-1-96 (SHA-1) |
SHA224 | HMAC-SHA-224 |
SHA256 | HMAC-SHA-256 |
SHA384 | HMAC-SHA-384 |
SHA512 | HMAC-SHA-512 |
priv_protocol:
| Value | Algorithm |
|---|---|
NoPriv | No privacy |
DES | CBC-DES |
AES | CFB128-AES-128 |
AES192 | CFB128-AES-192 (Blumenthal-draft key localization) |
AES256 | CFB128-AES-256 (Blumenthal-draft key localization) |
AES192C | CFB128-AES-192 (Reeder-draft key localization, Cisco) |
AES256C | CFB128-AES-256 (Reeder-draft key localization, Cisco) |
Note: SHA is SHA-1 and AES is AES-128 - both kept for backward compatibility. For modern deployments, prefer SHA256 (or stronger) and AES256. Use the *C privacy variants when interoperating with Cisco devices that follow the Reeder AES key-localization draft instead of the Blumenthal draft.
Sample
A sample policy including all parameters supported by the SNMP discovery backend.
config:
schedule: "0 */6 * * *" # Cron expression - every 6 hours
timeout: 300 # Timeout for policy in seconds (default 2 minutes)
snmp_timeout: 10 # Timeout for SNMP operations in seconds (default 5 seconds)
retries: 3 # Number of retries
defaults:
tags: ["snmp-discovery", "orb"]
site: "datacenter-01"
location: ".1.3.6.1.2.1.1.6.0" # Resolve from sysLocation (or use a literal like "rack-42")
asset_tag: ".1.3.6.1.2.1.1.4.0" # Resolve from sysContact (or use a literal)
role: "network"
tenant: "network-ops" # Bare name, or a map: { name: network-ops, group: infrastructure }
ip_address:
description: "SNMP discovered IP"
role: "management"
tenant: "network-ops"
# vrf accepts either a bare name (rd left empty) ...
# vrf: "management"
# ... or a map with name + optional rd / tenant / description / comments / tags:
vrf:
name: "management"
rd: "65000:100"
# Per-address-family overrides (optional): the family-specific VRF
# wins for that family's addresses, replacing vrf wholesale.
# vrf_ipv4: "ipv4-vrf"
# vrf_ipv6: { name: "ipv6-vrf", rd: "65000:6" }
interface:
description: "Auto-discovered interface"
if_type: "other"
interface_patterns:
- match: "^(GigabitEthernet|Gi).*"
type: "1000base-t"
- match: "^(TenGigE|Te).*"
type: "10gbase-x-sfpp"
interface_exclude_patterns:
- "^tap.*"
- "^veth.*"
device:
description: "SNMP discovered device"
comments: "Automatically discovered via SNMP"
vlan:
tags: ["snmp-discovery"]
group: "datacenter-01"
tenant: "network-ops"
options:
create_unknown_vlans: true # Default; set false to drop unknown VIDs from interface associations
lookup_extensions_dir: "/opt/orb/snmp-extensions" # Specifies a directory containing device data yaml files (see below)
scope:
targets:
- host: "192.168.1.1/24" # subnet support
- host: "192.168.2.2-10" # range support
- host: "10.0.0.1"
port: 162 # Non-standard SNMP port
netbox_id: 42
override_defaults:
role: "switch"
tags: ["custom"]
rack: "R12" # Rack placement (see above)
position: 40 # position and face: override_defaults only
face: "front"
device:
model: "CCR2004-16G-2S+" # Hard-override auto-discovered model
manufacturer: "MikroTik" # Hard-override auto-discovered manufacturer
platform: "RouterOS" # Hard-override auto-discovered platform
- host: "10.0.0.10"
port: 161
authentication: # Per-target authentication (optional)
protocol_version: "SNMPv3"
security_level: "authPriv"
username: "admin"
auth_protocol: "SHA"
auth_passphrase: "${SNMP_AUTH_PASS}"
priv_protocol: "AES"
priv_passphrase: "${SNMP_PRIV_PASS}"
authentication: # Policy-level authentication (fallback)
protocol_version: "SNMPv2c"
community: "public"Switch stacks / Virtual Chassis
When the target reports 2+ chassis rows in ENTITY-MIB (entPhysicalTable) with non-empty serials, snmp-discovery emits a NetBox VirtualChassis plus one Device per stack member, and routes each interface and IP address to the correct member. Detection is vendor-neutral and driven entirely by entPhysicalClass, entPhysicalContainedIn, and entPhysicalSerialNum; no vendor-specific MIB is required. Standalone switches, devices not in stack mode, and members without a serial fall back to the existing single-Device path with no change in behaviour.
The same chassis row supplies the standalone device's serial. On platforms where ENTITY-MIB yields no chassis serial - no entPhysicalTable at all, or a chassis row with an empty serial - the serial is read from the vendor's chassis-serial scalar (Juniper jnxBoxSerialNo, MikroTik mtxrSerialNumber) instead; a populated standard chassis serial always takes priority. See Device serial.
Topology patterns detected. Two valid ENTITY-MIB shapes are supported:
| Pattern | Chassis row's entPhysicalContainedIn | Used by |
|---|---|---|
| Flat | 0 (chassis rows at the ENTITY-MIB root) | Catalyst 9300/3850 stacks, Aruba CX VSF, Juniper EX/QFX Virtual Chassis, HP/H3C IRF, Huawei iStack, Brocade ICX |
| Wrapped | non-zero, pointing at a entPhysicalClass = 11 (stack) container | Cisco StackWise Virtual on 9400/9500/9600/etc. |
Emission shape (in order):
- Master
Device- plain (novc_position, novirtual_chassisref). Named<sysName>from the SNMP walk; serial taken from the lowest-id chassis row. VirtualChassis- named<sysName>, withmasterset to the inline matcher block of the master Device.- N − 1 member
Deviceentities - each named fromdefaults.stack_member_name_template(see Member naming), carryingvc_position = <memberID>and an inlinevirtual_chassisref pointing to the same matcher block. Per-member serial comes fromentPhysicalSerialNumon the member's chassis row; per-member model comes fromentPhysicalModelNamewhen populated, unlessdefaultsoroverride_defaultssetsdevice.model(see Override precedence), in which case the master and every member carry that model. - Interface / IPAddress entities - routed to the member that physically owns them. Routing uses
entAliasMappingTable(RFC 6933) when present, then falls back to ifName parsing: Cisco IOS/IOS-XE/NX-OS 3-tuple (Gi1/0/1,Te2/1/0/3, etc., including short formsTe/Fo/Hu/Tw/Fi/Twe), Junos FPC, Aruba CX numeric, H3C dashed. Subinterface unit suffixes (Gi2/0/1.100) strip to the parent before parsing.
Member naming. Non-master member names are rendered from defaults.stack_member_name_template, which takes two placeholders: {name} (the stack name, taken from sysName) and {id} (the device-reported member id). The default {name}-{id} reproduces the naming this backend emitted before the option existed, so setting nothing changes nothing.
Set a template when you pre-create member devices under your own convention, e.g. {name}-css{id} to match hand-created core-sw-css1 / core-sw-css2, so discovery updates those records instead of creating new ones. NetBox matches a member by name + site (+ tenant) ahead of virtual_chassis + vc_position, so an aligned name only lands on the pre-created device when its site and tenant already match. There is no numbering offset: the device-reported ids must equal your numbering.
Substitution is single-pass, so a value put in by one placeholder is never itself expanded: a stack whose sysName is the literal {id} renders as {id}-2, not 2-2. A template that is empty, uses an unknown placeholder, omits {name}, leaves an unbalanced brace, or does not vary by {id} is ignored with a warning and the default is used - the policy is never rejected, and the warning is logged once when the policy loads rather than on every scan. Substitution is textual and single-pass, so a stack whose sysName itself looks like a placeholder is treated as data.
Differences from device-discovery. The rules and the default are identical, so the same template string is accepted or rejected the same way in both backends, and both render the same name for any stack whose sysName does not itself contain a placeholder token. Two differences remain.
First, substitution: device-discovery chains two replacements and so re-expands a substituted value, rendering a stack named {id} as 2-2 where this backend renders {id}-2. Single-pass is the correct reading, since otherwise a device's own sysName can inject a placeholder, so the behaviour is not matched.
Second, and more visible, the master is named differently: device-discovery renders every member including the master through the template, while this backend leaves the master named <sysName>. For a two-member stack with the default template, device-discovery emits core-sw-1 / core-sw-2 and this backend emits core-sw / core-sw-2. If one physical stack is discovered by both backends, expect the master to differ.
Member ID derivation. One scheme is chosen for the whole member set, first usable wins. When entPhysicalParentRelPos is populated (> 0) and distinct across members it provides the member id directly; otherwise the trailing integer of entPhysicalName (Switch 2) is used. When neither column can number the members - some stacks report the same position on every chassis row and name them all Chassis - the leading number on each chassis's port descendants is used, reached by walking entPhysicalContainedIn downward (ports are named in the same namespace as ifName, e.g. 2/1/24). That tier is accepted only when every member yields exactly one distinct number, and those numbers are distinct across members and greater than zero; anything else falls through. The final fallback is the ordinal position of the chassis row in the inventory.
Master identity is pinned to the lowest member id present, regardless of live role - this is required because the Diode plugin resolves an existing VirtualChassis via its unique_master matcher, and pinning to the lowest id keeps the master Device stable across live stack-role failovers so re-runs upsert the existing VC instead of creating a new one. The other matcher fields used for VC re-identification (asset_tag, primary_ip4/6, name+site+tenant, and metadata.source_match) are carried consistently on both the rich master Device and the inline VC master ref.
When the device contradicts itself. A signal that is merely absent falls through to the next tier, and the ordinal fallback always yields ids. But a device that reports the same position on two chassis rows, or the same trailing number in two names, has asserted something impossible. If no other tier can resolve such a set, the stack is refused rather than guessed: no VirtualChassis and no member Devices are emitted, since inventing a numbering would put wrong vc_position values and wrong member Device names into NetBox.
The master does still receive a serial in that case, taken from the lowest entPhysicalIndex chassis row. Note this is a different ordering from the lowest-member-id rule above, and necessarily so: a refused set has no member ids to pin to, and the row index is the only ordering that does not depend on the disputed numbering. Only the numbering was ambiguous - each chassis row's serial was unambiguous - so refusing the structure while dropping the serial would discard a fact the device reported plainly.
Member AssetTag is cleared. Diode's highest-precedence matcher for dcim.device is asset_tag (unique). The master Device carries the policy defaults.asset_tag value if configured; member Devices have it explicitly cleared so multiple members do not collapse onto one NetBox row through a shared asset tag. Master / standalone AssetTag behaviour from defaults.asset_tag is unchanged. When the discover_asset_tags option is enabled, members instead receive their own per-row entPhysicalAssetID values - only the operator-supplied defaults tag is never replicated to members.
Orphaned member ports. If a chassis row is dropped from the validated payload (empty serial, duplicate serial collapsed against a lower-id row, etc.) but the device still reports ports owned by that member, those interfaces are skipped with a WARNING rather than routed to master. Routing them to master would silently misattribute member-N ports to a different device - operators see the warning in logs and the missing port in NetBox, not a corrupted port→device mapping.
LAG membership
Link-aggregation membership is read from the standard IEEE8023-LAG-MIB::dot3adAggPortTable (1.2.840.10006.300.43.1.2.1.1), so it is vendor-neutral. Each row is indexed by the member port's ifIndex and names the aggregator by its ifIndex; both resolve to interfaces already discovered from IF-MIB, and the member's Interface.lag is set to the aggregate. dot3adAggPortAttachedAggID (the port's current attachment) is used where the agent publishes it; dot3adAggPortSelectedAggID stands in on agents that publish only the selection column. A value of 0 means the port is not attached and is ignored. Nothing is created: aggregate interfaces (ae0, Port-channel1, …) come from the interface walk as before and must be typed lag (ifType 161) for the reference to be set.
Junos logical units. On Junos the aggregation port the MIB names is the logical unit (xe-0/0/0.0), not the physical port. NetBox does not allow a LAG parent on a virtual interface, so a member that is itself a subinterface is normalised to its physical parent by name - the same derivation used for Interface.parent - and the relationship is emitted once on the physical port: xe-0/0/24.0 → ae120 and xe-0/0/24.1876 → ae120 both become xe-0/0/24 → ae120. Platforms whose members are the physical ports themselves (Cisco, Arista, and most others) are used as-is.
Whether a member is a logical interface is decided on the ifType the device reported for it, not on the interface type emitted to NetBox - that type is resolved from the name first, so any name parsing as a child is typed virtual before ifType is read. A channelized lane (1/1/11:1, et-0/0/0:0) is a member in its own right and keeps its own membership; a member the walk carries no ifType for is left alone for the same reason. The reference is emitted only on an interface that can carry one. A target typed virtual is skipped with a warning, since NetBox refuses the interface outright and that would fail the target's whole ingestion; so is a target typed bridge or lag, which is not a LAG member in practice.
What is refused, with a warning. A member or aggregate ifIndex that is not in the interface walk; an aggregate the mapper did not type as lag; a virtual member whose physical parent is not in the walk, or whose parent name matches more than one interface on the device (a stack repeating a management-port name per member); a member that resolves to its own aggregate; and a physical port whose units name two different aggregates - that is contradictory, so the port is left without a lag rather than picking one. Runs after stack translation, so on a Virtual Chassis both member and aggregate references already name the owning stack member.
VRFs
When the discover_vrfs policy option is enabled (defaults to false), snmp-discovery walks the device's VRF MIB tables and emits a NetBox VRF entity per VRF, attached to the IPAddress entities of the VRF's member interfaces (membership is matched by ifIndex, so no name canonicalization is involved). With the option off, the VRF table columns are not walked at all - zero additional SNMP load.
MIB tiers. Four sources are tried in order until one yields VRFs:
- MPLS-L3VPN-STD-MIB (RFC 4382) - the standards path (
mplsL3VpnVrfTablefor names + route distinguishers,mplsL3VpnIfConfTablefor membership). Implemented by Cisco IOS/IOS-XE/IOS-XR, Juniper, Nokia, Huawei, and others. - MPLS-VPN-MIB (the pre-standard experimental arc) - same table shapes; common on older Cisco IOS.
- CISCO-VRF-MIB - VRF-lite platforms without the MPLS feature MIBs. No route distinguisher is available on this tier.
- JUNIPER-VPN-MIB
jnxVpnIfTable- membership only, for Junos platforms that answer the standard VRF table but not the standard membership table (some EX switches). OnlybgpIpVpnrows are read: the same table lists L2 circuits, L2 VPNs and VPLS instances, which are not VRFs. Rows are joined to the standard VRF by exact name, and the last index component is the memberifIndex.
A tier that exposes VRF names but no membership (split-arc agents) merges membership from the lower tiers; lower tiers never introduce additional VRF names on their own.
Limitation - routing instances that are not VPNs. Every one of these tables models BGP/MPLS VPNs. A routing instance configured as something else, such as a Junos instance-type virtual-router, has no route distinguisher and appears in none of them, so it is not emitted as a VRF and the addresses on its interfaces carry no VRF reference, whatever the interface type. The device publishes nothing over SNMP that could supply it. On platforms where this matters, device-discovery reads routing instances generally over NETCONF and is the complete source for that association; the two backends can run against the same device.
Precedence. A discovered VRF wins over the defaults.ip_address.vrf / vrf_ipv4 / vrf_ipv6 settings for member interfaces' addresses; every other address keeps the configured defaults. The device's primary IP reference is kept consistent with its underlying IP address entity, so NetBox (where IP identity is address + VRF) never sees the same address in two VRF contexts.
Route distinguishers. Both the RFC 4382 8-byte binary encoding (type 0/1/2) and the display-string form some agents return are decoded to the canonical ASN:nn / IP:nn text. Unset or undecodable RDs stay off the wire entirely, so the VRF matches NetBox records whose RD is empty - the same caveat as device-discovery applies: if a VRF with the same name already exists in NetBox with an RD while the device reports none, the first cycle creates a separate RD-less VRF record.
Limitation - VRF-scoped addresses. Some platforms only expose VRF-scoped IP addresses through SNMPv3 contexts or community@vrf conventions; snmp-discovery walks the standard IP-MIB tables in the default context and attaches VRFs to whatever addresses are visible there. Addresses hidden behind per-VRF contexts are not discovered (same as before this feature); per-context walking is a possible follow-up.
Prefixes
Prefix entities are derived from the discovered IP addresses - the network of each address/prefix-length - exactly as device-discovery does. One Prefix is emitted per unique (network, VRF) pair per target. This is on by default (emit_prefixes: true); set emit_prefixes: false in the policy options to opt out.
- VRF: a prefix whose addresses were attached to a discovered VRF (see VRFs) carries that VRF; everything else resolves from
defaults.prefix.vrf/vrf_ipv4/vrf_ipv6(independent of theip_addressknobs). - Scope: explicit
defaults.prefix.scope_site/scope_locationalways win; withpropagate_defaults_to_prefix_scope: trueand no explicit scope,defaults.site/defaults.locationcascade in. - Safety guards: zero-length networks (agent-quirk
0.0.0.0masks) and IPv4-mapped IPv6 addresses never derive prefixes. - Host prefixes and IPv6 link-locals are not derived by default: an IPv4
/32or IPv6/128"prefix" only restates the address, which is already emitted as anIPAddressentity, and anfe80::prefix is per-link rather than globally meaningful. Both are skipped. TheIPAddressentities are untouched, so the addresses stay documented - only the derivedPrefixis dropped. IPv4 link-local (169.254.0.0/16) and the loopback net (127.0.0.0/8) are ordinary networks by mask and are still derived. - Opting back in to host prefixes: set
emit_host_prefixes: trueto derive/32and/128prefixes again, for example when loopback/32s are deliberately tracked as prefixes in NetBox. Note its default (false) is the opposite ofemit_prefixes(true). The opt-in covers host prefixes only: IPv6 link-local prefixes stay suppressed even with it enabled, including anfe80::…/128address, which is a link-local that happens to carry host length rather than a loopback worth tracking. - Where an address's prefix comes from:
ipAdEntNetMaskfor an IPv4 address in the legacyipAddrTable, and theipAddressPrefixpointer intoipAddressPrefixTablefor a row in the modernipAddressTable. A prefix is used only when the device actually reported it: a netmask that is not a contiguous run of ones is refused, as is an all-zero mask (the agent quirk for "no mask configured", which 31 walks in the LibreNMS corpus report) and a pointer whose address family, interface or network disagrees with the row it sits on. Where nothing usable is reported, the address falls back to host length (/32or/128). - When a device fills in both tables: only one row per address is emitted, and the surviving row takes the reported prefix from the one dropped. Roughly one device in six that implements
ipAddressTablereturns a nullipAddressPrefixpointer for every row, so without this its addresses would all be emitted as/32even though itsipAddrTablecarried the real netmask. - IPv6 fallback: where the modern table reports no usable prefix, the deprecated
IPV6-MIBipv6AddrPfxLengthis read if the device answers it. It is believed only when neither current table resolved a prefix, and only where its interface index agrees with the address's own binding, since it is indexed per interface and one address may appear on several. Where the address has no binding to check against, the rows describing it must agree on the length or none is used. Most devices reporting IPv6 do not implement it; Junos is the common case that does. Addresses it lists thatipAddressTableomits (typically link-locals) are not emitted, since the table describes prefixes rather than addresses. - Data-quality note: an agent that reports no usable prefix for an address leaves it at host length. With the default settings those addresses derive no prefix, per the rule above, so a missing prefix table costs prefix coverage rather than filling IPAM with host routes. Enabling
emit_host_prefixeson such a target will produce a host prefix for every address it reports, which is usually not what you want. - VLAN association (
emit_prefix_vlan): when set tosvi-name, a derived prefix carries the VLAN of the SVI-style interface the contributing address lives on, so an address onVlan10associates its prefix with VLAN 10. Defaults tooff. Any unrecognized value normalizes tooffrather than erroring, so a typo disables the feature instead of writing a guess into NetBox. - Where the VLAN names come from: the Q-BRIDGE
dot1qVlanStaticTable, plus the VTP VLAN table (CISCO-VTP-MIB::vtpVlanName) on Cisco devices only. A VID that two VTP management domains name differently is treated as uncorroborated and gets no association: those are different Layer 2 domains, and an SVI naming the VID does not say which one it means. The VTP walk runs only while this option is enabled andemit_prefixesis on, since with no prefixes there is nothing to associate and the walk could only change which VLAN names are emitted. With either off, a Cisco target emits exactly the VLAN entities it emitted before the option existed. - Which interface names qualify: case-insensitively, an optional leading
interface, one ofvlan-interface,vlan id,vlanif,vlan,svi,vl, an optional separator, and a VLAN ID in 1-4094 with leading zeros stripped. SoVlan10,VLAN ID 0051andInterface vlan30all qualify. These are the names the device reports, beforevlan_interface_name_prefixrenames anything. Any name containing a dot is rejected, because the number after the dot is a subinterface index rather than reliably a VLAN ID. On any vendor, an interface also qualifies when itsifIndexis 100000 + VID − 1, itsifNameandifDescrare both exactly the VID, and itsifTypeispropVirtual(53): the layout Eltex MES 21xx/23xx, most Cisco small-business and some UniFi switches use for their VLAN interfaces. A bare number qualifies nowhere else. Switches that reportvlaninifDescrinstead (SGE2010, Dell PowerConnect 2824, Alcatel OmniStack LS) do not qualify, even withinterface_name_source: ifnameandvlan_interface_name_prefixset. - The VLAN must be configured on the device: only a VLAN the device's own VLAN tables report is attached, named or not: a name row, a
dot1qVlanStaticTablerow status, or a vendor catalog row such as Huawei's. A configured VLAN the device leaves unnamed is emitted under the placeholder nameVLAN<vid>, and the prefix points at that same VLAN, so the association sends no name that discovery was not already sending. Withcreate_unknown_vlansoff, such a VLAN is not emitted, so nothing attaches. A VID known only from an interface's VLAN membership (a PVID, a port mask ordot1qVlanCurrentTable) does not qualify: this never stubs a VLAN, and a miss is left unassociated. - Unanimity, and what it cannot cover: an interface is named by both
ifNameandifDescr, and when both parse to a VLAN id they must agree, or the interface contributes nothing. A prefix is then tagged only when every contributing address resolves to the same VLAN. Any disagreement, or a contributing address with no resolvable VLAN, leaves it untagged, and that is logged only when at least one address actually proposed a VLAN. It cannot span devices: if two devices report the same network through different VLANs, the last to report wins. - The association cannot be retracted: the Diode reconciler never diffs a field the payload omits, so a VLAN written onto a prefix cannot later be cleared by discovery, and a manual correction in NetBox is overwritten on the next poll that still finds a unanimous VLAN. This is why the option defaults to
off.
Modules / ModuleBays
When the discover_modules policy option is enabled, snmp-discovery emits NetBox Module and ModuleBay entities for each chassis slot reported in ENTITY-MIB entPhysicalTable (and, in full mode, for each transceiver sub-bay). Discovery is vendor-neutral: rows are selected by entPhysicalClass alone - chassis(3) anchors the device, container(5) rows become module bays, module(9) rows become modules - with PID-prefix classification used only to split modules into supervisor / linecard / transceiver / psu / fan types. The one exception is the built-in port module of fixed-configuration Cisco switches, described below. Any vendor that populates entPhysicalTable per ENTITY-MIB (RFC 6933) is supported; see the supported platforms page for the list known-tested. The option defaults to off so existing operators see zero behaviour change unless they explicitly opt in.
Three modes:
discover_modules | What gets emitted |
|---|---|
off (default) | No module / module-bay entities. Existing behaviour. |
linecards | One ModuleBay + Module per chassis slot (line cards, supervisors). PSU and fan modules are recognised by the PID classifier so they label correctly in metrics, but are never emitted as Module entities - useful when operators care about the slot inventory but not power/cooling FRUs. Transceiver sub-bays are skipped. |
full | linecards plus one extra ModuleBay + Module for every transceiver sub-bay reported by the device. Interfaces are linked to the optic or line module holding them when the device populates entAliasMappingTable (RFC 6933); see Interface.Module routing. |
Module type names. A module's type is named by the first of these that gives a value:
- The module row's
entPhysicalModelName. - A
modules:entry in alookup_extensions_dirfile for the row'sentPhysicalVendorType(see Module names). - On Comware devices (
sysObjectIDunder1.3.6.1.4.1.25506), the part number that ends the row'sentPhysicalDescr, such asJC614AinHP A10500 Main Processing Unit JC614A. Only a last word shaped like an HPE part number is taken. - The row's
entPhysicalVendorType, which is usually an OID, and finallyUnknown.
Emission order (standalone modular chassis): Device → all ModuleBay + Module entries → Interface / IPAddress entries. The order matters because each interface entity may reference the module installed in its bay; emitting modules first lets the Diode reconciler resolve Interface.module against the just-created module.
Virtual-chassis-of-modular (e.g. Cisco StackWise Virtual on Catalyst 9500 / 9600, Catalyst 9300 stack with FRU uplink modules). When a VC member is itself a modular chassis, modules and bays are dispatched per member via the ChassisInventory.Members map: each Module / ModuleBay carries device= set to the member that physically owns the slot, and the Module.module_bay reference points at that member's bay. Master identity follows the same lowest-member-id pinning as the VC envelope itself. The emission order becomes: Device(master) → VirtualChassis → Device(non-master members) → all ModuleBay + Module per member → Interface / IPAddress per member.
Empty bays. A container(5) row with no module(9) child (an Aruba CX 8400 pattern, where empty line-card slots still surface as containers) is emitted as a bare ModuleBay with no installed Module. This faithfully captures the physical chassis surface so operators see populated and empty slots in NetBox.
Chassis-rooted modules. On fixed-FRU switches where a module(9) row's entPhysicalContainedIn chain leads directly to the chassis(3) row without an intermediate container(5) bay, snmp-discovery synthesises a ModuleBay named Slot <ParentRelPos> derived from the module's own entPhysicalParentRelPos. The module is then installed in the synthesised bay, keeping the Device → ModuleBay → Module shape uniform regardless of how the vendor models its inventory tree.
Built-in port modules. A fixed-configuration Cisco switch (sysObjectID under 1.3.6.1.4.1.9) publishes a module(9) row for its own ports, with no model name and no serial: Switch 1 - WS-C2960X-48FPS-L - Fixed Module 0 on a model that stacks, WS-C3560-48TS - Fixed Module 0 on one that does not. That row is the switch rather than a part, so it is not emitted: its ports are linked to no module apart from a transceiver they hold, and transceivers keep their own bays. A row of that shape that reports a model name or a serial is still emitted.
A module that an earlier version created for such a row stays in NetBox, in its Slot N bay and with the ports still linked to it, since discovery never deletes. NetBox deletes a module's interfaces along with the module, and deleting the bay deletes the module, so unlink the interfaces first (bulk-edit their module to none) and only then delete the module or its bay. The module type, typically named after an OID or Unknown, can be deleted once no module uses it.
Interface.Module routing. In full mode an interface is linked to a module through entAliasMappingTable, which maps entity rows to ifIndex; without it no interface is linked. A row that is itself an emitted module, such as a transceiver, links its interface to that module, and any other row, such as an access port, to the nearest emitted module that contains it. When several rows map to one ifIndex, the interface is linked only if their modules lie on one containment chain, and then to the most specific one, so an optic wins over the line card holding its cage. Rows in unrelated modules, such as the management port of each stack member, leave the interface with no module rather than a guessed one. The reference is populated through a bay matcher (Device + Serial + ModuleBay{Name, Position, Device}) so the Diode reconciler resolves to the standalone Module already emitted in the same payload, rather than creating a duplicate inline.
Current sub-bay rendering trade-off (transient). In full mode the transceiver sub-bay is emitted device-rooted - i.e. without a module=parent_linecard link. As a result, NetBox renders the transceiver sub-bay at chassis level (alongside the line-card slot bays) instead of visually nested under its parent line card. The transceiver Module itself is still installed in the sub-bay correctly via Module.module_bay, so per-port optic visibility works as expected; only the bay-under-linecard hierarchy is lost. The link is dropped because, in the current per-entity reconciler, attaching module=parent_linecard on a sub-bay causes the parent Module to be re-created from inside the sub-bay's changeset and conflicts at apply with the line card already created by the prior top-level Module entity. The link will be restored once the reconciler resolves nested parent-module refs against committed sibling entities in a single ingest call.
Metrics. Three OTLP counters cover module discovery operationally: modules_emitted{vendor,type}, module_bays_emitted{vendor}, and modules_dropped{reason} (e.g. PSU/fan filtered from linecards, malformed row, missing parent). PSU and fan rows count in modules_dropped rather than modules_emitted even though their PIDs are recognised by the classifier.
Supported vendors. Module discovery works on any vendor that populates entPhysicalTable per RFC 6933 - see the supported platforms page for the platforms known-tested in v1.
Walking the device
Rows an agent returns out of index order are kept: some agents serve a
Q-BRIDGE or BRIDGE-MIB table with a later index before an earlier one, which
no operator can correct from this side, so the walk does not require
increasing OIDs. Two bounds stand in for the ordering check, and each ends
the table with the rows collected before it, kept and logged as a warning: an
agent delivering an OID it already delivered, and a table reaching 500,000
rows. A table the walk cannot finish for any other reason, the device going
silent or answering with something other than SNMP, fails the target, as it
always did. An SNMP error status ends a table without failing it. The policy's
timeout bounds the whole walk: once it expires, the walk stops at the next
row and the target fails.
Device Model Lookup
The lookup_extensions_dir config option points to a directory of YAML files that map SNMP sysObjectID OIDs to human-readable device model names. Without these files, snmp-discovery would ingest raw OIDs (for example .1.3.6.1.4.1.9.1.489) instead of recognizable model names (for example catalyst2955C12).
For some product lines the device's own chassis row names the model better than the lookup does. Cisco (sysObjectID under enterprise 9), HP ProCurve and ArubaOS-Switch (11.2.3.7), Palo Alto Networks (25461), Arista (30065) and Aruba CX (47196) report the orderable part number in entPhysicalModelName (for example WS-C2960X-48FPD-L rather than catWsC2960x48fpdL), which is what curated device types, such as those in the NetBox device-type library, record. So do FortiGate, FortiWiFi and FortiGate Rugged (12356.101.1), spelled with underscores (FGT_60F, FGR_60F_3G4G), which snmp-discovery rewrites into Fortinet's orderable form (FG-60F, FGR-60F-3G4G), and the D-Link switch families recorded so far (DGS-1510, DGS-3000, DGS-3420, DGS-3620 and DGS-3627, under 171.10.137, 171.10.141, 171.10.133, 171.10.119, 171.10.118 and 171.10.70). Virtual FortiGates, which report their platform rather than hardware, and the FortiGate 6000F, 7000E and 7000F chassis systems, whose one sysObjectID stands for every chassis size, keep the lookup name. A standalone device of those product lines is typed after its chassis row, as every stack member already is, so the same hardware gets one device type whether it is stacked or not. Other product lines, including Aruba wireless (14823), Comware (25506), other HP products and other D-Link families, keep the lookup name, as does any standalone device whose chassis row reports no usable model. Some catalogs record the part number as a device type's part_number rather than its model (for example model FortiGate 60F, part number FG-60F); Diode binds a device to such a type with diode-netbox-plugin 1.18.0 or later. An entry you add to lookup_extensions_dir that changes or adds a model wins over the chassis row for a standalone device; an unchanged copy of a bundled entry does not.
A curated set of vendor lookup files ships with the orb-agent and orb-discovery images (see SNMP Discovery - Supported Platforms), and lookup_extensions_dir only needs to be set when you want to add extra files or override the bundled ones.
File format
Lookup files must have a .yaml or .yml extension and contain a devices, manufacturers or modules section. The devices section is keyed by OID (note the leading .):
devices:
.1.3.6.1.4.1.9.1.1215: ciscoMwr2941DCA
.1.3.6.1.4.1.9.1.489: catalyst2955C12
.1.3.6.1.4.1.9.1.2101: ciscoASR92024TZMOverriding or extending coverage
To add your own OIDs or override a bundled file:
- Identify the
sysObjectIDfor your equipment (typically in vendor MIB files). - Create a YAML file in the format above with OIDs prefixed by
.. - Drop the file into the directory referenced by
lookup_extensions_dir.
The bundled files are always loaded, so the directory only needs the entries you add or change. To start from a bundled file, copy just that one:
git clone https://github.com/netboxlabs/orb-agent.git
cp orb-agent/orb-discovery/snmp-discovery/data/lookup_extensions/cisco.yaml /opt/orb/snmp-extensions/When snmp-discovery encounters a device, it reads the device's sysObjectID, searches the YAML files in lookup_extensions_dir for a match, and falls back to the raw OID when no match is found.
Module names (modules:)
A lookup file can also carry a modules: section that names the module type for an entPhysicalVendorType OID. Use it for modules that report no entPhysicalModelName and whose type would otherwise be named after the OID:
modules:
.1.3.6.1.4.1.25506.3.1.9.4.673: JC614A
.1.3.6.1.4.1.25506.3.1.9.4.680: JC623AAn entry applies only to a module that reports no model name of its own, because one vendor type often stands for several models. An entry for 0.0, the null vendor type that unrelated rows share, is ignored. Module names are read from lookup_extensions_dir only; no bundled file carries them.
Dynamic model resolution (shared sysObjectID)
Some vendors return the same sysObjectID for every model in their catalog (for example MikroTik uses .1.3.6.1.4.1.14988.1 across RouterOS devices), so a single static mapping cannot distinguish the actual model. The devices: map accepts OID references as values: instead of a literal model name, use an OID string (format .1.3.6.1...) that points to another OID already in the SNMP walk. At discovery time, snmp-discovery dereferences the walked value for that OID and uses it as the model name.
devices:
.1.3.6.1.4.1.14988.1: .1.3.6.1.2.1.1.1.0 # MikroTik: resolve model from sysDescr
.1.3.6.1.4.1.14988.2: mikrotikSwOSSwitch # Static literal (unchanged behavior)No extra SNMP traffic is generated - the referenced OID must already be collected by the policy's walk set. sysDescr (.1.3.6.1.2.1.1.1.0) is always walked. If the referenced OID is missing or empty for a given device, snmp-discovery falls back to using the raw sysObjectID. The bundled mikrotik.yaml keeps the historical static mikrotikRouter model string by default for backward compatibility; operators who want per-device MikroTik model names can opt in by adding the override above to their lookup_extensions_dir.
Default values from SNMP OIDs
Selected defaults fields accept either a literal value or an SNMP OID reference of the form .1.3.6.1.…. When the configured value matches the SNMP OID syntax (rooted at .1.3.6.1.), snmp-discovery dereferences the walked value at discovery time and uses that as the field value. Anything else is treated as a literal - including dotted-decimal literals such as "3.14.159" (a room number) or "10.0.0.1", which do not start with the standard SNMP Internet prefix.
| Defaults field | OID-reference supported? |
|---|---|
defaults.location | yes |
defaults.asset_tag | yes |
other defaults.* fields | not yet - literal only |
Any OID in the device-system-group walked snapshot is a valid reference target. The full list available today:
| OID | Name | Type | Typical use |
|---|---|---|---|
.1.3.6.1.2.1.1.1.0 | sysDescr | free text (often long) | Rarely useful as location/asset_tag because values commonly exceed NetBox's 100-char Location.name / 50-char asset_tag limits. |
.1.3.6.1.2.1.1.2.0 | sysObjectID | OID string (e.g. .1.3.6.1.4.1.9.1.1234) | Not a useful default source on its own. |
.1.3.6.1.2.1.1.4.0 | sysContact | free text | Some operators repurpose this as an inventory identifier - point defaults.asset_tag at it. |
.1.3.6.1.2.1.1.5.0 | sysName | free text | Device hostname - point defaults.asset_tag at it when hostname doubles as the inventory tag. |
.1.3.6.1.2.1.1.6.0 | sysLocation | free text | Physical location free-text per RFC 3418 - point defaults.location at it. |
OIDs walked under other mapping groups - most notably ENTITY-MIB rows such as entPhysicalSerialNum (.1.3.6.1.2.1.47.1.1.1.1.11.<row>) - are not reachable: the mapping framework groups walked PDUs per-Map() call, and defaults.location/defaults.asset_tag resolution runs against the device-system-group snapshot only.
defaults:
site: "datacenter-01"
location: ".1.3.6.1.2.1.1.6.0" # Use sysLocation
asset_tag: ".1.3.6.1.2.1.1.4.0" # Use sysContact (note: sysContact is RFC 3418 contact info,
# not an asset tag by default — only opt in if your
# operators have repurposed it for inventory tracking)Resolution rules:
- The OID-reference syntax matches
^\.1\.3\.6\.1\.(\d+\.)+\d+$; everything else stays a literal. - The leading dot is mandatory:
.1.3.6.1.…. A value without it (e.g.1.3.6.1.2.1.1.6.0) is treated as a literal and used verbatim - it will not be dereferenced. - A configured OID reference whose walked value is missing or empty leaves the field unset for that device - no fallback to a literal.
defaults.asset_tagis capped at NetBox's 50-character limit; longer resolved values are warn-logged and skipped (rather than truncated) to avoid silent asset-tag uniqueness collisions.defaults.locationresolved via an OID reference will create (or match) a NetBoxLocationobject named after the resolved string, scoped todefaults.site. Free-textsysLocationvalues can therefore produce messy Location objects ("Front Door", vendor defaults, etc.) - curate your fleet before enabling this in production. Resolved values longer than NetBox's 100-charLocation.namelimit will be rejected at upsert time.
entPhysicalAssetID (ENTITY-MIB .1.3.6.1.2.1.47.1.1.1.1.15) is the standards-aligned asset-tag source, but it is a table column requiring chassis-row selection (entPhysicalClass = chassis(3)). It is not yet reachable from this defaults.asset_tag mechanism and is tracked as a follow-up.
Manufacturer overrides
Manufacturers are derived from the Private Enterprise Number (PEN) segment of sysObjectID against a mechanically generated IANA catalog, which produces strings such as ciscoSystems or Aruba a Hewlett Packard Enterprise company. For NetBox deployments that already hold Cisco Systems / Aruba Manufacturer objects, any lookup-extension YAML file may also include a manufacturers: block keyed by IANA PEN:
manufacturers:
9: Cisco Systems # PEN 9 (Cisco)
14823: Aruba # PEN 14823 (Aruba/HPE)
devices:
.1.3.6.1.4.1.9.1.2495: c9300-48pOverrides are layered - a value from lookup_extensions_dir wins over a value from the bundled files, which wins over the raw IANA name. The built-in manufacturers.yaml remains IANA-sourced; renames are strictly opt-in via lookup-extension files.
Override precedence
When multiple sources can supply a device's manufacturer, model, or platform, the highest-priority non-empty value wins:
- Per-target
override_defaults.device.{model,manufacturer,platform}, then the policy'sdefaults.device.{model,manufacturer,platform}(hard overrides). A model set here is carried by a stack's master and every member, and chassis rows never replace it. On a device that answers nosysObjectID, a model and manufacturer set here together are its device type. - User
lookup_extensions_dir/*.yaml(manufacturers:anddevices:including dynamic refs). For the model, only an entry that changes or adds one counts, and a stack member's own chassis model (level 3) still wins over it, as it always has. - The chassis row's
entPhysicalModelName(model only). Every stack member takes any non-empty value. A standalone device of the product lines listed in Device Model Lookup takes it unless it is empty, a placeholder, unprintable or over-long, or the device reports more than one chassis row, including rows refused as ambiguous or reporting no serial. A sole chassis row with no serial still names the model. - Bundled
lookup_extensions/*.yaml(manufacturers:anddevices:) - Raw IANA manufacturer name / raw
sysObjectIDmodel
Netprobe
The netprobe input is the one input that generates traffic rather than observing it. Instead of watching packets on an interface, it runs an active test agai...
SNMP Discovery - Interface Type Matching
The SNMP discovery backend uses an intelligent six-tier priority system to automatically determine interface types. This system provides zero-configuration s...