Version: 4.0 Last Updated: 2025-01-25 Status: Phase 7 COMPLETE - v1.0.0 Production Release (2,557 tests, 51.40% coverage)
- Executive Summary
- Design Philosophy
- High-Level Architecture
- Core Components
- Scanning Modes
- Data Flow
- Technology Stack
- Design Patterns
ProRT-IP WarScan is a modern, high-performance network reconnaissance tool written in Rust. It combines the speed of Masscan (10M+ packets/second), the depth of Nmap's service detection, and the safety of memory-safe implementation.
- Speed: Internet-scale scanning (full IPv4 sweep in <6 minutes on appropriate hardware)
- Safety: Memory-safe Rust implementation eliminating buffer overflows and use-after-free vulnerabilities
- Stealth: Six evasion techniques including timing controls, decoys, fragmentation, TTL manipulation, bad checksums, and idle (zombie) scanning
- Completeness: Full-featured from host discovery through OS/service detection (85-90% detection rate, 187 Nmap probes)
- IPv6 Support: 100% coverage across all 6 scanner types (TCP SYN, TCP Connect, UDP, Stealth, Discovery, Decoy)
- Rate Limiting: Two-tier system (hostgroup control + AdaptiveRateLimiterV3) with -1.8% average overhead (faster than no limiting!)
- Extensibility: Plugin architecture and scripting engine for custom workflows
- Accessibility: Progressive interfaces from CLI → TUI → Web → GUI
Scan Types: 8 total
- TCP SYN (default, requires privileges)
- TCP Connect (fallback, no privileges)
- UDP (protocol-specific payloads)
- Stealth (FIN/NULL/Xmas/ACK) (firewall detection)
- Discovery (ICMP/ICMPv6/NDP, host enumeration)
- Decoy (source address spoofing for anonymity)
- Idle/Zombie (maximum anonymity via third-party relay)
Detection: 85-90% accuracy
- Service detection (187 Nmap probes embedded)
- Protocol parsers (HTTP, SSH, SMB, MySQL, PostgreSQL)
- Ubuntu/Debian/RHEL version mapping from banners
- OS fingerprinting (16 probes, 2,600+ signatures)
Performance: Production-Ready
- Common ports: 5.15ms (29x faster than Nmap)
- Full 65K ports: 259ms (146x faster than Phase 3 baseline)
- Idle scan: 500-800ms/port (stealth tradeoff, 300x slower)
- Zero-copy packet building (<1% overhead)
- NUMA optimization support (enterprise-ready)
- Modularity: Independent, testable components with clear interfaces
- Performance: Asynchronous I/O with zero-copy optimizations in hot paths
- Type Safety: Leverage Rust's type system to prevent invalid states
- Progressive Enhancement: Core functionality works without privileges; raw packets enhance capabilities
- Fail-Safe Defaults: Conservative settings prevent accidental network disruption
Each scanning technique, protocol handler, and output formatter exists as an independent, testable module. This enables:
- Unit testing of individual components in isolation
- Feature flags for conditional compilation (e.g., Lua plugins, Python bindings)
- Code reuse across different scanning modes
- Parallel development by multiple contributors
All I/O operations use Tokio's async runtime for maximum concurrency:
- Non-blocking I/O prevents thread starvation
- Work-stealing scheduler optimizes CPU utilization across cores
- Backpressure handling prevents memory exhaustion during large scans
- Graceful degradation under resource constraints
Minimize memory allocations and copies in hot paths:
- Memory-mapped I/O for large result files
- Borrowed data throughout the packet processing pipeline
- Pre-allocated buffers for packet crafting
- Lock-free data structures for inter-thread communication
Leverage Rust's type system to prevent invalid state transitions:
// Example: Type-safe scan state machine
enum ScanState {
Pending,
Probing { attempts: u8, last_sent: Instant },
Responded { packet: ResponsePacket },
Timeout,
Filtered,
}
// Compiler enforces state transitions
impl ScanState {
fn on_response(self, packet: ResponsePacket) -> Self {
match self {
ScanState::Probing { .. } => ScanState::Responded { packet },
_ => self, // Invalid transition ignored
}
}
}┌─────────────────────────────────────────────────────────────────┐
│ User Interface Layer │
│ (CLI Args Parser, TUI Dashboard, Web API, Desktop GUI) │
└────────────────────────┬─────────────────────────────────────────┘
│
┌────────────────────────▼─────────────────────────────────────────┐
│ Orchestration Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Scanner │ │ Rate │ │ Result │ │
│ │ Scheduler │ │ Controller │ │ Aggregator │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────┬─────────────────────────────────────────┘
│
┌────────────────────────▼─────────────────────────────────────────┐
│ Scanning Engine Layer │
│ ┌───────────────┐ ┌────────────────┐ ┌───────────────┐ │
│ │ Host Discovery│ │ Port Scanner │ │ Service Det. │ │
│ │ (ICMP/ARP) │ │ (TCP/UDP/SCTP) │ │ (Banners/Probes) │
│ └───────────────┘ └────────────────┘ └───────────────┘ │
│ ┌───────────────┐ ┌────────────────┐ ┌───────────────┐ │
│ │ OS Fingerprint│ │ Stealth Module │ │ Script Engine │ │
│ └───────────────┘ └────────────────┘ └───────────────┘ │
└────────────────────────┬─────────────────────────────────────────┘
│
┌────────────────────────▼─────────────────────────────────────────┐
│ Network Protocol Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Raw Packet │ │ TCP Stack │ │ Packet │ │
│ │ Crafting │ │ (Custom) │ │ Capture │ │
│ │ (pnet) │ │ │ │ (libpcap) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└────────────────────────┬─────────────────────────────────────────┘
│
┌────────────────────────▼─────────────────────────────────────────┐
│ Operating System Layer │
│ (Linux/Windows/macOS - Raw Sockets, BPF, Npcap, etc.) │
└───────────────────────────────────────────────────────────────────┘
- Parse command-line arguments and configuration files
- Present real-time progress and results
- Handle user interrupts and control signals
- Format output for human consumption
- Coordinate multi-phase scans (discovery → enumeration → deep inspection)
- Distribute work across worker threads
- Implement adaptive rate limiting and congestion control
- Aggregate and deduplicate results from multiple workers
- Implement specific scan techniques (SYN, UDP, ICMP, etc.)
- Perform service version detection and OS fingerprinting
- Execute stealth transformations (fragmentation, decoys, timing)
- Run plugin scripts for custom logic
- Craft raw packets at Ethernet/IP/TCP/UDP layers
- Capture and parse network responses
- Implement custom TCP/IP stack for stateless operation
- Apply BPF filters for efficient packet capture
- Platform-specific packet injection (AF_PACKET, BPF, Npcap)
- Privilege management (capabilities, setuid)
- Network interface enumeration and configuration
Purpose: Orchestrates scan jobs, manages target queues, distributes work across threads
Key Responsibilities:
- Parse and expand target specifications (CIDR, ranges, hostname lists)
- Randomize target order using permutation functions
- Shard targets across worker pools for parallel execution
- Coordinate multi-phase scans with dependency management
Implementation Pattern:
pub struct ScannerScheduler {
targets: TargetRandomizer,
workers: WorkerPool,
phases: Vec<ScanPhase>,
config: ScanConfig,
}
impl ScannerScheduler {
pub async fn execute(&mut self) -> Result<ScanReport> {
for phase in &self.phases {
match phase {
ScanPhase::Discovery => self.run_discovery().await?,
ScanPhase::Enumeration => self.run_enumeration().await?,
ScanPhase::DeepInspection => self.run_deep_inspection().await?,
}
}
Ok(self.generate_report())
}
}CDN Filtering Integration (Sprint 6.3 Bug Fix):
ProRT-IP supports intelligent filtering of CDN infrastructure IPs to reduce scan targets by 30-70% for internet-scale operations.
Critical Bug Fixed (2025-11-16):
The scanner scheduler has two entry points for executing scans:
scan_ports()- Internal method with full CDN filtering logic (lines 271-314)execute_scan_ports()- CLI entry point that LACKED filtering logic
Problem:
The CLI called execute_scan_ports() (via crates/prtip-cli/src/main.rs:557), which meant the --skip-cdn flag was non-functional in production despite filtering logic existing in scan_ports().
Root Cause:
Architectural mismatch between two scan execution methods. CDN filtering was implemented in the internal method but not in the CLI-facing method.
Fix (commit 19ba706):
Added 38 lines of CDN filtering logic to execute_scan_ports() at line 658, matching the pattern from scan_ports():
// In execute_scan_ports() - now includes CDN filtering
for target in targets {
let original_hosts = target.expand_hosts();
// Filter CDN IPs if enabled
let hosts = if let Some(ref detector) = self.cdn_detector {
let mut filtered = Vec::new();
let mut skipped = 0;
let mut provider_counts: std::collections::HashMap<CdnProvider, usize> =
std::collections::HashMap::new();
for host in original_hosts {
if let Some(provider) = detector.detect(&host) {
*provider_counts.entry(provider).or_insert(0) += 1;
skipped += 1;
debug!("Skipping CDN IP {}: {:?}", host, provider);
} else {
filtered.push(host);
}
}
if skipped > 0 {
let total = filtered.len() + skipped;
let reduction_pct = (skipped * 100) / total;
info!("Filtered {} CDN IPs ({}% reduction): {:?}",
skipped, reduction_pct, provider_counts);
}
if filtered.is_empty() {
debug!("All hosts filtered (CDN detection), continuing to next target");
continue;
}
debug!("Scanning {} hosts after CDN filtering", filtered.len());
filtered
} else {
original_hosts
};
// Continue with filtered hosts...
}Verification:
- 100% filtering rate confirmed across Cloudflare, AWS, Azure, Akamai, Fastly, Google Cloud
- Statistics logging working correctly
- Performance overhead: +37.5% for skip-all mode, -22.8% for whitelist mode (faster than baseline)
Supported CDN Providers:
- Cloudflare: 104.16.0.0/13 and 36 other ranges
- AWS CloudFront: 52.84.0.0/15 and 18 other ranges
- Azure CDN: 13.107.0.0/16 and 12 other ranges
- Akamai: 23.0.0.0/8 and 25 other ranges
- Fastly: 151.101.0.0/16 and 8 other ranges
- Google Cloud CDN: 35.186.0.0/16 and 15 other ranges
Purpose: Responsible scanning with precise control over network load and target concurrency
ProRT-IP implements a two-tier rate limiting architecture combining Nmap-compatible hostgroup control with industry-leading AdaptiveRateLimiterV3 achieving -1.8% average overhead (faster than no rate limiting!):
Purpose: Control concurrent target-level parallelism (Nmap --max-hostgroup / --min-hostgroup compatibility)
Key Responsibilities:
- Semaphore-based concurrent target limiting
- Applies to "multi-port" scanners (TCP SYN, TCP Connect, Concurrent)
- Separate from packet-per-second rate limiting
- Dynamic adjustment based on scan size
Implementation:
pub struct HostgroupLimiter {
semaphore: Arc<Semaphore>,
max_hostgroup: usize,
min_hostgroup: usize,
}CLI Flags:
--max-hostgroup N- Maximum concurrent targets (default: 100)--min-hostgroup N- Minimum concurrent targets (default: 1)
Scanner Categories:
Multi-Port Scanners (3): Hostgroup limiting applied
- ConcurrentScanner (adaptive parallelism)
- TcpConnectScanner (kernel stack)
- SynScanner (raw sockets)
Per-Port Scanners (4): No hostgroup limiting (per-port iteration)
- UdpScanner
- StealthScanner (FIN/NULL/Xmas/ACK)
- IdleScanner (zombie relay)
- DecoyScanner (source spoofing)
Benefits:
- Prevents overwhelming single targets
- Reduces memory usage (bounded concurrency)
- Nmap workflow compatibility
Status: ✅ Default Rate Limiter (promoted 2025-11-02) achieving -1.8% average overhead
Purpose: Packet-per-second throttling with two-tier convergence and Relaxed memory ordering
Key Innovations:
- Relaxed Memory Ordering: Eliminates memory barriers (10-30ns savings per operation)
- Two-Tier Convergence: Hostgroup-level aggregate + per-target batch scheduling
- Self-Correction: Convergence compensates for stale atomic reads:
batch *= sqrt(target/observed) - Batch Range: 1.0 → 10,000.0 packets/batch
Implementation:
pub struct AdaptiveRateLimiterV3 {
// Hostgroup-level tracking
hostgroup_rate: Arc<AtomicU64>,
hostgroup_last_time: Arc<AtomicU64>,
// Per-target state
batch_size: AtomicU64, // f64 as u64 bits
max_rate: u64, // packets per second
}
pub type RateLimiter = AdaptiveRateLimiterV3; // Type alias for backward compatibilityPerformance Achievement (Sprint 5.X):
| Rate (pps) | Baseline (ms) | With V3 (ms) | Overhead | Performance Grade |
|---|---|---|---|---|
| 10K | 8.9 ± 1.4 | 8.2 ± 0.4 | -8.2% | ✅ Best Case |
| 50K | 7.3 ± 0.3 | 7.2 ± 0.3 | -1.8% | ✅ Typical |
| 75K-200K | 7.2-7.4 | 7.0-7.2 | -3% to -4% | ✅ Sweet Spot |
| 500K-1M | 7.2-7.4 | 7.2-7.6 | +0% to +3.1% | ✅ Minimal |
Average Overhead: -1.8% (weighted by typical usage patterns)
CLI Flags:
--max-rate N- Maximum packets per second (auto-uses V3)-T0through-T5- Timing templates (paranoid → insane)
Benefits:
- Industry-leading performance (faster than no rate limiting!)
- 15.2 percentage points improvement over previous implementation
- 34% variance reduction (more consistent timing)
- Self-tuning (no manual configuration needed)
- Production-ready (comprehensive testing, 1,466 tests passing)
Multi-Port Scanners:
// Acquire hostgroup slot (Tier 1)
let _permit = hostgroup_limiter.acquire().await;
// Scan all ports for this target
for port in ports {
// Adaptive rate limiting (Tier 2: V3)
rate_limiter.wait().await;
send_packet(target_ip, port).await;
}Per-Port Scanners:
// No hostgroup limiting (per-port iteration)
for (target_ip, port) in targets_x_ports {
// Adaptive rate limiting (Tier 2: V3)
rate_limiter.wait().await;
send_packet(target_ip, port).await;
}Historical Context:
Prior to Sprint 5.X (2025-11-02), ProRT-IP used a three-layer system:
- ICMP Type 3 Code 13 Detection - Automatic backoff (removed, better handled at firewall level)
- Hostgroup Limiting - Concurrent target control (retained as Tier 1)
- Governor Token Bucket - Rate limiting with 40% overhead (replaced by V3)
The V3 promotion (Sprint 5.X Phase 5) consolidated to a cleaner two-tier architecture, achieving 15.2 percentage point overhead reduction (40% → -1.8%) while maintaining full Nmap compatibility.
For comprehensive usage examples, CLI flags, performance tuning, and troubleshooting, see docs/26-RATE-LIMITING-GUIDE.md.
Purpose: Collect, deduplicate, and merge scan results from multiple workers with memory-mapped I/O support
Key Responsibilities:
- Thread-safe result collection using lock-free queues
- Merge partial results for the same host/port (e.g., from retransmissions)
- Maintain canonical port state (open/closed/filtered)
- Stream results to output formatters without buffering entire dataset
- Handle out-of-order results from parallel workers
- NEW (Sprint 6.6): Memory-mapped I/O for internet-scale scans (77-86% RAM reduction)
Result Merging Logic:
pub struct ResultAggregator {
results: DashMap<TargetKey, TargetResult>,
output_tx: mpsc::Sender<ScanResult>,
}
impl ResultAggregator {
pub fn merge_result(&self, new_result: ScanResult) {
self.results.entry(new_result.key())
.and_modify(|existing| {
// Merge logic: open > closed > filtered > unknown
if new_result.state > existing.state {
existing.state = new_result.state;
}
existing.banners.extend(new_result.banners);
})
.or_insert(new_result.clone().into());
}
}Memory-Mapped I/O (Sprint 6.6):
For internet-scale scans (10K+ expected results), ProRT-IP automatically switches to memory-mapped I/O:
pub enum ResultWriter {
Memory(MemoryWriter), // Default for small scans
Mmap(MmapResultWriter), // Automatic for large scans
}
pub struct MmapResultWriter {
file: File,
mmap: MmapMut,
position: usize,
index: Vec<usize>, // Result offsets for iteration
}
impl MmapResultWriter {
pub fn write_result(&mut self, result: &ScanResult) -> Result<()> {
let serialized = bincode::serialize(result)?;
// Auto-grow if needed
if self.position + serialized.len() > self.mmap.len() {
self.grow(1024 * 1024)?; // +1MB increments
}
// Zero-copy write
self.mmap[self.position..self.position + serialized.len()]
.copy_from_slice(&serialized);
self.index.push(self.position);
self.position += serialized.len();
Ok(())
}
}Benefits:
- 77-86% RAM reduction (100K results: 35MB → 5MB)
- Transparent to scanner (same API)
- Zero-copy iteration via
MmapResultReader - Auto-growth (1MB increments)
- Configurable threshold (
--mmap-threshold N)
Purpose: Generate raw network packets for all scan types
Key Responsibilities:
- Build complete packets from Ethernet layer upward
- Apply stealth transformations (fragmentation, TTL manipulation, decoys)
- Calculate checksums including pseudo-headers
- Support source address/port spoofing
Builder Pattern:
let packet = TcpPacketBuilder::new()
.source(local_ip, random_port())
.destination(target_ip, target_port)
.sequence(random_seq())
.flags(TcpFlags::SYN)
.window_size(65535)
.tcp_option(TcpOption::Mss(1460))
.tcp_option(TcpOption::WindowScale(7))
.tcp_option(TcpOption::SackPermitted)
.tcp_option(TcpOption::Timestamp { tsval: now(), tsecr: 0 })
.build()?;Purpose: Receive and parse network responses efficiently
Key Responsibilities:
- Configure BPF filters to reduce captured traffic (e.g., only TCP/UDP/ICMP to scanner)
- Parse responses into structured data with zero-copy where possible
- Match responses to probes using connection tracking or stateless validation
- Handle out-of-order packets and duplicates
BPF Filter Example:
// Capture only packets destined to our scanner
let filter = format!(
"((tcp or udp) and dst host {}) or (icmp and host {})",
local_ip, local_ip
);
pcap_handle.filter(&filter, true)?;ProRT-IP provides full IPv6 support across all scanning modes (Sprint 5.1, 100% scanner coverage). The architecture uses runtime protocol dispatch to handle both IPv4 and IPv6 transparently.
pub enum IpAddr {
V4(Ipv4Addr),
V6(Ipv6Addr),
}
// All scanners use this pattern
pub async fn scan_target(addr: SocketAddr) -> Result<PortState> {
match addr.ip() {
IpAddr::V4(ipv4) => scan_ipv4(ipv4, addr.port()).await,
IpAddr::V6(ipv6) => scan_ipv6(ipv6, addr.port()).await,
}
}IPv6 Header (40 bytes, fixed size):
- Version (4 bits): Always 6
- Traffic Class (8 bits): QoS/priority
- Flow Label (20 bits): Flow identification
- Payload Length (16 bits): Length of payload (excluding header)
- Next Header (8 bits): Protocol (TCP=6, UDP=17, ICMPv6=58)
- Hop Limit (8 bits): TTL equivalent
- Source Address (128 bits): IPv6 source
- Destination Address (128 bits): IPv6 destination
Key Differences from IPv4:
- No fragmentation in router (sender-only)
- No header checksum (delegated to link layer)
- No options in main header (use extension headers)
- Minimum MTU: 1280 bytes (vs 68 bytes IPv4)
ICMPv6 Message Types:
- Type 1: Destination Unreachable (UDP port closed indication)
- Type 3: Time Exceeded (firewall drop indication)
- Type 128: Echo Request (ping6 equivalent)
- Type 129: Echo Reply (ping6 response)
- Type 135: Neighbor Solicitation (ARP equivalent)
- Type 136: Neighbor Advertisement (ARP reply equivalent)
Neighbor Discovery Protocol (NDP):
Target: 2001:db8::1234:5678
Solicited-Node Multicast: ff02::1:ff34:5678
^^^^^^^^
Last 24 bits
NDP provides:
- Address resolution (ARP equivalent)
- Router discovery
- Neighbor unreachability detection
- Duplicate address detection
- Uses kernel TCP stack (AF_INET6)
- No raw sockets required
- Full three-way handshake
- Automatic IPv4/IPv6 socket creation
- Raw socket (AF_PACKET/Npcap)
- IPv6 pseudo-header for TCP checksum
- 40-byte IPv6 header + 20-byte TCP header
- Requires root/administrator privileges
- Raw socket for receiving ICMPv6 responses
- ICMPv6 Type 1, Code 4: Port Unreachable (closed)
- Protocol-specific payloads (DNS, SNMP, etc.)
- Slower than TCP (10-100x) due to timeouts
- Raw packet crafting with unusual flag combinations
- IPv6 firewalls may behave differently than IPv4
- Stateful firewalls often block these scans
- Useful for firewall detection
- ICMPv6 Echo Request/Reply (Type 128/129)
- NDP Neighbor Solicitation/Advertisement (Type 135/136)
- Solicited-node multicast for efficient discovery
- Link-local and global unicast support
- Random IPv6 Interface Identifier (IID) generation
- Subnet-aware /64 decoy placement
- Avoids reserved IPv6 ranges (loopback, multicast, link-local, etc.)
- Source address spoofing (requires privileges)
IPv6 Overhead:
- Header size: +100% (40 bytes vs 20 bytes)
- Checksum calculation: -50% CPU (no IP checksum, TCP/UDP only)
- Latency: +0-25% (depending on network)
- Throughput: -3% at 1Gbps (negligible)
Optimization Strategies:
- Zero-copy packet building (reuse buffers)
- Batched syscalls (sendmmsg/recvmmsg)
- Parallel processing (multi-threaded runtime)
- Adaptive concurrency (scale with scan size)
For comprehensive IPv6 usage examples, CLI flags, and troubleshooting, see docs/23-IPv6-GUIDE.md.
Use Case: Large-scale initial discovery (internet-wide sweeps)
Characteristics:
- No connection state maintained per target
- SipHash-based sequence numbers encode target identity
- Maximum transmission speed (10M+ packets/second capable)
- Minimal memory footprint (O(1) state regardless of target count)
- Target randomization via permutation functions
Validation Without State:
// Encode target in sequence number
fn generate_seq(ip: Ipv4Addr, port: u16, key: (u64, u64)) -> u32 {
let mut hasher = SipHasher::new_with_keys(key.0, key.1);
hasher.write(&ip.octets());
hasher.write_u16(port);
(hasher.finish() & 0xFFFFFFFF) as u32
}
// Validate response without lookup
fn validate_response(packet: &TcpPacket, key: (u64, u64)) -> Option<TargetId> {
let expected_ack = generate_seq(
packet.dest_ip(),
packet.dest_port(),
key
).wrapping_add(1);
if packet.ack() == expected_ack {
Some(TargetId { ip: packet.dest_ip(), port: packet.dest_port() })
} else {
None
}
}Use Case: Detailed enumeration, stealth scanning, service detection
Characteristics:
- Per-connection state tracking in hash map
- Retransmission support with exponential backoff
- Congestion control based on RTT estimates
- Multiple scan types (SYN, FIN, NULL, Xmas, ACK, Window, Maimon)
- Deep packet inspection for OS fingerprinting
State Machine:
enum ConnectionState {
Pending,
SynSent { sent_at: Instant, seq: u32 },
SynAckReceived { rtt: Duration },
RstReceived,
Timeout { attempts: u8 },
}Use Case: Balanced speed and depth (recommended for most scans)
Workflow:
- Fast Discovery: Stateless SYN sweep across all targets (Phase 1)
- Filter Responsive: Identify hosts/ports that responded (Phase 2)
- Deep Enumeration: Stateful service detection on responsive targets only (Phase 3)
Benefits:
- 90%+ time reduction vs. full stateful scan
- Maintains accuracy for service detection
- Automatic fallback to stateful if response rate is low
Use Case: Maximum anonymity - scan target without revealing scanner's IP address
Architecture: Three-party relay system using "zombie" host as intermediary
Key Components:
- IP ID Tracking - Monitor zombie's IP ID sequence (must be sequential)
- Spoofed SYN/ACK - Send spoofed packets appearing to come from zombie
- Port State Inference - Deduce target port state from IP ID increments
How It Works:
Scanner (Hidden) Zombie (Relay) Target
│ │ │
│ 1. Probe │ │
├──────SYN/ACK───────>│ │
│ │<──RST (IP ID=100)─┤
│ │ │
│ 2. Spoof (src=Zombie, dst=Target:80) │
│───────────────────────────SYN────────────>│
│ │ │
│ │<──SYN/ACK (open)──┤ If port open
│ ├──RST──────────────>│ (zombie responds)
│ │ (IP ID inc by 2) │
│ │ │
│ 3. Probe again │ │
├──────SYN/ACK───────>│ │
│ │<──RST (IP ID=102)─┤
│ │ │
│ Port OPEN (IP ID increased by 2) │
IP ID Pattern Analysis:
- +0: Port filtered (no response from target)
- +1: Port closed (target sent RST to zombie, zombie ignored)
- +2: Port open (target sent SYN/ACK, zombie sent RST back)
Zombie Requirements:
- Sequential IP ID: Must use incrementing IP ID (not random)
- Low Traffic: Minimal background traffic (stable IP ID)
- Connectivity: Reachable from scanner and target
- OS Compatibility: Most Linux/BSD (not Windows/modern macOS)
Implementation:
pub struct IdleScanner {
zombie_addr: IpAddr,
target_addr: IpAddr,
ipid_tracker: IpIdTracker,
spoof_engine: SpoofEngine,
}
pub enum IpIdPattern {
Sequential, // +1 per packet (good zombie)
Random, // Unpredictable (bad zombie)
Global, // Shared counter (acceptable)
PerDestination, // Per-target counter (poor)
}
impl IdleScanner {
pub async fn scan_port(&mut self, port: u16) -> Result<PortState> {
// 1. Probe zombie (baseline IP ID)
let ipid_before = self.probe_zombie().await?;
// 2. Spoof SYN from zombie to target
self.send_spoofed_syn(self.target_addr, port).await?;
sleep(Duration::from_millis(500)).await;
// 3. Probe zombie again (measure IP ID change)
let ipid_after = self.probe_zombie().await?;
// 4. Infer port state from IP ID delta
match ipid_after.wrapping_sub(ipid_before) {
0 => Ok(PortState::Filtered),
1 => Ok(PortState::Closed),
2 => Ok(PortState::Open),
_ => Err(Error::ZombieUnstable),
}
}
}Performance Characteristics:
- Speed: 500-800ms per port (300x slower than direct SYN scan)
- Accuracy: 99.5% (when zombie requirements met)
- Anonymity: Maximum (target logs only zombie IP, not scanner)
- Detection: Extremely difficult (no direct connection to target)
Nmap Compatibility:
ProRT-IP provides full Nmap -sI flag parity:
- Automatic zombie discovery (
-sI RNDequivalent) - Manual zombie specification (
-sI <zombie_ip>) - Timing control (
-T0to-T5) - Port range support (single, ranges, lists)
- Verbose progress reporting
- Zombie suitability testing
Limitations:
- IPv6: Not yet implemented (planned for future sprint)
- Firewalls: Stateful firewalls may block spoofed packets
- Speed: 300x slower than direct scans (patience required)
- Zombie Finding: Requires sequential IP ID hosts (increasingly rare)
For comprehensive usage examples, zombie discovery, troubleshooting, and security considerations, see docs/25-IDLE-SCAN-GUIDE.md.
[Target Specification]
│
▼
[Target Parser & Expander]
│
▼
[Target Randomizer] ──────────┐
│ │
▼ │
[Worker Pool Distribution] │ (Permutation index)
│ │
├──────┬──────┬───────┘
▼ ▼ ▼
[Worker] [Worker] [Worker]
│ │ │
▼ ▼ ▼
[Packet Crafting Engine]
│ │ │
▼ ▼ ▼
[Raw Socket Transmission]
│
▼
[Network]
│
▼
[Packet Capture]
│
▼
[Response Parser]
│
▼
[Response Validator]
│
▼
[Result Aggregator]
│
▼
[Output Formatters]
│
▼
[Files/DB/Screen]
Phase 1: Discovery
├─ ICMP ping sweep
├─ TCP SYN to common ports (top 100)
├─ UDP to common services
└─ ARP scan (local networks)
│
▼
[Responsive Hosts List]
│
▼
Phase 2: Enumeration
├─ TCP SYN to all 65535 ports (responsive hosts)
├─ UDP to expanded port list
└─ Protocol-specific probes
│
▼
[Open Ports List]
│
▼
Phase 3: Deep Inspection
├─ Service version detection
├─ OS fingerprinting
├─ Banner grabbing
├─ SSL/TLS certificate analysis
└─ Script execution (NSE-like)
│
▼
[Complete Scan Report]
- Rust 1.70+ (MSRV - Minimum Supported Rust Version)
- Memory safety without garbage collection
- Zero-cost abstractions
- Fearless concurrency
- Excellent cross-platform support
- Tokio 1.35+ with multi-threaded scheduler
- Work-stealing task scheduler
- Efficient I/O event loop (epoll/kqueue/IOCP)
- Semaphores and channels for coordination
- Timer wheels for timeout management
- pnet 0.34+ for packet crafting and parsing
- pcap 1.1+ for libpcap bindings
- socket2 0.5+ for low-level socket operations
- etherparse 0.14+ for fast zero-copy packet parsing
- crossbeam 0.8+ for lock-free data structures (queues, deques)
- parking_lot 0.12+ for efficient mutexes (when locks are necessary)
- rayon 1.8+ for data parallelism in analysis phases
- rusqlite 0.30+ for SQLite backend (default)
- sqlx 0.7+ for PostgreSQL support (optional)
- serde 1.0+ for JSON/TOML/XML serialization
- Linux:
nixcrate for capabilities,libcfor syscalls - Windows:
winapifor Winsock2, Npcap SDK - macOS:
nixcrate for BPF device access
Used extensively for packet construction:
TcpPacketBuilder::new()
.source(ip, port)
.destination(target_ip, target_port)
.flags(TcpFlags::SYN)
.build()?Scan type selection:
trait ScanStrategy {
async fn execute(&self, target: SocketAddr) -> Result<PortState>;
}
struct SynScan;
struct FinScan;
struct UdpScan;
// Each implements ScanStrategy with different logicResult streaming:
trait ResultObserver {
fn on_result(&mut self, result: ScanResult);
}
struct FileWriter { /* ... */ }
struct DatabaseWriter { /* ... */ }
struct TerminalPrinter { /* ... */ }
// Aggregator notifies all registered observersCLI argument handling:
enum ScanCommand {
PortScan { targets: Vec<Target>, ports: PortRange },
HostDiscovery { network: IpNetwork },
ServiceDetection { targets: Vec<SocketAddr> },
}
impl ScanCommand {
fn execute(&self) -> Result<()> {
match self {
ScanCommand::PortScan { .. } => { /* ... */ }
// ...
}
}
}Compile-time state enforcement:
struct Scanner<S> {
state: PhantomData<S>,
// ...
}
struct Unconfigured;
struct Configured;
struct Running;
impl Scanner<Unconfigured> {
fn configure(self, config: ScanConfig) -> Scanner<Configured> {
// ...
}
}
impl Scanner<Configured> {
fn start(self) -> Scanner<Running> {
// Can only call start() if configured
// ...
}
}- Async I/O prevents blocking on slow network operations
- Lock-free queues eliminate contention in hot paths
- Zero-copy parsing reduces memory bandwidth requirements
- NUMA awareness keeps data local to processing cores
- Memory safety prevents buffer overflows and use-after-free
- Type safety catches logic errors at compile time
- Error handling forces explicit handling of failures
- Bounds checking prevents array overruns (with negligible overhead)
- Modular design enables independent testing and development
- Clear interfaces reduce coupling between components
- Comprehensive logging aids debugging and troubleshooting
- Documentation tests keep examples synchronized with code
- Plugin architecture supports custom scan logic
- Scripting engine enables rapid prototyping
- Output formatters are independent and pluggable
- Scan strategies can be added without core changes
The plugin system provides extensibility through sandboxed Lua plugins, enabling community-driven scanner enhancements.
PluginManager (src/plugin/plugin_manager.rs, 399 lines)
- Plugin discovery (scan
~/.prtip/plugins/directory) - Plugin loading (create Lua VM, parse metadata)
- Lifecycle management (on_load → execute → on_unload)
- Hot reload support (Arc<Mutex> for thread safety)
Plugin API (src/plugin/plugin_api.rs, 522 lines)
- Trait-based design for type safety
- ScanPlugin: Pre-scan setup, post-scan cleanup hooks
on_load()- Initialize pluginpre_scan(target)- Execute before scanpost_scan(target, results)- Execute after scanon_unload()- Cleanup
- OutputPlugin: Custom result formatting
on_load()- Initialize pluginformat_results(results)- Format scan resultson_unload()- Cleanup
- DetectionPlugin: Service detection
on_load()- Initialize pluginanalyze_banner(banner, port, protocol)- Passive detectionprobe_service(target, port)- Active detectionon_unload()- Cleanup
Lua API (src/plugin/lua_api.rs, 388 lines)
- Sandboxed Lua VM (mlua 0.11.3 with Lua 5.4)
- ProRT-IP API exposed to plugins:
prtip.log(level, message)- Loggingprtip.get_target()- Access scan targetprtip.connect(host, port)- Network operations (requires Network capability)prtip.send(socket, data)- Send dataprtip.receive(socket)- Receive dataprtip.close(socket)- Close connectionprtip.add_result(data)- Add scan result
Security Layer (src/plugin/security.rs, 320 lines)
- Capabilities-based access control:
- Network: Allow/deny network operations
- Filesystem: Allow/deny file access
- System: Allow/deny system calls
- Database: Allow/deny database operations
- Lua VM sandboxing:
- Remove
iolibrary (prevent arbitrary file access) - Remove
oslibrary (prevent command execution) - Remove
debuglibrary (prevent introspection) - Keep
string,table,math(safe libraries)
- Remove
- Resource limits:
- Memory: 100MB per plugin
- CPU: 5 seconds execution time
- Instructions: 1 million Lua instructions maximum
Metadata Parser (src/plugin/plugin_metadata.rs, 272 lines)
- TOML parsing (plugin.toml files)
- Version validation (semver compatibility)
- Capability parsing (Network/Filesystem/System/Database)
- Dependency tracking
Scanner Core
↓
PluginManager.discover_plugins()
↓
PluginManager.load_plugin(path)
├── Parse plugin.toml (metadata)
├── Create sandboxed Lua VM
├── Register ProRT-IP API (prtip.*)
├── Load main.lua
└── Call on_load() hook
↓
Plugin Execution
├── ScanPlugin: pre_scan() → scan → post_scan()
├── OutputPlugin: format_results()
└── DetectionPlugin: analyze_banner() or probe_service()
↓
Results → Output System
↓
PluginManager.unload_plugin()
└── Call on_unload() hook
Deny-by-Default Capabilities: Plugins must explicitly request capabilities in plugin.toml:
[plugin.capabilities]
network = false # Deny network access by default
filesystem = false # Deny filesystem access by default
system = false # Deny system calls by default
database = false # Deny database access by defaultSandboxing Enforcement:
- Lua VM created with restricted standard library
- Dangerous libraries removed before plugin execution
- Resource limits enforced at runtime
- Thread-safe implementation (Arc<Mutex>)
Example Plugins:
- banner-analyzer (DetectionPlugin): Passive analysis, no capabilities required
- ssl-checker (DetectionPlugin): Active probing, network capability required
- Plugin overhead: <2% per plugin
- 5 plugins overhead: <10% total
- Plugin loading: <100ms
- Hot reload: Zero downtime
See Plugin System Guide for complete documentation.
- Review Development Roadmap for implementation phases
- Consult Technical Specifications for detailed component design
- See Development Setup Guide for build environment configuration
- Examine Testing Strategy for quality assurance approach