Почему я читаю много ответных пакетов в каждой goroutine, когда я отправляю tcp shake package (SYN) во многие goroutines - PullRequest
0 голосов
/ 27 мая 2019

Я создаю сканер портов, чтобы проверить, открыт ли какой-либо порт TCP на удаленном компьютере.

Чтобы повысить производительность, я просто создаю и отправляю пакет TCP SYN на удаленный порт, а не делаю полный 3-способ рукопожатия. И если я получу пакет 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 процедур для вызова вышеуказанного кода.

goroutine1: ack-no=11111

goroutine2: ack-no=22222

goroutine2: ack-no=33333

...

Затем я обнаружил, что в каждой процедуре следующий оператор

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

будет читать весь ответный пакет, даже если пакет чтения не соответствует пакету syn, отправленному в текущей программе.

Например, на goroutine1 я отправил син пакет (ack-no = 11111) и прочитал из conn.Затем я обнаружил, что seq-no в пакете чтения может быть 11112,22223,33334 ... Поэтому я добавляю цикл и некоторую логику проверки в CHECK_IF_RESPONSE_MATCH_REQUEST.

Но логика чтения и проверки цикла делает процессор настолько высоким.

Вот результат теста (я запускаю его каждые 60 секунд)

cpu%-runseconds

Стоимость 10 лучших процессоров с использованием трехстороннего встряхивания, когдаopen 1000 goroutine (продолжительность: 60 с, общее количество выборок = 780 мс (1,30%)):

flame graph(3-way shakehand port scan)

Топ-10 процессорных ресурсов с использованием сканирования по tpp syn port при открытии1000 рутин (продолжительность: 60 с, общее количество образцов = 30,51 с (50,85%)):

flame graph(tcp syn port scan)

Что я хочу знать, это:

1.Почему conn.Read (байты) считывает все ответные пакеты в каждой процедуре? Является ли net.Dial ("ip4: tcp", targettip) правильным?

2.Есть более дешевый способвыполнять периодическое сканирование портов (каждые 60 секунд) без трехсторонней встряски

1 Ответ

0 голосов
/ 28 мая 2019

Быстрый ответ:

  1. закрыть conn в конце процедуры. net.Dial("ip4:tcp", targetip) правильно
  2. повторно использовать локальный эфемерный порт в парах сокетов local:port remote:port при создании пакета syn *

Примечания к 1:

Закрытие conn, возможно, не лучшая идея, следует сделать 2 выше для этих преимуществ:

  1. не тратит время процессора на закрытие локальных портов
  2. когда вы слишком быстро соединяете один и тот же адрес с одним и тем же адресом и позволяете ядру назначить локальный порт, стек ip ядра может на самом деле воспроизвести - для получения более подробной информации, копайтесь в этом ключевом слове TCP simultaneous connect
...