Очень медленный процесс сканирования открытых портов
// работа на тему Информационная безопасность
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. Как ускорить процесс?
Как указать порты одной строкой? (глупо выходит их вот так вводить через запятую)
Ответы (1 шт):
Если очень просто, то ответ такой: Используйте асинхронность, не ждите выполнение другой проверки!
В вашем коде надо сделать пару вещей:
Mainсделать сvoidнаasync Task.- Сделает задачу (
Task), которая будет проверять один порт.- То есть, у
PortScanвыкидываем все лишнее (цикл, очистку консоли) - Аргументом требуем порт
- Меняем с
voidнаasync Task. - Переделываем
.Connect()на.ConnectAsync(), добавив в началоawait. - Сам
TcpClientоборачиваем вusing, ибо его надо закрывать!
- То есть, у
- Преобразуем массив ваших портов в задачи, которые потом запускаем через
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,
};
Теперь у вас проверка портов пойдет асинхронно, то есть без ожидания завершения предыдущей проверки.
Но, у вас есть ряд моментов, которые можно улучшить, давайте сделаем это:
UserInput()- Почему этот метод не отдает результат, а записывает в некую статичную переменную? Ведь его задача, это спросить пользователя и отдать нам для работы его ответ. Давайте напишем чуть по-другому:static string AskUser(string message) { Write($"{message}: "); return ReadLine(); }Теперь метод отдает нам то, что ввел пользователь.
Вывод цветного сообщения - тут стоит тоже сделать удобный метод, например такой:
static void ColoredText(string title, string text, ConsoleColor color) { Write($"{title}: "); ForegroundColor = color; WriteLine(text); ResetColor(); }Теперь можно удобно вывести текст, допустим, статуса порта.
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(); } } }Улучшим метод сканирования - сейчас задача ждет таймаута, это тоже можно изменить:
Сделаем отдельный метод, (некую обертку над клиентом, которую по-хорошему лучше засунуть в отдельный класс), который будет пытаться сделать соединение, но также асинхронно будет запущен некий таймер, который выдаст ошибку, если выполниться он быстрее:
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, мы считаем его "закрытым".
Множественная проверка - вам не нужно инициализировать новый массив с портами, достаточно воспользоваться
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();
}
}
}
Результат выполнения:
Вот, собственно, и все, направление я вам показал, остальное за вами!
- 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);
Тут, по сути, все просто:
- Разбиваем все по запятой, что в итоге выдаст нам коллекцию строк.
- Каждое значение в этой коллекции разбиваем по
-, что даст нам, по сути, коллекцию, состоящую из коллекций строк. - Теперь нам нужен некий объект, который будет содержать первое и последнее значение каждой коллекции. Если там всего одно значение, то мин и макс будут одинаковыми.
(First: 1, Last: 2)- это кортеж (можно взять и анонимный тип (new {First: 1, Last: 2}). - Осталось нам сгенерировать цифры, для этого есть
Enumerable.Range(), который даст числа в указанном диапазоне. - Все, полученный результат сортируем.
Вызываем:
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
