Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1252824
  • 博文数量: 177
  • 博客积分: 1528
  • 博客等级: 上尉
  • 技术积分: 1891
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-15 18:03
文章分类

全部博文(177)

文章存档

2020年(1)

2018年(19)

2017年(4)

2016年(21)

2015年(40)

2014年(13)

2013年(26)

2012年(16)

2011年(37)

我的朋友

分类: LINUX

2015-02-06 11:37:25

1、 IP分片
 
   任何IP层接收到一份要发送的IP数据报时,它要判断向本地哪个接口发送数据,并查询该接口的MTU。IP把MTU与数据报的长度进行比较,如果需要则进行分片。分片可以发生在原始发送端主机上,也可以发送在中间路由器上。IP数据报分片后,只有到达目的主机后才进行重装。
IP首部与分片有关的字段:
 
(1)对于每份IP数据报来说,都有一个标识字段,该值在分片时被复制到每个片中。
 
(2)标志字段用其中一个bit表示更多的片,除最后一片外,其他每个分片都要设置为1。
 
(3)片偏移字段指的是该片偏移原始数据报开始处的位置。
 
(4)数据报被分片后,每个片的总长度要改为该片的长度值。
 
(5)标志字段中有一个bit表示不分片,如果该位1,IP将不对数据报进行分片。
   IP报即使丢失一片数据报也要重传整个数据报。为什么呢?因为IP层没有超时重传的机制,必须由更高层负责超时重传。
 
 
总结:
三个字段
    标识字段(ip_id):标识特定数据报的分片
    标志字段(ip_off的3个高位比特)
    偏移字段(ip_off的13个低位比特)
 
几个区别
    普通IP包:ip_off、MF 为 0
    最后一个分片包: ip_off > 0、MF 为 0
    其它分片包:ip_off ≥ 0、MF 为 1
 
2、 数据结构设计
 
链表_FRAG
    结点结构ipfrag,保存一个分片
    作用:保存同属于一个IP包的所有分片数据
    链表中各结点按ip_off由小到大排序
 
链表_IPQ
    结点结构ipq,作为_FRAG的头结点,描述属于同一个IP包的所有分片数据的共同特征
    作用:将目的地址相同的分片组织到一起
 
 
链表_HOSTFRAG
    结点结构hostfrags,作为_IPQ的头结点
    作用:将目的地址不同但hash值相同的分片数据组织到地起
 
hash表fragtable
    实现:struct hostfrags **fragtable
    作用: fragtable[index]为_HOSTFRAG的头结点
 
 
hash表及三个链表之间的关系图
 
3、 分片重组流程
 
  
 
4、 代码及相关注释
 
5、 几个细节
 
5.1 Step 14:在_FRAG链表中插入的位置
代码:
if (next->offset >= offset)
      break;
