devbits 1.3.3__tar.gz → 1.4.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.
- {devbits-1.3.3 → devbits-1.4.0}/PKG-INFO +66 -1
- {devbits-1.3.3 → devbits-1.4.0}/README.md +65 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits/__init__.py +1 -1
- {devbits-1.3.3 → devbits-1.4.0}/devbits/cli.py +135 -2
- devbits-1.4.0/devbits/network.py +967 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits/scripts.py +4 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits.egg-info/PKG-INFO +66 -1
- {devbits-1.3.3 → devbits-1.4.0}/devbits.egg-info/SOURCES.txt +1 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits.egg-info/entry_points.txt +1 -0
- {devbits-1.3.3 → devbits-1.4.0}/pyproject.toml +2 -1
- devbits-1.4.0/tests/test_netsurvey.py +346 -0
- devbits-1.3.3/devbits/network.py +0 -311
- {devbits-1.3.3 → devbits-1.4.0}/LICENSE +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits/cache.py +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits/gui.py +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits/image.py +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits/media.py +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits/project.py +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits/tui.py +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits/utils.py +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits/wifi.py +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits.egg-info/dependency_links.txt +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits.egg-info/requires.txt +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/devbits.egg-info/top_level.txt +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/setup.cfg +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/tests/test_cli.py +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/tests/test_gui_cli.py +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/tests/test_tui.py +0 -0
- {devbits-1.3.3 → devbits-1.4.0}/tests/test_wifi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devbits
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: A lightweight CLI toolkit for daily development utilities.
|
|
5
5
|
Author: Bruce Chuang
|
|
6
6
|
License-Expression: MIT
|
|
@@ -85,6 +85,7 @@ clipvideo --help
|
|
|
85
85
|
| Command | Description |
|
|
86
86
|
|---------|-------------|
|
|
87
87
|
| `netscan` | List devices connected to your local network (Wi-Fi / router) with their IP, MAC, and hostname. `--lookup` adds the manufacturer. |
|
|
88
|
+
| `netsurvey` | Map **every network segment** visible on the current link — your own interfaces, foreign subnets on the same switch, the addresses in use, and the conflicts between them. Built for shared networks (trade shows, offices). |
|
|
88
89
|
| `wifi` | Manage Wi-Fi: `list`, `connect` (arrow-key picker + hidden password prompt), `on`, `off`, `forget`. Linux and Windows fully; macOS without `list`. |
|
|
89
90
|
|
|
90
91
|
## Examples
|
|
@@ -124,6 +125,18 @@ netscan --lookup
|
|
|
124
125
|
# Scan a specific subnet, faster, without hostname lookups
|
|
125
126
|
netscan --network 192.168.1.0/24 --timeout 0.5 --no-resolve
|
|
126
127
|
|
|
128
|
+
# Map every network segment and address around you (passive, ~2s)
|
|
129
|
+
netsurvey
|
|
130
|
+
|
|
131
|
+
# Segments and conflicts only, no address lists
|
|
132
|
+
netsurvey --summary
|
|
133
|
+
|
|
134
|
+
# Also ping-sweep your ranges and the discovered ones
|
|
135
|
+
netsurvey --sweep
|
|
136
|
+
|
|
137
|
+
# Check specific ranges before assigning them at a venue
|
|
138
|
+
netsurvey --include 192.168.1.0/24 --include 10.0.0.0/24
|
|
139
|
+
|
|
127
140
|
# Show the Wi-Fi networks in range (Linux / Windows)
|
|
128
141
|
wifi list
|
|
129
142
|
|
|
@@ -146,6 +159,58 @@ wifi forget OldCafe
|
|
|
146
159
|
> laptops that use a randomized/private MAC show up as `(private)` and can't be
|
|
147
160
|
> attributed to a vendor.
|
|
148
161
|
|
|
162
|
+
### Surveying a shared network
|
|
163
|
+
|
|
164
|
+
`netscan` answers "who is on **my** subnet?". `netsurvey` answers "what is on
|
|
165
|
+
this **wire**?" — the question that matters in an exhibition hall, an office, or
|
|
166
|
+
a co-working space where other people's routers hand out their own ranges:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
INTERFACES
|
|
170
|
+
en0 192.168.0.122/24 gateway 192.168.0.1
|
|
171
|
+
utun6 10.2.244.44/32
|
|
172
|
+
|
|
173
|
+
SEGMENTS (3 total, 2 yours, 9 address(es) in use)
|
|
174
|
+
192.168.0.0/24 local (en0) swept 6 host(s)
|
|
175
|
+
192.168.0.1 06:f2:67:75:4d:e2 gateway,arp,ping gateway / router
|
|
176
|
+
192.168.0.122 4e:e5:41:93:7c:1f self,arp,mdns this device
|
|
177
|
+
...
|
|
178
|
+
10.77.0.0/24 foreign passive 2 host(s)
|
|
179
|
+
10.77.0.9 de:ad:be:ef:00:01 ssdp,arp
|
|
180
|
+
|
|
181
|
+
CONFLICTS (2)
|
|
182
|
+
! [duplicate-ip] 192.168.0.50 answers from 2 MAC addresses (…) — two devices claim the same address
|
|
183
|
+
- [shared-l2] 1 foreign segment(s) share this link: 10.77.0.0/24
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Three independent sources are combined, so segments you have no address in still
|
|
187
|
+
show up:
|
|
188
|
+
|
|
189
|
+
- **every local interface** with its real netmask (`ip` / `ifconfig` / `ipconfig`);
|
|
190
|
+
- the **ARP / neighbour cache**, which lists link neighbours *regardless of
|
|
191
|
+
subnet* — this is what exposes somebody else's range on the same switch;
|
|
192
|
+
- **SSDP and mDNS** answers, sent out of each interface (skip with `--no-passive`).
|
|
193
|
+
|
|
194
|
+
The default pass is passive and takes about two seconds. `--sweep` additionally
|
|
195
|
+
ping-sweeps your ranges and the discovered ones so each address list is complete;
|
|
196
|
+
`--include CIDR` sweeps a specific range you are about to assign. Ranges larger
|
|
197
|
+
than `--max-sweep` (4096 hosts) are narrowed to the /24 around your own address
|
|
198
|
+
or skipped, with a note on stderr.
|
|
199
|
+
|
|
200
|
+
Reported conflicts:
|
|
201
|
+
|
|
202
|
+
| Kind | Meaning |
|
|
203
|
+
|------|---------|
|
|
204
|
+
| `duplicate-ip` | One address answers from two MACs — two devices claim it. |
|
|
205
|
+
| `overlap` | A foreign range, or a second local interface (VPN!), overlaps one of your subnets. |
|
|
206
|
+
| `no-dhcp` | An interface self-assigned a `169.254.x.x` address; no DHCP server answered. |
|
|
207
|
+
| `router-bridge` | One MAC answers for addresses in several segments — a router joining them. |
|
|
208
|
+
| `shared-l2` | Foreign segments are present on your link (informational). |
|
|
209
|
+
|
|
210
|
+
> Survey only networks you are entitled to. `netsurvey` sends ordinary pings and
|
|
211
|
+
> standard discovery multicast — nothing privileged — but probing networks you
|
|
212
|
+
> don't administer may still violate policy or law.
|
|
213
|
+
|
|
149
214
|
### Wi-Fi
|
|
150
215
|
|
|
151
216
|
`wifi connect` lists everything in range — move with ↑/↓, press Enter to join,
|
|
@@ -68,6 +68,7 @@ clipvideo --help
|
|
|
68
68
|
| Command | Description |
|
|
69
69
|
|---------|-------------|
|
|
70
70
|
| `netscan` | List devices connected to your local network (Wi-Fi / router) with their IP, MAC, and hostname. `--lookup` adds the manufacturer. |
|
|
71
|
+
| `netsurvey` | Map **every network segment** visible on the current link — your own interfaces, foreign subnets on the same switch, the addresses in use, and the conflicts between them. Built for shared networks (trade shows, offices). |
|
|
71
72
|
| `wifi` | Manage Wi-Fi: `list`, `connect` (arrow-key picker + hidden password prompt), `on`, `off`, `forget`. Linux and Windows fully; macOS without `list`. |
|
|
72
73
|
|
|
73
74
|
## Examples
|
|
@@ -107,6 +108,18 @@ netscan --lookup
|
|
|
107
108
|
# Scan a specific subnet, faster, without hostname lookups
|
|
108
109
|
netscan --network 192.168.1.0/24 --timeout 0.5 --no-resolve
|
|
109
110
|
|
|
111
|
+
# Map every network segment and address around you (passive, ~2s)
|
|
112
|
+
netsurvey
|
|
113
|
+
|
|
114
|
+
# Segments and conflicts only, no address lists
|
|
115
|
+
netsurvey --summary
|
|
116
|
+
|
|
117
|
+
# Also ping-sweep your ranges and the discovered ones
|
|
118
|
+
netsurvey --sweep
|
|
119
|
+
|
|
120
|
+
# Check specific ranges before assigning them at a venue
|
|
121
|
+
netsurvey --include 192.168.1.0/24 --include 10.0.0.0/24
|
|
122
|
+
|
|
110
123
|
# Show the Wi-Fi networks in range (Linux / Windows)
|
|
111
124
|
wifi list
|
|
112
125
|
|
|
@@ -129,6 +142,58 @@ wifi forget OldCafe
|
|
|
129
142
|
> laptops that use a randomized/private MAC show up as `(private)` and can't be
|
|
130
143
|
> attributed to a vendor.
|
|
131
144
|
|
|
145
|
+
### Surveying a shared network
|
|
146
|
+
|
|
147
|
+
`netscan` answers "who is on **my** subnet?". `netsurvey` answers "what is on
|
|
148
|
+
this **wire**?" — the question that matters in an exhibition hall, an office, or
|
|
149
|
+
a co-working space where other people's routers hand out their own ranges:
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
INTERFACES
|
|
153
|
+
en0 192.168.0.122/24 gateway 192.168.0.1
|
|
154
|
+
utun6 10.2.244.44/32
|
|
155
|
+
|
|
156
|
+
SEGMENTS (3 total, 2 yours, 9 address(es) in use)
|
|
157
|
+
192.168.0.0/24 local (en0) swept 6 host(s)
|
|
158
|
+
192.168.0.1 06:f2:67:75:4d:e2 gateway,arp,ping gateway / router
|
|
159
|
+
192.168.0.122 4e:e5:41:93:7c:1f self,arp,mdns this device
|
|
160
|
+
...
|
|
161
|
+
10.77.0.0/24 foreign passive 2 host(s)
|
|
162
|
+
10.77.0.9 de:ad:be:ef:00:01 ssdp,arp
|
|
163
|
+
|
|
164
|
+
CONFLICTS (2)
|
|
165
|
+
! [duplicate-ip] 192.168.0.50 answers from 2 MAC addresses (…) — two devices claim the same address
|
|
166
|
+
- [shared-l2] 1 foreign segment(s) share this link: 10.77.0.0/24
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Three independent sources are combined, so segments you have no address in still
|
|
170
|
+
show up:
|
|
171
|
+
|
|
172
|
+
- **every local interface** with its real netmask (`ip` / `ifconfig` / `ipconfig`);
|
|
173
|
+
- the **ARP / neighbour cache**, which lists link neighbours *regardless of
|
|
174
|
+
subnet* — this is what exposes somebody else's range on the same switch;
|
|
175
|
+
- **SSDP and mDNS** answers, sent out of each interface (skip with `--no-passive`).
|
|
176
|
+
|
|
177
|
+
The default pass is passive and takes about two seconds. `--sweep` additionally
|
|
178
|
+
ping-sweeps your ranges and the discovered ones so each address list is complete;
|
|
179
|
+
`--include CIDR` sweeps a specific range you are about to assign. Ranges larger
|
|
180
|
+
than `--max-sweep` (4096 hosts) are narrowed to the /24 around your own address
|
|
181
|
+
or skipped, with a note on stderr.
|
|
182
|
+
|
|
183
|
+
Reported conflicts:
|
|
184
|
+
|
|
185
|
+
| Kind | Meaning |
|
|
186
|
+
|------|---------|
|
|
187
|
+
| `duplicate-ip` | One address answers from two MACs — two devices claim it. |
|
|
188
|
+
| `overlap` | A foreign range, or a second local interface (VPN!), overlaps one of your subnets. |
|
|
189
|
+
| `no-dhcp` | An interface self-assigned a `169.254.x.x` address; no DHCP server answered. |
|
|
190
|
+
| `router-bridge` | One MAC answers for addresses in several segments — a router joining them. |
|
|
191
|
+
| `shared-l2` | Foreign segments are present on your link (informational). |
|
|
192
|
+
|
|
193
|
+
> Survey only networks you are entitled to. `netsurvey` sends ordinary pings and
|
|
194
|
+
> standard discovery multicast — nothing privileged — but probing networks you
|
|
195
|
+
> don't administer may still violate policy or law.
|
|
196
|
+
|
|
132
197
|
### Wi-Fi
|
|
133
198
|
|
|
134
199
|
`wifi connect` lists everything in range — move with ↑/↓, press Enter to join,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
+
import ipaddress
|
|
4
5
|
import os
|
|
5
6
|
import shutil
|
|
6
7
|
import sys
|
|
@@ -33,6 +34,9 @@ _ANSI = {
|
|
|
33
34
|
"signal": "\033[32m",
|
|
34
35
|
"lock": "\033[33m",
|
|
35
36
|
"dim": "\033[2m",
|
|
37
|
+
"segment": "\033[1;36m",
|
|
38
|
+
"warn": "\033[1;31m",
|
|
39
|
+
"note": "\033[2m",
|
|
36
40
|
"reset": "\033[0m",
|
|
37
41
|
}
|
|
38
42
|
|
|
@@ -491,6 +495,58 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
491
495
|
help="Disable colored output (also honors NO_COLOR).")
|
|
492
496
|
p.set_defaults(func=cmd_netscan)
|
|
493
497
|
|
|
498
|
+
# ── netsurvey ──────────────────────────────────────────────
|
|
499
|
+
p = sub.add_parser(
|
|
500
|
+
"netsurvey",
|
|
501
|
+
help="Map every network segment and address in use around you.",
|
|
502
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
503
|
+
description=(
|
|
504
|
+
"Survey the whole environment instead of a single subnet: list your own\n"
|
|
505
|
+
"interfaces and their ranges, every segment visible on the same wire, the\n"
|
|
506
|
+
"addresses in use in each, and the clashes between them.\n\n"
|
|
507
|
+
"Built for shared networks — a trade-show hall, an office, a co-working\n"
|
|
508
|
+
"space — where someone else's router hands out an overlapping range and\n"
|
|
509
|
+
"traffic quietly goes to the wrong place. Reports duplicate IPs, ranges\n"
|
|
510
|
+
"that overlap your own, links with no DHCP, and routers bridging two\n"
|
|
511
|
+
"segments.\n\n"
|
|
512
|
+
"The default pass is passive and takes a couple of seconds: it reads the\n"
|
|
513
|
+
"ARP / neighbour cache (which lists neighbours on *foreign* subnets too)\n"
|
|
514
|
+
"and asks for SSDP and mDNS answers. --sweep additionally ping-sweeps\n"
|
|
515
|
+
"your ranges and the discovered ones so each address list is complete.\n\n"
|
|
516
|
+
"Survey only networks you are entitled to; probing networks you don't\n"
|
|
517
|
+
"administer may violate policy or law.\n\n"
|
|
518
|
+
"Examples:\n"
|
|
519
|
+
" devbits netsurvey # quick passive map\n"
|
|
520
|
+
" devbits netsurvey --summary # segments only\n"
|
|
521
|
+
" devbits netsurvey --sweep # also enumerate every host\n"
|
|
522
|
+
" devbits netsurvey --include 192.168.1.0/24 --include 10.0.0.0/24\n"
|
|
523
|
+
" devbits netsurvey --sweep --group-prefix 16 --timeout 0.4"
|
|
524
|
+
),
|
|
525
|
+
)
|
|
526
|
+
p.add_argument("--sweep", action="store_true",
|
|
527
|
+
help="Also ping-sweep your own and the discovered segments (slower, complete).")
|
|
528
|
+
p.add_argument("--include", metavar="CIDR", action="append", default=[],
|
|
529
|
+
help="Extra range to sweep, e.g. 10.0.0.0/24. Repeatable.")
|
|
530
|
+
p.add_argument("--group-prefix", type=int, default=24, metavar="N",
|
|
531
|
+
help="Prefix used to group addresses outside your own subnets. Default: 24")
|
|
532
|
+
p.add_argument("--timeout", type=float, default=0.6,
|
|
533
|
+
help="Per-host ping timeout in seconds. Default: 0.6")
|
|
534
|
+
p.add_argument("--workers", type=int, default=128,
|
|
535
|
+
help="Number of concurrent ping workers. Default: 128")
|
|
536
|
+
p.add_argument("--discover-timeout", type=float, default=2.0, metavar="SECONDS",
|
|
537
|
+
help="How long to listen for SSDP / mDNS answers. Default: 2.0")
|
|
538
|
+
p.add_argument("--no-passive", action="store_true",
|
|
539
|
+
help="Skip the multicast discovery step; read the ARP cache only.")
|
|
540
|
+
p.add_argument("--resolve", action="store_true",
|
|
541
|
+
help="Reverse-DNS each address for its hostname (slower).")
|
|
542
|
+
p.add_argument("--max-sweep", type=int, default=4096, metavar="HOSTS",
|
|
543
|
+
help="Refuse to sweep a range larger than this. Default: 4096")
|
|
544
|
+
p.add_argument("--summary", action="store_true",
|
|
545
|
+
help="List segments and conflicts only, without the addresses.")
|
|
546
|
+
p.add_argument("--no-color", action="store_true",
|
|
547
|
+
help="Disable colored output (also honors NO_COLOR).")
|
|
548
|
+
p.set_defaults(func=cmd_netsurvey)
|
|
549
|
+
|
|
494
550
|
# ── wifi ───────────────────────────────────────────────────
|
|
495
551
|
p = sub.add_parser(
|
|
496
552
|
"wifi",
|
|
@@ -741,8 +797,6 @@ def cmd_samplefiles(args: argparse.Namespace) -> None:
|
|
|
741
797
|
|
|
742
798
|
|
|
743
799
|
def cmd_netscan(args: argparse.Namespace) -> None:
|
|
744
|
-
import ipaddress
|
|
745
|
-
|
|
746
800
|
color = _use_color(False if args.no_color else None)
|
|
747
801
|
network = ipaddress.ip_network(args.network, strict=False) if args.network else None
|
|
748
802
|
|
|
@@ -771,6 +825,85 @@ def cmd_netscan(args: argparse.Namespace) -> None:
|
|
|
771
825
|
print(f"Found {len(devices)} device(s).")
|
|
772
826
|
|
|
773
827
|
|
|
828
|
+
def _survey_source_label(sources: set[str], width: int = 22) -> str:
|
|
829
|
+
"""Condense a device's discovery sources into a stable, readable column."""
|
|
830
|
+
order = ["self", "gateway", "arp", "ping", "ssdp", "mdns"]
|
|
831
|
+
label = ",".join(source for source in order if source in sources) or "-"
|
|
832
|
+
if len(label) > width - 1:
|
|
833
|
+
label = label[: width - 2] + "…"
|
|
834
|
+
return f"{label:<{width}}"
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def cmd_netsurvey(args: argparse.Namespace) -> None:
|
|
838
|
+
from .network import survey_network
|
|
839
|
+
|
|
840
|
+
color = _use_color(False if args.no_color else None)
|
|
841
|
+
if not 8 <= args.group_prefix <= 32:
|
|
842
|
+
raise ValueError("--group-prefix must be between 8 and 32")
|
|
843
|
+
|
|
844
|
+
survey, notes = survey_network(
|
|
845
|
+
include=args.include,
|
|
846
|
+
group_prefix=args.group_prefix,
|
|
847
|
+
sweep=args.sweep,
|
|
848
|
+
passive=not args.no_passive,
|
|
849
|
+
timeout=args.timeout,
|
|
850
|
+
workers=args.workers,
|
|
851
|
+
discover_timeout=args.discover_timeout,
|
|
852
|
+
resolve=args.resolve,
|
|
853
|
+
max_sweep_hosts=args.max_sweep,
|
|
854
|
+
progress=lambda message: print(message, file=sys.stderr),
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
print(_colorize("INTERFACES", "header", color))
|
|
858
|
+
for nic in survey.interfaces:
|
|
859
|
+
note = "gateway " + survey.gateway if survey.gateway and \
|
|
860
|
+
ipaddress.ip_address(survey.gateway) in nic.network else ""
|
|
861
|
+
if nic.is_link_local:
|
|
862
|
+
note = "self-assigned (no DHCP)"
|
|
863
|
+
name_col = _colorize(f"{nic.name:<12}", "self" if nic.ip == survey.self_ip else "dim", color)
|
|
864
|
+
print(f" {name_col}{f'{nic.ip}/{nic.prefix}':<22}{_colorize(note, 'note', color)}".rstrip())
|
|
865
|
+
|
|
866
|
+
print()
|
|
867
|
+
local_count = sum(1 for segment in survey.segments if segment.is_local)
|
|
868
|
+
print(_colorize(
|
|
869
|
+
f"SEGMENTS ({len(survey.segments)} total, {local_count} yours, "
|
|
870
|
+
f"{survey.device_count} address(es) in use)", "header", color,
|
|
871
|
+
))
|
|
872
|
+
for segment in survey.segments:
|
|
873
|
+
scope = f"local ({', '.join(segment.interfaces)})" if segment.is_local else "foreign"
|
|
874
|
+
how = "swept" if segment.swept else "passive"
|
|
875
|
+
head = f" {str(segment.network):<20}{scope:<24}{how:<10}{len(segment.devices)} host(s)"
|
|
876
|
+
print(_colorize(head.rstrip(), "segment" if segment.is_local else "warn", color))
|
|
877
|
+
if args.summary:
|
|
878
|
+
continue
|
|
879
|
+
for device in segment.devices:
|
|
880
|
+
note = "this device" if device.is_self else ("gateway / router" if device.is_gateway else "")
|
|
881
|
+
kind = "self" if device.is_self else ("gateway" if device.is_gateway else None)
|
|
882
|
+
ip_col = _colorize(f"{device.ip:<16}", kind, color) if kind else f"{device.ip:<16}"
|
|
883
|
+
mac_col = _colorize(f"{device.mac or '-':<20}", "mac", color)
|
|
884
|
+
host_col = _colorize(f"{device.hostname or '-':<28}", "host", color) if args.resolve else ""
|
|
885
|
+
seen_col = _colorize(_survey_source_label(device.sources), "note", color)
|
|
886
|
+
print(f" {ip_col}{mac_col}{host_col}{seen_col}{note}".rstrip())
|
|
887
|
+
|
|
888
|
+
print()
|
|
889
|
+
if survey.conflicts:
|
|
890
|
+
print(_colorize(f"CONFLICTS ({len(survey.conflicts)})", "header", color))
|
|
891
|
+
for conflict in survey.conflicts:
|
|
892
|
+
marker = "!" if conflict.kind != "shared-l2" else "-"
|
|
893
|
+
kind = "warn" if conflict.kind != "shared-l2" else "note"
|
|
894
|
+
print(_colorize(f" {marker} [{conflict.kind}] {conflict.message}", kind, color))
|
|
895
|
+
else:
|
|
896
|
+
print("No address or range conflicts detected.")
|
|
897
|
+
|
|
898
|
+
for note in notes:
|
|
899
|
+
print(_colorize(f" note: {note}", "note", color), file=sys.stderr)
|
|
900
|
+
if not args.sweep and not args.include:
|
|
901
|
+
print(_colorize(
|
|
902
|
+
"Passive pass only — run with --sweep to enumerate every address in each segment.",
|
|
903
|
+
"note", color,
|
|
904
|
+
), file=sys.stderr)
|
|
905
|
+
|
|
906
|
+
|
|
774
907
|
# ---------------------------------------------------------------------------
|
|
775
908
|
# wifi
|
|
776
909
|
# ---------------------------------------------------------------------------
|