Coverage Report

Created: 2026-06-30 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/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(JsonBuilder *js, uint8_t lc)
56
118k
{
57
118k
    jb_set_bool(js, "dir", DNP3_LINK_DIR(lc));
58
118k
    jb_set_bool(js, "pri", DNP3_LINK_PRI(lc));
59
118k
    jb_set_bool(js, "fcb", DNP3_LINK_FCB(lc));
60
118k
    jb_set_bool(js, "fcv", DNP3_LINK_FCV(lc));
61
118k
    jb_set_uint(js, "function_code", DNP3_LINK_FC(lc));
62
118k
}
63
64
static void JsonDNP3LogIin(JsonBuilder *js, uint16_t iin)
65
27.9k
{
66
27.9k
    if (iin) {
67
8.10k
        jb_open_array(js, "indicators");
68
69
8.10k
        int mapping = 0;
70
129k
        do {
71
129k
            if (iin & DNP3IndicatorsMap[mapping].value) {
72
12.3k
                jb_append_string(js, DNP3IndicatorsMap[mapping].name);
73
12.3k
            }
74
129k
            mapping++;
75
129k
        } while (DNP3IndicatorsMap[mapping].name != NULL);
76
8.10k
        jb_close(js);
77
8.10k
    }
78
27.9k
}
79
80
static void JsonDNP3LogApplicationControl(JsonBuilder *js, uint8_t ac)
81
118k
{
82
118k
    jb_set_bool(js, "fir", DNP3_APP_FIR(ac));
83
118k
    jb_set_bool(js, "fin", DNP3_APP_FIN(ac));
84
118k
    jb_set_bool(js, "con", DNP3_APP_CON(ac));
85
118k
    jb_set_bool(js, "uns", DNP3_APP_UNS(ac));
86
118k
    jb_set_uint(js, "sequence", DNP3_APP_SEQ(ac));
87
118k
}
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(JsonBuilder *js, DNP3Object *object)
95
51.6k
{
96
51.6k
    DNP3Point *item;
97
98
369k
    TAILQ_FOREACH(item, object->points, next) {
99
369k
        jb_start_object(js);
100
101
369k
        jb_set_uint(js, "prefix", item->prefix);
102
369k
        jb_set_uint(js, "index", item->index);
103
369k
        if (DNP3PrefixIsSize(object->prefix_code)) {
104
6.46k
            jb_set_uint(js, "size", item->size);
105
6.46k
        }
106
107
369k
        OutputJsonDNP3SetItem(js, object, item);
108
369k
        jb_close(js);
109
369k
    }
110
51.6k
}
111
112
/**
113
 * \brief Log the application layer objects.
114
 *
115
 * \param objects A list of DNP3 objects.
116
 * \param jb A JsonBuilder instance with an open array.
117
 */
