本文将提供真正可用的 TCP/IP 协议校验合合计算,即它不是众人理论上的 TCPIP 校验合计算方法,虽然我曾为此被坑的有点小小的头疼(开个小玩笑)
此代码摘要自本人的一个实验工程(astar-tun)故名思意这是一个 “tun/tap” 虚拟网卡层面(链路层)的一个小工程,它内部实现了一个super simple 专用于 “forwarding” 的 tcpip 协议栈(proto net stack),完整的协议栈;诸如:lwIP、uc/IP、TinyTCP、BSD / linux,win32。
下面为 tcpip 协议头的定义的,一般情况下 tcpip 头为 20 个字节,人们总会说 tcpip 至少 20 个字节,否则就不是 tcpip 协议,本人在此纠正这句话,tcpip 协议头是变长的,它可以是 0 个字节(虽然这不可能)这取决于协议头中 “th_off” 字段的值( “th_off * 4 = nTcpHeaderCount”)
#ifndef tcp_seq
#define tcp_seq tcp_seq
typedef volatile uint32_t tcp_seq;
#endif/*
* TCP header.
* Per RFC 793, September, 1981.
*/
typedef struct _tcphdr
{
uint16_t th_sport; /* source port */
uint16_t th_dport; /* destination port */
tcp_seq th_seq; /* sequence number */
tcp_seq th_ack; /* acknowledgement number */
uint8_t th_x2 : 4; /* (unused) */
uint8_t th_off : 4; /* data offset */
uint8_t th_flags;uint16_t th_win; /* window */
uint16_t th_sum; /* checksum */
uint16_t th_urp; /* urgent pointer */
} tcphdr;
上述代码是 “RFC 793” 定义的 “tcpip” 标准协议头,为 20 字节( “th_off” = 0x5),那么回到本文重心 tcpip 协议校验合究是连带着 payload(其实就是 tcp_segments) 一起 check,还是与 ip 协议相同只需要 check ip 协议头?tcpip 协议的设计者为了保证数据在“互联网”中传递的 payload 数据“正确与一致性”,电原子信号在“电缆或光缆”传输过程中可能会发生错误【光缆错误率会低很多】,或者中途的“交换机与路由器”出现一些问题,促使了 tcp 携带的 payload 数据出现故障,而这个问题几乎无时无刻不在发生。
int tcp_checksum(uint8_t* tcphdr, int tcplen, uint32_t* srcaddr, uint32_t* dstaddr)
{
uint8_t pseudoheader[12];
uint16_t checksum = 0;if (tcphdr != NULL && srcaddr && dstaddr)
{
memcpy(&pseudoheader[0], srcaddr, 4);
memcpy(&pseudoheader[4], dstaddr, 4);
pseudoheader[8] = 0; /* fill zeors */
pseudoheader[9] = IPPROTO_TCP;
memcpy(&pseudoheader[10], &tcplen, 2);uint8_t n = pseudoheader[10];
pseudoheader[10] = pseudoheader[11];
pseudoheader[11] = n;
checksum = ~tcpip_chksum(tcpip_chksum(0, pseudoheader, sizeof(pseudoheader)), tcphdr, tcplen);
}
return checksum;
}
tcplen = tcphdr + payload
tcp_checksum 除计算头部与载荷的值,还需要加上一个占“12”字节的 “tcp_psehdr” 伪头部,才可以(这个头里面其实没什么东西,就是标识了 srcaddr、dstaddr、prop、zeros(rsv)、tcplen 几个字段。
我们先把 pseudoheader 的 checksum 计算出来,然后在与 “tcphdr + payload” 的值,连接在一起最后“位取反(~)”就可以获取到正确的这个 tcpip packet 真正的 checksum。
int process_tcppacket_datapacket_ack(tcppcb* pcb, uint32_t cseq, uint32_t sseq)
{
if (pcb == NULL)
{
return 0;
}
uint32_t dstaddr = pcb->dstaddr;
uint32_t srcaddr = pcb->srcaddr;
uint16_t dstport = pcb->dstport;
uint16_t srcport = pcb->srcport;tcphdr tcpo;
tcpo.th_ack = __htonl(cseq);
tcpo.th_seq = __htonl(sseq);
tcpo.th_urp = 0;
tcpo.th_sum = 0;
tcpo.th_x2 = 0;
tcpo.th_flags = 0x10; // SETACK(tcpo.th_flags);
tcpo.th_off = 5;
tcpo.th_win = __htons(365);
tcpo.th_dport = __htons(srcport);
tcpo.th_sport = __htons(dstport);int packetlen = 20; // tcpo.th_off * 4
tcpo.th_sum = __htons(tcp_checksum((uint8_t*)&tcpo, packetlen, &dstaddr, &srcaddr));uint8_t* packet = ipv4_build_packet(dstaddr, srcaddr, 0x00, IPPROTO_TCP, (uint8_t*)&tcpo, &packetlen);
if (packet != NULL)
{
tun_write(tuntap, packet, packetlen);
__free(packet);
}
return 0;
}
上述代码是一从 “astar-tun” 中摘要的一段代码,它的作用主要用于返回 ack tcpip-packet 到 “tun/tap” 网卡中,上述代码直接展示了,你应当如何正确的调用 “tcp_checksum” 函数,下方列出 “tcp_checksum” 函数依赖的 “tcpip_chksum” 函数。
unsigned short tcpip_chksum(unsigned short initcksum, unsigned char* data, int datalen)
{
unsigned int checksum = initcksum;
bool odd = (datalen & 1) != 0 ? 1 : 0;
int index = 0;
if (odd)
{
datalen -= odd;
}
for (index = 0; index < datalen; index += 2)
{
checksum += ((unsigned long)data[index] << 8) + ((unsigned long)data[index + 1]);
}
if (odd)
{
checksum += ((unsigned long)data[index] << 8);
}
while (checksum >> 16)
{
checksum = (checksum & 0xFFFF) + (checksum >> 16);
}
return checksum;
}