Как найти повторные передачи TCP при прослушивании пакетов в C

#c #tcp #pcap #libpcap #retransmit-timeout

#c #tcp #pcap #libpcap #повторная передача-тайм-аут

Вопрос:

Я написал простой исходный файл, который может читать файлы pcap, используя библиотеку libpcap на C. Я могу анализировать пакеты один за другим и анализировать их до определенного момента. Я хочу иметь возможность определить, является ли проанализированный мной TCP-пакет повторной передачей TCP или нет. После тщательного поиска в Интернете я пришел к выводу, что для этого мне нужно отслеживать поведение трафика, а это означает также анализ ранее полученных пакетов.

Чего я на самом деле хочу добиться, так это сделать на базовом уровне то, что tcp.analysis.retransmission делает фильтр в wireshark.

Это MRE, который считывает файл pcap и анализирует TCP-пакеты, отправленные по IPv4. Функция find_retransmissions — это место, где анализируется пакет.

 #include <pcap.h>
#include <stdio.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <net/ethernet.h>
#include <string.h>

void process_packet(u_char *,const struct pcap_pkthdr * , const u_char *);
void find_retransmissions(const u_char * , int );

int main()
{
    pcap_t *handle;
    char errbuff[PCAP_ERRBUF_SIZE];
    handle = pcap_open_offline("smallFlows.pcap", errbuff);
    pcap_loop(handle, -1, process_packet, NULL);
}

void process_packet(u_char *args,
                    const struct pcap_pkthdr * header,
                    const u_char *buffer)
{
    int size = header->len;
    struct ethhdr *eth = (struct ethhdr *)buffer;
    if(eth->h_proto == 8) //Check if IPv4
    {
        struct iphdr *iph = (struct iphdr*)(buffer  sizeof(struct ethhdr));
        if(iph->protocol == 6) //Check if TCP
        {
             find_retransmissions(buffer,size);
        }
    }
}
 
 void find_retransmissions(const u_char * Buffer, int Size)
{
    static struct iphdr  previous_packets[20000];
    static struct tcphdr  previous_tcp[20000];
    static int index = 0;
    static int retransmissions = 0;
    int retransmission = 0;
    
    struct sockaddr_in source,dest;
    unsigned short iphdrlen;
    
    // IP header
    struct iphdr *iph = (struct iphdr *)(Buffer    sizeof(struct ethhdr));
    previous_packets[index] = *iph;
    
    iphdrlen =iph->ihl*4;

    memset(amp;source, 0, sizeof(source));
    source.sin_addr.s_addr = iph->saddr;
    memset(amp;dest, 0, sizeof(dest));
    dest.sin_addr.s_addr = iph->daddr;

    // TCP header
    struct tcphdr *tcph=(struct tcphdr*)(Buffer 
                                    iphdrlen 
                                    sizeof(struct ethhdr));
    previous_tcp[index]=*tcph;
    index  ;
    
    int header_size =  sizeof(struct ethhdr)   iphdrlen   tcph->doff*4;
    unsigned int segmentlength;
    segmentlength = Size - header_size;
    
    /* First check if a same TCP packet has been received */
    for(int i=0;i<index-1;i  )
    {
        // Check if packet has been resent
        unsigned short temphdrlen;
        temphdrlen = previous_packets[i].ihl*4;
        
        // First check IP header
        if ((previous_packets[i].saddr == iph->saddr) // Same source IP address
            amp;amp; (previous_packets[i].daddr == iph->daddr) // Same destination Ip address
            amp;amp; (previous_packets[i].protocol == iph->protocol) //Same protocol
            amp;amp; (temphdrlen == iphdrlen)) // Same header length
        {
            // Then check TCP header
            if((previous_tcp[i].source == tcph->source) // Same source port
                amp;amp; (previous_tcp[i].dest == tcph->dest) // Same destination port
                amp;amp; (previous_tcp[i].th_seq == tcph->th_seq) // Same sequence number
                amp;amp; (previous_tcp[i].th_ack==tcph->th_ack) // Same acknowledge number
                amp;amp; (previous_tcp[i].th_win == tcph->th_win) // Same window
                amp;amp; (previous_tcp[i].th_flags == tcph->th_flags) // Same flags
                amp;amp; (tcph->syn==1 || tcph->fin==1 ||segmentlength>0)) // Check if SYN or FIN are
            {                                                        // set or if tcp.segment 0
                // At this point the packets are almost identical
                //  Now Check previous communication to check for retransmission
                for(int z=index-1;z>=0;z--)
                {   
                    // Find packets going to the reverse direction
                    if ((previous_packets[z].daddr == iph->saddr) // Swapped IP source addresses
                        amp;amp; (previous_packets[z].saddr ==iph->daddr) // Same for IP dest addreses
                        amp;amp; (previous_packets[z].protocol == iph->protocol)) // Same protocol
                    {
                        if((previous_tcp[z].dest==tcph->source) // Swapped ports
                            amp;amp; (previous_tcp[z].source==tcph->dest)
                            amp;amp; (previous_tcp[z].th_seq-1 != tcph->th_ack) // Not Keepalive
                            amp;amp; (tcph->syn==1          // Either SYN is set
                                || tcph->fin==1       // Either FIN is set
                                || (segmentlength>0)) // Either segmentlength >0 
                            amp;amp; (previous_tcp[z].th_seq>tcph->th_seq) // Next sequence number is 
                                                                     // bigger than the expected 
                            amp;amp; (previous_tcp[z].ack  != 1))  // Last seen ACK is set
                        {
                            retransmission = 1;
                            retransmissions  ;
                            break;
                        }
                    }
                }
            }
        }
    }
    
    if (retransmission == 1)
    {
        printf("Retransmission: Truen");
        printf("nn******************IPv4 TCP Packet*************************n"); 
        printf("   |-IP Version       : %dn",(unsigned int)iph->version);
        printf("   |-Source IP        : %sn" , inet_ntoa(source.sin_addr) );
        printf("   |-Destination IP   : %sn" , inet_ntoa(dest.sin_addr) );
        printf("   |-Source Port      : %un",  ntohs(tcph->source));
        printf("   |-Destination Port : %un",  ntohs(tcph->dest));
        printf("   |-Protocol         : %dn",(unsigned int)iph->protocol);
        printf("   |-IP Header Length : %d DWORDS or %d Bytesn",
(unsigned int)iph->ihl,((unsigned int)(iph->ihl))*4);
        printf("   |-Payload Length   : %d Bytesn",Size - header_size);
        
    }
    printf("Total Retransmissions: %dn",retransmissions);
}
 

