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.

On this page
- At a Glance: Which Evidence Answers Which Question
- Pull the four exports first
- Normalize everything into one row per port
- Check one: ports with no traffic and no neighbour
- Check two: orphaned IPs
- Check three: bogus LAGs
- Check four: VLAN membership drift
- Put the run on a schedule and diff it
- Turn the findings into a worklist
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.
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.
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.
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?
How do I find unused switch ports?
What is an orphaned IP address?
How can I tell whether a LAG is misconfigured?
What should I document for every interface?
How often should I audit interfaces?
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

What Is ICMP? Ping, Traceroute, and the Messages Behind Them
ICMP explained: the control messages behind ping and traceroute, the message types that matter, and how ICMP interacts with firewalls and packet loss.
Read more
What Is TFTP and When Is It Still Used?
TFTP explained: how the trivial file transfer works over UDP, why switches and provisioning still use it, and where its lack of authentication becomes a risk.
Read more
How to Find and Fix IP Address Conflicts
Obelinf explains how IP address conflicts happen, the symptoms they leave on your network, the diagnostics that pinpoint the duplicate fast, and the fixes that keep them from coming back.
Read more