5

我编写了一个简单的源文件,可以使用 C 中的 libpcap 库读取 pcap 文件。我可以逐个解析数据包并分析它们。我希望能够推断出我解析的 TCP 数据包是否是 TCP 重传。在网上广泛搜索后,我得出结论,为此,我需要跟踪流量行为,这意味着还要分析以前收到的数据包。

我真正想要实现的是,在基本层面上,tcp.analysis.retransmission过滤器在wireshark中所做的事情。

这是一个读取 pcap 文件并分析通过 IPv4 发送的 TCP 数据包的 MRE。该函数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 wiki段落。我确实点击了谷歌必须提供的关于如何进行这种分析的每一页,但这是我唯一能找到的东西。我得到的结果有些正确,一些重传没有被注意到,我得到了很多 DUP-ACK 数据包,一些正常的流量也通过了(用wireshark检查)。我使用在这里找到的 smallFlows.pcap 文件,我相信我应该得到的结果应该与tcp.analysis.retransmission && not tcp.analysis.spurious_retransmissionwireshark 中的过滤器相同。这相当于88此 pcap 的重新传输。运行此代码会产生 45,我不明白为什么。

对不起,如果声明凌乱,我尽力清理它们。

4

2 回答 2

0

为了检测重传,您必须跟踪预期的序列号。如果序列号高于预期,则数据包是重传的( wireshark 文档的TCP 分析章节, 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_retransmissiontcp.analysis.fast_retransmissiontcp.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

于 2020-12-13T10:13:45.093 回答
0

重传的概念很简单:被发送的数据被再次发送。

在 TCP 中,每个传输的字节都有一个标识符。如果一个 TCP 段中有 5 个字节(只是一个假设的例子,实际情况当然更大),那么第一个段的标识符是 TCP 标头中的序列号,+1 表示第二个段,... , +4 为第 5 次。

接收方,当它想要确认一个字节时,它只是发送一个字节序号为+1的ACK。如果接收方想要确认我们示例中的 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 数据包只有在它携带带有之前发送的标识符的字节时才是重传。但如前所述,这可能不是真的,因为一个段可能包含一些较早发送的字节,加上更多从未发送过的字节,因此是重新传输和新传输之间的混合。

于 2020-12-16T15:40:52.557 回答