ipscout 2.1.0__tar.gz → 2.3.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.
- {ipscout-2.1.0 → ipscout-2.3.0}/CHANGELOG.md +228 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/PKG-INFO +6 -2
- {ipscout-2.1.0 → ipscout-2.3.0}/README.md +5 -1
- {ipscout-2.1.0 → ipscout-2.3.0}/docs/systemdesign/module_reference.md +53 -45
- {ipscout-2.1.0 → ipscout-2.3.0}/docs/usage.md +116 -29
- {ipscout-2.1.0 → ipscout-2.3.0}/pyproject.toml +11 -4
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/__init__.py +11 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/__init__conf__.py +1 -1
- ipscout-2.3.0/src/ipscout/api.py +720 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/arp.py +100 -8
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/banner.py +67 -72
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/bootp.py +96 -34
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/bpf.py +3 -2
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/bsdroute.py +64 -10
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/cli.py +299 -73
- ipscout-2.3.0/src/ipscout/deadline.py +70 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/dhcp.py +60 -10
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/dhcp_linux.py +5 -13
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/dhcp_macos.py +21 -12
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/dhcp_windows.py +10 -13
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/errors.py +104 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/factory.py +6 -5
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/interfaces_posix.py +5 -4
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/interfaces_windows.py +73 -33
- ipscout-2.3.0/src/ipscout/models.py +1528 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/mtu.py +66 -40
- ipscout-2.3.0/src/ipscout/neighbours.py +606 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/neighbours_linux.py +28 -49
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/neighbours_macos.py +54 -33
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/neighbours_windows.py +104 -56
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/netlink.py +10 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/packet.py +132 -25
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/pool.py +49 -5
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/ports.py +34 -3
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/portscan.py +163 -43
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/resolve.py +84 -73
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/routes.py +44 -23
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/routes_linux.py +22 -21
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/routes_macos.py +35 -41
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/routes_windows.py +23 -26
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/scan.py +149 -25
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/service.py +82 -14
- ipscout-2.3.0/src/ipscout/snmp.py +458 -0
- ipscout-2.3.0/src/ipscout/snmpber.py +385 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/subnet.py +1 -1
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/tcpsyn.py +5 -33
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/traceroute.py +121 -31
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/transport_posix.py +73 -60
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/transport_tcp.py +65 -19
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/transport_windows.py +77 -60
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/winapi.py +296 -62
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/wol.py +56 -11
- ipscout-2.3.0/tests/snmp_agent.py +121 -0
- ipscout-2.3.0/tests/test_api_resolve_once.py +243 -0
- ipscout-2.3.0/tests/test_async_resolution.py +110 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_cli.py +93 -0
- ipscout-2.3.0/tests/test_deadline.py +49 -0
- ipscout-2.3.0/tests/test_defaults.py +94 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_family_filter.py +1 -1
- ipscout-2.3.0/tests/test_family_options.py +193 -0
- ipscout-2.3.0/tests/test_family_socket.py +100 -0
- ipscout-2.3.0/tests/test_mac_fallback.py +312 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_models.py +52 -14
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_neighbours.py +5 -5
- ipscout-2.3.0/tests/test_p3core_bounds.py +150 -0
- ipscout-2.3.0/tests/test_p3main_interfaces_windows_family.py +50 -0
- ipscout-2.3.0/tests/test_p3win_family_numbering.py +72 -0
- ipscout-2.3.0/tests/test_p4a1_bounds.py +431 -0
- ipscout-2.3.0/tests/test_p4a2_argument_bounds.py +316 -0
- ipscout-2.3.0/tests/test_p4b1_bootp_bounds.py +76 -0
- ipscout-2.3.0/tests/test_p4b1_packet_bounds.py +82 -0
- ipscout-2.3.0/tests/test_p4b2_bsdroute.py +96 -0
- ipscout-2.3.0/tests/test_p4c_windows_data.py +246 -0
- ipscout-2.3.0/tests/test_p4main_handoffs.py +42 -0
- ipscout-2.3.0/tests/test_pass5_A_record_bounds.py +344 -0
- ipscout-2.3.0/tests/test_pass5_B_arguments.py +280 -0
- ipscout-2.3.0/tests/test_pass5_C_bounds.py +170 -0
- ipscout-2.3.0/tests/test_pass5_D_platform_backends.py +291 -0
- ipscout-2.3.0/tests/test_pass5_main_handoffs.py +38 -0
- ipscout-2.3.0/tests/test_pass5_main_ping_strict.py +35 -0
- ipscout-2.3.0/tests/test_port_bounds.py +94 -0
- ipscout-2.3.0/tests/test_route_backends_family.py +159 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_routes.py +6 -4
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_scan_and_subnet.py +25 -3
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_service.py +17 -10
- ipscout-2.3.0/tests/test_snmp.py +356 -0
- ipscout-2.3.0/tests/test_snmp_device.py +30 -0
- ipscout-2.3.0/tests/test_snmp_models.py +68 -0
- ipscout-2.3.0/tests/test_snmpber.py +207 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_tcp_fallback.py +31 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_traceroute.py +24 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_transport_posix_logic.py +22 -18
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_ttl_capability_probe.py +1 -4
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_winapi_layout.py +4 -4
- ipscout-2.1.0/src/ipscout/api.py +0 -433
- ipscout-2.1.0/src/ipscout/models.py +0 -855
- ipscout-2.1.0/src/ipscout/neighbours.py +0 -407
- {ipscout-2.1.0 → ipscout-2.3.0}/.gitignore +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/LICENSE +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/docs/development.md +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/docs/handover-observe-dhcp.md +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/docs/installation.md +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/__main__.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/interfaces.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/leases_linux.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/py.typed +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/serialize.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/sockerr.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/static_neighbours.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/typed_click.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/src/ipscout/unixio.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/capture_support.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/conftest.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/dhcp_capture_fixture.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_arp.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_banner.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_bootp.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_dhcp_capability_probe.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_dhcp_macos.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_dhcp_windows.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_factory.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_integration_internet.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_integration_loopback.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_interfaces.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_metadata.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_module_entry.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_observe_dhcp.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_packaging.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_packet.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_platform_markers.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_resolve.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_sockerr.py +0 -0
- {ipscout-2.1.0 → ipscout-2.3.0}/tests/test_static_neighbours.py +0 -0
|
@@ -5,6 +5,234 @@ the [Keep a Changelog](https://keepachangelog.com/) format.
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [2.3.0] 2026-10-26 12:19:25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `lookup_mac(ip, active=True, fallback_to_cache=True)` (and the same on `get_mac_address`, plus
|
|
13
|
+
`ipscout mac --active --fallback-to-cache`): ask actively, and read the neighbour cache instead
|
|
14
|
+
only when this host cannot ask, meaning no privilege (`IPScoutPermissionError`), or no interface
|
|
15
|
+
or route that can carry the request, or a send or read that failed (`IPScoutUnsupportedError`). An active ask nobody answered stays an
|
|
16
|
+
active answer and is never replaced by a cache entry; a host name and a zone naming no interface
|
|
17
|
+
still raise. `fallback_to_cache=True` without `active=True` raises `ValueError`.
|
|
18
|
+
- `MacMethod` (`ACTIVE`, `CACHE`) and `MacLookup.method`: every MAC answer says whether it was asked
|
|
19
|
+
for or read from the cache, and `ipscout mac` prints and emits it. `--strict` keeps it too, and now
|
|
20
|
+
also carries the interface of a direct answer.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- An active MAC lookup no longer reports a request it could not make as "nobody answered":
|
|
25
|
+
- On Windows, a `SendARP` or `ResolveIpNetEntry2` failure other than no reply (no route to the
|
|
26
|
+
address, among others) and a missing `iphlpapi.dll` now raise `IPScoutUnsupportedError`.
|
|
27
|
+
They used to return `None`. Only `ERROR_BAD_NET_NAME` reads as no reply.
|
|
28
|
+
- On Linux and macOS, a send or receive that fails after the socket opens (the interface went
|
|
29
|
+
away, the link is down) now raises `IPScoutUnsupportedError` too, instead of returning `None`.
|
|
30
|
+
- `lookup_mac(..., active=True)` resolves a zone before anything is sent on every platform, by
|
|
31
|
+
index or by either Windows name (the friendly one `local_interfaces()` reports, or the
|
|
32
|
+
`if_nametoindex` one), and raises `IPScoutResolutionError` when it names no interface. Before,
|
|
33
|
+
the routed branch and Windows never looked at the zone, so a typo passed unseen. The cache read
|
|
34
|
+
now matches on the interface the zone resolves to, so a numeric zone such as `%2` finds the
|
|
35
|
+
entry on that link rather than whichever link holds the address.
|
|
36
|
+
- On Windows, an active lookup of an address with no route raises `IPScoutUnsupportedError`
|
|
37
|
+
instead of reporting nobody answered: `SendARP` gives the same status for both.
|
|
38
|
+
|
|
39
|
+
## [2.2.0] 2026-10-26 11:06:07
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- `read_snmp_system` / `aread_snmp_system` and the `ipscout snmp-system` command: the SNMP system
|
|
44
|
+
group (sysUpTime, sysName, sysDescr, sysObjectID) of many hosts, one GET each, SNMPv1 or v2c as the
|
|
45
|
+
caller chooses. Each host gets an `SnmpSystem` record with an `SnmpState` (ANSWERED, SILENT,
|
|
46
|
+
REFUSED, ERROR, UNREACHABLE) instead of an exception; under v1 a value the agent lacks is asked
|
|
47
|
+
around and reported as `None`. The uptime tells a restart (it fell) from mere reachability.
|
|
48
|
+
- `ipscout.deadline`: the per-host deadline helpers, shared by the banner grab and the SNMP read.
|
|
49
|
+
- `packet.max_payload_size(family)` and `packet.require_payload_size(payload_size, family)`: the
|
|
50
|
+
one per-family payload bound, which `ping` and `build_echo_request` both apply.
|
|
51
|
+
- `models.RoundTrip` (a finite round trip of 0 or more milliseconds), `api.HostNames` /
|
|
52
|
+
`api.require_host_names` (a collection of host names, refusing one bare string),
|
|
53
|
+
`traceroute.TracerouteSettings` and `traceroute.DEFAULT_MAX_HOPS`, `bpf.MAX_BPF_DEVICES`, and a
|
|
54
|
+
`method` field on `portscan.PortScanSettings`.
|
|
55
|
+
- `ipscout.models.require_port`, `Port`, `MIN_PORT` and `MAX_PORT`: the one port-range check
|
|
56
|
+
(1-65535) every entry point and record taking a port number now applies.
|
|
57
|
+
- `ipscout.packet.MAX_PAYLOAD_SIZE_IPV4` (65507) and `MAX_PAYLOAD_SIZE_IPV6` (65527): the most ICMP
|
|
58
|
+
payload one echo can carry.
|
|
59
|
+
- `AddressFamily.ip_version` and `AddressFamily.of_version()`.
|
|
60
|
+
- `ipscout.portscan.DEFAULT_TIMEOUT` (1.0 s per port), and `DEFAULT_CONCURRENCY` exported from
|
|
61
|
+
`ipscout.api` and `ipscout.scan`; the CLI options take their defaults from these.
|
|
62
|
+
- Argument models and checks every entry point shares: `banner.BannerSettings`,
|
|
63
|
+
`portscan.PortScanSettings`, `scan.SweepSettings`; `models.Timeout` / `require_timeout` (above 0,
|
|
64
|
+
at most 2147483 s, finite), `models.MAX_TIMES` (65535, the 16-bit echo sequence),
|
|
65
|
+
`models.MAX_HOP_LIMIT` (255), `models.parse_address` / `require_address`, `models.needs_zone`,
|
|
66
|
+
`pool.Concurrency` / `require_concurrency` (at least 1), and `errors.validation_problems` /
|
|
67
|
+
`refused_arguments`, which word every argument refusal the same way.
|
|
68
|
+
- `models.DEFAULT_TIMEOUT` (2.0 s, re-exported by `service`), `models.DISCARD_PORT`,
|
|
69
|
+
`AddressFamily.unspecified_address`, `packet.MAX_IP_LENGTH`, `packet.UDP_HEADER_SIZE`,
|
|
70
|
+
`packet.IPV4_MIN_HEADER_SIZE`, `arp.normalise_mac` (where `neighbours.normalise_mac` now comes
|
|
71
|
+
from), `arp.format_hardware_address`, `arp.FRAME_RECEIVE_SIZE`, `ports.CAPTURE_RECEIVE_BUFFER`,
|
|
72
|
+
`ports.MIN_CAPTURE_WAIT`, `wol.DEFAULT_BROADCAST`, `scan.refuse_a_partial_scope`,
|
|
73
|
+
`scan.sweep_argument_refusal`.
|
|
74
|
+
- Windows backend helpers: `winapi.IpStatus` (an `IntEnum` of the IP_STATUS codes with their text),
|
|
75
|
+
`win_family` / `family_from_win`, `sockaddr_in6`, `sockaddr_inet_address`, `address_text`,
|
|
76
|
+
`reply_buffer_size`, `DEFAULT_TTL`, `ICMP_REPLY_HEADROOM`, `MAX_ADAPTER_ADDRESS_LENGTH`,
|
|
77
|
+
`NO_ERROR`, `MS_LAYOUT`; `transport_windows.ipv6_destination` and `echo_outcome`;
|
|
78
|
+
`neighbours_windows.declared_mac`. macOS route-socket helpers in `bsdroute`: `RTM_GET`,
|
|
79
|
+
`NO_METRICS`, `pack_sockaddr_in6`, `open_route_socket`.
|
|
80
|
+
|
|
81
|
+
### Changed
|
|
82
|
+
|
|
83
|
+
- Every argument refusal reads `<operation> refused its arguments: <argument>: <reason>` (one part
|
|
84
|
+
per bad argument) and is a plain `ValueError` with pydantic's report kept as `__cause__`. `ping`
|
|
85
|
+
raised the `pydantic.ValidationError` itself, and with `raise_on_error=False` its `.error` held
|
|
86
|
+
pydantic's text, error-type tags and a docs URL; the CLI printed the same text.
|
|
87
|
+
- Arguments that were accepted and then misreported are refused before anything is sent:
|
|
88
|
+
`concurrency` below 1 (silently run as 1); a `timeout` of zero, below zero, infinite or above
|
|
89
|
+
2147483 s in `ping`, `traceroute`, `scan_ports`, `syn_scan`, `arp_scan` and `grab_banners` (a zero
|
|
90
|
+
timeout reported open ports as `FILTERED`, a sweep returned the cache as its result, a traceroute
|
|
91
|
+
walked 30 silent hops, and 1e12 raised `OverflowError` mid-probe); `times` above 65535; a negative
|
|
92
|
+
`payload_size`; `max_hops` above 255; a sweep `limit` below 1; an empty `farewell` (silently not
|
|
93
|
+
sent); a NaN or infinite `DhcpSession` window.
|
|
94
|
+
- Address arguments are parsed at entry: `query_route` and `lookup_mac` refuse a host name, a
|
|
95
|
+
malformed literal, a literal of the other family or a zone on an IPv4 address with `ValueError`
|
|
96
|
+
(they returned `None` / `UNKNOWN`, the same as "no route"); `wake_on_lan` refuses a broadcast that
|
|
97
|
+
is empty, `0.0.0.0`, a name or not IPv4 (an empty one sent the packet to this host and reported it
|
|
98
|
+
sent); `scan_ports` resolves the host once and uses that address for every port (a name was
|
|
99
|
+
resolved per port, and the SYN path passed the name to `inet_aton`); an unresolvable host raises
|
|
100
|
+
`IPScoutResolutionError` instead of reporting every port `FILTERED`.
|
|
101
|
+
- Records refuse values no producer makes: `ResponseObject` (counts 0-65535, received at most sent
|
|
102
|
+
and matching the answered round trips, `reached` exactly when something answered, `ip` of its
|
|
103
|
+
`family`, a non-empty `error`), `TraceHop.ttl` 1-255, `InterfaceAddress` / `SubnetInfo` prefix
|
|
104
|
+
length within the family, `mtu` above 0, `Interface.index` at least 1, `SweepScope.limit` at least
|
|
105
|
+
1, `ObserveDhcpReport` windows, `JsonEnvelope` (`ok` exactly when `error` is absent),
|
|
106
|
+
`ParsedReply` field widths, `EchoPayload.token` exactly 8 bytes, and `DhcpReply` (canonical MAC,
|
|
107
|
+
a non-zero IPv4 offer, a 32-bit transaction id). `build_echo_request` refuses a token of the wrong
|
|
108
|
+
length and a sequence or identifier outside 0-65535.
|
|
109
|
+
- A `ping` that fails before sending reports what it knew: the resolved address and family when
|
|
110
|
+
resolution succeeded (it reported `0.0.0.0` / IPv4 for an IPv6 target), and 0 pings sent.
|
|
111
|
+
- `bootp.offers_from_frames(mac=...)` accepts any written form of the address; another spelling
|
|
112
|
+
than lowercase-colon silently matched nothing.
|
|
113
|
+
- CLI: a bad option value is a usage error (exit 2, naming the option) before any probe, checked
|
|
114
|
+
through the library's own argument types; `ping-many -c 0` exited 1 with every row failed.
|
|
115
|
+
`wake`, `gateway --to` and `mac` with a bad argument exit 2 naming the command (they escaped with
|
|
116
|
+
exit 1 or 22 and `command: null`). `traceroute --max-hops` is limited to 1-255. In human mode a
|
|
117
|
+
usage error shows Click's usage text instead of a traceback.
|
|
118
|
+
|
|
119
|
+
- Every port number is held to 1-65535 the same way, with the message
|
|
120
|
+
`port out of range 1-65535: N`. `scan_ports` / `ascan_ports` / `syn_scan` refuse a port LIST
|
|
121
|
+
holding an out-of-range port with `ValueError` (only a specification string was checked);
|
|
122
|
+
`grab_banners` / `agrab_banners` keep refusing a bad port, now in that wording; and
|
|
123
|
+
`PortResult`, `Banner` and `WakeReport` refuse one at construction, where pydantic's
|
|
124
|
+
`greater than or equal to 1` wording used to appear.
|
|
125
|
+
- `ping` / `aping` / `ping_many` check `tcp_port` on every call, also when no TCP fallback is
|
|
126
|
+
used: out of range it raises, or with `raise_on_error=False` is reported on `.error`. It used
|
|
127
|
+
to be ignored unless the fallback engaged.
|
|
128
|
+
- On Windows, `path_mtu()` sends its don't-fragment probes with TTL 128, Windows' own default, as
|
|
129
|
+
`ping` does. It used 64, so a target more than 64 hops away could not be probed.
|
|
130
|
+
- `Banner` requires `error` exactly when the state is `UNREACHABLE` and refuses an empty one. It
|
|
131
|
+
used to forbid `error` on the other states but allowed an `UNREACHABLE` banner without a reason.
|
|
132
|
+
|
|
133
|
+
- More arguments are checked strictly before anything is sent, in the
|
|
134
|
+
`<operation> refused its arguments: <argument>: <reason>` shape:
|
|
135
|
+
- `ping` / `aping` / `ping_many` / `is_reachable`: `times=True` sent one echo and `timeout="1"`
|
|
136
|
+
waited a second; both are refused now (`is_reachable`, which never raises, answers `False`).
|
|
137
|
+
- `ping_many` / `aping_many` and `grab_banners` / `agrab_banners`: one string passed as the host
|
|
138
|
+
collection (`ping_many("printer.lan")`) probed each character as a host; it is refused, and so
|
|
139
|
+
is a host that is not a string (a raw pydantic error from `Banner` before). `ping_many` checks
|
|
140
|
+
`concurrency` before it takes anything from the targets; the refusal used to come after the
|
|
141
|
+
targets iterable was consumed, as `concurrency must be at least 1, got 0`.
|
|
142
|
+
- `traceroute` / `atraceroute` / `trace_path` / `atrace_path`: `max_hops=True` walked one hop and
|
|
143
|
+
`3.0` or `"5"` raised `TypeError`; a non-bool `resolve_names` (`"no"` switched lookups on) and a
|
|
144
|
+
`timeout` of the wrong type are refused. The messages change from
|
|
145
|
+
`max_hops must be 1-255, got 256` (and `max_hops must be at least 1, got 0` in 2.1.0) to
|
|
146
|
+
`traceroute refused its arguments: max_hops: Input should be less than or equal to 255`. The
|
|
147
|
+
arguments are checked before the name is resolved, so a bad `max_hops` with an unresolvable
|
|
148
|
+
name is a `ValueError`, no longer `IPScoutResolutionError`.
|
|
149
|
+
- `scan_ports` / `ascan_ports` / `syn_scan` with a port LIST: `[True]` and `[80.5]` scanned port
|
|
150
|
+
1 and 80 and keyed the result `True` / `80.5`; they are refused, and a list port out of range
|
|
151
|
+
now reads `port scan refused its arguments: ports.0: port out of range 1-65535: 0` (a
|
|
152
|
+
specification string keeps the bare `port out of range 1-65535: 0`).
|
|
153
|
+
`scan_ports(method="syn")` with a plain string silently ran a connect scan; it is refused.
|
|
154
|
+
- `observe_dhcp_first_reachable`: a `probe_timeout` of 0, negative, NaN or text, or a `tcp_port`
|
|
155
|
+
outside 1-65535, watched the whole window and returned `None` (port 0 silently dropped the TCP
|
|
156
|
+
attempt); they are refused before the capture opens.
|
|
157
|
+
- `DhcpSession` / `observe_dhcp`: a `settle` above 2147483 s is refused. A `timeout` of 0 reads
|
|
158
|
+
`timeout must be above 0 and at most 2147483 seconds, got 0` (2.1.0:
|
|
159
|
+
`timeout must be positive, got 0: a window of zero could never see anything`).
|
|
160
|
+
- `get_mac_address`, `lookup_mac` and `resolve_active`: text that is not an address literal
|
|
161
|
+
raises `ValueError`. In 2.1.0 `get_mac_address("nonsense")` returned `None`, and with
|
|
162
|
+
`active=True` raised `IPScoutUnsupportedError` (no route). `resolve_active` checks its
|
|
163
|
+
`timeout` (0 returned `None` without waiting, -1 raised a raw `Timeout value out of range`).
|
|
164
|
+
- `SweepScope.from_networks("192.0.2.0/24", ...)` (one network as text) is refused instead of
|
|
165
|
+
being split into characters.
|
|
166
|
+
- `resolve_active` and `lookup_mac(active=True)` on Linux and macOS honour an RFC 4007 zone
|
|
167
|
+
(`fe80::1%eth0`, by name or index): the request goes out on that interface. The zoned text used to
|
|
168
|
+
make the backend's `inet_pton` fail, and `None` came back with nothing sent. A zone naming no
|
|
169
|
+
interface raises `IPScoutResolutionError`. On Windows the zone is still not used.
|
|
170
|
+
- `build_echo_request` refuses a `payload_size` its family cannot carry with the same message
|
|
171
|
+
`ping` uses (`10**12` raised `MemoryError`, 70000 a raw pydantic error, and IPv4 65508-65527 was
|
|
172
|
+
built). `build_arp_request` / `build_neighbour_solicitation` refuse a sender address that is not
|
|
173
|
+
six bytes (`"aa:bb"` was zero-padded into the frame).
|
|
174
|
+
- Records refuse more values no producer makes: `EchoResult.rtt_ms` NaN, infinite or negative, a
|
|
175
|
+
round trip without a source (or the reverse), `ttl_expired` without an answer; `TraceHop` reached
|
|
176
|
+
or named without an address, a round trip without an address, an empty `hostname`;
|
|
177
|
+
`Interface` with an empty name, an address filed under the other family, a hardware address not
|
|
178
|
+
in lowercase colon form or all zero, an MTU above 0xFFFFFFFF; `Neighbour` whose `ip` is not a
|
|
179
|
+
bare literal of its `family` or whose `mac` is not `aa:bb:cc:dd:ee:ff`; `MacLookup` whose
|
|
180
|
+
`via_ip` and `NEXT_HOP` scope disagree, `DIRECT` without a MAC or `UNKNOWN` with one;
|
|
181
|
+
`SubnetInfo` whose network is not the one its address sits on, whose broadcast is not that
|
|
182
|
+
network's, or whose gateway is not of its family; `SweepScope` networks covering more than its
|
|
183
|
+
limit; `FindIpReport` with `scanned` and `scope` disagreeing; `ObserveDhcpReport` and
|
|
184
|
+
`WakeReport` with a non-canonical MAC, unbounded windows, or a broadcast that is not a usable
|
|
185
|
+
IPv4 address.
|
|
186
|
+
- `SubnetInfo.network` is an `IPv4Network` / `IPv6Network` instead of text. JSON output is
|
|
187
|
+
unchanged; Python callers reading it as a string need `str(...)`.
|
|
188
|
+
- CLI: `wake`, `find-ip` and `observe-dhcp` report the MAC as `aa:bb:cc:dd:ee:ff` whatever spelling
|
|
189
|
+
was typed (they echoed the input).
|
|
190
|
+
|
|
191
|
+
### Fixed
|
|
192
|
+
|
|
193
|
+
- TCP fallback probes over IPv6 (`TcpEchoTransport`, `AsyncTcpEchoTransport`) could reach an IPv4
|
|
194
|
+
listener: an IPv4-mapped target (`::ffff:a.b.c.d`) connected through IPv4 on Linux and macOS,
|
|
195
|
+
and on macOS with Python 3.15 a plain IPv4 literal did too, so the host read as reachable over
|
|
196
|
+
IPv6. The probe socket now sets `IPV6_V6ONLY`, and such a target reads as unanswered.
|
|
197
|
+
- Windows `local_interfaces()`: an adapter with no friendly name was named `b'{GUID}'` (the repr
|
|
198
|
+
of the bytes) and one with no name at all `''`; it is now `{GUID}`, or `ifindex-<n>` /
|
|
199
|
+
`adapter-<position>` when even that is missing.
|
|
200
|
+
- macOS `dhcp_capture_available()`: a busy `/dev/bpf0` answered `False` although a capture would
|
|
201
|
+
claim the next free device; it now tries the same pool a capture does.
|
|
202
|
+
- `reverse_dns` answers `None` for an empty PTR name, which a traceroute with `resolve_names=True`
|
|
203
|
+
would otherwise have failed on.
|
|
204
|
+
- `aping`, `aping_many`, `ais_reachable` and `atraceroute` resolve their target in a worker
|
|
205
|
+
thread, and `atrace_path(resolve_names=True)` looks up each hop's name there too. They called
|
|
206
|
+
`getaddrinfo` / `gethostbyaddr` on the event-loop thread, so one slow name server stalled every
|
|
207
|
+
other task in the loop: in `aping_many`, one slow name held up the whole sweep.
|
|
208
|
+
- `is_reachable` / `ais_reachable` resolve the target once and probe that one address on both the
|
|
209
|
+
ICMP and the TCP attempt; they resolved twice, so round-robin DNS could make the two attempts
|
|
210
|
+
probe different hosts. ICMP versus TCP is now chosen for the family the target resolved to, not
|
|
211
|
+
for IPv4, and a socket error from either attempt answers `False` instead of escaping. A
|
|
212
|
+
`tcp_port` outside 1-65535 raised an uncaught `OverflowError` from the connect; it now makes only
|
|
213
|
+
the TCP attempt answer `False`, so a host that answers ICMP still reads as reachable.
|
|
214
|
+
- `ping` / `aping` / `ping_many` refuse a `payload_size` above 65507 bytes for an IPv4 target or
|
|
215
|
+
65527 for IPv6 (raised, or on `.error` with `raise_on_error=False`). Such an echo cannot be sent,
|
|
216
|
+
and the failed send was reported as a live host that never answered.
|
|
217
|
+
- `wake_on_lan` refuses a port outside 1-65535 with `ValueError`. It raised an uncaught
|
|
218
|
+
`OverflowError` from the socket layer, which the CLI printed as a traceback.
|
|
219
|
+
- An interface whose sysfs MTU reads 0 is listed with no MTU instead of failing the listing.
|
|
220
|
+
|
|
221
|
+
### Removed
|
|
222
|
+
|
|
223
|
+
Backend-module names replaced by the shared definitions above. Nothing in the top-level `ipscout`
|
|
224
|
+
namespace is removed or changed.
|
|
225
|
+
|
|
226
|
+
- `winapi.IP_SUCCESS`, `IP_DEST_NET_UNREACHABLE`, `IP_DEST_HOST_UNREACHABLE`, `IP_REQ_TIMED_OUT`,
|
|
227
|
+
`IP_TTL_EXPIRED_TRANSIT`: use `winapi.IpStatus` members, which compare equal to the same numbers.
|
|
228
|
+
- `neighbours_linux`, `neighbours_windows`, `bsdroute` and `tcpsyn` no longer define their own
|
|
229
|
+
`format_mac`, `MAC_LENGTH` and `checksum`; they import `arp.format_mac`, `arp.MAC_LENGTH` and
|
|
230
|
+
`packet.checksum` (the same RFC 1071 sum) and drop them from `__all__`. An old import still
|
|
231
|
+
resolves, except that `neighbours_windows.format_mac` now takes one argument: for a
|
|
232
|
+
length-prefixed buffer use `neighbours_windows.declared_mac(raw, length)`.
|
|
233
|
+
- `neighbours_windows.resolve_active(ip)` takes the family as a second argument; an IPv4 address in
|
|
234
|
+
a non-dotted-quad form such as `192.0.2` resolves to nothing instead of being read as `192.0.0.2`.
|
|
235
|
+
|
|
8
236
|
## [2.1.0] 2026-10-24 17:18:42
|
|
9
237
|
|
|
10
238
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ipscout
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Ping, traceroute and local-network inspection in pure Python - no subprocess, and no admin rights on the default paths
|
|
5
5
|
Project-URL: Homepage, https://github.com/bitranox/ipscout
|
|
6
6
|
Project-URL: Repository, https://github.com/bitranox/ipscout.git
|
|
@@ -202,6 +202,7 @@ ipscout reachable example.com
|
|
|
202
202
|
ipscout traceroute 1.1.1.1 --max-hops 10
|
|
203
203
|
ipscout scan-ports 192.168.1.10 --ports 22,80,443,8000-8100
|
|
204
204
|
ipscout banners 192.0.2.0/24 --port 21 --farewell 'QUIT\r\n' # what each host says first
|
|
205
|
+
ipscout snmp-system 192.0.2.0/28 # uptime, name and model over SNMP
|
|
205
206
|
ipscout mac 8.8.8.8 # the gateway's, labelled NEXT_HOP
|
|
206
207
|
ipscout find-ip dc:b2:2f:44:34:59 --scan
|
|
207
208
|
ipscout arp-scan --network 192.168.1.0/24
|
|
@@ -236,6 +237,7 @@ ipscout.default_gateway() # and query_route() for any destination
|
|
|
236
237
|
ipscout.subnet_info() # addressing, gateway, stored DHCP facts
|
|
237
238
|
ipscout.scan_ports(host, "22,80,8000-8100")
|
|
238
239
|
ipscout.grab_banners(hosts, 21, farewell=b"QUIT\r\n") # each host's first line, on one connection
|
|
240
|
+
ipscout.read_snmp_system(hosts) # uptime, sysName, sysDescr, sysObjectID; a falling uptime is a restart
|
|
239
241
|
ipscout.path_mtu("8.8.8.8")
|
|
240
242
|
ipscout.wake_on_lan(mac, broadcast="192.168.1.255")
|
|
241
243
|
ipscout.observe_dhcp(mac, interface="br0") # a machine that has no address yet (needs elevation)
|
|
@@ -244,7 +246,9 @@ ipscout.remove_neighbour(ip, interface="eth0") # and out again
|
|
|
244
246
|
```
|
|
245
247
|
|
|
246
248
|
A MAC address does not survive a router hop, so `lookup_mac` puts the scope in the answer and
|
|
247
|
-
`get_mac_address` returns `None` for anything routed rather than passing off the gateway's as the host's.
|
|
249
|
+
`get_mac_address` returns `None` for anything routed rather than passing off the gateway's as the host's. Every
|
|
250
|
+
answer also says in `method` whether it was asked for (`active=True`) or read from the cache, and
|
|
251
|
+
`fallback_to_cache=True` reads the cache only when this host cannot ask actively. A
|
|
248
252
|
sweep with no network given covers the subnets this host is attached to that fit inside one sweep's
|
|
249
253
|
4096-address budget, and names the ones left out - a container bridge on a `/16` is skipped rather than
|
|
250
254
|
cancelling the sweep, and a search that matched nothing while a network went uncovered says so instead of
|
|
@@ -156,6 +156,7 @@ ipscout reachable example.com
|
|
|
156
156
|
ipscout traceroute 1.1.1.1 --max-hops 10
|
|
157
157
|
ipscout scan-ports 192.168.1.10 --ports 22,80,443,8000-8100
|
|
158
158
|
ipscout banners 192.0.2.0/24 --port 21 --farewell 'QUIT\r\n' # what each host says first
|
|
159
|
+
ipscout snmp-system 192.0.2.0/28 # uptime, name and model over SNMP
|
|
159
160
|
ipscout mac 8.8.8.8 # the gateway's, labelled NEXT_HOP
|
|
160
161
|
ipscout find-ip dc:b2:2f:44:34:59 --scan
|
|
161
162
|
ipscout arp-scan --network 192.168.1.0/24
|
|
@@ -190,6 +191,7 @@ ipscout.default_gateway() # and query_route() for any destination
|
|
|
190
191
|
ipscout.subnet_info() # addressing, gateway, stored DHCP facts
|
|
191
192
|
ipscout.scan_ports(host, "22,80,8000-8100")
|
|
192
193
|
ipscout.grab_banners(hosts, 21, farewell=b"QUIT\r\n") # each host's first line, on one connection
|
|
194
|
+
ipscout.read_snmp_system(hosts) # uptime, sysName, sysDescr, sysObjectID; a falling uptime is a restart
|
|
193
195
|
ipscout.path_mtu("8.8.8.8")
|
|
194
196
|
ipscout.wake_on_lan(mac, broadcast="192.168.1.255")
|
|
195
197
|
ipscout.observe_dhcp(mac, interface="br0") # a machine that has no address yet (needs elevation)
|
|
@@ -198,7 +200,9 @@ ipscout.remove_neighbour(ip, interface="eth0") # and out again
|
|
|
198
200
|
```
|
|
199
201
|
|
|
200
202
|
A MAC address does not survive a router hop, so `lookup_mac` puts the scope in the answer and
|
|
201
|
-
`get_mac_address` returns `None` for anything routed rather than passing off the gateway's as the host's.
|
|
203
|
+
`get_mac_address` returns `None` for anything routed rather than passing off the gateway's as the host's. Every
|
|
204
|
+
answer also says in `method` whether it was asked for (`active=True`) or read from the cache, and
|
|
205
|
+
`fallback_to_cache=True` reads the cache only when this host cannot ask actively. A
|
|
202
206
|
sweep with no network given covers the subnets this host is attached to that fit inside one sweep's
|
|
203
207
|
4096-address budget, and names the ones left out - a container bridge on a `/16` is skipped rather than
|
|
204
208
|
cancelling the sweep, and a search that matched nothing while a network went uncovered says so instead of
|
|
@@ -58,7 +58,7 @@ The import-linter contract `Layered dependencies point one way only` is enforced
|
|
|
58
58
|
listed highest first; each may import only from those below it.
|
|
59
59
|
|
|
60
60
|
```
|
|
61
|
-
scan : subnet : portscan : banner : mtu : wol : dhcp sweeps and single-purpose probes
|
|
61
|
+
scan : subnet : portscan : banner : snmp : mtu : wol : dhcp sweeps and single-purpose probes
|
|
62
62
|
api ping / aping / ping_many / aping_many / is_reachable / ais_reachable
|
|
63
63
|
traceroute traceroute / atraceroute / trace_path / atrace_path
|
|
64
64
|
static_neighbours set_neighbour / remove_neighbour
|
|
@@ -72,8 +72,8 @@ transport_posix : transport_windows : transport_tcp
|
|
|
72
72
|
: routes_linux : routes_macos : routes_windows
|
|
73
73
|
: neighbours_linux : neighbours_macos : neighbours_windows
|
|
74
74
|
: dhcp_linux : dhcp_windows : dhcp_macos
|
|
75
|
-
packet : resolve : ports : winapi : netlink : bsdroute : arp : tcpsyn
|
|
76
|
-
: pool : sockerr : unixio : bootp : bpf
|
|
75
|
+
packet : deadline : resolve : ports : winapi : netlink : bsdroute : arp : tcpsyn
|
|
76
|
+
: pool : sockerr : unixio : bootp : bpf : snmpber
|
|
77
77
|
models
|
|
78
78
|
errors
|
|
79
79
|
```
|
|
@@ -104,8 +104,11 @@ caller asked for and what the machine could do. A transport reports what happene
|
|
|
104
104
|
### Data flow, one ping
|
|
105
105
|
|
|
106
106
|
1. `api.ping` resolves the target once through `resolve.resolve_one`, which fixes the address
|
|
107
|
-
family up front rather than guessing it later.
|
|
108
|
-
|
|
107
|
+
family up front rather than guessing it later. The async entry points (`aping`,
|
|
108
|
+
`ais_reachable`, `atraceroute`, and the hop names of `atrace_path`) run that lookup in a worker
|
|
109
|
+
thread, because `getaddrinfo` blocks for as long as the name server takes and one slow name on
|
|
110
|
+
the loop thread would stall every other probe in a sweep.
|
|
111
|
+
2. `api._request` builds a `service.PingRequest`. Pydantic field bounds reject a bad `times`,
|
|
109
112
|
`timeout` or `interval` here, before any packet moves, identically for every caller path.
|
|
110
113
|
3. `factory.make_transport` picks the backend.
|
|
111
114
|
4. `service.run_ping` sends the echoes, paced against the run's start so a slow reply does not push
|
|
@@ -198,42 +201,45 @@ own value only as a fallback, which keeps loopback and LAN timings meaningful.
|
|
|
198
201
|
|
|
199
202
|
### Public surface
|
|
200
203
|
|
|
201
|
-
| Module | Role
|
|
202
|
-
|
|
203
|
-
| `__init__.py` | Re-exports the public API so importers depend on the package, not its layout.
|
|
204
|
-
| `api.py` | `ping`, `aping`, `ping_many`, `aping_many`, `is_reachable`, `ais_reachable`. Owns the error contract.
|
|
205
|
-
| `traceroute.py` | `traceroute`, `atraceroute`, `trace_path`, `atrace_path`, and the capability refusal.
|
|
206
|
-
| `interfaces.py` | `local_interfaces`, dispatching to the per-OS backend.
|
|
207
|
-
| `routes.py` | `query_route`, `default_gateway`, dispatching to the per-OS backend.
|
|
208
|
-
| `neighbours.py` | `neighbours`, `lookup_mac`, `get_mac_address`, `resolve_active`, `normalise_mac
|
|
209
|
-
| `static_neighbours.py` | `set_neighbour`, `remove_neighbour`: write and delete a permanent IPv4 entry, choosing the interface and reading the result back.
|
|
210
|
-
| `scan.py` | `arp_scan`, `find_ip_by_mac`, `local_networks`, `sweep_scope`. Sweep, then read what the kernel learned.
|
|
211
|
-
| `subnet.py` | `subnet_info`, composing interfaces, routes and the stored lease.
|
|
212
|
-
| `portscan.py` | `scan_ports`, `ascan_ports`, `syn_scan`, `parse_ports`.
|
|
213
|
-
| `banner.py` | `grab_banners`, `agrab_banners`: connect, read the first line, say goodbye and close, all on one connection per host.
|
|
214
|
-
| `
|
|
215
|
-
| `
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
218
|
-
| `
|
|
204
|
+
| Module | Role |
|
|
205
|
+
|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
206
|
+
| `__init__.py` | Re-exports the public API so importers depend on the package, not its layout. |
|
|
207
|
+
| `api.py` | `ping`, `aping`, `ping_many`, `aping_many`, `is_reachable`, `ais_reachable`. Owns the error contract. |
|
|
208
|
+
| `traceroute.py` | `traceroute`, `atraceroute`, `trace_path`, `atrace_path`, and the capability refusal. |
|
|
209
|
+
| `interfaces.py` | `local_interfaces`, dispatching to the per-OS backend. |
|
|
210
|
+
| `routes.py` | `query_route`, `default_gateway`, dispatching to the per-OS backend. |
|
|
211
|
+
| `neighbours.py` | `neighbours`, `lookup_mac`, `get_mac_address`, `resolve_active`, `normalise_mac`, and `ask_or_read_cache`, the one rule for falling back from an active ask to the cache. |
|
|
212
|
+
| `static_neighbours.py` | `set_neighbour`, `remove_neighbour`: write and delete a permanent IPv4 entry, choosing the interface and reading the result back. |
|
|
213
|
+
| `scan.py` | `arp_scan`, `find_ip_by_mac`, `local_networks`, `sweep_scope`. Sweep, then read what the kernel learned. |
|
|
214
|
+
| `subnet.py` | `subnet_info`, composing interfaces, routes and the stored lease. |
|
|
215
|
+
| `portscan.py` | `scan_ports`, `ascan_ports`, `syn_scan`, `parse_ports`. |
|
|
216
|
+
| `banner.py` | `grab_banners`, `agrab_banners`: connect, read the first line, say goodbye and close, all on one connection per host. |
|
|
217
|
+
| `snmp.py` | `read_snmp_system`, `aread_snmp_system`: one SNMP GET of the system group per host on a connected UDP socket, re-asking under v1 without a value the agent lacks. |
|
|
218
|
+
| `mtu.py` | `path_mtu`, by kernel query on Linux and by bisection elsewhere. |
|
|
219
|
+
| `wol.py` | `wake_on_lan` and the magic packet it builds. |
|
|
220
|
+
| `dhcp.py` | `observe_dhcp` and its session: the capture loop, the quiet window and the platform dispatch. |
|
|
221
|
+
| `resolve.py` | `resolve`, `resolve_one` (returns a `ResolvedTarget` named tuple), `reverse_dns`, `family_of`. |
|
|
222
|
+
| `factory.py` | `make_transport`, `make_async_transport`, `icmp_available`. The only `sys.platform` branch in the probing path. |
|
|
219
223
|
|
|
220
224
|
### Core
|
|
221
225
|
|
|
222
|
-
| Module | Role
|
|
223
|
-
|
|
224
|
-
| `errors.py` | `IPScoutError` and its subclasses, sweep refusals included. No dependencies.
|
|
225
|
-
| `models.py` | Frozen result models and the `str`-subclass enums.
|
|
226
|
-
| `ports.py` | `EchoResult`, `EchoTransport`, `AsyncEchoTransport`. The seams.
|
|
227
|
-
| `packet.py` | ICMP encode and decode. Total functions over bytes: no sockets, no clock, no I/O.
|
|
228
|
-
| `service.py` | `PingRequest`, `run_ping`, `arun_ping`. Probe sequencing and aggregation.
|
|
229
|
-
| `netlink.py` | Message and attribute walkers and encoders, and the acknowledgement reader, for the Linux route and neighbour backends.
|
|
230
|
-
| `bsdroute.py` | Routing-message and `sockaddr` walkers and encoders for the macOS backends, plus `sysctl`.
|
|
231
|
-
| `arp.py` | ARP and NDP codecs. Testable without the privilege the sockets need.
|
|
232
|
-
| `bootp.py` | BOOTP/DHCP reply codec, separate from the capture for the same reason.
|
|
233
|
-
| `bpf.py` | BSD packet filter ioctl encoding and record splitting, shared by two backends.
|
|
234
|
-
| `tcpsyn.py` | TCP SYN codec and its pseudo-header checksum, for the half-open scan.
|
|
235
|
-
| `
|
|
236
|
-
| `
|
|
226
|
+
| Module | Role |
|
|
227
|
+
|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
228
|
+
| `errors.py` | `IPScoutError` and its subclasses, sweep refusals included, and `could_not_ask` for a resolution request that could not be sent. No dependencies. |
|
|
229
|
+
| `models.py` | Frozen result models and the `str`-subclass enums. |
|
|
230
|
+
| `ports.py` | `EchoResult`, `EchoTransport`, `AsyncEchoTransport`. The seams. |
|
|
231
|
+
| `packet.py` | ICMP encode and decode. Total functions over bytes: no sockets, no clock, no I/O. |
|
|
232
|
+
| `service.py` | `PingRequest`, `run_ping`, `arun_ping`. Probe sequencing and aggregation. |
|
|
233
|
+
| `netlink.py` | Message and attribute walkers and encoders, and the acknowledgement reader, for the Linux route and neighbour backends. |
|
|
234
|
+
| `bsdroute.py` | Routing-message and `sockaddr` walkers and encoders for the macOS backends, plus `sysctl`. |
|
|
235
|
+
| `arp.py` | ARP and NDP codecs. Testable without the privilege the sockets need. |
|
|
236
|
+
| `bootp.py` | BOOTP/DHCP reply codec, separate from the capture for the same reason. |
|
|
237
|
+
| `bpf.py` | BSD packet filter ioctl encoding and record splitting, shared by two backends. |
|
|
238
|
+
| `tcpsyn.py` | TCP SYN codec and its pseudo-header checksum, for the half-open scan. |
|
|
239
|
+
| `deadline.py` | `within` and `failure_reason`: one deadline across several awaits, shared by the banner grab and the SNMP read. |
|
|
240
|
+
| `snmpber.py` | BER encode of an SNMP GET and a structural parse of the reply. Total functions over bytes, like `packet.py`. |
|
|
241
|
+
| `pool.py` | `gather_bounded`: a fixed worker pool, so a long work list never becomes one Task per item. |
|
|
242
|
+
| `sockerr.py` | `is_refusal`: one reading of a refused connect across every platform's spelling, shared by every module that connects. |
|
|
237
243
|
|
|
238
244
|
### Platform backends
|
|
239
245
|
|
|
@@ -258,13 +264,13 @@ own value only as a fallback, which keeps loopback and LAN timings meaningful.
|
|
|
258
264
|
|
|
259
265
|
### CLI adapter
|
|
260
266
|
|
|
261
|
-
| Module | Role
|
|
262
|
-
|
|
263
|
-
| `cli.py` | The rich-click group, twenty-
|
|
264
|
-
| `serialize.py` | `to_jsonable` and `dumps` at the output boundary.
|
|
265
|
-
| `typed_click.py` | Typed facade over rich-click's partially-typed decorators.
|
|
266
|
-
| `__main__.py` | `python -m ipscout`.
|
|
267
|
-
| `__init__conf__.py` | Static package metadata, kept in sync with `pyproject.toml` by automation.
|
|
267
|
+
| Module | Role |
|
|
268
|
+
|---------------------|------------------------------------------------------------------------------|
|
|
269
|
+
| `cli.py` | The rich-click group, twenty-three subcommands, `_emit` and `_fail`, `main`. |
|
|
270
|
+
| `serialize.py` | `to_jsonable` and `dumps` at the output boundary. |
|
|
271
|
+
| `typed_click.py` | Typed facade over rich-click's partially-typed decorators. |
|
|
272
|
+
| `__main__.py` | `python -m ipscout`. |
|
|
273
|
+
| `__init__conf__.py` | Static package metadata, kept in sync with `pyproject.toml` by automation. |
|
|
268
274
|
|
|
269
275
|
---
|
|
270
276
|
|
|
@@ -281,6 +287,8 @@ and mutating it can only make it disagree with reality.
|
|
|
281
287
|
| `InterfaceAddress` | `address`, `prefix_len` |
|
|
282
288
|
| `MacLookup` | `ip`, `mac`, `scope`, `via_ip`, `interface` |
|
|
283
289
|
| `SubnetInfo` | `interface`, `address`, `prefix_len`, `network`, `family`, `broadcast`, `gateway`, `dns_servers`, `domain`, `dhcp_server`, `lease_obtained`, `lease_expires`, `mtu` |
|
|
290
|
+
| `Banner` | `host`, `port`, `state`, `line`, `error` |
|
|
291
|
+
| `SnmpSystem` | `host`, `state`, `uptime_seconds`, `name`, `description`, `object_id`, `error` |
|
|
284
292
|
| `CapabilityReport` | `icmp_ipv4`, `icmp_ipv6`, `traceroute` |
|
|
285
293
|
| `ReachabilityReport` | `target`, `reachable` |
|
|
286
294
|
| `ResolveReport` | `target`, `addresses` |
|