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

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

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

Это 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(&source, 0, sizeof(source));
    source.sin_addr.s_addr = iph->saddr;
    memset(&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
            && (previous_packets[i].daddr == iph->daddr) // Same destination Ip address
            && (previous_packets[i].protocol == iph->protocol) //Same protocol
            && (temphdrlen == iphdrlen)) // Same header length
        {
            // Then check TCP header
            if((previous_tcp[i].source == tcph->source) // Same source port
                && (previous_tcp[i].dest == tcph->dest) // Same destination port
                && (previous_tcp[i].th_seq == tcph->th_seq) // Same sequence number
                && (previous_tcp[i].th_ack==tcph->th_ack) // Same acknowledge number
                && (previous_tcp[i].th_win == tcph->th_win) // Same window
                && (previous_tcp[i].th_flags == tcph->th_flags) // Same flags
                && (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
                        && (previous_packets[z].saddr ==iph->daddr) // Same for IP dest addreses
                        && (previous_packets[z].protocol == iph->protocol)) // Same protocol
                    {
                        if((previous_tcp[z].dest==tcph->source) // Swapped ports
                            && (previous_tcp[z].source==tcph->dest)
                            && (previous_tcp[z].th_seq-1 != tcph->th_ack) // Not Keepalive
                            && (tcph->syn==1          // Either SYN is set
                                || tcph->fin==1       // Either FIN is set
                                || (segmentlength>0)) // Either segmentlength >0 
                            && (previous_tcp[z].th_seq>tcph->th_seq) // Next sequence number is 
                                                                     // bigger than the expected 
                            && (previous_tcp[z].ack  != 1))  // Last seen ACK is set
                        {
                            retransmission = 1;
                            retransmissions++;
                            break;
                        }
                    }
                }
            }
        }
    }
    
    if (retransmission == 1)
    {
        printf("Retransmission: True\n");
        printf("\n\n******************IPv4 TCP Packet*************************\n"); 
        printf("   |-IP Version       : %d\n",(unsigned int)iph->version);
        printf("   |-Source IP        : %s\n" , inet_ntoa(source.sin_addr) );
        printf("   |-Destination IP   : %s\n" , inet_ntoa(dest.sin_addr) );
        printf("   |-Source Port      : %u\n",  ntohs(tcph->source));
        printf("   |-Destination Port : %u\n",  ntohs(tcph->dest));
        printf("   |-Protocol         : %d\n",(unsigned int)iph->protocol);
        printf("   |-IP Header Length : %d DWORDS or %d Bytes\n",
(unsigned int)iph->ihl,((unsigned int)(iph->ihl))*4);
        printf("   |-Payload Length   : %d Bytes\n",Size - header_size);
        
    }
    printf("Total Retransmissions: %d\n",retransmissions);
}

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

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


person Konstantinos Zafeiris    schedule 10.12.2020    source источник
comment
Я разместил часть того же кода по другому из моих вопросов, но для другой не имеющей отношения к делу проблемы, с которой я столкнулся до того, как столкнулся с этой.   -  person Konstantinos Zafeiris    schedule 10.12.2020
comment
Это повторная передача, если она начинается с повторения предыдущего пакета. Нет флага для повторной передачи, вы должны сопоставить его с предыдущими пакетами.   -  person user253751    schedule 10.12.2020
comment
@user253751 user253751 Да, это то, что я делаю в своем коде, но есть ошибка в том, как я проверяю повторную передачу.   -  person Konstantinos Zafeiris    schedule 10.12.2020
comment
Я рекомендую сделать меньший файл pcap с повторной передачей, которой вы не соответствуете, и использовать gdb, чтобы найти, почему вы не соответствуете ему.   -  person Ôrel    schedule 10.12.2020
comment
Я не думаю, что порядковый номер, окно, флаги и т. д. должны быть идентичными при повторной передаче. Отправителю разрешается добавлять дополнительные данные к повторной передаче, разбивать пакеты на более мелкие и т. д. Повторно передаются байты, а не пакеты. Байты идентифицируются на основе их порядковых номеров.   -  person user253751    schedule 10.12.2020
comment
(Источник: tools.ietf.org/html/rfc793 стр. 42-43 говорит: отправка TCP... может переупаковывать сегменты в очереди повторной передачи)   -  person user253751    schedule 10.12.2020


Ответы (2)


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

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

Установите, если выполняются все следующие условия:

  • Это не пакет проверки активности.
  • В прямом направлении длина сегмента больше нуля или установлен флаг 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. Это требует, чтобы диссектор хранил некоторую дополнительную информацию о состоянии и память, но позволяет гораздо лучше обнаруживать интересные события TCP, такие как повторные передачи. Это позволяет гораздо лучше и точнее измерять потери пакетов и повторные передачи, чем это доступно в любом другом анализаторе протоколов. (Но это все еще не идеально)

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

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

  • Повторная передача TCP — происходит, когда отправитель повторно передает пакет после истечения срока подтверждения.

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

...

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

person ralf htp    schedule 13.12.2020
comment
Спасибо за ваш ответ, который предоставил дополнительную информацию о том, как искать ретрансляции. Если я найду что-нибудь новое, я попытаюсь реализовать это в своем коде и посмотрю, изменился ли результат. - person Konstantinos Zafeiris; 13.12.2020

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

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

Получатель, когда он хочет подтвердить байт, он просто отправляет ACK с порядковым номером байта +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 является повторной передачей только тогда, когда он несет байты с идентификаторами, которые были отправлены ранее. Но, как было сказано ранее, это может быть не так, поскольку сегмент может содержать несколько байтов, отправленных ранее, плюс еще больше никогда не отправленных, следовательно, это смесь повторных передач и новых передач.

person caveman    schedule 16.12.2020
comment
Спасибо за Ваш ответ. Это пролило некоторый свет на TCP-связь. Теперь я думаю, что полностью понимаю, почему мы никогда не можем быть на 100% уверены в повторных передачах, которые мы фиксируем. - person Konstantinos Zafeiris; 18.12.2020
comment
Пожалуйста. Однако не уверен, что я сказал что-то, что подразумевало бы это. Все, что я сказал, это то, что каждый байт имеет уникальный идентификатор, и если этот уникальный идентификатор появляется снова, это означает, что он передается повторно. Я только что сказал, что данный сегмент TCP может содержать как повторно переданные байты, так и новые байты. Если я не упускаю из виду более глубокое значение, которого я не вижу, я не понимаю, как это означает, что повторные передачи не могут быть обнаружены на 100%. - person caveman; 18.12.2020
comment
Что ж, после попытки понять ваш ответ я попытался реализовать в качестве фильтра только те условия, которые вы указали в своем ответе. На самом деле часть 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-Retransmissions, но я также получил Dup-ACK и пакеты Out-Of-Order. Итак, я имел в виду в своем комментарии, что с этим фильтром вы получаете повторные передачи, но другие вещи также проходят. - person Konstantinos Zafeiris; 19.12.2020