libnids中TCP/IP栈实现细节分析——TCP会话重组

libnids是网络安全方面的一个库,可以用来检测网络上的攻击行为。其中最有价值的部分是,它模拟了linux内核中3层和4层的协议栈。可以供我们进一步研究linux内核中的TCP/IP协议栈做一些有价值的参考。这里简单谈谈这个库中模拟3、4层协议的实现细节(在继续读下去之前,有必要复习一下TCP/IP协议相关理论,主要是滑动窗口协议)。这里送上一张网上到处都有的TCP状态转化图,算是开胃小菜:

在TCP/IP协议栈中,3层对应的是IP层,4层对应TCP层,在这里,从3层到4层转化主要做了两件重要的事情:IP分片重组和TCP会话重组。本篇先分析其中TCP会话重组的部分(自顶向下嘛,哈哈)。

OK,先看下重要的数据结构,在tcp.h中:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

struct skbuff {

  //万年不变的next和prev,这向我们昭示了这是一个双向队列。

  //对于每个TCP会话(ip:端口<- ->ip:端口)都要维护两个skbuf队列(每个方向都有一个嘛)

  //每个skbuf对应网络上的一个IP包,TCP流就是一个接一个的IP包嘛。

  structskbuff *next;

  structskbuff *prev;

  void*data;

  u_int len;

  u_int truesize;

  u_int urg_ptr;

  charfin;

  charurg;

  u_int seq;

  u_int ack;

};

这个结构体就是模仿的内核中的sk_buff结构体,只不过比内核中的要小很多(你懂的,因为这里只做会话重组)。

下面是在nids.h中的

1

2

3

4

5

6

7

struct tuple4

{

u_short source;

u_short dest;

u_int saddr;

u_int daddr;

};

这是用来表示一个TCP连接的,不解释。

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

struct half_stream

{

  charstate;

  charcollect;

  charcollect_urg;

  char*data;//这里存放着已经按顺序集齐排列好的数据

  intoffset;

  intcount;//这里存放data中数据的字节数

  intcount_new;//这里存放data中还没回调过的数据的字节数

  intbufsize;

  intrmem_alloc;

  inturg_count;

  u_int acked;

  u_int seq;

  u_int ack_seq;

  u_int first_data_seq;

  u_char urgdata;

  u_char count_new_urg;

  u_char urg_seen;

  u_int urg_ptr;

  u_short window;

  u_char ts_on;//tcp时间戳选项是否打开

  u_char wscale_on;//窗口扩展选项是否打开

  u_int curr_ts;

  u_int wscale;

  //下面是ip包缓冲区

  structskbuff *list;

  structskbuff *listtail;

}

这个是用来表示“半个TCP会话”,其实就是一个方向上的TCP流。

还有

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

struct tcp_stream

{

  structtuple4 addr;

  charnids_state;

  structlurker_node *listeners;

  structhalf_stream client;

  structhalf_stream server;

  structtcp_stream *next_node;

  structtcp_stream *prev_node;

  inthash_index;

  structtcp_stream *next_time;

  structtcp_stream *prev_time;

  intread;

  structtcp_stream *next_free;

  void*user;

};

显然,这是用来表示一个完整的TCP会话了,最后是static struct tcp_stream **tcp_stream_table;一个TCP会话指针的数组,其实就是hash表了。

下面来看处理过程,先是初始化:

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

int tcp_init(int size){

  ...

  //初始化全局tcp会话哈希表

  tcp_stream_table_size = size;

  tcp_stream_table =calloc(tcp_stream_table_size,sizeof(char*));

  if(!tcp_stream_table) {

    nids_params.no_mem("tcp_init");

    return-1;

  }

  //设置最大会话数,为了哈希的效率,哈希表的元素个数上限设为3/4表大小

  max_stream = 3 * tcp_stream_table_size / 4;

  //先将max_stream个tcp会话结构体申请好,放着(避免后面陆陆续续申请浪费时间)。

  streams_pool = (structtcp_stream *)malloc((max_stream + 1) *sizeof(structtcp_stream));

  if(!streams_pool) {

    nids_params.no_mem("tcp_init");

    return-1;

  }

  //ok,将这个数组初始化成链表

  for(i = 0; i < max_stream; i++)

    streams_pool[i].next_free = &(streams_pool[i + 1]);

  streams_pool[max_stream].next_free = 0;

  free_streams = streams_pool;

  ...

  return0;

}

