Suricata-流的处理

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 [email protected]

1. 流的内存处理

在suricata中跟踪流就需要使用内存。流越多,所需的内存就越多。
因此我们要保持对内存使用的控制,有几个选项:

  • 用于设置流引擎将使用的最大字节数的memcap选项
  • 用于设置哈希表大小的哈希大小
  • 用于以下内容的预分配:

    • 对于还不属于流的数据包,Suricata创建了一个新的流。这是一个相对昂贵的行动。
    • 由此带来的风险是,攻击者/黑客可以在此部分攻击引擎系统。
    • 当它们确保一台计算机获得许多具有不同元组的数据包时,引擎必须生成许多新的流。
    • 这样,攻击者就可以淹没系统。
    • 为了减轻引擎过载,此选项指示Suricata在内存中保持多个流就绪。这样一来,Suricata就不那么容易受到此类攻击。
流引擎有一个独立于包处理的管理线程。这个线程称为流管理器。该线程确保尽可能在Memcap内。将准备10000个流。
flow:
  memcap: 33554432              #The maximum amount of bytes the flow-engine will make use of.
  hash_size: 65536              #Flows will be organized in a hash-table. With this option you can set the
                                #size of the hash-table.
  Prealloc: 10000               #The amount of flows Suricata has to keep ready in memory.
  emergency_recovery: 30                  #Percentage of 1000 prealloc'd flows.
  prune_flows: 5                          #Amount of flows being terminated during the emergency mode.

1.1 memcap选项

memcap选项用于设置流引擎将使用的最大字节数。

  • 默认memcap为32M,即33554432字节。
#define FLOW_DEFAULT_MEMCAP      (32 * 1024 * 1024) /* 32 MB */
SC_ATOMIC_SET(flow_config.memcap, FLOW_DEFAULT_MEMCAP);
  • 通过FLOW_CHECK_MEMCAP来检查内存分配的字节数是否超过了memcap。
/** \brief check if a memory alloc would fit in the memcap
 *
 *  \param size memory allocation size to check
 *
 *  \retval 1 it fits
 *  \retval 0 no fit
 */
#define FLOW_CHECK_MEMCAP(size) \
    ((((uint64_t)SC_ATOMIC_GET(flow_memuse) + (uint64_t)(size)) <= SC_ATOMIC_GET(flow_config.memcap)))

1.2 hash_size选项

hash_size选项用于设置哈希表大小的哈希大小。

  • hash种子是一个随机数。
  • hash大小默认为65536。
#define FLOW_DEFAULT_HASHSIZE    65536
flow_config.hash_rand   = (uint32_t)RandomGet();
flow_config.hash_size   = FLOW_DEFAULT_HASHSIZE;

1.3 prealloc选项

prealloc选项用于设置内存中预分配流的数量。

#define FLOW_DEFAULT_PREALLOC    10000
flow_config.prealloc    = FLOW_DEFAULT_PREALLOC;
/* pre allocate flows */
for (i = 0; i < flow_config.prealloc; i++) {
    if (!(FLOW_CHECK_MEMCAP(sizeof(Flow) + FlowStorageSize()))) {
        SCLogError(SC_ERR_FLOW_INIT, "preallocating flows failed: "
                "max flow memcap reached. Memcap %"PRIu64", "
                "Memuse %"PRIu64".", SC_ATOMIC_GET(flow_config.memcap),
                ((uint64_t)SC_ATOMIC_GET(flow_memuse) + (uint64_t)sizeof(Flow)));
        exit(EXIT_FAILURE);
    }

    Flow *f = FlowAlloc();
    if (f == NULL) {
        SCLogError(SC_ERR_FLOW_INIT, "preallocating flow failed: %s", strerror(errno));
        exit(EXIT_FAILURE);
    }

    FlowEnqueue(&flow_spare_q,f);
}

1.4 emergency_recovery选项

emergency_recovery选项使得流引擎进入紧急模式。在此模式下,引擎将利用较短的超时时间。其让流利用较短的超时时间,它使流以更积极的方式过期,因此将有更多空间容纳新的流。

  • 紧急恢复。紧急恢复设置为30。这是预分配流的百分比,在此百分比之后,流引擎将恢复正常(当10000个流中的30%完成时)。
  • 修剪流。如果在紧急模式中,过度超时没有所需的结果,则此选项是最终的解决方案。它结束了一些流,即使他们还没有达到他们的超时时间。修剪流选项显示每次设置新流时将终止的流的数量。
#define FLOW_DEFAULT_EMERGENCY_RECOVERY 30
flow_config.emergency_recovery = FLOW_DEFAULT_EMERGENCY_RECOVERY;

1.4.1 紧急模式进入

  1. 获取新的Flow
static Flow *FlowGetNew(ThreadVars *tv, DecodeThreadVars *dtv, const Packet *p)
{
...
    f = FlowDequeue(&flow_spare_q);
    if (f == NULL) {
        /* If we reached the max memcap, we get a used flow */
  1. 如果达到MEMCAP后,进入紧急模式,超时时间改为紧急超时时间。
        if (!(FLOW_CHECK_MEMCAP(sizeof(Flow) + FlowStorageSize()))) {
            /* declare state of emergency */
            if (!(SC_ATOMIC_GET(flow_flags) & FLOW_EMERGENCY)) {
                SC_ATOMIC_OR(flow_flags, FLOW_EMERGENCY);

                FlowTimeoutsEmergency();

                /* under high load, waking up the flow mgr each time leads
                 * to high cpu usage. Flows are not timed out much faster if
                 * we check a 1000 times a second. */
                FlowWakeupFlowManagerThread();
            }

            f = FlowGetUsedFlow(tv, dtv);
  1. 遍历哈希,直到可以释放流。

    • 不要修剪包或流消息在使用的流。
    • 输出日志。
    • flow_prune_idx确保我们不会每次都从顶部开始,因为那样会清除散列的顶部,从而导致在高压下搜索时间越来越长。
static Flow *FlowGetUsedFlow(ThreadVars *tv, DecodeThreadVars *dtv)
{
...
        if (SC_ATOMIC_GET(f->use_cnt) > 0) {
            FBLOCK_UNLOCK(fb);
            FLOWLOCK_UNLOCK(f);
            continue;
        }
  1. 从hash中删除,设置FORCED和EMERGENCY标志。
        /* remove from the hash */

        f->flow_end_flags |= FLOW_END_FLAG_FORCED;

        if (SC_ATOMIC_GET(flow_flags) & FLOW_EMERGENCY)
            f->flow_end_flags |= FLOW_END_FLAG_EMERGENCY;
  1. log记录,清除旧内存,初始为新状态,增加flow_prune_idx
        /* invoke flow log api */
        if (dtv && dtv->output_flow_thread_data)
            (void)OutputFlowLog(tv, dtv->output_flow_thread_data, f);

        FlowClearMemory(f, f->protomap);

        FlowUpdateState(f, FLOW_STATE_NEW);

        FLOWLOCK_UNLOCK(f);

        (void) SC_ATOMIC_ADD(flow_prune_idx, (flow_config.hash_size - cnt));

1.4.1 紧急模式退出

  1. 获取spare队列中的flow数
static TmEcode FlowManager(ThreadVars *th_v, void *thread_data)
{
...
        uint32_t len = 0;
        FQLOCK_LOCK(&flow_spare_q);
        len = flow_spare_q.len;
        FQLOCK_UNLOCK(&flow_spare_q);
        StatsSetUI64(th_v, ftd->flow_mgr_spare, (uint64_t)len);
  1. 如果可用flow与预分配流的百分比大于emergency_recovery选项的配置。
            if (len * 100 / flow_config.prealloc > flow_config.emergency_recovery) {
                SC_ATOMIC_AND(flow_flags, ~FLOW_EMERGENCY);
  1. 恢复正常的超时时间,退出紧急状态。
                FlowTimeoutsReset();

未完待续...

你可能感兴趣的:(网络安全)