IP具有一种重要功能,就是当分组过大而不适合在所选硬件接口上发送时,能够对分组进行分片。过大的分组被分成两个或多个大小适合在所选定网络上发送的IP分片。而在去目的主机的路途中,分片还可能被中间的路由器继续分片。在目的主机上,一个IP数据报可能放在一个IP分组内或者多个IP分组内。因为各个分片可能以不同的路径到达目的主机,所以只有目的主机才有机会看到所有分片。因此,也只有目的主机才能把所有分片重装成一个完整的数据报,提交给合适的运输层协议。
IP首部内有三个字段实现分片和重装:标识字段(ip_id)、标志字段(ip_off的3个高位比特)和偏移字段(ip_off的13个低位比特)。标志字段由三个1bit标志组成。
1) 比特0是保留的,必须为0;
2) 比特1是“不分片”(DF)标志
,如果将这一比特置1,IP将不对数据报进行分片
,这时如果有需要进行分片的数据报到来,会丢弃此数据报并发送一个ICMP差错报文给起始端。
3) 比特2是“更多分片”(MF)标志。
除了最后一片外,其他每个组成数据报的片都要把该比特置
1
。
片偏移字段指的是该片偏移原始数据报开始处的位置。另外,当数据报被分片后,每个片的总长度值要改为该片的长度值。
BSD中,标志和偏移字段结合起来,由ip_off访问。ip_id标识了特定数据报的分片,ip_off确定了分片在原始数据报内的位置,除最后一个分片外,MF标识每个分片。
下面先简单讲一下IP分片的流程:
1
)当IP需要分片时,会
从原来的分组中把
IP
首部和
IP
选项复制到新的分组中,IP首部复制在一个结构中,只复制那些将被复制到每个分片中的选项。
2
)设置分片包括
MF
比特的偏移字段
(
ip_off
)
。如果原来分组中已设置了
MF
比特,则在所有分片中都把
MF
置位。如果原来分组中没有设置
MF
比特,则除了最后一个分片外,其他所有分片中的
MF
都置位。
3
)
为分片设置长度,以网络字节序存储长度。
4
)从原始分组中把数据复制到分片中。调整新创建的分片的
mbuf
分组首部,使其具有正确的全长。把新分片的接口指针清零,把
ip_off
转换成网络字节序,计算新分片的检验和。通过
m_nextpkt
把该分片与前面的分片链接起来。
在讲
IP重组之前,先介绍一下ipq的结构:
struct
ipq {
struct ipq * next, * prev; /* to other reass headers */
u_char ipq_ttl; /* time for reass q to live */
u_char ipq_p; /* protocol of this fragment */
u_short ipq_id; /* sequence id for reassembly */
struct ipasfrag * ipq_next, * ipq_prev;
/* to ip headers of fragments */
struct in_addr ipq_src,ipq_dst;
};
struct ipasfrag {
#if BYTE_ORDER == LITTLE_ENDIAN
u_char ip_hl: 4 ,
ip_v: 4 ;
#endif
#if BYTE_ORDER == BIG_ENDIAN
u_char ip_v: 4 ,
ip_hl: 4 ;
#endif
u_char ipf_mff; /* XXX overlays ip_tos: use low bit
* to avoid destroying tos;
* copied from (ip_off&IP_MF) */
short ip_len;
u_short ip_id;
short ip_off;
u_char ip_ttl;
u_char ip_p;
u_short ip_sum;
struct ipasfrag * ipf_next; /* next fragment */
struct ipasfrag * ipf_prev; /* previous fragment */
};
struct ipq * next, * prev; /* to other reass headers */
u_char ipq_ttl; /* time for reass q to live */
u_char ipq_p; /* protocol of this fragment */
u_short ipq_id; /* sequence id for reassembly */
struct ipasfrag * ipq_next, * ipq_prev;
/* to ip headers of fragments */
struct in_addr ipq_src,ipq_dst;
};
struct ipasfrag {
#if BYTE_ORDER == LITTLE_ENDIAN
u_char ip_hl: 4 ,
ip_v: 4 ;
#endif
#if BYTE_ORDER == BIG_ENDIAN
u_char ip_v: 4 ,
ip_hl: 4 ;
#endif
u_char ipf_mff; /* XXX overlays ip_tos: use low bit
* to avoid destroying tos;
* copied from (ip_off&IP_MF) */
short ip_len;
u_short ip_id;
short ip_off;
u_char ip_ttl;
u_char ip_p;
u_short ip_sum;
struct ipasfrag * ipf_next; /* next fragment */
struct ipasfrag * ipf_prev; /* previous fragment */
};
具体结构的组织信息如下下图所示:
1)所有结构都放在一个
mbuf的数据区内。
2)
ipq链表由next和prev链接起来的ipq结构组成。每个ipq结构保存了唯一标识一个IP数据报的四个字段。
3)当作为分片链表的头访问时,每个
ipq结构被看成是一个ipasfrag结构。这些分片由ipf_next和ipf_prev链接起来,分别覆盖了ipq结构的ipq_next和ipq_prev成员。
4)每个
ipasfrag结构都覆盖了到达分片的ip结构,与分片一起到达的数据在缓存中跟在该结构之后。ipasfrag结构中的字段ipf_mff,ipf_next,ipf_prev的含义与其在ip结构中不太相同。
IP重组的流程:
1)由于ip_off包含DF比特、MF比特以及分片偏移,如果MF比特或分片偏移非零,则DF就被掩盖掉了,分组就是一个必须被重装的分片。如果两者都为零,则分组就是一个完整的数据报,不需要进行重组。
2
)在一个全局双向链表i p q上记录不完整的数据报。分片是由4元组{ip_id、ip_src、ip_dst和ip_p}唯一标识的,利用这个
4元组作为匹配项对表ipq进行线性搜索,为当前分片找到合适的数据报。
3)修改
ip_len,从中减去标准IP首部和任何选项,把MF标志复制到ipf_mff的低位,把ip_tos覆盖掉。用8乘ip_off,把它从以8字节为单元转换成以1字节为单元。ipf_mff和ip_off决定ipintr是否应该重组。
4
)通过把当前分片与以前收到的分片组合在一起,能重装成一个完整的数据报,它就返回指向该重装好的数据报的指针。如果没有重装好,则保存该分片,跳到next去处理下一个分片。如果重装处理产生一个完整的数据报,就把这个完整的数据报上传给合适的运输层协议。