【问题标题】:How to Find TCP Retransmissions while sniffing packets in C如何在 C 中嗅探数据包时查找 TCP 重传
【发布时间】:2021-03-22 02:35:18
【问题描述】:

我编写了一个简单的源文件,可以使用 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);
        }
    }
}
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检查)。我使用找到here 的smallFlows.pcap 文件,我相信我应该得到的结果应该与wireshark 中的tcp.analysis.retransmission &amp;&amp; not tcp.analysis.spurious_retransmission 过滤器相同。这相当于此 pcap 的 88 重传。 运行此代码产生 45,我不明白为什么。

抱歉,if 语句凌乱,我已尽力清理它们。

【问题讨论】:

  • 我已经在我的另一个问题上发布了相同代码的一部分,但是在遇到这个问题之前,我遇到了另一个不相关的问题。
  • 如果它以重复前一个数据包开始,则为重传。没有重传标志,你必须将它与之前的数据包匹配。
  • 我建议做一个较小的 pcap 文件重新传输你不匹配,并使用 gdb 找出你不匹配的原因
  • 我不认为序列号、窗口、标志等在重传中必须相同。发送方可以在重传中添加额外的数据,将数据包分成更小的数据包等。重传的是字节,而不是数据包。字节是根据它们的序列号来识别的。
  • (来源:tools.ietf.org/html/rfc793 第 42-43 页说:“发送 TCP ......可能会重新打包重传队列上的段”)

标签: c tcp pcap libpcap retransmit-timeout


【解决方案1】:

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

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

接收方,当它想要确认一个字节时,它只是发送一个字节序号+1的ACK。如果接收方想要确认我们示例中的 5 个字节,它会确认第 5 个字节,即seq_num + 4 + 1。在您的情况下,您执行此计算以获得下一个预期的序列号 seq_num + 4 + 1

然后,为了检测是否发生了重新传输,您只需知道同一源是否发送了一个序列号低于预期seq_num + 4 + 1 的 TCP 段。

比方说,在下一个传输的 TCP 消息中没有得到seq_num + 4 + 1,而是得到了seq_num。这意味着本段是前一段的重传。

但是这是否意味着这个带有重传的 TCP 段只包含重传?不可以。它可以包含前一段的重新传输,以及下一段的额外字节。这就是为什么您需要计算段中的总字节数以判断有多少字节是重新传输的一部分,以及有多少是新传输的一部分。如您所见,TCP 重传不是每个段的二进制,而是可以跨段重叠。因为我们真的是在重新传输字节。我们只是将字节存储在段中以减少 TCP 标头的开销。

现在,如果你得到seq_num + 2 + 1 怎么办?这有点奇怪,因为它表明前一个段仅部分重传。它基本上表明它只是从第 3 个字节开始重新传输。如果该段只有 3 个字节,它会重新传输第 3、4 和第 5 个字节(即只有前一个段的字节)。但如果它有 10 个字节,则意味着第 6、7、8、9 和 10 个字节是新字节(不重传)。

在我看来,您只能说 TCP 数据包只有在它携带带有之前发送的标识符的字节时才是重传。但如前所述,这可能不是真的,因为一个段可能包含一些较早发送的字节,加上更多从未发送过的字节,因此是重新传输和新传输之间的混合。

【讨论】:

  • 感谢您的回答。它揭示了 TCP 通信。我现在认为我完全理解为什么我们永远无法 100% 确定我们捕获的重传。
  • 不客气。但是,不确定我是否说了任何暗示。我所说的只是每个字节都有一个唯一标识符,如果再次看到该唯一标识符,则意味着它被重新传输。我刚刚说过,给定的 TCP 段可以混合有重新传输的字节和新的字节。除非我错过了我没有看到的更深层次的暗示,否则我看不出这意味着无法 100% 检测到重新传输。
  • 在尝试理解您的答案之后,我尝试仅将您在答案中陈述的条件实现为过滤器。实际上是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 重传,但我也得到了 Dup-ACK 和乱序数据包。所以我在评论中的意思是,使用此过滤器,您可以获得重传,但其他内容也可以通过。
【解决方案2】:

为了检测重传,您必须跟踪预期的序列号。如果序列号高于预期,则数据包是重传的(wireshark 文档的 TCP Analysis 章节, https://www.wireshark.org/docs/wsug_html_chunked/ChAdvTCPAnalysis.html)

TCP重传

当满足以下所有条件时设置:

  • 这不是保活数据包。
  • 在正向,段长度大于零或设置了 SYN 或 FIN 标志。
  • 下一个预期序号大于当前序号

除了TCP重传,还有TCP Spurious RetransmissionTCP Fast Retransmission

基本上,只有在包丢失时才需要重新传输。 分析丢失的段不一致:

图源: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

【讨论】:

  • 感谢您的回答,为如何搜索重传提供了一些额外的见解。如果我发现任何新的东西,我会尝试在我的代码中实现它,看看它是否改变了结果。
猜你喜欢
  • 2020-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-12
  • 2011-03-26
  • 2016-02-08
  • 1970-01-01
  • 2015-11-24
相关资源
最近更新 更多