Scanning
Probes are responsible for three things: writing packets for a target, parsing responses, and attaching metadata tags. Every probe in scan/src/probe derives from the same traits.
Core traits
ProbeParser(defined inscan/src/probe/mod.rs) drives packet construction and parsing. Implement the following methods:write_transport(&self, packet, src_addr)should fill a transport-layer header and return the protocol plus byte count.set_target_transport(&self, packet, src, dst)finalises checksums before transmission.parse_transport(protocol, packet)extracts your result type from a response.
ScanTagProviderlets your result decorateScanResultobjects with strongly-typed tags. Return aVec<ScanResultTag>describing the metadata you captured.
#[derive(Debug, Clone)]
pub struct FooReply {
pub field: u16,
}
impl ScanTagProvider for FooReply {
fn tags(&self) -> Vec<ScanResultTag> {
vec![ScanResultTag::Custom {
key: "foo_field".to_string(),
value: self.field.to_string(),
}]
}
}
#[derive(clap::Args, Clone, Serialize, Deserialize, Default)]
pub struct FooProbe {
#[arg(long, default_value = "1234")]
pub port: u16,
}
impl ProbeParser for FooProbe {
type Result = FooReply;
fn write_transport(&self, packet: &mut [u8], _src: IpAddr) -> Option<(IpNextHeaderProtocol, usize)> {
let mut udp = MutableUdpPacket::new(packet)?;
udp.set_source(54321);
udp.set_destination(self.port);
udp.set_length(8);
udp.set_checksum(0);
Some((IpNextHeaderProtocols::Udp, 8))
}
fn set_target_transport(&self, packet: &mut [u8], src: IpAddr, dst: IpAddr) {
let mut udp = MutableUdpPacket::new(packet).unwrap();
let sum = match (src, dst) {
(IpAddr::V4(s), IpAddr::V4(d)) => pnet::packet::udp::ipv4_checksum(&udp.to_immutable(), &s, &d),
(IpAddr::V6(s), IpAddr::V6(d)) => pnet::packet::udp::ipv6_checksum(&udp.to_immutable(), &s, &d),
_ => unreachable!(),
};
udp.set_checksum(sum);
}
fn parse_transport(protocol: IpNextHeaderProtocol, packet: &[u8]) -> Option<Self::Result> {
if protocol == IpNextHeaderProtocols::Udp {
let udp = pnet::packet::udp::UdpPacket::new(packet)?;
Some(FooReply { field: udp.get_source() })
} else {
None
}
}
}Registering the probe
- Create a module in
scan/src/probe/(or extend an existing one) that defines yourProbeParserimplementation and result type. - Add a new variant to
ProbeConfiginscan/src/lib.rs, extend thespawn_probematch, and update the Clap documentation comment. - Expose factory functions in the Python bindings (
pyrmap/src/lib.rs) so scripting workflows can opt into the new probe. - Document configuration flags under
site/content/docs/scanning/so the sidebar stays in sync.
Tips
- Probes operate on raw packets. Allocate buffers large enough for Ethernet + IPv4/IPv6 + transport headers when calling
write_ethernet. - Keep per-target state inside your
Resulttype if you need to correlate replies; results are processed after dealiasing completes. - Use
ScanTagProviderto surface structured data—downstream CSV exports and Python bindings consume these tags automatically.