Этот подход основан на параграфе wireshark wiki о повторной передаче. Я буквально щелкнул по каждой странице, которую Google может предложить о том, как подойти к этому анализу, но это было единственное, что я смог найти.
Результаты, которые я получаю, в некоторой степени верны, некоторые повторные передачи остаются незамеченными, я получаю много пакетов DUP-ACK, а также проходит некоторый обычный трафик (проверено с помощью wireshark). Я использую файл smallFlows.pcap, найденный здесь, и я считаю, что результаты, которые я должен получить, должны совпадать с tcp.analysis.retransmission amp;amp; not tcp.analysis.spurious_retransmission фильтром в wireshark. Что равносильно 88 повторным передачам для этого pcap.
Запуск этого кода дает 45, и я не могу понять, почему.

Извините за беспорядочные инструкции if, я изо всех сил старался их очистить.

Комментарии:

1. Я опубликовал часть того же кода по другому из моих вопросов, но для другой неразрешимой проблемы, с которой я столкнулся, до того, как столкнулся с этим.

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

3. Я рекомендую создать файл pcap меньшего размера с повторной передачей, который вам не соответствует, и использовать gdb, чтобы выяснить, почему вы ему не соответствуете

4. Я не думаю, что порядковый номер, окно, флаги и т.д. должны быть идентичны при повторной передаче. Отправителю разрешено добавлять дополнительные данные к повторной передаче, разделять пакеты на более мелкие и т. Д. Повторно передаются байты , а не пакеты. Байты идентифицируются на основе их порядковых номеров.

5. (Источник: tools.ietf.org/html/rfc793 на странице 42-43 написано: «Отправляющий TCP … может переупаковывать сегменты в очереди повторной передачи»)

Ответ №1:

