9 min read

The Interface Inventory Audit: Finding Unused Ports, Orphaned IPs, and Bogus LAGs

A step by step interface audit you can run this week: the exact commands to pull from each switch, the API queries that find orphaned IPs, and how to read LAG flags that hide a broken bundle.

ByAndré Ribeiro· Founder, Obelinf
The Interface Inventory Audit: Finding Unused Ports, Orphaned IPs, and Bogus LAGs
The Interface Inventory Audit: Finding Unused Ports, Orphaned IPs, and Bogus LAGs · October 18, 2026
On this page

An interface list is the easiest inventory to let rot and the most expensive one to distrust. A single port carries a name, a type, an enabled state, a description, a MAC address, an untagged VLAN, a tagged list, a LAG membership, sometimes a parent relationship, and one or more IP addresses, and every value is owned by a different system or person. That is a lot of surface area for a change window to leave behind.

This is a working procedure rather than a theory piece. It walks through the four exports to pull from each device, how to squeeze them into one row per port, the four checks that catch almost all interface drift, and how to put the whole thing on a schedule so the diff between two runs becomes the real signal. You can start on a single switch today and extend it as the process proves itself.

At a Glance: Which Evidence Answers Which Question

Obelinf records Device config and counters LLDP or CDP neighbours
Unused and dark ports Expected state, description, owner Link state, MAC table, counters Whether anything answers
Descriptions and owners Human maintained, with history Description field only Remote hostname as a hint
VLAN membership Untagged VLAN plus tagged list Running switchport config Sometimes advertises a VLAN
LAG membership Members linked to their bundle Etherchannel and LACP state Peer port identifier
IP to interface links Address attached to the interface Address table and ARP Absent
Diffable over time Change history per field Snapshot only Snapshot only

Pull the four exports first

Everything else in the audit is comparison, so the first job is getting evidence off the devices without touching configuration. On Cisco IOS or NX-OS, capture the interface list, the description table, the MAC table, the neighbour table, the bundle summary, and the per port switchport state.

show interfaces status
show interfaces description
show mac address-table
show lldp neighbors detail
show etherchannel summary
show interfaces switchport

On Juniper Junos, the equivalents are the interface summary, the Ethernet switching table, the neighbour list, and the LACP state.

show interfaces terse | no-more
show ethernet-switching table
show lldp neighbors
show lacp interfaces

On a Linux host, the equivalent view comes from the link, bridge, and neighbour tools, with ethtool for the negotiated speed and duplex that the audit will need later.

ip -br link
bridge link
bridge vlan show
ip -br neigh
ethtool eth0

Save each command to a file rather than reading it on screen, and name the file after the device and the date. A single line per device is enough to start collecting.

mkdir -p audit/2026-10-18
ssh sw01 'show interfaces status'   > audit/2026-10-18/sw01-status.txt
ssh sw01 'show mac address-table'   > audit/2026-10-18/sw01-macs.txt
ssh sw01 'show etherchannel summary' > audit/2026-10-18/sw01-lag.txt

Normalize everything into one row per port

The comparison only works if every source speaks the same language, and no two of those outputs do. Reduce each one to a table keyed on device name plus interface name, with a fixed column order you keep for every run. The columns that matter are the ones you will compare.

device  interface  type  mode  enabled  description  untagged  tagged  lag  parent  mac

The recorded side comes out of your inventory in one query. Pull the interface list and print it as tab separated values so it joins cleanly against the device exports.

export OBELINF_API_KEY="your_read_only_key"
export OBELINF_ORG="acme-netops"

curl --fail --silent --show-error \
  -H "Authorization: Bearer ${OBELINF_API_KEY}" \
  "https://api.obelinf.com/${OBELINF_ORG}/device-interfaces?limit=500" \
  > records.json

