---
title: "Scanner de Portas de Rede"
url: "https://rustlang.com.br/projetos/port-scanner/"
markdown_url: "https://rustlang.com.br/projetos/port-scanner.MD"
description: "Construa um scanner de portas TCP em Rust com varredura concorrente usando threads, timeout configurável e detecção de serviços."
date: "2026-02-24"
author: "Equipe Rust Brasil"
---

# Scanner de Portas de Rede

Construa um scanner de portas TCP em Rust com varredura concorrente usando threads, timeout configurável e detecção de serviços.


Scanners de portas são ferramentas essenciais para administradores de rede e profissionais de segurança. Eles permitem descobrir quais serviços estão rodando em uma máquina ao verificar quais portas TCP estão abertas e aceitando conexões. Neste projeto, vamos construir um scanner de portas TCP em Rust que usa threads para varredura concorrente, tornando a operação muito mais rápida do que verificar cada porta sequencialmente.

Este projeto é uma excelente oportunidade para aprender sobre programação concorrente em Rust com `std::thread`, comunicação entre threads com channels, operações de rede com `std::net` e o modelo de segurança que Rust oferece para programação paralela.

## O Que Vamos Construir

Nosso `portscan` terá os seguintes recursos:

- Varredura de range de portas configurável
- Varredura concorrente com número de threads ajustável
- Timeout de conexão configurável
- Detecção de serviços conhecidos por porta
- Exibição progressiva dos resultados
- Resumo com estatísticas da varredura

## Estrutura do Projeto

```
portscan/
├── Cargo.toml
└── src/
    ├── main.rs
    ├── cli.rs
    ├── scanner.rs
    └── servicos.rs
```

## Configurando o Projeto

```bash
cargo new portscan
cd portscan
```

Configure o `Cargo.toml`:

```toml
[package]
name = "portscan"
version = "0.1.0"
edition = "2021"

[dependencies]
clap = { version = "4", features = ["derive"] }
colored = "2"
```

Note que não precisamos de crates externas para rede — o `std::net` da biblioteca padrão fornece tudo que precisamos para conexões TCP.

## Passo 1: Interface de Linha de Comando

```rust
// src/cli.rs
use clap::Parser;

#[derive(Parser, Debug)]
#[command(name = "portscan")]
#[command(about = "Scanner de portas TCP com varredura concorrente")]
pub struct Cli {
    /// Endereço do host a escanear (IP ou hostname)
    pub host: String,

    /// Porta inicial do range
    #[arg(short = 'i', long, default_value_t = 1)]
    pub porta_inicio: u16,

    /// Porta final do range
    #[arg(short = 'f', long, default_value_t = 1024)]
    pub porta_fim: u16,

    /// Número de threads para varredura concorrente
    #[arg(short = 't', long, default_value_t = 100)]
    pub threads: usize,

    /// Timeout de conexão em milissegundos
    #[arg(short = 'o', long, default_value_t = 500)]
    pub timeout_ms: u64,

    /// Exibir apenas portas abertas (omitir fechadas)
    #[arg(short = 'a', long, default_value_t = true)]
    pub apenas_abertas: bool,

    /// Exibir progresso da varredura
    #[arg(short, long)]
    pub progresso: bool,
}
```

## Passo 2: Mapa de Serviços Conhecidos

```rust
// src/servicos.rs
use std::collections::HashMap;

/// Retorna um mapa de portas para nomes de serviços conhecidos
pub fn mapa_servicos() -> HashMap<u16, &'static str> {
    let mut mapa = HashMap::new();

    mapa.insert(20, "FTP (dados)");
    mapa.insert(21, "FTP (controle)");
    mapa.insert(22, "SSH");
    mapa.insert(23, "Telnet");
    mapa.insert(25, "SMTP");
    mapa.insert(53, "DNS");
    mapa.insert(80, "HTTP");
    mapa.insert(110, "POP3");
    mapa.insert(143, "IMAP");
    mapa.insert(443, "HTTPS");
    mapa.insert(465, "SMTPS");
    mapa.insert(587, "SMTP (submission)");
    mapa.insert(993, "IMAPS");
    mapa.insert(995, "POP3S");
    mapa.insert(1433, "MS SQL Server");
    mapa.insert(1521, "Oracle DB");
    mapa.insert(3306, "MySQL");
    mapa.insert(3389, "RDP");
    mapa.insert(5432, "PostgreSQL");
    mapa.insert(5672, "RabbitMQ");
    mapa.insert(6379, "Redis");
    mapa.insert(8080, "HTTP alternativo");
    mapa.insert(8443, "HTTPS alternativo");
    mapa.insert(9200, "Elasticsearch");
    mapa.insert(27017, "MongoDB");

    mapa
}
```

## Passo 3: Motor de Varredura Concorrente

