Network environment discovery: local interfaces/gateway/listening ports + authorized CIDR sweep (TCP-responsive probing). Assessment, not exploitation. Composes infrastructure-security-scan.
| Tier | operations |
| Role | unclassified (baselined) |
| Path | crates/operations/net-discovery |
| Edition | 2021 |
| Targets | net_report, self_report, operations_net_discovery |
| Public items | 28 across 4 modules |
| Tests | 29 |
What it is for
operations-net-discovery — network environment discovery.
Lets an application discover and understand its network environment:
local_report— self-discovery (no authorization): this host's
interfaces, the subnets it sits on, its default gateway, and the TCP ports it exposes. Introspecting the machine you already run on is not the dual-use case the sweep guards against.
sweep— authorized discovery: bounded TCP-connect probing over a
CIDR the operator has explicitly authorized (see authorize_range).
to_scan_findings— bridge open-port observations into
infrastructure-security-scan for assessment.
Posture: assessment, not exploitation. No payloads, no exploits, no writes — the same posture as infrastructure-security-scan, which this crate composes.
Platforms. Supported on Linux, Windows, and FreeBSD only; OS access is isolated behind the Prober seam (SystemProber in production, testkit::FixtureProber for zero-I/O tests). Building on any other target is a hard error.
Capabilities
Ipv4Cidr
Hand-rolled IPv4 CIDR (u32 math) — no external dependency (Gate 1 decision).
| Item |
|---|
pub struct Ipv4Cidr |
Ipv4Cidr :: fn parse(s : & str) -> Result <Self, DiscoverError> |
Ipv4Cidr :: fn network(& self) -> Ipv4Addr |
Ipv4Cidr :: fn prefix(& self) -> u8 |
Ipv4Cidr :: fn canonical(& self) -> String |
Ipv4Cidr :: fn contains(& self, addr : Ipv4Addr) -> bool |
Ipv4Cidr :: fn host_count(& self) -> u64 |
Ipv4Cidr :: fn hosts(& self) -> Ipv4CidrHosts |
Ipv4CidrHosts
Hand-rolled IPv4 CIDR (u32 math) — no external dependency (Gate 1 decision).
| Item |
|---|
pub struct Ipv4CidrHosts |
Ipv4CidrHosts :: fn next(& mut self) -> Option <Ipv4Addr> |
DiscoverError
Error types. DiscoverError is the crate-level result; ProbeError is the
| Item |
|---|
pub enum DiscoverError |
ProbeError
Error types. DiscoverError is the crate-level result; ProbeError is the
| Item |
|---|
pub enum ProbeError |
ListeningTool
The I/O seam. All OS access — interface/gateway enumeration, listening-port
| Item |
|---|
pub enum ListeningTool |
ProbeOutcome
The I/O seam. All OS access — interface/gateway enumeration, listening-port
| Item |
|---|
pub enum ProbeOutcome |
ProbeOutcome :: fn host_reachable(self) -> bool |
Prober
The I/O seam. All OS access — interface/gateway enumeration, listening-port
| Item |
|---|
pub trait Prober |
RawInterface
The I/O seam. All OS access — interface/gateway enumeration, listening-port
| Item |
|---|
pub struct RawInterface |
RawListening
The I/O seam. All OS access — interface/gateway enumeration, listening-port
| Item |
|---|
pub struct RawListening |
SystemProber
The I/O seam. All OS access — interface/gateway enumeration, listening-port
| Item |
|---|
pub struct SystemProber |
SystemProber :: fn interfaces(& self) -> Result <Vec <RawInterface>, ProbeError> |
SystemProber :: fn default_gateway(& self) -> Result <Option <Ipv4Addr>, ProbeError> |
SystemProber :: fn listening_raw(& self) -> Result <RawListening, ProbeError> |
SystemProber :: fn tcp_connect(& self, addr : SocketAddr, timeout : Duration) -> ProbeOutcome |
FixtureProber
Public test harness: a FixtureProber returning canned OS data so the
| Item |
|---|
pub struct FixtureProber |
FixtureProber :: fn interfaces(& self) -> Result <Vec <RawInterface>, ProbeError> |
FixtureProber :: fn default_gateway(& self) -> Result <Option <Ipv4Addr>, ProbeError> |
FixtureProber :: fn listening_raw(& self) -> Result <RawListening, ProbeError> |
FixtureProber :: fn tcp_connect(& self, addr : SocketAddr, _timeout : Duration) -> ProbeOutcome |
How to use it
From `examples/net_report.rs`:
use data_export::{CsvExporter, ExportColumn, ExportConfig, ExportFormat, JsonExporter};
use operations_net_discovery::{
authorize_range, local_report, sweep, DiscoverError, LocalNetworkReport, ScanTarget,
SweepOptions, SystemProber,
};
use serde::Serialize;
#[derive(Serialize)]
struct IfaceRow {
interface: String,
up: bool,
loopback: bool,
address: String,
prefix: u8,
network: String,
}
From `examples/self_report.rs`:
use operations_net_discovery::{local_report, SystemProber};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let report = local_report(&SystemProber)?;
println!("default gateway: {:?}", report.default_gateway);
println!("\ninterfaces:");
for iface in &report.interfaces {
let flags = match (iface.is_up, iface.is_loopback) {
(true, true) => "up,loopback",
(true, false) => "up",
(false, _) => "down",
};
println!(" {} [{}]", iface.name, flags);
for a in &iface.ipv4 {
println!(
Module structure
operations_net_discovery
cidrerrorprobertestkit
flowchart TD n_operations_net_discovery["operations_net_discovery"] n_operations_net_discovery --> n_cidr["cidr"] n_operations_net_discovery --> n_error["error"] n_operations_net_discovery --> n_prober["prober"] n_operations_net_discovery --> n_testkit["testkit"]
Public surface
`cidr`
| Item | What it is |
|---|---|
pub struct Ipv4Cidr | A canonicalized IPv4 CIDR block. |
Ipv4Cidr :: fn parse(s : & str) -> Result <Self, DiscoverError> | Parse "a.b.c.d/n", canonicalizing the address down to its network |
Ipv4Cidr :: fn network(& self) -> Ipv4Addr | The network address (canonical base of the block). |
Ipv4Cidr :: fn prefix(& self) -> u8 | The prefix length (0..=32). |
Ipv4Cidr :: fn canonical(& self) -> String | Canonical "network/prefix" string — stable for authorization/compare. |
Ipv4Cidr :: fn contains(& self, addr : Ipv4Addr) -> bool | Whether addr falls within this block. |
Ipv4Cidr :: fn host_count(& self) -> u64 | Number of probeable hosts (see hosts for the policy). |
Ipv4Cidr :: fn hosts(& self) -> Ipv4CidrHosts | Iterator over the probeable host addresses |
pub struct Ipv4CidrHosts | Iterator produced by Ipv4Cidr::hosts. |
Ipv4CidrHosts :: fn next(& mut self) -> Option <Ipv4Addr> | — |
`error`
| Item | What it is |
|---|---|
pub enum DiscoverError | Crate-level error. |
pub enum ProbeError | Failure surface of the Prober seam |
`prober`
| Item | What it is |
|---|---|
pub struct RawInterface | A raw interface as reported by the OS (before mapping to Interface). |
pub enum ListeningTool | Which OS tool produced a RawListening capture — selects the parser. |
pub struct RawListening | Raw stdout of a listening-port tool plus the tool that produced it. |
pub enum ProbeOutcome | Outcome of a single TCP-connect probe |
ProbeOutcome :: fn host_reachable(self) -> bool | Whether this outcome proves the remote host is reachable. |
pub trait Prober | The I/O seam |
pub struct SystemProber | Production Prober: netdev for interfaces/gateway, the platform's listening-port tool via a strict argument-vector subprocess, and std::net::TcpStream::connect_timeout for sweeps. |
SystemProber :: fn interfaces(& self) -> Result <Vec <RawInterface>, ProbeError> | — |
SystemProber :: fn default_gateway(& self) -> Result <Option <Ipv4Addr>, ProbeError> | — |
SystemProber :: fn listening_raw(& self) -> Result <RawListening, ProbeError> | — |
SystemProber :: fn tcp_connect(& self, addr : SocketAddr, timeout : Duration) -> ProbeOutcome | — |
`testkit`
| Item | What it is |
|---|---|
pub struct FixtureProber | A Prober backed by canned data |
FixtureProber :: fn interfaces(& self) -> Result <Vec <RawInterface>, ProbeError> | — |
FixtureProber :: fn default_gateway(& self) -> Result <Option <Ipv4Addr>, ProbeError> | — |
FixtureProber :: fn listening_raw(& self) -> Result <RawListening, ProbeError> | — |
FixtureProber :: fn tcp_connect(& self, addr : SocketAddr, _timeout : Duration) -> ProbeOutcome | — |
Re-exports. Exported here, defined elsewhere.
| Export | Defined in |
|---|---|
Ipv4Cidr | cidr::Ipv4Cidr |
infer_service | service::infer_service |
to_scan_findings | findings::to_scan_findings |
{ActiveScanGrant,ScanFinding,ScanSeverity,ScanTarget} | infrastructure_security_scan::{ActiveScanGrant,ScanFinding,ScanSeverity,ScanTarget} |
{DiscoverError,ProbeError} | error::{DiscoverError,ProbeError} |
{ListeningTool,ProbeOutcome,Prober,RawInterface,RawListening,SystemProber} | prober::{ListeningTool,ProbeOutcome,Prober,RawInterface,RawListening,SystemProber} |
{authorize_range,sweep,AuthorizedRange,HostReport,PortObservation,SweepOptions,DEFAULT_PORTS,} | sweep::{authorize_range,sweep,AuthorizedRange,HostReport,PortObservation,SweepOptions,DEFAULT_PORTS,} |
{local_report,IfAddr,Interface,ListeningPort,LocalNetworkReport,Transport} | local::{local_report,IfAddr,Interface,ListeningPort,LocalNetworkReport,Transport} |
Boundary
Reaches into infrastructure.
Shares tier operations with 40 other crates: operations-approval-workflow, operations-assessments, operations-block-imaging, operations-boot-media, operations-browser-agent-worker, operations-camera-discovery, operations-camera-liveview, operations-camera-registry, … (40 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | operations |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/operations/net-discovery |
| Vocabulary in force (lexicon) | current |
Tier flow. Which tiers this crate's own edges cross.
flowchart LR n_operations["operations"] --> n_infrastructure["infrastructure"]
Dependencies
Runtime, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `infrastructure-security-scan` | infrastructure | no | always |
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
netdev | ^0.45 | — | no | always |
serde | ^1 | derive | no | always |
thiserror | ^2 | — | no | always |
Development, in this workspace.
| Crate | Tier | Optional | Only on |
|---|---|---|---|
| `data-export` | data | no | always |
Development, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
Build. None.
Depended on by. Nothing in this workspace.
Signal flow — what reaches this crate, and what it reaches.
flowchart LR SELF["operations-net-discovery"] SELF -->|development| n_data_export["data-export"] SELF -->|runtime| n_infrastructure_security_scan["infrastructure-security-scan"] classDef self fill:#1f883d,stroke:#1f883d,color:#fff; class SELF self;
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| example | net_report | `examples/net_report.rs` |
| example | self_report | `examples/self_report.rs` |
| lib | operations_net_discovery | `src/lib.rs` |
Error model
| Error type | Named by |
|---|---|
DiscoverError | declared, no public signature returns it |
ProbeError | declared, no public signature returns it |
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | none detected |
| database access | none detected |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | none detected |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 29 |
| Integration tests | 0 |
| Examples | 2 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
cidr | 2 | 0 | 0 |
error | 2 | 1 | 0 |
prober | 6 | 2 | 0 |
testkit | 1 | 0 | 0 |
What the tests establish, by name:
contains_respects_prefix—src/cidr.rsparses_and_canonicalizes—src/cidr.rsrejects_bad_input—src/cidr.rsslash_24_excludes_network_and_broadcast—src/cidr.rsslash_31_yields_both_rfc3021—src/cidr.rsslash_32_is_single_host—src/cidr.rsslash_zero_does_not_overflow—src/cidr.rsslash_zero_hosts_iterator_does_not_overflow—src/cidr.rsemits_one_finding_per_open_port—src/findings.rsno_open_ports_yields_no_findings—src/findings.rsempty_output_is_empty_not_error—src/listening.rsparses_netstat_windows—src/listening.rsparses_sockstat—src/listening.rsparses_ss—src/listening.rsskips_garbage_lines—src/listening.rsss_zone_id_on_ipv4_is_parsed_not_dropped—src/listening.rsgateway_error_folds_to_none—src/local.rshappy_path_assembles_all_domains—src/local.rsinterface_error_is_fail_loud—src/local.rslistening_error_folds_to_empty—src/local.rsmaps_well_known—src/service.rsunknown_is_none—src/service.rsauthorized_target_yields_range—src/sweep.rsinvalid_cidr_is_rejected—src/sweep.rslarge_range_errors_without_materializing—src/sweep.rsrefused_only_host_is_responsive_but_has_no_open_ports—src/sweep.rsscope_too_large_is_rejected—src/sweep.rssweep_reports_only_responsive_hosts_with_open_ports—src/sweep.rsunauthorized_target_is_refused—src/sweep.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 19 | 28 |
Public modules with a //! block | 4 | 4 |
pie showData
title Public items with rustdoc
"Documented" : 19
"No rustdoc detected" : 9
Metrics
| Metric | Value |
|---|---|
| Rust source files | 10 |
| Source lines | 1437 |
| Code lines | 1052 |
| Public API items | 28 |
| Public modules | 4 |
| Tests | 29 |
| Examples | 2 |
| Cargo features | 0 |
| Direct runtime dependencies | 4 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"enum" : 4
"method" : 17
"struct" : 6
"trait" : 1
pie showData
title Rust source composition
"Code" : 1052
"Blank or comment" : 385
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.