X-SCAN:Rust从零实现一个命令行端口扫描工具

news2025/3/17 13:56:31

0. 成品预览

本文将基于Rust构建一个常见的网络工具,端口扫描器。

按照惯例,还是和之前实现的文本编辑器一样,我给这个工具起名为X-SCAN,它的功能很简单,通过命令行参数的方式对指定IP进行扫描,扫描结束之后返回该IP地址中处于开放状态的端口号,学完本文,你将自己实现一个如下效果的端口扫描工具(截图以CSDN平台的IP地址的扫描结果为例)

image-20240518085144512


1. 相关依赖

tokio = {version = "1.37.0",features = ["full"]}
bpaf = {version = "0.9.12",features = ["derive","bright-color"]}
ansi_term = "0.12.1"
prettytable-rs = "0.10.0"
  • Tokio :用于异步编程
  • bpaf :一个简化命令行实现的库
  • ansi_term:美化终端字符
  • prettytable-rs:将数据进行表格化打印

上面这些依赖都是在后续的代码中需要用到的,后面会在针对每一个依赖库进行简单的入门讲解,便于理解最终要实现的端口扫描工具。过多的还是建议去官方文档学习。


2. 基本实现原理

通过异步请求对目标IP的端口进行tcp链接扫描,一旦连接建立成功,将本次连接的端口号返回,以此类推,直到全部扫描结束,打印扫描的结果即可。

image-20240517212712200


3. 几个依赖库的快速入门

这小节会对上面列出来的几个依赖库进行简单的入门,为后续编码扫清障碍。

3.1 tokio

  • Tokio官网

tokio中,实现异步编程的两大核心

  • async
  • await

如果某个函数需要异步执行,可以通过async关键字实现,比如下面connect函数的定义

use mini_redis::Result;
use mini_redis::client::Client;
use tokio::net::ToSocketAddrs;

pub async fn connect<T: ToSocketAddrs>(addr: T) -> Result<Client> {
    // ...
}
  • 这个异步函数的定义看起来像一个普通的同步函数,但实际上是以异步方式运行的。这意味着在代码编写时,异步函数的语法和结构与同步函数类似,使得编写异步代码更加直观和易于理解。

  • Rust 编译器会对异步函数进行转换和优化,以便在运行时能够以异步的方式执行。

  • 当异步函数内部遇到 .await 关键字时,它会暂时挂起当前操作,将控制权交还给线程,从而允许线程执行其他任务。

  • 当异步操作在后台进行时,线程并不会被阻塞,而是可以继续执行其他任务,从而提高程序的效率和并发性能。

async fn say_hi() {
    println!("Tokio");
}

#[tokio::main]
async fn main() {
    let op = say_hi();

    println!("hello");

    op.await;
}
  • 使用#[tokio::main]宏将主函数标记为异步。
    • 运行时包含异步任务调度器,提供事件 I/O、计时器等。运行时不会自动启动,因此需要 main 函数启动它。
  • 对于异步函数,它的调用方式和普通的Rust函数类似,无需其他冗余操作;
  • 当异步函数被调用时,函数体不会立即执行,而是会返回一个表示操作的值,类似于返回一个尚未执行的操作描述标识;
  • 这个概念类似于返回一个零参数的闭包,闭包本身不会立即执行,而是等待进一步的操作;
  • 要执行异步函数代表的操作,这就需要用到了另外一个关键字:await,它作用在操作返回值上,用来触发异步操作;

依据上面的描述,示例代码会打印:

hello Tokio

3.2 bpaf

这是一个多功能且易用的命令行参数解析工具。通过借助这个lib可以快速高效的编写命令行程序,由于我们的端口扫描器需要手动通过命令行输入IP和端口范围等参数,因此这无疑是一个不错的选择。

  • 仓库地址

  • 基本用法

// 导入 bpaf crate 中的 Bpaf trait
use bpaf::Bpaf;

// 定义一个结构体 SpeedAndDistance,自动实现 Clone、Debug 和 Bpaf trait
#[derive(Clone, Debug, Bpaf)]
#[bpaf(options, version)] // 添加额外属性 options 和 version
struct SpeedAndDistance {
    speed: f64,     // 速度
    distance: f64,  // 距离
}

