以太网的包分三部分组成,Ethernet header,IP header,content,首先是以太网头,
typedef struct
{
unsigned short dest_hwaddr[3];
unsigned short source_hwaddr[3];
unsigned short frame_type;
}ETH_HEADER;
三个部分分别表示,目的MAC,源MAC和帧类型。帧类型表示后面数据的类型,对于ARP请求和应答来说,该字段的值是0x0806,对于ip包来说应该是0x0800
接下来是IP头
typedef struct { unsigned short ver_len_tos; //高位字节包括版本和IHL;低位字节为服务类型 unsigned short total_length; //总长 unsigned short identifier; //标识 unsigned short fragmeng_info; //标志和分段偏移 unsigned short ttlandprotocol; //高位为生存时间;低位为协议 unsigned short header_cksum; //报头校验和 unsigned short source_ipaddr[2]; unsigned short dest_ipaddr[2]; }IP_HEADER;
各部分意思见注释
一个典型的IP头报文的结构如下图所示
按照从左到右依次递增的字节顺序,字节内部左为高位右为地位,解析时需要对此有很清楚的认识
1. Version, 4个bit,4带表IPV4,6代表IPV6和SIP,其他的数值不常用。
2. Header Length, 4个bit,其值为IP包头的长度。因为有option字段,所以要设置这个值。
3. ToS,现在所用的IP包的这个字段已经被DiffServ所代替,一共8个字节,前6个为DSCP(DiffServ Code Point),后两个为ECN。
4.Total Length, 因为前面标记过Header Length,因此用Total Length减去头部的长度就可以得到数据包的长度。total length一共16个字节, which indicates that IP包的最大长度为65535.
5.Identifier,因为数据链路层的协议限制了MTU的大小,当数据包的长度超过二层协议的MTU的时候,就需要将IP包进行分段。相同的IP包被分段后具有相同的Identifier值,因此传输之后重新组合的时候可以区分不同的数据包。
PS:IP包一旦被分段之后,将会一直到receiver才会进行重组。
6.Flags, 一共3个字节,第一个bit保留不用,which is always zero~. 第二个bit叫做DF。 DONOT FRAGMENT,这个字段如果被置为1的话路由器将不会对数据包进行分段。这个功能可以用来检测二层协议的MTU值。第三个bit叫做MF, More Fragment。接收者受到数据包之后将检测这个字段,在被分段的数据包中,只有最后一个这个字节是0,其他的全部为1,因此接收者可以根据这个值判断是否接收到最后一个分段,从而进行重组。
7.Fragment Offset, 记录分段的偏移值。接收者会根据这个值进行数据包的重新组和。
8.TTL,不同的应用程序对TTL有不同的初始值。建议的默认TTL为64. This value will be decreased by router hop by hop, and sometimes people also call this number as hop count.
9. Protocol, 用来记录端口号,8位,因此PC的端口号为0~65535
10. Header Checksum, 校验和。路由器并不计算被封装在IP包内的数据的校验和,因为内部数据有自己的校验字段。而由于TTL是逐跳变化的,因此这个CHECKSUM必须在每台路由器上都要计算。RFC1141提出了简化计算的方案。
11.Source&Destination Address 32位的IPV4地址。
最后是包的内容就是真正要传送的部分,在这之前的都是附加信息,是计算机加上去用来在网络上传输时使用的。
下面来看看当计算机收到一个以太网的包时是怎么解析的。
首先,以太网上每个包的大小不能超过1500字节,这是由以太网MTU决定的,超过这个大小就要按照不大于这个大小的方案进行分组然后传输由于每个包必须加上最小14字节长度的Ethernet Header,和不低于20字节长度的IP header,还有UDP(8byte)或者TCP(20byte)头剩下的部分才可以作为正文内容部分,也就是说实际传输时每个包必须按照最大不能超过1500-14-20-20=1446bytes或者1500-14-20-8=1458bytes的大小进行分组。
那么,当我们收到这个包之后,要对其进行解析可分以下三步,
第一步,解析Ethernet Header
第二步,解析IP Header
第三步,解析Tcp或者Udp Header
最后才是正文部分。
下面以第二部分解析IP Header为例进行讲解:
假设收到的报文的缓冲区指针为*pBuffer,那么首先进行对齐,这里可以借助结构体IP_HEADER进行对齐,这样每部分自动就被分配到了相应的成员上,便于引用进行进一步解析。
对于报文中每个结构占16位的部分直接引用结构体成员就可以了,对于4位或者8位的结构就要单独进行提取解析,拿前两个VERSION 和IHL为例讲解怎么解析,首先借助IP_HEADER的成员ver_len_tos;
代码如下:
ETH_HEADER* pEther= (ETH_HEADER*)pEthData; IP_HEADER *pIPData=(IP_HEADER*)(pEther+1); //将Ethernet Header头跳过,然后按照IP_HEADER进行对齐 int version = (pIPData->ver_len_tos>>4)&0x000f; //由于ver_len_tos占2字节version在低字节部分的高位所以进行如上操作 int IHL = ((pIPData->ver_len_tos&0x000f)*4);//同上,IHL在ver_len_tos的低字节地位所以直接取低四位由于这里的单位是4字节所以乘以4 int Type_of_service = (pIPData->ver_len_tos>>8)&0x00ff);//同上先位移将高八位移入底八位然后取低八位