当prev和next结点都存在时满足的条件
offset? ( prev->offsetnext->offset
隐含意思:当前分片与prev之前的结点无重叠,与prev可能有重叠
 
 
 
5.2 step 15:和prev有重叠,调整当前分片
 
调整前图
 
调整后图
step 15:和prev有重叠,调整当前分片后没有判断当前数据的长度,之后会创建一个无用结点!
 
////////////////////////////////////////////////////////////////////////
source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetextsearch ] ~ [ file search ] ~
Linux Cross Reference
Linux-2.6.17
/net/ipv4/
Version: ~ [ 2.6.16 ] ~ 2.6.17 ] ~ 
Architecture: ~ [  ] ~ i386 ] ~ [  ] ~ [  ] ~ [  ] ~
 1 /*
   * INET         An implementation of the TCP/IP protocol suite for the LINUX
   *              operating system. INET is implemented using the BSD Socket
   *              interface as the means of communication with the user level.
   *
   *              The IP fragmentation functionality.
   *             
   * Version:     $Id: ip_fragment.c,v 1.59 2002/01/12 07:54:56 davem Exp $
   *
   * Authors:     Fred N. van Kempen
   *              Alan Cox
   *
   * Fixes:
   *              Alan Cox        :    Split from ip.c , see ip_input.c for history.
   *              David S. Miller :       Begin massive cleanup...
   *              Andi Kleen      :       Add sysctls.
   *              xxxx            :       Overlapfrag bug.
   *              Ultima          :       ip_expire() kernel panic.
   *              Bill Hawes      :       Frag accounting and evictor fixes.
   *              John McDonald   :       0 length frag bug.
   *              Alexey Kuznetsov:       SMP races, threading, cleanup.
   *              Patrick McHardy :       LRU queue of frag heads for evictor.
   */
 
 /* Add new segment to existing queue. */
 static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
 {
         struct sk_buff *prev, *next;
         int flagsoffset;
........................
........................
........................
........................
         /* Find out which fragments are in front and at the back of us
          * in the chain of fragments so far. We must know where to put
          * this fragment, right?
          */
         prev = NULL;
         for(next = qp->fragmentsnext != NULLnext = next->next) {
                 if (FRAG_CB(next)->offset >= offset)
                         break; /* bingo! */
                 prev = next;
         }
         /* We found where to put this one. Check for overlap with
          * preceding fragment, and, if needed, align things so that
          * any overlaps are eliminated.
          */
         if (prev) {
                 int i = (FRAG_CB(prev)->offset + prev->len) - offset;
                 if (i > 0) {
                         offset += i;
                         if (end <= offset)
                                 goto err;
                         if (!pskb_pull(skbi))
                                 goto err;
                         if (skb->ip_summed != CHECKSUM_UNNECESSARY)
                                 skb->ip_summed = CHECKSUM_NONE;
                 }
         }
         while (next && FRAG_CB(next)->offset < end) {
                 int i = end - FRAG_CB(next)->offset/* overlap is 'i' bytes */
                 if (i < next->len) {
                         /* Eat head of the next overlapped fragment
                          * and leave the loop. The next ones cannot overlap.
                          */
                         if (!pskb_pull(nexti))
                                 goto err;
                         FRAG_CB(next)->offset += i;
                         qp->meat -= i;
........................
........................
........................
........................
 
 
   ~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~
 
 
 
This page was automatically generated by the
LXR engine.


Visit the LXR main site for more
information.
 
////////////////////////////////////////////////////////////////////////
 
5.3 step 18:
    i = end - next->offset
    tmp->len -= i
    tmp->offset += i
    tmp->ptr += i
    和next有重叠时的两种情况:
      1:
      2:
5.4    ipq结构中的特殊成员timer
    作用:描述已经收到的属于同一IP包的所有分片的存活期限
    创建:step 8
    初始设置:step 10
    expires为当前时间+30秒
    function:失效时的处理函数地址
    data:失效时的处理函数的参数,即timer所在的ipq结点首地址
 
 
5.5    ipq结构中的特殊成员timer
    更新:
        所有ipq结点中的timer成员组成一个双向链表_TIMER,链首:timer_head,链尾:timer_tail
        分片链表超时处理 step 1 : 当一个_IPQ链表在30秒内没有再收到分片数据时,放弃重组
        timer的更新step 13 :当收到一个分片时,重新设置失效时间,并将该分片所属ipq中的timer移到_TIMER的尾部
 
 
5.6    分片数据是否可重组的判断
 
    借助ipq结构中的成员变量len
        初始值:0
        更新:仅当收到最后一个分片时才设置len
    判断:
        当ipq.len为0时直接返回
        当收到最后一个分片后才检查_FRAG链表中的各分片是否相连 (ip_done函数中)判断
    代码:
        参tag: zeq_final_frag

好了,有时间了,来看看libnids中IP分片重组的方法吧。

在此之前,如果不懂IP分片技术的话,请参照。IP分片技术比较简单暴力,没有TCP那样复杂复杂的窗口协议。基本上只是暴力的拆分和重组,代码基本在ip_defragment.c中。

先从总体上说说。首先,每个IP(主机)都会有IP分片包(注意是IP,不是IP对)。所以,每个IP都有一个如下的结构体来维护上面的所以IP分片:

1
2
3
4
5
6
7
8
9
10
11
struct hostfrags {
  struct ipq *ipqueue;//这里维护IP碎片队列
  int ip_frag_mem;
  u_int ip;//主机对应的IP地址
  //很明显,下面三行告诉我们,这是哈希表的一个元素
  int hash_index;
  struct hostfrags *prev;
  struct hostfrags *next;
};
//下面这个就是维护所有IP的哈希表了。
static struct hostfrags **fragtable;

每个IP下面又有很多的被分片的IP包——IP碎片队列,IP碎片队列的定义在这:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Describe an entry in the "incomplete datagrams" queue. */
struct ipq {
  unsigned char *mac;        /* pointer to MAC header                */
  struct ip *iph;        /* pointer to IP header                 */
  int len;            /* total length of original datagram    */
  short ihlen;            /* length of the IP header              */
  short maclen;            /* length of the MAC header             */
  struct timer_list timer;    /* when will this queue expire?         */
  struct ipfrag *fragments;    /* linked list of received fragments    */
  struct hostfrags *hf;
  struct ipq *next;        /* linked list pointers                 */
  struct ipq *prev;
  // struct device *dev;    /* Device - for icmp replies */
};

最终的IP碎片的定义在这:

1
2
3
4
5
6
7
8
9
10
/* Describe an IP fragment. */
struct ipfrag {
  int offset;            /* offset of fragment in IP datagram    */
  int end;            /* last byte of data in datagram        */
  int len;            /* length of this fragment              */
  struct sk_buff *skb;        /* complete received fragment           */
  unsigned char *ptr;        /* pointer into real fragment data      */
  struct ipfrag *next;        /* linked list pointers                 */
  struct ipfrag *prev;
};

由于libnids中的分片重组代码是从内核中拿出来修改的,所以保留了内核的注释。这里就不多做解释了。

好了步入处理逻辑,照例,先看初始化:

1
2
3
4
5
6
7
8
9
10
11
12
void
ip_frag_init(int n)
{
  struct timeval tv;
 
  gettimeofday(&tv, 0);
  time0 = tv.tv_sec;
  fragtable = (struct hostfrags **) calloc(n, sizeof(struct hostfrags *));
  if (!fragtable)
    nids_params.no_mem("ip_frag_init");
  hash_size = n;
}

简单到不能再简单——分片了一个主机的哈希表。分完手工。好吧,看重组逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//先是判断是否为分片的函数
int
ip_defrag_stub(struct ip *iph, struct ip **defrag)
{
  int offset, flags, tot_len;
  struct sk_buff *skb;
 
  numpack++;
  //先处理超时事件
  timenow = 0;//刷新时间
  while (timer_head && timer_head->expires < jiffies()) {
    this_host = ((struct ipq *) (timer_head->data))->hf;
    timer_head->function(timer_head->data);
  }
 
  //然后计算分片的偏移
  offset = ntohs(iph->ip_off);
  flags = offset & ~IP_OFFSET;
  offset &= IP_OFFSET;
 
  //此包不是分片
  if (((flags & IP_MF) == 0) && (offset == 0)) {
    ip_defrag(iph, 0);
    return IPF_NOTF;
  }
 
  //此包是分片,先申请一个sk_buff把分片的数据保存起来,然后交给defrag函数
  tot_len = ntohs(iph->ip_len);
  skb = (struct sk_buff *) malloc(tot_len + sizeof(struct sk_buff));
  if (!skb)
      nids_params.no_mem("ip_defrag_stub");
  skb->data = (char *) (skb + 1);
  memcpy(skb->data, iph, tot_len);
  skb->truesize = tot_len + 16 + nids_params.dev_addon;
  skb->truesize = (skb->truesize + 15) & ~15;
  skb->truesize += nids_params.sk_buff_size;
 
  //如果集齐了一个ip包的所有分片ip_defrag将返回合并后的ip包,此时返回IPF_NEW,进行下一步的ip包处理
  //否则,返回IPF_ISF,跳过ip包处理
  if ((*defrag = (struct ip *)ip_defrag((struct ip *) (skb->data), skb)))
    return IPF_NEW;
 
  return IPF_ISF;
}
 
/* Process an incoming IP datagram fragment. */
//这里就是分片重组的主要逻辑了
static char *
ip_defrag(struct ip *iph, struct sk_buff *skb)
{
  struct ipfrag *prev, *next, *tmp;
  struct ipfrag *tfp;
  struct ipq *qp;
  char *skb2;
  unsigned char *ptr;
  int flags, offset;
  int i, ihl, end;
 
  //如果是分片,而且host哈希表里还没有对应的host项的话,果断新建一个
  //此处还负责将this_host变量设为当前ip对应的host
  if (!hostfrag_find(iph) && skb)
    hostfrag_create(iph);
 
  /* Start by cleaning up the memory. */
  //内存用太多了,panic之,然后释放当前host分片所用的内存
  if (this_host)
    if (this_host->ip_frag_mem > IPFRAG_HIGH_THRESH)
      ip_evictor();
   
  /* Find the entry of this IP datagram in the "incomplete datagrams" queue. */
  //这里,找到这个ip包对应的ip分片链表
  if (this_host)
    qp = ip_find(iph);
  else
    qp = 0;
 
  /* Is this a non-fragmented datagram? */
  offset = ntohs(iph->ip_off);
  flags = offset & ~IP_OFFSET;
  offset &= IP_OFFSET;
  if (((flags & IP_MF) == 0) && (offset == 0)) {
    if (qp != NULL)
      ip_free(qp);      /* Fragmented frame replaced by full
                   unfragmented copy */
    return 0;
  }
 
  /* ip_evictor() could have removed all queues for the current host */
  if (!this_host)
    hostfrag_create(iph);
 
  offset <<= 3;           /* offset is in 8-byte chunks */
  ihl = iph->ip_hl * 4;
 
  /*
    If the queue already existed, keep restarting its timer as long as
    we still are receiving fragments.  Otherwise, create a fresh queue
    entry.
  */
  //如果当前host下来过此包的碎片
  if (qp != NULL) {
    /* ANK. If the first fragment is received, we should remember the correct
       IP header (with options) */
    if (offset == 0) {
      qp->ihlen = ihl;
      memcpy(qp->iph, iph, ihl + 8);
    }
    del_timer(&qp->timer);
    qp->timer.expires = jiffies() + IP_FRAG_TIME;    /* about 30 seconds */
    qp->timer.data = (unsigned long) qp; /* pointer to queue */
    qp->timer.function = ip_expire;  /* expire function */
    add_timer(&qp->timer);
  }
  //否则新建一个碎片队列
  else {
    /* If we failed to create it, then discard the frame. */
    if ((qp = ip_create(iph)) == NULL) {
      kfree_skb(skb, FREE_READ);
      return NULL;
    }
  }
  /* Attempt to construct an oversize packet. */
  //再大的ip包也不能大过65535啊,一经发现,直接放弃
  if (ntohs(iph->ip_len) + (int) offset > 65535) {
    // NETDEBUG(printk("Oversized packet received from %s\n", int_ntoa(iph->ip_src.s_addr)));
    nids_params.syslog(NIDS_WARN_IP, NIDS_WARN_IP_OVERSIZED, iph, 0);
    kfree_skb(skb, FREE_READ);
    return NULL;
  }
 
  //下面就开始在碎片队列里面找位置了,同时处理好重叠
  //如果有重叠,把重叠的旧的部分去掉
  /* Determine the position of this fragment. */
  end = offset + ntohs(iph->ip_len) - ihl;
 
  /* Point into the IP datagram 'data' part. */
  ptr = (unsigned char *)(skb->data + ihl);
 
  /* Is this the final fragment? */
  if ((flags & IP_MF) == 0)
    qp->len = end;
 
  /*
    Find out which fragments are in front and at the back of us in the
    chain of fragments so far.  We must know where to put this
    fragment, right?
  */
  prev = NULL;
  for (next = qp->fragments; next != NULL; next = next->next) {
    if (next->offset >= offset)
      break;            /* bingo! */
    prev = next;
  }
  /*
    We found where to put this one.  Check for overlap with preceding
    fragment, and, if needed, align things so that any overlaps are
    eliminated.
  */
  if (prev != NULL && offset < prev->end) {
    nids_params.syslog(NIDS_WARN_IP, NIDS_WARN_IP_OVERLAP, iph, 0);
    i = prev->end - offset;
    offset += i;        /* ptr into datagram */
    ptr += i;           /* ptr into fragment data */
  }
  /*
    Look for overlap with succeeding segments.
    If we can merge fragments, do it.
  */
  for (tmp = next; tmp != NULL; tmp = tfp) {
    tfp = tmp->next;
    if (tmp->offset >= end)
      break;            /* no overlaps at all */
    nids_params.syslog(NIDS_WARN_IP, NIDS_WARN_IP_OVERLAP, iph, 0);
     
    i = end - next->offset;  /* overlap is 'i' bytes */
    tmp->len -= i;       /* so reduce size of    */
    tmp->offset += i;        /* next fragment        */
    tmp->ptr += i;
    /*
      If we get a frag size of <= 0, remove it and the packet that it
      goes with. We never throw the new frag away, so the frag being
      dumped has always been charged for.
    */
    if (tmp->len <= 0) {
      if (tmp->prev != NULL)
    tmp->prev->next = tmp->next;
      else
    qp->fragments = tmp->next;
       
      if (tmp->next != NULL)
    tmp->next->prev = tmp->prev;
       
      next = tfp;       /* We have killed the original next frame */
 
      frag_kfree_skb(tmp->skb, FREE_READ);
      frag_kfree_s(tmp, sizeof(struct ipfrag));
    }
  }
  //下面往队列中插入当前碎片
  /* Insert this fragment in the chain of fragments. */
  tfp = NULL;
  tfp = ip_frag_create(offset, end, skb, ptr);
   
  /*
    No memory to save the fragment - so throw the lot. If we failed
    the frag_create we haven't charged the queue.
  */
  if (!tfp) {
    nids_params.no_mem("ip_defrag");
    kfree_skb(skb, FREE_READ);
    return NULL;
  }
  /* From now on our buffer is charged to the queues. */
  tfp->prev = prev;
  tfp->next = next;
  if (prev != NULL)
    prev->next = tfp;
  else
    qp->fragments = tfp;
 
  if (next != NULL)
    next->prev = tfp;
 
  /*
    OK, so we inserted this new fragment into the chain.  Check if we
    now have a full IP datagram which we can bump up to the IP
    layer...
  */
  //查看是不是碎片都搜集齐了,如果齐了,组合成一个大ip包返回
  if (ip_done(qp)) {
    skb2 = ip_glue(qp);     /* glue together the fragments */
    return (skb2);
  }
  return (NULL);
}

阅读(2777) | 评论(0) | 转发(0) |
0

上一篇:tcp重组原理

下一篇:libnids库分析

给主人留下些什么吧!~~