很简单,做了两件事:1.初始化tcp会话哈希表。2.初始化会话池。这个初始化函数只在库初始化时执行一次。

初始化完成之后,就进入了pcap_loop中了,nids中的回调函数是nids_pcap_handler,在这个函数里面做了些ip分片重组(等下篇再说)后(tcp包)便来到了process_tcp函数,这里tcp会话重组开始了。来看看。

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

void process_tcp(u_char * data, int skblen){

  //处理头,得到ip包和tcp包

  structip *this_iphdr = (structip *)data;

  structtcphdr *this_tcphdr = (structtcphdr *)(data + 4 * this_iphdr->ip_hl);

  ...//此处忽略安检代码

  //在哈希表里找找,如果没有此tcp会话则看看是不是要新建一个

  if(!(a_tcp = find_stream(this_tcphdr, this_iphdr, &from_client))) {

    //这里判断此包是否是tcp回话周期中的第一个包(由客户端发出的syn包)

    //如果是,说明客户端发起了一个连接,那就新建一个回话

    if((this_tcphdr->th_flags & TH_SYN) &&

    !(this_tcphdr->th_flags & TH_ACK) &&

    !(this_tcphdr->th_flags & TH_RST))

      add_new_tcp(this_tcphdr, this_iphdr);

    //否则,果断忽略

    return;

  }

  //如果找到会话,根据数据流向,将发送方(snd)和接收方(rcv)设置好

  if(from_client) {

    snd = &a_tcp->client;

    rcv = &a_tcp->server;

  }

  else{

    rcv = &a_tcp->client;

    snd = &a_tcp->server;

  }

  //来了一个SYN包

  if((this_tcphdr->th_flags & TH_SYN)) {

    //syn包是用来建立新连接的,所以,要么来自客户端且没标志(前面处理了),要么来自服务端且加ACK标志

    //所以这里只能来自服务器,检查服务器状态是否正常,不正常的话果断忽略这个包

    if(from_client || a_tcp->client.state != TCP_SYN_SENT ||

      a_tcp->server.state != TCP_CLOSE || !(this_tcphdr->th_flags & TH_ACK))

      return;

    //忽略流水号错误的包

    if(a_tcp->client.seq != ntohl(this_tcphdr->th_ack))

      return;

    //自此,说明此包是服务端的第二次握手包,初始化连接(初始状态、流水号、窗口大小等等)

    a_tcp->server.state = TCP_SYN_RECV;

    a_tcp->server.seq = ntohl(this_tcphdr->th_seq) + 1;

    a_tcp->server.first_data_seq = a_tcp->server.seq;

    a_tcp->server.ack_seq = ntohl(this_tcphdr->th_ack);

    a_tcp->server.window = ntohs(this_tcphdr->th_win);

    //下面处理tcp的一些附加选项

    //先是时间戳选项

    if(a_tcp->client.ts_on) {

        a_tcp->server.ts_on = get_ts(this_tcphdr, &a_tcp->server.curr_ts);

    if(!a_tcp->server.ts_on)

        a_tcp->client.ts_on = 0;

    }elsea_tcp->server.ts_on = 0;

    //再是窗口扩大选项

    if(a_tcp->client.wscale_on) {

        a_tcp->server.wscale_on = get_wscale(this_tcphdr, &a_tcp->server.wscale);

    if(!a_tcp->server.wscale_on) {

        a_tcp->client.wscale_on = 0;

        a_tcp->client.wscale  = 1;

        a_tcp->server.wscale = 1;

    }

    }else{

        a_tcp->server.wscale_on = 0;

        a_tcp->server.wscale = 1;

    }

    //syn包处理完,返回

    return;

  }

  if(

    ! (  !datalen && ntohl(this_tcphdr->th_seq) == rcv->ack_seq )/*不是流水号正确且没数据的包*/

    &&//而且这个包不再当前窗口之内

    ( !before(ntohl(this_tcphdr->th_seq), rcv->ack_seq + rcv->window*rcv->wscale) ||//流水号大于等于窗口右侧

          before(ntohl(this_tcphdr->th_seq) + datalen, rcv->ack_seq) //数据包尾部小于窗口左侧

        )

     )

     //这个包不正常,果断放弃

     return;

  //如果是rst包,ok,关闭连接

  //将现有数据推给注册的回调方,然后销毁这个会话。

  if((this_tcphdr->th_flags & TH_RST)) {

    if(a_tcp->nids_state == NIDS_DATA) {

      structlurker_node *i;

      a_tcp->nids_state = NIDS_RESET;

      //下面回调所有的钩子

      for(i = a_tcp->listeners; i; i = i->next)

    (i->item) (a_tcp, &i->data);

    }

    nids_free_tcp_stream(a_tcp);

    return;

  }

  /* PAWS(防止重复报文)check 检查时间戳*/

  if(rcv->ts_on && get_ts(this_tcphdr, &tmp_ts) &&

    before(tmp_ts, snd->curr_ts))

  return;  

  //好的,ack包来了

  if((this_tcphdr->th_flags & TH_ACK)) {

    //如果是从客户端来的,且两边都在第二次握手的状态上

    if(from_client && a_tcp->client.state == TCP_SYN_SENT &&

    a_tcp->server.state == TCP_SYN_RECV) {

      //在此情况下,流水号又对得上,好的,这个包是第三次握手包,连接建立成功

      if(ntohl(this_tcphdr->th_ack) == a_tcp->server.seq) {

    a_tcp->client.state = TCP_ESTABLISHED;//更新客户端状态

    a_tcp->client.ack_seq = ntohl(this_tcphdr->th_ack);//更新ack序号

    {

      structproc_node *i;

      structlurker_node *j;

      void*data;

      a_tcp->server.state = TCP_ESTABLISHED;//更新服务端状态

      a_tcp->nids_state = NIDS_JUST_EST;//这个是安全方面的,这里无视之

          //下面这个循环是回调所有钩子函数,告知连接建立

      for(i = tcp_procs; i; i = i->next) {

        charwhatto = 0;

        charcc = a_tcp->client.collect;

        charsc = a_tcp->server.collect;

        charccu = a_tcp->client.collect_urg;

        charscu = a_tcp->server.collect_urg;

        (i->item) (a_tcp, &data);//回调

        if(cc < a_tcp->client.collect)

          whatto |= COLLECT_cc;

        if(ccu < a_tcp->client.collect_urg)

          whatto |= COLLECT_ccu;

        if(sc < a_tcp->server.collect)

          whatto |= COLLECT_sc;

        if(scu < a_tcp->server.collect_urg)

          whatto |= COLLECT_scu;

        if(nids_params.one_loop_less) {

                if (a_tcp->client.collect >=2) {

                   a_tcp->client.collect=cc;

                   whatto&=~COLLECT_cc;

                }

                if (a_tcp->server.collect >=2 ) {

                   a_tcp->server.collect=sc;

                   whatto&=~COLLECT_sc;

                }

        }

        if(whatto) {

          j = mknew(structlurker_node);

          j->item = i->item;

          j->data = data;

          j->whatto = whatto;

          j->next = a_tcp->listeners;

          a_tcp->listeners = j;

        }

      }

      if(!a_tcp->listeners) {

        nids_free_tcp_stream(a_tcp);

        return;

      }

      a_tcp->nids_state = NIDS_DATA;

    }

      }

      // return;

    }

  }

  //自此,握手包处理完毕

  //下面就是挥手包了

  if((this_tcphdr->th_flags & TH_ACK)) {

    //先调用handle_ack更新ack序号

    handle_ack(snd, ntohl(this_tcphdr->th_ack));

    //更新状态,回调告知连接关闭,然后释放连接

    if(rcv->state == FIN_SENT)

      rcv->state = FIN_CONFIRMED;

    if(rcv->state == FIN_CONFIRMED && snd->state == FIN_CONFIRMED) {

      structlurker_node *i;

      a_tcp->nids_state = NIDS_CLOSE;

      for(i = a_tcp->listeners; i; i = i->next)

    (i->item) (a_tcp, &i->data);

      nids_free_tcp_stream(a_tcp);

      return;

    }

  }

  //下面处理数据包,和初始的fin包

  if(datalen + (this_tcphdr->th_flags & TH_FIN) > 0)

    //就将数据更新到接收方缓冲区

    tcp_queue(a_tcp, this_tcphdr, snd, rcv,

          (char *) (this_tcphdr) + 4 * this_tcphdr->th_off,

          datalen, skblen);

  //更新窗口大小

  snd->window = ntohs(this_tcphdr->th_win);

  //如果缓存溢出(说明出了问题),果断释放连接

  if(rcv->rmem_alloc > 65535)

    prune_queue(rcv, this_tcphdr);

  if(!a_tcp->listeners)

    nids_free_tcp_stream(a_tcp);

}

