为什么当我在许多goroutine中发送一个TCP震动数据包(SYN)时,我在每个goroutine中都读取了许多响应数据包

时间:2019-05-27 04:27:14

标签: sockets go networking tcp

我正在构建端口扫描程序,以检查远程计算机上的某些TCP端口是否打开。

为了提高性能,我只构建了TCP SYN数据包并将其发送到远程端口,而不是进行完整的三向握手。如果成功接收到SYN-ACK数据包,则该端口将被视为开放状态。

这是我的代码的一部分:

conn, _:= net.Dial("ip4:tcp", target)
tcpSynPacket := BuildTcpSynPacket() // here I build a tcp syn packet
conn.Write(tcpSynPacket.Marshal())
deadlineTime := time.NewTicker(time.Second * 2)
defer deadlineTime.Stop()
for {
    select {
case <-deadlineTime.C:
    return nil
default:
        bytes := make([]byte, 128)
        conn.SetReadDeadline(time.Now().Add(time.Millisecond * 200))
        readnum, err := conn.Read(bytes)
        responsePacket := parstTCPHeader(bytes[:readnum])
        matched := CHECK_IF_RESPONSE_MATCH_REQUEST(tcpSynPacket,responsePacket) // here I'll check if ack-no,src-port,dest-port in tcpSynPacket match seq-no,dest-port,src-port in response packet
        if !matched {
            // unmatched packets,may response for another routine
            continue
        }
        if responsePacket.rst_flg == 1 {
            // the port would be consider as close
            // build func return struct,and return
            ....
        }else {
            // the port would be consider as open
            // build func return struct,and return
            ....
        }
}

在旧代码中没有for循环和CHECK_IF_RESPONSE_MATCH_REQUEST语句。但是,当我进行压力测试时,我发现这很有必要。

假设我们将测试端口80是否在66.220.146.94处打开。我打开了1000个goroutines来调用上述代码。

goroutine1: ack-no=11111

goroutine2: ack-no=22222

goroutine2: ack-no=33333

...

然后我发现在每个goroutine中,以下语句

readnum, err := conn.Read(bytes)
responsePacket := parstTCPHeader(bytes[:readnum])

将读取所有响应数据包,即使读取的数据包与当前goroutine发送的syn数据包不匹配。

例如,在goroutine1,我发送了syn包(ack-no = 11111),并从conn中读取。然后我发现读取数据包中的seq-no可能是11112,22223,33334 ... 因此,我在CHECK_IF_RESPONSE_MATCH_REQUEST添加了一个循环和一些检查逻辑。

但是循环读取和检查逻辑使cpu如此之高。

这是测试结果(我每60秒运行一次)

cpu%-runseconds

打开1000个goroutine时使用三向摇杆的前10 cpu成本(持续时间:60s,总样本= 780ms(1.30%)):

flame graph(3-way shakehand port scan)

打开1000个goroutine时使用tcp syn端口扫描的前10个cpu成本(持续时间:60s,总样本= 30.51s(50.85%)):

flame graph(tcp syn port scan)

我想知道的是:

1。为什么conn.Read(bytes)读取每个goroutine中的所有响应包?net.Dial(“ ip4:tcp”,targetip)是否正确?

2。是否有一种低成本的方式来进行周期性的端口扫描(每60秒一次)而无需进行三向摇动

1 个答案:

答案 0 :(得分:0)

快速解答:

  1. 在例程结束时关闭connnet.Dial("ip4:tcp", targetip)是正确的
  2. 制作同步数据包时,请在套接字对local:port remote:port中重新使用本地临时端口

关于1的注释:

关闭conn可能不是最好的主意,应该执行以上2以获得这些好处:

  1. 不浪费CPU时间来关闭本地端口
  2. 当您对同一个地址的多个cons设置过快并让内核分配本地端口时,内核ip堆栈实际上可能会播放-有关更多详细信息,请挖掘此关键字TCP simultaneous connect