C/C++ TCP/IP 协议簇校验合计算方法(tcpip checksum)

    本文将提供真正可用的 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;
}

猜你喜欢

转载自blog.csdn.net/liulilittle/article/details/81200804
今日推荐