Ненадежные результаты при одновременном сканировании нескольких портов

Фон:

Я читал Black Hat Go, где автор представляет простой сканер портов, использующий подпрограммы go1:

package main

import (
    "fmt"
    "net"
)

func main() {
    for i := 1; i <= 9000; i++ {
        go func(j int) {
            address := fmt.Sprintf("127.0.0.1:%d", j)
            conn, err := net.Dial("tcp", address)
            if err != nil {
                return
            }
            conn.Close()
            fmt.Printf("%d open\n", j)
        }(i)
    }
}

И далее он упоминает следующее:

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

Чтобы проверить это, я запустил 2 php-сервера2 на портах. 8000 и 8500 и запустил приведенный выше код для сканирования моих локальных портов.

Каждый раз это давало мне противоречивые результаты. Иногда он обнаруживал оба открытых порта, иногда нет.

Вопрос:

  1. Являются ли противоречивые результаты следствием некоторых ограничений TCP?

  2. Есть ли способ рассчитать оптимальное количество портов, которые можно сканировать параллельно, чтобы результаты оставались правильными?

Изменить:

Кажется, я пропустил группы ожидания в приведенном выше коде.

Помимо этого, есть ли что-нибудь еще (ограничение ОС или ограничение протокола), которое препятствует одновременному сканированию портов в большом диапазоне?


person athul.sure    schedule 31.05.2020    source источник
comment
Можете ли вы добавить код для печати err и добавить несколько примеров вывода на свой вопрос? Одним из ограничений может быть количество дескрипторов открытых файлов, которые может иметь каждый процесс.   -  person Mark Plotnick    schedule 31.05.2020
comment
@MarkPlotnick Ты был прав! Я столкнулся с ошибкой socket: too many open files. Можете ли вы написать это как ответ, чтобы я мог принять его?   -  person athul.sure    schedule 31.05.2020


Ответы (1)


Ваша основная функция завершится, как только цикл for завершится. Если основная функция завершается, то же самое происходит и со всеми запущенными ею горутинами. Вам нужно дождаться завершения горутин. Этого можно добиться, например, с помощью sync.WaitGroup.

package main

import (
    "fmt"
    "net"
    "sync"
    "time"
)

func main() {

    // This will help you to keep track of the goroutines
    var wg sync.WaitGroup

    for i := 1; i <= 9000; i++ {

        // Increment the counter for each goroutine you start.
        wg.Add(1)

        go func(j int) {

            // Make sure the wait group counter is decremented when the goroutine exits
            defer wg.Done()

            address := fmt.Sprintf("127.0.0.1:%d", j)
            conn, err := net.DialTimeout("tcp", address, 2 * time.Second)
            if err != nil {
               return
            }
            conn.Close()
            fmt.Printf("%d open\n", j)
        }(i)
    }

    // Wait for all goroutines to finish before exiting main
    wg.Wait()
}

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

Это требует лучшей обработки ошибок, но делает свое дело

package main

import (
    "fmt"
    "log"
    "net"
    "sync"
    "time"
)

var minPort = 1
var maxPort = 65535
var timeout = 2 * time.Second
const parallel = 50

func main(){
    fmt.Println("portscan called")

    // Create a buffered channel with a size equal to the number of goroutines
    ctrl := make(chan int, parallel)

    // Keep track of the currently active goroutines
    var wg sync.WaitGroup

    for p := 1; p <= parallel; p++ {
        wg.Add(1)

        // Start a goroutine...
        go func(p int) {
            log.Printf("Starting goroutine %d", p)

            // ...listening to the control channel.
            // For every value this goroutine reads from the
            // channel...
            for i := range ctrl {
                address := fmt.Sprintf("127.0.0.1:%d", i)

                // ...try to conncet to the port.
                conn, err := net.DialTimeout("tcp", address, timeout)
                if err == nil {
                    conn.Close()
                    log.Printf("[%3d]: %5d open", p, i)
                }
                // TBD: ERROR HANDLING!!!
            }

            // If the channel is closed, this goroutine is done.
            wg.Done()
            log.Printf("[%3d]: Exiting", p)
        }(p)
    }

    // Fill the control channel with values.
    // If the channel is full, the write operation
    // to the channel will block until one of the goroutines
    // reads a value from it.
    for i := minPort; i <= maxPort; i++ {
        ctrl <- i
    }
    // We have sent all values, so the channel can be closed.
    // The goroutines will finish their current connection attempt,
    // notice that the channel is closed and will in turn call wg.Done().
    close(ctrl)

    // When all goroutines have announced that they are done, we can exit.
    wg.Wait()
}
person Markus W Mahlberg    schedule 31.05.2020
comment
Я запустил приведенный выше код, и он по-прежнему не может правильно определить открытые порты при сканировании от 1 до 9000. Кажется, это работает, если я уменьшу его до меньшего диапазона. - person athul.sure; 31.05.2020
comment
Запустите его с помощью отладчика или зарегистрируйте ошибки: поскольку TCP-соединение обычно требует открытия двух файловых дескрипторов, у вас, скорее всего, закончатся доступные файловые дескрипторы. Я работаю над решением, которое будет соответствующим образом разбито. - person Markus W Mahlberg; 31.05.2020