5. IP协议的实现
IP数据包的格式如下表所示:
0VersionHeader lengthType of ServiceTotal Length
32IdentificationFlagsFragment Offset
64Time to LiveProtocolHeader Checksum
96Source Address
128Destination Address
160Options
160 or 192+Data
IP协议的简化:IP协议在网络中主要完成路由选择和网络分段的功能。起始Bit 0-3表示版本号,对IPv4来说取值为4即0100即可。Header length域指明IP数据包header的长度(不包括数据Data域),以四字节为单位,因为Options域是可选的所以IP Header的长度并不固定。我们不使用Option域,所以取最小值5,表示Header长度为20字节。服务类型域(Type of Service, TOS)是为特殊的应用如VoIP等保留的,我们不使用,赋值为零即可。接下来2个字节的Total Length域表示整个数据包的长度,包括Header和Data,以字节为单位。 标识域(Identification)用来给数据包一个唯一的编号,用于验证和跟踪等,我们不使用,直接赋值为零即可。Flags和Offset用于分段包的重组,我们不使用,把Flags的第2位设为1表示是不可分段的,Offset赋值为零即可。生存时间(Time to Live, TTL)表示该数据包在网络上的有效期,我们简单的把它设为最大值0xFF即可。协议域(Protocol)表示传输层使用什么协议,RFC790文档为每个协议都规定了唯一的编号,如UDP编号为17。Header Checksum为Header区域的校验和,在校验之前该域初始为0,然后计算整个头部的校验和,把结果存放在该域,计算校验的方法是把头部看成以16位为单位的数字组成,依次进行二进制反码求和。接下来的八个字节是源IP地址和目的IP地址,没什么可说的。
综上所述,我们只保留了IP协议中必须的关键字段,因而简化了设计,对IP数据包进行填充的代码段如下:
p->ver_ihl = 0x45; // 1 Byte
p->tos = 0x00; // 1 Byte
p->tlen = htons(len); // 2 Byte
p->identification = htons(0x00); // 2 Byte
p->flags_fo = htons(0x4000); // 2 Byte
p->ttl = 0xFF; // 1 Byte
p->proto = 17; // 1 Byte, 17 for UDP
p->ip_src = htonl(NetOurIP); // 4 Byte
p->ip_dest = htonl(NetServerIP); // 4 Byte
p->crc = 0x0; // 2 Byte, To be
p->crc = checksum( buf, sizeof(struct ip_header) );
CheckSum 校验和:
IP,TCP,UDP等许多协议的头部都设置了校验和项,它们采用的算法是一样的,将被校验的数据按16位进行划分(若数据字节长度为奇数,则在数据尾部补一个字节0),对每16位求反码和,然后再对和取反码。 代码如下:
{
unsigned long sum = 0;
unsigned short *p = (unsigned short *)ptr;
while (len > 1)
{
sum += *p++;
len -= 2;
}
if(len == 1)
sum += *(unsigned char *)p;
while(sum>>16)
sum = (sum&0xffff) + (sum>>16);
return (unsigned short)((~sum)&0xffff);
}