```rust
// src/scanner.rs
use std::net::{SocketAddr, TcpStream, ToSocketAddrs};
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};

/// Resultado da varredura de uma porta
#[derive(Debug, Clone)]
pub struct ResultadoPorta {
    pub porta: u16,
    pub aberta: bool,
}

/// Resultado completo da varredura
pub struct ResultadoVarredura {
    pub host: String,
    pub portas_abertas: Vec<u16>,
    pub portas_fechadas: usize,
    pub tempo_total: Duration,
    pub total_escaneadas: usize,
}

/// Resolve o hostname para um endereço IP
pub fn resolver_host(host: &str) -> Result<String, String> {
    let endereco = format!("{}:0", host);
    match endereco.to_socket_addrs() {
        Ok(mut addrs) => {
            if let Some(addr) = addrs.next() {
                Ok(addr.ip().to_string())
            } else {
                Err(format!("Não foi possível resolver '{}'", host))
            }
        }
        Err(e) => Err(format!("Erro ao resolver '{}': {}", host, e)),
    }
}

/// Verifica se uma porta está aberta tentando uma conexão TCP
fn verificar_porta(endereco: &SocketAddr, timeout: Duration) -> bool {
    TcpStream::connect_timeout(endereco, timeout).is_ok()
}

/// Executa a varredura de portas usando múltiplas threads
pub fn escanear(
    host: &str,
    ip: &str,
    porta_inicio: u16,
    porta_fim: u16,
    num_threads: usize,
    timeout_ms: u64,
    mostrar_progresso: bool,
) -> ResultadoVarredura {
    let inicio = Instant::now();
    let timeout = Duration::from_millis(timeout_ms);

    let portas: Vec<u16> = (porta_inicio..=porta_fim).collect();
    let total = portas.len();

    // Canal para receber resultados das threads
    let (tx, rx) = mpsc::channel::<ResultadoPorta>();

    // Dividir as portas em chunks para as threads
    let chunk_size = (total + num_threads - 1) / num_threads;
    let chunks: Vec<Vec<u16>> = portas
        .chunks(chunk_size)
        .map(|c| c.to_vec())
        .collect();

    let mut handles = Vec::new();

    for chunk in chunks {
        let tx = tx.clone();
        let ip = ip.to_string();

        let handle = thread::spawn(move || {
            for porta in chunk {
                let endereco: SocketAddr = format!("{}:{}", ip, porta)
                    .parse()
                    .expect("Endereço inválido");

                let aberta = verificar_porta(&endereco, timeout);

                let _ = tx.send(ResultadoPorta { porta, aberta });
            }
        });

        handles.push(handle);
    }

    // Fechar o transmissor original para que o receptor saiba quando parar
    drop(tx);

    // Coletar resultados
    let mut portas_abertas = Vec::new();
    let mut portas_fechadas: usize = 0;
    let mut processadas: usize = 0;

    for resultado in rx {
        processadas += 1;

        if resultado.aberta {
            portas_abertas.push(resultado.porta);
        } else {
            portas_fechadas += 1;
        }

        if mostrar_progresso && processadas % 50 == 0 {
            let percentual = (processadas as f64 / total as f64) * 100.0;
            eprint!(
                "\rProgresso: {:.1}% ({}/{} portas)",
                percentual, processadas, total
            );
        }
    }

    if mostrar_progresso {
        eprintln!("\rProgresso: 100.0% ({}/{} portas)  ", total, total);
    }

    // Aguardar todas as threads terminarem
    for handle in handles {
        let _ = handle.join();
    }

    portas_abertas.sort();

    ResultadoVarredura {
        host: host.to_string(),
        portas_abertas,
        portas_fechadas,
        tempo_total: inicio.elapsed(),
        total_escaneadas: total,
    }
}
```

O scanner divide o range de portas em chunks iguais e atribui cada chunk a uma thread. Cada thread tenta conectar em suas portas e envia o resultado pelo canal (`mpsc::channel`). A thread principal coleta os resultados conforme chegam. Essa abordagem é simples e eficaz — com 100 threads, uma varredura de 1024 portas leva apenas alguns segundos.

## Passo 4: Integrando no main.rs

