A TheDarkArtist's Project
read more at https://www.thedarkartist.in/projects
RustRecon
When I first set out to build RustRecon, my goal was simple: create a Rust-powered tool that scans a given IP address on specified ports and generates a JSON report about which ports are open, what services might be running, and any known vulnerabilities related to those services. No fluff, no complicated features — just the core essentials.
Why? Because I wanted a straightforward project to sharpen my async Rust skills, understand safe concurrency, and build a clean, modular tool. I wasn’t trying to reinvent the wheel or create a Nmap killer. Instead, I aimed for a project that’s easy to use, easy to extend, and firmly grounded in Rust’s idiomatic design.
What RustRecon Does, in Plain Words
Here’s what it does:
- Reads an IP address and a list of ports from a simple
config.toml
file. - Asynchronously scans each port to see if it’s open.
- Guesses the service based on the port number (like SSH on 22).
- Associates known vulnerabilities (hardcoded CVEs) with these services.
- Generates a JSON report listing the open ports, services, and vulnerabilities.
That’s it.
RustRecon doesn’t do banner grabbing, OS fingerprinting, or brute forcing — those things can come later, if at all. For now, it’s a basic async TCP port scanner with a reporting feature.
Starting with Configuration
RustRecon reads the target IP and ports from a configuration file:
ip = "192.168.1.1"
ports = [22, 80, 443, 8080]
This lets me scan any IP or set of ports by editing just one file, without recompiling.
On the Rust side, I parse this config using Serde and TOML:
#[derive(Deserialize)]
pub struct Config {
pub ip: String,
pub ports: Vec<u16>,
}
impl Config {
pub fn from_file(file: &str) -> Self {
let content = std::fs::read_to_string(file).expect("Failed to read config file");
toml::from_str(&content).expect("Failed to parse config file")
}
}
The from_file
method is straightforward: read the file and deserialize it. If the file’s missing or corrupted, the program panics — which is fine for now, since I want clear failure feedback.
This config abstraction is handy — I don’t have to worry about CLI parsing or complex setup yet. Just change config.toml
and rerun.
Diving into Async Scanning
The heart of RustRecon is asynchronous scanning using Tokio.
Here’s a simple async function that attempts to connect to a socket address within a 1-second timeout:
pub async fn scan_port(addr: SocketAddr, tx: mpsc::Sender<u16>) {
match tokio::time::timeout(Duration::from_secs(1), TcpStream::connect(addr)).await {
Ok(Ok(_)) => {
let _ = tx.send(addr.port()).await;
}
_ => {}
}
}
If the TCP connection succeeds, it sends the open port number back through an async channel (mpsc::Sender
). If it times out or fails, it silently ignores that port.
This function is key because:
- It’s async, so I can launch many scans in parallel without blocking.
- It uses
tokio::time::timeout
to avoid hanging on closed or filtered ports. - The
mpsc
channel lets me collect results safely across tasks.
This kind of asynchronous concurrency is exactly why I picked Rust for this project — combining high performance with safety.
Launching Concurrent Scans
Inside the main function, I spawn a Tokio task for each port:
#[tokio::main]
async fn main() {
let config = Config::from_file("config.toml");
let (tx, mut rx) = mpsc::channel::<u16>(4);
for port in &config.ports {
let tx = tx.clone();
let ip_addr = format!("{}:{}", config.ip, port).parse().unwrap();
tokio::spawn(async move {
scanner::scan_port(ip_addr, tx).await;
});
}
drop(tx);
// Collect open ports and generate report here ...
}
Few notes:
- The
mpsc::channel(4)
buffer size is small but enough for this demo. - Cloning the sender (
tx.clone()
) lets each task send results independently. drop(tx)
closes the sending side once all tasks are launched, so the receiver knows when scanning ends.
Processing Scan Results
Once tasks send open ports over the channel, the main task listens for results:
while let Some(port) = rx.recv().await {
let service = match port {
22 => "SSH",
80 | 8080 => "HTTP",
443 => "HTTPS",
_ => "Unknown",
};
let vulnerabilities = match service {
"SSH" => vec!["CVE-2020-14001", "CVE-2018-15473"],
"HTTP" => vec!["CVE-2019-12345", "CVE-2017-5638"],
"HTTPS" => vec!["CVE-2020-12345"],
_ => vec![],
};
report_data.push((port, service, vulnerabilities));
}
Here I match port numbers to well-known services, then add some hardcoded CVE identifiers for demo purposes.
This approach is obviously naive — real-world scanners would do banner grabbing or fingerprinting to identify services dynamically. But for now, it’s enough to demonstrate a basic scan-to-report pipeline.
Generating the JSON Report
The final step is writing the collected data to a file, report.json
:
pub fn generate_report(report_data: Vec<(u16, &'static str, Vec<&'static str>)>) {
let report: Vec<_> = report_data.iter().map(|(port, service, vulnerabilities)| {
json!({
"port": port,
"service": service,
"vulnerabilities": vulnerabilities,
})
}).collect();
let mut file = std::fs::File::create("report.json").expect("Failed to create report file");
file.write_all(serde_json::to_string_pretty(&report).unwrap().as_bytes())
.expect("Failed to write report file");
}
Using Serde JSON here keeps it simple, readable, and easy to extend. This output could be consumed by other tools or humans to understand network exposure.
Why Rust?
Building RustRecon in Rust felt natural and efficient:
- Rust’s ownership model ensured there were no data races or memory bugs.
- Tokio’s async runtime made concurrent scanning straightforward without complex threading.
- Serde made parsing config and serializing reports painless.
- The compiler’s strictness helped me catch errors early — no guesswork.
If I had done this in a scripting language, async might have been trickier or less performant.
The Project Structure
RustRecon’s code lives in a few focused modules:
RustRecon/
├── Cargo.toml
├── config.toml
├── src/
│ ├── main.rs # Orchestrates scanning, channels, reporting
│ ├── config.rs # Config parsing logic
│ ├── scanner.rs # Port scanning async code
│ ├── report.rs # JSON report generation
This modular design keeps concerns separated and code maintainable. Each file is small and focused.
What I Learned Along the Way
RustRecon was a great exercise to understand:
- Using
tokio::spawn
for parallel async tasks - Handling inter-task communication safely with channels
- Parsing configuration files with Serde and TOML
- Structuring Rust projects idiomatically
- Writing minimal but extensible networking tools
The project remains simple, but the principles it covers are the foundation for more advanced scanners or network tools I want to build.
Future Plans for RustRecon
While RustRecon works as-is, there’s room to grow:
- Range and CIDR scanning: Support subnet scanning instead of a single IP.
- Banner grabbing: Extract service banners for better identification.
- Vulnerability database integration: Pull real-time CVE data from APIs.
- Output formats: Add CSV, XML, or even an interactive HTML report.
- GUI layer: I already included
gtk
in the dependencies. Adding a GUI frontend using GTK will make RustRecon more user-friendly, especially for quick scans without editing config files. - Plugin architecture: To add service fingerprinting or custom scan modules.
- Multi-target scanning: Scan multiple IPs or hostnames concurrently.
GTK is Ready When I Am
I included GTK and GIO dependencies early on because I’m planning a GUI:
[dependencies]
gtk = "0.18"
gio = "0.19"
glib = "0.19"
These crates give me access to a mature, cross-platform UI toolkit in Rust. Building a simple GTK frontend will let me:
- Enter IP and ports interactively
- Display scan progress live
- Show results in a table or treeview
- Export reports with a click
I’m excited to dive into GTK next. It complements the current CLI/async backend perfectly.
Closing Thoughts
RustRecon isn’t complex or flashy. It’s just a focused, well-structured Rust project that does one thing well: scanning specified ports asynchronously and reporting results clearly.
Writing it gave me practical async Rust experience, reinforced good design practices, and left me with a solid foundation for future expansions.
If you’re exploring Rust networking or async, starting with a small project like this can teach you a lot about Rust’s power and elegance.
And hey, sometimes simple tools are exactly what you need.
If you want the source or want to discuss ideas, I’m always up for it.
Let me know if you want me to expand any part or tweak tone further!
Created At
Tue, Jun 24, 2025
Last Updated at
Tue, Jun 24, 2025
Project Tags
- Rust
- Networking
Average Reading Time
6 Minutes | 1508 Words