- Comprehensive analysis of lldp2map's discovery algorithms - BFS recursive topology discovery with depth limits - LLDP-MIB walking with smart fallback strategies - Recommended Elixir/Phoenix implementation phases - Database schema for device neighbors - Topology visualization options - Auto-discovery workflow enhancements - Estimated 8-11 days implementation effort Based on research of https://github.com/buraglio/lldp2map
13 KiB
LLDP Topology Discovery Analysis for Towerops
Executive Summary
lldp2map is a well-architected Go CLI tool that performs recursive network topology discovery using LLDP (Link Layer Discovery Protocol) via SNMP. This analysis examines how its approach could significantly enhance Towerops' device discovery and network visualization capabilities.
Current Towerops Discovery Limitations
- Manual Device Addition: Users must manually add each device's IP address
- No Topology Awareness: No understanding of physical network connections
- No Automatic Neighbor Discovery: Missing devices that are reachable via LLDP neighbors
- No Visual Network Maps: No built-in topology visualization
- Limited Relationship Data: Device relationships not captured or displayed
lldp2map Key Features & Algorithms
1. Recursive BFS (Breadth-First Search) Discovery
Algorithm (from cmd/root.go:138-215):
queue := []queueItem{{host: seedHost, depth: 0}}
visited := map[string]bool{}
for len(queue) > 0 {
item := queue[0]
queue = queue[1:]
if visited[item.host] {
continue
}
visited[item.host] = true
// Walk LLDP neighbors
info, err := lldp.Walk(client)
// Add discovered neighbors to queue
for _, neighbor := range info.Neighbors {
for _, ip := range neighbor.MgmtAddrs {
if !visited[ipStr] && item.depth < maxHops {
queue = append(queue, queueItem{
host: ipStr,
depth: item.depth + 1
})
}
}
}
}
Benefits:
- Automatically discovers entire network from a single seed device
- Configurable depth limit prevents runaway discovery
- Handles network cycles via visited tracking
- Scales to large networks efficiently
2. Comprehensive LLDP MIB Walking
OIDs Used (from internal/lldp/walker.go:13-45):
Local System:
- 1.0.8802.1.1.2.1.3.3.0 lldpLocSysName (device hostname)
- 1.0.8802.1.1.2.1.3.7.1.4 lldpLocPortDesc (local port names)
Remote Neighbors:
- 1.0.8802.1.1.2.1.4.1.1.7 lldpRemPortId (remote port ID)
- 1.0.8802.1.1.2.1.4.1.1.8 lldpRemPortDesc (remote port name)
- 1.0.8802.1.1.2.1.4.1.1.9 lldpRemSysName (neighbor hostname)
- 1.0.8802.1.1.2.1.4.2.1.3 lldpRemManAddrIfId (management IPs)
- 1.0.8802.1.1.2.1.4.1.1.4 lldpRemChassisIdSubtype
- 1.0.8802.1.1.2.1.4.1.1.5 lldpRemChassisId
Interface Addresses:
- 1.3.6.1.2.1.4.34.1.3 ipAddressIfIndex (IPv4+IPv6, RFC 4293)
- 1.3.6.1.2.1.4.20.1.1 ipAdEntAddr (IPv4 fallback, RFC 1213)
ARP Resolution:
- 1.3.6.1.2.1.4.22.1.2 ipNetToPhysAddr (MAC→IP resolution)
Smart Fallback Strategy:
- Try modern management address table (
lldpRemManAddrIfId) - Fall back to chassis ID with network address type
- For MAC chassis IDs, resolve via device ARP table
- This ensures maximum compatibility across device vendors
3. Advanced OID Index Parsing
Example: Management Address Index (internal/lldp/walker.go:242-284):
Index format: timeMark.portNum.remIndex.addrSubtype.addrLen.addr[bytes]
- addrSubtype: 1=IPv4 (4 bytes), 2=IPv6 (16 bytes)
- Handles both IPv4 and IPv6 natively
- Extracts neighbor identification across multiple MIB entries
Parsing robustness:
- Handles malformed OID responses gracefully
- Filters out loopback and link-local addresses
- Deduplicates addresses across multiple discovery paths
4. Topology Graph Structure
Data Model (internal/graph/topology.go):
type Node struct {
Name string // Device hostname
IP string // Management IP for SNMP
Addrs []string // All interface IPs (optional)
}
type Edge struct {
From string // Source device name
To string // Destination device name
FromPort string // Local port (eth0, ge-0/0/1, etc)
ToPort string // Remote port
}
Edge Deduplication:
- Automatically removes A→B when B→A exists
- Prevents duplicate links in visual diagrams
- Maintains bidirectional link information
Recommended Implementation for Towerops
Phase 1: Core LLDP Discovery (Elixir Migration)
New Elixir Modules:
# lib/towerops/snmp/lldp.ex
defmodule Towerops.Snmp.Lldp do
@moduledoc """
LLDP topology discovery via SNMP.
Walks LLDP-MIB tables to discover network neighbors.
"""
@oid_loc_sys_name "1.0.8802.1.1.2.1.3.3.0"
@oid_rem_sys_name "1.0.8802.1.1.2.1.4.1.1.9"
@oid_rem_man_addr "1.0.8802.1.1.2.1.4.2.1.3"
def discover_neighbors(device_id) do
# Walk LLDP tables
# Parse neighbor data
# Return list of %{hostname, ports, management_ips}
end
end
# lib/towerops/topology/discovery.ex
defmodule Towerops.Topology.Discovery do
@moduledoc """
Recursive network topology discovery using LLDP.
"""
def discover_from_seed(device_id, opts \\\\ []) do
max_depth = Keyword.get(opts, :max_depth, 10)
# BFS queue implementation
# Track visited devices
# Build topology graph
end
end
# lib/towerops/topology/neighbor.ex (new schema)
defmodule Towerops.Topology.Neighbor do
@moduledoc """
Represents an LLDP neighbor relationship between devices.
"""
schema "device_neighbors" do
belongs_to :device, Device
belongs_to :neighbor_device, Device
field :local_port, :string
field :remote_port, :string
field :discovered_at, :utc_datetime
field :last_seen_at, :utc_datetime
timestamps()
end
end
Database Schema:
CREATE TABLE device_neighbors (
id UUID PRIMARY KEY,
device_id UUID NOT NULL REFERENCES devices(id) ON DELETE CASCADE,
neighbor_device_id UUID REFERENCES devices(id) ON DELETE SET NULL,
local_port TEXT NOT NULL,
remote_port TEXT,
discovered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
inserted_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
UNIQUE(device_id, local_port, neighbor_device_id)
);
CREATE INDEX idx_neighbors_device ON device_neighbors(device_id);
CREATE INDEX idx_neighbors_neighbor ON device_neighbors(neighbor_device_id);
Phase 2: Topology Worker (Background Discovery)
New Oban Worker:
defmodule Towerops.Workers.TopologyDiscoveryWorker do
use Oban.Worker, queue: :discovery, max_attempts: 3
@impl Oban.Worker
def perform(%Oban.Job{args: %{"seed_device_id" => device_id}}) do
# Run discovery from seed device
# Update device_neighbors table
# Create new devices for unknown neighbors
# Broadcast topology update to LiveView
:ok
end
end
Trigger Options:
- Manual: User clicks "Discover Topology" button
- Scheduled: Nightly cron job for each site
- Event-Driven: When new device added with LLDP enabled
Phase 3: Topology Visualization (LiveView Component)
New LiveView:
defmodule ToweropsWeb.TopologyLive.Show do
use ToweropsWeb, :live_view
def mount(%{"site_id" => site_id}, _session, socket) do
neighbors = Topology.list_site_neighbors(site_id)
{:ok,
socket
|> assign(:site_id, site_id)
|> assign(:neighbors, neighbors)
|> assign(:layout_format, "force_directed")}
end
def handle_event("run_discovery", %{"device_id" => device_id}, socket) do
TopologyDiscoveryWorker.enqueue(%{seed_device_id: device_id})
{:noreply, put_flash(socket, :info, "Topology discovery started")}
end
end
Visualization Options:
- SVG Force-Directed Graph: Use D3.js or Cytoscape.js via Alpine.js hook
- Interactive Canvas: vis-network or sigma.js
- Export to Draw.io: Generate XML for external editing
- Export to PNG/PDF: Server-side Graphviz rendering
Phase 4: Device Auto-Discovery Enhancement
Current Flow:
User manually adds device → SNMP discovery runs → Sensors created
Enhanced Flow with LLDP:
User adds seed device →
SNMP discovery runs →
LLDP neighbor discovery runs →
New devices auto-created (pending approval) →
Recursive SNMP discovery on approved neighbors
UI Changes:
# New "Pending Devices" tab on Devices page
def handle_event("approve_discovered_device", %{"id" => id}, socket) do
case Devices.approve_discovered_device(id) do
{:ok, device} ->
# Enable SNMP polling
# Run discovery on new device
# Continue topology discovery if depth allows
{:error, _} ->
# Show error
end
end
Benefits for Towerops
For Network Operators
- Faster Onboarding: Add one device, discover entire network
- Topology Awareness: Visual understanding of network structure
- Change Detection: Alert when topology changes (new/missing links)
- Documentation: Auto-generated network diagrams for customers
- Troubleshooting: See physical path between devices
For Developers
- Path Analysis: Calculate shortest path between devices
- Impact Analysis: "If this device fails, what's affected?"
- Capacity Planning: Identify bottleneck links
- Configuration Validation: Verify redundancy in topology
Technical Advantages
- Standards-Based: LLDP is IEEE 802.1AB standard, vendor-neutral
- Low Overhead: Single SNMP walk per device
- Incremental Updates: Only re-scan changed devices
- Scalable: BFS with depth limit prevents resource exhaustion
Implementation Complexity
Estimated Effort:
- Phase 1 (Core Discovery): 2-3 days
- Phase 2 (Background Worker): 1 day
- Phase 3 (Visualization): 3-5 days
- Phase 4 (Auto-Discovery): 2 days
- Total: 8-11 days
Dependencies:
- No new external libraries needed (use existing SnmpKit)
- Graphviz optional (only for export features)
- D3.js or Cytoscape.js for visualization (already using Alpine.js)
Risks & Mitigation
Risks:
-
LLDP Not Enabled: Some devices don't have LLDP enabled
- Mitigation: Provide instructions to enable, support manual links
-
Management Address Missing: Neighbor has no mgmt IP
- Mitigation: Use chassis ID fallback like lldp2map does
-
Large Network Scan: Could overwhelm SNMP on many devices
- Mitigation: Rate limiting, depth limits, async background jobs
-
Stale Data: Topology changes not reflected in real-time
- Mitigation: Periodic re-scan, "last_seen_at" timestamps
Comparison: lldp2map vs Towerops Integration
| Feature | lldp2map (Go CLI) | Towerops Enhancement |
|---|---|---|
| Discovery | One-time, manual | Background cron + manual trigger |
| Storage | In-memory graph | PostgreSQL with TimescaleDB |
| Visualization | Static PNG/PDF | Interactive LiveView with real-time updates |
| Integration | Standalone | Integrated with existing devices, sites, alerts |
| Multi-tenancy | None | Organization/site scoped |
| Authentication | CLI flags | Inherited from device SNMP credentials |
| History | None | Track topology changes over time |
| Alerting | None | Alert on topology changes |
Recommended Next Steps
-
Prototype (1 day):
- Create basic
Lldp.discover_neighbors/1function - Test on production devices to verify LLDP support
- Validate OID parsing logic
- Create basic
-
Schema & Migration (0.5 days):
- Add
device_neighborstable - Create Ecto schema and context functions
- Add
-
Worker Implementation (1 day):
- Build
TopologyDiscoveryWorker - Add manual trigger UI
- Build
-
Visualization (3-5 days):
- Choose visualization library (recommend Cytoscape.js)
- Build interactive topology view
- Add export options
-
Auto-Discovery (2 days):
- Pending devices workflow
- Approval UI and logic
Code Quality Notes
lldp2map Strengths to Adopt:
- ✅ Comprehensive OID coverage - handles multiple MIB table formats
- ✅ Smart fallbacks - gracefully degrades when data missing
- ✅ Clean separation - SNMP, LLDP, Graph, Render all separate
- ✅ Robust parsing - handles malformed/unexpected SNMP responses
- ✅ IPv6 support - first-class support, not an afterthought
Additional Improvements for Towerops:
- Historical tracking - store topology snapshots for trend analysis
- Change alerting - notify when new neighbors appear/disappear
- Multi-site correlation - show connections between sites
- Performance metrics - link utilization on topology edges
- Agent integration - prefer local agent polling for LLDP data
Conclusion
lldp2map demonstrates a production-ready approach to network topology discovery that would significantly enhance Towerops' value proposition. The recursive BFS algorithm, comprehensive LLDP MIB coverage, and smart fallback mechanisms provide a solid foundation.
By adapting this approach to Elixir/Phoenix with background workers, persistent storage, and interactive visualization, Towerops can offer automated network discovery and topology mapping as a key differentiator from manual monitoring tools.
Recommendation: Implement Phases 1-2 (8-11 days) to provide immediate value, then iterate on visualization and auto-discovery based on customer feedback.