TCP会话重组.docx

上传人:b****3 文档编号:3442882 上传时间:2022-11-23 格式:DOCX 页数:27 大小:99.35KB
下载 相关 举报
TCP会话重组.docx_第1页
第1页 / 共27页
TCP会话重组.docx_第2页
第2页 / 共27页
TCP会话重组.docx_第3页
第3页 / 共27页
TCP会话重组.docx_第4页
第4页 / 共27页
TCP会话重组.docx_第5页
第5页 / 共27页
点击查看更多>>
下载资源
资源描述

TCP会话重组.docx

《TCP会话重组.docx》由会员分享,可在线阅读,更多相关《TCP会话重组.docx(27页珍藏版)》请在冰豆网上搜索。

TCP会话重组.docx

TCP会话重组

libnids是网络安全方面的一个库,可以用来检测网络上的攻击行为。

其中最有价值的部分是,它模拟了linux内核中3层和4层的协议栈。

可以供我们进一步研究linux内核中的TCP/IP协议栈做一些有价值的参考。

这里简单谈谈这个库中模拟3、4层协议的实现细节(在继续读下去之前,有必要复习一下TCP/IP协议相关理论,主要是滑动窗口协议)。

这里送上一张网上到处都有的TCP状态转化图,算是开胃小菜:

基本概念

四元组:

源IP地址、目的IP地址、源端口、目的端口。

五元组:

源IP地址、目的IP地址、协议号、源端口、目的端口。

六元组:

源MAC地址、源IP地址、源端口号、目的MAC地址、目的IP地址和目的IP地址。

七元组:

源MAC地址、源IP地址、源端口号、目的MAC地址、目的IP地址和目的IP地址和协议号。

 

五元组确定一个会话还是四元组?

五元组通常是指由源IP地址,源端口,目的IP地址,目的端口和传输层协议号这五个量组成的一个集合。

例如:

192.168.0.1/10000/TCP/121.14.88.76/80就构成了一个五元组。

其意义是,一个IP地址为192.168.1.1的终端通过端口10000,利用TCP协议,和IP地址为121.14.88.76,端口为80的终端进行连接通讯。

五元组能够唯一确定一个会话。

 

在TCP会话重组时,使用序列号确定TCP报文顺序可以解决数据报文不按顺序到达及其重传问题,并且利用二维链表对TCP会话就行还原。

难点在于解决多连接问题、IP包乱序到达和TCP会话重传的问题。

原因:

TCP协议是TCP/IP协议族中一个重要组成部分,TCP数据流的重组是高层协议分析系统设计和实现的基础。

TCP协议是面向连接的可靠传输协议,而TCP下层的IP协议却是面向报文的不可靠协议,这回带来问题:

IP不能保证TCP报文可靠的、顺序的传输。

为了解决这个问题,TCP采取滑动窗口机制、字节流编号机制和快速重传算法机制等。

这可以保证数据的可靠传输。

TCP会话(TCP_Session_IDT)可以通过四元组<源IP地址、目的IP地址、源端口号和目的端口号>唯一标识。

使用HASH表快速查找定位的特征,解决多个TCP会话同时处理的问题,快速处理多个会话问题。

在TCP头中SequenceNumber是判断该数据包是否重传和包乱序的重要参数。

在TCP连接刚建立时,会为后续TCP传输设置一个初始的SequenceNumber,每传送一个包含有效数据的TCP包,后续传送的TCP数据包的SequenceNumber会作响应的修改,如果前一个包长度为N,则这个包的SequenceNumber为前一个包SequenceNumber加N。

它是为保证TCP数据包按序传输来设计的,可以有效的实现TCP数据的完整传输,特别是当数据传输出现错误时可以有效进行错误纠正。

TCP重组数据文件写指针的SYN算法如下:

File_Init_Write_Pointer=Init_SequenceNumber+1;

File_write_Pointer=CurrentSequenceNumber–File_init_Write_point;

