在C语言中计算IP和TCP头校验和

3
我正在编写一个简单的程序来发送/接收TCP数据包,并将其整合到一个更大的项目中。在校验和部分卡住了,我计算出来的数字与Wireshark中显示的校验和数字不匹配。
在校验和函数方面,我重新使用了Mike Muss的代码,如下所示:
static int
in_cksum(u_short *addr, int len)
{
    register int nleft = len;
    register u_short *w = addr;
    register int sum = 0;
    u_short answer = 0;

    /*
     * Our algorithm is simple, using a 32 bit accumulator (sum), we add
     * sequential 16 bit words to it, and at the end, fold back all the
     * carry bits from the top 16 bits into the lower 16 bits.
     */
    while (nleft > 1)  {
        sum += *w++;
        nleft -= 2;
    }

    /* mop up an odd byte, if necessary */
    if (nleft == 1) {
        *(u_char *)(&answer) = *(u_char *)w ;
        sum += answer;
    }

    /* add back carry outs from top 16 bits to low 16 bits */
    sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
    sum += (sum >> 16);         /* add carry */
    answer = ~sum;              /* truncate to 16 bits */
    return(answer);
}

我收到了一个数据包并将其存储在一个char buffer[2048]中。为了获取IP头,我执行以下操作:
struct iphdr* ip;
ip = (struct iphdr*) buffer;

从这里开始,我可以正确地读取信息,例如ip->protocolip->saddr等,甚至可以像Wireshark中显示的那样打印出正确的校验和。

printf("Print checksum = 0x%x\n",ntohs(ip->check));

然后我尝试使用上述函数计算校验和并将其打印出来:
printf("My calculated checksum =0x%x\n",in_cksum ((unsigned short*) ip, sizeof(struct iphdr)));

我得到的是"My calculated checksym = 0x0",看起来IP头部没有任何东西。我猜可能没有正确传递in_cksum函数参数,但我不确定如何修复或者是否还有其他问题。
下面解决了IP校验和问题。然而,当我尝试计算TCP校验和时遇到了类似的问题。以下是我获取TCP头部的方式:
tcp=(struct tcphdr*) (buffer+sizeof(struct iphdr);

在此之后,我可以再次正确地阅读有关TCP头部的信息,例如 tcp->sourcetcp->dest,甚至是 tcp->check
然后我尝试重新计算校验和,如下所示:
tcp->check=0;
tcp->check=in_cksum((unsigned short*)tcp, ntohs(ip->tot_length)-(4*ip->ihl));

我在这里得到的结果与之前打印出来的不同。我认为我的问题可能是我传递给cksum函数的长度,但我不太确定如何修复它。

请注意,对于关闭投票者来说,这个问题是关于IP校验和,而不是TCP校验和。 - undefined
@Alnitak 抱歉,我实际上有一个关于TCP校验和的问题,希望你能再次帮助。谢谢! - undefined
你应该将这个问题作为一个单独的问题提出,或者参考已经涉及到TCP的其他问题。注意:TCP校验和需要在“伪IP头”上计算校验和。 - undefined
1
也就是:tcp = (struct tcphdr *)(ip + 4 * ip->ihl) - 不要使用sizeof(struct iphdr) - undefined
感谢@alnitak的建议。我尝试了伪IP头部,离我想要的最终结果非常接近,但仍然存在问题。我会将它作为另一个问题发布。 - undefined
2个回答

6
IP校验和的计算不能包括包含该校验和的字段——该字段应使用零代替。从RFC 791中得知:
“校验和字段是标题中所有16位字的一位补码之和的16位一位补码。为了计算校验和,校验和字段的值为零。”
我没有亲自算过,但我怀疑在整个报头上重新计算校验和会导致计算的总和相互抵消,从而得到零。[我查看了维基百科-它也同意:“如果没有损坏,对整个IP头进行求和的结果应为零。”因此,当验证校验和时,零实际上是正确的结果]
请注意,IP头只能是4字节的倍数长,因此在这种特定情况下,您的填充代码是不必要的,尽管如果想要计算其他数据的通用一位补码校验和,则可能会很有用。
此外,您应将4 * ip->ihl传递而不是sizeof(struct iphdr)以考虑可能在标头中的任何IP选项,首先确保您的缓冲区至少有那么多字节。

感谢 @Alnitak 的回复。根据您提供的信息,我能够计算出正确的校验和。对于我的迟回,我表示抱歉。 - undefined

-2

sum += (sum >> 16); /* 加上进位 / answer = ~sum; / 截断为16位 */

如果你将进位加到低2字节并取反。

显然会得到零。


不,仅仅添加进位并不能得到零。真正得到零的是将最初计算出的校验和值包含在计算中。 - undefined
实际上,我对这个语句感到困惑。answer = ~sum; /* 截断为16位 */ - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接