Для обнаружения повторной передачи вы должны отслеживать ожидаемый порядковый номер. Если порядковый номер выше, чем ожидалось, пакет является повторно переданным (глава TCP Analysis документации wireshark, https://www.wireshark.org/docs/wsug_html_chunked/ChAdvTCPAnalysis.html )

Повторная передача TCP

Устанавливается, когда все из следующего истинно:

  • Это не пакет keepalive.
  • В прямом направлении длина сегмента больше нуля или установлен флаг SYN или FIN.
  • Следующий ожидаемый порядковый номер больше текущего порядкового номера

Помимо повторной передачи TCP, существует также ложная повторная передача TCP и быстрая повторная передача TCP

По сути, повторная передача необходима только в том случае, если пакет потерян. Анализ несоответствия потерянных сегментов :

введите описание изображения здесь

источник графики: http://www.opentextbooks.org.hk/ditatopic/3578

Для обнаружения этого типа сбоя в wireshark tcp.analysis.ack_lost_segment используется фильтр. Возможно, попробуйте реализовать это.

(https://serverfault.com/questions/626273/how-can-i-write-a-filter-to-get-tcp-sequence-number-inconsisten)

В wireshark можно применить несколько фильтров для выявления всех типов несоответствий в порядковых номерах, т.Е. tcp.analysis.retransmission , tcp.analysis.spurious_retransmission И tcp.analysis.fast_retransmission , для общего случая проверки потери пакетов на tcp.analysis.ack_lost_segment

https://superuser.com/questions/828294/how-can-i-get-the-actual-tcp-sequence-number-in-wireshark

По умолчанию Wireshark и TShark будут отслеживать все сеансы TCP и внедрять собственную сырую версию Sliding_Windows. Для этого требуется некоторая дополнительная информация о состоянии и память, которые должны храниться в dissector, но позволяет гораздо лучше обнаруживать интересные события TCP, такие как повторные передачи. Это позволяет намного лучше и точнее измерять потери пакетов и повторные передачи, чем доступно в любом другом анализаторе протоколов. (Но это все еще не идеально)

Эта функция не должна слишком сильно влиять на требования Wireshark к памяти во время выполнения, но при необходимости может быть отключена.

Когда эта функция включена, мониторинг скользящего окна внутри Wireshark обнаружит и запустит отображение интересных событий для TCP, таких как :

  • Повторная передача TCP — происходит, когда отправитель повторно передает пакет после истечения срока подтверждения.
  • Быстрая повторная передача TCP — происходит, когда отправитель повторно передает пакет до истечения таймера подтверждения. Отправители получают некоторые пакеты, порядковый номер которых больше, чем у подтвержденных пакетов. Отправители должны выполнить быструю повторную передачу при получении 3 повторяющихся подтверждений.

источник : https://gitlab.com/wireshark/wireshark/-/wikis/TCP_Analyze_Sequence_Numbers

Комментарии:

1. Спасибо за ваш ответ, предоставивший дополнительную информацию о том, как искать повторные передачи. Если я найду что-то новое, я попытаюсь реализовать это в своем коде и посмотреть, изменило ли это результат.

Ответ №2:

Концепция повторной передачи проста: данные, которые были отправлены, были отправлены снова.

В TCP каждый переданный байт имеет идентификатор. Если в сегменте TCP содержится 5 байт (просто гипотетический пример, на самом деле, конечно, все больше), то идентификатором первого сегмента является порядковый номер в заголовке TCP, 1 для 2-го сегмента, …, 4 для 5-го.

Получатель, когда он хочет подтвердить байт, он просто отправляет подтверждение с порядковым номером байта 1. Если получатель хочет подтвердить 5 байтов, как в нашем примере, он подтверждает 5-й байт, который есть seq_num 4 1 . В вашем случае вы выполняете это вычисление, чтобы получить следующий ожидаемый порядковый номер seq_num 4 1 .

Затем, чтобы определить, произошла ли повторная передача, вы просто знаете, отправил ли тот же источник сегмент TCP с порядковым номером, который ниже ожидаемого seq_num 4 1 .

Скажем, вместо того, чтобы получать seq_num 4 1 следующее переданное TCP-сообщение, вы получили seq_num . Это означает, что этот сегмент является повторной передачей предыдущего.

Но означает ли это, что этот сегмент TCP с повторной передачей содержит только повторные передачи? Нет. Он может содержать повторные передачи из предыдущего сегмента плюс дополнительные байты для следующего сегмента. Вот почему вам нужно подсчитать общее количество байтов в сегментах, чтобы определить, сколько байтов являются частью повторных передач, а сколько — частью новой передачи. Как вы видите, повторная передача TCP не является двоичной для каждого сегмента, но может перекрываться между сегментами. Потому что мы действительно повторно передаем байты. Мы просто храним байты в сегментах для уменьшения накладных расходов на заголовок TCP.

Теперь, что, если вы получили seq_num 2 1 ? Это немного странно, потому что это указывает на то, что предыдущий сегмент был передан только частично. Это в основном указывает на то, что он повторно передает только с байта 3. Если сегмент имеет только 3 байта, он повторно передает 3-й, 4-й и 5-й байты (т. Е. Только байты предыдущего сегмента). Но если он имеет, скажем, 10байт, это означает, что 6-й, 7-й, 8-й, 9-й и 10-й байты являются новыми байтами (не повторно переданными).

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

Комментарии:

1. Спасибо за ваш ответ. Это пролило некоторый свет на TCP-связь. Теперь я думаю, что полностью понимаю, почему мы никогда не можем быть на 100% уверены в повторных передачах, которые мы перехватываем.

2. Пожалуйста. Однако не уверен, сказал ли я что-нибудь, что подразумевало бы это. Все, что я сказал, это то, что каждый байт имеет уникальный идентификатор, и если этот уникальный идентификатор снова отображается, это означает, что он повторно передается. Я только что сказал, что данный сегмент TCP может содержать смесь повторно переданных байтов и новых байтов. Если я не упускаю более глубокое значение, которое я не вижу, я не понимаю, как это подразумевает, что повторные передачи не могут быть обнаружены на 100%.

3. После попытки понять ваш ответ я попытался реализовать в качестве фильтра только те условия, которые вы указали в своем ответе. На самом деле Say, instead of getting seq_num 4 1 in the next transmitted TCP message, you got seq_num. This means that the this segment is a re-transmission of the previous one. часть. В результате я получил, как вы сказали, большую часть повторных передач TCP, но я также получил дублирующие и неупорядоченные пакеты. Итак, что я имел в виду в своем комментарии, так это то, что с помощью этого фильтра вы получаете повторные передачи, но также проходят и другие вещи.