The IP checksum is the 16 bit one's complement of the one's complement sum of all 16 bit words in the header.
“1的补码”即反码。
IP只对首部而不对数据部分计算校验和,计算方法是将首部视为16位整数的序列,对首部所有16位整数各求反码,并将结果相加,再对求得的和计算一次二进制反码。
计算机是用二进制补码来表示整数的,下面是等价的算法。
下面以UDP计算伪头部校验和的函数为例:
/*------------------------------------------------------------------------
* udpcksum - compute a UDP pseudo-header checksum
*------------------------------------------------------------------------
*/
unsigned short
udpcksum(struct ep *pep, int len)
{
struct ip *pip = (struct ip *)pep->ep_data;
struct udp *pudp = (struct udp *)pip->ip_data;
unsigned short *sptr;
unsigned long ucksum;
int i;
ucksum = 0;
sptr = (unsigned short *) &pip->ip_src;
/* 2*IP_ALEN octets = IP_ALEN shorts... */
/* they are in net order. */
for (i=0; i<IP_ALEN; ++i)
ucksum += *sptr++;
sptr = (unsigned short *)pudp;
ucksum += hs2net(IPT_UDP + len);
if (len % 2) {
((char *)pudp)[len] = 0; /* pad */
len += 1; /* for the following division */
}
len >>= 1; /* convert to length in shorts */
for (i=0; i<len; ++i)
ucksum += *sptr++;
ucksum = (ucksum >> 16) + (ucksum & 0xffff);
ucksum += (ucksum >> 16);
return (short)(~ucksum & 0xffff);
}
其余校验和的算法是类似的。
Linux中用汇编语言加速校验和计算的代码:
static inline __sum16 ip_fast_csum(const void *iph, unsigned int ihl)
{
unsigned int sum;
asm volatile("movl (%1), %0 ;\n"
"subl $4, %2 ;\n"
"jbe 2f ;\n"
"addl 4(%1), %0 ;\n"
"adcl 8(%1), %0 ;\n"
"adcl 12(%1), %0;\n"
"1: adcl 16(%1), %0 ;\n"
"lea 4(%1), %1 ;\n"
"decl %2 ;\n"
"jne 1b ;\n"
"adcl $0, %0 ;\n"
"movl %0, %2 ;\n"
"shrl $16, %0 ;\n"
"addw %w2, %w0 ;\n"
"adcl $0, %0 ;\n"
"notl %0 ;\n"
"2: ;\n"
/* Since the input registers which are loaded with iph and ihl
are modified, we must also specify them as outputs, or gcc
will assume they contain their original values. */
: "=r" (sum), "=r" (iph), "=r" (ihl)
: "1" (iph), "2" (ihl)
: "memory");
return (__force __sum16)sum;
}