好了,tcp包的基本处理流程就这些了,主要做了连接的建立、释放、状态迁移这些工作,下面看看连接的缓冲区是如何维护的(主要就是如何更新的)。来看tcp_queue函数:

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

static void

tcp_queue(structtcp_stream * a_tcp,structtcphdr * this_tcphdr,

      structhalf_stream * snd,structhalf_stream * rcv,

      char*data,int datalen, int skblen

      )

{

  u_int this_seq = ntohl(this_tcphdr->th_seq);

  structskbuff *pakiet, *tmp;

  /*

   * Did we get anything new to ack?

   */

  //EXP_SEQ是目前已集齐的数据流水号,我们希望收到从这里开始的数据

  //先判断数据是不是在EXP_SEQ之前开始

  if(!after(this_seq, EXP_SEQ)) {

    //再判断数据长度是不是在EXP_SEQ之后,如果是,说明有新数据,否则是重发的包,无视之

    if(after(this_seq + datalen + (this_tcphdr->th_flags & TH_FIN), EXP_SEQ)) {

      /* the packet straddles our window end */

      get_ts(this_tcphdr, &snd->curr_ts);

      //ok,更新集齐的数据区,值得一提的是add_from_skb函数一旦发现集齐了一段数据之后

      //便立刻调用notify函数,在notify函数里面将数据推给回调方

      add_from_skb(a_tcp, rcv, snd, (u_char *)data, datalen, this_seq,

           (this_tcphdr->th_flags & TH_FIN),

           (this_tcphdr->th_flags & TH_URG),

           ntohs(this_tcphdr->th_urp) + this_seq - 1);

      /*

       * Do we have any old packets to ack that the above

       * made visible? (Go forward from skb)

       */

      //此时EXP_SEQ有了变化了,看看缓冲区里的包有没有符合条件能用同样的方法处理掉的

      //有就处理掉,然后释放

      pakiet = rcv->list;

      while(pakiet) {

    if(after(pakiet->seq, EXP_SEQ))

      break;

    if(after(pakiet->seq + pakiet->len + pakiet->fin, EXP_SEQ)) {

      add_from_skb(a_tcp, rcv, snd, pakiet->data,

               pakiet->len, pakiet->seq, pakiet->fin, pakiet->urg,

               pakiet->urg_ptr + pakiet->seq - 1);

        }

    rcv->rmem_alloc -= pakiet->truesize;

    if(pakiet->prev)

      pakiet->prev->next = pakiet->next;

    else

      rcv->list = pakiet->next;

    if(pakiet->next)

      pakiet->next->prev = pakiet->prev;

    else

      rcv->listtail = pakiet->prev;

    tmp = pakiet->next;

    free(pakiet->data);

    free(pakiet);

    pakiet = tmp;

      }

    }

    else

      return;

  }

  //这里说明现在这个包是个乱序到达的(数据开始点超过了EXP_SEQ),放到缓冲区等待处理,注意保持缓冲区有序

  else{

    structskbuff *p = rcv->listtail;

    pakiet = mknew(structskbuff);

    pakiet->truesize = skblen;

    rcv->rmem_alloc += pakiet->truesize;

    pakiet->len = datalen;

    pakiet->data =malloc(datalen);

    if(!pakiet->data)

      nids_params.no_mem("tcp_queue");

    memcpy(pakiet->data, data, datalen);

    pakiet->fin = (this_tcphdr->th_flags & TH_FIN);

    /* Some Cisco - at least - hardware accept to close a TCP connection

     * even though packets were lost before the first TCP FIN packet and

     * never retransmitted; this violates RFC 793, but since it really

     * happens, it has to be dealt with... The idea is to introduce a 10s

     * timeout after TCP FIN packets were sent by both sides so that

     * corresponding libnids resources can be released instead of waiting

     * for retransmissions which will never happen.  -- Sebastien Raveau

     */

    if(pakiet->fin) {

      snd->state = TCP_CLOSING;

      if(rcv->state == FIN_SENT || rcv->state == FIN_CONFIRMED)

    add_tcp_closing_timeout(a_tcp);

    }

    pakiet->seq = this_seq;

    pakiet->urg = (this_tcphdr->th_flags & TH_URG);

    pakiet->urg_ptr = ntohs(this_tcphdr->th_urp);

    for(;;) {

      if(!p || !after(p->seq, this_seq))

    break;

      p = p->prev;

    }

    if(!p) {

      pakiet->prev = 0;

      pakiet->next = rcv->list;

      if(rcv->list)

         rcv->list->prev = pakiet;

      rcv->list = pakiet;

      if(!rcv->listtail)

    rcv->listtail = pakiet;

    }

    else{

      pakiet->next = p->next;

      p->next = pakiet;

      pakiet->prev = p;

      if(pakiet->next)

    pakiet->next->prev = pakiet;

      else

    rcv->listtail = pakiet;

    }

  }

}