检查TCP会话中是否存在空洞,可以来确定会话重组成功、失败和超时。

TCP建立连接需要3次握手,而终止一个连接需要4次握手。

这是因为一个TCP连接时全双工的,每个方向必须单独的进行关闭。

规则1:

六元组<源MAC地址、源IP地址、源端口号、目的MAC地址、目的IP地址和目的IP地址>,协议号是TCP,它应该是唯一的会话。

规则2:

TCP头中4元组,它应该是唯一的,不唯一说明存在重传情况。

在TCP/IP协议栈中,3层对应的是IP层,4层对应TCP层,在这里,从3层到4层转化主要做了两件重要的事情:

IP分片重组和TCP会话重组。

本篇先分析其中TCP会话重组的部分(自顶向下嘛,哈哈)。

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

[cpp]viewplaincopyprint?

1.struct skbuff   

2.{  

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

  

4.  //对于每个TCP会话(ip:

端口<- ->ip:

端口)都要维护两个skbuf队列(每个方向都有一个嘛)  

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

  

6.  struct skbuff *next;  

7.  struct skbuff *prev;  

8.   

9.  void *data;  

10.  u_int len;  

11.  u_int truesize;  

12.  u_int urg_ptr;  

13.   

14.  char fin;  

15.  char urg;  

16.  u_int seq;  

17.  u_int ack;  

18.};  

structskbuff

{

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

//对于每个TCP会话(ip:

端口<-->ip:

端口)都要维护两个skbuf队列(每个方向都有一个嘛)

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

structskbuff*next;

structskbuff*prev;

void*data;

u_intlen;

u_inttruesize;

u_inturg_ptr;

charfin;

charurg;

u_intseq;

u_intack;

};

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

下面是在nids.h中的

[cpp]viewplaincopyprint?

1.struct tuple4  

2.{  

3.    u_short source;  

4.    u_short dest;  

5.    u_int saddr;  

6.    u_int daddr;  

7.};  

structtuple4

{

   u_shortsource;

   u_shortdest;

   u_intsaddr;

   u_intdaddr;

};

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

[cpp]viewplaincopyprint?

1.struct half_stream  

2.{  

3.  char state;  

4.  char collect;  

5.  char collect_urg;  

6.   

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

8.  int offset;  

9.  int count; //这里存放data中数据的字节数  

10.  int count_new; //这里存放data中还没回调过的数据的字节数  

11.  int bufsize;  

12.  int rmem_alloc;  

13.   

14.  int urg_count;  

15.  u_int acked;  

16.  u_int seq;  

17.  u_int ack_seq;  

18.  u_int first_data_seq;  

19.  u_char urgdata;  

20.  u_char count_new_urg;  

21.  u_char urg_seen;  

22.  u_int urg_ptr;  

23.  u_short window;  

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

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

26.  u_int curr_ts;  

27.  u_int wscale;  

28.   

29.  //下面是ip包缓冲区  

30.  struct skbuff *list;  

31.  struct skbuff *listtail;  

32.}  

structhalf_stream

{

charstate;

charcollect;

charcollect_urg;

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

intoffset;

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

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

intbufsize;

intrmem_alloc;

inturg_count;

u_intacked;

u_intseq;

u_intack_seq;

u_intfirst_data_seq;

u_charurgdata;

u_charcount_new_urg;

u_charurg_seen;

u_inturg_ptr;

u_shortwindow;

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

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

u_intcurr_ts;

u_intwscale;

//下面是ip包缓冲区

structskbuff*list;

structskbuff*listtail;

}

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

还有

[cpp]viewplaincopyprint?

1.struct tcp_stream  

2.{  

3.  struct tuple4 addr;  

4.  char nids_state;  

5.  struct lurker_node *listeners;  

6.  struct half_stream client;  

7.  struct half_stream server;  

8.  struct tcp_stream *next_node;  

9.  struct tcp_stream *prev_node;  

10.  int hash_index;  

11.  struct tcp_stream *next_time;  

12.  struct tcp_stream *prev_time;  

13.  int read;  

14.  struct tcp_stream *next_free;  

15.  void *user;  

16.};  

