]> git.ipfire.org Git - people/ms/suricata.git/blame - doc/devguide/extending/app-layer/transactions.rst
devguide/transactions: add TSL_STATE enum snippet
[people/ms/suricata.git] / doc / devguide / extending / app-layer / transactions.rst
CommitLineData
a5b344e0
JF
1************
2Transactions
3************
4
5.. contents:: Table of Contents
6
7General Concepts
8================
9
84311ab1 10For Suricata, transactions are an abstraction that help with detecting and logging. An example of a complete transaction is
a5b344e0
JF
11a pair of messages in the form of a request (from client to server) and a response (from server to client) in HTTP.
12
13In order to know when to log an event for a given protocol, the engine tracks the progress of each transaction - that
14is, when is it complete, or when it reaches a key intermediate state. They aid during the detection phase,
15when dealing with protocols that can have large PDUs (protocol data units), like TCP, in controlling state for partial rule matching -- in case of rules that mention more than one field.
16
17Transactions are implemented and stored in the per-flow state. The engine interacts with them using a set of callbacks the parser registers.
18
19How the engine uses transactions
20================================
21
22Logging
23~~~~~~~
24
25Suricata controls when logging should happen based on transaction completeness. For simpler protocols, such as ``dns``
26or ``ntp``, that will most
27likely happen once per transaction, by the time of its completion. In other cases, like with HTTP, this may happen at intermediary states.
28
29In ``OutputTxLog``, the engine will compare current state with the value defined for the logging to happen, per flow
30direction (``logger->tc_log_progress``, ``logger->ts_log_progress``). If state is less than that value, the engine skips to
31the next logger. Code snippet from: suricata/src/output-tx.c:
32
33.. code-block:: c
34
35 static TmEcode OutputTxLog(ThreadVars *tv, Packet *p, void *thread_data)
36 {
37 .
38 .
39 .
40 if ((ts_eof && tc_eof) || last_pseudo) {
41 SCLogDebug("EOF, so log now");
42 } else {
43 if (logger->LogCondition) {
44 int r = logger->LogCondition(tv, p, alstate, tx, tx_id);
45 if (r == FALSE) {
46 SCLogDebug("conditions not met, not logging");
47 goto next_logger;
48 }
49 } else {
50 if (tx_progress_tc < logger->tc_log_progress) {
51 SCLogDebug("progress not far enough, not logging");
52 goto next_logger;
53 }
54
55 if (tx_progress_ts < logger->ts_log_progress) {
56 SCLogDebug("progress not far enough, not logging");
57 goto next_logger;
58 }
59 }
60 }
61 .
62 .
63 .
64 }
65
66Rule Matching
67~~~~~~~~~~~~~
68
69Transaction progress is also used for certain keywords to know what is the minimum state before we can expect a match: until that, Suricata won't even try to look for the patterns.
70
71As seen in ``DetectAppLayerMpmRegister2`` that has ``int progress`` as parameter, and ``DetectAppLayerInspectEngineRegister2``, which expects ``int tx_min_progress``, for instance. In the code snippet,
72``HTTP2StateDataClient``, ``HTTP2StateDataServer`` and ``0`` are the values passed to the functions.
73
74
75.. code-block:: c
76
77 void DetectFiledataRegister(void)
78 {
79 .
80 .
81 DetectAppLayerMpmRegister2("file_data", SIG_FLAG_TOSERVER, 2,
82 PrefilterMpmFiledataRegister, NULL,
83 ALPROTO_HTTP2, HTTP2StateDataClient);
84 DetectAppLayerMpmRegister2("file_data", SIG_FLAG_TOCLIENT, 2,
85 PrefilterMpmFiledataRegister, NULL,
86 ALPROTO_HTTP2, HTTP2StateDataServer);
87 .
88 .
89 DetectAppLayerInspectEngineRegister2("file_data",
90 ALPROTO_HTTP2, SIG_FLAG_TOCLIENT, HTTP2StateDataServer,
91 DetectEngineInspectFiledata, NULL);
92 DetectAppLayerInspectEngineRegister2(
93 "file_data", ALPROTO_FTPDATA, SIG_FLAG_TOSERVER, 0, DetectEngineInspectFiledata, NULL);
94 .
95 .
96 }
97
98Progress Tracking
99=================
100
101As a rule of thumb, transactions will follow a request-response model: if a transaction has had a request and a response, it is complete.
102
103But if a protocol has situations where a request or response won’t expect or generate a message from its counterpart,
104it is also possible to have uni-directional transactions. In such cases, transaction is set to complete at the moment of
105creation.
106
107For example, DNS responses may be considered as completed transactions, because they also contain the request data, so
108all information needed for logging and detection can be found in the response.
109
110In addition, for file transfer protocols, or similar ones where there may be several messages before the file exchange
111is completed (NFS, SMB), it is possible to create a level of abstraction to handle such complexity. This could be achieved by adding phases to the model implemented by the protocol (e.g., protocol negotiation phase (SMB), request parsed (HTTP), and so on).
112
113This is controlled by implementing progress states. In Suricata, those will be enums that are incremented as the parsing
114progresses. A state will start at 0. The higher its value, the closer the transaction would be to completion.
115
84311ab1 116The engine interacts with transactions' state using a set of callbacks the parser registers. State is defined per flow direction (``STREAM_TOSERVER`` / ``STREAM_TOCLIENT``).
a5b344e0
JF
117
118In Summary - Transactions and State
119~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
120
121- Initial state value: ``0``.
122- Simpler scenarios: state is simply an int. ``1`` represents transaction completion, per direction.
123- Complex Transaction State in Suricata: ``enum`` (Rust: ``i32``). Completion is indicated by the highest enum value (some examples are: SSH, HTTP, HTTP2, DNS, SMB).
124
125Examples
126========
127
128This section shares some examples from Suricata codebase, to help visualize how Transactions work and are handled by the engine.
129
130Enums
131~~~~~
132
133Code snippet from: rust/src/ssh/ssh.rs:
134
135.. code-block:: rust
136
137 pub enum SSHConnectionState {
138 SshStateInProgress = 0,
139 SshStateBannerWaitEol = 1,
140 SshStateBannerDone = 2,
141 SshStateFinished = 3,
142 }
143
144From src/app-layer-ftp.h:
145
146.. code-block:: c
147
148 enum {
149 FTP_STATE_IN_PROGRESS,
150 FTP_STATE_PORT_DONE,
151 FTP_STATE_FINISHED,
152 };
153
f65b3908
JF
154From src/app-layer-ssl.h:
155
156.. code-block:: c
157
158 enum {
159 TLS_STATE_IN_PROGRESS = 0,
160 TLS_STATE_CERT_READY = 1,
161 TLS_HANDSHAKE_DONE = 2,
162 TLS_STATE_FINISHED = 3
163 };
164
a5b344e0
JF
165API Callbacks
166~~~~~~~~~~~~~
167
168In Rust, this is done via the RustParser struct. As seen in rust/src/applayer.rs:
169
170.. code-block:: rust
171
172 /// Rust parser declaration
173 pub struct RustParser {
174 .
175 .
176 .
177 /// Progress values at which the tx is considered complete in a direction
178 pub tx_comp_st_ts: c_int,
179 pub tx_comp_st_tc: c_int,
180 .
181 .
182 .
183 }
184
185In C, the callback API is:
186
187.. code-block:: c
188
189 void AppLayerParserRegisterStateProgressCompletionStatus(
190 AppProto alproto, const int ts, const int tc)
191
192Simple scenario described, in Rust:
193
194rust/src/dhcp/dhcp.rs:
195
196.. code-block:: rust
197
198 tx_comp_st_ts: 1
199 tx_comp_st_tc: 1
200
201For SSH, this looks like this:
202
203rust/src/ssh/ssh.rs:
204
205.. code-block:: rust
206
207 tx_comp_st_ts: SSHConnectionState::SshStateFinished as i32,
208 tx_comp_st_tc: SSHConnectionState::SshStateFinished as i32,
209
210In C, callback usage would be as follows:
211
212src/app-layer-dcerpc.c:
213
214.. code-block:: c
215
216 AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_DCERPC, 1, 1);
217
218src/app-layer-ftp.c:
219
220.. code-block:: c
221
222 AppLayerParserRegisterStateProgressCompletionStatus(
223 ALPROTO_FTP, FTP_STATE_FINISHED, FTP_STATE_FINISHED);
224
225Sequence Diagrams
226~~~~~~~~~~~~~~~~~
227
228A DNS transaction in Suricata can be considered unidirectional:
229
d6c5dfac 230.. image:: img/DnsUnidirectionalTransactions.png
a5b344e0
JF
231 :width: 600
232 :alt: A sequence diagram with two entities, Client and Server, with an arrow going from the Client to the Server, labeled "DNS Request". After that, there is a dotted line labeled "Transaction Completed".
233
234An HTTP2 transaction is an example of a bidirectional transaction, in Suricata (note that transactions in HTTP2 may
235overlap, scenario not shown in this Sequence Diagram):
236
237.. TODO add another example for overlapping HTTP2 transaction
238
239.. image:: img/HTTP2BidirectionalTransaction.png
240 :width: 600
241 :alt: A sequence diagram with two entities, Client and Server, with an arrow going from the Client to the Server labeled "Request" and below that an arrow going from Server to Client labeled "Response". Below those arrows, a dotted line indicates that the transaction is completed.
242
243A TLS Handshake is a more complex example, where several messages are exchanged before the transaction is considered completed:
244
245.. image:: img/TlsHandshake.png
246 :width: 600
247 :alt: A sequence diagram with two entities, Client and Server, with an arrow going from the Client to the Server labeled "ClientHello" and below that an arrow going from Server to Client labeled "ServerHello". Below those arrows, several more follow from Server to Client and vice-versa, before a dotted line indicates that the transaction is finally completed.
248
249Template Protocol
250~~~~~~~~~~~~~~~~~
251
252Suricata has a template protocol for educational purposes, which has simple bidirectional transactions.
253
254A completed transaction for the template looks like this:
255
256.. image:: img/TemplateTransaction.png
257 :width: 600
258 :alt: A sequence diagram with two entities, Client and Server, with an arrow going from the Client to the Server, labeled "Request". An arrow below that first one goes from Server to Client.
259
260Following are the functions that check whether a transaction is considered completed, for the Template Protocol. Those are called by the Suricata API. Similar functions exist for each protocol, and may present implementation differences, based on what is considered a transaction for that given protocol.
261
262In C:
263
264.. code-block:: c
265
266 static int TemplateGetStateProgress(void *txv, uint8_t direction)
267 {
268 TemplateTransaction *tx = txv;
269
270 SCLogNotice("Transaction progress requested for tx ID %"PRIu64
271 ", direction=0x%02x", tx->tx_id, direction);
272
273 if (direction & STREAM_TOCLIENT && tx->response_done) {
274 return 1;
275 }
276 else if (direction & STREAM_TOSERVER) {
277 /* For the template, just the existence of the transaction means the
278 * request is done. */
279 return 1;
280 }
281
282 return 0;
283 }
284
285And in Rust:
286
287.. code-block:: rust
288
289 pub extern "C" fn rs_template_tx_get_alstate_progress(
290 tx: *mut std::os::raw::c_void,
291 _direction: u8,
292 ) -> std::os::raw::c_int {
293 let tx = cast_pointer!(tx, TemplateTransaction);
294
295 // Transaction is done if we have a response.
296 if tx.response.is_some() {
297 return 1;
298 }
299 return 0;
300 }
301
302Work In Progress changes
303========================
304
305Currently we are working to have files be part of the transaction instead of the per-flow state, as seen in https://redmine.openinfosecfoundation.org/issues/4444.
306
84311ab1 307Another work in progress is to limit the number of transactions per flow, to prevent Denial of Service (DoS) by quadratic complexity - a type of attack that may happen to protocols which can have multiple transactions at the same time - such as HTTP2 so-called streams (see https://redmine.openinfosecfoundation.org/issues/4530).
a5b344e0
JF
308
309Common words and abbreviations
310==============================
311
312- al, applayer: application layer
313- alproto: application layer protocol
314- alstate: application layer state
315- engine: refers to Suricata core detection logic
316- flow: a bidirectional flow of packets with the same 5-tuple elements (protocol, source ip, destination ip, source port, destination port. Vlans can be added as well)
317- PDU: Protocol Data Unit
318- rs: rust
319- tc: to client
320- ts: to server
321- tx: transaction