fn main() {
    // 解析命令行参数并返回选项
    let opts = speed_and_distance().run();
    
    // 打印解析得到的选项信息
    println!("Options: {:?}", opts);
}
  • 通过结构体的方式定义了两个属性,分别是速度和距离;
  • 由于我们需要将这两个字段作为命令行输入的参数,因此这里使用了#[bpaf(options,version)]

示例代码中定义了两个参数,在运行时,通过下面的命令即可指定参数值执行程序

cargo run -- --speed 20.0 --distance 100.0

image-20240518093613844

需要注意的是,这个crate有两种不同的用法,过多内容请移步文档。

下面两个crate对本项目的实质性功能不会产生影响并且使用也相对简单,这里就只做简单的介绍,具体的示例就不再写了,感兴趣的可以自己学习一下。

3.3 ansi_term

这个小工具用于美化字段字符的。虽然它的有无并不会影响我们项目的实际功能,但是通过这个工具,我们可以给自己的项目画一个有颜色的炫酷字符图案logo,这看起来是一件很酷的事情。

3.4 prettytable-rs

用于将输出构建成终端表格的形式进行打印,并且可以指定表格颜色等信息。美化和规范输出。


4. 步入正题

开始正式编码之前,先分析一下大致的实施步骤。

我们的X-SCAN大致可以分为三个小块。

  • 命令行参数的定义解析:负责解析命令行参数
  • 端口扫描的函数:负责完成扫描的核心任务
  • Rust主函数:调用扫描函数并将结果组织返回

基于此,这里将按照这个步骤依次展开讲解;

4.1 参数定义

我们的X-SCAN一共需要三个参数,分别是:

  1. IP地址:Address
  2. 起始端口号:start_port
  3. 结束端口号:end_port
// 命令行参数定义
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
pub struct Argument {
    #[bpaf(long, short, argument("Address"), fallback(IPFALLBACK))]
    /// 想要嗅探的地址,必须是有效的IPv4地址。将回退到127.0.0.1
    pub address: IpAddr,
    #[bpaf(
        long("start"),
        short('s'),
        guard(start_port_guard, "必须大于0"),
        fallback(1u16)
    )]
    pub start_port: u16,

    #[bpaf(
        long("end"),
        short('e'),
        guard(end_port_guard, "必须小于或等于65535"),
        fallback(MAX)
    )]
    pub end_port: u16,
}
  • 这里主要用到了bpaf,这个上面讲过了,但是这里有一些东西需要提一下;
  • 这里用到了guard作为字段的条件约束,指明该参数应该满足的规则,它需要指定一个校验函数;
  • 引入了longshort两个属性,用来指定参数的长格式和短格式两种风格;
  • fallback用来指定参数默认值,在用户没有显式指定参数时,它的值将用作默认值;

上面的代码中大概也注意到了,在指定IP地址参数时,我们用到了两个默认值的常量,下面是他们的定义:

const IPFALLBACK: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
const MAX: u16 = 65535;
  • 常量( MAXIPFALLBACK ):这些是用作默认值的预定义值。 MAX 设置结束端口的最大值,确保它不超过允许的最大端口号 (65535)。
  • IPFALLBACK 提供默认 IP 地址(127.0.0.1,这是本地主机),以防用户未指定 IP 地址。

4.2 扫描函数

这个scan 函数是一个异步函数,旨在检查给定 IP 地址上的特定端口是否打开。

