【问题标题】:Recalculate TCP checksum重新计算 TCP 校验和
【发布时间】:2018-03-28 09:15:03
【问题描述】:

我正在编写一个用于 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

任何建议都会很棒!

【问题讨论】:

  • 请显示变量的声明以及它们的类型定义。
  • @Acorn:我已经继续添加变量声明及其类型定义。我们正在使用的所有内容都是未签名的,而且大部分时间都很短。
  • @rustyx:如果我没记错的话,我确实需要担心算术过程中带有“环绕”值的进位。我肯定会很清楚,因为它肯定不一致。
  • @rustyx:我将 pdiff 声明为带符号的 short 并删除了 -1 和 +1 if 语句,并且我的校验和值仍然经常不正确 1。在这种情况下: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

标签: c


【解决方案1】:
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 标头的更改。

【讨论】:

    【解决方案2】:

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

    在 2 的补码机器上,1 的补码和必须是 通过“结束进位”计算,即任何溢出 从最高有效位被添加到最低 有效位。

    所以你应该在更新校验和时“反转”“结束进位”。

    即每个负进位减 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;
    

    【讨论】:

    • 经过一些测试,我相信这非常有效:)
    • 经过进一步测试,您的弃牌似乎没有考虑负面进位,只考虑正面。
    • 我能够使用您的代码来制定可行的解决方案。折叠必须在两次减法之后和两次加法之后分别进行,以解释之后产生的正负进位。
    【解决方案3】:

    TCP/IP 校验和使用 1 的补码算法,类似于 2 的补码加进位反馈。 IE。如果将两个 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); }

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-01-04
      • 1970-01-01
      • 2016-03-09
      • 1970-01-01
      • 2018-02-09
      • 2019-01-30
      • 2012-10-08
      • 2015-01-30
      相关资源
      最近更新 更多