Suricata-Stream引擎

Thank Zhihao Tao for your hard work. The document spent countless nights and weekends, using his hard work to make it convenient for everyone.
If you have any questions, please send a email to zhihao.tao@outlook.com


1. 流引擎

流引擎(Stream)跟踪TCP连接。该引擎分为两部分:流跟踪引擎和重组引擎。流跟踪引擎监视连接状态,重组引擎将按原样重组流。

2. 流跟踪引擎

2.1 配置选项

2.1.1 memcap选项

流引擎有两个可以设置的memcap。一个用于流跟踪引擎,一个用于重组引擎。

  • 流跟踪引擎将流的信息保留在内存中。有关状态TCP序列号TCP窗口的信息。为了保留此信息,它可以利用memcap允许的容量。
#define STREAMTCP_DEFAULT_MEMCAP                (32 * 1024 * 1024) /* 32mb */
SC_ATOMIC_SET(stream_config.memcap, STREAMTCP_DEFAULT_MEMCAP);
stream:
  memcap: 64mb                # Max memory usage (in bytes) for TCP session tracking
  • 重组引擎必须将数据段保存在内存中,以便能够重建流。为了避免资源不足,使用memcap限制使用的内存。
#define STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP     (64 * 1024 * 1024) /* 64mb */
SC_ATOMIC_SET(stream_config.reassembly_memcap , STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP);
reassembly:
  memcap: 256mb             # Memory reserved for stream data reconstruction (in bytes)

2.1.2 checksum_validation选项

TCP数据包具有所谓的校验和。这是一个内部代码,可以查看数据包是否已到达良好状态。流引擎将不会处理校验和错误的数据包。可以通过输入no代替yes来取消此选项。

stream:
  checksum_validation: yes    # Validate packet checksum, reject packets with invalid checksums.
  1. 收到TCP报文时,需要判断校验和。
TmEcode StreamTcp (ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue *postpq)
{
...
        if (stream_config.flags & STREAMTCP_INIT_FLAG_CHECKSUM_VALIDATION) {
            if (StreamTcpValidateChecksum(p) == 0) {
                StatsIncr(tv, stt->counter_tcp_invalid_checksum);
                return TM_ECODE_OK;
            }
        } else {
            p->flags |= PKT_IGNORE_CHECKSUM;
        }
    } else {
        p->flags |= PKT_IGNORE_CHECKSUM; //TODO check that this is set at creation
    }
  1. 进行校验和校验。
