如何在 C 中嗅探数据包时查找 TCP 重传

Kon*_*ris 5 c tcp pcap libpcap retransmit-timeout

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

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

这是一个 MRE,它读取 pcap 文件并分析通过 IPv4 发送的 TCP 数据包。该功能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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)

此方法基于有关重新传输的wireshark wiki段落。我真的点击了谷歌提供的关于如何进行这种分析的每一页,但这是我唯一能找到的。我得到的结果有些正确,一些重传没有被注意到,我收到了很多 DUP-ACK 数据包,一些正常的流量也通过了(用wireshark检查)。我使用在这里找到的 smallFlows.pcap 文件,我相信我应该得到的结果应该与tcp.analysis.retransmission && not tcp.analysis.spurious_retransmissionwireshark中的过滤器相同。这相当于88此 pcap 的重传。运行此代码产生 45,我不明白为什么。

对不起,如果语句混乱,我尽力清理它们。

ral*_*htp 1

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