Coverage Report

Created: 2026-09-28 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/src/output-json-dnp3.c
Line
Count
Source
1
/* Copyright (C) 2015-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
#include "suricata-common.h"
19
#include "detect.h"
20
#include "pkt-var.h"
21
#include "conf.h"
22
23
#include "threads.h"
24
#include "threadvars.h"
25
#include "tm-threads.h"
26
27
#include "util-print.h"
28
#include "util-unittest.h"
29
#include "util-buffer.h"
30
#include "util-debug.h"
31
32
#include "app-layer.h"
33
#include "app-layer-parser.h"
34
#include "app-layer-dnp3.h"
35
#include "app-layer-dnp3-objects.h"
36
37
#include "detect-dnp3.h"
38
39
#include "output.h"
40
#include "output-json.h"
41
#include "output-json-dnp3.h"
42
#include "output-json-dnp3-objects.h"
43
44
typedef struct LogDNP3FileCtx_ {
45
    uint32_t    flags;
46
    uint8_t     include_object_data;
47
    OutputJsonCtx *eve_ctx;
48
} LogDNP3FileCtx;
49
50
typedef struct LogDNP3LogThread_ {
51
    LogDNP3FileCtx *dnp3log_ctx;
52
    OutputJsonThreadCtx *ctx;
53
} LogDNP3LogThread;
54
55
static void JsonDNP3LogLinkControl(SCJsonBuilder *js, uint8_t lc)
56
94.9k
{
57
94.9k
    SCJbSetBool(js, "dir", DNP3_LINK_DIR(lc));
58
94.9k
    SCJbSetBool(js, "pri", DNP3_LINK_PRI(lc));
59
94.9k
    SCJbSetBool(js, "fcb", DNP3_LINK_FCB(lc));
60
94.9k
    SCJbSetBool(js, "fcv", DNP3_LINK_FCV(lc));
61
94.9k
    SCJbSetUint(js, "function_code", DNP3_LINK_FC(lc));
62
94.9k
}
63
64
static void JsonDNP3LogIin(SCJsonBuilder *js, uint16_t iin)
65
429
{
66
429
    if (iin) {
67
426
        SCJbOpenArray(js, "indicators");
68
69
426
        int mapping = 0;
70
6.81k
        do {
71
6.81k
            if (iin & DNP3IndicatorsMap[mapping].value) {
72
3.43k
                SCJbAppendString(js, DNP3IndicatorsMap[mapping].name);
73
3.43k
            }
74
6.81k
            mapping++;
75
6.81k
        } while (DNP3IndicatorsMap[mapping].name != NULL);
76
426
        SCJbClose(js);
77
426
    }
78
429
}
79
80
static void JsonDNP3LogApplicationControl(SCJsonBuilder *js, uint8_t ac)
81
94.9k
{
82
94.9k
    SCJbSetBool(js, "fir", DNP3_APP_FIR(ac));
83
94.9k
    SCJbSetBool(js, "fin", DNP3_APP_FIN(ac));
84
94.9k
    SCJbSetBool(js, "con", DNP3_APP_CON(ac));
85
94.9k
    SCJbSetBool(js, "uns", DNP3_APP_UNS(ac));
86
94.9k
    SCJbSetUint(js, "sequence", DNP3_APP_SEQ(ac));
87
94.9k
}
88
89
/**
90
 * \brief Log the items (points) for an object.
91
 *
92
 * TODO: Autogenerate this function based on object definitions.
93
 */
94
static void JsonDNP3LogObjectItems(SCJsonBuilder *js, DNP3Object *object)
95
42.0k
{
96
42.0k
    DNP3Point *item;
97
98
301k
    TAILQ_FOREACH(item, object->points, next) {
99
301k
        SCJbStartObject(js);
100
101
301k
        SCJbSetUint(js, "prefix", item->prefix);
102
301k
        SCJbSetUint(js, "index", item->index);
103
301k
        if (DNP3PrefixIsSize(object->prefix_code)) {
104
31.2k
            SCJbSetUint(js, "size", item->size);
105
31.2k
        }
106
107
301k
        OutputJsonDNP3SetItem(js, object, item);
108
301k
        SCJbClose(js);
109
301k
    }
110
42.0k
}
111
112
/**
113
 * \brief Log the application layer objects.
114
 *
115
 * \param objects A list of DNP3 objects.
116
 * \param jb A SCJsonBuilder instance with an open array.
117
 */
