/*
*This IP datagram is too large to be sent in one piece.Break it up into
*smaller pieces (each of size equal to IP header plus
*a block of the data of the original IP data part) that will yet fit in a
*single device frame, and queue such a frame for sending.
*/
int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff*))
{
struct iphdr *iph;//ip头指针
int raw = 0;
int ptr;
struct net_device *dev;//网络设备指针
struct sk_buff *skb2;//新建一个skb的指针
unsigned int mtu, hlen, left, len, ll_rs;
int offset;//偏移
int not_last_frag;//不是最后一个分片
struct rtable *rt = (struct rtable*)skb->dst;
int err = 0;//出错?
dev = rt->u.dst.dev;//令dev等于在skb中的dev值
/*
*Point into the IP datagram header.
*/
iph = skb->nh.iph;//ip头指针指向skb中的ip头
if (unlikely((iph->frag_off & htons(IP_DF)) && !skb->local_df)) {
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
htonl(dst_pmtu(&rt->u.dst)));
kfree_skb(skb);//貌似检查是否允许分片,如果不允许就直接释放skb,其他的几个没看懂
return -EMSGSIZE;//分组比接口的MTU(最大传输单元)大
}
/*
*Setup starting values.
*/
hlen = iph->ihl * 4;//计算ip头长度
mtu = dst_pmtu(&rt->u.dst) - hlen;/* Size of data space */
//这里的mtu应该是去掉ip头后,数据部分的最大长度
/* When frag_list is given, use it. First, check its validity:
* some transformers could create wrong frag_list or break existing
* one, it is not prohibited. In this case fall back to copying.
*
* LATER: this step can be merged to real generation of fragments,
* we can switch to copy when see the first bad fragment.
*/
//在skbuff.h中定义了如下#define skb_shinfo(SKB)((struct skb_shared_info *)((SKB)->end))
//在缓冲区数据的末尾,有一个数据结构skb_shared_info,
//它保存了数据块的附加信息。这个数据结构紧跟在end指针所指的地址之后(end指针指示数据的末尾)。
//下面是这个结构的定义:
//struct skb_shared_info {
//atomic_tdataref;
//unsigned intnr_frags;
//unsigned shorttso_size;
//unsigned shorttso_seqs;
//struct sk_buff*frag_list;
//skb_frag_tfrags[MAX_SKB_FRAGS];
//};
/***********************************
dataref表示数据块的“用户”数,这个值在下一节(克隆和拷贝缓冲区)中有描述。nf_frags,frag_list和frags用于存储IP分片。
skb_is_nonlinear函数用于测试一个缓冲区是否是分片的,而skb_linearize可以把分片组合成一个单一的缓冲区。组合分片涉及到
数据拷贝,它将严重影响系统性能。
需要注意的是:sk_buff中没有指向skb_shared_info结构的指针。如果要访问这个结构,就需要使用skb_info宏,这个宏简单地返
回end指针:
********************************/
if (skb_shinfo(skb)->frag_list) {
struct sk_buff *frag;//又定义了一个指向skb的指针,下面将会使用
int first_len = skb_pagelen(skb);
//skb_pagelen(skb)计算的就是skb中的自身数据的,函数如下
//static inline int skb_pagelen(const struct sk_buff *skb)
//{
//int i, len = 0;
//for (i = (int)skb_shinfo(skb)->nr_frags - 1; i >= 0; i--)
//len += skb_shinfo(skb)->frags[i].size;
//return len + skb_headlen(skb);
//}
/********************************************
在skb_pagelen里首先计算skb_shinfo(skb)->frags的长度,然后再加上skb_headlen()的值,得出的是什么呢?
是隶属于本skb的,不包含skb_shinfo(skb)->frag_list的skb数据的长度,也就是第一个skb的值了。这也就是为什么被命名为
first_len的原因
**********************************************/
if (first_len - hlen > mtu ||
((first_len - hlen) & 7) ||
(iph->frag_off & htons(IP_MF|IP_OFFSET)) ||
skb_cloned(skb))
goto slow_path;
//没看大明白,但从第一个来看,大于mtu就应当要分片了,
//goto slow_path应当为分片
//下面的for循环遍历frag_list指向的sk_buff结构的链表,
//检查其是否需要分片,如果需要分片,就goto slow_path
//我认为可能是为了保证每个分片都足够小,不超过mtu,
//应为不同网络要求的mtu不一定相同,可能存在重复分片的情况,所以要检查一下
//个人观点,仅供参考
for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next) {
/* Correct geometry. */
if (frag->len > mtu ||
((frag->len & 7) && frag->next) ||
skb_headroom(frag) < hlen)
goto slow_path;
/* Partially cloned skb? */
if (skb_shared(frag))
goto slow_path;
}
/* Everything is OK. Generate! */
//相关数据的初始化
err = 0;
offset = 0;//初始偏移为0
frag = skb_shinfo(skb)->frag_list;
//frag设置为指向frag_list的第一个的指针
skb_shinfo(skb)->frag_list = NULL;//原来的设为NULL
skb->data_len = first_len - skb_headlen(skb);//数据部分长度
skb->len = first_len;//ip数据包长度
iph->tot_len = htons(first_len);//总长度
iph->frag_off |= htons(IP_MF);//设置分片的指示位
ip_send_check(iph);//头部校验
for (;;) {
/* Prepare header of the next frame,
* before previous one went down. */
if (frag) {
//如果frag非空,设置frag指向的skb结构
frag->ip_summed = CHECKSUM_NONE;
frag->h.raw = frag->data;
frag->nh.raw = __skb_push(frag, hlen);
memcpy(frag->nh.raw, iph, hlen);
//复制ip头部
iph = frag->nh.iph;//指向新的ip头
iph->tot_len = htons(frag->len);
ip_copy_metadata(frag, skb);
//关于ip_copy_metadata函数,百度之据说是复制其他一些关于skb的设置
if (offset == 0)//偏移为0,表示为第一个分片包
ip_options_fragment(frag);//一般第一个分片包会加入一些选项
offset += skb->len - hlen;//计算下一个offset
iph->frag_off = htons(offset>>3);
//因为ip包中的offset一位表示一个字节,因此要右移3位,扩大8倍
if (frag->next != NULL)//如果不是最后一个分片,设置MF位为1
iph->frag_off |= htons(IP_MF);
/* Ready, complete checksum */
ip_send_check(iph);
}
err = output(skb);//发送函数
if (err || !frag)//如果出错或frag为空
break;
skb = frag;//frag赋予skb
frag = skb->next;//frag指向下一个
skb->next = NULL;//清除skb于frag的联系
}//for循环结束