structtcp_stream

{

structtuple4addr;

charnids_state;

structlurker_node*listeners;

structhalf_streamclient;

structhalf_streamserver;

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会话了,最后是staticstructtcp_stream**tcp_stream_table;一个TCP会话指针的数组,其实就是hash表了。

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

[cpp]viewplaincopyprint?

1.int tcp_init(int size)  

2.{  

3.  ...  

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

5.  tcp_stream_table_size = size;  

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

7.  if (!

tcp_stream_table) {  

8.    nids_params.no_mem("tcp_init");  

9.    return -1;  

10.  }  

11.   

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

13.  max_stream = 3 * tcp_stream_table_size / 4;  

14.   

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

  

16.  streams_pool = (struct tcp_stream *) malloc((max_stream + 1) * sizeof(struct tcp_stream));  

17.  if (!

streams_pool) {  

18.    nids_params.no_mem("tcp_init");  

19.    return -1;  

20.  }  

21.   

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

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

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

25.  streams_pool[max_stream].next_free = 0;  

26.  free_streams = streams_pool;  

27.   

28.  ...  

29.  return 0;  

30.}  

inttcp_init(intsize)

{

...

//初始化全局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

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会话重组开始了。

来看看。

[cpp]viewplaincopyprint?

1.void process_tcp(u_char * data, int skblen){  

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

3.  struct ip *this_iphdr = (struct ip *)data;  

4.  struct tcphdr *this_tcphdr = (struct tcphdr *)(data + 4 * this_iphdr->ip_hl);  

5.   

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

7.   

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

9.  if (!

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

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

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

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

13.    !

(this_tcphdr->th_flags & TH_ACK) &&  

14.    !

(this_tcphdr->th_flags & TH_RST))  

15.      add_new_tcp(this_tcphdr, this_iphdr);  

16.    //否则,果断忽略  

17.    return;  

18.  }  

19.   

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

21.  if (from_client) {  

22.    snd = &a_tcp->client;  

23.    rcv = &a_tcp->server;  

24.  }  

25.  else {  

26.    rcv = &a_tcp->client;  

27.    snd = &a_tcp->server;  

28.  }  

29.   

30.  //来了一个SYN包  

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

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

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

34.    if (from_client || a_tcp->client.state !

= TCP_SYN_SENT ||  

35.      a_tcp->server.state !

= TCP_CLOSE || !

(this_tcphdr->th_flags & TH_ACK))  

36.      return;  

37.   

38.    //忽略流水号错误的包  

39.    if (a_tcp->client.seq !

= ntohl(this_tcphdr->th_ack))  

40.      return;  

41.   

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

43.    a_tcp->server.state = TCP_SYN_RECV;  

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

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

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

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

48.   

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

50.    //先是时间戳选项  

51.    if (a_tcp->client.ts_on) {  

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

53.    if (!

a_tcp->server.ts_on)  

54.        a_tcp->client.ts_on = 0;  

55.    } else a_tcp->server.ts_on = 0;  

56.    //再是窗口扩大选项  

57.    if (a_tcp->client.wscale_on) {  

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

59.    if (!

a_tcp->server.wscale_on) {  

60.        a_tcp->client.wscale_on = 0;  

61.        a_tcp->client.wscale  = 1;  

62.        a_tcp->server.wscale = 1;  

63.    }  

64.    } else {  

65.        a_tcp->server.wscale_on = 0;  

66.        a_tcp->server.wscale = 1;  

67.    }  

68.    //syn包处理完,返回  

69.    return;  

70.  }  

71.   

72.  if (  

73.    !

 (  !

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

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

75.    ( !

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

76.         

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 党团工作 > 入党转正申请

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1