118
static void JsonDNP3LogObjects(SCJsonBuilder *js, DNP3ObjectList *objects)
119
57.8k
{
120
57.8k
    DNP3Object *object;
121
122
158k
    TAILQ_FOREACH(object, objects, next) {
123
158k
        SCJbStartObject(js);
124
158k
        SCJbSetUint(js, "group", object->group);
125
158k
        SCJbSetUint(js, "variation", object->variation);
126
158k
        SCJbSetUint(js, "qualifier", object->qualifier);
127
158k
        SCJbSetUint(js, "prefix_code", object->prefix_code);
128
158k
        SCJbSetUint(js, "range_code", object->range_code);
129
158k
        SCJbSetUint(js, "start", object->start);
130
158k
        SCJbSetUint(js, "stop", object->stop);
131
158k
        SCJbSetUint(js, "count", object->count);
132
133
158k
        if (object->points != NULL && !TAILQ_EMPTY(object->points)) {
134
42.0k
            SCJbOpenArray(js, "points");
135
42.0k
            JsonDNP3LogObjectItems(js, object);
136
42.0k
            SCJbClose(js);
137
42.0k
        }
138
139
158k
        SCJbClose(js);
140
158k
    }
141
57.8k
}
142
143
static void JsonDNP3LogRequest(SCJsonBuilder *js, DNP3Transaction *dnp3tx)
144
47.7k
{
145
47.7k
    JB_SET_STRING(js, "type", "request");
146
147
47.7k
    SCJbOpenObject(js, "control");
148
47.7k
    JsonDNP3LogLinkControl(js, dnp3tx->lh.control);
149
47.7k
    SCJbClose(js);
150
151
47.7k
    SCJbSetUint(js, "src", DNP3_SWAP16(dnp3tx->lh.src));
152
47.7k
    SCJbSetUint(js, "dst", DNP3_SWAP16(dnp3tx->lh.dst));
153
154
47.7k
    SCJbOpenObject(js, "application");
155
156
47.7k
    SCJbOpenObject(js, "control");
157
47.7k
    JsonDNP3LogApplicationControl(js, dnp3tx->ah.control);
158
47.7k
    SCJbClose(js);
159
160
47.7k
    SCJbSetUint(js, "function_code", dnp3tx->ah.function_code);
161
162
47.7k
    if (!TAILQ_EMPTY(&dnp3tx->objects)) {
163
47.6k
        SCJbOpenArray(js, "objects");
164
47.6k
        JsonDNP3LogObjects(js, &dnp3tx->objects);
165
47.6k
        SCJbClose(js);
166
47.6k
    }
167
168
47.7k
    SCJbSetBool(js, "complete", dnp3tx->complete);
169
170
    /* Close application. */
171
47.7k
    SCJbClose(js);
172
47.7k
}
173
174
static void JsonDNP3LogResponse(SCJsonBuilder *js, DNP3Transaction *dnp3tx)
175
47.1k
{
176
47.1k
    if (dnp3tx->ah.function_code == DNP3_APP_FC_UNSOLICITED_RESP) {
177
12.5k
        JB_SET_STRING(js, "type", "unsolicited_response");
178
34.6k
    } else {
179
34.6k
        JB_SET_STRING(js, "type", "response");
180
34.6k
    }
181
182
47.1k
    SCJbOpenObject(js, "control");
183
47.1k
    JsonDNP3LogLinkControl(js, dnp3tx->lh.control);
184
47.1k
    SCJbClose(js);
185
186
47.1k
    SCJbSetUint(js, "src", DNP3_SWAP16(dnp3tx->lh.src));
187
47.1k
    SCJbSetUint(js, "dst", DNP3_SWAP16(dnp3tx->lh.dst));
188
189
47.1k
    SCJbOpenObject(js, "application");
190
191
47.1k
    SCJbOpenObject(js, "control");
192
47.1k
    JsonDNP3LogApplicationControl(js, dnp3tx->ah.control);
193
47.1k
    SCJbClose(js);
194
195
47.1k
    SCJbSetUint(js, "function_code", dnp3tx->ah.function_code);
196
197
47.1k
    if (!TAILQ_EMPTY(&dnp3tx->objects)) {
198
10.1k
        SCJbOpenArray(js, "objects");
199
10.1k
        JsonDNP3LogObjects(js, &dnp3tx->objects);
200
10.1k
        SCJbClose(js);
201
10.1k
    }
202
203
47.1k
    SCJbSetBool(js, "complete", dnp3tx->complete);
204
205
    /* Close application. */
206
47.1k
    SCJbClose(js);
207
208
47.1k
    SCJbOpenObject(js, "iin");
209
47.1k
    JsonDNP3LogIin(js, (uint16_t)(dnp3tx->iin.iin1 << 8 | dnp3tx->iin.iin2));
210
47.1k
    SCJbClose(js);
211
47.1k
}
212
213
bool AlertJsonDnp3(void *vtx, SCJsonBuilder *js)
214
303
{
215
303
    DNP3Transaction *tx = (DNP3Transaction *)vtx;
216
303
    if (tx->done && tx->buffer == NULL) {
217
        /* Carrier TX for app-layer event, don't log. */
218
0
        return false;
219
0
    }
220
303
    bool logged = false;
221
303
    SCJbOpenObject(js, "dnp3");
222
303
    if (tx->is_request && tx->done) {
223
23
        SCJbOpenObject(js, "request");
224
23
        JsonDNP3LogRequest(js, tx);
225
23
        SCJbClose(js);
226
23
        logged = true;
227
23
    }
228
303
    if (!tx->is_request && tx->done) {
229
279
        SCJbOpenObject(js, "response");
230
279
        JsonDNP3LogResponse(js, tx);
231
279
        SCJbClose(js);
232
279
        logged = true;
233
279
    }
234
303
    SCJbClose(js);
235
303
    return logged;
236
303
}
237
238
static int JsonDNP3LoggerToServer(ThreadVars *tv, void *thread_data,
239
    const Packet *p, Flow *f, void *state, void *vtx, uint64_t tx_id)
