topology-scanner 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. topology_scanner-1.0.0/LICENSE +21 -0
  2. topology_scanner-1.0.0/PKG-INFO +246 -0
  3. topology_scanner-1.0.0/README.md +210 -0
  4. topology_scanner-1.0.0/pyproject.toml +79 -0
  5. topology_scanner-1.0.0/setup.cfg +4 -0
  6. topology_scanner-1.0.0/src/topology_scanner/__init__.py +11 -0
  7. topology_scanner-1.0.0/src/topology_scanner/__main__.py +4 -0
  8. topology_scanner-1.0.0/src/topology_scanner/classifier.py +122 -0
  9. topology_scanner-1.0.0/src/topology_scanner/cli.py +205 -0
  10. topology_scanner-1.0.0/src/topology_scanner/export.py +392 -0
  11. topology_scanner-1.0.0/src/topology_scanner/graph.py +178 -0
  12. topology_scanner-1.0.0/src/topology_scanner/history.py +252 -0
  13. topology_scanner-1.0.0/src/topology_scanner/rangos.py +66 -0
  14. topology_scanner-1.0.0/src/topology_scanner/scanner.py +160 -0
  15. topology_scanner-1.0.0/src/topology_scanner/snmp.py +783 -0
  16. topology_scanner-1.0.0/src/topology_scanner/webapp.py +1782 -0
  17. topology_scanner-1.0.0/src/topology_scanner.egg-info/PKG-INFO +246 -0
  18. topology_scanner-1.0.0/src/topology_scanner.egg-info/SOURCES.txt +31 -0
  19. topology_scanner-1.0.0/src/topology_scanner.egg-info/dependency_links.txt +1 -0
  20. topology_scanner-1.0.0/src/topology_scanner.egg-info/entry_points.txt +3 -0
  21. topology_scanner-1.0.0/src/topology_scanner.egg-info/requires.txt +15 -0
  22. topology_scanner-1.0.0/src/topology_scanner.egg-info/top_level.txt +1 -0
  23. topology_scanner-1.0.0/tests/test_classifier.py +104 -0
  24. topology_scanner-1.0.0/tests/test_cli.py +412 -0
  25. topology_scanner-1.0.0/tests/test_demo_arbol.py +58 -0
  26. topology_scanner-1.0.0/tests/test_export.py +623 -0
  27. topology_scanner-1.0.0/tests/test_graph.py +255 -0
  28. topology_scanner-1.0.0/tests/test_history.py +309 -0
  29. topology_scanner-1.0.0/tests/test_rangos.py +72 -0
  30. topology_scanner-1.0.0/tests/test_scanner.py +192 -0
  31. topology_scanner-1.0.0/tests/test_snmp.py +904 -0
  32. topology_scanner-1.0.0/tests/test_version.py +13 -0
  33. topology_scanner-1.0.0/tests/test_webapp.py +1114 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Serrano Marín
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.4
2
+ Name: topology-scanner
3
+ Version: 1.0.0
4
+ Summary: Network topology scanner (nmap + SNMP) with device classification and an interactive HTML map
5
+ Author: danielsm107
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/danielsm107/network-topology-scanner
8
+ Project-URL: Issues, https://github.com/danielsm107/network-topology-scanner/issues
9
+ Project-URL: Changelog, https://github.com/danielsm107/network-topology-scanner/blob/main/CHANGELOG.md
10
+ Keywords: nmap,snmp,lldp,network,topology,scanner,inventory
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Console
13
+ Classifier: Environment :: Web Environment
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: System :: Networking :: Monitoring
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: python-nmap>=0.7.1
24
+ Requires-Dist: networkx>=3.0
25
+ Requires-Dist: pyvis>=0.3.2
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == "dev"
28
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
29
+ Requires-Dist: ruff>=0.6; extra == "dev"
30
+ Provides-Extra: web
31
+ Requires-Dist: streamlit>=1.33; extra == "web"
32
+ Provides-Extra: snmp
33
+ Requires-Dist: pysnmp>=7.1; extra == "snmp"
34
+ Requires-Dist: cryptography>=43; extra == "snmp"
35
+ Dynamic: license-file
36
+
37
+ # Network Topology Scanner
38
+
39
+ [![CI](https://github.com/danielsm107/network-topology-scanner/actions/workflows/ci.yml/badge.svg)](https://github.com/danielsm107/network-topology-scanner/actions/workflows/ci.yml)
40
+
41
+ Scans a network range, discovers active hosts, their open ports/services,
42
+ vendor (via MAC) and device type, and generates an interactive HTML topology
43
+ graph with icons per category (router, PC, NAS, printer, etc). Flags hosts
44
+ with sensitive open ports in red (Telnet, SMB, RDP, FTP, VNC), stores every
45
+ scan in SQLite to diff against the previous one (new/dropped hosts, ports
46
+ that changed), and can export the inventory to CSV. Optionally queries your
47
+ routers/switches over SNMP to draw the **real topology** (which host hangs
48
+ from which switch port or access point) instead of an approximate star.
49
+ Available as a CLI or as a local web interface (Streamlit, optional).
50
+
51
+ ![Command Center dashboard](https://raw.githubusercontent.com/danielsm107/network-topology-scanner/main/docs/screenshots/dashboard.png)
52
+ ![Inventory and changes](https://raw.githubusercontent.com/danielsm107/network-topology-scanner/main/docs/screenshots/inventario.png)
53
+
54
+ *Screenshots use fictional demo data — no real network is exposed here.*
55
+
56
+ ## ⚠️ Legal notice
57
+ Only use this tool **on networks you own or have explicit authorization to
58
+ scan** (your homelab, or your workplace's network if you have permission).
59
+ Scanning networks without consent is illegal in most countries.
60
+
61
+ ## Project structure
62
+
63
+ ```
64
+ network-topology-scanner/
65
+ ├── src/topology_scanner/
66
+ │ ├── scanner.py # nmap scanning (discovery + ports)
67
+ │ ├── classifier.py # Device classification by MAC/vendor
68
+ │ ├── graph.py # Graph construction (networkx)
69
+ │ ├── export.py # HTML export (pyvis), text and CSV
70
+ │ ├── history.py # Scan history in SQLite (diff vs. the previous scan)
71
+ │ ├── snmp.py # Real topology via SNMP (ARP, routes, MAC table, neighbors)
72
+ │ ├── cli.py # Command-line interface
73
+ │ └── webapp.py # Web interface (Streamlit, optional)
74
+ ├── scripts/demo_arbol.py # Fictional SNMP tree for the README screenshot
75
+ ├── tests/ # pytest, with nmap mocks (no real network needed)
76
+ └── pyproject.toml # single source of dependencies (pip install -e ".[dev,web,snmp]")
77
+ ```
78
+
79
+ ## Installation
80
+
81
+ Requires Python 3.10+.
82
+
83
+ ```bash
84
+ # System dependency
85
+ sudo apt install nmap # Linux
86
+ # On Windows: download the installer from nmap.org
87
+
88
+ # Virtual environment
89
+ python3 -m venv venv
90
+ source venv/bin/activate # Linux/Mac
91
+ # venv\Scripts\Activate.ps1 # Windows PowerShell
92
+
93
+ # From PyPI
94
+ pip install topology-scanner
95
+ pip install "topology-scanner[web,snmp]" # optional: web interface and real topology via SNMP
96
+ ```
97
+
98
+ For development, from a clone of the repo:
99
+
100
+ ```bash
101
+ pip install -e ".[dev,web,snmp]"
102
+ ```
103
+
104
+ ## Usage
105
+
106
+ ```bash
107
+ sudo topology-scanner 192.168.1.0/24
108
+ sudo topology-scanner 192.168.1.0/24 --rapido
109
+ sudo topology-scanner 192.168.1.0/24 --con-so --output my_network.html
110
+
111
+ # Several ranges at once (e.g. your LAN and a homelab subnet behind another router)
112
+ sudo topology-scanner 192.168.1.0/24 10.0.10.0/24
113
+
114
+ # Real topology: query the router(s) over SNMP (see below)
115
+ export TOPOLOGY_SNMP_COMMUNITY='your-read-only-community'
116
+ sudo -E topology-scanner 192.168.1.0/24 --snmp 192.168.1.1
117
+
118
+ # Alternative without the installed entry point:
119
+ sudo python3 -m topology_scanner 192.168.1.0/24
120
+ ```
121
+
122
+ | Flag | Description | Default |
123
+ |-------------------|-------------------------------------------------------------------|-----------------------|
124
+ | `range` | CIDR range(s) to scan, space-separated; history is kept per range | (required) |
125
+ | `--ports` | Ports to scan (nmap format) | common ports |
126
+ | `--nmap-args` | Extra arguments passed to nmap | `-sV -T4` |
127
+ | `--output` | Output HTML file name | `topologia_red.html` |
128
+ | `--con-so` | Enables OS detection (-O), the slowest option | disabled |
129
+ | `--sin-2-fases` | Disables the preliminary ping scan, scans the full range directly | disabled |
130
+ | `--rapido` | Max-speed preset, ports only (incompatible with `--con-so`/`--nmap-args`) | disabled |
131
+ | `--csv` | Also exports a CSV inventory to the given path | disabled |
132
+ | `--history-db` | SQLite file where scan history is stored | `historial.db` |
133
+ | `--sin-historial` | Skips saving the scan to history / diffing against the previous one | disabled |
134
+ | `--snmp` | Routers/switches to query over SNMP v2c (comma-separated IPs) | disabled (star layout) |
135
+
136
+ ## Web interface
137
+
138
+ Requires the `[web]` extra (see Installation). Form with automatic detection
139
+ of your local network, real cancellable scanning (kills the actual nmap
140
+ process, not just the UI), results table, CSV download and the embedded
141
+ graph. Several ranges can be scanned at once by separating them with
142
+ spaces in the range field. The "Real topology via SNMP" switch in the sidebar draws the tree
143
+ described below, over SNMP v2c or v3 (community and keys are password
144
+ inputs; leave them empty to use the `TOPOLOGY_SNMP_*` environment variables).
145
+
146
+ ```bash
147
+ topology-scanner-web
148
+ # or, without the installed entry point:
149
+ streamlit run src/topology_scanner/webapp.py
150
+ ```
151
+
152
+ ## Real topology via SNMP
153
+
154
+ By default the graph is an approximate star: nmap knows *which* hosts are
155
+ alive, not *how* they are connected. With `--snmp`, the scanner reads a few
156
+ standard tables from your routers/switches and builds a tree instead:
157
+
158
+ | SNMP table | What it tells | Used for |
159
+ |---|---|---|
160
+ | Routes (`ipCidrRouteTable`) | "10.0.10.0/24 is behind 192.168.1.44" | Upstream gateway, downstream routers and their subnets |
161
+ | Neighbors (MikroTik `mtxrNeighborTable`, MNDP/LLDP/CDP) | "Access point X is on my ether2" | Links between network devices |
162
+ | Neighbors (standard `LLDP-MIB`, any vendor) | "Switch Y sees me on its port3" | Links, with the port on **both** ends (`ether2 ↔ port3`) |
163
+ | MAC table (`BRIDGE-MIB`) | "MAC aa:bb:… is on port wlan1" | Which port each host is on |
164
+ | ARP (`ipNetToMediaTable`) | "192.168.1.20 is aa:bb:…" | Matching MACs to the IPs nmap found |
165
+
166
+ Hosts seen on a port that leads to another network device hang from that
167
+ device; hosts that only SNMP sees (not detected by nmap) are shown too —
168
+ an unknown device on a switch port is exactly what you want to notice. If no
169
+ device answers, the scan still finishes with the star layout.
170
+
171
+ ![Real topology via SNMP](https://raw.githubusercontent.com/danielsm107/network-topology-scanner/main/docs/screenshots/arbol-snmp.png)
172
+
173
+ *Fictional demo data, generated with `python scripts/demo_arbol.py` (no scan involved).*
174
+
175
+ **Setup** (MikroTik RouterOS example — read-only, restricted to the
176
+ scanning machine):
177
+
178
+ ```
179
+ /snmp community add name=<long-random-string> addresses=<scanner-ip>/32 read-access=yes write-access=no
180
+ /snmp community set [find name=public] disabled=yes
181
+ /snmp set enabled=yes
182
+ ```
183
+
184
+ The community is read from the `TOPOLOGY_SNMP_COMMUNITY` environment
185
+ variable, never from a command-line argument (arguments show up in `ps` and
186
+ in your shell history). On Windows PowerShell:
187
+ `$env:TOPOLOGY_SNMP_COMMUNITY = "..."`.
188
+
189
+ **SNMPv3** (recommended: v2c sends the community in clear text). In RouterOS
190
+ v6 the community name is the v3 user, and `security=private` means
191
+ authentication + encryption:
192
+
193
+ ```
194
+ /snmp community add name=<user> addresses=<scanner-ip>/32 security=private \
195
+ authentication-protocol=SHA1 authentication-password=<8+ chars> \
196
+ encryption-protocol=AES encryption-password=<8+ chars> read-access=yes write-access=no
197
+ ```
198
+
199
+ | Variable | Meaning | Default |
200
+ |---|---|---|
201
+ | `TOPOLOGY_SNMP_USER` | v3 user (if set, v3 is used and the community is ignored) | — |
202
+ | `TOPOLOGY_SNMP_AUTH_KEY` | Authentication key (8+ characters) | required |
203
+ | `TOPOLOGY_SNMP_PRIV_KEY` | Encryption key (8+ characters); empty = authenticate without encryption | — |
204
+ | `TOPOLOGY_SNMP_AUTH_PROTO` | `MD5`, `SHA`, `SHA224`, `SHA256`, `SHA384`, `SHA512` | `SHA` |
205
+ | `TOPOLOGY_SNMP_PRIV_PROTO` | `DES`, `AES`, `AES192`, `AES256` | `AES` |
206
+
207
+ **Current limitations**: links between network devices come
208
+ from MikroTik's neighbor table and from the standard LLDP-MIB, so devices
209
+ that don't run LLDP (or don't expose it over SNMP) are only placed by
210
+ routes, ARP and the MAC table.
211
+
212
+ ## Device classification
213
+
214
+ Each host's MAC address is used to look up its vendor (via nmap's built-in
215
+ database), which is then heuristically mapped to a category with its own
216
+ icon: router, firewall, VM, NAS, printer, camera, IoT, mobile, Apple, PC.
217
+
218
+ **Limitation**: MAC resolution only works if the scanning machine is on the
219
+ **same L2 segment** as the target host (ARP doesn't cross routers/VLANs).
220
+ Hosts on other subnets will show up as category "unknown".
221
+
222
+ ## Tests
223
+
224
+ ```bash
225
+ pytest tests/ -v
226
+ pytest tests/ --cov=topology_scanner # with coverage
227
+ ```
228
+
229
+ `scanner.py` tests use `unittest.mock` to simulate nmap's responses, so they
230
+ run without a real network or the nmap binary installed.
231
+
232
+ ## Roadmap
233
+
234
+ - [x] Visual alerts for sensitive open ports (RDP, Telnet, SMB, FTP, VNC)
235
+ - [x] Scan history in SQLite (new/dropped hosts, ports that change)
236
+ - [x] CSV export for audits/inventory
237
+ - [x] Visual icon/color legend in the HTML
238
+ - [x] Web interface with Streamlit
239
+ - [x] CI: run tests + ruff on every push
240
+ - [x] Real topology via SNMP against routers/switches, CLI (instead of the approximate star layout)
241
+ - [x] SNMP in the web interface
242
+ - [x] Standard LLDP-MIB (links between devices from any vendor, with the port on both ends)
243
+ - [x] SNMPv3 (authentication and encryption)
244
+ - [x] v1.0 on PyPI, published by GitHub Actions from a tag (trusted publishing, no tokens)
245
+
246
+ See [CHANGELOG.md](https://github.com/danielsm107/network-topology-scanner/blob/main/CHANGELOG.md) for release notes.
@@ -0,0 +1,210 @@
1
+ # Network Topology Scanner
2
+
3
+ [![CI](https://github.com/danielsm107/network-topology-scanner/actions/workflows/ci.yml/badge.svg)](https://github.com/danielsm107/network-topology-scanner/actions/workflows/ci.yml)
4
+
5
+ Scans a network range, discovers active hosts, their open ports/services,
6
+ vendor (via MAC) and device type, and generates an interactive HTML topology
7
+ graph with icons per category (router, PC, NAS, printer, etc). Flags hosts
8
+ with sensitive open ports in red (Telnet, SMB, RDP, FTP, VNC), stores every
9
+ scan in SQLite to diff against the previous one (new/dropped hosts, ports
10
+ that changed), and can export the inventory to CSV. Optionally queries your
11
+ routers/switches over SNMP to draw the **real topology** (which host hangs
12
+ from which switch port or access point) instead of an approximate star.
13
+ Available as a CLI or as a local web interface (Streamlit, optional).
14
+
15
+ ![Command Center dashboard](https://raw.githubusercontent.com/danielsm107/network-topology-scanner/main/docs/screenshots/dashboard.png)
16
+ ![Inventory and changes](https://raw.githubusercontent.com/danielsm107/network-topology-scanner/main/docs/screenshots/inventario.png)
17
+
18
+ *Screenshots use fictional demo data — no real network is exposed here.*
19
+
20
+ ## ⚠️ Legal notice
21
+ Only use this tool **on networks you own or have explicit authorization to
22
+ scan** (your homelab, or your workplace's network if you have permission).
23
+ Scanning networks without consent is illegal in most countries.
24
+
25
+ ## Project structure
26
+
27
+ ```
28
+ network-topology-scanner/
29
+ ├── src/topology_scanner/
30
+ │ ├── scanner.py # nmap scanning (discovery + ports)
31
+ │ ├── classifier.py # Device classification by MAC/vendor
32
+ │ ├── graph.py # Graph construction (networkx)
33
+ │ ├── export.py # HTML export (pyvis), text and CSV
34
+ │ ├── history.py # Scan history in SQLite (diff vs. the previous scan)
35
+ │ ├── snmp.py # Real topology via SNMP (ARP, routes, MAC table, neighbors)
36
+ │ ├── cli.py # Command-line interface
37
+ │ └── webapp.py # Web interface (Streamlit, optional)
38
+ ├── scripts/demo_arbol.py # Fictional SNMP tree for the README screenshot
39
+ ├── tests/ # pytest, with nmap mocks (no real network needed)
40
+ └── pyproject.toml # single source of dependencies (pip install -e ".[dev,web,snmp]")
41
+ ```
42
+
43
+ ## Installation
44
+
45
+ Requires Python 3.10+.
46
+
47
+ ```bash
48
+ # System dependency
49
+ sudo apt install nmap # Linux
50
+ # On Windows: download the installer from nmap.org
51
+
52
+ # Virtual environment
53
+ python3 -m venv venv
54
+ source venv/bin/activate # Linux/Mac
55
+ # venv\Scripts\Activate.ps1 # Windows PowerShell
56
+
57
+ # From PyPI
58
+ pip install topology-scanner
59
+ pip install "topology-scanner[web,snmp]" # optional: web interface and real topology via SNMP
60
+ ```
61
+
62
+ For development, from a clone of the repo:
63
+
64
+ ```bash
65
+ pip install -e ".[dev,web,snmp]"
66
+ ```
67
+
68
+ ## Usage
69
+
70
+ ```bash
71
+ sudo topology-scanner 192.168.1.0/24
72
+ sudo topology-scanner 192.168.1.0/24 --rapido
73
+ sudo topology-scanner 192.168.1.0/24 --con-so --output my_network.html
74
+
75
+ # Several ranges at once (e.g. your LAN and a homelab subnet behind another router)
76
+ sudo topology-scanner 192.168.1.0/24 10.0.10.0/24
77
+
78
+ # Real topology: query the router(s) over SNMP (see below)
79
+ export TOPOLOGY_SNMP_COMMUNITY='your-read-only-community'
80
+ sudo -E topology-scanner 192.168.1.0/24 --snmp 192.168.1.1
81
+
82
+ # Alternative without the installed entry point:
83
+ sudo python3 -m topology_scanner 192.168.1.0/24
84
+ ```
85
+
86
+ | Flag | Description | Default |
87
+ |-------------------|-------------------------------------------------------------------|-----------------------|
88
+ | `range` | CIDR range(s) to scan, space-separated; history is kept per range | (required) |
89
+ | `--ports` | Ports to scan (nmap format) | common ports |
90
+ | `--nmap-args` | Extra arguments passed to nmap | `-sV -T4` |
91
+ | `--output` | Output HTML file name | `topologia_red.html` |
92
+ | `--con-so` | Enables OS detection (-O), the slowest option | disabled |
93
+ | `--sin-2-fases` | Disables the preliminary ping scan, scans the full range directly | disabled |
94
+ | `--rapido` | Max-speed preset, ports only (incompatible with `--con-so`/`--nmap-args`) | disabled |
95
+ | `--csv` | Also exports a CSV inventory to the given path | disabled |
96
+ | `--history-db` | SQLite file where scan history is stored | `historial.db` |
97
+ | `--sin-historial` | Skips saving the scan to history / diffing against the previous one | disabled |
98
+ | `--snmp` | Routers/switches to query over SNMP v2c (comma-separated IPs) | disabled (star layout) |
99
+
100
+ ## Web interface
101
+
102
+ Requires the `[web]` extra (see Installation). Form with automatic detection
103
+ of your local network, real cancellable scanning (kills the actual nmap
104
+ process, not just the UI), results table, CSV download and the embedded
105
+ graph. Several ranges can be scanned at once by separating them with
106
+ spaces in the range field. The "Real topology via SNMP" switch in the sidebar draws the tree
107
+ described below, over SNMP v2c or v3 (community and keys are password
108
+ inputs; leave them empty to use the `TOPOLOGY_SNMP_*` environment variables).
109
+
110
+ ```bash
111
+ topology-scanner-web
112
+ # or, without the installed entry point:
113
+ streamlit run src/topology_scanner/webapp.py
114
+ ```
115
+
116
+ ## Real topology via SNMP
117
+
118
+ By default the graph is an approximate star: nmap knows *which* hosts are
119
+ alive, not *how* they are connected. With `--snmp`, the scanner reads a few
120
+ standard tables from your routers/switches and builds a tree instead:
121
+
122
+ | SNMP table | What it tells | Used for |
123
+ |---|---|---|
124
+ | Routes (`ipCidrRouteTable`) | "10.0.10.0/24 is behind 192.168.1.44" | Upstream gateway, downstream routers and their subnets |
125
+ | Neighbors (MikroTik `mtxrNeighborTable`, MNDP/LLDP/CDP) | "Access point X is on my ether2" | Links between network devices |
126
+ | Neighbors (standard `LLDP-MIB`, any vendor) | "Switch Y sees me on its port3" | Links, with the port on **both** ends (`ether2 ↔ port3`) |
127
+ | MAC table (`BRIDGE-MIB`) | "MAC aa:bb:… is on port wlan1" | Which port each host is on |
128
+ | ARP (`ipNetToMediaTable`) | "192.168.1.20 is aa:bb:…" | Matching MACs to the IPs nmap found |
129
+
130
+ Hosts seen on a port that leads to another network device hang from that
131
+ device; hosts that only SNMP sees (not detected by nmap) are shown too —
132
+ an unknown device on a switch port is exactly what you want to notice. If no
133
+ device answers, the scan still finishes with the star layout.
134
+
135
+ ![Real topology via SNMP](https://raw.githubusercontent.com/danielsm107/network-topology-scanner/main/docs/screenshots/arbol-snmp.png)
136
+
137
+ *Fictional demo data, generated with `python scripts/demo_arbol.py` (no scan involved).*
138
+
139
+ **Setup** (MikroTik RouterOS example — read-only, restricted to the
140
+ scanning machine):
141
+
142
+ ```
143
+ /snmp community add name=<long-random-string> addresses=<scanner-ip>/32 read-access=yes write-access=no
144
+ /snmp community set [find name=public] disabled=yes
145
+ /snmp set enabled=yes
146
+ ```
147
+
148
+ The community is read from the `TOPOLOGY_SNMP_COMMUNITY` environment
149
+ variable, never from a command-line argument (arguments show up in `ps` and
150
+ in your shell history). On Windows PowerShell:
151
+ `$env:TOPOLOGY_SNMP_COMMUNITY = "..."`.
152
+
153
+ **SNMPv3** (recommended: v2c sends the community in clear text). In RouterOS
154
+ v6 the community name is the v3 user, and `security=private` means
155
+ authentication + encryption:
156
+
157
+ ```
158
+ /snmp community add name=<user> addresses=<scanner-ip>/32 security=private \
159
+ authentication-protocol=SHA1 authentication-password=<8+ chars> \
160
+ encryption-protocol=AES encryption-password=<8+ chars> read-access=yes write-access=no
161
+ ```
162
+
163
+ | Variable | Meaning | Default |
164
+ |---|---|---|
165
+ | `TOPOLOGY_SNMP_USER` | v3 user (if set, v3 is used and the community is ignored) | — |
166
+ | `TOPOLOGY_SNMP_AUTH_KEY` | Authentication key (8+ characters) | required |
167
+ | `TOPOLOGY_SNMP_PRIV_KEY` | Encryption key (8+ characters); empty = authenticate without encryption | — |
168
+ | `TOPOLOGY_SNMP_AUTH_PROTO` | `MD5`, `SHA`, `SHA224`, `SHA256`, `SHA384`, `SHA512` | `SHA` |
169
+ | `TOPOLOGY_SNMP_PRIV_PROTO` | `DES`, `AES`, `AES192`, `AES256` | `AES` |
170
+
171
+ **Current limitations**: links between network devices come
172
+ from MikroTik's neighbor table and from the standard LLDP-MIB, so devices
173
+ that don't run LLDP (or don't expose it over SNMP) are only placed by
174
+ routes, ARP and the MAC table.
175
+
176
+ ## Device classification
177
+
178
+ Each host's MAC address is used to look up its vendor (via nmap's built-in
179
+ database), which is then heuristically mapped to a category with its own
180
+ icon: router, firewall, VM, NAS, printer, camera, IoT, mobile, Apple, PC.
181
+
182
+ **Limitation**: MAC resolution only works if the scanning machine is on the
183
+ **same L2 segment** as the target host (ARP doesn't cross routers/VLANs).
184
+ Hosts on other subnets will show up as category "unknown".
185
+
186
+ ## Tests
187
+
188
+ ```bash
189
+ pytest tests/ -v
190
+ pytest tests/ --cov=topology_scanner # with coverage
191
+ ```
192
+
193
+ `scanner.py` tests use `unittest.mock` to simulate nmap's responses, so they
194
+ run without a real network or the nmap binary installed.
195
+
196
+ ## Roadmap
197
+
198
+ - [x] Visual alerts for sensitive open ports (RDP, Telnet, SMB, FTP, VNC)
199
+ - [x] Scan history in SQLite (new/dropped hosts, ports that change)
200
+ - [x] CSV export for audits/inventory
201
+ - [x] Visual icon/color legend in the HTML
202
+ - [x] Web interface with Streamlit
203
+ - [x] CI: run tests + ruff on every push
204
+ - [x] Real topology via SNMP against routers/switches, CLI (instead of the approximate star layout)
205
+ - [x] SNMP in the web interface
206
+ - [x] Standard LLDP-MIB (links between devices from any vendor, with the port on both ends)
207
+ - [x] SNMPv3 (authentication and encryption)
208
+ - [x] v1.0 on PyPI, published by GitHub Actions from a tag (trusted publishing, no tokens)
209
+
210
+ See [CHANGELOG.md](https://github.com/danielsm107/network-topology-scanner/blob/main/CHANGELOG.md) for release notes.
@@ -0,0 +1,79 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "topology-scanner"
7
+ version = "1.0.0"
8
+ description = "Network topology scanner (nmap + SNMP) with device classification and an interactive HTML map"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "danielsm107" }]
13
+ keywords = ["nmap", "snmp", "lldp", "network", "topology", "scanner", "inventory"]
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Environment :: Console",
17
+ "Environment :: Web Environment",
18
+ "Intended Audience :: System Administrators",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: System :: Networking :: Monitoring",
24
+ ]
25
+ dependencies = [
26
+ "python-nmap>=0.7.1",
27
+ "networkx>=3.0",
28
+ "pyvis>=0.3.2",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "pytest>=8.0",
34
+ "pytest-cov>=5.0",
35
+ "ruff>=0.6",
36
+ ]
37
+ web = [
38
+ # >=1.33 por st.fragment(run_every=...), que usa _fragmento_progreso()
39
+ # para refrescar el estado del escaneo sin recargar toda la página.
40
+ "streamlit>=1.33",
41
+ ]
42
+ snmp = [
43
+ # >=7.1: API asyncio con nombres snake_case (walk_cmd,
44
+ # UdpTransportTarget.create) que usa snmp.py. Exige Python >=3.10.
45
+ "pysnmp>=7.1",
46
+ # pysnmp la usa para cifrar en SNMPv3 (AES/DES) pero no la declara como
47
+ # dependencia; >=43 por cryptography.hazmat.decrepit, que es lo que importa.
48
+ "cryptography>=43",
49
+ ]
50
+
51
+ [project.urls]
52
+ Homepage = "https://github.com/danielsm107/network-topology-scanner"
53
+ Issues = "https://github.com/danielsm107/network-topology-scanner/issues"
54
+ Changelog = "https://github.com/danielsm107/network-topology-scanner/blob/main/CHANGELOG.md"
55
+
56
+ [project.scripts]
57
+ topology-scanner = "topology_scanner.cli:main"
58
+ topology-scanner-web = "topology_scanner.webapp:lanzar"
59
+
60
+ [tool.setuptools.packages.find]
61
+ where = ["src"]
62
+
63
+ [tool.pytest.ini_options]
64
+ pythonpath = ["src"]
65
+ testpaths = ["tests"]
66
+
67
+ [tool.ruff]
68
+ target-version = "py310"
69
+ # El código ya usa líneas más largas que las 88 por defecto (diccionarios de
70
+ # datos, f-strings en español) sin que aporte legibilidad partirlas todas.
71
+ line-length = 120
72
+
73
+ [tool.ruff.lint]
74
+ # Sin esto, ruff sin [tool.ruff.lint] activa ~800 reglas (prácticamente
75
+ # "ALL", incluidas categorías irrelevantes aquí como async/trio o
76
+ # compatibilidad py2/3) - demasiado ruido para CI. Selección explícita:
77
+ # pyflakes + pycodestyle, imports (I, ya usado para ordenar), pyupgrade,
78
+ # bugbear, simplify y las reglas propias de ruff.
79
+ select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ """topology_scanner — escáner de topología de red con nmap + networkx + pyvis."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ # pyproject.toml es la única fuente de verdad para la versión - antes
7
+ # había una copia a mano aquí que podía desincronizarse.
8
+ __version__ = version("topology-scanner")
9
+ except PackageNotFoundError:
10
+ # Código fuente sin instalar (ni siquiera "pip install -e .")
11
+ __version__ = "0+unknown"
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()