🔥 Пишем высокоскоростной port scanner на Rust с async Tokio
Забудь про nmap, когда надо быстро. Rust + Tokio = сканим 65k портов за секунды, а не минуты.
💡 Зачем это вообще нужно?
Представь: тебе надо просканить пачку хостов на баг-баунти, а nmap тормозит как Win98 на Pentium II. Админы уже залатали дыру, пока ты ждал результатов. Rust с async I/O через Tokio делает конкурентные TCP-коннекты параллельно — без GIL, без тормозов, чисто турбо-режим.
Плюшки:
• Скорость: 3 секунды на все 65k портов (если инет не плачет)
• Async всё: Tokio runtime крутит тысячи задач одновременно
• Лёгкий вес: бинарник меньше 5 МБ, никаких зависимостей на проде
• Кастомизация: пиши свой payload, интегрируй с Nmap, шли алерты в Telegram
🛠️ Что понадобится
Установи Rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Создай проект:
cargo new turbo_port_scanner
cd turbo_port_scanner
Добавь зависимости в `Cargo.toml`:
[package]
name = "turbo_port_scanner"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1", features = ["full"] }
futures = "0.3"
clap = { version = "4", features = ["derive"] }
Разбираем:
• `tokio` с фичей `full` — весь асинхронный рантайм
• `futures` — для работы с `FuturesUnordered` (пачка задач)
• `clap` — парсинг аргументов CLI (чтоб не писать руками)
💻 Полный код сканера
Основной файл `src/main.rs`
use clap::Parser;
use futures::stream::{FuturesUnordered, StreamExt};
use std::net::{IpAddr, SocketAddr};
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::time::timeout;
/// Turbo Port Scanner — качает все порты за секунды 💣
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
/// IP-адрес или хост для сканирования
#[clap(value_parser)]
target: String,
/// Начальный порт (default: 1)
#[clap(short = 's', long, default_value_t = 1)]
start_port: u16,
/// Конечный порт (default: 65535)
#[clap(short = 'e', long, default_value_t = 65535)]
end_port: u16,
/// Таймаут на подключение в миллисекундах (default: 200)
#[clap(short = 't', long, default_value_t = 200)]
timeout_ms: u64,
/// Лимит одновременных подключений (default: 5000)
#[clap(short = 'c', long, default_value_t = 5000)]
concurrency: usize,
}
/// Сканирует один порт — возвращает Some(port), если открыт
async fn scan_port(ip: IpAddr, port: u16, timeout_duration: Duration) -> Option<u16> {
let socket_addr = SocketAddr::new(ip, port);
// Устанавливаем TCP-коннект с таймаутом
match timeout(timeout_duration, TcpStream::connect(socket_addr)).await {
Ok(Ok(_)) => Some(port), // Порт открыт — красота!
Ok(Err(_)) | Err(_) => None, // Таймаут или отказ — мимо
}
}
#[tokio::main]
async fn main() {
let args = Args::parse();
// Парсим target в IP (можно добавить DNS-резолв, но пока сойдёт)
let ip: IpAddr = args.target.parse().expect("❌ Неверный IP-адрес!");
println!("🔍 Сканируем {} | Порты: {}-{}", ip, args.start_port, args.end_port);
println!("⏱️ Таймаут: {}ms | Конкурентность: {}", args.timeout_ms, args.concurrency)
; println!("💣 Поехали!\n");
let timeout_duration = Duration::from_millis(args.timeout_ms);
let mut futures = FuturesUnordered::new();
let mut open_ports = Vec::new();
// Создаём пачку асинхронных задач для каждого порта
for port in args.start_port..=args.end_port {
futures.push(scan_port(ip, port, timeout_duration));
// Ограничиваем одновременные задачи (чтоб сеть не умерла)
if futures.len() >= args.concurrency {
if let Some(Some(open_port)) = futures.next().await {
println!("✅ Порт {} открыт", open_port);
open_ports.push(open_port);
}
}
}
// Собираем оставшиеся результаты
while let Some(result) = futures.next().await {
if let Some(open_port) = result {
println!("✅ Порт {} открыт", open_port);
open_ports.push(open_port);
}
} println!("\n🎯 Найдено открытых портов: {}", open_ports.len()
); println!("📋 Список: {:?}", open_ports);
}
🚀 Как это работает (разбор по косточкам)
- Парсинг аргументов через `clap`
`clap` автоматически генерит CLI с флагами `-s`, `-e`, `-t`, `-c` . Админу не надо читать доку — просто `--help` и вперёд.
- Async runtime с Tokio
`#tokio::main` запускает многопоточный runtime с event loop . Каждое `await` — это точка переключения задач, пока TCP ждёт ответа, другие задачи работают .
- FuturesUnordered — пачка задач
`FuturesUnordered` складывает все задачи в кучу и выполняет их конкурентно . Как только одна завершается — забираем результат через `.next()`.
- Ограничение конкурентности
Если пустить все 65k портов одновременно — сеть умрёт, файловые дескрипторы кончатся, админ заметит флуд . Лимитим через `concurrency` (по дефолту 5000 — sweet spot).
- Таймаут на коннект
`tokio::time::timeout` убивает задачу, если порт не отвечает . 200ms — оптимально для локалки, для удалёнки можно поднять до 500-1000ms.
🔧 Компиляция и запуск
Собираем релиз (с оптимизацией):
cargo build --release
Бинарник появится в `target/release/turbo_port_scanner` .
Запускаем:
# Сканим localhost, все порты, таймаут 100ms
./target/release/turbo_port_scanner 127.0.0.1 -s 1 -e 65535 -t 100 -c 10000
# Сканим удалённый хост, только популярные порты
./target/release/turbo_port_scanner 192.168.1.1 -s 1 -e 1024 -t 500
Пример вывода:
🔍 Сканируем 192.168.1.1 | Порты: 1-1024
⏱️ Таймаут: 500ms | Конкурентность: 5000
💣 Поехали!
✅ Порт 22 открыт
✅ Порт 80 открыт
✅ Порт 443 открыт
🎯 Найдено открытых портов: 3
📋 Список: [22, 80, 443]
💣 Эксплуатация и лайфхаки
Интеграция с Nmap
Найденные порты можно сразу скормить nmap’у для детального скана:
./turbo_port_scanner 192.168.1.1 -s 1 -e 65535 | grep "открыт" | awk '{print $2}' | tr '\n' ',' | xargs -I {} nmap -p {} -sV 192.168.1.1Результат: сканер быстро нашёл порты, nmap их подробно обследовал — профит !
DNS-резолв
Добавь резолв домена через `tokio::net::lookup_host`:
use tokio::net::lookup_host;
let addrs = lookup_host(format!("{}:80", args.target)).await.expect("DNS fail");
let ip = addrs.into_iter().next().expect("No IP found").ip();
UDP-скан
TCP — это легко. UDP — сложнее, но реально:
use tokio::net::UdpSocket;
async fn scan_udp_port(ip: IpAddr, port: u16) -> Option<u16> {
let socket = UdpSocket::bind("0.0.0.0:0").await.ok()?;
socket.connect(SocketAddr::new(ip, port)).await.ok()?;
socket.send(&[0x00]).await.ok()?;
let mut buf = [0u8; 1024];
match timeout(Duration::from_millis(500), socket.recv(&mut buf)).await {
Ok(Ok(_)) => Some(port), // Получили ответ — порт открыт
_ => None,
}
}
Сохранение результатов в JSON
Добавь `serde` и `serde_json`:
use serde::Serialize;
use std::fs::File;
#[derive(Serialize)]
struct ScanResult {
target: String,
open_ports: Vec<u16>,
}
// После сканирования
let result = ScanResult { target: args.target, open_ports };
let file = File::create("scan_result.json").unwrap();
serde_json::to_writer_pretty(file, &result).unwrap();
Обход rate-limit’ов
Если админ настроил iptables с лимитом подключений:
• Уменьши `concurrency` до 500-1000
• Увеличь `timeout` до 1-2 секунд
• Добавь рандомные задержки между батчами
Stealth-режим
Хочешь тихо? Лимитируй скорость:
use tokio::time::sleep;
for port in args.start_port..=args.end_port {
futures.push(scan_port(ip, port, timeout_duration));
sleep(Duration::from_millis(10)).await; // 10ms задержка между портами
}
Вывод: Для быстрой разведки — Rust. Для детального анализа — потом подключай nmap .
🔍 Что ещё можно запилить?
• Banner grabbing: После коннекта считывай первые байты — SSH баннеры, HTTP Server headers
• IPv6 поддержка: Уже работает, просто передавай IPv6-адрес
• CIDR-нотация: Сканируй целые подсети `192.168.1.0/24`
• Output форматы: CSV, XML, Markdown — для отчётов в баг-баунти
• Rate-adaptive learning: Автоматически подстраивай `timeout` и `concurrency` под сеть
🐛 Дебаг и оптимизация
Проблема: “Too many open files”
Решение: Увеличь лимит дескрипторов:
ulimit -n 10000
Проблема: Медленно на удалённых хостах
Решение: Подними `timeout` до 1000ms, уменьши `concurrency` до 1000 .
Проблема: Много false positives
Решение: Добавь проверку — отправь данные и жди ответ:
let mut stream = TcpStream::connect(socket_addr).await?;
stream.write_all(b"HEAD / HTTP/1.0\r\n\r\n").await?;
let mut buf = [0u8; 1024];
stream.read(&mut buf).await?; // Если ответил — точно открыт
🎬 Заключение
Вот так, за 200 строк Rust’а получаем сканер, который рвёт nmap по скорости . Async Tokio — это магия без overhead’а, а Rust гарантирует, что не будет segfault’ов на проде .
Итоги:
• Tokio runtime = параллелизм без GIL
• FuturesUnordered = пачка задач без блокировок
• Лимит конкурентности = не грохни сеть и админа
• Интеграция с nmap = профит на максималках