240
47.7k
{
241
47.7k
    SCEnter();
242
47.7k
    LogDNP3LogThread *thread = (LogDNP3LogThread *)thread_data;
243
47.7k
    DNP3Transaction *tx = vtx;
244
245
47.7k
    SCJsonBuilder *js =
246
47.7k
            CreateEveHeader(p, LOG_DIR_FLOW, "dnp3", NULL, thread->dnp3log_ctx->eve_ctx);
247
47.7k
    if (unlikely(js == NULL)) {
248
0
        return TM_ECODE_OK;
249
0
    }
250
251
47.7k
    SCJbOpenObject(js, "dnp3");
252
47.7k
    JsonDNP3LogRequest(js, tx);
253
47.7k
    SCJbClose(js);
254
47.7k
    OutputJsonBuilderBuffer(tv, p, p->flow, js, thread->ctx);
255
47.7k
    SCJbFree(js);
256
257
47.7k
    SCReturnInt(TM_ECODE_OK);
258
47.7k
}
259
260
static int JsonDNP3LoggerToClient(ThreadVars *tv, void *thread_data,
261
    const Packet *p, Flow *f, void *state, void *vtx, uint64_t tx_id)
262
46.8k
{
263
46.8k
    SCEnter();
264
46.8k
    LogDNP3LogThread *thread = (LogDNP3LogThread *)thread_data;
265
46.8k
    DNP3Transaction *tx = vtx;
266
267
46.8k
    SCJsonBuilder *js =
268
46.8k
            CreateEveHeader(p, LOG_DIR_FLOW, "dnp3", NULL, thread->dnp3log_ctx->eve_ctx);
269
46.8k
    if (unlikely(js == NULL)) {
270
0
        return TM_ECODE_OK;
271
0
    }
272
273
46.8k
    SCJbOpenObject(js, "dnp3");
274
46.8k
    JsonDNP3LogResponse(js, tx);
275
46.8k
    SCJbClose(js);
276
46.8k
    OutputJsonBuilderBuffer(tv, p, p->flow, js, thread->ctx);
277
46.8k
    SCJbFree(js);
278
279
46.8k
    SCReturnInt(TM_ECODE_OK);
280
46.8k
}
281
282
static int JsonDNP3Logger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state,
283
        void *vtx, uint64_t tx_id)
