【问题标题】:Update UDP checksum in fragmented packets更新分段数据包中的 UDP 校验和
【发布时间】:2017-04-25 01:50:13
【问题描述】:

我正在构建一个网络设备。我需要支持 NAT 和 IP 数据包分片。当我更改 UDP 数据包的源地址或目标地址时,我必须更正 UDP 校验和(以及 IP 校验和,但这很简单)。当数据包被分段时,我必须收集所有分段以重新计算校验和。我知道旧地址和新地址。我愿意:

  1. 取消否定校验和
  2. 减去旧地址
  3. 添加新地址
  4. 重新减少和并取反

此过程并不总是有效。有什么方法可以更新校验和而不是从头开始重新计算?

我试过了:

long CalcCheckSumAdd(unsigned char *pbHeader, int iSize, long lInitial){

    long lSum = lInitial;

    while (iSize > 1){

        lSum += *((unsigned short*)pbHeader);

        pbHeader += 2;

        iSize -= 2;

    }

    if (iSize > 0) lSum += *pbHeader;

    return lSum;

}

long CalcCheckSumSubract(unsigned char *pbHeader, int iSize, long lInitial){

    long lSum = lInitial;

    while (iSize > 1){

        lSum -= *((unsigned short*)pbHeader);

        pbHeader += 2;

        iSize -= 2;

    }

    if (iSize > 0) lSum -= *pbHeader;

    return lSum;

}

unsigned short CalcCheckSumFinish(long lSum){

    while (lSum >> 16){

        lSum = (lSum & 0xFFFF) + (lSum >> 16);

    }

    return (unsigned short)(~lSum);

}

long CalcCheckSumUnfinish(unsigned short usSum){

    // Can't totally undo lossy finish logic

    return ~usSum;

}

unsigned short CalcCheckSumUpdateAddress(unsigned short usOldSum, unsigned long ulOldAddress, unsigned long ulNewAddress){

    long lSumFixed = CalcCheckSumUnfinish(usOldSum);

    lSumFixed = CalcCheckSumSubract((unsigned char*)&ulOldAddress,sizeof(ulOldAddress),lSumFixed);

    lSumFixed = CalcCheckSumAdd((unsigned char*)&ulNewAddress,sizeof(ulNewAddress),lSumFixed);

    return CalcCheckSumFinish(lSumFixed);

}

谢谢!

编辑:在下面添加单元测试代码

#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

long CalcCheckSumAdd(unsigned char *pbHeader, int iSize, long lInitial){

    long lSum = lInitial;

    while (iSize > 1){

        lSum += *((unsigned short*)pbHeader);

        pbHeader += 2;

        iSize -= 2;

    }

    if (iSize > 0) lSum += *pbHeader;

    return lSum;

}

unsigned short CalcCheckSumFinish(long lSum){

    while (lSum >> 16){

        lSum = (lSum & 0xFFFF) + (lSum >> 16);

    }

    return (unsigned short)(~lSum);

}

void Randomize(unsigned char *pucPacket, unsigned long ulSize){

    for (unsigned long ulByte = 0; ulByte < ulSize; ulByte++){

        pucPacket[ulByte] = (unsigned char)(255 * rand() / RAND_MAX);

    }

}

unsigned short Calc(unsigned char *pucPacket, unsigned long ulSize){

    long lSum = CalcCheckSumAdd(pucPacket,ulSize,0);

    return CalcCheckSumFinish(lSum);

}

unsigned short Fix(unsigned short usOrig, unsigned int uiOld, unsigned int uiNew){

    // TODO: Replace this with something that makes main never fail
    usOrig -= uiOld & 0xffff;
    usOrig -= uiOld >> 16 & 0xffff;
    usOrig += uiNew & 0xffff;
    usOrig += uiNew >>16 & 0xffff;

    return usOrig;

}

void Break(unsigned char *pucPacket, unsigned int *puiOld, unsigned int *puiNew){

    unsigned int *puiChange = (unsigned int*)pucPacket;

    *puiOld = *puiChange;

    Randomize((unsigned char*)puiNew,sizeof(unsigned int));

    *puiChange = *puiNew;

}

void PrintBuffer(const char *szName, unsigned char *pucBuff, unsigned int uiSize){

    printf("%s: ",szName);

    for (unsigned int uiByte = 0; uiByte < uiSize; uiByte++){

        printf("%02X",(unsigned int)pucBuff[uiByte]);

    }

    printf("\n");

}

void PrintTestCase(unsigned char *pucOrig, unsigned char *pucChanged, unsigned int uiSize, unsigned short usOrig, unsigned short usChanged, unsigned short usFixed){

    PrintBuffer("Original Buffer",pucOrig,uiSize);
    PrintBuffer("Changed Buffer ",pucChanged,uiSize);

    printf("Orig    checksum: %04X\n",(unsigned int)usOrig);
    printf("Changed checksum: %04X\n",(unsigned int)usChanged);
    printf("Fixed   checksum: %04X\n",(unsigned int)usFixed);

}

