I'm building a port scanner to check if some TCP port at remote machine is open.
To improve performance, I just build and send a TCP SYN packet to remote port instead of doing full 3-way handshake.And if I receive SYN-ACK packet successfully,then that port will be considered as open.
Here is a part of my code:
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
....
}
}
There are not for loop and CHECK_IF_RESPONSE_MATCH_REQUEST statement at old code.But when I do stress test, I found it's necessary.
Let's say we'll test if port 80 is open at 66.220.146.94.I open 1000 goroutines to call above code.
goroutine1: ack-no=11111
goroutine2: ack-no=22222
goroutine2: ack-no=33333
...
Then I found that in every goroutine,the following statement
readnum, err := conn.Read(bytes)
responsePacket := parstTCPHeader(bytes[:readnum])
will read all response packet even the read packet do not match the syn packet sent at current goroutine.
For example, at goroutine1, I sent syn packet(ack-no=11111),and read from conn. Then I found the seq-no in read packet could be 11112,22223,33334... So I add a loop and some check logic at CHECK_IF_RESPONSE_MATCH_REQUEST.
But the loop read and check logic make cpu so high.
Here is a test result(I run it every 60 seconds)
Top 10 cpu cost using 3-way shakehand when open 1000 goroutine(Duration: 60s, Total samples = 780ms ( 1.30%)):
Top 10 cpu cost using tcp syn port scan when open 1000 goroutine(Duration: 60s, Total samples = 30.51s (50.85%)):
What I want to know is that:
1.Why conn.Read(bytes) read all response packets in every goroutine?Is net.Dial("ip4:tcp", targetip) correct?
2.Is there a lower-cost way to do periodic port scan(every 60 seconds) without doing 3-way shakehand