suricata
app-layer-expectation.c
Go to the documentation of this file.
1/* Copyright (C) 2017-2021 Open Information Security Foundation
2 *
3 * You can copy, redistribute or modify this Program under the terms of
4 * the GNU General Public License version 2 as published by the Free
5 * Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * version 2 along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 * 02110-1301, USA.
16 */
17
18/**
19 * \defgroup applayerexpectation Application Layer Expectation
20 *
21 * Handling of dynamic parallel connection for application layer similar
22 * to FTP.
23 *
24 * @{
25 *
26 * Some protocols like FTP create dynamic parallel flow (called expectation). In
27 * order to assign a application layer protocol to these expectation, Suricata
28 * needs to parse message of the initial protocol and create and maintain a list
29 * of expected flow.
30 *
31 * Application layers must use the here described API to implement this mechanism.
32 *
33 * When parsing a application layer message describing a parallel flow, the
34 * application layer can call AppLayerExpectationCreate() to declare an
35 * expectation. By doing that the next flow coming with corresponding IP parameters
36 * will be assigned the specified application layer. The resulting Flow will
37 * also have a Flow storage set that can be retrieved at index
38 * AppLayerExpectationGetDataId():
39 *
40 * ```
41 * data = (char *)FlowGetStorageById(f, AppLayerExpectationGetFlowId());
42 * ```
43 * This storage can be used to store information that are only available in the
44 * parent connection and could be useful in the parent connection. For instance
45 * this is used by the FTP protocol to propagate information such as file name
46 * and ftp operation to the FTP data connection.
47 */
48
49/**
50 * \file
51 *
52 * \author Eric Leblond <eric@regit.org>
53 */
54
55#include "queue.h"
56#include "suricata-common.h"
57
58#include "ippair-storage.h"
59#include "flow-storage.h"
60
62
63#include "util-print.h"
64
65static IPPairStorageId g_ippair_expectation_id = { .id = -1 };
66static FlowStorageId g_flow_expectation_id = { .id = -1 };
67
68SC_ATOMIC_DECLARE(uint32_t, expectation_count);
69
70#define EXPECTATION_TIMEOUT 30
71#define EXPECTATION_MAX_LEVEL 10
72
73typedef struct Expectation_ {
79 /* use pointer to Flow as identifier of the Flow the expectation is linked to */
80 void *orig_f;
81 void *data;
84
85typedef struct ExpectationData_ {
86 /** Start of Expectation Data structure must be a pointer
87 * to free function. Set to NULL to use SCFree() */
88 void (*DFree)(void *);
90
91typedef struct ExpectationList_ {
92 CIRCLEQ_HEAD(EList, Expectation_) list;
93 uint8_t length;
95
96static void ExpectationDataFree(void *e)
97{
98 SCLogDebug("Free expectation data");
100 if (ed->DFree) {
101 ed->DFree(e);
102 } else {
103 SCFree(e);
104 }
105}
106
107/**
108 * Free expectation
109 */
110static void AppLayerFreeExpectation(Expectation *exp)
111{
112 if (exp->data) {
113 ExpectationData *expdata = (ExpectationData *)exp->data;
114 if (expdata->DFree) {
115 expdata->DFree(exp->data);
116 } else {
117 SCFree(exp->data);
118 }
119 }
120 SCFree(exp);
121}
122
123static void ExpectationListFree(void *el)
124{
125 ExpectationList *exp_list = (ExpectationList *)el;
126 if (exp_list == NULL)
127 return;
128
129 if (exp_list->length > 0) {
130 Expectation *exp = NULL, *pexp = NULL;
131 CIRCLEQ_FOREACH_SAFE(exp, &exp_list->list, entries, pexp) {
132 CIRCLEQ_REMOVE(&exp_list->list, exp, entries);
133 exp_list->length--;
134 AppLayerFreeExpectation(exp);
135 }
136 }
137 SCFree(exp_list);
138}
139
141{
142 uint64_t x = SC_ATOMIC_GET(expectation_count);
143 return x;
144}
145
147{
148 g_ippair_expectation_id =
149 IPPairStorageRegister("expectation", sizeof(void *), NULL, ExpectationListFree);
150 g_flow_expectation_id =
151 FlowStorageRegister("expectation", sizeof(void *), NULL, ExpectationDataFree);
152 SC_ATOMIC_INIT(expectation_count);
153}
154
155static inline int GetFlowAddresses(Flow *f, Address *ip_src, Address *ip_dst)
156{
157 memset(ip_src, 0, sizeof(*ip_src));
158 memset(ip_dst, 0, sizeof(*ip_dst));
159 if (FLOW_IS_IPV4(f)) {
162 } else if (FLOW_IS_IPV6(f)) {
165 } else {
166 return -1;
167 }
168 return 0;
169}
170
171static ExpectationList *AppLayerExpectationLookup(Flow *f, IPPair **ipp)
172{
173 Address ip_src, ip_dst;
174 if (GetFlowAddresses(f, &ip_src, &ip_dst) == -1)
175 return NULL;
176 *ipp = IPPairLookupIPPairFromHash(&ip_src, &ip_dst);
177 if (*ipp == NULL) {
178 return NULL;
179 }
180
181 return IPPairGetStorageById(*ipp, g_ippair_expectation_id);
182}
183
184
185static ExpectationList *AppLayerExpectationRemove(IPPair *ipp,
186 ExpectationList *exp_list,
187 Expectation *exp)
188{
189 CIRCLEQ_REMOVE(&exp_list->list, exp, entries);
190 AppLayerFreeExpectation(exp);
191 SC_ATOMIC_SUB(expectation_count, 1);
192 exp_list->length--;
193 if (exp_list->length == 0) {
194 IPPairSetStorageById(ipp, g_ippair_expectation_id, NULL);
195 ExpectationListFree(exp_list);
196 exp_list = NULL;
197 }
198 return exp_list;
199}
200
201/**
202 * Create an entry in expectation list
203 *
204 * Create a expectation from an existing Flow. Currently, only Flow between
205 * the two original IP addresses are supported. In case of success, the
206 * ownership of the data pointer is taken. In case of error, the pointer
207 * to data has to be freed by the caller.
208 *
209 * \param f a pointer to the original Flow
210 * \param direction the direction of the data in the expectation flow
211 * \param src source port of the expected flow, use 0 for any
212 * \param dst destination port of the expected flow, use 0 for any
213 * \param alproto the protocol that need to be set on the expected flow
214 * \param data pointer to data that will be attached to the expected flow
215 *
216 * \return -1 if error
217 * \return 0 if success
218 */
220 AppProto alproto, void *data)
221{
222 ExpectationList *exp_list = NULL;
223 IPPair *ipp;
224 Address ip_src, ip_dst;
225
226 Expectation *exp = SCCalloc(1, sizeof(*exp));
227 if (exp == NULL)
228 return -1;
229
230 exp->sp = src;
231 exp->dp = dst;
232 exp->alproto = alproto;
233 exp->ts = f->lastts;
234 exp->orig_f = (void *)f;
235 exp->data = data;
236 exp->direction = direction;
237
238 if (GetFlowAddresses(f, &ip_src, &ip_dst) == -1)
239 goto error;
240 ipp = IPPairGetIPPairFromHash(&ip_src, &ip_dst);
241 if (ipp == NULL)
242 goto error;
243
244 exp_list = IPPairGetStorageById(ipp, g_ippair_expectation_id);
245 if (exp_list) {
246 CIRCLEQ_INSERT_HEAD(&exp_list->list, exp, entries);
247 /* In case there is already EXPECTATION_MAX_LEVEL expectations waiting to be fulfilled,
248 * we remove the older expectation to limit the total number of expectations */
249 if (exp_list->length >= EXPECTATION_MAX_LEVEL) {
250 Expectation *last_exp = CIRCLEQ_LAST(&exp_list->list);
251 CIRCLEQ_REMOVE(&exp_list->list, last_exp, entries);
252 AppLayerFreeExpectation(last_exp);
253 /* We keep the same amount of expectation so we fully release
254 * the IP pair */
256 IPPairRelease(ipp);
257 return 0;
258 }
259 } else {
260 exp_list = SCCalloc(1, sizeof(*exp_list));
261 if (exp_list == NULL)
262 goto error;
263 exp_list->length = 0;
264 CIRCLEQ_INIT(&exp_list->list);
265 CIRCLEQ_INSERT_HEAD(&exp_list->list, exp, entries);
266 IPPairSetStorageById(ipp, g_ippair_expectation_id, exp_list);
267 }
268
269 exp_list->length += 1;
270 SC_ATOMIC_ADD(expectation_count, 1);
272 /* As we are creating the expectation, we release lock on IPPair without
273 * setting the ref count to 0. This way the IPPair will be kept till
274 * cleanup */
275 IPPairUnlock(ipp);
276 return 0;
277
278error:
279 SCFree(exp);
280 return -1;
281}
282
283/**
284 * Return Flow storage identifier corresponding to expectation data
285 *
286 * \return expectation data identifier
287 */
289{
290 return g_flow_expectation_id;
291}
292
293/**
294 * Function doing a lookup in expectation list and updating Flow if needed.
295 *
296 * This function lookup for a existing expectation that could match the Flow.
297 * If found and if the expectation contains data it store the data in the
298 * expectation storage of the Flow.
299 *
300 * \return an AppProto value if found
301 * \return ALPROTO_UNKNOWN if not found
302 */
304{
305 AppProto alproto = ALPROTO_UNKNOWN;
306 IPPair *ipp = NULL;
307 Expectation *lexp = NULL;
308 Expectation *exp = NULL;
309
310 int x = SC_ATOMIC_GET(expectation_count);
311 if (x == 0) {
312 return ALPROTO_UNKNOWN;
313 }
314
315 /* Call will take reference of the ip pair in 'ipp' */
316 ExpectationList *exp_list = AppLayerExpectationLookup(f, &ipp);
317 if (exp_list == NULL)
318 goto out;
319
320 CIRCLEQ_FOREACH_SAFE(exp, &exp_list->list, entries, lexp) {
321 if ((exp->direction & flags) && ((exp->sp == 0) || (exp->sp == f->sp)) &&
322 ((exp->dp == 0) || (exp->dp == f->dp))) {
323 alproto = exp->alproto;
324 if (f->alproto_ts == ALPROTO_UNKNOWN) {
325 f->alproto_ts = alproto;
326 }
327 if (f->alproto_tc == ALPROTO_UNKNOWN) {
328 f->alproto_tc = alproto;
329 }
330 void *fdata = FlowGetStorageById(f, g_flow_expectation_id);
331 if (fdata) {
332 /* We already have an expectation so let's clean this one */
333 ExpectationDataFree(exp->data);
334 } else {
335 /* Transfer ownership of Expectation data to the Flow */
336 if (FlowSetStorageById(f, g_flow_expectation_id, exp->data) != 0) {
337 SCLogDebug("Unable to set flow storage");
338 }
339 }
340 exp->data = NULL;
341 exp_list = AppLayerExpectationRemove(ipp, exp_list, exp);
342 if (exp_list == NULL)
343 goto out;
344 continue;
345 }
346 /* Cleaning remove old entries */
348 exp_list = AppLayerExpectationRemove(ipp, exp_list, exp);
349 if (exp_list == NULL)
350 goto out;
351 continue;
352 }
353 }
354
355out:
356 if (ipp)
357 IPPairRelease(ipp);
358 return alproto;
359}
360
362{
363 IPPair *ipp = NULL;
364 Expectation *exp = NULL;
365 Expectation *pexp = NULL;
366
367 int x = SC_ATOMIC_GET(expectation_count);
368 if (x == 0) {
369 return;
370 }
371
372 /* Call will take reference of the ip pair in 'ipp' */
373 ExpectationList *exp_list = AppLayerExpectationLookup(f, &ipp);
374 if (exp_list == NULL)
375 goto out;
376
377 CIRCLEQ_FOREACH_SAFE(exp, &exp_list->list, entries, pexp) {
378 /* Cleaning remove old entries */
379 if (exp->orig_f == (void *)f) {
380 exp_list = AppLayerExpectationRemove(ipp, exp_list, exp);
381 if (exp_list == NULL)
382 goto out;
383 }
384 }
385
386out:
387 if (ipp)
388 IPPairRelease(ipp);
389}
390
391/**
392 * @}
393 */
uint16_t dst
uint16_t src
uint16_t AppProto
@ ALPROTO_UNKNOWN
uint8_t flags
Definition decode-gre.h:0
uint16_t Port
Definition decode.h:218
void * FlowGetStorageById(const Flow *f, FlowStorageId id)
int FlowSetStorageById(Flow *f, FlowStorageId id, void *ptr)
FlowStorageId FlowStorageRegister(const char *name, const unsigned int size, void *(*Alloc)(unsigned int), void(*Free)(void *))
#define FLOW_IS_IPV6(f)
Definition flow.h:172
#define FLOW_COPY_IPV4_ADDR_TO_PACKET(fa, pa)
Definition flow.h:180
#define FLOW_HAS_EXPECTATION
Definition flow.h:114
#define FLOW_COPY_IPV6_ADDR_TO_PACKET(fa, pa)
Definition flow.h:185
#define FLOW_IS_IPV4(f)
Definition flow.h:170
void AppLayerExpectationClean(Flow *f)
AppProto AppLayerExpectationHandle(Flow *f, uint8_t flags)
struct Expectation_ Expectation
uint64_t ExpectationGetCounter(void)
FlowStorageId AppLayerExpectationGetFlowId(void)
void AppLayerExpectationSetup(void)
int AppLayerExpectationCreate(Flow *f, int direction, Port src, Port dst, AppProto alproto, void *data)
struct ExpectationData_ ExpectationData
#define EXPECTATION_MAX_LEVEL
struct ExpectationList_ ExpectationList
#define EXPECTATION_TIMEOUT
void * IPPairGetStorageById(IPPair *h, IPPairStorageId id)
IPPairStorageId IPPairStorageRegister(const char *name, const unsigned int size, void *(*Alloc)(unsigned int), void(*Free)(void *))
int IPPairSetStorageById(IPPair *h, IPPairStorageId id, void *ptr)
void IPPairUnlock(IPPair *h)
Definition ippair.c:508
void IPPairRelease(IPPair *h)
Definition ippair.c:502
IPPair * IPPairGetIPPairFromHash(Address *a, Address *b)
Definition ippair.c:521
IPPair * IPPairLookupIPPairFromHash(Address *a, Address *b)
look up a ippair in the hash
Definition ippair.c:620
#define CIRCLEQ_INSERT_HEAD(head, elm, field)
Definition queue.h:151
#define CIRCLEQ_INIT(head)
Definition queue.h:126
#define CIRCLEQ_LAST(head)
Definition queue.h:95
#define CIRCLEQ_ENTRY(type)
Definition queue.h:85
#define CIRCLEQ_HEAD(name, type)
Definition queue.h:76
#define CIRCLEQ_REMOVE(head, elm, field)
Definition queue.h:171
#define CIRCLEQ_FOREACH_SAFE(var, head, field, tvar)
Definition queue.h:106
Flow data structure.
Definition flow.h:356
AppProto alproto_ts
Definition flow.h:451
Port dp
Definition flow.h:372
AppProto alproto_tc
Definition flow.h:452
uint32_t flags
Definition flow.h:421
SCTime_t lastts
Definition flow.h:410
FlowAddress src
Definition flow.h:359
Port sp
Definition flow.h:361
FlowAddress dst
Definition flow.h:359
#define SC_ATOMIC_ADD(name, val)
add a value to our atomic variable
#define SC_ATOMIC_INIT(name)
wrapper for initializing an atomic variable.
#define SC_ATOMIC_DECLARE(type, name)
wrapper for declaring atomic variables.
#define SC_ATOMIC_SUB(name, val)
sub a value from our atomic variable
#define SC_ATOMIC_GET(name)
Get the value from the atomic variable.
#define SCLogDebug(...)
Definition util-debug.h:275
#define SCFree(p)
Definition util-mem.h:61
#define SCCalloc(nm, sz)
Definition util-mem.h:53
#define SCTIME_SECS(t)
Definition util-time.h:57