网络入侵检测系统之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

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

Snort实现

  • Snort是否进行流重组,以配置决定,默认配置中是进行TCP流重组的
track tcpTrack sessions for TCP. The default is ”yes”
max tcpMaximum simultaneous TCP sessions tracked. The default is 262144,maximum is 1048576,minimum is 2
memcapMemcap for TCP packet storage. The default is 8388608 (8MB), maximum is 1073741824(1GB), minimum is 32768(32KB)
timeoutSession timeout. The default is 30, the minimum is 1, and the maximum is 86400 (approximately 1 day).
policyThe Operating System policy for the target OS
overlap limitLimits the number of overlapping packets per session. The default is 0(unlimited)
max_queued_bytesLimit the number of bytes queued for reassembly on a given TCP session to bytes. Default is 1048576(1MB).
max_queued_segLimit the number of segments queued for reassembly on a given TCP session. The default is 2621
ignore_portsThe default is 0(unlimited)
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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

于顾而言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值