From 62271a15ca57351eee8820617f210179494d7101 Mon Sep 17 00:00:00 2001 From: Manfred Heil Junior Date: Sat, 26 Sep 2026 10:34:28 -0300 Subject: [PATCH 1/7] Comeca a etapa de portas da varredura Primeira parte da fatia 7: lista padrao de 24 portas com o servico de cada uma, leitura da lista digitada, sonda por conexao TCP sem enviar dados e a etapa de portas do varredor, com limite de conexoes no total e por host. Por padrao, este computador e os aparelhos com MAC aleatorio, provavelmente pessoais, ficam fora da verificacao, como recomenda o parecer. A janela, a linha de comando, o relatorio e os testes vem nos proximos commits. Autores: Manfred Heil Junior --- src/mapnet.nucleo/portas/portas.cs | 203 ++++++++++++++++++++++++ src/mapnet.nucleo/varredura/modelos.cs | 27 ++++ src/mapnet.nucleo/varredura/varredor.cs | 22 ++- 3 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 src/mapnet.nucleo/portas/portas.cs diff --git a/src/mapnet.nucleo/portas/portas.cs b/src/mapnet.nucleo/portas/portas.cs new file mode 100644 index 0000000..68fe461 --- /dev/null +++ b/src/mapnet.nucleo/portas/portas.cs @@ -0,0 +1,203 @@ +using System.Collections.Concurrent; +using System.Globalization; +using System.Net; +using System.Net.Sockets; + +namespace MapNet.Nucleo; + +/// Portas TCP comuns, com o serviço de cada uma, e a leitura da lista digitada. +public static class ListaPortas +{ + public const int MaximoDePortas = 100; + + private static readonly SortedDictionary _servicos = new() + { + [21] = "FTP", + [22] = "SSH", + [23] = "Telnet", + [25] = "SMTP", + [53] = "DNS", + [80] = "HTTP", + [135] = "RPC do Windows", + [139] = "NetBIOS", + [443] = "HTTPS", + [445] = "Compartilhamento do Windows", + [515] = "Impressão LPD", + [554] = "Câmera RTSP", + [631] = "Impressão IPP", + [1433] = "SQL Server", + [3306] = "MySQL", + [3389] = "Área de trabalho remota", + [5000] = "NAS e UPnP", + [5900] = "VNC", + [8000] = "HTTP alternativo", + [8080] = "HTTP alternativo", + [8291] = "MikroTik Winbox", + [8443] = "HTTPS alternativo", + [9100] = "Impressão direta", + [37777] = "NVR Dahua", + }; + + public static IReadOnlyList Padrao { get; } = _servicos.Keys.ToList(); + + public static string Servico(int porta) => _servicos.TryGetValue(porta, out var s) ? s : string.Empty; + + /// "80 (HTTP), 443 (HTTPS), 8123". + public static string Texto(IEnumerable portas) => + string.Join(", ", portas.Select(p => Servico(p) is { Length: > 0 } s ? $"{p} ({s})" : p.ToString(CultureInfo.InvariantCulture))); + + /// + /// Lê "padrao" ou uma lista como "22,80,443". Porta fora de 1 a 65535, texto que não é + /// número e lista com mais de portas são recusados. + /// + public static IReadOnlyList? Interpretar(string? texto, out string? erro) + { + erro = null; + var t = texto?.Trim() ?? string.Empty; + if (t.Length == 0 || t.Equals("padrao", StringComparison.OrdinalIgnoreCase) || t.Equals("padrão", StringComparison.OrdinalIgnoreCase)) + { + return Padrao; + } + + var portas = new SortedSet(); + foreach (var parte in t.Split([',', ';', ' '], StringSplitOptions.RemoveEmptyEntries)) + { + if (!int.TryParse(parte, NumberStyles.None, CultureInfo.InvariantCulture, out var p) || p < 1 || p > 65535) + { + erro = $"Porta inválida: {parte}. Use números de 1 a 65535, separados por vírgula."; + return null; + } + + portas.Add(p); + } + + if (portas.Count > MaximoDePortas) + { + erro = $"São {portas.Count} portas. O limite é {MaximoDePortas}."; + return null; + } + + return portas.ToList(); + } +} + +/// Diz se a porta aceita conexão. Os testes trocam por uma sonda simulada. +public interface ISondaPorta +{ + Task AbertaAsync(IPAddress ip, int porta, int tempoMs, CancellationToken cancelamento); +} + +/// +/// Abre a conexão TCP e fecha em seguida, sem enviar nenhum dado. Porta que recusa ou não +/// responde no tempo limite conta como fechada. Não pede administrador. +/// +public sealed class SondaPortaTcp : ISondaPorta +{ + public async Task AbertaAsync(IPAddress ip, int porta, int tempoMs, CancellationToken cancelamento) + { + using var limite = CancellationTokenSource.CreateLinkedTokenSource(cancelamento); + limite.CancelAfter(tempoMs); + using var socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; + try + { + await socket.ConnectAsync(new IPEndPoint(ip, porta), limite.Token).ConfigureAwait(false); + return true; + } + catch (OperationCanceledException) when (!cancelamento.IsCancellationRequested) + { + return false; + } + catch (SocketException) + { + return false; + } + } +} + +/// +/// Etapa de portas da varredura. Só verifica os hosts que a descoberta achou, nunca sai +/// deles. Por padrão, deixa de fora este computador e os aparelhos com MAC aleatório, que são +/// quase sempre pessoais: o MAC aleatório serve de sinal, não de prova. +/// +public static class EtapaPortas +{ + public const string Nome = "Verificação de portas"; + + public const string MotivoMacAleatorio = "fora da verificação por padrão: MAC aleatório, aparelho provavelmente pessoal"; + + public const string MotivoEsteComputador = "não se aplica (este computador)"; + + /// Hosts que entram na verificação. Os outros recebem o motivo. + public static IReadOnlyList Escolher(IReadOnlyList hosts, bool incluirMacAleatorio) + { + var escolhidos = new List(); + foreach (var h in hosts) + { + if (h.EhEsteComputador) + { + h.MotivoSemPortas = MotivoEsteComputador; + } + else if (h.MacAleatorio && !incluirMacAleatorio) + { + h.MotivoSemPortas = MotivoMacAleatorio; + } + else + { + escolhidos.Add(h); + } + } + + return escolhidos; + } + + public static async Task VerificarAsync( + IReadOnlyList hosts, + OpcoesVarredura opcoes, + ISondaPorta sonda, + IProgress? progresso, + CancellationToken cancelamento) + { + var alvos = Escolher(hosts, opcoes.PortasEmMacAleatorio); + var total = alvos.Count; + var concluidos = 0; + using var global = new SemaphoreSlim(Math.Max(1, opcoes.ConexoesPortas)); + progresso?.Report(new ProgressoVarredura(Nome, 0, total)); + + var tarefas = alvos.Select(async host => + { + using var porHost = new SemaphoreSlim(Math.Max(1, opcoes.ConexoesPorHost)); + var abertas = new ConcurrentBag(); + var portas = opcoes.Portas.Select(async porta => + { + await porHost.WaitAsync(cancelamento).ConfigureAwait(false); + try + { + await global.WaitAsync(cancelamento).ConfigureAwait(false); + try + { + if (await sonda.AbertaAsync(host.Ip, porta, opcoes.TempoPortaMs, cancelamento).ConfigureAwait(false)) + { + abertas.Add(porta); + } + } + finally + { + global.Release(); + } + } + finally + { + porHost.Release(); + } + }); + await Task.WhenAll(portas).ConfigureAwait(false); + + host.PortasAbertas = abertas.Order().ToList(); + host.PortasVerificadas = true; + var feitos = Interlocked.Increment(ref concluidos); + progresso?.Report(new ProgressoVarredura(Nome, feitos, total, host)); + }); + + await Task.WhenAll(tarefas).ConfigureAwait(false); + } +} diff --git a/src/mapnet.nucleo/varredura/modelos.cs b/src/mapnet.nucleo/varredura/modelos.cs index e3c9bac..a292e9d 100644 --- a/src/mapnet.nucleo/varredura/modelos.cs +++ b/src/mapnet.nucleo/varredura/modelos.cs @@ -23,6 +23,22 @@ public sealed class OpcoesVarredura /// Usa o ARP além do ping. Desligar só para teste. public bool UsarArp { get; set; } = true; + + /// Verifica as portas TCP dos hosts encontrados. Desligado por padrão: quem usa liga. + public bool OlharPortas { get; set; } + + public IReadOnlyList Portas { get; set; } = ListaPortas.Padrao; + + /// Inclui na verificação de portas os aparelhos com MAC aleatório, quase sempre pessoais. + public bool PortasEmMacAleatorio { get; set; } + + /// Conexões TCP abertas ao mesmo tempo, somando todos os hosts. + public int ConexoesPortas { get; set; } = 128; + + /// Conexões TCP ao mesmo tempo num só host. + public int ConexoesPorHost { get; set; } = 8; + + public int TempoPortaMs { get; set; } = 800; } /// Andamento da varredura, para barra de progresso e mensagem na tela. @@ -62,6 +78,17 @@ public sealed class HostEncontrado public bool EhEsteComputador { get; set; } + /// Portas TCP que aceitaram conexão, em ordem. + public IReadOnlyList PortasAbertas { get; set; } = []; + + public bool PortasVerificadas { get; set; } + + /// Por que as portas deste host não foram verificadas. Null quando foram, ou quando a etapa não rodou. + public string? MotivoSemPortas { get; set; } + + /// Portas para a tabela: "22, 80, 443". + public string PortasTexto => string.Join(", ", PortasAbertas); + /// Melhor nome disponível, na ordem DNS reverso, NetBIOS, mDNS. public string Nome => NomeDns ?? NomeNetBios ?? NomeMdns ?? string.Empty; diff --git a/src/mapnet.nucleo/varredura/varredor.cs b/src/mapnet.nucleo/varredura/varredor.cs index 807dd6c..7c4a5f1 100644 --- a/src/mapnet.nucleo/varredura/varredor.cs +++ b/src/mapnet.nucleo/varredura/varredor.cs @@ -17,12 +17,14 @@ public sealed class Varredor private readonly OpcoesVarredura _opcoes; private readonly TabelaOui _oui; private readonly ISondaArp? _arp; + private readonly ISondaPorta _portas; - public Varredor(OpcoesVarredura? opcoes = null, TabelaOui? oui = null, ISondaArp? arp = null) + public Varredor(OpcoesVarredura? opcoes = null, TabelaOui? oui = null, ISondaArp? arp = null, ISondaPorta? portas = null) { _opcoes = opcoes ?? new OpcoesVarredura(); _oui = oui ?? TabelaOui.Embutida; _arp = _opcoes.UsarArp ? arp ?? SondaArp.Padrao() : null; + _portas = portas ?? new SondaPortaTcp(); } /// @@ -92,6 +94,24 @@ public async Task VarrerAsync( } } + if (!resultado.Cancelada && _opcoes.OlharPortas) + { + try + { + await EtapaPortas.VerificarAsync(resultado.Hosts, _opcoes, _portas, progresso, cancelamento).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + resultado.Cancelada = true; + } + + var fora = resultado.Hosts.Count(h => h.MotivoSemPortas == EtapaPortas.MotivoMacAleatorio); + if (fora > 0) + { + resultado.Avisos.Add($"{fora} aparelho(s) com MAC aleatório ficaram fora da verificação de portas, por serem provavelmente pessoais."); + } + } + if (resultado.Cancelada) { resultado.Avisos.Add("A varredura foi interrompida antes do fim. O relatório mostra só o que foi levantado até ali."); From 9293b02afdedfb2226b8ff2e8f7606de01c8c89d Mon Sep 17 00:00:00 2001 From: Manfred Heil Junior Date: Sat, 26 Sep 2026 11:26:08 -0300 Subject: [PATCH 2/7] Liga a verificacao de portas a tela, a linha de comando e ao relatorio A etapa de portas do nucleo passa a aparecer para quem usa. Na janela, a caixa Verificar portas comeca desmarcada, a lista aceita padrao ou ate 100 portas e os aparelhos com MAC aleatorio ficam de fora, a nao ser que o tecnico marque a opcao. A primeira varredura com portas em cada rede diz o que vai ser enviado e pede confirmacao, como recomendou a analise juridica. Na linha de comando, --portas liga e --incluir-mac-aleatorio inclui os aparelhos pessoais. A tabela ganha a coluna Portas, com o servico na dica, o filtro acha host por porta aberta, o detalhe mostra as portas ou o motivo de nao terem sido verificadas e os atalhos http e https usam a 8080, 8000 ou 8443 quando so ela esta aberta. O relatorio ganha a coluna, o detalhe e a linha de portas verificadas. O modo de demonstracao tem portas de exemplo. A janela abre com 1280 de largura e a versao passa a 0.3.0. Autores: Manfred Heil Junior --- Directory.Build.props | 2 +- README.md | 19 +- docs/superpowers/pendencias.md | 10 +- public/index.html | 4 +- .../linha-de-comando/argumentos.cs | 35 +- src/mapnet.nucleo/painel/demonstracao.cs | 39 +- src/mapnet.nucleo/painel/detalhe-host.cs | 35 +- src/mapnet.nucleo/painel/linha-host.cs | 17 +- src/mapnet.nucleo/painel/painel-varredura.cs | 129 ++++++- .../relatorios/relatorio-html.cs | 10 +- src/mapnet.nucleo/varredura/modelos.cs | 3 + src/mapnet.nucleo/varredura/varredor.cs | 1 + src/mapnet/app.manifest | 2 +- src/mapnet/janela-principal.xaml | 35 +- src/mapnet/modo-linha-de-comando.cs | 9 +- testes/mapnet.testes/demonstracao-testes.cs | 16 + testes/mapnet.testes/painel-testes.cs | 82 ++++ testes/mapnet.testes/portas-testes.cs | 352 ++++++++++++++++++ 18 files changed, 763 insertions(+), 37 deletions(-) create mode 100644 testes/mapnet.testes/portas-testes.cs diff --git a/Directory.Build.props b/Directory.Build.props index 49a979c..08bf641 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ enable enable true - 0.2.5 + 0.3.0 Manfred Heil Junior MT - Manfred Tecnologia MapNet - MT diff --git a/README.md b/README.md index 9b0d9d0..3e4a56d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Página do programa: **https://mapnet.manfred.com.br**. Download da versão mais Software livre, sob licença [GPL-3.0](LICENSE). -Não é scanner de vulnerabilidade: o programa não testa senha, não contorna autenticação e não explora vulnerabilidade. Para achar os equipamentos, ele manda ping, pedidos ARP e consultas de nome (DNS, NetBIOS e mDNS) aos endereços da sub-rede escolhida. +Não é scanner de vulnerabilidade: o programa não testa senha, não contorna autenticação e não explora vulnerabilidade. Para achar os equipamentos, ele manda ping, pedidos ARP e consultas de nome (DNS, NetBIOS e mDNS) aos endereços da sub-rede escolhida. Com a opção **Verificar portas** ligada, ele também abre e fecha uma conexão TCP em cada porta da lista, nos equipamentos que encontrou, sem enviar dados. **Uso autorizado.** O MapNet - MT serve para inventariar equipamentos em redes nas quais você tem autorização para fazer a verificação. Confira o intervalo de endereços antes de iniciar e respeite as regras da rede e dos dispositivos examinados. @@ -24,7 +24,8 @@ As regras do projeto estão no [`AGENTS.md`](AGENTS.md) e o desenho em [`docs/su | 4 | Console de diagnóstico: ping, tracert, DNS, ARP, conexões e rotas (sai como v0.2.3) | `diagnostico` | [#17](https://github.com/manfredjr/mapnet/pull/17) | Publicada na v0.2.5 | | 5 | Ações de manutenção com elevação sob demanda, na aba Manutenção do console (sai como v0.2.4) | `manutencao` | [#18](https://github.com/manfredjr/mapnet/pull/18) | Publicada na v0.2.5 | | 6 | Painel de detalhe do host, com ping, tracert, navegador, área de trabalho remota e pasta compartilhada (sai como v0.2.5) | `detalhe-host` | [#19](https://github.com/manfredjr/mapnet/pull/19) | Publicada na v0.2.5 | -| 7 em diante | Portas TCP, identificação leve, classificação e relatório completo em HTML, XML e CSV | a definir | - | A fazer | +| 7 | Portas TCP comuns abertas, com lista configurável, desligadas por padrão e com confirmação por rede (sai como v0.3.0) | `portas` | Plano em [#26](https://github.com/manfredjr/mapnet/pull/26), código em [PREENCHER] | Em revisão | +| 8 a 10 | Identificação leve, classificação por tipo de equipamento e relatório também em XML e CSV (até a v1.0.0) | a definir | - | A fazer | A versão 0.2 segue o desenho em [`docs/superpowers/specs/2026-09-25-mapnet-0.2-painel-do-tecnico-design.md`](docs/superpowers/specs/2026-09-25-mapnet-0.2-painel-do-tecnico-design.md). @@ -39,10 +40,11 @@ O programa é um arquivo só, `mapnet.exe`. Não precisa instalar nada no comput 1. Abra o `mapnet.exe`. 2. Na faixa verde do topo, escolha a interface de rede. A primeira da lista é a que tem gateway, quase sempre a certa. A coluna **Minha máquina** mostra o computador (nome, grupo de trabalho ou domínio, usuário), a placa (tipo, MAC, velocidade, MTU), os endereços (IPv4, IPv6, gateway, DNS, sufixo e a sub-rede que vai ser varrida) e o DHCP com a validade da concessão. No Wi-Fi, mostra também a rede, a banda, o canal e o sinal. 3. O botão **Consultar IP público** pergunta o IP ao serviço `1.1.1.1`, do Cloudflare, e só roda com o clique. Se o relatório for gravado depois da consulta, o IP público vai junto. -4. Clique em **Iniciar varredura**. Clicar numa linha da tabela abre, ao lado, o detalhe do host: MAC, fabricante, os nomes pelo DNS reverso, NetBIOS e mDNS, o grupo de trabalho, o ping com o TTL e uma pista do sistema pelo TTL. Os botões do detalhe fazem ping contínuo e tracert no console, abrem http e https no navegador, a área de trabalho remota e a pasta compartilhada, e copiam os dados. Um novo clique na mesma linha, a tecla Esc ou o botão **Fechar** fecham o detalhe. A tabela vai se enchendo enquanto os hosts respondem, e o mesmo botão vira **Cancelar**. O campo **Filtrar** procura por IP, nome, MAC, fabricante ou observação, e cada coluna ordena com um clique no título. -5. O console embaixo registra o andamento e os avisos, e a barra de estado mostra o resumo. O console tem também uma aba para cada ferramenta: Ping (normal ou contínuo), Tracert, DNS com escolha do servidor, ARP, Conexões e Rotas. Os campos de host e de servidor DNS já vêm com o gateway e o DNS da interface. Enquanto a ferramenta roda, **Executar** vira **Parar**. **Copiar** e **Limpar** valem para a aba aberta. -6. No fim, o relatório é gravado em `Documentos\MapNet - MT`. O botão **Relatório** abre o relatório no navegador, salva em outro lugar ou abre a pasta. -7. A aba **Manutenção** tem quatro botões, que rodam os comandos oficiais do Windows e mostram a saída ali mesmo: **Limpar cache DNS** (`ipconfig /flushdns`), **Liberar e renovar IP** (`ipconfig /release` e `/renew`), **Limpar tabela ARP** (`netsh interface ip delete arpcache`) e **Resetar Winsock e TCP/IP** (`netsh winsock reset` e `netsh int ip reset`). O programa continua abrindo como usuário comum. Quando a ação pede administrador, o Windows mostra a tela do UAC só para aquele comando. Renovar o IP e resetar o Winsock derrubam a rede por alguns segundos e pedem confirmação antes. O reset só vale depois de reiniciar o computador. +4. Para saber também as portas abertas de cada equipamento, marque **Verificar portas** acima da tabela. A **Lista** vem com `padrão`, as 24 portas comuns (a dica do campo mostra quais são), ou aceita até 100 portas separadas por vírgula, como `80,443,9100`. Os aparelhos com MAC aleatório, quase sempre celulares e notebooks pessoais, ficam de fora, a não ser que você marque **Incluir aparelhos com MAC aleatório**. Na primeira varredura com portas em cada rede, o programa diz o que vai enviar e pede confirmação. Use só em rede que você tem autorização para verificar. +5. Clique em **Iniciar varredura**. Clicar numa linha da tabela abre, ao lado, o detalhe do host: MAC, fabricante, os nomes pelo DNS reverso, NetBIOS e mDNS, o grupo de trabalho, o ping com o TTL, uma pista do sistema pelo TTL e as portas abertas, com o serviço de cada uma. Os botões do detalhe fazem ping contínuo e tracert no console, abrem http e https no navegador (na 8080, 8000 ou 8443 quando só ela está aberta), a área de trabalho remota e a pasta compartilhada, e copiam os dados. Um novo clique na mesma linha, a tecla Esc ou o botão **Fechar** fecham o detalhe. A tabela vai se enchendo enquanto os hosts respondem, e o mesmo botão vira **Cancelar**. O campo **Filtrar** procura por IP, nome, MAC, fabricante, observação ou número de porta aberta (`9100` lista as impressoras), e cada coluna ordena com um clique no título. +6. O console embaixo registra o andamento e os avisos, e a barra de estado mostra o resumo. O console tem também uma aba para cada ferramenta: Ping (normal ou contínuo), Tracert, DNS com escolha do servidor, ARP, Conexões e Rotas. Os campos de host e de servidor DNS já vêm com o gateway e o DNS da interface. Enquanto a ferramenta roda, **Executar** vira **Parar**. **Copiar** e **Limpar** valem para a aba aberta. +7. No fim, o relatório é gravado em `Documentos\MapNet - MT`. O botão **Relatório** abre o relatório no navegador, salva em outro lugar ou abre a pasta. +8. A aba **Manutenção** tem quatro botões, que rodam os comandos oficiais do Windows e mostram a saída ali mesmo: **Limpar cache DNS** (`ipconfig /flushdns`), **Liberar e renovar IP** (`ipconfig /release` e `/renew`), **Limpar tabela ARP** (`netsh interface ip delete arpcache`) e **Resetar Winsock e TCP/IP** (`netsh winsock reset` e `netsh int ip reset`). O programa continua abrindo como usuário comum. Quando a ação pede administrador, o Windows mostra a tela do UAC só para aquele comando. Renovar o IP e resetar o Winsock derrubam a rede por alguns segundos e pedem confirmação antes. O reset só vale depois de reiniciar o computador. ### Pela linha de comando @@ -50,6 +52,7 @@ O programa é um arquivo só, `mapnet.exe`. Não precisa instalar nada no comput mapnet --interfaces mapnet --varrer mapnet --varrer --interface 2 --saida C:\Relatorios --abrir +mapnet --varrer --portas 80,443,9100 mapnet --ajuda ``` @@ -63,6 +66,8 @@ mapnet --ajuda | `--tempo-ping ` | Espera de cada ping, de 100 a 10000. Padrão: 1000 | | `--paralelo ` | Endereços sondados ao mesmo tempo, de 1 a 256. Padrão: 64 | | `--sem-arp` | Descobre hosts só pelo ping | +| `--portas [lista]` | Verifica as portas TCP dos hosts encontrados, só abrindo e fechando a conexão. Sem lista, usa as 24 portas comuns; com lista, até 100 portas, como `22,80,443`. Desligado sem esta opção | +| `--incluir-mac-aleatorio` | Com `--portas`, inclui os aparelhos com MAC aleatório, que ficam de fora por padrão | O `.exe` é de janela, então o Prompt de Comando devolve o cursor antes de o programa terminar. Para esperar o fim, use `start /wait mapnet --varrer` no Prompt de Comando ou termine a linha com `| Out-Host` no PowerShell. Redirecionar para arquivo (`> saida.txt`) funciona direto, em UTF-8. @@ -104,6 +109,8 @@ Se o valor bater com o do `.txt`, o executável é exatamente o que foi publicad - **Nome:** DNS reverso, NetBIOS (o mesmo do `nbtstat -A`, que traz também o grupo de trabalho ou domínio) e mDNS (nomes `.local` de celulares, Macs, impressoras e aparelhos Linux). - **Relatório HTML:** arquivo único, com resumo da varredura, cartões de contagem, tabela filtrável e ordenável, detalhe de cada host ao clicar na linha e a lista de fabricantes. +- **Portas (desde a v0.3.0, desligadas por padrão):** para cada host encontrado, abre e fecha a conexão TCP em cada porta da lista, sem enviar nenhum dado, com até 128 conexões ao mesmo tempo, 8 por host e 800 ms de espera por porta. Porta que aceita a conexão aparece como aberta. Este computador e os aparelhos com MAC aleatório ficam de fora, e o motivo aparece no detalhe. O relatório ganha a coluna Portas e diz quais portas foram verificadas. A identificação do serviço (título da página, banner, certificado) fica para a v0.4.0. + Sub-rede maior que /22 (1022 endereços) é varrida só no bloco /22 em volta do IP do computador, com aviso no relatório. A faixa manual fica para uma próxima versão. ## Primeira vez na máquina diff --git a/docs/superpowers/pendencias.md b/docs/superpowers/pendencias.md index d6bdc80..d8ac995 100644 --- a/docs/superpowers/pendencias.md +++ b/docs/superpowers/pendencias.md @@ -54,9 +54,17 @@ Fechado em parte na fatia 3 (0.2.2). No tamanho padrão da janela, IP, Origem, M | Item | Motivo | O que fecha | |---|---|---| -| Portas abertas no detalhe | O desenho prevê as portas no detalhe quando a fatia de portas existir | Versão 1, fatia de portas TCP | +| Portas abertas no detalhe | Fechado na fatia 7 (0.3.0): o detalhe mostra as portas abertas com o serviço, ou o motivo de não terem sido verificadas | Fechado | | Esc e novo clique na linha testados só no modelo | O agente não teve acesso ao teclado e ao mouse na janela de teste. A lógica de fechar está coberta por teste, e a ligação na janela é curta | O Manfred abrir e fechar o detalhe com Esc e com novo clique | +## Fatia 7: portas TCP + +| Item | Motivo | O que fecha | +|---|---|---| +| Portas desligadas por padrão | O plano da versão 1.0 previa as portas ligadas, com `--sem-portas` para desligar. A análise jurídica de 26/09/2026 recomendou pedir o escopo antes da sondagem e deixar aparelhos pessoais de fora por padrão. A fatia seguiu a recomendação: a caixa começa desmarcada, a primeira varredura com portas em cada rede pede confirmação e a linha de comando liga com `--portas` | O Manfred confirmar a escolha ou pedir as portas ligadas ao abrir | +| Portas numa rede de verdade | A etapa foi testada com sonda simulada e no modo de demonstração. Não rodou numa rede de verdade no teste do agente | O Manfred varrer a rede da MT com as portas ligadas e conferir, por exemplo, a 80 ou 443 no roteador, a 9100 numa impressora e a 445 num Windows com compartilhamento | +| Confirmação vale para a sessão | A pergunta aparece uma vez por sub-rede enquanto o programa está aberto. Fechar e abrir de novo pergunta outra vez | Guardar a confirmação, se o Manfred achar a pergunta repetitiva no uso | + ## Página de privacidade | Item | Motivo | O que fecha | diff --git a/public/index.html b/public/index.html index a4dd126..141d445 100644 --- a/public/index.html +++ b/public/index.html @@ -48,7 +48,7 @@