```rust
// src/main.rs
mod cli;
mod scanner;
mod servicos;

use clap::Parser;
use cli::Cli;
use colored::*;
use std::collections::HashMap;

fn main() {
    let cli = Cli::parse();

    // Validar range de portas
    if cli.porta_inicio > cli.porta_fim {
        eprintln!(
            "{} A porta inicial ({}) deve ser menor ou igual à final ({}).",
            "ERRO:".red().bold(),
            cli.porta_inicio,
            cli.porta_fim
        );
        std::process::exit(1);
    }

    // Resolver hostname
    let ip = match scanner::resolver_host(&cli.host) {
        Ok(ip) => ip,
        Err(e) => {
            eprintln!("{} {}", "ERRO:".red().bold(), e);
            std::process::exit(1);
        }
    };

    let total_portas = (cli.porta_fim - cli.porta_inicio + 1) as usize;

    println!("{}", "Scanner de Portas TCP".bold().cyan());
    println!("  Host:       {} ({})", cli.host, ip);
    println!("  Range:      {}-{} ({} portas)", cli.porta_inicio, cli.porta_fim, total_portas);
    println!("  Threads:    {}", cli.threads);
    println!("  Timeout:    {} ms", cli.timeout_ms);
    println!();

    // Executar a varredura
    let resultado = scanner::escanear(
        &cli.host,
        &ip,
        cli.porta_inicio,
        cli.porta_fim,
        cli.threads,
        cli.timeout_ms,
        cli.progresso,
    );

    // Carregar mapa de serviços
    let servicos: HashMap<u16, &str> = servicos::mapa_servicos();

    // Exibir resultados
    if resultado.portas_abertas.is_empty() {
        println!(
            "{} Nenhuma porta aberta encontrada no range {}-{}.",
            "INFO:".yellow().bold(),
            cli.porta_inicio,
            cli.porta_fim
        );
    } else {
        println!(
            "{}\n",
            "Portas abertas encontradas:".bold().green()
        );

        println!(
            "  {:<8} {:<8} {}",
            "PORTA".bold(),
            "ESTADO".bold(),
            "SERVIÇO".bold()
        );
        println!("  {}", "-".repeat(45));

        for porta in &resultado.portas_abertas {
            let servico = servicos
                .get(porta)
                .unwrap_or(&"Desconhecido");

            println!(
                "  {:<8} {:<8} {}",
                porta.to_string().cyan(),
                "aberta".green(),
                servico
            );
        }
    }

    // Resumo
    println!("\n{}", "Resumo da Varredura".bold().cyan());
    println!(
        "  Portas abertas:    {}",
        resultado.portas_abertas.len().to_string().green().bold()
    );
    println!(
        "  Portas fechadas:   {}",
        resultado.portas_fechadas
    );
    println!(
        "  Total escaneadas:  {}",
        resultado.total_escaneadas
    );
    println!(
        "  Tempo total:       {:.2} segundos",
        resultado.tempo_total.as_secs_f64()
    );
    println!(
        "  Velocidade:        {:.0} portas/segundo",
        resultado.total_escaneadas as f64 / resultado.tempo_total.as_secs_f64()
    );
}
```

## Como Executar

```bash
cargo build --release
```

Exemplos de uso:

```bash
# Escanear portas comuns (1-1024) de um host
./target/release/portscan localhost

# Saída exemplo:
# Scanner de Portas TCP
#   Host:       localhost (127.0.0.1)
#   Range:      1-1024 (1024 portas)
#   Threads:    100
#   Timeout:    500 ms
#
# Portas abertas encontradas:
#
#   PORTA    ESTADO   SERVIÇO
#   ---------------------------------------------
#   22       aberta   SSH
#   80       aberta   HTTP
#   443      aberta   HTTPS
#   5432     aberta   PostgreSQL
#
# Resumo da Varredura
#   Portas abertas:    4
#   Portas fechadas:   1020
#   Total escaneadas:  1024
#   Tempo total:       2.34 segundos
#   Velocidade:        437 portas/segundo

# Escanear range específico com mais threads
./target/release/portscan 192.168.1.1 -i 80 -f 9000 -t 200

# Escanear com timeout menor (mais rápido, mas pode perder portas lentas)
./target/release/portscan meuservidor.com -o 200

# Varredura completa (0-65535) com progresso
./target/release/portscan localhost -i 1 -f 65535 -t 500 --progresso

# Escanear portas de banco de dados
./target/release/portscan dbserver -i 3306 -f 5432 -t 50
```

## Desafios para Expandir

1. **Banner grabbing**: Após detectar uma porta aberta, envie e receba dados para identificar o serviço exato e sua versão (ex: "OpenSSH 9.0" ou "Apache 2.4").

2. **Varredura UDP**: Além de TCP, implemente varredura de portas UDP enviando datagramas e analisando respostas ICMP "port unreachable" para detectar portas fechadas.

3. **Exportação de resultados**: Adicione flags para exportar em JSON, CSV ou formato XML compatível com o Nmap, facilitando a integração com outras ferramentas de segurança.

4. **Detecção de firewall**: Analise os tempos de resposta e padrões de erro para diferenciar entre portas realmente fechadas e portas filtradas por firewall.

5. **Varredura assíncrona com tokio**: Substitua threads por tarefas assíncronas usando `tokio` para melhorar a escalabilidade e reduzir o consumo de memória em varreduras de grande escala.

## Veja Também

- [Módulo net](/stdlib/net-module/) — programação de rede em Rust
- [Threads em Rust](/stdlib/thread/) — criando e gerenciando threads
- [Channels](/stdlib/channels/) — comunicação entre threads com canais
- [Executando Threads](/receitas/executar-threads/) — receita prática de concorrência
- [Rust para DevOps](/artigos/rust-para-devops/) — ferramentas de infraestrutura em Rust
