Networking 更新碎片数据包中的UDP校验和

Networking 更新碎片数据包中的UDP校验和,networking,udp,checksum,nat,ip-fragmentation,Networking,Udp,Checksum,Nat,Ip Fragmentation,我正在建造一个网络设备。我需要支持NAT和IP数据包碎片。当我更改UDP数据包的源地址或目标地址时,我还必须纠正UDP校验和和IP校验和,但这很简单。当数据包被分割时,我必须收集所有的片段来重新计算校验和。我知道旧地址和新地址。我想: 取消对校验和的求反 减去旧地址 添加新地址 重新求和并取反 这个过程并不总是有效的。有没有办法更新校验和,而不是从头开始重新计算 我试过: long CalcCheckSumAdd(unsigned char *pbHeader, int iSize, long

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

取消对校验和的求反 减去旧地址 添加新地址 重新求和并取反 这个过程并不总是有效的。有没有办法更新校验和,而不是从头开始重新计算

我试过:

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);

}
谢谢

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


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

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校验和。

你说得对,上面的解决方案只在某些情况下有效,但我有一个新的implem,它可以处理所有类型的数据包碎片,不管是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;

}

碎片和重组应发生在第3层,并对第4层透明。我真的不认为需要重新计算UDP校验和由于碎片。当数据包在退出接口处退出路由器时,NAT之后应该发生碎片,并且第4层校验和应该已经重新计算和更新。@RonMaupin我的设备接收碎片化的UDP数据包。然后,它必须拒绝它。如果不重新计算/更新UDP校验和,则无法更改IP/UDP数据包的源地址或目标地址,因为UDP校验和包含包含IP源地址和目标地址的sudo标头。路由器也必须做同样的事情。路由器不重新组装碎片数据包,这是接收主机的工作。目前的趋势是,路由器和防火墙被配置为除了第一个片段之外甚至不接受数据包片段。这来自RFC 791:基本互联网服务是面向数据报的,并在网关上提供数据报片段,重新组装在目标主机的目标internet协议模块上进行。@RonMaupin我没有尝试重新组装数据包。我正在尝试更改数据包的源地址。由于数据包是IP/UDP,我需要重新计算UDP校验和。为了重新计算UDP校验和,我需要所有的片段,我想避免收集片段,只是做一些数学运算来更新校验和,而不是重新计算它。大多数路由器会将ICMP消息发送回主机,以进行基于TCP的分段。对于UDP,路由器不需要做很多事情来支持分段,除非它们正在进行NAT。Cisco 2911支持IP/UDP分段。感谢您的尝试,但您的解决方案不起作用。问题是它没有考虑在所有单词相加后如何减少校验和。如果您想测试其他想法,我已经添加了一些单元测试代码。谢谢根据数据包大小/内容,这种方法可以工作。这并不总是有效的。
/* 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;
}