重新计算TCP校验和

时间:2018-03-28 09:15:04

标签: c

我正在编写用于DDoS缓解的用户空间应用程序,并实施某些缓解策略,我需要能够动态更改TCP选项以及数据包标头中的序列和确认号等内容。

计算校验和的通常方法是重新创建一个新的TCP伪头并迭代整个数据包来计算校验和,但还有另一种方法只计算改变后的字和做一个字之间的差异 - 补语减法。

我当前的代码似乎经常被1或2关闭。我怀疑这是一个问题,因为我没有正确处理携带/借用。我对如何解决这个问题一无所知:

unsigned short pseq1, pseq2, pseq3, pseq4
unsigned short sum1, sum2, sum3, prevcheck;
short pdiff1, pdiff2;

u_char *pkt_data;

prevcheck = (pkt_data[50] << 8) | pkt_data[51];

pseq1 = (pkt_data[38] << 8) | pkt_data[39]; 
pseq2 = (pkt_data[40] << 8) | pkt_data[41];

pkt_data[38] = ((seq_num - offsetResult) >> 24) & 0xFF;
pkt_data[39] = ((seq_num - offsetResult) >> 16) & 0xFF; 
pkt_data[40] = ((seq_num - offsetResult) >> 8) & 0xFF;
pkt_data[41] = (seq_num - offsetResult) & 0xFF;

pseq3 = (pkt_data[38] << 8) | pkt_data[39];
pseq4 = (pkt_data[40] << 8) | pkt_data[41];

pdiff1 = pseq1 - pseq3;

pdiff2 = pseq2 - pseq4;

sum1 = ~pdiff1 + ~pdiff2;

sum2 = ~sum1;

sum3 = sum2 + prevcheck; 

pkt_data[50] = (sum3 >> 8) & 0xFF; 
pkt_data[51] = sum3 & 0xFF;

在这个例子中:68 05 ca 57 94 05 60 73 5c d0 57 bf 08 00 45 00 00 2c 00 00 40 00 3f 06 bc a5 b9 aa 2a 6a 42 f9 58 19 00 50 ed 48 fc e4 57 e5 6e c0 f6 c8 60 12 72 10 fe f3 00 00 02 04 05 b4 00 00

生成的校验和为fef3时应为fef2

任何建议都会很棒!

3 个答案:

答案 0 :(得分:0)

TCP / IP校验和使用1的补码算法,类似于2的补码和进位反馈。即如果你添加两个16位值,并获得一个进位,你需要在总和上加1。

您的代码使用无符号16位整数(顺便说一下,我推荐使用fixed width integer types,而不是intshort,当大小很重要时,如本例所示),所以在添加它们时遗失了。

更好的方法是使用32位变量作为中间结果,然后反馈进位。例如:

uint16_t a, b; ... uint32_t sum = (uint32_t)a + (uint32_t)b; if (sum > 0x10000u) { sum = (sum >> 16) + (sum & 0xffff); }

答案 1 :(得分:0)

根据RFC 1071,校验和是使用16位1的补码和计算的。

  

在2的补码机器上,1的补码总和必须是           通过&#34;结束携带&#34;,即任何溢出来计算           从最重要的位添加到最少           重要的一点。

所以你应该&#34;反向&#34; &#34;结束携带&#34;更新校验和时。

即。每个负值进位减去1,每个正值进位加1。

这样的事情:

int32_t sum; // or just int, but make sure it's 32-bit or more
unsigned short pseq1, pseq2, pseq3, pseq4
unsigned short prevcheck;

u_char *pkt_data;

prevcheck = (pkt_data[50] << 8) | pkt_data[51];

pseq1 = (pkt_data[38] << 8) | pkt_data[39]; 
pseq2 = (pkt_data[40] << 8) | pkt_data[41];

pkt_data[38] = ((seq_num - offsetResult) >> 24) & 0xFF;
pkt_data[39] = ((seq_num - offsetResult) >> 16) & 0xFF; 
pkt_data[40] = ((seq_num - offsetResult) >> 8) & 0xFF;
pkt_data[41] = (seq_num - offsetResult) & 0xFF;

pseq3 = (pkt_data[38] << 8) | pkt_data[39];
pseq4 = (pkt_data[40] << 8) | pkt_data[41];

sum = ~prevcheck - pseq1 - pseq2;

while (sum >> 16)
    sum = (sum & 0xFFFF) + (sum >> 16); // "end around carry"

sum += pseq3 + pseq4;

while (sum >> 16)
    sum = (sum & 0xFFFF) + (sum >> 16); // "end around carry"

sum3 = (short)~sum;

pkt_data[50] = (sum3 >> 8) & 0xFF; 
pkt_data[51] = sum3 & 0xFF;

答案 2 :(得分:0)

u_int32_t sum;
u_int16_t oldSeq1, oldSeq2, newSeq1, newSeq2;
u_int16_t oldChecksum;

sum = ~oldChecksum - oldSeq1 - oldSeq2;

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

sum = sum + newSeq1 + newSeq2;

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

sum = (u_int16_t)~sum;

由于我的要求只是编辑序列或确认序列号,上面的代码是我使用所有建议的解决方案。这将考虑所有进位,因为折叠在每次算术运算后完成,而不是在结束时可能会扭曲结果。

只要您将短于16位的内容填充到16位,这对IP头或TCP标头的更改同样有效。