O que ele faz

Todos os equipamentos da sub-rede

Por ping e por ARP. Quem bloqueia ping também aparece.

Fabricante e nome

Pelo MAC, na tabela do IEEE, e por DNS, NetBIOS e mDNS.

De onde vem a rede

DHCP, gateway, DNS, Wi-Fi e, com um clique, o IP público.

-

Detalhe de cada equipamento

Nomes, TTL e atalhos para ping, navegador e área de trabalho remota.

+

Detalhe de cada equipamento

Nomes, TTL, portas abertas e atalhos para ping, navegador e área de trabalho remota.

Ferramentas de rede

Ping, tracert, DNS, ARP, conexões e rotas, com a saída em português.

Manutenção

Limpar o cache DNS, renovar o IP, limpar o ARP e resetar o Winsock.

Relatório em HTML

Arquivo único para guardar com a documentação do cliente.

@@ -74,7 +74,7 @@

Como usar

Uso autorizado

O MapNet serve para inventariar equipamentos em redes nas quais você tem autorização para fazer a verificação.

    -
  • O que ele envia: ping, pedidos ARP e consultas de nome (DNS, NetBIOS e mDNS) aos endereços da sub-rede escolhida, as mesmas mensagens que os computadores de uma rede trocam entre si.
  • +
  • O que ele envia: ping, pedidos ARP e consultas de nome (DNS, NetBIOS e mDNS) aos endereços da sub-rede escolhida, as mesmas mensagens que os computadores de uma rede trocam entre si. Se você ligar a verificação de portas, ele também abre e fecha uma conexão TCP em cada porta da lista, só nos equipamentos encontrados e sem enviar dados.
  • O que ele não faz: não testa senhas, não contorna autenticação, não explora vulnerabilidades e não muda configuração de nada na rede. A manutenção mexe só no computador de quem usa, e só com o clique.
  • Sua parte: confira o intervalo de endereços antes de iniciar e respeite as regras da rede e dos dispositivos examinados.