jq -r '.interfaces[] | [
      .device_name, .name, .type, (.mode // ""), (.enabled|tostring),
      (.description // ""), (.untagged_vlan_vid // ""),
      ([.tagged_vlans[].vid] | join(",")), (.lag_name // ""), (.parent_name // "")
    ] | @tsv' records.json | sort > records.tsv

Then build the live side from the same device exports, keeping the same column order. Whatever cannot be read from the command goes in as an empty field, because a missing value in a column is itself a finding.

The audit pipeline from device exports and inventory records through four checks to a worklist Device exports status, MAC, LLDP, LAG Inventory records API export to TSV One row per port fixed columns Checks and worklist unused, orphan, LAG, VLAN The four checks all run against the same joined table. Add a column rather than a separate script for each check, and keep the previous run so you can diff the output. keep the column order identical between runs or the diff is noise.

Check one: ports with no traffic and no neighbour

The live MAC table is the cheapest signal that a port has ever had something plugged in, and the neighbour table is the cheapest signal of what. Join the recorded port list against the set of ports that appear in either export and print the ports that are missing from both.

cut -f1,2 records.tsv | sort -u > recorded-ports.txt
awk 'NR>1 {print $1"\t"$2}' audit/2026-10-18/sw01-macs.txt | sort -u > live-ports.txt
comm -23 recorded-ports.txt live-ports.txt

Everything the comm command prints is a port your records describe as in service and the device has no memory of. Some of those will be genuinely unused, some are administratively down on purpose, and some are patched to a device that is powered off. Separate them by reading the description and the link state.

ssh sw01 'show interfaces status | include notconnect|disabled'
ssh sw01 'show interfaces description | include down'

A port that is disabled with a description naming a device is a deliberate shutdown, and the description is the only thing that explains it. A port that is notconnect, has no description, and has not appeared in the MAC table for the two most recent runs is the one worth shutting and documenting. The audit output is the disagreement, not the decision.

Check two: orphaned IPs

An orphaned address is one that still exists in the inventory but points at nothing. In Obelinf the interface reference is cleared automatically when an interface is removed, so a deleted port leaves its addresses behind on the subnet, attached to nothing. One query finds them.

curl --fail --silent --show-error \
  -H "Authorization: Bearer ${OBELINF_API_KEY}" \
  "https://api.obelinf.com/${OBELINF_ORG}/ip-addresses?limit=500" \
  | jq -r '.ip_addresses[]
      | select(.device_id == null and .vm_id == null)
      | select(.interface_id == null and .vm_interface_id == null)
      | select(.is_reserved == false)
      | [.address, (.hostname // ""), .subnet_cidr, (.updated_at|tostring)] | @tsv'

Each line is an address that is neither on a device nor reserved, which means it is occupying space on a subnet with no owner. Two things are worth doing with that list. Release the addresses that belong to nothing so utilization reflects reality, and check the updated_at date against your change history to see which deletion or migration produced each one.

An interface record and the records it connects to, from VLANs and LAGs to IP addresses Interface record Untagged VLAN native or access Tagged VLANs trunk membership LAG and parent bundle and breakout IP addresses one may be primary When a link in this tree breaks, the top record still looks healthy. The orphan query walks the branch that broke and reports the addresses left attached to nothing.

Check three: bogus LAGs

Bundles hide their problems well, so read the member flags rather than the bundle state. On Cisco and NX-OS the summary prints one flag per member, and the flag is the audit signal.

sw01# show etherchannel summary
Group  Port-channel  Protocol    Ports
------+-------------+-----------+------------------------------------------
10     Po10(SD)      LACP        Gi1/0/1(P)  Gi1/0/2(P)  Gi1/0/3(I)  Gi2/0/1(D)

Read the flags as follows. (P) means the member is bundled and forwarding. (D) means the member is down. (s) means the member is suspended, usually because it failed negotiation or the bundle fell below its minimum link threshold. (I) means the member is standalone and carrying traffic outside the bundle, which is the dangerous one, because the port is up while the hash ignores it.

Cross check every bundle against your inventory to catch the other common failure, a member recorded under a bundle that lives on a different device. The query groups members by bundle id and flags any bundle whose members span more than one device name.

curl --fail --silent --show-error \
  -H "Authorization: Bearer ${OBELINF_API_KEY}" \
  "https://api.obelinf.com/${OBELINF_ORG}/device-interfaces?limit=500" \
  | jq -r '.interfaces[] | select(.lag_id != null) | [.lag_id, .device_name] | @tsv' \
  | sort -u \
  | awk -F'\t' '{c[$1]++; dev[$1]=dev[$1]" "$2} END {for (l in c) if (c[l] > 1) print l":"dev[l]}'

The same pattern catches parent relationships for breakout ports. If a recorded member names no lag_name while the device shows it bundled, the record is behind, and if the record names a bundle the device does not know, the device is behind. Either way the fix is a record or a configuration change, never a guess.

Etherchannel member flags distinguishing a healthy bundle from one with suspended and standalone members Healthy bundle, every member (P) Gi1/0/1(P)Gi1/0/2(P)Gi1/0/3(P)Gi1/0/4(P) Traffic hashed across four members Drifted bundle, flags disagree Gi1/0/1(P)Gi1/0/2(s)Gi1/0/3(I)Gi2/0/1(D) One member outside the bundle The bundle is up in both cases. Only the flags tell you whether the redundancy survived.

Check four: VLAN membership drift

Membership drift is easy to see once both sides of the comparison exist. On the device, the switchport state prints the administrative and operational mode plus the assigned VLANs.

sw01# show interfaces switchport
Name: Gi1/0/12
Administrative Mode: trunk
Operational Mode: trunk
Access Mode VLAN: 999
Trunking Native Mode VLAN: 999
Trunking VLANs Enabled: 20,30,40

The two drifts worth flagging in the record are an access port that has picked up tagged VLANs and a trunk port that has an empty tagged list. Both are one query each.

jq -r '.interfaces[]
  | select(.mode == "access" and (.tagged_vlans | length) > 0)
  | [.device_name, .name, (.tagged_vlans | map(.vid) | join(","))] | @tsv' records.json

jq -r '.interfaces[]
  | select(.mode == "trunk" and (.tagged_vlans | length) == 0)
  | [.device_name, .name, (.untagged_vlan_vid // "none")] | @tsv' records.json

Keep the raw JSON from the interface query so these run without another call. Save it next to the device exports under the same date, and the next audit has something to compare against.

Put the run on a schedule and diff it

A cleanup happens once. A control happens repeatedly. Wrap the exports and the four checks in one script, write the output to a dated directory, and keep two runs at minimum so you can print the difference.

diff <(sort audit/2026-10-04/live-ports.txt)  <(sort audit/2026-10-18/live-ports.txt)
diff <(sort audit/2026-10-04/orphan-ips.tsv)  <(sort audit/2026-10-18/orphan-ips.tsv)

The diff is where the audit earns its keep. A port that appeared in the MAC table last week and is gone this week is a device that moved or died. A new orphan address means something was deleted without its allocation being released. A bundle that gained a member from a different device is a change nobody described. None of those stand out in a full list, and all of them stand out in a diff.

Turn the findings into a worklist

The audit produces three kinds of result and they should not be mixed. Record fixes correct the inventory when the network is right. Release actions free addresses that belong to nothing. Configuration fixes go into a change window.

Finding Evidence Fix Owner
Port with no MAC and no neighbour for two runs show mac address-table, show lldp neighbors Describe, then shutdown if confirmed unused Network team
Address with no device or interface Orphan IP query Release the allocation IPAM owner
Bundle member on another device LAG grouping query Remove or rebuild in a change window Network team
Access port holding tagged VLANs Tagged list query Correct the record or the port Network team
Description naming a device that moved Device export compared to records Update the description and owner Whoever did the move

An audit at interface level sits inside the wider sweep that a network audit checklist describes, and the two are worth running together. The checklist covers ownership and documentation gaps around the fleet, while this procedure answers the port by port questions underneath. Keeping the interface rows in a structured inventory rather than a saved spreadsheet is what makes the comparison a query instead of an afternoon of sorting, and it is the reason the second audit takes minutes rather than hours.

Start with one switch. Pull the four exports, join them, and read the comm output. The first run tells you which ports you cannot explain, and that short list is usually enough to justify putting the whole thing on a schedule.

Frequently Asked Questions

What is an interface inventory audit?
An interface inventory audit compares what your records say about every port against what the devices and your other records actually show. In practice it means exporting the interface list, the MAC table, the neighbour table, and the bundle summary from each device, putting them in one row per port, and then comparing that against your inventory. The differences you find are the audit result.
How do I find unused switch ports?
Run `show interfaces status` and `show mac address-table` together, then look for ports that are not connected, that have no learned MAC addresses, and that have no LLDP or CDP neighbour. One reading is not enough, because a port with no link may be unused, powered off at the far end, cabled to a dead device, or administratively shutdown on purpose. Three clean signals together are what justify calling a port unused.
What is an orphaned IP address?
An orphaned IP is an address record that claims to be allocated but no longer points at an interface, device, or VM. In Obelinf the reference is cleared automatically when an interface is deleted, which leaves the address behind on the subnet with nothing attached to it. Orphans inflate utilization and cause duplicate assignment the next time someone allocates from the same range.
How can I tell whether a LAG is misconfigured?
Start with `show etherchannel summary` and read the member flags. A member marked `(P)` is bundled and carrying traffic, `(D)` is down, `(s)` is suspended by negotiation or by the minimum link threshold, and `(I)` is standalone and not in the bundle at all. A bundle showing several `(I)` members is up but not spreading traffic. Then verify from your inventory that every member of the same bundle lives on the same device.
What should I document for every interface?
The interface name, its type, whether it is enabled, its description, its mode, the untagged or native VLAN, the tagged VLAN list, its LAG or parent relationship, its MAC address, and the IP addresses assigned to it. Those fields are what let you answer the same question from either direction, from a port to its networks or from an address back to the port it lives on.
How often should I audit interfaces?
Run the full audit once a quarter and a lighter version after every change window, migration, or decommissioning project. The value comes from keeping the previous run and comparing, because a port that newly lost its neighbour or a bundle that gained a member on another device is far easier to spot as a difference than as a row in a long list.

Free Tools

Put this into practice with the free calculators, reference tables, and checklists in the Obelinf toolkit.

Stop reaching for a spreadsheet

Obelinf keeps every subnet, device, circuit, and rack in one live source of truth, with audit logs and a topology view. Free for personal use.

Related Articles