static inline int StreamTcpValidateChecksum(Packet *p)
{
...
    if (p->level4_comp_csum == -1) {
        if (PKT_IS_IPV4(p)) {
            p->level4_comp_csum = TCPChecksum(p->ip4h->s_ip_addrs,
                                              (uint16_t *)p->tcph,
                                              (p->payload_len +
                                                  TCP_GET_HLEN(p)),
                                              p->tcph->th_sum);

2.1.3 prealloc-sessions选项

prealloc_sessions选项指示Suricata在内存中保持多个会话就绪。目的是为了减轻Suricata因快速会话创建而过载的情况,

stream:
  prealloc-sessions: 2k       # 2k sessions prealloc'd per stream thread
  • 默认值
#define STREAMTCP_DEFAULT_PREALLOC              2048
stream_config.prealloc_sessions = STREAMTCP_DEFAULT_PREALLOC;
  • TcpSession创建预分配的内存池。
TmEcode StreamTcpThreadInit(ThreadVars *tv, void *initdata, void **data)
{
...
    SCMutexLock(&ssn_pool_mutex);
    if (ssn_pool == NULL) {
        ssn_pool = PoolThreadInit(1, /* thread */
                0, /* unlimited */
                stream_config.prealloc_sessions,
                sizeof(TcpSession),
                StreamTcpSessionPoolAlloc,
                StreamTcpSessionPoolInit, NULL,
                StreamTcpSessionPoolCleanup, NULL);
        stt->ssn_pool_id = 0;
        SCLogDebug("pool size %d, thread ssn_pool_id %d", PoolThreadSize(ssn_pool), stt->ssn_pool_id);
...

2.1.4 midstream选项

TCP会话以三次握手开始。之后,数据就可以发送或接收。会话可以持续很长时间。
但是通常在启动了几个TCP会话后启动Suricata。Suricata会错过这些会话的原始设置。这个设置总是包含很多信息。如果希望Suricata从那时起检查流,可以通过将midstream选项设置为true来进行检查。默认设置为false

midstream: false             # do not allow midstream 
2.1.4.1 协议识别
  • 多模协议识别时,如果midstream选项是false则不进行检测。
static AppProto AppLayerProtoDetectPMGetProto(
        AppLayerProtoDetectThreadCtx *tctx,
        Flow *f, const uint8_t *buf, uint16_t buflen,
        uint8_t direction, AppProto *pm_results, bool *rflow)
{
...
    /* pattern found, yay */
    if (m > 0) {
        FLOW_SET_PM_DONE(f, direction);
        SCReturnUInt((uint16_t)m);

    /* handle non-found in non-midstream case */
    } else if (!stream_config.midstream) {
        /* we can give up if mpm gave no results and its search depth
         * was reached. */
        if (m < 0) {
            FLOW_SET_PM_DONE(f, direction);
            SCReturnUInt(0);
        } else if (m == 0) {
            SCReturnUInt(0);
        }
        SCReturnUInt((uint16_t)m);

    /* handle non-found in midstream case */
    } else if (m <= 0) {
 ...
  • 端口协议识别时,需要重置流的检查方向,再次进行检测。
static AppProto AppLayerProtoDetectPPGetProto(Flow *f,
        const uint8_t *buf, uint32_t buflen,
        uint8_t ipproto, const uint8_t idir,
        bool *reverse_flow)
{
...
again_midstream:
...
 noparsers:
    if (stream_config.midstream == true && idir == dir) {
        if (idir == STREAM_TOSERVER) {
            dir = STREAM_TOCLIENT;
        } else {
            dir = STREAM_TOSERVER;
        }
        SCLogDebug("no match + midstream, retry the other direction %s",
                (dir == STREAM_TOSERVER) ? "toserver" : "toclient");
        goto again_midstream;
    }
...
2.1.4.2 TCP协议处理
  • midstream选项是false时,没有状态的tcp流不再处理。
static int StreamTcpPacketStateNone(ThreadVars *tv, Packet *p,
                        StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq)
{
...
    } else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) {
        if (stream_config.midstream == FALSE &&
                stream_config.async_oneside == FALSE)
            return 0;
...
    } else if (p->tcph->th_flags & TH_ACK) {
        if (stream_config.midstream == FALSE)
            return 0;
...
  • 重用新的TCP会话
int TcpSessionPacketSsnReuse(const Packet *p, const Flow *f, const void *tcp_ssn)
{
    if (p->proto == IPPROTO_TCP && p->tcph != NULL) {
        if (TcpSessionPacketIsStreamStarter(p) == 1) {
            if (TcpSessionReuseDoneEnough(p, f, tcp_ssn) == 1) {
                return 1;
            }
        }
    }
    return 0;
}

static int TcpSessionPacketIsStreamStarter(const Packet *p)
{
    if (p->tcph->th_flags == TH_SYN) {
        SCLogDebug("packet %"PRIu64" is a stream starter: %02x", p->pcap_cnt, p->tcph->th_flags);
        return 1;
    }

    if (stream_config.midstream == TRUE || stream_config.async_oneside == TRUE) {
        if (p->tcph->th_flags == (TH_SYN|TH_ACK)) {
            SCLogDebug("packet %"PRIu64" is a midstream stream starter: %02x", p->pcap_cnt, p->tcph->th_flags);
            return 1;
        }
    }
    return 0;
}

static int TcpSessionReuseDoneEnough(const Packet *p, const Flow *f, const TcpSession *ssn)
{
    if (p->tcph->th_flags == TH_SYN) {
        return TcpSessionReuseDoneEnoughSyn(p, f, ssn);
    }

    if (stream_config.midstream == TRUE || stream_config.async_oneside == TRUE) {
        if (p->tcph->th_flags == (TH_SYN|TH_ACK)) {
            return TcpSessionReuseDoneEnoughSynAck(p, f, ssn);
        }
    }

    return 0;
}

2.1.5 async_oneside选项

Suricata能够看到连接的所有数据包。不过,有些网络使其更加复杂。一些网络流量遵循与另一部分不同的路径,换句话说:流量是异步的。为了确保Suricata会检查它所看到的一个部分,而不是感到混乱,AsyncOneSide选项将被激活。默认情况下,选项设置为false

async_oneside: false         # do not enable async stream handling
  • async_oneside选项是false时,没有状态的tcp流不再处理。
static int StreamTcpPacketStateNone(ThreadVars *tv, Packet *p,
                        StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq)
{
...
    } else if ((p->tcph->th_flags & (TH_SYN|TH_ACK)) == (TH_SYN|TH_ACK)) {
        if (stream_config.midstream == FALSE &&
                stream_config.async_oneside == FALSE)
            return 0;
...
    } else if (p->tcph->th_flags & TH_ACK) {
        if (stream_config.midstream == FALSE)
            return 0;
...
  • 当我们收到一个SYN包,现在开始接收SYN/ACK时,如果我们从发送SYN的同一个主机接收ACK,这意味着ASNYC流
static int StreamTcpPacketStateSynSent(ThreadVars *tv, Packet *p,
                        StreamTcpThread *stt, TcpSession *ssn, PacketQueue *pq)
{
...
    } else if (p->tcph->th_flags & TH_ACK) {
        /* Handle the asynchronous stream, when we receive a  SYN packet
           and now istead of receving a SYN/ACK we receive a ACK from the
           same host, which sent the SYN, this suggests the ASNYC streams.*/
        if (stream_config.async_oneside == FALSE)
            return 0;

...
  • 在TCP的TCP_SYN_RECV状态,收到ACK报文,如果数据包的序列号等于预期的序列号处理数据。
static int StreamTcpPacketStateSynRecv(ThreadVars *tv, Packet *p,
...
    } else if (p->tcph->th_flags & TH_ACK) {
...
            /* If asynchronous stream handling is allowed then set the session,
               if packet's seq number is equal the expected seq no.*/
        } else if (stream_config.async_oneside == TRUE &&
                (SEQ_EQ(TCP_GET_SEQ(p), ssn->server.next_seq)))
        {
            /*set the ASYNC flag used to indicate the session as async stream
              and helps in relaxing the windows checks.*/
...
  • 其他状态参加源码。

2.1.6 inline选项

Suricata在normal/IDS模式下分块检查内容。在inline/IPS模式下,在滑动窗口方式检查内容。

inline: no                   # stream inline mode

如果Suricata被设置为inline模式,它必须在发送到接收方之前立即检查数据包。通过这种方式,Suricata能够在需要时直接***丢弃***数据包。

2.1.6.1 IDS模式

IDS模式下,Suricata以块的形式检查流量。属于通常的检测模式。
IDS

2.1.6.2 IPS模式

Inline/IPS模式下,Suricata以滑动窗口的方式检查流量。
IPS

2.1.7 drop-invalid选项

drop-invalid选项可以设置为no,以避免阻止流引擎认为无效的数据包。这对于覆盖某些第2层IPS设置中出现的一些奇怪情况非常有用。

drop-invalid: yes           # in inline mode, drop packets that are invalid with regards to streaming engine

2.1.8 bypass选项

当会话的任一侧达到其深度时,旁路选项将激活流/会话的bypass
警告
绕道会导致重要交通流丢失。小心使用。

bypass: no                  # Bypass packets when stream.reassembly.depth is reached.

2.1.9 max-synack-queued选项

最大的SYN/ACK入队数。

#define STREAMTCP_DEFAULT_MAX_SYNACK_QUEUED     5
max-synack-queued: 5        # Max different SYN/ACKs to queue

3. 流重组引擎

流重组
在这里插入图片描述

3.1 配置选项

3.1.1 memcap选项

流引擎有两个可以设置的memcap。一个用于流跟踪引擎,一个用于重组引擎。

  • 流跟踪引擎将流的信息保留在内存中。有关状态TCP序列号TCP窗口的信息。为了保留此信息,它可以利用memcap允许的容量。
#define STREAMTCP_DEFAULT_MEMCAP                (32 * 1024 * 1024) /* 32mb */
SC_ATOMIC_SET(stream_config.memcap, STREAMTCP_DEFAULT_MEMCAP);
stream:
  memcap: 64mb                # Max memory usage (in bytes) for TCP session tracking
  • 重组引擎必须将数据段保存在内存中,以便能够重建流。为了避免资源不足,使用memcap限制使用的内存。
#define STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP     (64 * 1024 * 1024) /* 64mb */
SC_ATOMIC_SET(stream_config.reassembly_memcap , STREAMTCP_DEFAULT_REASSEMBLY_MEMCAP);
reassembly:
  memcap: 256mb             # Memory reserved for stream data reconstruction (in bytes)

3.1.2 depth选项

depth选项,可以控制流重组的范围。默认为1MB。执行文件提取的协议分析器可以覆盖每个流上的depth设置。

depth: 1mb                # The depth of the reassembling.

3.1.3 chunk_size选项

对重组数据是进行分块检查。这些块的大小设置toserver_chunk_sizetoclient_chunk_size。为了避免边界的可预测性,可以通过添加一个随机因素来改变其大小。

toserver_chunk_size: 2560 # inspect raw stream in chunks of at least this size
toclient_chunk_size: 2560 # inspect raw stream in chunks of at least
randomize-chunk-size: yes
#randomize-chunk-range: 10

3.1.4 raw选项

raw重组选项装通过简单的内容,pcre关键字利用和其他未在特定协议缓冲区(如http_uri)上执行的有效负载检查来完成的。这种类型的重新组装可以关闭。

reassembly:
  raw: no

3.1.5 segment-prealloc选项

传入段存储在流中的列表中。为了避免恒定的内存分配,使用了每个线程内存池。

reassembly:
  segment-prealloc: 2048    # pre-alloc 2k segments per thread

3.1.6 check-overlap-different-data选项

在同一序列号上重新发送不同的数据是一种扰乱网络检测的方法。

reassembly:
  check-overlap-different-data: true

3.1.7 IDS和IPS对于重组数据的处理

  • Normal/IDS对确认的数据进行重组
    IDS

  • Inline/IPS对未确认的数据进行重组
    IPS

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

phantasms

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

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

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

打赏作者

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

抵扣说明:

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

余额充值