diff --git a/src/mapnet.nucleo/linha-de-comando/argumentos.cs b/src/mapnet.nucleo/linha-de-comando/argumentos.cs index 2cbeada..919d405 100644 --- a/src/mapnet.nucleo/linha-de-comando/argumentos.cs +++ b/src/mapnet.nucleo/linha-de-comando/argumentos.cs @@ -33,9 +33,17 @@ mapnet abre a janela --tempo-ping espera de cada ping, de 100 a 10000 (padrão: 1000) --paralelo endereços sondados ao mesmo tempo, de 1 a 256 (padrão: 64) --sem-arp usa só o ping para descobrir hosts + --portas [lista] verifica as portas TCP dos hosts encontrados, só abrindo + e fechando a conexão. Sem lista, usa as 24 portas comuns; + com lista, por exemplo 22,80,443 (até 100 portas) + --incluir-mac-aleatorio inclui nas portas os aparelhos com MAC aleatório, quase + sempre pessoais, que ficam de fora por padrão - Exemplo: + Exemplos: mapnet --varrer --interface Wi-Fi --saida C:\Relatorios --abrir + mapnet --varrer --portas 80,443,9100 + + Use a verificação de portas só em rede que você tem autorização para verificar. """; public ComandoCli Comando { get; private set; } = ComandoCli.Janela; @@ -91,6 +99,22 @@ public static ArgumentosCli Interpretar(IReadOnlyList args) case "--sem-arp": a.Opcoes.UsarArp = false; break; + case "--portas": + a.Opcoes.OlharPortas = true; + var lista = i + 1 < args.Count && !args[i + 1].StartsWith("--", StringComparison.Ordinal) ? args[++i] : null; + if (ListaPortas.Interpretar(lista, out var erroPortas) is { } portas) + { + a.Opcoes.Portas = portas; + } + else + { + a.Erros.Add(erroPortas!); + } + + break; + case "--incluir-mac-aleatorio": + a.Opcoes.PortasEmMacAleatorio = true; + break; case "--tempo-ping": if (Numero(Valor(args, ref i, arg, a.Erros), 100, 10000, arg, a.Erros) is int tempo) { @@ -116,10 +140,15 @@ public static ArgumentosCli Interpretar(IReadOnlyList args) a.Erros.Add("Use só um comando por vez: --varrer, --interfaces, --ajuda ou --versao."); } - var opcoesDeVarredura = a.Interface != null || a.Saida != null || a.Abrir; + var opcoesDeVarredura = a.Interface != null || a.Saida != null || a.Abrir || a.Opcoes.OlharPortas; if (opcoesDeVarredura && a.Comando == ComandoCli.Janela) { - a.Erros.Add("As opções --interface, --saida e --abrir pedem o comando --varrer."); + a.Erros.Add("As opções --interface, --saida, --abrir e --portas pedem o comando --varrer."); + } + + if (a.Opcoes.PortasEmMacAleatorio && !a.Opcoes.OlharPortas) + { + a.Erros.Add("A opção --incluir-mac-aleatorio só vale junto com --portas."); } return a; diff --git a/src/mapnet.nucleo/painel/demonstracao.cs b/src/mapnet.nucleo/painel/demonstracao.cs index 1e8c583..cf71ab3 100644 --- a/src/mapnet.nucleo/painel/demonstracao.cs +++ b/src/mapnet.nucleo/painel/demonstracao.cs @@ -41,10 +41,12 @@ public static DependenciasPainel Dependencias() VelocidadeBps = 1_000_000_000, }; + var opcoes = new OpcoesVarredura(); return new DependenciasPainel { + Opcoes = opcoes, ListarInterfaces = () => [wifi, cabo], - Varrer = VarrerAsync, + Varrer = (i, p, c) => VarrerAsync(i, opcoes, p, c), SalvarEm = (r, caminho) => RelatorioHtml.SalvarAsync(r, caminho), // Documentos Públicos: o caminho não traz o nome do usuário, que apareceria nas imagens. PastaRelatorios = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments), "MapNet - MT", "demonstracao"), @@ -235,8 +237,25 @@ HostEncontrado Host(byte final, string mac, long? ping, Action? ]; } - /// Varredura de mentira: anda pelos 254 endereços em uns 4 segundos e relata os hosts de exemplo. - private static async Task VarrerAsync(InterfaceRede i, IProgress progresso, CancellationToken cancelamento) + /// Portas abertas de exemplo, pelo último número do IP dos . + private static readonly Dictionary _portasExemplo = new() + { + [1] = [53, 80, 443], + [5] = [80, 443, 515, 631, 9100], + [12] = [22, 80], + [31] = [8080], + [40] = [80, 443], + [57] = [135, 139, 445, 3389], + [101] = [5000], + [142] = [554, 8000], + }; + + /// + /// Varredura de mentira: anda pelos 254 endereços em uns 4 segundos e relata os hosts de + /// exemplo. Com as portas ligadas, preenche as portas de exemplo, com a mesma regra de quem + /// fica de fora que a varredura de verdade usa. + /// + private static async Task VarrerAsync(InterfaceRede i, OpcoesVarredura opcoes, IProgress progresso, CancellationToken cancelamento) { var inicio = DateTimeOffset.Now; var subRede = Varredor.SubRedeAVarrer(i, new OpcoesVarredura().PrefixoMinimo, out _); @@ -261,6 +280,20 @@ private static async Task VarrerAsync(InterfaceRede i, IProg await Task.Delay(15, CancellationToken.None).ConfigureAwait(true); } + if (!resultado.Cancelada && opcoes.OlharPortas) + { + resultado.PortasVerificadas = opcoes.Portas; + var alvos = EtapaPortas.Escolher(resultado.Hosts, opcoes.PortasEmMacAleatorio); + for (var n = 0; n < alvos.Count; n++) + { + var h = alvos[n]; + h.PortasAbertas = _portasExemplo.TryGetValue(h.Ip.GetAddressBytes()[3], out var p) ? p.Where(opcoes.Portas.Contains).ToList() : []; + h.PortasVerificadas = true; + progresso.Report(new ProgressoVarredura(EtapaPortas.Nome, n + 1, alvos.Count, h)); + await Task.Delay(120, CancellationToken.None).ConfigureAwait(true); + } + } + resultado.Fim = DateTimeOffset.Now; return resultado; } diff --git a/src/mapnet.nucleo/painel/detalhe-host.cs b/src/mapnet.nucleo/painel/detalhe-host.cs index 9dd1e6d..f5bf2dc 100644 --- a/src/mapnet.nucleo/painel/detalhe-host.cs +++ b/src/mapnet.nucleo/painel/detalhe-host.cs @@ -20,7 +20,7 @@ public enum AcaoHost public static class DetalheHost { /// Arquivo (ou endereço) e argumentos para abrir no Windows. Só IPv4. - public static (string Arquivo, string? Argumentos) Destino(AcaoHost acao, IPAddress ip) + public static (string Arquivo, string? Argumentos) Destino(AcaoHost acao, IPAddress ip, int? porta = null) { if (ip.AddressFamily != AddressFamily.InterNetwork) { @@ -28,16 +28,44 @@ public static (string Arquivo, string? Argumentos) Destino(AcaoHost acao, IPAddr } var texto = ip.ToString(); + var sufixo = porta is int p ? ":" + p.ToString(System.Globalization.CultureInfo.InvariantCulture) : string.Empty; return acao switch { - AcaoHost.AbrirHttp => ($"http://{texto}/", null), - AcaoHost.AbrirHttps => ($"https://{texto}/", null), + AcaoHost.AbrirHttp => ($"http://{texto}{sufixo}/", null), + AcaoHost.AbrirHttps => ($"https://{texto}{sufixo}/", null), AcaoHost.AreaDeTrabalhoRemota => ("mstsc.exe", $"/v:{texto}"), AcaoHost.PastaCompartilhada => ($@"\\{texto}", null), _ => throw new ArgumentOutOfRangeException(nameof(acao)), }; } + /// + /// Porta que o atalho http ou https deve usar. Quando a porta comum (80 ou 443) está fechada + /// e uma alternativa está aberta, como a 8080 de muito painel de câmera, vai a alternativa. + /// Null é a porta comum. + /// + public static int? PortaWeb(HostEncontrado h, bool https) + { + if (!h.PortasVerificadas) + { + return null; + } + + var (comum, alternativas) = https ? (443, new[] { 8443 }) : (80, new[] { 8080, 8000 }); + if (h.PortasAbertas.Contains(comum)) + { + return null; + } + + return alternativas.FirstOrDefault(h.PortasAbertas.Contains) is var a and > 0 ? a : null; + } + + /// Texto das portas para o detalhe e o relatório. + public static string TextoPortas(HostEncontrado h) => + h.PortasVerificadas + ? (h.PortasAbertas.Count > 0 ? ListaPortas.Texto(h.PortasAbertas) : "nenhuma das portas verificadas está aberta") + : h.MotivoSemPortas ?? "não verificadas nesta varredura"; + /// Pista do sistema pelo TTL da resposta ao ping. public static string? PistaDoTtl(int? ttl) => ttl switch { @@ -68,6 +96,7 @@ public static IReadOnlyList Itens(HostEncontrado h) } itens.Add(new("ARP", h.RespondeuArp ? "respondeu" : h.EhEsteComputador ? "não se aplica (este computador)" : "não respondeu")); + itens.Add(new("Portas abertas", TextoPortas(h))); if (h.Marcas.Count > 0) { itens.Add(new("Observação", string.Join(", ", h.Marcas))); diff --git a/src/mapnet.nucleo/painel/linha-host.cs b/src/mapnet.nucleo/painel/linha-host.cs index dd3ae38..63e7189 100644 --- a/src/mapnet.nucleo/painel/linha-host.cs +++ b/src/mapnet.nucleo/painel/linha-host.cs @@ -39,6 +39,15 @@ public LinhaHost(HostEncontrado host) public string Observacao => string.Join(", ", _host.Marcas); + /// Portas abertas, só os números: "22, 80, 443". + public string Portas => _host.PortasTexto; + + /// Dica da coluna: cada porta com o serviço, ou o motivo de não ter sido verificada. + public string? PortasDica => + _host.PortasAbertas.Count > 0 ? ListaPortas.Texto(_host.PortasAbertas) + : _host.MotivoSemPortas is { } motivo ? char.ToUpperInvariant(motivo[0]) + motivo[1..] + : null; + public bool EhGateway => _host.EhGateway; public bool EhEsteComputador => _host.EhEsteComputador; @@ -50,7 +59,10 @@ public void Atualizar(HostEncontrado host) Avisar(string.Empty); } - /// O filtro procura o texto no IP, no nome, no MAC, no fabricante e na observação. + /// + /// O filtro procura o texto no IP, no nome, no MAC, no fabricante e na observação. Um número + /// que é porta aberta também acha o host: "9100" lista as impressoras. + /// public bool Contem(string texto) { if (string.IsNullOrWhiteSpace(texto)) @@ -67,7 +79,8 @@ public bool Contem(string texto) || Mac.Contains(t, StringComparison.OrdinalIgnoreCase) || (macBusca.Length > 0 && Mac.Replace(":", string.Empty).Contains(macBusca, StringComparison.OrdinalIgnoreCase)) || Fabricante.Contains(t, StringComparison.OrdinalIgnoreCase) - || Observacao.Contains(t, StringComparison.OrdinalIgnoreCase); + || Observacao.Contains(t, StringComparison.OrdinalIgnoreCase) + || _host.PortasAbertas.Any(p => p.ToString(System.Globalization.CultureInfo.InvariantCulture) == t); } private void Avisar([CallerMemberName] string? propriedade = null) => diff --git a/src/mapnet.nucleo/painel/painel-varredura.cs b/src/mapnet.nucleo/painel/painel-varredura.cs index 4dcc8e6..ebd673a 100644 --- a/src/mapnet.nucleo/painel/painel-varredura.cs +++ b/src/mapnet.nucleo/painel/painel-varredura.cs @@ -30,6 +30,12 @@ public sealed class DependenciasPainel public int PrefixoMinimo { get; init; } = new OpcoesVarredura().PrefixoMinimo; + /// + /// Opções que a varredura usa. O painel liga e desliga as portas aqui antes de chamar + /// , que precisa ler este mesmo objeto. + /// + public OpcoesVarredura Opcoes { get; init; } = new(); + public string PastaRelatorios { get; init; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "MapNet - MT"); @@ -78,11 +84,13 @@ public static IReadOnlyList FerramentasPadrao() /// As partes de verdade: interfaces do Windows, varredor e relatório HTML. public static DependenciasPainel Padrao() { - var prefixo = new OpcoesVarredura().PrefixoMinimo; + var opcoes = new OpcoesVarredura(); + var prefixo = opcoes.PrefixoMinimo; return new() { + Opcoes = opcoes, ListarInterfaces = LeitorInterfaces.Listar, - Varrer = (i, p, c) => new Varredor().VarrerAsync(i, p, c), + Varrer = (i, p, c) => new Varredor(opcoes).VarrerAsync(i, p, c), SalvarEm = (r, caminho) => RelatorioHtml.SalvarAsync(r, caminho), LerMaquina = i => Task.Run(() => LeitorMaquina.Ler(i, FontesMaquina.Padrao(), prefixo)), IpPublico = new IpPublicoCloudflare(), @@ -119,6 +127,8 @@ public sealed class PainelVarredura : INotifyPropertyChanged private AbaConsole? _abaManutencao; private bool _manutencaoRodando; private LinhaHost? _hostSelecionado; + private string _textoPortas = "padrão"; + private readonly HashSet _redesConfirmadas = []; public PainelVarredura(DependenciasPainel dependencias) { @@ -267,7 +277,13 @@ public void AcaoNoHost(AcaoHost acao) return; } - var (arquivo, argumentos) = DetalheHost.Destino(acao, h.Host.Ip); + var porta = acao switch + { + AcaoHost.AbrirHttp => DetalheHost.PortaWeb(h.Host, https: false), + AcaoHost.AbrirHttps => DetalheHost.PortaWeb(h.Host, https: true), + _ => null, + }; + var (arquivo, argumentos) = DetalheHost.Destino(acao, h.Host.Ip, porta); try { abrir(arquivo, argumentos); @@ -552,6 +568,70 @@ public string Filtro public bool PodeTrocarInterface => _estado == EstadoPainel.Parado; + /// Verifica as portas TCP dos hosts encontrados. Desligado ao abrir: quem usa liga. + public bool OlharPortas + { + get => _dep.Opcoes.OlharPortas; + set + { + if (_dep.Opcoes.OlharPortas == value || !PodeTrocarInterface) + { + return; + } + + _dep.Opcoes.OlharPortas = value; + Avisar(); + } + } + + /// "padrão" ou a lista digitada, como "22,80,443". Só é conferida ao iniciar. + public string TextoPortas + { + get => _textoPortas; + set + { + var novo = value ?? string.Empty; + if (_textoPortas == novo || !PodeTrocarInterface) + { + return; + } + + _textoPortas = novo; + Avisar(); + } + } + + /// Inclui na verificação de portas os aparelhos com MAC aleatório, quase sempre pessoais. + public bool PortasEmMacAleatorio + { + get => _dep.Opcoes.PortasEmMacAleatorio; + set + { + if (_dep.Opcoes.PortasEmMacAleatorio == value || !PodeTrocarInterface) + { + return; + } + + _dep.Opcoes.PortasEmMacAleatorio = value; + Avisar(); + } + } + + public string DicaPortasPadrao => "Portas da lista padrão: " + ListaPortas.Texto(ListaPortas.Padrao); + + /// + /// Pergunta feita antes da primeira verificação de portas em cada sub-rede. Diz o que vai ser + /// enviado e lembra da autorização, como recomenda a análise jurídica de 26/09/2026. + /// + public static string PerguntaPortas(SubRede subRede, int quantidade, bool incluiMacAleatorio) => + $"Verificar portas em {subRede}?\n\n" + + $"Para cada equipamento encontrado, o MapNet abre e fecha uma conexão TCP em {quantidade} porta(s), sem enviar dados. " + + "Não testa senha nem explora falha.\n\n" + + (incluiMacAleatorio + ? "Os aparelhos com MAC aleatório, quase sempre celulares e notebooks pessoais, também entram.\n\n" + : "Os aparelhos com MAC aleatório, quase sempre pessoais, ficam de fora.\n\n") + + "Faça isso só em rede que você tem autorização para verificar."; + public bool PodeAcionarPrincipal => (_estado == EstadoPainel.Parado && _interfaceSelecionada != null) || _estado == EstadoPainel.Varrendo; @@ -669,6 +749,11 @@ public async Task VarrerAsync() return; } + if (_dep.Opcoes.OlharPortas && !PrepararPortas(interfaceRede)) + { + return; + } + _cancelamento = new CancellationTokenSource(); HostSelecionado = null; Hosts.Clear(); @@ -684,6 +769,11 @@ public async Task VarrerAsync() var subRede = Varredor.SubRedeAVarrer(interfaceRede, _dep.PrefixoMinimo, out var aviso); Console.Escrever($"Varredura de {subRede} ({subRede.QuantidadeHosts} endereços) pela interface {interfaceRede.Nome}."); + if (_dep.Opcoes.OlharPortas) + { + Console.Escrever($"Portas: {_dep.Opcoes.Portas.Count} porta(s) TCP em cada host encontrado, só abrindo e fechando a conexão."); + } + if (aviso != null) { EscreverAviso(aviso); @@ -731,6 +821,38 @@ public async Task VarrerAsync() await SalvarAsync(resultado, Path.Combine(PastaRelatorios, RelatorioHtml.NomeArquivo(resultado))); } + /// + /// Confere a lista de portas e, na primeira vez em cada sub-rede, pede a confirmação. Sem + /// lista válida ou sem o sim do técnico, a varredura não começa. + /// + private bool PrepararPortas(InterfaceRede interfaceRede) + { + var portas = ListaPortas.Interpretar(_textoPortas, out var erro); + if (portas is null) + { + Console.Escrever($"Lista de portas: {erro}"); + Falhou?.Invoke(erro!); + return false; + } + + _dep.Opcoes.Portas = portas; + var subRede = Varredor.SubRedeAVarrer(interfaceRede, _dep.PrefixoMinimo, out _); + var chave = $"{subRede}|{_dep.Opcoes.PortasEmMacAleatorio}"; + if (_redesConfirmadas.Contains(chave)) + { + return true; + } + + if (_dep.Confirmar?.Invoke(PerguntaPortas(subRede, portas.Count, _dep.Opcoes.PortasEmMacAleatorio)) != true) + { + Console.Escrever("Varredura não iniciada: a verificação de portas não foi confirmada."); + return false; + } + + _redesConfirmadas.Add(chave); + return true; + } + /// Grava o último resultado em outro lugar, escolhido pelo técnico. public Task SalvarComoAsync(string caminho) => UltimoResultado is { } r && _estado == EstadoPainel.Parado ? SalvarAsync(r, caminho) : Task.CompletedTask; @@ -846,6 +968,7 @@ private void AvisarBotoes() { Avisar(nameof(PodeTrocarInterface)); Avisar(nameof(PodeAcionarPrincipal)); + Avisar(nameof(OlharPortas)); Avisar(nameof(TextoBotaoPrincipal)); Avisar(nameof(Varrendo)); Avisar(nameof(PodeAbrirRelatorio)); diff --git a/src/mapnet.nucleo/relatorios/relatorio-html.cs b/src/mapnet.nucleo/relatorios/relatorio-html.cs index 0d54975..3cfa1f8 100644 --- a/src/mapnet.nucleo/relatorios/relatorio-html.cs +++ b/src/mapnet.nucleo/relatorios/relatorio-html.cs @@ -99,6 +99,7 @@ public static string Gerar(ResultadoVarredura r) Gateway{{C(i.Gateway?.ToString() ?? "não configurado")}} DNS{{C(i.Dns.Count > 0 ? string.Join(", ", i.Dns) : "não configurado")}} Início{{C(Data(r.Inicio))}} + Portas verificadas{{C(r.PortasVerificadas is { } portas ? $"{portas.Count} porta(s) TCP, só abrindo e fechando a conexão: {ListaPortas.Texto(portas)}" : "não verificadas nesta varredura")}} Duração{{C(Duracao(r.Duracao))}}{{(r.Cancelada ? " (interrompida)" : "")}} Computador{{C(r.NomeComputador)}} @@ -118,6 +119,7 @@ public static string Gerar(ResultadoVarredura r) MAC Fabricante Ping (ms) + Portas Observação @@ -146,7 +148,7 @@ public static string Gerar(ResultadoVarredura r)
Relatório gerado pelo MapNet - MT {{C(ResultadoVarredura.VersaoPrograma)}}, da MT - Manfred Tecnologia. - Levantamento de inventário: o programa só registra o que a rede mostra a qualquer computador ligado nela. + Levantamento de inventário por ping, ARP e consultas de nome{{(r.PortasVerificadas is null ? "" : ", com verificação de portas por conexão TCP")}}. O programa não testa senha nem explora falha.
@@ -176,7 +178,7 @@ private static void AcrescentarMaquina(StringBuilder html, IReadOnlyList{C(h.MacTexto)}"); html.Append($"{C(h.Fabricante)}"); html.Append($"{(h.TempoPingMs.HasValue ? h.TempoPingMs.Value.ToString(_ptBr) : "-")}"); + html.Append($" 0 ? ListaPortas.Texto(h.PortasAbertas) : null)}\">{C(h.PortasTexto)}"); html.Append($"{string.Concat(marcas.Select(m => $"{C(m)}"))}"); html.Append(""); - html.Append("
"); + html.Append("
"); Item(html, "IP", h.Ip.ToString()); Item(html, "MAC", h.MacTexto.Length > 0 ? h.MacTexto : "não obtido"); Item(html, "Fabricante", h.Fabricante.Length > 0 ? h.Fabricante : "não identificado"); @@ -200,6 +203,7 @@ private static void AcrescentarHost(StringBuilder html, HostEncontrado h) Item(html, "Nome exibido vem de", h.OrigemNome.Length > 0 ? h.OrigemNome : "nenhuma fonte respondeu"); Item(html, "Ping", h.RespondeuPing ? $"respondeu em {h.TempoPingMs} ms" + (h.Ttl.HasValue ? $", TTL {h.Ttl}" : "") : "não respondeu"); Item(html, "ARP", h.RespondeuArp ? "respondeu" : h.EhEsteComputador ? "não se aplica (este computador)" : "não respondeu"); + Item(html, "Portas abertas", DetalheHost.TextoPortas(h)); html.Append("
"); } diff --git a/src/mapnet.nucleo/varredura/modelos.cs b/src/mapnet.nucleo/varredura/modelos.cs index a292e9d..31445c2 100644 --- a/src/mapnet.nucleo/varredura/modelos.cs +++ b/src/mapnet.nucleo/varredura/modelos.cs @@ -150,6 +150,9 @@ public sealed class ResultadoVarredura public bool Cancelada { get; set; } + /// Lista de portas que a etapa de portas usou. Null quando a etapa não rodou. + public IReadOnlyList? PortasVerificadas { get; set; } + public string NomeComputador { get; init; } = Environment.MachineName; /// Dados da coluna "Minha máquina" para a seção do relatório. Null quando não foram lidos. diff --git a/src/mapnet.nucleo/varredura/varredor.cs b/src/mapnet.nucleo/varredura/varredor.cs index 7c4a5f1..4812605 100644 --- a/src/mapnet.nucleo/varredura/varredor.cs +++ b/src/mapnet.nucleo/varredura/varredor.cs @@ -96,6 +96,7 @@ public async Task VarrerAsync( if (!resultado.Cancelada && _opcoes.OlharPortas) { + resultado.PortasVerificadas = _opcoes.Portas; try { await EtapaPortas.VerificarAsync(resultado.Hosts, _opcoes, _portas, progresso, cancelamento).ConfigureAwait(false); diff --git a/src/mapnet/app.manifest b/src/mapnet/app.manifest index 7a5e219..887b182 100644 --- a/src/mapnet/app.manifest +++ b/src/mapnet/app.manifest @@ -1,6 +1,6 @@ - + diff --git a/src/mapnet/janela-principal.xaml b/src/mapnet/janela-principal.xaml index 3de4af6..435a630 100644 --- a/src/mapnet/janela-principal.xaml +++ b/src/mapnet/janela-principal.xaml @@ -8,7 +8,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Style="{StaticResource JanelaMT}" Icon="pack://application:,,,/mapnet;component/recursos/mapnet.ico" - Width="1180" Height="740" MinWidth="960" MinHeight="600" + Width="1280" Height="740" MinWidth="960" MinHeight="600" WindowStartupLocation="CenterScreen"> @@ -134,9 +134,21 @@ Foreground="{StaticResource PincelTextoSuave}" /> + ToolTip="IP, nome, MAC, fabricante, observação ou número de porta aberta" /> + + + + + + + @@ -82,12 +84,8 @@ HorizontalAlignment="Center" Margin="0,10,0,0" AutomationProperties.Name="MT - Manfred Tecnologia" ToolTip="MT - Manfred Tecnologia. Abre o site manfred.com.br no navegador."> + Height="110" RenderOptions.BitmapScalingMode="HighQuality" /> - -