Что такое сканер портов?

Просто сканер портов — это приложение или инструмент, который используется для идентификации открытых портов в сети или на сервере. Короче говоря, сканеры портов отправляют пакеты данных на соответствующие номера портов и проверяют ответы, чтобы определить, открыт или закрыт конкретный порт.

Почему Голанг?

Golang – это язык программирования, разработанный Google, с открытым исходным кодом, скомпилированным и статически типизированным. Он был разработан как удобочитаемый, эффективный и высокопроизводительный язык программирования с учетом простоты.

В Go есть поддержка параллелизма, которая позволяет выполнять функции одновременно и независимо, известные как горутины. Каждая из этих горутин требует всего 2 КБ памяти, что делает язык масштабируемым и эффективным для запуска нескольких параллельных процессов.

В нашем случае при использовании сканера портов пакеты данных должны отправляться на определенные номера портов, а ответы должны проверяться, чтобы определить, открыт порт или закрыт. Однако сканирование большого количества портов может занять много времени, так как каждый из них должен сканироваться отдельно.

Однако, используя поддержку параллелизма в Go, а именно «процедуры go», мы можем ускорить процесс сканирования портов. Эта функция позволяет одновременно сканировать порты, запуская несколько процессов одновременно, что приводит к сокращению времени сканирования.

Давайте построим это

импорт пакетов

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strconv"
    "strings"
    "time"
)

Это пакеты Go, которые мы собираемся использовать для этого приложения.

  1. "bufio" — этот пакет реализует буферизованный ввод-вывод. Он позволяет читать и записывать данные фрагментами, что может повысить производительность при работе с большими объемами данных.
  2. fmt — этот пакет предоставляет функции форматирования и печати для программ Go. Он позволяет создавать форматированные выходные строки, а также печатать данные на консоли или в других выходных потоках.
  3. “net” — этот пакет предоставляет набор функций и типов для работы с сетевыми подключениями, включая TCP, UDP и IP. Он также обеспечивает поддержку работы с DNS и другими сетевыми протоколами.
  4. "os" — этот пакет предоставляет набор функций и типов для работы с операционной системой, включая операции с файлами и каталогами, управление процессами и переменные среды.
  5. "strconv" – этот пакет предоставляет функции для преобразования строк в другие типы, например целые числа и числа с плавающей запятой, а также для форматирования чисел как строк.
  6. "строки" — этот пакет предоставляет функции для работы со строками, включая поиск, замену и разделение.
  7. "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 основных процесса,

  1. Инициализировать массив «openPorts» и канал «результаты».
  2. Используя цикл for, переберите диапазон номеров портов между startPort и endPort. Внутри цикла создайте новую горутину для каждого номера порта, используя ключевое слово «go».
  3. в роутинге 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 секунд. Однако для того же сценария мне потребовалось в десять раз больше времени для завершения процесса сканирования без использования функции параллелизма.

вы можете найти код здесь