diff --git a/Cargo.lock b/Cargo.lock index f2feb6d..9253325 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,15 +181,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bitflags" version = "2.10.0" @@ -225,6 +216,29 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "bytecheck" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0caa33a2c0edca0419d15ac723dff03f1956f7978329b1e3b5fdaaaed9d3ca8b" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89385e82b5d1821d2219e0b095efa2cc1f246cbf99080f3be46a1a85c0d392d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -1689,6 +1703,26 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "munge" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "native-tls" version = "0.2.14" @@ -2272,6 +2306,7 @@ dependencies = [ "parking_lot", "rand", "regex", + "rkyv", "rlimit", "serde", "serde_json", @@ -2313,7 +2348,6 @@ name = "prtip-scanner" version = "0.5.9" dependencies = [ "anyhow", - "bincode", "chrono", "criterion", "crossbeam", @@ -2335,6 +2369,7 @@ dependencies = [ "prtip-network", "rand", "regex", + "rkyv", "rustls", "serde", "serde_json", @@ -2372,6 +2407,26 @@ dependencies = [ "uuid", ] +[[package]] +name = "ptr_meta" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "quanta" version = "0.12.6" @@ -2412,6 +2467,15 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rancor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a063ea72381527c2a0561da9c80000ef822bdd7c3241b1cc1b12100e3df081ee" +dependencies = [ + "ptr_meta", +] + [[package]] name = "rand" version = "0.8.5" @@ -2562,6 +2626,15 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "rend" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadadef317c2f20755a64d7fdc48f9e7178ee6b0e1f7fce33fa60f1d68a276e6" +dependencies = [ + "bytecheck", +] + [[package]] name = "ring" version = "0.17.14" @@ -2576,6 +2649,36 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360b333c61ae24e5af3ae7c8660bd6b21ccd8200dbbc5d33c2454421e85b9c69" +dependencies = [ + "bytecheck", + "bytes", + "hashbrown 0.16.1", + "indexmap", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02f8cdd12b307ab69fe0acf4cd2249c7460eb89dce64a0febadf934ebb6a9e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "rlimit" version = "0.10.2" @@ -2884,6 +2987,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.11" diff --git a/Cargo.toml b/Cargo.toml index ce9fa9a..00d0bc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ rusqlite = { version = "0.31", features = ["bundled"] } # Memory-mapped I/O (Sprint 6.6) memmap2 = "0.9" -bincode = "1.3" +rkyv = "0.8.14" # CSV export (promote to workspace) csv = "1.3" diff --git a/crates/prtip-core/Cargo.toml b/crates/prtip-core/Cargo.toml index 60486ee..56f3ef4 100644 --- a/crates/prtip-core/Cargo.toml +++ b/crates/prtip-core/Cargo.toml @@ -30,6 +30,7 @@ rand = { workspace = true } sysinfo = { workspace = true } flate2 = "1.0" dirs = "5.0" +rkyv = { workspace = true } [dev-dependencies] tokio = { workspace = true } diff --git a/crates/prtip-core/src/lib.rs b/crates/prtip-core/src/lib.rs index d702ea6..ecef24c 100644 --- a/crates/prtip-core/src/lib.rs +++ b/crates/prtip-core/src/lib.rs @@ -136,4 +136,4 @@ pub use resource_monitor::{ pub use retry::{retry_with_backoff, RetryConfig}; pub use service_db::{ServiceMatch, ServiceProbe, ServiceProbeDb}; pub use templates::{ScanTemplate, TemplateManager}; -pub use types::{PortRange, PortState, Protocol, ScanResult, ScanTarget, ScanType, TimingTemplate}; +pub use types::{PortRange, PortState, Protocol, ScanResult, ScanResultRkyv, ScanTarget, ScanType, TimingTemplate}; diff --git a/crates/prtip-core/src/types.rs b/crates/prtip-core/src/types.rs index 7084f49..e8fd48e 100644 --- a/crates/prtip-core/src/types.rs +++ b/crates/prtip-core/src/types.rs @@ -3,6 +3,7 @@ use crate::error::{Error, Result}; use chrono::{DateTime, Utc}; use ipnetwork::IpNetwork; +use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; use serde::{Deserialize, Serialize}; use std::fmt; use std::net::IpAddr; @@ -315,7 +316,8 @@ impl Iterator for PortRangeIterator { } /// State of a scanned port -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Archive, RkyvSerialize, RkyvDeserialize)] +#[rkyv(derive(Debug))] pub enum PortState { /// Port is open and accepting connections Open, @@ -475,6 +477,53 @@ impl fmt::Display for TimingTemplate { } } +/// Serializable representation of ScanResult for rkyv +#[derive(Debug, Clone, Archive, RkyvSerialize, RkyvDeserialize)] +#[rkyv(derive(Debug))] +pub struct ScanResultRkyv { + target_ip: IpAddr, + port: u16, + state: PortState, + response_time_nanos: u128, + timestamp_nanos: i64, + banner: Option, + service: Option, + version: Option, + raw_response: Option>, +} + +impl From<&ScanResult> for ScanResultRkyv { + fn from(result: &ScanResult) -> Self { + Self { + target_ip: result.target_ip, + port: result.port, + state: result.state, + response_time_nanos: result.response_time.as_nanos(), + timestamp_nanos: result.timestamp.timestamp_nanos_opt().unwrap_or(0), + banner: result.banner.clone(), + service: result.service.clone(), + version: result.version.clone(), + raw_response: result.raw_response.clone(), + } + } +} + +impl From for ScanResult { + fn from(rkyv: ScanResultRkyv) -> Self { + Self { + target_ip: rkyv.target_ip, + port: rkyv.port, + state: rkyv.state, + response_time: Duration::from_nanos(rkyv.response_time_nanos as u64), + timestamp: DateTime::from_timestamp_nanos(rkyv.timestamp_nanos), + banner: rkyv.banner, + service: rkyv.service, + version: rkyv.version, + raw_response: rkyv.raw_response, + } + } +} + /// Result of scanning a single port #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ScanResult { diff --git a/crates/prtip-scanner/Cargo.toml b/crates/prtip-scanner/Cargo.toml index c42f5e5..b073d45 100644 --- a/crates/prtip-scanner/Cargo.toml +++ b/crates/prtip-scanner/Cargo.toml @@ -87,7 +87,7 @@ pcap-file = "2.0" # Memory-mapped I/O memmap2 = { workspace = true } -bincode = { workspace = true } +rkyv = { workspace = true, features = ["alloc"] } [dev-dependencies] tokio = { workspace = true } diff --git a/crates/prtip-scanner/src/output/mmap_reader.rs b/crates/prtip-scanner/src/output/mmap_reader.rs index 84003e6..0fb92b4 100644 --- a/crates/prtip-scanner/src/output/mmap_reader.rs +++ b/crates/prtip-scanner/src/output/mmap_reader.rs @@ -1,13 +1,14 @@ //! Memory-mapped result reader for zero-copy access to scan results use memmap2::Mmap; -use prtip_core::ScanResult; +use prtip_core::{ScanResult, ScanResultRkyv}; use std::fs::File; use std::io; use std::path::Path; const HEADER_SIZE: usize = 64; const ENTRY_SIZE: usize = 512; +const LENGTH_PREFIX_SIZE: usize = 8; // 8 bytes for length to maintain alignment /// Memory-mapped result reader pub struct MmapResultReader { @@ -75,10 +76,24 @@ impl MmapResultReader { } let offset = HEADER_SIZE + (index * self.entry_size); - let entry_bytes = &self.mmap[offset..offset + self.entry_size]; + + // Read length prefix (8 bytes) + let len_bytes: [u8; 8] = self.mmap[offset..offset + LENGTH_PREFIX_SIZE].try_into().ok()?; + let len = u64::from_le_bytes(len_bytes) as usize; + + if len == 0 || len + LENGTH_PREFIX_SIZE > self.entry_size { + return None; + } - // Deserialize the entry (bincode handles trailing zeros) - bincode::deserialize(entry_bytes).ok() + // Copy data to an aligned buffer (rkyv requires alignment) + let entry_bytes = &self.mmap[offset + LENGTH_PREFIX_SIZE..offset + LENGTH_PREFIX_SIZE + len]; + let aligned_data: Vec = entry_bytes.to_vec(); + + // Deserialize using rkyv + match rkyv::from_bytes::(&aligned_data) { + Ok(rkyv_result) => Some(rkyv_result.into()), + Err(_) => None, + } } /// Create an iterator over all entries diff --git a/crates/prtip-scanner/src/output/mmap_writer.rs b/crates/prtip-scanner/src/output/mmap_writer.rs index 7b9ee77..2643550 100644 --- a/crates/prtip-scanner/src/output/mmap_writer.rs +++ b/crates/prtip-scanner/src/output/mmap_writer.rs @@ -5,13 +5,14 @@ //! fixed-size entries for zero-copy random access. use memmap2::{MmapMut, MmapOptions}; -use prtip_core::ScanResult; +use prtip_core::{ScanResult, ScanResultRkyv}; use std::fs::{File, OpenOptions}; use std::io; use std::path::Path; const HEADER_SIZE: usize = 64; // Version, entry_count, entry_size, checksum const ENTRY_SIZE: usize = 512; // Fixed-size entries (padded if needed) +const LENGTH_PREFIX_SIZE: usize = 8; // 8 bytes for length to maintain alignment /// Memory-mapped result writer pub struct MmapResultWriter { @@ -56,25 +57,34 @@ impl MmapResultWriter { } let offset = HEADER_SIZE + (self.entry_count * ENTRY_SIZE); - let entry_bytes = bincode::serialize(result) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - - if entry_bytes.len() > ENTRY_SIZE { + + // Convert to rkyv-compatible type and serialize + let rkyv_result: ScanResultRkyv = result.into(); + let entry_bytes = rkyv::to_bytes::(&rkyv_result) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e.to_string()))?; + + // Check if data + length prefix fits + if entry_bytes.len() + LENGTH_PREFIX_SIZE > ENTRY_SIZE { return Err(io::Error::new( io::ErrorKind::InvalidData, format!( - "Entry size {} exceeds maximum {}", + "Entry size {} (+{} length) exceeds maximum {}", entry_bytes.len(), + LENGTH_PREFIX_SIZE, ENTRY_SIZE ), )); } - // Write serialized data - self.mmap[offset..offset + entry_bytes.len()].copy_from_slice(&entry_bytes); + // Write length prefix (8 bytes for alignment) + let len = entry_bytes.len() as u64; + self.mmap[offset..offset + LENGTH_PREFIX_SIZE].copy_from_slice(&len.to_le_bytes()); + + // Write serialized data after length prefix + self.mmap[offset + LENGTH_PREFIX_SIZE..offset + LENGTH_PREFIX_SIZE + entry_bytes.len()].copy_from_slice(&entry_bytes); // Zero-fill remaining space - for i in entry_bytes.len()..ENTRY_SIZE { + for i in (LENGTH_PREFIX_SIZE + entry_bytes.len())..ENTRY_SIZE { self.mmap[offset + i] = 0; } diff --git a/docs/archive/19-PHASE4-ENHANCEMENTS.md b/docs/archive/19-PHASE4-ENHANCEMENTS.md index 039f073..2f617f4 100644 --- a/docs/archive/19-PHASE4-ENHANCEMENTS.md +++ b/docs/archive/19-PHASE4-ENHANCEMENTS.md @@ -83,7 +83,7 @@ Following the successful completion of Phase 4 (Performance Optimization) with 7 **Primary Research:** - **Local Code Analysis**: Analyzed code_ref/ directory containing RustScan (Rust), Nmap (C++), Masscan fragments - **GitHub Repository Review**: Deep dive into RustScan/RustScan (18.2K stars), robertdavidgraham/masscan (24.9K stars) -- **Online Research**: 15+ articles including Medium analyses, GeeksforGeeks tutorials, findsec.org comparisons +- **Online Research**: 15+ articles including Medium analyses, GeeksforGeeks tutorials, network scanner comparisons - **Community Discussions**: Reddit (r/netsec, r/rust), Stack Overflow, GitHub issues across all projects **Analysis Period:** October 2025 @@ -1583,8 +1583,8 @@ Create comprehensive usage examples library, common scenarios guide, update all - URL: https://medium.com/@lukwagoasuman236/pros-dont-use-nmap-or-rustscan-they-use-this-2026-d9e0964ece1b - Insights: Performance comparison, use case recommendations -2. **"Top Network Scanners Compared: Nmap, Masscan, ZMap, and More"** (findsec.org) - - URL: https://findsec.org/index.php/blog/493-nmap-vs-masscan-zmap-rustscan-comparison +2. ~~**"Top Network Scanners Compared: Nmap, Masscan, ZMap, and More"** (findsec.org)~~ + - ~~URL: https://findsec.org/index.php/blog/493-nmap-vs-masscan-zmap-rustscan-comparison~~ (Link no longer accessible) - Insights: Feature matrix, speed benchmarks, tool selection guide 3. **"01/31/2025 – masscan vs nmap Scan"** (victsao.wordpress.com) @@ -1621,7 +1621,7 @@ Create comprehensive usage examples library, common scenarios guide, update all **Total Sources:** - **Code Repositories**: 3 (RustScan, Masscan, Nmap) -- **Online Articles**: 15+ (Medium, GeeksforGeeks, findsec.org, etc.) +- **Online Articles**: 15+ (Medium, GeeksforGeeks, network scanner comparisons, etc.) - **Technical Documentation**: Nmap book, RFC specifications - **Community Discussions**: Reddit, Stack Overflow, GitHub issues