开发我们的第一个基于Rust的应用:端口扫描器

缘起

  • 最近翻看Github,发现一个头部高Star(1.5万)的项目RustScan,仔细分析业务用途,也就是个端口扫描器。
  • 这么朴实的端口扫描器还能刷这么高的star?项目代码写得这么多,超级复杂,前端后端数据像意大利面一样混搭一起,像是故意让人看不懂的样子。
  • 作为一个魂力只有7级的资深菜鸟,我不信这个邪,铁着头就搞了个端口扫描器最常用的基础功能。
  • 最重要的是,要让人能看得懂,能有收获。

开源&与效果

教程更新:实现一个 Rust 端口扫描工具

我们将编写一个简单的 Rust 端口扫描工具,该工具使用异步编程进行并行端口扫描,扫描指定网段的代理端口,并输出开放端口的相关信息。我们将继续使用之前创建的 Rust 项目,并添加新的模块和功能。


步骤 1:准备工作

在开始之前,确保你已经安装了以下工具和库:

  1. 安装 Tokio:这是一个用于异步编程的 Rust 库。
  2. 安装 Futures:处理并发任务的库。
  3. 修改 Cargo.toml 文件:为项目添加所需的依赖项。

Cargo.toml 文件中,添加以下依赖项:

toml 复制代码
[dependencies]
tokio = { version = "1", features = ["full"] }
futures = "0.3"

步骤 2:创建端口扫描工具的结构

src 文件夹中,创建两个模块 scanner.rsproxy_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:编译和运行

现在,你可以运行端口扫描工具了:

  1. 编译和运行:

    arduino 复制代码
    cargo run -- -ips 192.168.1.0/24

    这将扫描 192.168.1.0/24 网段的代理端口。

  2. 显示帮助信息:

    arduino 复制代码
    cargo run -- -h

相关推荐
余衫马6 分钟前
基于 JNI + Rust 实现一种高性能 Excel 导出方案(上篇)
开发语言·rust·excel
小哈里12 分钟前
【后端开发】Go语言编程实践,Goroutines和Channels,基于共享变量的并发,反射与底层编程
开发语言·后端·golang·编程·并发
hxj..1 小时前
【网络安全】CSRF
安全·web安全·csrf
liuxin334455661 小时前
领养我的宠物:SpringBoot开发指南
spring boot·后端·宠物
2401_857636391 小时前
宠物领养平台建设:SpringBoot案例分析
spring boot·后端·宠物
布朗克1681 小时前
JWT介绍和结合springboot项目实践(登录、注销授权认证管理)
java·spring boot·后端·安全·jwt
Qspace丨轻空间1 小时前
气膜建筑:打造全天候安全作业空间,提升工程建设效率—轻空间
大数据·科技·安全·生活·娱乐
城沐小巷2 小时前
外卖点餐系统小程序
前端·后端·微信小程序