TransWikia.com

Очень медленный процесс сканирования открытых портов

Stack Overflow на русском Asked by Zekoy on December 15, 2020

// работа на тему Информационная безопасность
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    class Program
    {
        private static string IP = "";

        static void Main(string[] args)
        {
            Console.Title = "PortScanner";
            UserInput();
            PortScan();
            Console.ReadKey();
        }

        private static void UserInput()
        {
            Console.Write("IP Address:", ConsoleColor.White);
            IP = Console.ReadLine();
        }

        private static void PortScan()
        {
            Console.Clear();
            TcpClient Scan = new TcpClient();
            foreach (int s in Ports)
            {
                try
                {
                    Scan.Connect(IP, s);
                    Console.WriteLine($"[{s}] | OPEN", ConsoleColor.Green);
                }
                catch
                {
                    Console.WriteLine($"[{s}] | CLOSED", ConsoleColor.Red);
                }
            }
        }

        private static int[] Ports = new int[]
        {
        1,
        2,
        3,
        80,
        };

    }
}

        

Очень долго и длительно сканирует порты на указаном IP. Как ускорить процесс?

Как указать порты одной строкой? (глупо выходит их вот так вводить через запятую)

One Answer

Если очень просто, то ответ такой: Используйте асинхронность, не ждите выполнение другой проверки!
В вашем коде надо сделать пару вещей:

  1. Main сделать с void на async Task.
  2. Сделает задачу (Task), которая будет проверять один порт.
    • То есть, у PortScan выкидываем все лишнее (цикл, очистку консоли)
    • Аргументом требуем порт
    • Меняем с void на async Task.
    • Переделываем .Connect() на .ConnectAsync(), добавив в начало await.
    • Сам TcpClient оборачиваем в using, ибо его надо закрывать!
  3. Преобразуем массив ваших портов в задачи, которые потом запускаем через Task.WhenAll().

В итоге получиться нечто такое:

private static string IP = "";

static async Task Main(string[] args)
{
    Console.Title = "PortScanner";
    UserInput();
    Console.Clear();

    var tasks = Ports.Select(x => PortScan(x));
    await Task.WhenAll(tasks);

    Console.ReadKey();
}

private static void UserInput()
{
    Console.Write("IP Address:", ConsoleColor.White);
    IP = Console.ReadLine();
}

private static async Task PortScan(int port)
{
    using TcpClient Scan = new TcpClient();

    try
    {
        await Scan.ConnectAsync(IP, port);
        Console.WriteLine($"[{port}] | OPEN", ConsoleColor.Green);
    }
    catch
    {
        Console.WriteLine($"[{port}] | CLOSED", ConsoleColor.Red);
    }
}

private static int[] Ports = new int[]
{
1,
2,
3,
80,
};

Теперь у вас проверка портов пойдет асинхронно, то есть без ожидания завершения предыдущей проверки.

Но, у вас есть ряд моментов, которые можно улучшить, давайте сделаем это:

  1. UserInput() - Почему этот метод не отдает результат, а записывает в некую статичную переменную? Ведь его задача, это спросить пользователя и отдать нам для работы его ответ. Давайте напишем чуть по-другому:

    static string AskUser(string message)
    {
        Write($"{message}: ");
        return ReadLine();
    }
    

    Теперь метод отдает нам то, что ввел пользователь.

  2. Вывод цветного сообщения - тут стоит тоже сделать удобный метод, например такой:

    static void ColoredText(string title, string text, ConsoleColor color)
    {
        Write($"{title}: ");
        ForegroundColor = color;
        WriteLine(text);
        ResetColor();
    }
    

    Теперь можно удобно вывести текст, допустим, статуса порта.

  3. IP - Научитесь использовать правильные типы, а не некие базовые. Для ip в c# есть специальный класс IPAddress, работайте с ним! Давайте сделаем метод, который будет спрашивать пользователя ip, если он некорректен, то просит по новой, а в случае успешного ввода, отдаст нам IPAddress:

    static IPAddress AskIp(string message)
    {
        while (true)
        {
            var reply = AskUser(message);
            if (IPAddress.TryParse(reply, out var ip))
            {
                return ip;
            }
            else
            {
                ColoredText(reply, "Не является IP", ConsoleColor.Red);
                ReadKey();
                Clear();
            }
        }
    }
    
  4. Улучшим метод сканирования - сейчас задача ждет таймаута, это тоже можно изменить:

    • Сделаем отдельный метод, (некую обертку над клиентом, которую по-хорошему лучше засунуть в отдельный класс), который будет пытаться сделать соединение, но также асинхронно будет запущен некий таймер, который выдаст ошибку, если выполниться он быстрее:

      static async Task TimeoutConnectAsync(IPAddress ip, int port, TimeSpan connectTimeout = default)
      {
          if (connectTimeout == default) 
              connectTimeout = TimeSpan.FromMilliseconds(500);
      
          using var client = new TcpClient();
      
          var cancelTask = Task.Delay(connectTimeout);
          var connectTask = client.ConnectAsync(ip, port);
      
          await Task.WhenAny(connectTask, cancelTask);
      
          if (cancelTask.IsCompleted)
          {
              throw new TimeoutException();
          }
      }
      
    • Ну и сама задача проверки одного порта:

      static async Task CheckPortAsync(IPAddress ip, int port, TimeSpan connectTimeout = default)
      {
          bool result;
      
          try
          {
              await TimeoutConnectAsync(ip, port, connectTimeout);
              result = true;
          }
          catch
          {
              result = false;
          }
      
          ColoredText($"{ip}:{port}", result ? "Открыт" : "Закрыт", result ? ConsoleColor.Green : ConsoleColor.Red);
      }
      

    Теперь при проверке порта, если он не доступен в течении 500ms, мы считаем его "закрытым".

  5. Множественная проверка - вам не нужно инициализировать новый массив с портами, достаточно воспользоваться params и передавать в метод все порты через запятую. Давайте сделаем новый метод, который будет принимать список портов, ну и проверит их все разом:

    static async Task CheckPortsAsync(IPAddress ip, params int[] ports)
    {
        var tasks = ports.Select(x => CheckPortAsync(ip, x));
        await Task.WhenAll(tasks);
    }
    

    Вызывается это просто, await CheckPortsAsync(ip, 1, 2, 3, 80);

