65.9K
CodeProject 正在变化。 阅读更多。
Home

32 位 UDP 校验和

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (12投票s)

2003年12月1日

3分钟阅读

viewsIcon

109452

关于使用 32 位整数进行校验和的文章。

引言

本文介绍了一个使用 32 位分组的 UPD/IP 数据包的校验和例程。

背景

请参阅 RFC 768 以了解 UDP 协议和 UDP 校验和。 特别是,你需要了解用于 UDP 校验和的伪头部。 请参阅 RFC 1071 以了解互联网校验和的理论。

我使用 winpcap (http://winpcap.polito.it) 来监视 UDP 数据流,我需要一个校验和例程,但我发现的所有示例都基于效率低下的 16 位字节分组。 然而,在 RFC 1071 中,我发现了三个关键原则,可以实现更高效的流程

首先,如果最终将结果折叠回 16 位字,则分组的大小无关紧要。 在 C 中,不容易检查整数溢出,因此你需要一个累加器,它可以容纳所有求和的溢出。 对于 32 位分组,累加器需要 64 位。 在过程结束时,你可以将 64 位折叠成 16 位以获得有效的校验和结果。

其次,如果您正在生成一个校验和以插入到数据包中,则字节顺序在最后之前无关紧要。 如果你只在接收端检查校验和,结果应该是 0xFFFF,所以在这种情况下,字节顺序根本无关紧要。 因此,您可以通过不为每个字节分组调用 ntohs() 来节省一些开销。

最后,零不影响校验和结果,因此用零填充剩余字节以形成长整数是可以的。

使用代码

要使用 UDPchecksum() 例程,您可以简单地将其粘贴到您的 winpcap 项目(或任何具有数据包捕获工具的项目)中,并重命名全局指针以匹配您选择的名称。 Winpcap 用 winpcap 标头信息以及包含捕获的数据包的缓冲区填充结构。 在我的版本中,这些指针是类的成员,但我为此示例删除了该详细信息。 在调用 UDPchecksum() 之前,它们必须指向包含标头和数据包的 winpcap 实体。 当然,你可以将指针设为私有并在函数调用中传递它们。

这个版本中有两个廉价(高效)的技巧

首先,校验和伪头部是通过修改 IP 头部创建的。 伪头部由以下元素组成

  • IP 源地址 = 4 字节
  • IP 目标地址 = 4 字节
  • 协议 = 2 字节
  • UDP 长度 = 2 字节

这些必须添加到校验和才能获得正确的结果。 由于 IP 源地址和目标地址以及协议字段是连续的并且紧接在 UDP 部分之前,我们已经有了一个良好的开端。 我只是将 UPD 长度字段复制到 IP 标头的 IP 校验和字段,并且我将 IP 生存时间字节置零。 现在,伪头部已完成并与 UDP 头部连续。 校验和过程从数据包的第 22 个字节开始,这是我刚刚组装的伪头部的开头。

其次,我通过用零替换紧接在 UDP 数据包之后的四个字节来解决当数据长度不是 4 个字节的倍数时该怎么做的问题。 这些将是以太网帧校验序列或以太网填充符。 同样,我不介意修改剩余的以太网字段以简化处理 UDP 数据包。 现在,如果在 UDP 数据包的末尾还剩一个字节要读取,则会读取三个零与其形成 32 位分组,并且对结果没有影响。

我的版本的返回值是 'true' 如果校验和结果正确。

#define UDP_LEN 38        // position of UDP Header UDP Length field
#define IP_LIVE 22        // position of IP Header Time to Live field
#define IP_CHECKSUM 24        // position of IP Header IP Checksum field

// pointer to the header filled by winpcap for each captured packet
struct pcap_pkthdr    *pHeader;
// pointer to the buffer containing the packet captured by winpcap
u_char            *pPkt_data;

////////////////////////////////////////////////////////////////////////////
// UDPchecksum routine
//   expects pHeader to point to the winpcap pcap_pkthdr structure
//   expects pPkt_data to point to the captured packet buffer
//   returns true if the UDP portion of the packet has a valid checksum

bool UDPchecksum()
{
    // a 64 bit accumulator
    _int64 qwSum = 0;
    int nSize;
    // a byte pointer to allow pointing anywhere in the buffer
    // pHeader->caplen doesn't include the ethernet 
    // FCS and filler (4+ bytes at end)
    unsigned char *pB = (unsigned char *)(pPkt_data + pHeader->caplen);
    // pDW is a pointer to dwords for grouping bytes
    // initialize to point to the FCS (or filler if packet length < 46) 
    // after UDP
    ULONG *pDW  = (ULONG *)(pB);

    // The 4+ bytes received after UDP are ethernet FCS and 
    // filler - ok to mangle.
    // Put 0's after UDP for groupings of leftover bytes < 4 at
    // the end of UDP
    // (adding one, two, or three bytes == 0 into checksum will not 
    // affect the result)
    *pDW = 0;
    // construct the pseudo header starting at 22
    // (IP Source & Dest Addresses are already in place)
    *(pPkt_data + IP_LIVE) = 0;
    *(pPkt_data + IP_CHECKSUM + 1) = *(pPkt_data + UDP_LEN + 1);
    *(pPkt_data + IP_CHECKSUM) = *(pPkt_data + UDP_LEN);

    // point pDW to beginning of pseudo header
    pB = (pPkt_data + 22);
    pDW = (ULONG *)(pB);

    // set the size of bytes to inspect to the size of the UDP portion
    // plus the pseudo header starting at the IP header 'Time to Live' byte.
    dwSize = pHeader->caplen - IP_LIVE;

    while( nSize > 0 )  {
          // Add DWORDs into the accumulator
        // This loop should be 'unfolded' to increase speed
        qwSum +=  (ULONG) *pDW++;
        nSize -= 4;
    }
    //  Fold 64-bit sum to 16 bits
    while (qwSum >> 16)
       qwSum = (qwSum & 0xffff) + (qwSum >> 16);
    
    // a correct checksum result is 0xFFFF (at the receiving end)
    return (qwSum == 0xFFFF);
}

历史

暂无更改!

© . All rights reserved.