【发布时间】:2017-04-25 01:50:13
【问题描述】:
我正在构建一个网络设备。我需要支持 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);
}
谢谢!
编辑:在下面添加单元测试代码
#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