/// 异步函数:扫描指定地址和端口
async fn scan(tx: Sender<u16>, start_port: u16, addr: IpAddr) {
    match TcpStream::connect(format!("{}:{}", addr, start_port)).await {
        Ok(_) => {
            print!(".");
            io::stdout().flush().unwrap();
            tx.send(start_port).unwrap();
        }
        Err(_) => {}
    }
}
  • 函数签名: async fn scan(tx: Sender<u16>, start_port: u16, addr: IpAddr) :这定义了一个名为 scan 的异步函数,它采用三个参数:
    • txSender<u16> 类型,用于将数据(在本例中为端口号)发送到程序的另一部分。
    • start_port :要检查的端口号;
    • addr :要检查端口的 IP 地址。
  • TcpStream::connect(format!("{}:{}", addr, start_port)).await :此行尝试建立到指定 addrstart_port 的 TCP 连接。使用 await 关键字是因为 TcpStream::connect 是一个异步操作,您需要等待它完成才能继续,这一点之前也说过了;
  • 使用match表达式来处理返回的不同结果,具体如下:
    • Ok(_):连接成功,捕获一个开放端口;
      • print!(".")这里用来在扫描过程中打印...,作为正在扫描的视觉提示;
      • io::stdout().flush().unwrap():通过刷新标准输出缓冲区确保点立即显示在屏幕上,达到实时加载的视觉效果;
      • tx.send(start_port).unwrap():通过 tx 通道返回开放端口号,以由程序的其他部分处理或记录。
    • Err(_):连接失败,表示本次连接的端口为关闭状态,不做任何操作;
  • 利用异步编程有效地处理可能长时间运行的网络操作,而不会阻止程序其他部分的执行。允许同时扫描多个端口,从而加快扫描过程。

4.3 结果处理

main 函数设置异步环境、收集参数并生成用于扫描指定范围内的每个端口的任务。通过表格整理返回结果并打印 ;

#[tokio::main]
async fn main() {
    print_infos();
    let opts = argument().run();

    let (tx, rx) = channel();

    for i in opts.start_port..opts.end_port {
        let tx = tx.clone();
        task::spawn(async move { scan(tx, i, opts.address).await });
    }
    let mut open_ports = vec![];

    drop(tx);

    for p in rx {
        open_ports.push(p);
    }

    println!("");
    open_ports.sort();

    let mut table = Table::new();
    table.add_row(Row::new(vec![
        Cell::new("Port").style_spec("Fg=blue"),
        Cell::new("Status").style_spec("Fg=blue"),
    ]));

    for port in open_ports {
        table.add_row(Row::new(vec![
            Cell::new(&port.to_string()),
            Cell::new("is open"),
        ]));
    }

    table.printstd();
}
  • #[tokio::main] :该属性宏将常规 main 函数转换为异步主函数。它设置 Tokio 运行时,这是运行异步代码所必需的。
  • let opts = arguments().run(); :此行调用 arguments() 函数。该函数构造并解析命令行参数,返回 Arguments 结构体存储在 opts 中。
  • let (tx, rx) = channel(); :这里创建了生产者、单消费者 通道。 tx 是发送者, rx 是接收者。该通道用于异步任务之间的通信。
  • 接着就是端口扫描的一个循环处理:
    • 第10行 :为每个端口生成一个新的异步任务。使用当前端口号 i 、克隆的发送者 tx 和目标 IP 地址 opts.address 调用 scan 函数。每个任务将尝试连接到其分配的端口并通过通道将结果发送回。
  • drop(tx); :显式删除原始发件人。这很重要,因为它标识将不再在此通道上发送消息,从而允许接收者在处理所有发送的消息后退出循环。
  • 对于结果的处理,这里创建了一个vec数组,此循环从通道接收消息。每条消息代表一个开放端口号并将其存入vec之中;
  • 对于23-27行,使用prettytable-rs提供的方法构建表格的表头,包括端口Port和开放状态Status;
  • 29-36行则是将结果添加到表格中并打印在终端。

4.4 打印版本信息

对于图案信息,大家可以去这个网站生成之后复制过来.https://patorjk.com/software/taag/

我们新增一个函数,用来在重新启动时打印X-SCAN的字符LOGO和版本等信息:

fn print_infos() {
    println!(
        "{}",
        Red.paint(
            r#"
         __   __            _____    _____              _   _ 
         \ \ / /           / ____|  / ____|     /\     | \ | |
          \ V /   ______  | (___   | |         /  \    |  \| |
           > <   |______|  \___ \  | |        / /\ \   | . ` |
          / . \            ____) | | |____   / ____ \  | |\  |
         /_/ \_\          |_____/   \_____| /_/    \_\ |_| \_|
                                                              
        author:代号0408
        version:0.1.0                                                      
        "#
        )
    );
}

别忘了在main函数中调用;


5. 使用方式

cargo run -- --address 8.137.10.104 --start 1 --end 8888
  • address参数:指定要扫描的IP地址
  • start 参数:指定起始端口
  • end参数:指定结束端口

当然,你也可以对参数使用短格式来执行程序:

cargo run -- --address 49.232.219.30 -s 1 -e 10000

注意,使用参数的短格式形式时参数前面的短横线也需要调整为一条短横线(-),长格式参数使用两条(--);

假设我们不指定IP地址,那么它将会默认扫描本地127.0.0.1;

cargo run --   -s 1 -e 10000

  • 项目地址:https://github.com/08820048/X-SCAN

免责声明:X-SCAN工具仅供合法授权的网络安全测试和评估使用,作者不对任何非法或未经授权的使用行为承担责任。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1694722.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL--数据库--基础知识

目录 1、 数据库作用 2、sql认识 1、DDL 整数类型 浮点 主键 约束: 2、DML 插入数据 修改数据 删除数据 3、DQL-基础查询 字符函数&#xff1a; 逻辑处理&#xff1a; 数学函数&#xff1a; 日期函数&#xff1a; 分组函数&#xff1a; 条件查询: 模糊查询 LIK…

Pycharm在下载安装第三方库时速度慢或超时问题 / 切换国内镜像地址

pycharm下载第三方库速度极慢&#xff0c;搜索了一下&#xff0c;发现方法非常乱&#xff0c;稍作整理。这个问题一般都会出现&#xff0c;在我们开发中遇到的常见问题&#xff0c;根据以下解决方法&#xff0c;基本可以解决&#xff0c;但是不能100%保证 Installing packages …

算法金 | Dask,一个超强的 python 库

本文来源公众号“算法金”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;Dask&#xff0c;一个超强的 python 库 1 Dask 概览 在数据科学和大数据处理的领域&#xff0c;高效处理海量数据一直是一项挑战。 为了应对这一挑战&am…

基于open3d加载kitti数据集bin文件

前言 在自动驾驶领域&#xff0c;Kitti数据集是一个非常流行的点云数据集&#xff0c;广泛用于3D目标检测、跟踪和其他相关研究。Open3D是一个强大的开源库&#xff0c;专门用于处理和可视化三维数据。本文将介绍如何使用Open3D来加载和可视化Kitti数据集中的.bin文件。 准备…

【Qt 学习笔记】Qt窗口 | Qt窗口介绍 | QMainwindow类及各组件介绍

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Qt窗口 | Qt窗口介绍 | QMainwindow类及各组件介绍 文章编号&#xff…

第14章 数据分析案例——2012联邦选举委员会数据库

美国联邦选举委员会发布了有关政治竞选赞助方面的数据。其中包括赞助者的姓名、职业、雇主、地址以及出资额等信息。我们对2012年美国总统大选的数据集比较感兴趣。&#xff08;http://www.fec.gov/disclosurep/PDownload.do&#xff09;。我在2012年6月下载的数据集是一个150M…

【JavaEE进阶】——一万字带你深刻理解Spring IoCDI

目录 &#x1f6a9;Spring是什么 &#x1f388;什么是容器&#xff1f; &#x1f388;什么是 IoC&#xff1f; &#x1f4dd;传统开发思路 &#x1f4dd;IOC思想 &#x1f4dd;IoC 优势 &#x1f388;DI 介绍 &#x1f6a9;IoC 详解 &#x1f388;Bean的存储 &#x…

Python 脚本化 Git 操作:简单、高效、无压力

前言 如何判定此次测试是否达标&#xff0c;代码覆盖率是衡量的标准之一。前段时间&#xff0c;利用fastapi框架重写了覆盖率统计服务&#xff0c;核心其实就是先获取全量代码覆盖率&#xff0c;然后通过diff操作统计增量代码覆盖率&#xff0c;当然要使用diff操作&#xff0c…

力扣HOT100 - 21. 合并两个有序链表

解题思路&#xff1a; class Solution {public ListNode mergeTwoLists(ListNode list1, ListNode list2) {ListNode dum new ListNode(0), cur dum;while (list1 ! null && list2 ! null) {if (list1.val < list2.val) {cur.next list1;list1 list1.next;} els…

嵌入式之音频基础知识

声音特性 1、响度&#xff1a;人主观上感觉声音的大小&#xff08;俗称音量&#xff09;&#xff0c;由“振幅”和人离声源的距离决定&#xff0c;振幅越大响度越大&#xff0c;人和声源的距离越小&#xff0c;响度越大&#xff1b; 2、音调&#xff1a;声音的高低&#xff0…

Jenkins安装 :Aws EC2下Docker镜像安装

1 安装docker # 安装docker $ sudo yum install -y docker# 启动docker daemon $ sudo systemctl start docker# 用户加入docker组 $ sudo usermod -aG docker username 2 docker安装jenkins $ docker pull jenkins/jenkins:lts# 安装成功 $ docker images REPOSITORY …

vue 表单些某项 v-if 控制后,想在显示时添加验证

效果: 可以为<el-form-item>添加 key 然后prop正常写就行 (key需要唯一值) <el-form-item label"设置" v-if"advanced_setting" key"threshold" prop"threshold"><el-inputv-model"form_Warning.threshold"p…

Python数字比大小获取大的数

目录 一、引言 二、数字比较的基本语法 三、获取较大的数 使用条件语句 使用内置函数 四、处理特殊情况 比较非数字类型 处理无穷大和NaN 五、应用实例 在游戏开发中比较分数 在数据分析中找出最大值 六、优化与性能 七、总结 一、引言 在Python编程的广阔天地中…

通过RAG架构LLM应用程序

在之前的博客文章中&#xff0c;我们已经描述了嵌入是如何工作的&#xff0c;以及RAG技术是什么。本节我们我们将使用 LangChain 库以及 RAG 和嵌入技术在 Python 中构建一个简单的 LLM 应用程序。 我们将使用 LangChain 库在 Python 中构建一个简单的 LLM 应用程序。LangChai…

Python高效数据分析的综合复习指南【时间处理与机器学习】

五、时间处理 一、时间戳-----Timestamp类型 方法1&#xff1a;使用Timestamp创建 pandas.Timestamp(ts_input, freqNone, tzNone, unitNone, yearNone, monthNone, dayNone, hourNone, minuteNone, secondNone, microsecondNone, tzinfoNone, offsetNone) import pandas a…

ICML 2024 时空数据(Spatial-Temporal)论文总结

2024ICML&#xff08;International Conference on Machine Learning&#xff0c;国际机器学习会议&#xff09;在2024年7月21日-27日在奥地利维也纳举行 &#xff08;好像ICLR24现在正在维也纳开&#xff09;。 本文总结了ICML 24有关时空数据(Spatial-temporal) 的相关论文…

机器学习预测-CNN数据预测示例

介绍 这段代码是一个基于 TensorFlow 和 Keras 的深度学习模型&#xff0c;用于进行数据的回归任务。让我逐步解释一下&#xff1a; 导入必要的库&#xff1a;这里导入了 NumPy 用于数值计算&#xff0c;Pandas 用于数据处理&#xff0c;Matplotlib 用于绘图&#xff0c;Tenso…

Docker学习(3):镜像使用

当运行容器时&#xff0c;使用的镜像如果在本地中不存在&#xff0c;docker 就会自动从 docker 镜像仓库中下载&#xff0c;默认是从 Docker Hub 公共镜像源下载。 一、列出镜像列表 可以使用 docker images 来列出本地主机上的镜像。 各个选项说明&#xff1a; REPOSITORY&am…

AI大模型:大数据+大算力+强算法

前言&#xff1a;好久不见&#xff0c;甚是想念&#xff0c;我是辣条&#xff0c;我又回来啦&#xff0c;兄弟们&#xff0c;一别两年&#xff0c;还有多少老哥们在呢&#xff1f; 目录 一年半没更文我干啥去了&#xff1f; AI大模型火了 人工智能 大模型的理解 为什么学习…

H5扫描二维码相关实现

H5 Web网页实现扫一扫识别解析二维码&#xff0c;就现在方法的npm包就能实现&#xff0c;在这个过程中使用过html5-qrcode 和 vue3-qr-reader。 1、html5-qrcode的使用 感觉html5-qrcode有点小坑&#xff0c;在使用的时候识别不成功还总是进入到错误回调中出现类似NotFoundExc…