118
static void JsonDNP3LogObjects(JsonBuilder *js, DNP3ObjectList *objects)
119
67.7k
{
120
67.7k
    DNP3Object *object;
121
122
165k
    TAILQ_FOREACH(object, objects, next) {
123
165k
        jb_start_object(js);
124
165k
        jb_set_uint(js, "group", object->group);
125
165k
        jb_set_uint(js, "variation", object->variation);
126
165k
        jb_set_uint(js, "qualifier", object->qualifier);
127
165k
        jb_set_uint(js, "prefix_code", object->prefix_code);
128
165k
        jb_set_uint(js, "range_code",  object->range_code);
129
165k
        jb_set_uint(js, "start", object->start);
130
165k
        jb_set_uint(js, "stop", object->stop);
131
165k
        jb_set_uint(js, "count", object->count);
132
133
165k
        if (object->points != NULL && !TAILQ_EMPTY(object->points)) {
134
51.6k
            jb_open_array(js, "points");
135
51.6k
            JsonDNP3LogObjectItems(js, object);
136
51.6k
            jb_close(js);
137
51.6k
        }
138
139
165k
        jb_close(js);
140
165k
    }
141
67.7k
}
142
143
void JsonDNP3LogRequest(JsonBuilder *js, DNP3Transaction *dnp3tx)
144
21.3k
{
145
21.3k
    JB_SET_STRING(js, "type", "request");
146
147
21.3k
    jb_open_object(js, "control");
148
21.3k
    JsonDNP3LogLinkControl(js, dnp3tx->lh.control);
149
21.3k
    jb_close(js);
150
151
21.3k
    jb_set_uint(js, "src", DNP3_SWAP16(dnp3tx->lh.src));
152
21.3k
    jb_set_uint(js, "dst", DNP3_SWAP16(dnp3tx->lh.dst));
153
154
21.3k
    jb_open_object(js, "application");
155
156
21.3k
    jb_open_object(js, "control");
157
21.3k
    JsonDNP3LogApplicationControl(js, dnp3tx->ah.control);
158
21.3k
    jb_close(js);
159
160
21.3k
    jb_set_uint(js, "function_code", dnp3tx->ah.function_code);
161
162
21.3k
    if (!TAILQ_EMPTY(&dnp3tx->objects)) {
163
21.3k
        jb_open_array(js, "objects");
164
21.3k
        JsonDNP3LogObjects(js, &dnp3tx->objects);
165
21.3k
        jb_close(js);
166
21.3k
    }
167
168
21.3k
    jb_set_bool(js, "complete", dnp3tx->complete);
169
170
    /* Close application. */
171
21.3k
    jb_close(js);
172
21.3k
}
173
174
void JsonDNP3LogResponse(JsonBuilder *js, DNP3Transaction *dnp3tx)
175
27.9k
{
176
27.9k
    if (dnp3tx->ah.function_code == DNP3_APP_FC_UNSOLICITED_RESP) {
177
6.52k
        JB_SET_STRING(js, "type", "unsolicited_response");
178
21.4k
    } else {
179
21.4k
        JB_SET_STRING(js, "type", "response");
180
21.4k
    }
181
182
27.9k
    jb_open_object(js, "control");
183
27.9k
    JsonDNP3LogLinkControl(js, dnp3tx->lh.control);
184
27.9k
    jb_close(js);
185
186
27.9k
    jb_set_uint(js, "src", DNP3_SWAP16(dnp3tx->lh.src));
187
27.9k
    jb_set_uint(js, "dst", DNP3_SWAP16(dnp3tx->lh.dst));
188
189
27.9k
    jb_open_object(js, "application");
190
191
27.9k
    jb_open_object(js, "control");
192
27.9k
    JsonDNP3LogApplicationControl(js, dnp3tx->ah.control);
193
27.9k
    jb_close(js);
194
195
27.9k
    jb_set_uint(js, "function_code", dnp3tx->ah.function_code);
196
197
27.9k
    if (!TAILQ_EMPTY(&dnp3tx->objects)) {
198
6.20k
        jb_open_array(js, "objects");
199
6.20k
        JsonDNP3LogObjects(js, &dnp3tx->objects);
200
6.20k
        jb_close(js);
201
6.20k
    }
202
203
27.9k
    jb_set_bool(js, "complete", dnp3tx->complete);
204
205
    /* Close application. */
206
27.9k
    jb_close(js);
207
208
27.9k
    jb_open_object(js, "iin");
209
27.9k
    JsonDNP3LogIin(js, (uint16_t)(dnp3tx->iin.iin1 << 8 | dnp3tx->iin.iin2));
210
27.9k
    jb_close(js);
211
27.9k
}
212
213
static int JsonDNP3LoggerToServer(ThreadVars *tv, void *thread_data,
214
    const Packet *p, Flow *f, void *state, void *vtx, uint64_t tx_id)
215
50.5k
{
216
50.5k
    SCEnter();
217
50.5k
    LogDNP3LogThread *thread = (LogDNP3LogThread *)thread_data;
218
50.5k
    DNP3Transaction *tx = vtx;
219
220
50.5k
    JsonBuilder *js = CreateEveHeader(p, LOG_DIR_FLOW, "dnp3", NULL, thread->dnp3log_ctx->eve_ctx);
221
50.5k
    if (unlikely(js == NULL)) {
222
0
        return TM_ECODE_OK;
223
0
    }
224
225
50.5k
    jb_open_object(js, "dnp3");
226
50.5k
    JsonDNP3LogRequest(js, tx);
227
50.5k
    jb_close(js);
228
50.5k
    OutputJsonBuilderBuffer(js, thread->ctx);
229
50.5k
    jb_free(js);
230
231
50.5k
    SCReturnInt(TM_ECODE_OK);
232
50.5k
}
233
234
static int JsonDNP3LoggerToClient(ThreadVars *tv, void *thread_data,
235
    const Packet *p, Flow *f, void *state, void *vtx, uint64_t tx_id)
236
63.4k
{
237
63.4k
    SCEnter();
238
63.4k
    LogDNP3LogThread *thread = (LogDNP3LogThread *)thread_data;
239
63.4k
    DNP3Transaction *tx = vtx;
240
241
63.4k
    JsonBuilder *js = CreateEveHeader(p, LOG_DIR_FLOW, "dnp3", NULL, thread->dnp3log_ctx->eve_ctx);
242
63.4k
    if (unlikely(js == NULL)) {
243
0
        return TM_ECODE_OK;
244
0
    }
245
246
63.4k
    jb_open_object(js, "dnp3");
247
63.4k
    JsonDNP3LogResponse(js, tx);
248
63.4k
    jb_close(js);
249
63.4k
    OutputJsonBuilderBuffer(js, thread->ctx);
250
63.4k
    jb_free(js);
251
252
63.4k
    SCReturnInt(TM_ECODE_OK);
253
63.4k
}
254
255
static int JsonDNP3Logger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state,
256
        void *vtx, uint64_t tx_id)
