Device Discovery
The device discovery backend leverages NAPALM to connect to network devices and collect network information.
Supported Platforms
Device discovery supports 48 NAPALM drivers covering 17+ network vendors out of the box:
- 7 standard NAPALM drivers - Arista EOS, Cisco IOS/IOS-XE, IOS-XR, NX-OS, and Juniper Junos. These are tried automatically during driver auto-discovery.
- 41 custom drivers bundled with the backend - extending coverage to Palo Alto Networks, Fortinet, Check Point, Huawei, Nokia, HPE Aruba, Extreme Networks, Dell, NVIDIA (Cumulus/Mellanox), MikroTik, Ubiquiti, Ruckus/Brocade, Ciena, Ericsson, and more. No extra installation is needed - select them with
driver:on a scope entry or opt them into auto-discovery via thediscovery_driversoption.
For the full driver list with vendor and platform details - including which drivers support VLAN associations, switch stacks / Virtual Chassis, and module discovery - see Device Discovery - Supported Platforms.
Diode Entities
The device discovery backend uses Diode Python SDK to ingest the following entities:
- Device
- Interface
- Device Type
- Platform
- Manufacturer
- Site
- Role
- IP Address
- Prefix
- VLAN
- VRF
- VirtualChassis
- Module
- ModuleBay
Interfaces are attached to the device and ip addresses will be attached to the interfaces. Prefixes are added to the same interface site that it belongs to. Host prefixes (IPv4 /32, IPv6 /128) and IPv6 link-locals (fe80::/10) are not derived as prefixes by default; host prefixes can be restored with the emit_host_prefixes option - see Prefix.
When a target is a switch stack / Virtual Chassis (NetBox VirtualChassis), device-discovery emits one VirtualChassis entity plus one Device per member, and routes each interface/IP to the member that physically owns it - see Switch stacks / Virtual Chassis below. Drivers that do not implement stack discovery (or devices not in stack mode) fall through to the existing single-Device path with no change in behaviour.
When the discover_modules policy option is enabled, device-discovery additionally emits Module and ModuleBay entities for each chassis slot on a modular target (and, in full mode, each transceiver sub-bay), plus, on the seven drivers that implement it, a device-rooted ModuleBay for any optic that has no slot, linecard or FRU module above it - the whole inventory on a fixed-port target, or just the fixed ports on a chassis whose only module is an uplink - see Modules / ModuleBays below. Defaults to off, so existing operators see zero behaviour change.
When the discover_vrfs policy option is enabled, device-discovery emits a VRF entity for each VRF configured on the device and attaches it to the IP addresses and prefixes of the interfaces inside that VRF - see VRFs below. Defaults to false, so existing operators see zero behaviour change.
On drivers that report link-aggregation membership, interfaces that are members of a link aggregation carry a lag reference to their aggregate interface - see LAG membership below. On by default; set emit_lag_membership: false to opt out.
Configuration
The device_discovery backend does not require any special configuration, though overriding host, port and log_level values can be specified. The backend will use the diode settings specified in the common subsection to forward discovery results.
orb:
backends:
common:
diode:
target: grpc://192.168.0.100:8080/diode
client_id: ${DIODE_CLIENT_ID}
client_secret: ${DIODE_CLIENT_SECRET}
agent_name: agent01
device_discovery:
host: 192.168.5.11 # default localhost
port: 8857 # default 8072
log_level: ERROR # default INFO
| Parameter | Type | Required | Description |
|---|---|---|---|
| host | str | no | REST API host (default localhost) |
| port | int | no | REST API port (default 8072) |
| log_level | str | no | Log level (default INFO) - see Log level and troubleshooting |
Log level and troubleshooting
log_level accepts trace, debug, info, warn/warning, error/err/exception
and critical/fatal, case-insensitively. The default is INFO. An unrecognised value
falls back to INFO and logs a warning rather than failing to start.
Precedence, highest first: an explicit log_level, then debug: true on the backend,
then the agent already running at debug (-d or orb.debug.enable).
A host that is reachable but not a manageable device logs exactly one record. When a target answers on the scanned port but cannot be logged into, device-discovery emits a single WARNING per target per run, with no traceback:
WARNING:device_discovery.policy.runner:Policy my_policy, Hostname 10.0.0.5: Cannot connect to 10.0.0.5Stable phrases to alert or grep on: Cannot connect to and Authentication to device failed.
A sweep over unreachable address space produces one line per host, not one per traceback frame.
Tracebacks for these expected failures are emitted only at DEBUG, on a single line with
newlines escaped as \n. To read one back:
printf '%b\n' "<the traceback: value>"log_level: DEBUG alone is not enough to see debug output. The agent maps the
backend's DEBUG: lines to its own debug level and its root handler drops them at Info.
Use orb.debug.enable or -d as the single troubleshooting switch; use log_level only
to go quieter than INFO. Note that DEBUG also raises napalm, netmiko, paramiko and
ncclient verbosity considerably (ncclient dumps multi-line XML), so point it at one
target rather than a subnet.
Policy
Device discovery policies are broken down into two subsections: config and scope.
Config
Config defines data for the whole scope and is optional overall.
| Parameter | Type | Required | Description |
|---|---|---|---|
| schedule | cron format | no | If defined, it will execute scope following cron schedule time. If not defined, it will execute scope only once |
| defaults | map | no | key value pair that defines default values |
| options | map | no | key value pair that defines config options |
Options
Current supported options:
| Key | Type | Description |
|---|---|---|
| platform_omit_version | bool | If True, only the driver name will be used as the NetBox platform name (defaults to 'False' if not specified) |
| port_scan_ports | list | TCP ports to probe before discovery if hostname is a IP Range or a Subnet (defaults to [22,23,80,443,830,57400]) |
| port_scan_timeout | float | TCP port probe timeout in seconds (defaults to 0.5) |
| capture_running_config | bool | If True, collects the running configuration from the device and ingests it as a DeviceConfig entity (defaults to 'False' if not specified) |
| capture_startup_config | bool | If True, collects the startup/saved configuration from the device and ingests it as a DeviceConfig entity (defaults to 'False' if not specified) |
| sanitize_config | bool | If False, captured configuration is stored as-is without redacting sensitive values such as passwords and pre-shared keys (defaults to 'True' if not specified) |
| discovery_drivers | list | Restrict auto-discovery to this ordered list of driver names (e.g. [paloalto_panos, huawei_vrp]). Only used when a scope entry has no driver set. If not specified, only standard NAPALM drivers are tried. Custom drivers (paloalto_panos, paloalto_panos_ssh, huawei_vrp) must be listed explicitly to be used in auto-discovery. See the supported platforms page for the full list. |
| create_unknown_vlans | bool | When discovering interface↔VLAN associations, auto-emit a VLAN entity for any VID referenced on an interface but absent from the device's VLAN database. 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). Only drivers that implement get_interfaces_vlans() populate these associations - see the supported platforms page. |
| discover_modules | str | Controls emission of Module / ModuleBay entities. One of off (default - no modules emitted, zero behaviour change), linecards (one Module per chassis slot on a modular target - line cards, supervisors, etc.; transceiver sub-bays and device-rooted optics both skipped), or full (linecards plus one Module per transceiver sub-bay or device-rooted optic; interfaces carry a module= ref to the transceiver they're connected to). An optic with no slot, linecard or FRU module above it - the whole inventory on a fixed-port target, or the fixed ports on a chassis whose only module is an uplink - is discovered too, as a device-rooted ModuleBay. Only drivers that implement get_modules() populate module data - see the supported platforms page. See Modules / ModuleBays for the emission shape and current sub-bay rendering trade-off. |
| propagate_defaults_to_prefix_scope | bool | When True AND no explicit defaults.prefix.scope_* is set, defaults.site cascades to Prefix.scope_site (the literal placeholder "undefined" is skipped) and defaults.location cascades to Prefix.scope_location. Defaults to False. Setting any explicit defaults.prefix.scope_* puts the operator in "explicit mode" and the cascade is skipped wholesale. |
| discover_vrfs | bool | When True, discovers VRFs from the device via the driver's get_network_instances() and attaches each VRF to the IP addresses and prefixes of its member interfaces. A discovered VRF takes precedence over the defaults.*.vrf / vrf_ipv4 / vrf_ipv6 settings for those interfaces; interfaces in the default routing table keep the configured defaults. Defaults to False. Only drivers that implement get_network_instances() populate VRF data - see the supported platforms page. See VRFs for filtering rules and route-distinguisher handling. |
| emit_lag_membership | bool | Defaults to true. Set lag on each link-aggregation member port to its aggregate interface, from the driver's get_interfaces_lag(). Set false to leave lag unset and skip the driver call. Only drivers that implement get_interfaces_lag() report membership - see the supported platforms page. See LAG membership. Mirrors the snmp-discovery option of the same name. |
| emit_host_prefixes | bool | Derive a Prefix from IPv4 /32 and IPv6 /128 addresses. Defaults to False: a host prefix only restates the address, which is already emitted as an IPAddress entity, so no prefix is derived for them. Set True to restore them, 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 Prefix. |
| emit_prefix_vlan | str | Associate a derived Prefix with the VLAN of the interface the contributing address lives on: the VLAN ID the device reports for that interface where the driver supports it, else the one an SVI-style name states. One of off (default) or svi-name. Any scalar is read as its text, so a bare on, a number and a mistyped mode all resolve to off with a warning rather than erroring; a list or mapping is a policy error, as it is for snmp-discovery. See Prefix. |
| emit_device_name | bool | Emit Device.name from the discovered device name - the hostname fact, or the fqdn fact under device_name_source: fqdn. Defaults to True. Set False to suppress the name on the matched device so continual discovery stops proposing a hostname rename when the discovered hostname differs from the NetBox name. Only takes effect when the device is matchable another way - a scope netbox_id, or defaults.device.asset_tag; otherwise the name is kept and a warning is logged, because name is a primary NetBox device matcher and dropping it unguarded would emit a device NetBox cannot resolve. Matching by serial alone does not qualify (Device.serial is not unique in NetBox). On a virtual-chassis stack only the master's name is suppressed; member names come from stack_member_name_template. Mirrors the snmp-discovery option of the same name. |
| device_name_source | string | Fact used for Device.name: hostname (default) or fqdn. With fqdn, the fqdn fact is used only when it positively looks like a domain-qualified form of the hostname - no whitespace and, case-insensitively, the hostname followed by a dot and at least one more character. Anything else falls back to the hostname: placeholders such as None (junos), Unknown (ios family), N/A (paloalto) or ios's <hostname>.not set, an fqdn equal to the hostname, or a hostname that already contains a dot (several drivers blindly append the domain again, producing rtr1.dc1.example.net.dc1.example.net). Does not apply to virtual-chassis stacks: every member's name, the master's included, comes from stack_member_name_template. Note: Diode matches devices by name, so switching an existing deployment to fqdn creates new records unless the NetBox devices are renamed first. An unrecognized value logs a warning and resolves to hostname. |
Modules the device cannot identify
Some devices report a module with a serial number and a description but no part
number. A Cisco C9200L does this for DAC cables, and a 2960S reports the literal
placeholder Unspecified for an SFP whose vendor coding it does not recognise.
These are recorded rather than dropped.
The optic's own EEPROM is asked first (ios only). show inventory has no
manufacturer field at all, but an SFP carries its vendor and part number in its
EEPROM regardless of whether the switch recognises the coding, so
show idprom interface <ifname> is run for each transceiver the inventory could
not name. Where it answers with both, they become the module's manufacturer and
model - a real part such as CISCO-FINISAR / FTRJ8519P1BNL-C3 rather than a
generic bucket. The cost is one command per unnamed optic, not per port, so a
switch whose optics are all recognised issues none; the command is unavailable
on some platforms and images, and a failure is not fatal.
Otherwise the description is used as the model, and the manufacturer is set
to Unknown rather than the switch's own vendor, because the device has not
told us who made the part and reporting Unspecified usually means it is not
the chassis vendor's. Filing them under Unknown also keeps them findable:
every module NetBox holds only a description for can be listed with a single
manufacturer filter.
A part that names its own manufacturer is filed under that manufacturer even
when the inventory row was unidentified. dcim.moduletype matches on
(manufacturer, model), so a third-party optic filed under the chassis vendor
would put a part that vendor did not make into their catalog.
A module with a serial but neither a part number nor a description is skipped, since NetBox requires a model and there would be nothing to call it.
Manual corrections do not survive. NetBox matches a Module by the module bay it occupies, so if you correct one of these by hand and discovery runs again, your edit is overwritten. This applies to any discovered module, but it matters most here, where the model is a description rather than a part number.
Currently applies to the ios driver. Other drivers still skip modules without
a part number.
Defaults
Current supported defaults:
| Key | Type | Description |
|---|---|---|
| site | str | NetBox Site Name (defaults to 'undefined' if not specified) |
| role | str | Device role (e.g., switch) (defaults to 'undefined' if not specified) |
| if_type | str | Default interface type when no pattern matches (defaults to 'other' if not specified) |
| interface_patterns | list | User-defined interface type patterns (see Interface Type Matching) |
| interface_exclude_patterns | list | Regex patterns to exclude interfaces (and their IPs) from ingestion (see Interface Exclusion) |
| location | str | Device location |
| rack | str | Rack name to place the device in. See Rack placement |
| position | number | Rack U position, a whole or half U from 1. Per device only, in a target's override_defaults, with face and a rack. See Rack placement |
| face | str | Rack face, front or rear. Per device only, in a target's override_defaults, with position |
| stack_member_name_template | str | Template for stack / Virtual Chassis member device names. Placeholders: {name} (the stack name) and {id} (the device-reported member id). Defaults to {name}-{id}, which reproduces the legacy naming. See Switch stacks / Virtual Chassis. |
| tenant | str/map | Device tenant |
| description | str | General description |
| comments | str | General comments |
| tags | list | List of tags |
Nested Defaults
| Key | Type | Description |
|---|---|---|
| device | map | Device-specific defaults |
| ├─ model | str | Device type model (overrides the model automatically retrieved from NAPALM) |
| ├─ manufacturer | str | Device manufacturer (overrides the vendor automatically retrieved from NAPALM) |
| ├─ platform | str | Device platform (overrides the defined/discovered NAPALM driver name and OS version) |
| ├─ description | str | Device description |
| ├─ comments | str | Device comments |
| ├─ tags | list | Device tags |
| ├─ asset_tag | str | Device asset tag |
| tenant | map | Tenant-specific defaults. In a target's override_defaults, a different tenant replaces the policy's as a whole (see VRF tenant) |
| ├─ name | str | Tenant name |
| ├─ group | str | Tenant group |
| ├─ description | str | Tenant description |
| ├─ tags | list | Tenant tags |
| interface | map | Interface-specific defaults |
| ├─ description | str | Interface description |
| ├─ tags | list | Interface tags |
| ipaddress | map | IP address-specific defaults |
| ├─ role | str | IP address role |
| ├─ tenant | str | IP address tenant |
| ├─ vrf | str/map | IP address VRF name, or VRF object with route distinguisher. Used for both address families unless an AF-specific override below is set. |
| ├─ vrf_ipv4 | str/map | IPv4-specific VRF override (same shape as vrf). When set, IPv4 IP addresses are emitted with this VRF; IPv6 still uses vrf. |
| ├─ vrf_ipv6 | str/map | IPv6-specific VRF override (same shape as vrf). When set, IPv6 IP addresses are emitted with this VRF; IPv4 still uses vrf. |
| ├─ description | str | IP address description |
| ├─ comments | str | IP address comments |
| ├─ tags | list | IP address tags |
| prefix | map | Prefix-specific defaults |
| ├─ role | str | Prefix role |
| ├─ tenant | str | Prefix tenant |
| ├─ vrf | str/map | Prefix VRF name, or VRF object with route distinguisher. Used for both address families unless an AF-specific override below is set. |
| ├─ vrf_ipv4 | str/map | IPv4-specific VRF override for prefixes (same shape as vrf). When set, IPv4 prefixes use this VRF; IPv6 still uses vrf. |
| ├─ vrf_ipv6 | str/map | IPv6-specific VRF override for prefixes (same shape as vrf). When set, IPv6 prefixes use this VRF; IPv4 still uses vrf. |
| ├─ description | str | Prefix description |
| ├─ comments | str | Prefix comments |
| ├─ tags | list | Prefix tags |
| ├─ scope_site | str | Prefix scope_site (see Prefix for the oneof precedence rule and cascade behaviour) |
| ├─ scope_location | str | Prefix scope_location |
| vrf | map | VRF-specific defaults (used within ipaddress and prefix) |
| ├─ name | str | VRF name |
| ├─ rd | str | Route distinguisher (e.g. 65000:100) |
| ├─ tenant | str/map | Tenant the VRF belongs to: a name, or a map with name and optional group / description / comments / tags. Never taken from the address or prefix tenant (see VRF tenant) |
| ├─ description | str | VRF description, written to the VRF on every run |
| ├─ comments | str | 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 |
| ├─ group | str/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 VLAN group below). In a per-device override_defaults, the group replaces the policy value as a whole |
| ├─ tenant | str | VLAN tenant |
| ├─ role | str | VLAN role |
| ├─ description | str | VLAN description |
| ├─ comments | str | VLAN comments |
| ├─ tags | list | VLAN tags |
VRF tenant
A VRF map accepts only the keys above, and its tenant map only name, group, description, comments and tags. Any other key is refused, so a misspelt key such as rd, tenant or group is not silently dropped.
The address and prefix 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, a device carrying its own and, through its primary address, that address's and its VRF's. 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 and another tenant default (tenant, ipaddress.tenant, prefix.tenant, vlan.tenant or another VRF's tenant) name the same tenant, write them 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 or rewrites the tenant on every run, so such a policy is refused, as is one that writes a tenant group two ways. Each target's merged override_defaults is checked too; copies in different targets, or in different policies, are not compared, so keep those consistent yourself.
In a per-target override_defaults, a tenant or VRF that differs from the policy's replaces it as a whole, keeping only the policy's name when it gives none:
- a tenant differs when its name differs, or when it gives a group other than the policy's, including one the policy lacks;
- a VRF differs when its name differs, when it gives an
rdother than the policy's (including one the policy lacks), when both give a tenant and the tenants differ, or, with nordon either side, when it gives a tenant the policy lacks. Diode finds a VRF with an RD by the RD alone and one without by its name and tenant, so each of these is another VRF.
Otherwise the override refines the policy's field by field, for example a tenant added to a VRF that has an rd, and a bare name equal to the policy's, or a blank one, leaves it as it is. This applies to every tenant default, not only VRF tenants. 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.
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:
ipaddress:
vrf: &example-vrf
name: "Example VRF"
tenant:
name: "Example Tenant GmbH"
group: "Example Group"
prefix:
vrf: *example-vrfVLAN group
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 | str | VLAN group name (required in the map form) |
| scope_site | str | Scope the group to this site instead of defaults.site |
| scope_site_group | str | Scope the group to a site group |
| scope_region | str | Scope the group to a region |
| scope_location | str | 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
The scope defines a list of devices that can be accessed and pulled data.
| Parameter | Type | Required | Description |
|---|---|---|---|
| hostname | string | yes | Device hostname. It also supports subnets (e.g. 192.168.1.0/28) and IP ranges in the format 192.168.0.1-192.168.0.10 or 192.168.0.1-10. |
| username | string | yes | Device username |
| password | string | yes | Device username's password |
| driver | string | no | If defined, connect using the specified NAPALM driver. If not set, all installed drivers are tried (or the discovery_drivers list if configured). |
| optional_args | map | no | NAPALM optional arguments defined here. Commonly used: ssh_config_file for jumphost support (see SSH Configuration guide), canonical_int for interface naming, timeout for slow connections. |
| override_defaults | map | no | Allows overriding of any defaults for a specific device in the scope. Fields merge with the policy's, except that a VLAN group the override sets, and a tenant or VRF it names differently, replace the policy's as a whole (see VRF tenant) |
| netbox_id | integer | no | NetBox device primary key. When set, the diode plugin matches the device by PK instead of by name. Ignored when hostname is a subnet or IP range. |
Subnet and range expansion
A CIDR excludes its network and broadcast addresses, so 10.0.0.0/24 is 254
addresses and 10.0.0.0/22 is 1022. A /31 and a /32 have no such pair to
exclude and stay 2 and 1. IPv6 has no broadcast, so only the network address is
excluded: fd00::/126 is 3. A range excludes nothing, because someone who
wrote 0 and 255 meant them, so 10.0.0.0-255 is 256.
A policy may expand to at most 65536 addresses in total, counted across all
its scope entries before any expansion happens. The budget spans the policy
rather than one entry, so sixteen /16 scopes are refused together even though
each is under the limit on its own. A policy over the budget is rejected when it
is written, naming the total and the limit:
policy scopes expand to 1048544 addresses in total, more than the limit of 65536A single entry over the limit is also refused at expansion time as a backstop: it is skipped, named in an error, and recorded as a failed run, leaving the rest of the policy unaffected.
Rack placement
rack places the device in a NetBox rack, policy-wide in defaults or per device in override_defaults. The rack is sent with the device's site and, when location is set, its location. A device can also be given a U position and a face, but only per device, in a target's override_defaults:
scope:
- hostname: 192.0.2.10
username: admin
password: ${PASS}
override_defaults:
rack: R12
position: 40
face: frontpositionis a U from 1, in steps of 0.5 (40.5is a half U).faceisfrontorrear. They are set together, and the target needs a rack, its own or the policy's. A target'srack: ""keeps its device out of the policy's rack.- Quote a numeric rack name (
rack: "01"). YAML reads an unquoted01as the number 1 and010as 8, so a number is refused rather than guessed at. - A target with
netbox_idneeds asite(its own or the policy's) to setpositionandface: without one no site is sent, and the rack could not be looked up. Two targets reaching the same host (one address or name, written as itself, a/32or a one-address range, on the sameoptional_args.port), or with the samenetbox_idordevice.asset_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). An asset tag in the policydefaultsreaches every target, so it makes all of them one device. A target is matched by its strongest identifier, in the order Diode matches on (netbox_id, thendevice.asset_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 none: 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. - A policy is refused when its
defaultssetpositionorface, when a target whosehostnameexpands to more than one address (a subnet or range) sets them, or when two targets are placed at the same rack, U and face in one site. A device sent without a location (none on the target or in the policydefaults) counts as any location, since its rack is matched by name across the site. - On a switch stack, only the master, which is the lowest member id (see Switch stacks / Virtual Chassis), 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. This also applies to a rack set in the policydefaults. - Without
position, no position is sent, so NetBox keeps whatever it has.
Placement follows Diode's rules:
- A rack name that doesn't exist in the site is created, like any other referenced object. Use the exact NetBox name, and set
locationwhen racks in different locations share a name. - A placement NetBox can't accept (the U is taken, the device doesn't 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 isn't in NetBox yet, sent to a U another device already occupies, updates that other device, because Diode also matches devices by rack, position and face. Make sure the U is free before setting it.
- The position is applied on every run, so a device moved in NetBox moves back unless its
override_defaultsentry changes.
SSH Configuration and Jumphost Support
For advanced SSH scenarios including bastion/jumphost connectivity, VRF-aware connections, and multi-hop SSH configurations, see the dedicated guide: SSH Configuration and Jumphost Support.
The ssh_config_file optional argument allows you to specify a standard OpenSSH configuration file for connecting to devices through intermediate jump servers:
scope:
- driver: ios
hostname: 192.168.10.5
username: admin
password: ${DEVICE_PASS}
optional_args:
ssh_config_file: /opt/orb/ssh-napalm.confSee the SSH Configuration guide for complete examples, security best practices, and troubleshooting.
Sample
A sample policy including all parameters supported by the device discovery backend.
orb:
...
policies:
device_discovery:
discovery_1:
config:
schedule: "* * * * *"
defaults:
site: New York NY
role: switch
if_type: other
interface_patterns:
- match: "^(GigabitEthernet|Gi).*"
type: "1000base-t"
- match: "^(TenGig|Te).*"
type: "10gbase-x-sfpp"
- match: "^Loopback.*"
type: "virtual"
interface_exclude_patterns:
- "^tap.*"
- "^veth.*"
location: Row A
rack: Rack-01
tenant: NetBox Labs
description: for all
comments: comment all
tags: [tag1, tag2]
device:
model: C9200-48P
manufacturer: Cisco
asset_tag: ASSET-001
description: device description
comments: this device
tags: [tag3, tag4]
interface:
description: interface description
tags: [tag5]
ipaddress:
description: my ip
comments: my comment
tags: [tag6]
vrf:
name: VRF-A
rd: "65000:100"
prefix:
description:
comments:
tags: [tag7]
vrf:
name: VRF-A
rd: "65000:100"
vlan:
role: role
scope:
- driver: ios
hostname: 192.168.0.5
username: admin
password: ${PASS}
optional_args:
canonical_int: True
ssh_config_file: /opt/orb/ssh-napalm.conf
- hostname: myhost.com
username: remote
password: 12345
netbox_id: 42
override_defaults:
role: router
location: Row BCustom Driver Discovery Example
Use discovery_drivers to limit auto-discovery to a specific set of drivers. This is useful when you know the device type in advance or when using custom NAPALM drivers shipped with device-discovery (paloalto_panos, paloalto_panos_ssh, huawei_vrp).
orb:
...
policies:
device_discovery:
panos_discovery:
config:
schedule: "0 * * * *"
options:
discovery_drivers:
- paloalto_panos
- paloalto_panos_ssh
defaults:
site: DC1
scope:
- hostname: 192.168.10.20
username: admin
password: ${PANOS_PASS}In this example, only the paloalto_panos and paloalto_panos_ssh drivers are tried during auto-discovery for devices in this policy. If you set driver explicitly on a scope entry, discovery_drivers is ignored for that entry.
Advanced Sample
You can reuse credentials across multiple devices in the scope section by using YAML anchors (&) and aliases (<<). This reduces redundancy and simplifies configuration management.
orb:
...
policies:
device_discovery:
discovery_1:
credentials: &ios_credentials
username: admin
password: ${PASS}
driver: ios
config:
defaults:
site: my site
tenant: my tenant
scope:
- hostname: 192.168.10.3
<<: *ios_credentials
- hostname: 192.168.10.5
<<: *ios_credentialsIn this example:
- The
credentialssection defines reusable credentials using the anchor&ios_credentials. - The
<<: *ios_credentialsalias is used to include the credentials in multiple devices within thescopesection.
Switch stacks / Virtual Chassis
When a driver implements stack-member discovery and the target reports 2+ members with serials, device-discovery emits a NetBox VirtualChassis plus one Device per member, and routes each interface and IP address to the correct member based on the interface name prefix (e.g. GigabitEthernet1/0/1 → member 1, GigabitEthernet2/0/12 → member 2). Standalone switches, devices not in stack mode, and members without a serial (which Diode cannot resolve) fall back to the existing single-Device path with no change in behaviour.
Emission shape (in order):
- Master
Device- plain (novc_position, novirtual_chassisref). Named from the member-name template (below); by default<hostname>-<id>, where<hostname>is the management hostname and<id>is the master's stack-member id. VirtualChassis- named<hostname>, withmasterset to the inline matcher block of the master Device.- N − 1 member
Deviceentities - each carriesvc_position = <member id>and an inlinevirtual_chassisref pointing to the same matcher block. - Interface / IPAddress entities - routed to the member device whose id matches the interface name prefix. Logical interfaces with no parseable member id (
Vlan*,Loopback*,Port-channel*, etc.) land on the master.
Member naming. Every member device name - master and non-master - is rendered from defaults.stack_member_name_template. The template supports two placeholders: {name} (the stack name, i.e. the management hostname) and {id} (the device-reported member id). The default {name}-{id} reproduces the historical names (core-sw-1, core-sw-2), so existing deployments are unaffected. Set a custom template when you pre-create member devices under a different 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; the template does not offer a zero-based offset, so the device-reported ids must equal your numbering (css1/css2 ↔ ids 1/2). An empty, malformed, or otherwise unusable template is ignored with a WARNING and the default is used - a bad config never rejects the policy or aborts discovery.
Master pinning. The logical master sent to Diode is always the lowest stack-member id present, regardless of live role. This is required because the NetBox Diode plugin resolves an existing VirtualChassis via its unique_master matcher - pinning to the lowest id keeps the master Device stable across StackWise 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 so the plugin's matcher cascade resolves through the same record on every cycle.
Orphaned member ports. If a member is dropped from the validated payload (missing serial, duplicate id, etc.) but the device still reports its ports via show interfaces, 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.
Supported drivers. Stack discovery is opt-in per driver (analogous to interface↔VLAN associations). See the supported platforms page for the current list; vendors land as follow-up PRs as the underlying drivers gain stack-discovery support.
When a driver supports it, 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. Trunks that allow every VLAN (e.g. Cisco IOS Trunking VLANs Enabled: ALL or 1-4094) are emitted as tagged-all so NetBox sees the proper 802.1Q semantics rather than a tagged interface with an empty allowed-VLAN list. 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. Malformed CLI rows that the driver cannot parse fail closed (plain tagged mode with no tagged VLANs and a logged warning) - they never silently widen an interface to all VLANs. 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.
Modules / ModuleBays
When a driver implements module discovery and the discover_modules policy option is enabled, device-discovery emits NetBox Module and ModuleBay entities for each chassis slot (e.g. a Catalyst 9404R supervisor + line cards in slots 1–4) and, in full mode, for each transceiver sub-bay reported by the device. An optic whose inventory row has no slot, linecard or FRU module above it - the whole inventory on a standalone non-modular switch (e.g. a Cat 3850 / 9300 with no removable line cards), or just the fixed ports on a chassis whose only module is an uplink - is discovered too, as a device-rooted ModuleBay named after its interface (see below). Device-rooted optic discovery is implemented by seven drivers - ios, eos, nxos, nxos_ssh, iosxr, nokia_sros and aruba_aoscx. Other drivers that implement get_modules() still discover slot-based modules only, so a fixed-port target on one of those - and any device whose driver does not implement module discovery at all - falls back to the existing emission with no change in behaviour; see the supported platforms page for the per-driver state. 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 / fan modules a driver classifies explicitly). Every optic is skipped, whether it's a transceiver sub-bay under a linecard or a device-rooted bay with no parent slot at all - useful when operators care about the slot inventory but not per-port optics. A fixed-port device therefore emits nothing in this mode. |
full | linecards plus one extra ModuleBay + Module for every optic reported by the device - a transceiver sub-bay under its parent linecard, or a device-rooted bay named after its interface when no parent slot exists. Interfaces backed by a transceiver carry a module= reference to the transceiver module so NetBox shows which port is populated by which optic. |
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. Catalyst 9300 stack with FRU uplinks, Catalyst 9400 / 9500 in StackWise Virtual mode). When a VC member is itself a modular chassis, modules and bays are dispatched per member: 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. The emission order becomes: Device(master) → VirtualChassis → Device(non-master members) → all ModuleBay + Module per member → Interface / IPAddress per member. A stack member with no slot bays at all - the FRU-less members of a mixed stack, or every member on a stack of otherwise-fixed-port switches - still emits its own optics as device-rooted bays under that member's Device; only a member with neither slot bays nor optics emits no module entries. One exception: on iosxr, a multi-rack target whose inventory reports no linecard rows at all promotes optics for the first rack only and refuses later racks with a warning, because there is no validated chassis set to attribute them to.
Optics with no parent module. An optic whose inventory row reports no enclosing slot, linecard or FRU module is discovered as a ModuleBay rooted on the device and named after its interface, rather than being dropped. This covers two cases: a wholly fixed-port chassis (every optic is device-rooted, since nothing above it exists), and the fixed ports of a chassis whose only module is an uplink (the uplink's optics nest under it as usual; the fixed ports' optics have no parent and are device-rooted alongside it). Promotion only fires on an optic already classified as a transceiver with a non-empty model and serial; an optic that resolves a parent bay is emitted exactly as before, nested under it. In linecards mode these device-rooted bays are skipped along with every other optic, so a fixed-port device produces no modules at all in that mode.
An optic the device serialises but does not identify - an inventory row with a serial but no PID - is recorded rather than dropped; see Modules the device cannot identify above for how it is emitted. The manufacturer is filed under the generic Unknown rather than the device's own vendor: substituting the row's description for a part number would otherwise split what is physically one optic model across two different NetBox ModuleType records as different devices report (or omit) that description text, so segregating these rows under Unknown keeps them distinguishable from genuinely identified parts instead of polluting the vendor's real catalog.
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. This trade-off is specific to sub-bays that give up a real parent link: a fixed-port optic's bay is device-rooted because no parent module exists at all, which is the correct representation rather than a compromise, and is unaffected by any future reconciler fix.
Supported drivers. Module discovery is opt-in per driver (analogous to interface↔VLAN associations and stack discovery). See the supported platforms page for the current list; vendors land as follow-up PRs as the underlying drivers gain module-discovery support.
LAG membership
When a driver implements get_interfaces_lag(), each interface that is a member of a link aggregation carries a lag reference to its aggregate interface (ae0, Port-channel1, …). Nothing is created: both the member and the aggregate must already be among the discovered interfaces, and the aggregate must be typed lag (the built-in patterns type ae*, Port-channel* and Bundle-Ether* as lag). On by default; set emit_lag_membership: false to leave lag unset and skip the driver call.
Physical ports only. NetBox carries a LAG parent on the physical port and does not allow one on a virtual interface. Where a platform reports membership per logical unit - Junos does - the driver collapses every unit onto its physical port: xe-0/0/24.0 → ae120.0 and xe-0/0/24.1876 → ae120.1876 become one membership, xe-0/0/24 → ae120.
What is skipped, with a warning. An aggregate that is not among the discovered interfaces, or that is not typed lag; a member typed virtual (NetBox refuses the interface outright, which would fail its ingestion), bridge or lag; a member that names itself as its aggregate; a name more than one discovered interface carries (it cannot be attributed); 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. A member the policy excluded through interface_exclude_patterns is skipped silently.
Switch stacks / Virtual Chassis. The reference names the stack member that owns the aggregate, so a member port on one stack member can point at an aggregate attributed to another (on Junos the ae interfaces are attributed to the master). NetBox accepts such a cross-member reference only while both devices belong to the same virtual chassis, so if a stack member's own device or virtual-chassis membership fails to ingest, NetBox also refuses that member's LAG ports.
Removing a member. When a port leaves an aggregate between discovery cycles, its existing lag in NetBox is not cleared automatically - the same Diode PATCH-semantics limitation noted for interface VLAN associations above. Clear it in NetBox manually.
Supported drivers. See the supported platforms page.
VRFs
When the discover_vrfs policy option is enabled (defaults to false), device-discovery calls the driver's standard NAPALM get_network_instances() getter and emits a NetBox VRF entity per VRF configured on the device. Each discovered VRF is attached to the IPAddress and Prefix entities of the interfaces inside that VRF; interfaces in the default routing table carry no discovered VRF and keep whatever defaults.*.vrf configuration is in effect.
Precedence. Device state beats policy defaults: for an interface inside a discovered VRF, the discovered VRF wins over defaults.ipaddress.vrf / vrf_ipv4 / vrf_ipv6 (and the defaults.prefix equivalents). The defaults remain the fallback for all other interfaces, so mixed configurations work as expected.
Filtering. Not every network instance a device reports is an operator-meaningful VRF. The following are skipped:
- the default instance (the global routing table),
- L2 instance types (VPLS / EVPN / L2VPN instances - these have no NetBox VRF equivalent),
- platform-internal instances whose names start with
__(e.g. Cisco's__Platform_iVRF:_ID00_).
Management VRFs (e.g. Cisco Mgmt-vrf, Junos mgmt_junos) are real VRFs and are kept.
Route distinguisher. The rd field is set only when the device reports a real value. Devices without MPLS routinely report an empty or placeholder RD - those stay off the wire, so the VRF matches a NetBox VRF record whose RD is empty rather than creating one with a bogus RD. Note the Diode reconciliation caveat: an ingested VRF without an RD only matches NetBox VRFs whose RD is unset. If a VRF with the same name already exists in NetBox with an RD configured, the first discovery cycle creates a separate RD-less VRF record alongside it (subsequent cycles converge on the RD-less record). If you pre-seed VRFs in NetBox and want discovery to match them, either leave their RD unset or make sure the device reports the same RD.
Supported drivers. VRF discovery is available on drivers that implement get_network_instances() - see the supported platforms page for the current list. On drivers without support, enabling the option logs a warning per cycle and discovery continues without VRF data.
What NAPALM Collects Automatically
The tables below show which fields are populated automatically from the device versus which must be provided via defaults in the policy configuration.
Device
| Field | Source | Notes |
|---|---|---|
| Name | get_facts() → hostname | Auto-collected |
| Model | get_facts() → model | Auto-collected; overridable via defaults.device.model |
| Manufacturer | get_facts() → vendor | Auto-collected; overridable via defaults.device.manufacturer |
| Platform | get_facts() → os_version + driver name | Format: "<DRIVER> <os_version>". Overridable via defaults.device.platform. Use platform_omit_version: true to use only the driver name |
| Serial number | get_facts() → serial_number | Auto-collected |
| Status | - | Always set to "active" |
| Site | Not collected | Must be set via defaults.site |
| Role | Not collected | Must be set via defaults.role |
| Location | Not collected | Must be set via defaults.location |
| Rack | Not collected | Set via defaults.rack or a target's override_defaults.rack |
| Position, face | Not collected | Set per device via a target's override_defaults.position and override_defaults.face |
| Tenant | Not collected | Must be set via defaults.tenant |
| Description | Not collected | Must be set via defaults.device.description |
| Comments | Not collected | Must be set via defaults.device.comments |
| Tags | Not collected | Must be set via defaults.tags or defaults.device.tags |
Interface
| Field | Source | Notes |
|---|---|---|
| Name | get_interfaces() → interface key | Auto-collected |
| Enabled | get_interfaces() → is_enabled | Auto-collected |
| MAC address | get_interfaces() → mac_address | Auto-collected |
| Description | get_interfaces() → description | Auto-collected; falls back to defaults.interface.description if empty |
| Speed | get_interfaces() → speed (Mbps) | Auto-collected; stored in NetBox as Kbps |
| MTU | get_interfaces() → mtu | Auto-collected |
| LAG | get_interfaces_lag() → member port → aggregate | On drivers that implement it, unless emit_lag_membership: false - see LAG membership |
| Type | Interface name pattern matching + speed | Determined by: (1) user interface_patterns, (2) built-in patterns, (3) speed-based detection, (4) defaults.if_type. Subinterfaces (. or : separator) are always "virtual" |
| Tags | Not collected | Must be set via defaults.tags or defaults.interface.tags |
IP Address
| Field | Source | Notes |
|---|---|---|
| Address (with prefix length) | get_interfaces_ip() | Auto-collected; IPv4 and IPv6 |
| Assigned interface | - | Automatically linked to the interface |
| Role | Not collected | Must be set via defaults.ipaddress.role |
| VRF | get_network_instances() when options.discover_vrfs: true | Otherwise set via defaults.ipaddress.vrf (a discovered VRF wins over the defaults - see VRFs) |
| Tenant | Not collected | Must be set via defaults.ipaddress.tenant |
Prefix
Prefixes are derived from IP addresses discovered on interfaces. The network address is computed automatically from each discovered IP/prefix-length.
Two shapes are not derived by default, because they carry no IPAM value and generate large volumes of noise:
| Not derived | Example | Why | Restorable |
|---|---|---|---|
Host prefixes - IPv4 /32, IPv6 /128 | a 10.0.0.1/32 loopback | The "prefix" only restates the address, duplicating the IPAddress entity emitted alongside it. A driver that reports no prefix length defaults to /32 / /128, so this also covers those. | Yes - emit_host_prefixes: true |
IPv6 link-local - fe80::/10, any mask | fe80::5a86:70f0:a8:e47f/128 | Link-locals are per-link and not globally meaningful, so one prefix per link-local address is pure churn. | No |
The IPAddress entity is always still emitted in both cases, so the interface and its address stay fully documented - only the derived Prefix is skipped. 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.
Set emit_host_prefixes: true in the policy options to derive host prefixes again - for example when loopback /32s are deliberately tracked as prefixes in NetBox:
policies:
my-policy:
config:
options:
emit_host_prefixes: trueThe opt-in covers host prefixes only. IPv6 link-local prefixes stay suppressed even with it enabled, including an fe80::…/128 address, which is a link-local that happens to carry host length rather than a loopback worth tracking.
| Field | Source | Notes |
|---|---|---|
| Prefix (network address) | Derived from IP address | Auto-computed; host prefixes and IPv6 link-locals are skipped (see above) |
| VRF | get_network_instances() when options.discover_vrfs: true | Otherwise set via defaults.prefix.vrf (a discovered VRF wins over the defaults - see VRFs) |
| Role / Tenant | Not collected | Must be set via defaults.prefix.* |
| Scope (site / location) | Not collected | Set via defaults.prefix.scope_* (see Nested Defaults) or opt into the cascade via options.propagate_defaults_to_prefix_scope |
Prefix scope is a oneof - a Prefix carries one of scope_site or scope_location. When both defaults.prefix.scope_* are set, the most-specific wins on the wire: scope_location > scope_site. By default defaults.site does NOT auto-fill Prefix.scope_site - set options.propagate_defaults_to_prefix_scope: true to enable the cascade. Any explicit defaults.prefix.scope_* puts the operator in "explicit mode" and the cascade is skipped wholesale, so a cascaded more-specific scope can't override an operator's explicit less-specific choice. Clearing an existing scope requires editing NetBox directly.
VLAN association (emit_prefix_vlan). When set to svi-name, a derived prefix
carries the VLAN of the interface the contributing address lives on, so an address on
Vlan10 associates its prefix with VLAN 10. Defaults to off. Any scalar is
read as its text, so a bare on, a number, a YAML timestamp and a mistyped mode all
resolve to off with a warning rather than erroring: a typo disables the feature instead
of writing a guess into NetBox. A list or mapping is rejected, matching what the
snmp-discovery decoder does with one.
The device's own VLAN ID comes first. Drivers that implement the optional
get_interfaces_vlan_id() getter report the VLAN ID each L3 interface is bound to, read
from the device rather than the name, so interfaces whose names carry no usable VLAN ID
are associated too: a RouterOS VLAN interface named sfpplus1.156 with vlan-id=156,
a FastIron ve 400 that is the router-interface of VLAN 40, or a Junos irb.166 or
vlan.20 that the switch's VLAN table names as the L3 interface of a VLAN. Where the name and the
device disagree, the device wins. The driver can also withhold an interface it reports
but whose tag is not a plain 802.1Q VLAN; such an interface is not associated, whatever
its name says. The getter is only called while the option is svi-name. See the
supported platforms page for
the drivers and what each one withholds. Every other interface falls back to the name
rule below.
Which interface names qualify. Case-insensitively: an optional leading interface
followed by a separator, one of vlan-interface, vlan id, vlanif, vlan, svi,
vl, then an optional separator before a VLAN ID in 1-4094 with leading zeros stripped.
So Vlan10, VLAN ID 0051 and Interface vlan30 qualify, while the run-together
InterfaceVlan30 does not. Any name containing a dot is
rejected, because the number after the dot is a subinterface index rather than reliably
a VLAN ID.
The VLAN must already be known and named. Only a VLAN already found in the device's
VLAN database with a non-empty name is attached, whether its ID came from the device or
from the name. Unlike create_unknown_vlans, this never stubs a VLAN; a miss is left
unassociated. A routed subinterface whose tag has no entry in the device's VLAN table
(a Junos ae0.100, a Cisco Gi0/0.100) is therefore skipped by design.
Unanimity, and what it cannot cover. A prefix is 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. On a virtual chassis the check spans every member, because a
Prefix is keyed globally rather than per member. 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. That includes a VLAN you set on the prefix by hand: once discovery
resolves one, every poll writes it. This is why the option defaults to off. Set
defaults.vlan.group (with defaults.site) before enabling it, so the
VLAN each prefix references matches the one already scoped in NetBox instead of a
duplicate.
VLAN
| Field | Source | Notes |
|---|---|---|
| VID | get_vlans() → VLAN ID | Auto-collected |
| Name | get_vlans() → VLAN name | Auto-collected |
| Group / Role / Tenant | Not collected | Must be set via defaults.vlan.* |
Virtual Chassis (switch stacks)
Only emitted when the driver implements get_chassis_members() and the target reports 2+ stack members with serials. See Switch stacks / Virtual Chassis above for the emission shape and the supported platforms page for the driver list.
| Field | Source | Notes |
|---|---|---|
VirtualChassis.name | device.hostname | The management hostname becomes the VC name |
VirtualChassis.master | Lowest member id present | Pinned to lowest id (not live role) so the master Device is stable across StackWise role failovers |
VirtualChassis.domain | Driver-supplied (when available) | Optional; only some platforms surface a VC domain id |
Member Device.name | stack_member_name_template | Default {name}-{id} → e.g. core-sw-1, core-sw-2. Configurable - see Member naming |
Member Device.serial | Per-member from driver | Required - members without a serial are dropped |
Member Device.model | Per-member from driver | Falls back to chassis model if the driver doesn't surface per-member models |
Member Device.vc_position | Stack-member id | Preserved exactly (e.g. id=1,2,4 if slot 3 is empty) |
| Member-interface routing | Interface name prefix | Parsed via parse_member_id - Cisco IOS canonical (Gi1/0/1 → member 1), mGig families (TwoGigabitEthernet, FiveGigabitEthernet, TenGigabitEthernet, TwentyFiveGigE), Junos FPC (ge-1/0/0 → FPC 1), Aruba CX bare 3-tuple (1/1/1), HP/H3C Comware hyphenated speed prefixes (Ten-GigabitEthernet, Twenty-FiveGigE, FortyGigE, FiftyGigE, TwoHundredGigE, FourHundredGigE), Huawei VRP (XGigabitEthernet, 10GE/25GE/40GE/50GE/100GE/200GE/400GE); FastIron ICX uses the same bare 3-tuple form as Aruba CX |