好了,主体函数就这些了,其他的函数比较简单,不解释。协议栈模拟,考验的就是细心周到啊。

好了,有时间了,来看看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
structhostfrags {
  structipq *ipqueue;//这里维护IP碎片队列
  intip_frag_mem;
  u_int ip;//主机对应的IP地址
  //很明显,下面三行告诉我们,这是哈希表的一个元素
  inthash_index;
  structhostfrags *prev;
  structhostfrags *next;
};
//下面这个就是维护所有IP的哈希表了。
staticstructhostfrags **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. */
structipq {
  unsignedchar*mac;       /* pointer to MAC header                */
  structip *iph;       /* pointer to IP header                 */
  intlen;           /* total length of original datagram    */
  shortihlen;           /* length of the IP header              */
  shortmaclen;           /* length of the MAC header             */
  structtimer_list timer;   /* when will this queue expire?         */
  structipfrag *fragments;   /* linked list of received fragments    */
  structhostfrags *hf;
  structipq *next;       /* linked list pointers                 */
  structipq *prev;
  // struct device *dev;    /* Device - for icmp replies */
};

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

1
2
3
4
5
6
7
8
9
10
/* Describe an IP fragment. */
structipfrag {
  intoffset;           /* offset of fragment in IP datagram    */
  intend;           /* last byte of data in datagram        */
  intlen;           /* length of this fragment              */
  structsk_buff *skb;       /* complete received fragment           */
  unsignedchar*ptr;       /* pointer into real fragment data      */
  structipfrag *next;       /* linked list pointers                 */
  structipfrag *prev;
};

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

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

