写点什么

网络入侵检测系统之 Suricata(十一)--TCP 重组实现详解

作者:于顾而言
  • 2022 年 9 月 17 日
    江苏
  • 本文字数:2490 字

    阅读完需:约 8 分钟

网络入侵检测系统之Suricata(十一)--TCP重组实现详解

TCP 重组一直是入侵检测系统中最为重要也是最难的一部分,它涉及到全流量的缓存,因此存储消耗十分巨大,据统计 100 万的会话就要产生 1G~10G 的内存缓存,因此设计一套 TCP 重组优化的算法十分必要,目前优化的办法有两种,一种是尽量不去 TCP 重组减少缓存包括红绿名单,配置,抽样算法,另一种就是将重组下沉到硬件例如 FPGA,减少以软件方式缓存。我横向对比了三种目前流行的入侵检测系统,,看看 TCP 软件重组上这三种 IDS/IPS 系统有什么优化点:

QNSM首页、文档和下载 - 高性能网络安全监控引擎 - OSCHINA - 中文开源技术交流社区

Snort - Network Intrusion Detection & Prevention System

GitHub - OISF/suricata: Suricata git repository maintained by the OISF

QSNM 实现

  • QSNM 是否进行流重组,以条件编译确定__QNSM_STREAM_REASSEMBLE,默认配置中是不进行 TCP 流重组的

  • 同一个流的 TCP 都会进行流重组,上下行都在一个缓存队列中,最大支持 8 个报文,且不考虑重叠部分

  • 重组方法基于 hashmap + 双向链表

  • TCP 流缓存删除方式:1. 老化 2. 无需进一步解析 3. 命中规则



/** tcp stream reassemble */... ...qnsm_list_for_each_prev_entry(tmp_tcp_data, &que->tcp_que, node) { same_dir = (dir == tmp_tcp_data->dir); sort1 = cur_tcp_data->seq; if (same_dir)    {  sort2 = tmp_tcp_data->seq;    }  else     {  sort2 = tmp_tcp_data->ack;    } diff = packet_sequence_diff(sort2, sort1); /* 根据diff进行链表insert */ ... ... QNSM_LIST_ADD_AFTER(&cur_tcp_data->node, &tmp_tcp_data->node); que->data_cnt++; cache->cur_pkt_num++;}... ...
复制代码
  1. tcp_queue is null



  1. cur_seg->seq > tmp_seg->seq/ack




cur_seg->seq = tmp_seg->seq/ack




Snort 实现

  • Snort 是否进行流重组,以配置决定,默认配置中是进行 TCP 流重组的


if ( SEQ_GT(rcv->r_win_base, tdb->seq) ){ //Received data segment whose seq no is less than already ACKed bytes if(SEQ_GT(rcv->r_nxt_ack, tdb->seq))    { //We have already seen the data and this is a retransmission with  different packet size //Add the packet to seglist if the size is more than offset uint32_t offset = rcv->r_win_base - tdb->seq; if ( offset < p->dsize )        { tdb->seq += offset; p->data += offset; p->dsize -= (uint16_t)offset; StreamQueue(rcv, p, tdb, tcpssn); //Restore the original seq and dsize before the packet is egressed p->dsize += (uint16_t)offset; p->data -= offset; tdb->seq -= offset;        }    } else    { //We have NOT seen the data. Add it to stream queue StreamQueue(rcv, p, tdb, tcpssn);    }}else StreamQueue(rcv, p, tdb, tcpssn);
复制代码
  • Reassemble 重组方法基于 hashmap + 双向链表:

typedef struct _StreamTracker{ StreamTcpPolicy *tcp_policy; StreamSegment *seglist;       /* first queued segment */ StreamSegment *seglist_tail;  /* last queued segment */ /* Local in the context of these variables means the local part * of the connection.  For example, if this particular StreamTracker * was tracking the client side of a connection, the l_unackd value * would represent the client side of the connection's last unacked * sequence number */ uint32_t l_unackd;     /* local unack'd seq number */ uint32_t l_nxt_seq;    /* local next expected sequence */ uint32_t l_window;     /* local receive window */ uint32_t r_nxt_ack;    /* next expected ack from remote side */ uint32_t r_win_base;   /* remote side window base sequence number * (i.e. the last ack we got) */ uint32_t isn;          /* initial sequence number */ uint32_t ts_last;      /* last timestamp (for PAWS) */ uint32_t ts_last_pkt;  /* last packet timestamp we got */ uint32_t seglist_base_seq;   /* seq of first queued segment */ uint32_t seg_count;          /* number of current queued segments */ uint32_t seg_bytes_total;    /* total bytes currently queued */ uint32_t seg_bytes_logical;  /* logical bytes queued (total - overlaps) */ uint32_t total_bytes_queued; /* total bytes queued (life of session) */ uint32_t total_segs_queued;  /* number of segments queued (life) */ uint32_t overlap_count;      /* overlaps encountered */ uint32_t small_seg_count; uint16_t reassembly_policy;} StreamTracker;typedef struct _StreamSegment{ uint8_t *data; uint8_t *payload; struct _StreamSegment *prev; struct _StreamSegment *next; struct timeval tv; uint32_t caplen; uint32_t pktlen; uint32_t ts; uint32_t seq; uint16_t orig_dsize; uint16_t size; } StreamSegment;typedef struct _TcpSession{ StreamTracker client; StreamTracker server;    ...}
复制代码
  1. SEQ_EQ(tdb->seq, tail->seq + tail->size)


    /* segment fit cleanly at the end of the segment list */

  2. SEQ_LEQ(dist_head, dist_tail) /* Start iterating at the head (left) */

  3. handle left overlaps

  4. handle right overlaps



Windows/BSD 倾向于原始报文,除了后续报文起始序列号在原始报文前这种情况。Windows/BSD: <1><1><1><4><4><2><3><3><3><6><6><6><7><7><7><3.3><3.3><3.3><3.4><3.4><3.5><3.5><3.6><11>Linux 倾向于原始报文,除了后续报文起始序列号在原始报文之前,或后续报文起始序列号相同但终止序列号在原始报文后的情况 Linux: <1><1><1><4><4><2><3><3><3><6><6><6><7><7><7><3.3><3.3><3.3><3.4><3.4><3.5><3.5><11><11>



Suricata 实现




Reference

  1. snort_manual.pdf

  2. snort-2.9.16.1 code

  3. qnsm-master code

  4. 解析Snort的TCP流重组

  5. suricata5.0 code

  6. suricata 6.0.3 tcp reassemble

发布于: 刚刚阅读数: 3
用户头像

于顾而言

关注

| 诗酒趁年华诗酒 √ | 2022.09.10 加入

| NJUST Optical M.A. | | SASE | SangFor | Senior Developer | 知乎专栏:https://www.zhihu.com/people/whisper-of-the-Koo

评论

发布
暂无评论
网络入侵检测系统之Suricata(十一)--TCP重组实现详解_网络安全_于顾而言_InfoQ写作社区