The checksum field is the 16 bit one’s complement of the one’s complement sum of all 16-bit words in the header and text. If a segment contains an odd number of header and text octets to be checksummed, the last octet is padded on the right with zeros to form a 16-bit word for checksum purposes. The pad is not transmitted as part of the segment. While computing the checksum, the checksum field itself is replaced with zeros.
1.Adjacent octets to be checksummed are paired to form 16-bit integers, and the 1’s complement sum of these 16-bit integers is formed.
2.To generate a checksum, the checksum field itself is cleared, the 16-bit 1’s complement sum is computed over the octets concerned, and the 1’s complement of this sum is placed in the checksum field.
3.To check a checksum, the 1’s complement sum is computed over the same set of octets, including the checksum field. If the result is all 1 bits (-0 in 1’s complement arithmetic), the check succeeds.
可以看到,TCP校验和、IP校验和的计算方法是基本一致的,除了计算的范围不同。
实现
基于2.6.18、x86_64。
csum_tcpudp_nofold()按4字节累加伪首部到sum中。
123456789101112
static inline unsigned long csum_tcpudp_nofold (unsigned long saddr, unsigned long daddr,
unsigned short len, unsigned short proto,
unsigned int sum)
{
asm("addl %1, %0\n" /* 累加daddr */
"adcl %2, %0\n" /* 累加saddr */
"adcl %3, %0\n" /* 累加len(2字节), proto, 0*/
"adcl $0, %0\n" /*加上进位 */
: "=r" (sum)
: "g" (daddr), "g" (saddr), "g" ((ntohs(len) << 16) + proto*256), "0" (sum));
return sum;
}
csum_tcpudp_magic()产生最终的校验和。
首先,按4字节累加伪首部到sum中。
其次,累加sum的低16位、sum的高16位,并且对累加的结果取反。
最后,截取sum的高16位,作为校验和。
123456789101112131415161718192021222324252627
static inline unsigned short int csum_tcpudp_magic(unsigned long saddr, unsigned long daddr,
unsigned short len, unsigned short proto,
unsigned int sum)
{
return csum_fold(csum_tcpudp_nofold(saddr, daddr, len, proto, sum));
}
static inline unsigned int csum_fold(unsigned int sum)
{
__asm__(
"addl %1, %0\n"
"adcl 0xffff, %0"
: "=r" (sum)
: "r" (sum << 16), "0" (sum & 0xffff0000)
/* 将sum的低16位,作为寄存器1的高16位,寄存器1的低16位补0。
* 将sum的高16位,作为寄存器0的高16位,寄存器0的低16位补0。
* 这样,addl %1, %0就累加了sum的高16位和低16位。
*
* 还要考虑进位。如果有进位,adcl 0xfff, %0为:0x1 + 0xffff + %0,寄存器0的高16位加1。
* 如果没有进位,adcl 0xffff, %0为:0xffff + %0,对寄存器0的高16位无影响。
*/
);
return (~sum) >> 16; /* 对sum取反,返回它的高16位,作为最终的校验和 */
}
/**
* csum_partial_copy_from_user - Copy and checksum from user space.
* @src: source address (user space)
* @dst: destination address
* @len: number of bytes to be copied.
* @isum: initial sum that is added into the result (32bit unfolded)
* @errp: set to -EFAULT for an bad source address.
*
* Returns an 32bit unfolded checksum of the buffer.
* src and dst are best aligned to 64bits.
*/
unsigned int csum_partial_copy_from_user(const unsigned char __user *src,
unsigned char *dst, int len, unsigned int isum, int *errp)
{
might_sleep();
*errp = 0;
if (likely(access_ok(VERIFY_READ, src, len))) {
/* Why 6, not 7? To handle odd addresses aligned we would need to do considerable
* complications to fix the checksum which is defined as an 16bit accumulator. The fix
* alignment code is primarily for performance compatibility with 32bit and that will handle
* odd addresses slowly too.
* 处理X010、X100、X110的起始地址。不处理X001,因为这会使复杂度大增加。
*/
if (unlikely((unsigned long)src & 6)) {
while (((unsigned long)src & 6) && len >= 2) {
__u16 val16;
*errp = __get_user(val16, (__u16 __user *)src);
if (*errp)
return isum;
*(__u16 *)dst = val16;
isum = add32_with_carry(isum, val16);
src += 2;
dst += 2;
len -= 2;
}
}
/* 计算函数是用纯汇编实现的,应该是因为效率吧 */
isum = csum_parial_copy_generic((__force void *)src, dst, len, isum, errp, NULL);
if (likely(*errp == 0))
return isum; /* 成功 */
}
*errp = -EFAULT;
memset(dst, 0, len);
return isum;
}