Linux -- IPV4、IP、TCP、UDP数据报头的格式以及校验和算法

一、以下各个头文件所在的位置为

Ubuntu下目录/usr/include/linux/

Fedora下目录 /usr/src/kernels/2.6.35.6-45.fc14.i686/include/linux/

二、ip头部的结构体定义如下

#include <linux/ip.h>
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u8    ihl:4,
        version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
    __u8    version:4,
        ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif
    __u8    tos;
    __be16  tot_len;
    __be16  id;
    __be16  frag_off;
    __u8    ttl;
    __u8    protocol;
    __sum16 check;
    __be32  saddr;
    __be32  daddr;
    /*The options start here. */
}; 

结构体成员对应的结构如下图所示。

下面主要说明几个字段:

iphdr->ihl:首部长度(4bit),首部长度指的是IP头部占32 bit(4字节)字的数目(也就是IP头部包含多少个4字节),包括任何选项。由于它是一个4比特字段, 因此首部最长为60个字节。

iphdr->check:IP首部检验和字段(16位,2字节),只计算IP头部的的所有字段的校验和,它不对首部后面的数据进行计算。 

发送方:计算一份数据报的IP头部检验和,则需要首先把此检验和字段置为0。然后对首部中每个16 bit(2字节)进行二进制反码求和(整个首部看成是由一串16 bit的字组成),然后结果存在此检验和字段中。

接受方:当收到一份IP数据报后,对首部中每个16 bit(2字节)进行二进制反码的求和。由于接收方在计算过程中包含了发送方存在首部中的检验和,因此,如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该为全1。如果结果不是全1(即检验和错误),那么IP就丢弃收到的数据报。但是不生成差错报文,由上层去发现丢失的数据报并进行重传。

iphdr->tot_len:总长度字段(16位)是指整个IP数据报的长度(包含后面的协议头和数据),以字节为单位。

利用首部长度字段和总长度字段,就可以知道 IP数据报中数据内容的起始位置和长度。由于该字段长16比特,所以IP数据报最长可达65535字节,总长度字段是IP首部中必要的内容,因为一些数据链路(如以太网)需要填充一些数据以达到最小长度。尽管以太网的最小帧长为46字节,但是IP数据可能会更短。如果没有总长度字段,那么IP层就不知道46字节中有多少是IP数据报的内容。

三、tcp头部的结构体定义如下

#include <linux/tcp.h>
struct tcphdr {
    __be16  source;
    __be16  dest;
    __be32  seq;
    __be32  ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u16   res1:4, doff:4, fin:1, syn:1, rst:1,
            psh:1,  ack:1,  urg:1, ece:1, cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
    __u16   doff:4, res1:4, cwr:1, ece:1, urg:1,
            ack:1,  psh:1,  rst:1, syn:1, fin:1;
#else
#error  "Adjust your <asm/byteorder.h> defines"
#endif  
    __be16  window;
    __sum16 check;
    __be16  urg_ptr;
};

结构体成员对应的结构如下图所示。

tcphdr->doff:TCP头部长度,指明了在TCP头部包含多少个32位的字。

此信息是必须的,因为options域的长度是可变的,所以整个TCP头部的长度也是变化的。从技术上讲,这个域实际上指明了数据部分在段内部的其起始地址(以32位字作为单位进行计量),因为这个数值正好是按字为单位的TCP头部的长度,所以,二者的效果是等同的。

tcphdr->check:检验和,覆盖了整个的TCP报文段(包含后面的数据),这是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证。

四、udp头部的结构体定义如下

struct udphdr {        
    __be16  source;
    __be16  dest;
    __be16  len;
    __sum16 check;
};

udp协议很简单,其数据报由udp首部和用户数据两部分组成,其中首部只有8字节。

1、源端口号(Source Port):长度为16位(2字节),指明发送数据的进程的端口号。

2、目的端口号(Destination Port):长度为16位(2字节),指明目的主机接收数据的进程的端口号。

3、长度(Data Length):长度为16位(2字节),该字段值为udp报头和数据两部分的总字节数。

4、检验和(Checksum):长度为16位(2字节),udp检验和是udp报头和udp数据的所有数据的检验和。对报文中每个16 bit(2字节)进行二进制反码的求和。由发送端计算和存储,由接收端校验。

5、数据

五、Ip头和tcpudp头的数据校验和的算法函数

char setIpCheck(struct iphdr* iphdrp)
{
    iphdrp->check = 0;
    iphdrp->check = htons(checksum(0, (u_int8_t *)iphdrp, iphdrp->ihl << 2));
    return iphdrp->check == 0 ? -1 : 0;
}

char setTcpCheck(struct iphdr* iphdrp)
{
    struct tcphdr *tcphdrp = (struct tcphdr*)((u_int8_t*)iphdrp + (iphdrp->ihl<<2));
    tcphdrp->check = 0;
    
    size_t tcplen = ntohs(iphdrp->tot_len) - (iphdrp->ihl<<2); 
    u_int32_t cksum = 0;     
    cksum += ntohs((iphdrp->saddr >> 16) & 0x0000ffff);   
    cksum += ntohs(iphdrp->saddr & 0x0000ffff);   
    cksum += ntohs((iphdrp->daddr >> 16) & 0x0000ffff);
    cksum += ntohs(iphdrp->daddr & 0x0000ffff);
    cksum += iphdrp->protocol & 0x00ff;
    cksum += tcplen; 
    tcphdrp->check = htons(checksum(cksum, (u_int8_t*)tcphdrp, tcplen));

    return tcphdrp->check == 0 ? -1 : 0;
}

char setUdpCheck(struct iphdr* iphdrp)
{
    struct udphdr *udphdrp = (struct udphdr*)((u_int8_t*)iphdrp + (iphdrp->ihl<<2));
    udphdrp->check = 0;

    size_t udplen = ntohs(iphdrp->tot_len) - (iphdrp->ihl<<2); 
    u_int32_t cksum = 0;     
    cksum += ntohs((iphdrp->saddr >> 16) & 0x0000ffff);   
    cksum += ntohs(iphdrp->saddr & 0x0000ffff);   
    cksum += ntohs((iphdrp->daddr >> 16) & 0x0000ffff);
    cksum += ntohs(iphdrp->daddr & 0x0000ffff);
    cksum += iphdrp->protocol & 0x00ff;
    cksum += udplen; 
    udphdrp->check = htons(checksum(cksum, (u_int8_t*)udphdrp, udplen));

    return udphdrp->check == 0 ? -1 : 0;
}


static u_int16_t checksum(u_int32_t init, u_int8_t *addr, size_t count)
{
    /* Compute Internet Checksum for "count" bytes * beginning at location "addr". */ 
    u_int32_t sum = init; 
    while( count > 1 )
    { 
        /* This is the inner loop */ 
        sum += ntohs(* (u_int16_t*) addr);     
        addr += 2;     
        count -= 2; 
    }
    /* Add left-over byte, if any */ 
    if( count > 0 )      
        sum += ntohs(( *(u_int8_t *) addr ));
    /* Fold 32-bit sum to 16 bits */ 
    while (sum>>16)    
        sum = (sum & 0xffff) + (sum >> 16); 
    return (u_int16_t)~sum;
}

 

  • 39
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青椒*^_^*凤爪爪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值