A Go tool that recursively walks SNMP LLDP neighbor tables across network devices and generates a topology diagram as PNG, PDF, Draw.io, or Excalidraw. Available as both a CLI and a cross-platform GUI.
- SNMPv2c and SNMPv3 support (MD5/SHA/SHA256/SHA512 auth; DES/AES/AES192/AES256 priv)
- Recursive BFS discovery — follows management addresses from each LLDP neighbor, with chassis ID and ARP/NDP-table fallback for devices that do not advertise explicit management addresses
- IPv6 NDP neighbor cache — resolves MAC-based chassis IDs to IPv6 addresses via the RFC 4293 unified neighbor table, falling back to the legacy IPv4-only ARP table
- Address family filtering —
--addr-family ipv4|ipv6|bothlimits both displayed labels and next-hop discovery to one address family - Prefix exclusion —
--ignore-prefixaccepts CIDR ranges to exclude from node labels and BFS discovery (repeatable) - Verbose discovery log — each BFS step explains why a device is queued, skipped, filtered, or cannot be recursed into
- Interface address labels — optionally annotate each node with its IPv4/IPv6 addresses via
--show-addrs - Four output formats — PNG and PDF (via Graphviz), Draw.io XML, Excalidraw JSON
- Cross-platform GUI — launch with
--guior build the dedicatedlldp2map-guibinary for a point-and-click interface with live log output and a Cancel button - Configurable hop depth, timeout, retries, and port
- Port labels on edges (local port → remote port)
- Full IPv6 support for both SNMP transport and LLDP management address discovery
- Go 1.21+
- Graphviz (
dotbinary must be in PATH) — required for PNG/PDF output only
# macOS
brew install graphviz
# Debian / Ubuntu
sudo apt install graphviz
# RHEL / Fedora
sudo dnf install graphvizThe GUI depends on OpenGL and (on Linux) X11. These are only needed when building with GUI support (the default).
# Debian / Ubuntu
sudo apt install libgl1-mesa-dev xorg-dev
# RHEL / Fedora
sudo dnf install mesa-libGL-devel libX11-develgit clone https://github.com/buraglio/lldp2map.git
cd lldp2map
go build -o lldp2map .Use the nogui build tag to produce a pure-Go CLI binary with no OpenGL or X11
requirements. This is the right choice for headless servers and CI environments.
go build -tags nogui -o lldp2map .go build -o lldp2map-gui ./cmd/lldp2map-guigo install fyne.io/fyne/v2/cmd/fyne@latest
fyne package -os darwin -appID io.github.buraglio.lldp2map -name lldp2mapgo install github.com/buraglio/lldp2map@latestlldp2map <host> [flags]
# Via the combined binary
lldp2map --gui
# Via the dedicated GUI binary
lldp2map-guiLaunches a Fyne-based desktop window with all flags exposed as form fields, a live scrolling discovery log, an infinite progress bar, and Cancel / Open Result buttons.
| Flag | Default | Description |
|---|---|---|
-c, --community |
public |
SNMPv2c community string |
-v, --version |
2c |
SNMP version: 2c or 3 |
--username |
SNMPv3 username | |
--auth-proto |
SHA |
SNMPv3 auth protocol: MD5, SHA, SHA256, SHA512 |
--auth-pass |
SNMPv3 authentication passphrase | |
--priv-proto |
AES |
SNMPv3 priv protocol: DES, AES, AES192, AES256 |
--priv-pass |
SNMPv3 privacy passphrase | |
--sec-level |
authpriv |
SNMPv3 security level: noauth, auth, authpriv |
--port |
161 |
SNMP UDP port |
--timeout |
5 |
SNMP timeout in seconds |
--retries |
2 |
SNMP retries per request |
--max-hops |
10 |
Maximum BFS depth for recursive discovery |
--show-addrs |
false |
Annotate nodes with interface IPv4/IPv6 addresses (walks IP-MIB on each device) |
--addr-family |
both |
Address family for --show-addrs labels and next-hop discovery: ipv4, ipv6, or both |
--ignore-prefix |
CIDR prefix to exclude from labels and discovery (repeatable) | |
-o, --output |
network-map.png |
Output file path |
-f, --format |
png |
Output format: png, pdf, drawio, excalidraw |
--gui |
Launch the graphical interface (must be the first and only argument) |
SNMPv2c, default community:
lldp2map -c public 3fff::1SNMPv2c, PDF output, limit to 3 hops:
lldp2map -c public -f pdf -o topology.pdf --max-hops 3 3fff::1SNMPv3 with auth and privacy (recommended):
lldp2map -v 3 \
--username netops \
--auth-proto SHA \
--auth-pass MyAuthPass \
--priv-proto AES \
--priv-pass MyPrivPass \
--sec-level authpriv \
-o network.png \
3fff:1::1SNMPv3 auth-only, show interface addresses:
lldp2map -v 3 \
--username monitor \
--auth-proto SHA256 \
--auth-pass MyAuthPass \
--sec-level auth \
--show-addrs \
3fff:1::1Show only IPv6 addresses, ignore documentation and loopback prefixes:
lldp2map -c public \
--show-addrs \
--addr-family ipv6 \
--ignore-prefix 3fff::/20 \
--ignore-prefix ::1/128 \
3fff::1Exclude RFC1918 and loopback from discovery and labels:
lldp2map -c public \
--ignore-prefix 10.0.0.0/8 \
--ignore-prefix 172.16.0.0/12 \
--ignore-prefix 192.168.0.0/16 \
--ignore-prefix 127.0.0.0/8 \
10.0.0.1Export to Draw.io:
lldp2map -c public -f drawio 3fff::1Export to Excalidraw:
lldp2map -c public -f excalidraw 3fff::1Launch GUI:
lldp2map --gui
# or
lldp2map-guiThe diagram above was generated from a synthetic topology using lldp2map --show-addrs. Each node shows the device name and, when --show-addrs is set, its interface addresses. Port labels appear near the originating device on each link.
- Connects to the seed device via SNMP and walks the LLDP-MIB remote neighbor table
- Extracts neighbor system names, local/remote port descriptions, and management addresses (IPv4 and IPv6)
- Enqueues each discovered management address into a BFS queue for recursive discovery
- Optionally walks the IP-MIB address table (
--show-addrs) to collect all interface addresses per device - Repeats until the queue is empty or
--max-hopsdepth is reached - Renders the completed graph to the requested output format
For recursive discovery, lldp2map uses a four-tier fallback to locate a reachable IP for each neighbor:
- LLDP management address (
lldpRemManAddrIfId) — explicit management IP advertised by the remote device - Chassis networkAddress (subtype 5) — IP encoded directly in the LLDP chassis ID field
- Chassis MAC → RFC 4293 NDP/ARP lookup (subtype 4) — resolves a MAC-addressed chassis ID to an IP via the queried device's unified IPv4+IPv6 neighbor table (
ipNetToPhysicalPhysAddress); prefers global-unicast IPv6 over IPv4 when both exist for the same MAC - Chassis MAC → legacy ARP fallback — if the RFC 4293 table is unavailable, falls back to the IPv4-only ARP table (
ipNetToMediaPhysAddress)
Neighbors for which no IP can be resolved are still added to the topology map but are logged as non-recursable. Devices that are only IPv6-reachable (common in dual-stack and IPv6-only networks) are handled by tier 3. This covers devices such as MikroTik routers that advertise a MAC chassis ID without an explicit management address.
If LLDP walk fails partway (e.g., the device responds to SNMP but does not implement the LLDP MIB), the device is still added to the topology using its IP and whatever system name was retrievable.
--addr-family and --ignore-prefix are applied consistently in two places:
- Node labels — addresses collected by
--show-addrsare filtered before being written to the diagram - BFS discovery — management addresses are filtered before being enqueued as next-hop targets; if all management addresses for a neighbor are excluded, the neighbor is logged as filtered and not recursed into
When --show-addrs is set, each device is additionally queried for its full interface address list using the IP-MIB:
- Primary:
ipAddressTable(RFC 4293,1.3.6.1.2.1.4.34) — covers both IPv4 and IPv6 - Fallback:
ipAddrTable(RFC 1213,1.3.6.1.2.1.4.20) — IPv4 only, used if the modern table is unavailable
Loopback (127.0.0.0/8, ::1) and link-local (fe80::/10) addresses are excluded. All other unicast addresses are shown in the node label, subject to --addr-family and --ignore-prefix filtering.
| Format | Flag | Extension | Requires |
|---|---|---|---|
| PNG | png |
.png |
Graphviz |
pdf |
.pdf |
Graphviz | |
| Draw.io | drawio |
.drawio |
Nothing |
| Excalidraw | excalidraw |
.excalidraw |
Nothing |
Draw.io and Excalidraw exports use a circular layout computed by lldp2map. Nodes can be freely repositioned in the editor after import. Draw.io edges re-route automatically when nodes are moved; Excalidraw lines do not (re-run the tool or drag endpoints manually).
| OID | Name | Purpose |
|---|---|---|
1.0.8802.1.1.2.1.3.3.0 |
lldpLocSysName | Local device hostname |
1.0.8802.1.1.2.1.3.7.1.4 |
lldpLocPortDesc | Local port descriptions |
1.0.8802.1.1.2.1.4.1.1.4 |
lldpRemChassisIdSubtype | Remote chassis ID subtype (4=MAC, 5=networkAddress) |
1.0.8802.1.1.2.1.4.1.1.5 |
lldpRemChassisId | Remote chassis ID (used for management address fallback) |
1.0.8802.1.1.2.1.4.1.1.7 |
lldpRemPortId | Remote port identifier |
1.0.8802.1.1.2.1.4.1.1.8 |
lldpRemPortDesc | Remote port description |
1.0.8802.1.1.2.1.4.1.1.9 |
lldpRemSysName | Remote system name |
1.0.8802.1.1.2.1.4.2.1.3 |
lldpRemManAddrIfId | Remote management addresses |
1.3.6.1.2.1.4.22.1.2 |
ipNetToMediaPhysAddress | ARP table (MAC→IPv4, legacy fallback) |
1.3.6.1.2.1.4.35.1.4 |
ipNetToPhysicalPhysAddress | Unified neighbor table (MAC→IPv4+IPv6, RFC 4293) |
1.3.6.1.2.1.4.34.1.3 |
ipAddressIfIndex | Interface addresses, IPv4+IPv6 (RFC 4293) |
1.3.6.1.2.1.4.20.1.1 |
ipAdEntAddr | Interface addresses, IPv4 only (RFC 1213, fallback) |
lldp2map/
├── main.go # Entry point (GUI+CLI); routes --gui to gui.Run()
├── main_nogui.go # Entry point (CLI only, build tag: nogui)
├── cmd/
│ ├── root.go # CLI flags and run() (Cobra)
│ └── lldp2map-gui/main.go # Standalone GUI binary entry point
├── gui/app.go # Fyne GUI (--gui flag or lldp2map-gui binary)
├── internal/
│ ├── discover/discover.go # BFS discovery engine (shared by CLI and GUI)
│ ├── filter/addr.go # Address family and prefix filtering
│ ├── snmp/client.go # SNMP v2c/v3 client (gosnmp)
│ ├── lldp/walker.go # LLDP MIB walker, OID parser, IP address walker
│ ├── graph/topology.go # In-memory topology graph
│ └── render/
│ ├── layout.go # Circular layout engine (shared)
│ ├── graphviz.go # PNG/PDF via Graphviz dot
│ ├── drawio.go # Draw.io XML export
│ └── excalidraw.go # Excalidraw JSON export
├── docs/
│ ├── gen-example.go # Synthetic example diagram generator (go:build ignore)
│ └── example.png # Example diagram embedded in this README
├── go.mod
└── go.sum
- fyne.io/fyne/v2 — cross-platform GUI framework
- github.com/gosnmp/gosnmp — SNMP v2c/v3
- github.com/spf13/cobra — CLI framework
Linux hosts running lldpd do not expose LLDP neighbor data via SNMP by default. lldpd must be configured to operate as an AgentX sub-agent alongside snmpd. Without this, lldp2map can still discover a Linux host as a neighbor (seen from an adjacent device's LLDP table), but it cannot recurse into that host to find its neighbors.
This is particularly useful on operating systems like proxmox which also house LXCs and VMs that may also be running lldpd. I don't know if this works on VMWare or HyperV, or if lldp is exposed under windows, althugh it can be enabled.
To enable SNMP on a Linux host running lldpd:
- Install and configure
snmpd, then add AgentX master support to/etc/snmp/snmpd.conf:
master agentx
- Start
lldpdwith the-xflag to connect as an AgentX sub-agent:
lldpd -xOr, in /etc/default/lldpd (Debian/Ubuntu) or /etc/sysconfig/lldpd (RHEL/Fedora):
DAEMON_ARGS="-x"
- Restart both services:
systemctl restart snmpd lldpdOnce configured, lldp2map can walk the LLDP-MIB on the Linux host just like any other device.
- Neighbors with no resolvable IP (no management address, no chassis networkAddress, and no ARP/NDP entry for their MAC) are added to the map but not recursed into. The discovery log explicitly reports this for each such neighbor.
--addr-familyand--ignore-prefixaffect both displayed labels and BFS discovery. If all management addresses for a neighbor are excluded by these filters, that neighbor will not be recursed into (this is logged clearly).- Duplicate edges (A→B and B→A) are automatically deduplicated.
- If
lldpLocSysNameis not available, the device IP is used as the node label. --show-addrsadds one extra SNMP walk per visited device. On large networks this increases discovery time.- Devices with SNMP ACLs must permit access from the host running lldp2map (you do have SNMP ACLs, right?).
- The GUI requires a display. On headless servers use the CLI.
- Example addresses in this README use the
3fff::/20documentation prefix defined in RFC 9637.