257
118k
{
258
118k
    SCEnter();
259
118k
    DNP3Transaction *tx = vtx;
260
118k
    if (tx->is_request && tx->done) {
261
50.5k
        JsonDNP3LoggerToServer(tv, thread_data, p, f, state, vtx, tx_id);
262
68.4k
    } else if (!tx->is_request && tx->done) {
263
63.4k
        JsonDNP3LoggerToClient(tv, thread_data, p, f, state, vtx, tx_id);
264
63.4k
    }
265
118k
    SCReturnInt(TM_ECODE_OK);
266
118k
}
267
268
static void OutputDNP3LogDeInitCtxSub(OutputCtx *output_ctx)
269
0
{
270
0
    SCLogDebug("cleaning up sub output_ctx %p", output_ctx);
271
0
    LogDNP3FileCtx *dnp3log_ctx = (LogDNP3FileCtx *)output_ctx->data;
272
0
    SCFree(dnp3log_ctx);
273
0
    SCFree(output_ctx);
274
0
}
275
276
#define DEFAULT_LOG_FILENAME "dnp3.json"
277
278
static OutputInitResult OutputDNP3LogInitSub(ConfNode *conf, OutputCtx *parent_ctx)
279
4
{
280
4
    OutputInitResult result = { NULL, false };
281
4
    OutputJsonCtx *json_ctx = parent_ctx->data;
282
283
4
    LogDNP3FileCtx *dnp3log_ctx = SCCalloc(1, sizeof(*dnp3log_ctx));
284
4
    if (unlikely(dnp3log_ctx == NULL)) {
285
0
        return result;
286
0
    }
287
4
    dnp3log_ctx->eve_ctx = json_ctx;
288
289
4
    OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx));
290
4
    if (unlikely(output_ctx == NULL)) {
291
0
        SCFree(dnp3log_ctx);
292
0
        return result;
293
0
    }
294
4
    output_ctx->data = dnp3log_ctx;
295
4
    output_ctx->DeInit = OutputDNP3LogDeInitCtxSub;
296
297
4
    SCLogInfo("DNP3 log sub-module initialized.");
298
299
4
    AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_DNP3);
300
301
4
    result.ctx = output_ctx;
302
4
    result.ok = true;
303
4
    return result;
304
4
}
305
306
307
static TmEcode JsonDNP3LogThreadInit(ThreadVars *t, const void *initdata, void **data)
308
4
{
309
4
    LogDNP3LogThread *thread = SCCalloc(1, sizeof(*thread));
310
4
    if (unlikely(thread == NULL)) {
311
0
        return TM_ECODE_FAILED;
312
0
    }
313
314
4
    if (initdata == NULL) {
315
0
        SCLogDebug("Error getting context for DNP3.  \"initdata\" is NULL.");
316
0
        goto error_exit;
317
0
    }
318
319
4
    thread->dnp3log_ctx = ((OutputCtx *)initdata)->data;
320
4
    thread->ctx = CreateEveThreadCtx(t, thread->dnp3log_ctx->eve_ctx);
321
4
    if (thread->ctx == NULL) {
322
0
        goto error_exit;
323
0
    }
324
325
4
    *data = (void *)thread;
326
327
4
    return TM_ECODE_OK;
328
329
0
error_exit:
330
0
    SCFree(thread);
331
0
    return TM_ECODE_FAILED;
332
4
}
333
334
static TmEcode JsonDNP3LogThreadDeinit(ThreadVars *t, void *data)
335
0
{
336
0
    LogDNP3LogThread *thread = (LogDNP3LogThread *)data;
337
0
    if (thread == NULL) {
338
0
        return TM_ECODE_OK;
339
0
    }
340
0
    FreeEveThreadCtx(thread->ctx);
341
0
    SCFree(thread);
342
0
    return TM_ECODE_OK;
343
0
}
344
345
void JsonDNP3LogRegister(void)
346
74
{
347
74
    OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonDNP3Log", "eve-log.dnp3",
348
74
            OutputDNP3LogInitSub, ALPROTO_DNP3, JsonDNP3Logger, JsonDNP3LogThreadInit,
349
            JsonDNP3LogThreadDeinit, NULL);
350
74
}