我正在构建端口扫描程序,以检查远程计算机上的某些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秒运行一次)
打开1000个goroutine时使用三向摇杆的前10 cpu成本(持续时间:60s,总样本= 780ms(1.30%)):
打开1000个goroutine时使用tcp syn端口扫描的前10个cpu成本(持续时间:60s,总样本= 30.51s(50.85%)):
我想知道的是:
1。为什么conn.Read(bytes)读取每个goroutine中的所有响应包?net.Dial(“ ip4:tcp”,targetip)是否正确?
2。是否有一种低成本的方式来进行周期性的端口扫描(每60秒一次)而无需进行三向摇动
答案 0 :(得分:0)
快速解答:
conn
。 net.Dial("ip4:tcp", targetip)
是正确的local:port remote:port
中重新使用本地临时端口关于1的注释:
关闭conn
可能不是最好的主意,应该执行以上2
以获得这些好处:
TCP simultaneous connect