同时扫描多个端口时结果不可靠

Unreliable results when scanning multiple ports concurrently

背景:

我正在阅读 Black Hat Go,作者介绍了一个使用 go 例程的简单端口扫描器1

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)
    }
}

然后他提到了以下内容:

Scanning an excessive number of hosts or ports simultaneously may cause network or system limitations to skew your results.

为了测试它,我在端口 8000 和 8500 上启动了 2 个 php 服务器 2 和 运行 上面的代码来扫描我的本地端口。

每次都给我不一致的结果。有时它会检测到两个打开的端口,有时则不会。

问题:

  1. 是否由于 TCP 的某些限制导致结果不一致?

  2. 有没有办法计算出可以并行扫描的最佳端口数,从而使结果保持正确?

编辑:

我似乎在上面的代码中遗漏了等待组。

除此之外,是否有其他任何东西(OS 限制或协议限制)阻止在大型 运行ge 上进行并发端口扫描?

您的主函数将在 for 循环结束后立即退出。如果 main 函数退出,那么由它启动的所有 goroutines 也会退出。您需要等待 goroutines 完成。例如,这可以通过 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()
}

编辑: 对我来说,原来的代码由于缺少文件描述符而无法正常工作。以下功能可靠地工作。

它需要更好的错误处理,但确实有效

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()
}