Я тут не стал говорить про ООП и прочие особенности, просто знайте, что в одном классе это писать не хорошо, разбейте все это по разным классам, которые будут отвечать за что-то одно.

Весь код:

static async Task Main()
{
    var ip = AskIp("IP Address");
    await CheckPortsAsync(ip, 1, 2, 3, 80);
}

static async Task CheckPortAsync(IPAddress ip, int port, TimeSpan connectTimeout = default)
{
    bool result;

    try
    {
        await TimeoutConnectAsync(ip, port, connectTimeout);
        result = true;
    }
    catch
    {
        result = false;
    }

    ColoredText($"{ip}:{port}", result ? "Открыт" : "Закрыт", result ? ConsoleColor.Green : ConsoleColor.Red);
}

static async Task CheckPortsAsync(IPAddress ip, params int[] ports)
{
    var tasks = ports.Select(x => CheckPortAsync(ip, x));
    await Task.WhenAll(tasks);
}

static async Task TimeoutConnectAsync(IPAddress ip, int port, TimeSpan connectTimeout = default)
{
    if (connectTimeout == default)
        connectTimeout = TimeSpan.FromMilliseconds(500);

    using var client = new TcpClient();

    var cancelTask = Task.Delay(connectTimeout);
    var connectTask = client.ConnectAsync(ip, port);

    await Task.WhenAny(connectTask, cancelTask);

    if (cancelTask.IsCompleted)
    {
        throw new TimeoutException();
    }
}

static void ColoredText(string title, string text, ConsoleColor color)
{
    Write($"{title}: ");
    ForegroundColor = color;
    WriteLine(text);
    ResetColor();
}

static string AskUser(string message)
{
    Write($"{message}: ");
    return ReadLine();
}
static IPAddress AskIp(string message)
{
    while (true)
    {
        var reply = AskUser(message);
        if (IPAddress.TryParse(reply, out var ip))
        {
            return ip;
        }
        else
        {
            ColoredText(reply, "Не является IP", ConsoleColor.Red);
            ReadKey();
            Clear();
        }
    }
}

Результат выполнения:

Result

Вот, собственно, и все, направление я вам показал, остальное за вами!

  • P.S. Почему ReadLine(), а не Console.ReadLine()? Потому что мне так удобней) Делается при помощи using static System.Console;.
  • P.S.S. Так, как здесь идет асинхронность, то выводить результат может "коряво", тут уже играйтесь с lock и прочими приблудами. Ну а вообще, по-хорошему, лучше делать Task<bool> и уже после того, как все задачи завершились, выводить результат. Метод проверки не обязан выводить что либо, это, по сути, нарушение SOLID, так что подумайте об этом!

можно что-то оригинальное придумать, типа 1-80, 123, 443, 500-2000

Если нужно такое, то можно написать примерно следующий метод:

private static IEnumerable<int> ParseRange(string range) => 
    range.Split(',')
    .Select(x => x.Split('-'))
    .Select(x => (First: int.Parse(x.First()), Last: int.Parse(x.Last())))
    .SelectMany(x => Enumerable.Range(x.First, x.Last - x.First + 1))
    .OrderBy(x => x);

Тут, по сути, все просто:

  1. Разбиваем все по запятой, что в итоге выдаст нам коллекцию строк.
  2. Каждое значение в этой коллекции разбиваем по -, что даст нам, по сути, коллекцию, состоящую из коллекций строк.
  3. Теперь нам нужен некий объект, который будет содержать первое и последнее значение каждой коллекции. Если там всего одно значение, то мин и макс будут одинаковыми. (First: 1, Last: 2) - это кортеж (можно взять и анонимный тип (new {First: 1, Last: 2}).
  4. Осталось нам сгенерировать цифры, для этого есть Enumerable.Range(), который даст числа в указанном диапазоне.
  5. Все, полученный результат сортируем.

Вызываем:

var ports = ParseRange("1, 5-10, 13, 80, 100-100, 115-120")

Результат:

1
5
6
7
8
9
10
13
80
100
115
116
117
118
119
120

Correct answer by EvgeniyZ on December 15, 2020

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP