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. 流引擎
- 2. 流跟踪引擎
- 2.1 配置选项
- 2.1.1 `memcap`选项
- 2.1.2 `checksum_validation`选项
- 2.1.3 `prealloc-sessions`选项
- 2.1.4 `midstream`选项
- 2.1.4.1 协议识别
- 2.1.4.2 TCP协议处理
- 2.1.5 `async_oneside`选项
- 2.1.6 `inline`选项
- 2.1.6.1 IDS模式
- 2.1.6.2 IPS模式
- 2.1.7 `drop-invalid`选项
- 2.1.8 `bypass`选项
- 2.1.9 `max-synack-queued`选项
- 3. 流重组引擎
- 3.1 配置选项
- 3.1.1 `memcap`选项
- 3.1.2 `depth`选项
- 3.1.3 `chunk_size`选项
- 3.1.4 `raw`选项
- 3.1.5 `segment-prealloc`选项
- 3.1.6 `check-overlap-different-data`选项
- 3.1.7 IDS和IPS对于重组数据的处理
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.
- 收到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}
- 进行校验和校验。
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 packetand now istead of receving a SYN/ACK we receive a ACK from thesame 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 streamand 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以块的形式检查流量。属于通常的检测模式。
2.1.6.2 IPS模式
Inline/IPS
模式下,Suricata以滑动窗口的方式检查流量。
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_size
和toclient_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
对确认的数据进行重组
-
Inline/IPS
对未确认的数据进行重组