缘起
- 最近翻看Github,发现一个头部高Star(1.5万)的项目RustScan,仔细分析业务用途,也就是个端口扫描器。
- 这么朴实的端口扫描器还能刷这么高的star?项目代码写得这么多,超级复杂,前端后端数据像意大利面一样混搭一起,像是故意让人看不懂的样子。
- 作为一个魂力只有7级的资深菜鸟,我不信这个邪,铁着头就搞了个端口扫描器最常用的基础功能。
- 最重要的是,要让人能看得懂,能有收获。
开源&与效果
教程更新:实现一个 Rust 端口扫描工具
我们将编写一个简单的 Rust 端口扫描工具,该工具使用异步编程进行并行端口扫描,扫描指定网段的代理端口,并输出开放端口的相关信息。我们将继续使用之前创建的 Rust 项目,并添加新的模块和功能。
步骤 1:准备工作
在开始之前,确保你已经安装了以下工具和库:
- 安装 Tokio:这是一个用于异步编程的 Rust 库。
- 安装 Futures:处理并发任务的库。
- 修改
Cargo.toml
文件:为项目添加所需的依赖项。
在 Cargo.toml
文件中,添加以下依赖项:
toml
[dependencies]
tokio = { version = "1", features = ["full"] }
futures = "0.3"
步骤 2:创建端口扫描工具的结构
在 src
文件夹中,创建两个模块 scanner.rs
和 proxy_validator.rs
。这些模块将分别负责执行端口扫描和验证代理协议。
2.1 scanner.rs
模块
在 scanner.rs
中,我们将定义两个主要功能:生成 IP 地址范围和扫描指定端口。
rust
// scanner.rs
use std::net::{IpAddr, Ipv4Addr};
use tokio::net::TcpStream;
use std::time::Duration;
use tokio::time::timeout;
pub fn generate_ip_range(base_ip: Ipv4Addr, subnet_mask: u8) -> Vec<Ipv4Addr> {
let mut ip_range = Vec::new();
let base_ip_octets = base_ip.octets();
let range_size = 2u32.pow(32 - subnet_mask as u32);
for i in 1..range_size {
let ip = Ipv4Addr::new(
base_ip_octets[0],
base_ip_octets[1],
base_ip_octets[2],
base_ip_octets[3] + (i % 256) as u8,
);
ip_range.push(ip);
}
ip_range
}
pub async fn scan_port(ip: IpAddr, port: u16) -> PortScanResult {
let address = format!("{}:{}", ip, port);
let result = timeout(Duration::from_secs(1), TcpStream::connect(&address)).await;
match result {
Ok(Ok(_stream)) => PortScanResult {
ip,
port,
is_open: true,
protocol: None,
},
_ => PortScanResult {
ip,
port,
is_open: false,
protocol: None,
},
}
}
pub struct PortScanResult {
pub ip: IpAddr,
pub port: u16,
pub is_open: bool,
pub protocol: Option<String>,
}
generate_ip_range
函数根据给定的基地址和子网掩码生成 IP 地址范围。scan_port
函数尝试连接指定的 IP 和端口,超时 1 秒,判断端口是否开放。
2.2 proxy_validator.rs
模块
在 proxy_validator.rs
中,我们将实现一个简单的代理验证函数,用于检查端口开放后是否存在有效的代理。
rust
// proxy_validator.rs
use reqwest::Client;
pub async fn validate_proxy(proxy_address: &str) -> Result<String, reqwest::Error> {
let client = Client::new();
let res = client
.get(format!("http://{}/", proxy_address))
.timeout(std::time::Duration::from_secs(3))
.send()
.await;
match res {
Ok(response) => Ok(response.status().to_string()),
Err(_) => Ok("Unknown".to_string()),
}
}
validate_proxy
函数通过 HTTP 请求检查端口是否有效,并返回相应的协议类型。
步骤 3:实现主程序
在 src/main.rs
中,我们将组合之前创建的模块并实现主程序逻辑。以下是完整的 main.rs
示例:
rust
// main.rs
mod scanner;
mod proxy_validator;
use scanner::{scan_port, generate_ip_range};
use proxy_validator::validate_proxy;
use std::net::{IpAddr, Ipv4Addr};
use std::time::Instant;
use futures::future::join_all;
use std::env;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let start_time = Instant::now();
// 获取命令行参数
let args: Vec<String> = env::args().collect();
if args.contains(&"-h".to_string()) {
println!("使用方式: \n -ips <网段> 指定要扫描的网段 (例如: 192.168.1.0/24)\n -h 显示帮助信息");
return Ok(());
}
let input = if let Some(idx) = args.iter().position(|x| x == "-ips") {
args.get(idx + 1).map(|s| s.as_str()).unwrap_or("192.168.1.0/24")
} else {
"192.168.1.0/24"
};
let (base_ip_str, mask_str) = input.split_once('/').unwrap_or(("192.168.1.0", "24"));
let base_ip: Ipv4Addr = base_ip_str.parse()?;
let subnet_mask: u8 = mask_str.parse()?;
let ip_range = generate_ip_range(base_ip, subnet_mask);
let ports_to_scan = vec![3128, 8080, 8888, 1080, 8000, 8001, 9050, 8081, 8118, 3129, 5000, 8119, 8110, 3124, 9999, 8443, 8088, 1081];
println!("开始扫描 {} 的代理端口...", input);
let mut tasks = Vec::new();
for ip in &ip_range {
for &port in &ports_to_scan {
tasks.push(scan_port(IpAddr::V4(*ip), port));
}
}
let results = join_all(tasks).await;
println!("扫描结果:");
println!("------------------------");
let mut open_ports = 0;
for result in results {
if result.is_open {
open_ports += 1;
let protocol = if let Some(protocol) = &result.protocol {
protocol.clone()
} else {
validate_proxy(&format!("{}:{}", result.ip, result.port)).await.unwrap_or("Unknown".to_string())
};
println!("IP {}:{:5} - 开放 - 协议: {}", result.ip, result.port, protocol);
}
}
let duration = start_time.elapsed();
println!("扫描统计:");
println!("------------------------");
println!("扫描总IP数: {}", ip_range.len());
println!("扫描端口数: {}", ports_to_scan.len());
println!("发现开放端口: {}", open_ports);
println!("扫描总用时: {:.2}秒", duration.as_secs_f64());
println!("平均每个IP用时: {:.2}毫秒",
(duration.as_millis() as f64) / (ip_range.len() as f64)
);
Ok(())
}
步骤 4:编译和运行
现在,你可以运行端口扫描工具了:
-
编译和运行:
arduinocargo run -- -ips 192.168.1.0/24
这将扫描
192.168.1.0/24
网段的代理端口。 -
显示帮助信息:
arduinocargo run -- -h