int main(){

    srand((unsigned int)time(nullptr));

    unsigned char pucDataOrig[100];
    unsigned char pucDataChanged[100];

    bool bTestFailed = false;

    while (!bTestFailed){

        Randomize(pucDataOrig,sizeof(pucDataOrig));

        memcpy(pucDataChanged,pucDataOrig,sizeof(pucDataOrig));

        unsigned short usOrig = Calc(pucDataOrig,sizeof(pucDataOrig));

        unsigned int uiOld = 0,
                     uiNew = 0;

        Break(pucDataChanged,&uiOld,&uiNew);

        unsigned short usFixed = Fix(usOrig,uiOld,uiNew);

        unsigned short usChanged = Calc(pucDataChanged,sizeof(pucDataChanged));

        if (usChanged == usFixed){

            printf(".");

        }else{

            printf("\nTest case failed\n");
            PrintTestCase(pucDataOrig,pucDataChanged,sizeof(pucDataOrig),usOrig,usChanged,usFixed);

            bTestFailed = true;

        }

    }

    return 0;

}

【问题讨论】:

  • 碎片和重组应该发生在第 3 层,并且对第 4 层是透明的。由于碎片,我真的不认为需要重新计算 UDP 校验和。分片应该在 NAT 之后发生,因为数据包在出口接口处离开路由器,并且第 4 层校验和应该已经重新计算和更新。
  • @RonMaupin 我的设备收到分段的 UDP 数据包。然后它必须对其进行 NAT。您无法在不重新计算/更新 UDP 校验和的情况下更改 IP/UDP 数据包的源地址或目标地址,因为 UDP 校验和包含包含 IP 源地址和目标地址的 sudo-header。路由器必须做同样的事情。
  • 路由器不会重新组装分段的数据包,这是接收主机的工作。目前的趋势是,路由器和防火墙被配置为甚至不接受数据包片段(第一个片段除外)。
  • 这来自 RFC 791:“基本的 Internet 服务是面向数据报的,并在网关处提供数据报的分段,并在目标主机中的目标 Internet 协议模块处进行重组。 "
  • @RonMaupin 我没有尝试重新组装数据包。我正在尝试更改数据包的源地址。由于数据包是 IP/UDP,我需要重新计算 UDP 校验和。我需要所有片段来重新计算 UDP 校验和,我想避免需要收集片段,只需做一些数学运算来更新校验和而不是重新计算它。大多数路由器会将 ICMP 消息发送回主机以进行基于 TCP 的分段。对于 UDP,路由器不需要做太多的事情来支持分片(除非他们正在做 NAT)。我拥有的 Cisco 2911 支持 IP/UDP 分段。

标签: networking udp checksum nat ip-fragmentation


【解决方案1】:

你是对的,上面的解决方案只适用于某些情况,但我有一个适用于所有类型数据包(分段或不分段,UDP、TCP、IP)的新实现。这是实现:

/* incremental checksum update */
static inline void
cksum_update(uint16_t *csum, uint32_t from, uint32_t to)
{
    uint32_t sum, csum_c, from_c, res, res2, ret, ret2;

    csum_c = ~((uint32_t)*csum);
    from_c = ~from;
    res = csum_c + from_c;
    ret = res + (res < from_c);

   res2 = ret + to;
   ret2 = res2 + (res2 < to);

   sum = ret2;
   sum = (sum & 0xffff) + (sum >> 16);
   sum = (sum & 0xffff) + (sum >> 16);
   *csum = (uint16_t)~sum;

}

您现在可以在翻译数据包地址和发送之前使用此功能:

/* Update L4 checksums on all packet a part from [2nd, n] fragment */
switch (IS_FRAG(ipv4_hdr) ? 0 : ipv4_hdr->next_proto_id) {
case IPPROTO_TCP:
{
    struct tcp_hdr *tcp_hdr = tcp_header(pkt);

    /* Compute TCP checksum using incremental update */
    cksum_update(&tcp_hdr->cksum, old_ip_addr, *address);
    break;
}
case IPPROTO_UDPLITE:
case IPPROTO_UDP:
{
    struct udp_hdr *udp_hdr = udp_header(pkt);

    /* Compute UDP checksum using incremental update */
    cksum_update(&udp_hdr->dgram_cksum, old_ip_addr, *address);
    break;
}
default:
    break;
}

【讨论】:

  • 这个公式对你有用吗?我收到的结果与预期的不同也许我们有错误?你能帮忙吗?谢谢
【解决方案2】:

你必须减去旧的ip地址并在udp校验和上添加新的,这是伪代码:

udp_hdr->dgram_cksum -= old_ipv4_addr & 0xffff;
udp_hdr->dgram_cksum -= old_ipv4_addr >> 16 & 0xffff;
udp_hdr->dgram_cksum += new_ipv4_addr & 0xffff;
udp_hdr->dgram_cksum += new_ipv4_addr >>16 & 0xffff;

这应该处理 IP 片段上的 UDP 校验和。

【讨论】:

  • 感谢您的尝试,但您的解决方案不起作用。这样做的问题是它没有考虑在所有单词加起来之后校验和是如何减少的。如果您想测试任何其他想法,我已经添加了一些单元测试代码。谢谢!
  • 根据数据包大小/内容,这种方法可以工作。它并不总是有效。
猜你喜欢
  • 2015-08-31
  • 1970-01-01
  • 1970-01-01
  • 2013-08-29
  • 1970-01-01
  • 2010-12-01
  • 2014-06-15
  • 1970-01-01
  • 2015-01-22
相关资源
最近更新 更多