记一次端口扫描工具实现

  1. 端口扫描工具
    1. 实现方案
    2. 实现代码
    3. 遇到的问题
      1. Java Fiber
      2. Rust

端口扫描工具

工具如其名,主要功能是扫描服务器端口是否开放。常用的方式是扫描服务器(S)在某一段端口(P)范围内,有哪些端口正在被监听。

如:扫描服务器 127.0.0.1,端口 [80, 2000)段内,查看被监听的端口。

实现方案

  • 和对端服务器端口尝试建立 socket 链接,如果链接成功建立,则表明该端口正在被监听。
  • 使用多线程方案,加快端口扫描速度
  • 使用协程方案,降低资源使用,加快扫描速度

以上就是大致的是实现步骤。本次实现了

  • Java 单线程扫描
  • Java 多线程扫描
  • Java 协程扫描
  • Rust 协程扫描

没错,>..< 实际上这是一次学习 Java Quasar Fiber 和 Rust 过程中的小练习。

实现代码

实现已放在 github 仓库,感兴趣的可以下载自行运行。

遇到的问题

Java Fiber 和 Rust 的实现过程中都遇到了关于协程使用上的问题,在此记录一下。

Java Fiber

首先需要在函数上标明 @Suspendable,协程内的阻塞函数需要标记此注解才能让协程正常服务。

其次协程中使用的阻塞函数,需要有自己的封装实现。线程方式中使用 java.net.Socket#connect 函数是调用的 JDK 原生实现 java.net.PlainSocketImpl#socketConnect
这个实现是阻塞的,此阻塞的实现,会影响阻塞住协程,使得协程的性能无法发挥。
Quasar 为此单独提供了 FiberSocketChannel 实现,此实现调用了 co.paralleluniverse.fibers.io.AsyncFiberSocketChannel#connect
底层是异步 IO 实现,使用 Fiber.park 接替了原有的阻塞实现。

使用过程中可以感受到,使用 Fiber 接替现有的代码,还是有需要改造的部分,无法零成本接入。(此处如果我有使用不合理的地方,请邮件我改进,谢谢

Rust

Rust 版本实现中,使用了三方依赖 may 作为 coroutine 的实现依赖。出现了同 Java 版本类似的问题。
原生的 std::net::TcpStream 依然会阻塞住协程,还是需要使用 may 提供的异步 IO 实现 may::net::TcpStream 才能匹配协程提升性能。

另外就是 Rust 的 TcpStream::connect_timeout 使用方式不能按照官方提供的文档使用。

use std::net::TcpStream;

if let Ok(stream) = TcpStream::connect("127.0.0.1:8080") {
    println!("Connected to the server!");
} else {
    println!("Couldn't connect to server...");
}

按照此方法使用 TcpStream::connect_timeout,会导致 if 判断提前进入 else 逻辑,造成逻辑异常。
必须将链接建立的语句提前,才能逻辑正常。

// 正确实现
let stream = TcpStream::connect_timeout(&addrs[0], timeout);
if stream.is_ok() {
    stream.unwrap().shutdown(Shutdown::Both).expect("shutdown tcp stream fail");
    return true;
} else {
    return false;
}

诡异的问题

实现 Rust 版本的测试结果中,出现了诡异的问题。问题描述如下:

使用 rdial --start-port 80 --end-port 9000 --hostname 127.0.0.1 --timeout 200 执行时,
结果返回了 [1080, 4198] 两个接口,实际上 6394 接口也是在被监听,但没有被扫描出来。
修改使用 rdial --start-port 6000 --end-port 9000 --hostname 127.0.0.1 --timeout 200 执行时,
此时结果返回了 6394 接口。

经过 debug 确认了所有端口都有的的确确被扫描到,并没有漏掉 6394 端口。

继续尝试增加 timeout 超时时间,发现还是无法扫描到 6394 端口,但是有个特殊现象——6394 端口在建立链接语句执行后,立马返回了“close”日志,并没有经过设置好的等待时间。

最后发现问题是在操作系统限制的 fd 上,使用 ulimit -n 发现此时设置为 4864,可以看到我们的扫描程序第一次执行的确扫描到了 4198 这个接口监听,而忽略了 6394 这个接口。

答案呼之欲出,执行 ulimit -n 10000 将 fd 限制提升到 10000 之后再次执行,此时返回结果 [1080, 4198, 6394],问题解决。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 nickchenyx@gmail.com

Title:记一次端口扫描工具实现

Count:1k

Author:nickChen

Created At:2020-11-17, 14:33:02

Updated At:2023-05-08, 23:27:10

Url:http://nickchenyx.github.io/2020/11/17/dial/

Copyright: 'Attribution-non-commercial-shared in the same way 4.0' Reprint please keep the original link and author.