Что такое сканер портов?
Просто сканер портов — это приложение или инструмент, который используется для идентификации открытых портов в сети или на сервере. Короче говоря, сканеры портов отправляют пакеты данных на соответствующие номера портов и проверяют ответы, чтобы определить, открыт или закрыт конкретный порт.
Почему Голанг?
Golang – это язык программирования, разработанный Google, с открытым исходным кодом, скомпилированным и статически типизированным. Он был разработан как удобочитаемый, эффективный и высокопроизводительный язык программирования с учетом простоты.
В Go есть поддержка параллелизма, которая позволяет выполнять функции одновременно и независимо, известные как горутины. Каждая из этих горутин требует всего 2 КБ памяти, что делает язык масштабируемым и эффективным для запуска нескольких параллельных процессов.
В нашем случае при использовании сканера портов пакеты данных должны отправляться на определенные номера портов, а ответы должны проверяться, чтобы определить, открыт порт или закрыт. Однако сканирование большого количества портов может занять много времени, так как каждый из них должен сканироваться отдельно.
Однако, используя поддержку параллелизма в Go, а именно «процедуры go», мы можем ускорить процесс сканирования портов. Эта функция позволяет одновременно сканировать порты, запуская несколько процессов одновременно, что приводит к сокращению времени сканирования.
Давайте построим это
импорт пакетов
package main import ( "bufio" "fmt" "net" "os" "strconv" "strings" "time" )
Это пакеты Go, которые мы собираемся использовать для этого приложения.
- "bufio" — этот пакет реализует буферизованный ввод-вывод. Он позволяет читать и записывать данные фрагментами, что может повысить производительность при работе с большими объемами данных.
- fmt — этот пакет предоставляет функции форматирования и печати для программ Go. Он позволяет создавать форматированные выходные строки, а также печатать данные на консоли или в других выходных потоках.
- “net” — этот пакет предоставляет набор функций и типов для работы с сетевыми подключениями, включая TCP, UDP и IP. Он также обеспечивает поддержку работы с DNS и другими сетевыми протоколами.
- "os" — этот пакет предоставляет набор функций и типов для работы с операционной системой, включая операции с файлами и каталогами, управление процессами и переменные среды.
- "strconv" – этот пакет предоставляет функции для преобразования строк в другие типы, например целые числа и числа с плавающей запятой, а также для форматирования чисел как строк.
- "строки" — этот пакет предоставляет функции для работы со строками, включая поиск, замену и разделение.
- "time" — этот пакет предоставляет функции для работы с датами и временем, включая синтаксический анализ, форматирование и вычисление разницы во времени.
Чтение пользовательского ввода
reader := bufio.NewReader(os.Stdin)
Сначала определяет «читатель» для чтения входных данных из стандартного входного потока.
fmt.Print("Enter the address: ") address, _ := reader.ReadString('\n') address = strings.TrimSpace(address) fmt.Print("Enter the protocol (tcp/udp): ") protocol, _ := reader.ReadString('\n') protocol = strings.TrimSpace(protocol) fmt.Print("Enter the starting port: ") startPortStr, _ := reader.ReadString('\n') startPortStr = strings.TrimSpace(startPortStr) startPort, err := strconv.Atoi(startPortStr) if err != nil { fmt.Printf("Error: %v\n", err) return } fmt.Print("Enter the ending port: ") endPortStr, _ := reader.ReadString('\n') endPortStr = strings.TrimSpace(endPortStr) endPort, err := strconv.Atoi(endPortStr) if err != nil { fmt.Printf("Error: %v\n", err) return }
Затем после использования «читателя» для чтения пользовательских входов один за другим и сохранения пользовательских входов в переменных «адрес», «протокол», «startPortStr» и «endPortStr».
Здесь я проверяю ввод один раз после того, как пользователь вводит значения в систему.
Вызов функции
openPorts := scanPorts(address, protocol, startPort, endPort) if len(openPorts) == 0 { fmt.Println("No open ports found") } else { fmt.Printf("Open ports: %v\n", openPorts) }
Здесь функция scanPorts вызывается после того, как пользователь вводит необходимые параметры. Эти входные значения передаются в функцию «scanPorts», которая возвращает массив открытых портов, если они найдены.
Написание функции scanPorts
func scanPorts(address string, protocol string, startPort int, endPort int) []int { openPorts := []int{} results := make(chan int) startTime := time.Now() for port := startPort; port <= endPort; port++ { go func(port int) { target := fmt.Sprintf("%s:%d", address, port) conn, err := net.Dial(protocol, target) if err == nil { conn.Close() results <- port } else { results <- 0 } }(port) } for i := 0; i < endPort-startPort+1; i++ { port := <-results if port != 0 { openPorts = append(openPorts, port) } } endTime := time.Now() duration := endTime.Sub(startTime) fmt.Print(" \n") fmt.Printf("Scanned %d ports in %s\n", endPort-startPort+1, duration) return openPorts }
Здесь происходят 4 основных процесса,
- Инициализировать массив «openPorts» и канал «результаты».
- Используя цикл for, переберите диапазон номеров портов между startPort и endPort. Внутри цикла создайте новую горутину для каждого номера порта, используя ключевое слово «go».
- в роутинге go это основная строка кода, которая используется для сканирования порта
conn, err := net.Dial(protocol, target)
Эта строка пытается установить сетевое соединение с адресом и портом target
, используя функцию net.Dial()
из пакета net
. Аргумент protocol
указывает тип используемого сетевого протокола (например, tcp
или udp
). Функция возвращает объект соединения conn
и ошибку err
.
Если система обнаружила какие-либо открытые порты в сети, она отправляет их в канал результатов. results <- port
В нашей функции эта строка сразу же вызывает анонимную функцию с аргументом port
, создавая новую горутину, которая будет выполняться одновременно с основной программой.
}(port)
В целом, этот раздел кода одновременно сканирует диапазон портов, создавая новую горутину для каждого порта с использованием ключевого слова go
. Каждая горутина пытается установить сетевое соединение с адресом и портом, указанными в переменных address
и port
, используя функцию net.Dial()
, и отправляет число port
на канал results
, если соединение успешно. Если соединение не удается, вместо этого на канал results
отправляется значение 0
.
Полученные результаты
Основное преимущество использования подпрограмм go для параллелизма заключается в том, что они позволяют полностью использовать вычислительную мощность во время процесса сканирования. Это приводит к значительному сокращению времени, необходимого для всего процесса сканирования, по сравнению с инструментами, которые сканируют один порт за раз.
В тестовом сценарии, который был выполнен на моем ПК, сканирование 10 000 TCP-портов с использованием подпрограмм go заняло примерно 7 секунд. Однако для того же сценария мне потребовалось в десять раз больше времени для завершения процесса сканирования без использования функции параллелизма.
вы можете найти код здесь