284
102k
{
285
102k
    SCEnter();
286
102k
    DNP3Transaction *tx = vtx;
287
102k
    if (tx->done && tx->buffer == NULL) {
288
        /* Carrier TX for app-layer event, don't log. */
289
88
        SCReturnInt(TM_ECODE_OK);
290
88
    }
291
102k
    if (tx->is_request && tx->done) {
292
47.7k
        JsonDNP3LoggerToServer(tv, thread_data, p, f, state, vtx, tx_id);
293
54.6k
    } else if (!tx->is_request && tx->done) {
294
46.8k
        JsonDNP3LoggerToClient(tv, thread_data, p, f, state, vtx, tx_id);
295
46.8k
    }
296
102k
    SCReturnInt(TM_ECODE_OK);
297
102k
}
298
299
static void OutputDNP3LogDeInitCtxSub(OutputCtx *output_ctx)
300
0
{
301
0
    SCLogDebug("cleaning up sub output_ctx %p", output_ctx);
302
0
    LogDNP3FileCtx *dnp3log_ctx = (LogDNP3FileCtx *)output_ctx->data;
303
0
    SCFree(dnp3log_ctx);
304
0
    SCFree(output_ctx);
305
0
}
306
307
static OutputInitResult OutputDNP3LogInitSub(SCConfNode *conf, OutputCtx *parent_ctx)
308
4
{
309
4
    OutputInitResult result = { NULL, false };
310
4
    OutputJsonCtx *json_ctx = parent_ctx->data;
311
312
4
    LogDNP3FileCtx *dnp3log_ctx = SCCalloc(1, sizeof(*dnp3log_ctx));
313
4
    if (unlikely(dnp3log_ctx == NULL)) {
314
0
        return result;
315
0
    }
316
4
    dnp3log_ctx->eve_ctx = json_ctx;
317
318
4
    OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx));
319
4
    if (unlikely(output_ctx == NULL)) {
320
0
        SCFree(dnp3log_ctx);
321
0
        return result;
322
0
    }
323
4
    output_ctx->data = dnp3log_ctx;
324
4
    output_ctx->DeInit = OutputDNP3LogDeInitCtxSub;
325
326
4
    SCLogInfo("DNP3 log sub-module initialized.");
327
328
4
    SCAppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_DNP3);
329
330
4
    result.ctx = output_ctx;
331
4
    result.ok = true;
332
4
    return result;
333
4
}
334
335
336
static TmEcode JsonDNP3LogThreadInit(ThreadVars *t, const void *initdata, void **data)
337
4
{
338
4
    LogDNP3LogThread *thread = SCCalloc(1, sizeof(*thread));
339
4
    if (unlikely(thread == NULL)) {
340
0
        return TM_ECODE_FAILED;
341
0
    }
342
343
4
    if (initdata == NULL) {
344
0
        SCLogDebug("Error getting context for DNP3.  \"initdata\" is NULL.");
345
0
        goto error_exit;
346
0
    }
347
348
4
    thread->dnp3log_ctx = ((OutputCtx *)initdata)->data;
349
4
    thread->ctx = CreateEveThreadCtx(t, thread->dnp3log_ctx->eve_ctx);
350
4
    if (thread->ctx == NULL) {
351
0
        goto error_exit;
352
0
    }
353
354
4
    *data = (void *)thread;
355
356
4
    return TM_ECODE_OK;
357
358
0
error_exit:
359
0
    SCFree(thread);
360
0
    return TM_ECODE_FAILED;
361
4
}
362
363
static TmEcode JsonDNP3LogThreadDeinit(ThreadVars *t, void *data)
364
0
{
365
0
    LogDNP3LogThread *thread = (LogDNP3LogThread *)data;
366
0
    if (thread == NULL) {
367
0
        return TM_ECODE_OK;
368
0
    }
369
0
    FreeEveThreadCtx(thread->ctx);
370
0
    SCFree(thread);
371
0
    return TM_ECODE_OK;
372
0
}
373
374
void JsonDNP3LogRegister(void)
375
78
{
376
78
    OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonDNP3Log", "eve-log.dnp3",
377
78
            OutputDNP3LogInitSub, ALPROTO_DNP3, JsonDNP3Logger, JsonDNP3LogThreadInit,
378
78
            JsonDNP3LogThreadDeinit);
379
78
}