首页 > 解决方案 > 如何在 C 中嗅探数据包时查找 TCP 重传

问题描述

我编写了一个简单的源文件,可以使用 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,我不明白为什么。

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

标签: ctcppcaplibpcapretransmit-timeout

解决方案


为了检测重传,您必须跟踪预期的序列号。如果序列号高于预期,则数据包是重传的( 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


推荐阅读