1
2
3
4
5
6
7
8
9
10
11
12
void
ip_frag_init(intn)
{
  structtimeval tv;
  gettimeofday(&tv, 0);
  time0 = tv.tv_sec;
  fragtable = (structhostfrags **)calloc(n,sizeof(structhostfrags *));
  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(structip *iph,structip **defrag)
{
  intoffset, flags, tot_len;
  structsk_buff *skb;
  numpack++;
  //先处理超时事件
  timenow = 0;//刷新时间
  while(timer_head && timer_head->expires < jiffies()) {
    this_host = ((structipq *) (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);
    returnIPF_NOTF;
  }
  //此包是分片,先申请一个sk_buff把分片的数据保存起来,然后交给defrag函数
  tot_len = ntohs(iph->ip_len);
  skb = (structsk_buff *)malloc(tot_len +sizeof(structsk_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 = (structip *)ip_defrag((structip *) (skb->data), skb)))
    returnIPF_NEW;
  returnIPF_ISF;
}
/* Process an incoming IP datagram fragment. */
//这里就是分片重组的主要逻辑了
staticchar*
ip_defrag(structip *iph,structsk_buff *skb)
{
  structipfrag *prev, *next, *tmp;
  structipfrag *tfp;
  structipq *qp;
  char*skb2;
  unsignedchar*ptr;
  intflags, offset;
  inti, 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 */
    return0;
  }
  /* 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 = (unsignedlong) 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);
      returnNULL;
    }
  }
  /* 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);
    returnNULL;
  }
  //下面就开始在碎片队列里面找位置了,同时处理好重叠
  //如果有重叠,把重叠的旧的部分去掉
  /* Determine the position of this fragment. */
  end = offset + ntohs(iph->ip_len) - ihl;
  /* Point into the IP datagram 'data' part. */
  ptr = (unsignedchar*)(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(structipfrag));
    }
  }
  //下面往队列中插入当前碎片
  /* 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);
    returnNULL;
  }
  /* 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);
}

好了,打完收工!

libnids中TCP/IP栈实现细节分析——TCP会话重组相关推荐

  1. CISA 发布关于 Treck TCP/IP 栈中新漏洞的 ICS 安全公告

     聚焦源代码安全,网罗国内外最新资讯! Treck TCP/IP 栈的安全更新,解决了两个可导致远程代码执行或拒绝服务的严重漏洞.美国网络安全和基础设施安全局 (CISA) 发布安全公告,警告组织机构 ...

  2. uIP 一个免费的TCP/IP栈

    uIP 一个免费的TCP/IP栈 原文:Adam Dunkels adam@dunkels.com 2002年2月15日 翻译:张伟林   2003年5月17日 okelinchang@163.com ...

  3. 深入分析Linux操作系统对于TCP/IP栈的实现原理与具体过程

    一.Linux内核与网络体系结构 在我们了解整个linux系统的网络体系结构之前,我们需要对整个网络体系调用,初始化和交互的位置,同时也是Linux操作系统中最为关键的一部分代码-------内核,有 ...

  4. 4个开源 TCP/IP 栈被曝33个漏洞,数百万智能和工业设备受影响

     聚焦源代码安全,网罗国内外最新资讯! 编译:奇安信代码卫士团队 Forescout 公司的安全研究员披露了150多个厂商产品固件中当前所使用的四个开源 TCP/IP 库中的33个安全缺陷,被统称为 ...

  5. java堆和客栈_java中堆和栈的区别分析

    堆和栈是java数据结构里非常重要的概念,本文较为详细的分析了二者之间的区别.供大家参考.具体如下: Java的堆是一个运行时数据区,类的(对象从中分配空间.这些对象通过new.newarray.an ...

  6. TCP/IP网络编程之基于TCP的服务端/客户端(二)

    回声客户端问题 上一章TCP/IP网络编程之基于TCP的服务端/客户端(一)中,我们解释了回声客户端所存在的问题,那么单单是客户端的问题,服务端没有任何问题?是的,服务端没有问题,现在先让我们回顾下服 ...

  7. 单片机tcp ip协议c语言,单片机TCP IP协议栈实现的原理

    对已TCP IP协议栈,我们已经说了很多关于它的原理相关的知识了.但是只有原理是不够的,在这方面我们将要举出一个实际操作实例为大家讲解,那么首先我们来看一下有关于单片机TCP/IP就是在单片机上运行的 ...

  8. TCP/IP协议(二)tcp/ip基础知识

    转载:http://www.cnblogs.com/imyalost/p/6139191.html 一.TCP/IP的标准化 1.TCP/IP的含义 一般来说,TCP/IP是利用IP进行通信时所必须用 ...

  9. TCP/IP网络编程之基于TCP的服务端/客户端(一)

    TCP/IP网络编程之基于TCP的服务端/客户端(一) 理解TCP和UDP 根据数据传输方式的不同,基于网络协议的套接字一般分为TCP套接字和UDP套接字.因为TCP套接字是面向连接的,因此又称为基于 ...

  10. 什么是TCP/IP?-四张图解释TCP/IP四层协议模型

    什么是四层模型 第一层:应用层,主要有负责web浏览器的HTTP协议, 文件传输的FTP协议,负责电子邮件的SMTP协议,负责域名系统的DNS等. 第二层:传输层,主要是有可靠传输的TCP协议,特别高 ...

最新文章

  1. linux7挂载ntfs分区,刚安装centos7,请教大神如何挂载ntfs的分区
  2. 由各大企业移除MongoDB,回看关系模型与文档模型之争
  3. itk下FFTW的FFT和IFFT
  4. 这两天发现一个老外用JavaScript编写的好东西:dp.SyntaxHighlighter。
  5. oracle学习的方法:
  6. 天天用 Spring,bean 实例化原理你懂吗?
  7. 加载特征主数据自动删除重复关键值
  8. python asyncio和celery对比_如何将Celery与asyncio结合? - python
  9. 近期H5项目开发小结
  10. 5怎么选国外节点_外卖包装怎么选?这5个技巧要掌握
  11. udp文件服务器,UDP客户端服务器文件传输
  12. 回归即登顶社交应用排行榜,多牛传媒开启人人“第二春”
  13. QT开发pjsip的VOIP,A8平台运行
  14. VMware Workstation虚拟机无法运行
  15. cad转excel插件c2e_CAD表格互转EXCEL插件
  16. 用Python在word的指定位置插入图片(使用Python-docx包)
  17. R语言 使用getGEO()直接进行差异表达分析并显示Entrez_id和Symbol_id
  18. 英语听力软件测试自学,每日英语听力应该怎么学习?
  19. 计算机硬件未来发展前景,计算机硬件的未来发展趋势
  20. python和r语言哪个简单_python与r语言哪个简单

热门文章

  1. mysql dump 1449_关于mysqldump的ERROR 1449 问题 | 学步园
  2. 判断入射满射c语言编码,数学上可以分三类函数包括() 答案:单射双射满射...
  3. 怎样选择适合自己的视频加密软件方案?
  4. dtmf拨号原理matlab,matlab综合实验dtmf拨号器设计.doc
  5. 聊一聊阿里P8、P9及以上人的水平
  6. cout 声明与定义
  7. 环境和社会风险分类c类_A、B、C级风险隐患分类标准
  8. matlab emd imf波形,emd分解后画出IMF的波形
  9. node 加密解密模块_NodeJS加密解密及node-rsa加密解密用法详解
  10. python将数值存入excel指定单元格