Coverage Report

Created: 2026-05-16 07:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/runmode-netmap.c
Line
Count
Source
1
/* Copyright (C) 2014-2022 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
* \ingroup netmap
20
*
21
* @{
22
*/
23
24
/**
25
 * \file
26
 *
27
 * \author Aleksey Katargin <gureedo@gmail.com>
28
 * \author Bill Meeks <billmeeks8@gmail.com>
29
 *
30
 * Netmap runmode
31
 *
32
 */
33
34
#include "suricata-common.h"
35
#include "decode.h"
36
#include "runmodes.h"
37
#include "runmode-netmap.h"
38
#include "util-runmodes.h"
39
#include "util-ioctl.h"
40
#include "util-byte.h"
41
#include "util-time.h"
42
43
#ifdef HAVE_NETMAP
44
#define NETMAP_WITH_LIBS
45
#include <net/netmap_user.h>
46
#endif /* HAVE_NETMAP */
47
48
#include "source-netmap.h"
49
#include "util-conf.h"
50
#include "suricata.h"
51
#include "util-bpf.h"
52
53
extern uint16_t max_pending_packets;
54
55
const char *RunModeNetmapGetDefaultMode(void)
56
0
{
57
0
    return "workers";
58
0
}
59
60
static int NetmapRunModeIsIPS(void)
61
0
{
62
0
    int nlive = LiveGetDeviceCount();
63
0
    int ldev;
64
0
    ConfNode *if_root;
65
0
    ConfNode *if_default = NULL;
66
0
    ConfNode *netmap_node;
67
0
    int has_ips = 0;
68
0
    int has_ids = 0;
69
70
    /* Find initial node */
71
0
    netmap_node = ConfGetNode("netmap");
72
0
    if (netmap_node == NULL) {
73
0
        return 0;
74
0
    }
75
76
0
    if_default = ConfNodeLookupKeyValue(netmap_node, "interface", "default");
77
78
0
    for (ldev = 0; ldev < nlive; ldev++) {
79
0
        const char *live_dev = LiveGetDeviceName(ldev);
80
0
        if (live_dev == NULL) {
81
0
            SCLogError("Problem with config file");
82
0
            return 0;
83
0
        }
84
0
        if_root = ConfNodeLookupKeyValue(netmap_node, "interface", live_dev);
85
86
0
        if (if_root == NULL) {
87
0
            if (if_default == NULL) {
88
0
                SCLogError("Problem with config file");
89
0
                return 0;
90
0
            }
91
0
            if_root = if_default;
92
0
        }
93
94
0
        const char *copymodestr = NULL;
95
0
        const char *copyifacestr = NULL;
96
0
        if (ConfGetChildValueWithDefault(if_root, if_default, "copy-mode", &copymodestr) == 1 &&
97
0
                ConfGetChildValue(if_root, "copy-iface", &copyifacestr) == 1) {
98
0
            if (strcmp(copymodestr, "ips") == 0) {
99
0
                has_ips = 1;
100
0
            } else {
101
0
                has_ids = 1;
102
0
            }
103
0
        } else {
104
0
            has_ids = 1;
105
0
        }
106
0
    }
107
108
0
    if (has_ids && has_ips) {
109
0
        SCLogWarning("Netmap using both IPS and TAP/IDS mode, this will not be "
110
0
                     "allowed in Suricata 8 due to undefined behavior. See ticket #5588.");
111
0
        for (ldev = 0; ldev < nlive; ldev++) {
112
0
            const char *live_dev = LiveGetDeviceName(ldev);
113
0
            if (live_dev == NULL) {
114
0
                SCLogError("Problem with config file");
115
0
                return 0;
116
0
            }
117
0
            if_root = ConfNodeLookupKeyValue(netmap_node, "interface", live_dev);
118
0
            const char *copymodestr = NULL;
119
120
0
            if (if_root == NULL) {
121
0
                if (if_default == NULL) {
122
0
                    SCLogError("Problem with config file");
123
0
                    return 0;
124
0
                }
125
0
                if_root = if_default;
126
0
            }
127
128
0
            if (!((ConfGetChildValueWithDefault(if_root, if_default, "copy-mode", &copymodestr) ==
129
0
                          1) &&
130
0
                        (strcmp(copymodestr, "ips") == 0))) {
131
0
                SCLogError("Netmap IPS mode used and interface '%s' is in IDS or TAP mode. "
132
0
                           "Sniffing '%s' but expect bad result as stream-inline is activated.",
133
0
                        live_dev, live_dev);
134
0
            }
135
0
        }
136
0
    }
137
138
0
    return has_ips;
139
0
}
140
141
static void NetmapRunModeEnableIPS(void)
142
0
{
143
0
    if (NetmapRunModeIsIPS()) {
144
0
        SCLogInfo("Netmap: Setting IPS mode");
145
0
        EngineModeSetIPS();
146
0
    }
147
0
}
148
149
void RunModeIdsNetmapRegister(void)
150
37
{
151
37
    RunModeRegisterNewRunMode(RUNMODE_NETMAP, "single", "Single threaded netmap mode",
152
37
            RunModeIdsNetmapSingle, NetmapRunModeEnableIPS);
153
37
    RunModeRegisterNewRunMode(RUNMODE_NETMAP, "workers",
154
37
            "Workers netmap mode, each thread does all"
155
37
            " tasks from acquisition to logging",
156
37
            RunModeIdsNetmapWorkers, NetmapRunModeEnableIPS);
157
37
    RunModeRegisterNewRunMode(RUNMODE_NETMAP, "autofp",
158
37
            "Multi-threaded netmap mode.  Packets from "
159
37
            "each flow are assigned to a single detect "
160
37
            "thread.",
161
37
            RunModeIdsNetmapAutoFp, NetmapRunModeEnableIPS);
162
37
    return;
163
37
}
164
165
#ifdef HAVE_NETMAP
166
167
static void NetmapDerefConfig(void *conf)
168
{
169
    NetmapIfaceConfig *pfp = (NetmapIfaceConfig *)conf;
170
    /* config is used only once but cost of this low. */
171
    if (SC_ATOMIC_SUB(pfp->ref, 1) == 1) {
172
        SCFree(pfp);
173
    }
174
}
175
176
static int ParseNetmapSettings(NetmapIfaceSettings *ns, const char *iface,
177
        ConfNode *if_root, ConfNode *if_default)
178
{
179
    ns->threads = 0;
180
    ns->promisc = true;
181
    ns->checksum_mode = CHECKSUM_VALIDATION_AUTO;
182
    ns->copy_mode = NETMAP_COPY_MODE_NONE;
183
    strlcpy(ns->iface, iface, sizeof(ns->iface));
184
185
    if (ns->iface[0]) {
186
        size_t len = strlen(ns->iface);
187
        if (ns->iface[len-1] == '+') {
188
            SCLogWarning("%s: interface uses obsolete '+' notation. Using '^' instead", ns->iface);
189
            ns->iface[len-1] = '^';
190
            ns->sw_ring = true;
191
        } else if (ns->iface[len-1] == '^') {
192
            ns->sw_ring = true;
193
        }
194
    }
195
196
    /* we will need the base interface name for later */
197
    char base_name[IFNAMSIZ];
198
    strlcpy(base_name, ns->iface, sizeof(base_name));
199
    if (strlen(base_name) > 0 &&
200
            (base_name[strlen(base_name) - 1] == '^' || base_name[strlen(base_name) - 1] == '*')) {
201
        base_name[strlen(base_name) - 1] = '\0';
202
    }
203
204
    /* prefixed with netmap or vale means it's not a real interface
205
     * and we don't check offloading. */
206
    if (strncmp(ns->iface, "netmap:", 7) != 0 &&
207
            strncmp(ns->iface, "vale", 4) != 0) {
208
        ns->real = true;
209
    }
210
211
    if (if_root == NULL && if_default == NULL) {
212
        SCLogInfo("%s: unable to find netmap config for interface \"%s\" or \"default\", using "
213
                  "default values",
214
                iface, iface);
215
        goto finalize;
216
217
    /* If there is no setting for current interface use default one as main iface */
218
    } else if (if_root == NULL) {
219
        if_root = if_default;
220
        if_default = NULL;
221
    }
222
223
    const char *threadsstr = NULL;
224
    if (ConfGetChildValueWithDefault(if_root, if_default, "threads", &threadsstr) != 1) {
225
        ns->threads = 0;
226
        ns->threads_auto = true;
227
    } else {
228
        if (strcmp(threadsstr, "auto") == 0) {
229
            ns->threads = 0;
230
            ns->threads_auto = true;
231
        } else {
232
            if (StringParseUint16(&ns->threads, 10, 0, threadsstr) < 0) {
233
                SCLogWarning("%s: invalid config value for threads: %s, resetting to 0", iface,
234
                        threadsstr);
235
                ns->threads = 0;
236
            }
237
        }
238
    }
239
240
    ConfSetBPFFilter(if_root, if_default, iface, &ns->bpf_filter);
241
242
    int boolval = 0;
243
    (void)ConfGetChildValueBoolWithDefault(if_root, if_default, "disable-promisc", (int *)&boolval);
244
    if (boolval) {
245
        SCLogInfo("%s: disabling promiscuous mode", ns->iface);
246
        ns->promisc = false;
247
    }
248
249
    const char *tmpctype;
250
    if (ConfGetChildValueWithDefault(if_root, if_default,
251
                "checksum-checks", &tmpctype) == 1)
252
    {
253
        if (strcmp(tmpctype, "auto") == 0) {
254
            ns->checksum_mode = CHECKSUM_VALIDATION_AUTO;
255
        } else if (ConfValIsTrue(tmpctype)) {
256
            ns->checksum_mode = CHECKSUM_VALIDATION_ENABLE;
257
        } else if (ConfValIsFalse(tmpctype)) {
258
            ns->checksum_mode = CHECKSUM_VALIDATION_DISABLE;
259
        } else {
260
            SCLogWarning("%s: invalid value for checksum-checks '%s'", iface, tmpctype);
261
        }
262
    }
263
264
    const char *copymodestr;
265
    if (ConfGetChildValueWithDefault(if_root, if_default,
266
                "copy-mode", &copymodestr) == 1)
267
    {
268
        if (strcmp(copymodestr, "ips") == 0) {
269
            ns->copy_mode = NETMAP_COPY_MODE_IPS;
270
        } else if (strcmp(copymodestr, "tap") == 0) {
271
            ns->copy_mode = NETMAP_COPY_MODE_TAP;
272
        } else {
273
            SCLogWarning("%s: invalid copy-mode %s (valid are tap, ips)", iface, copymodestr);
274
        }
275
    }
276
277
finalize:
278
279
    ns->ips = (ns->copy_mode != NETMAP_COPY_MODE_NONE);
280
281
    if (ns->threads_auto) {
282
        /* As NetmapGetRSSCount used to be broken on Linux,
283
         * fall back to GetIfaceRSSQueuesNum if needed. */
284
        ns->threads = NetmapGetRSSCount(base_name);
285
        if (ns->threads == 0) {
286
            /* need to use base_name of interface here */
287
            ns->threads = GetIfaceRSSQueuesNum(base_name);
288
        }
289
    }
290
    if (ns->threads <= 0) {
291
        ns->threads = 1;
292
    }
293
294
    return 0;
295
}
296
297
/**
298
 * \brief extract information from config file
299
 *
300
 * The returned structure will be freed by the thread init function.
301
 * This is thus necessary to copy the structure before giving it
302
 * to thread or to reparse the file for each thread (and thus have
303
 * new structure.
304
 *
305
 * \return a NetmapIfaceConfig corresponding to the interface name
306
 */
307
static void *ParseNetmapConfig(const char *iface_name)
308
{
309
    ConfNode *if_root = NULL;
310
    ConfNode *if_default = NULL;
311
    const char *out_iface = NULL;
312
313
    if (iface_name == NULL) {
314
        return NULL;
315
    }
316
317
    NetmapIfaceConfig *aconf = SCCalloc(1, sizeof(*aconf));
318
    if (unlikely(aconf == NULL)) {
319
        return NULL;
320
    }
321
322
    aconf->DerefFunc = NetmapDerefConfig;
323
    strlcpy(aconf->iface_name, iface_name, sizeof(aconf->iface_name));
324
    SC_ATOMIC_INIT(aconf->ref);
325
    (void) SC_ATOMIC_ADD(aconf->ref, 1);
326
327
    /* Find initial node */
328
    ConfNode *netmap_node = ConfGetNode("netmap");
329
    if (netmap_node == NULL) {
330
        SCLogInfo("%s: unable to find netmap config using default value", iface_name);
331
    } else {
332
        if_root = ConfFindDeviceConfig(netmap_node, aconf->iface_name);
333
        if_default = ConfFindDeviceConfig(netmap_node, "default");
334
    }
335
336
    /* parse settings for capture iface */
337
    ParseNetmapSettings(&aconf->in, aconf->iface_name, if_root, if_default);
338
339
    /* if we have a copy iface, parse that as well */
340
    if (netmap_node != NULL &&
341
            ConfGetChildValueWithDefault(if_root, if_default, "copy-iface", &out_iface) == 1)
342
    {
343
        if (strlen(out_iface) > 0) {
344
            if_root = ConfFindDeviceConfig(netmap_node, out_iface);
345
            ParseNetmapSettings(&aconf->out, out_iface, if_root, if_default);
346
        }
347
    }
348
349
    int ring_count = 0;
350
    if (aconf->in.real)
351
        ring_count = NetmapGetRSSCount(aconf->iface_name);
352
    if (strlen(aconf->iface_name) > 0 &&
353
            (aconf->iface_name[strlen(aconf->iface_name) - 1] == '^' ||
354
                    aconf->iface_name[strlen(aconf->iface_name) - 1] == '*')) {
355
        SCLogDebug("%s -- using %d netmap host ring pair%s", aconf->iface_name, ring_count,
356
                ring_count == 1 ? "" : "s");
357
    } else {
358
        SCLogDebug("%s -- using %d netmap ring pair%s", aconf->iface_name, ring_count,
359
                ring_count == 1 ? "" : "s");
360
    }
361
362
    for (int i = 0; i < ring_count; i++) {
363
        char live_buf[32] = { 0 };
364
        snprintf(live_buf, sizeof(live_buf), "netmap%d", i);
365
        LiveRegisterDevice(live_buf);
366
    }
367
368
    /* we need the base interface name with any trailing software
369
     * ring marker stripped for HW offloading checks */
370
    char base_name[sizeof(aconf->in.iface)];
371
    strlcpy(base_name, aconf->in.iface, sizeof(base_name));
372
    /* for a sw_ring enabled device name, strip the trailing char */
373
    if (aconf->in.sw_ring) {
374
        base_name[strlen(base_name) - 1] = '\0';
375
    }
376
377
    /* netmap needs all offloading to be disabled */
378
    if (aconf->in.real) {
379
        if (LiveGetOffload() == 0) {
380
            (void)GetIfaceOffloading(base_name, 1, 1);
381
        } else {
382
            DisableIfaceOffloading(LiveGetDevice(base_name), 1, 1);
383
        }
384
    }
385
386
    SC_ATOMIC_RESET(aconf->ref);
387
    (void) SC_ATOMIC_ADD(aconf->ref, aconf->in.threads);
388
    SCLogPerf("%s: using %d threads", aconf->iface_name, aconf->in.threads);
389
390
    LiveDeviceHasNoStats();
391
    return aconf;
392
}
393
394
static int NetmapConfigGeThreadsCount(void *conf)
395
{
396
    NetmapIfaceConfig *aconf = (NetmapIfaceConfig *)conf;
397
    return aconf->in.threads;
398
}
399
400
typedef enum { NETMAP_AUTOFP, NETMAP_WORKERS, NETMAP_SINGLE } NetmapRunMode_t;
401
402
static int NetmapRunModeInit(NetmapRunMode_t runmode)
403
{
404
    SCEnter();
405
406
    TimeModeSetLive();
407
408
    const char *live_dev = NULL;
409
    (void)ConfGet("netmap.live-interface", &live_dev);
410
411
    const char *runmode_str = "unknown";
412
    int ret;
413
    switch (runmode) {
414
        case NETMAP_AUTOFP:
415
            runmode_str = "autofp";
416
            ret = RunModeSetLiveCaptureAutoFp(ParseNetmapConfig, NetmapConfigGeThreadsCount,
417
                    "ReceiveNetmap", "DecodeNetmap", thread_name_autofp, live_dev);
418
            break;
419
        case NETMAP_WORKERS:
420
            runmode_str = "workers";
421
            ret = RunModeSetLiveCaptureWorkers(ParseNetmapConfig, NetmapConfigGeThreadsCount,
422
                    "ReceiveNetmap", "DecodeNetmap", thread_name_workers, live_dev);
423
            break;
424
        case NETMAP_SINGLE:
425
            runmode_str = "single";
426
            ret = RunModeSetLiveCaptureSingle(ParseNetmapConfig, NetmapConfigGeThreadsCount,
427
                    "ReceiveNetmap", "DecodeNetmap", thread_name_single, live_dev);
428
            break;
429
    }
430
    if (ret != 0) {
431
        FatalError("Unable to start runmode %s", runmode_str);
432
    }
433
434
    SCLogDebug("%s initialized", runmode_str);
435
436
    SCReturnInt(0);
437
}
438
439
int RunModeIdsNetmapAutoFp(void)
440
{
441
    return NetmapRunModeInit(NETMAP_AUTOFP);
442
}
443
444
/**
445
* \brief Single thread version of the netmap processing.
446
*/
447
int RunModeIdsNetmapSingle(void)
448
{
449
    return NetmapRunModeInit(NETMAP_SINGLE);
450
}
451
452
/**
453
 * \brief Workers version of the netmap processing.
454
 *
455
 * Start N threads with each thread doing all the work.
456
 *
457
 */
458
int RunModeIdsNetmapWorkers(void)
459
{
460
    return NetmapRunModeInit(NETMAP_WORKERS);
461
}
462
#else
463
int RunModeIdsNetmapAutoFp(void)
464
0
{
465
0
    SCEnter();
466
0
    FatalError("Netmap not configured");
467
0
    SCReturnInt(0);
468
0
}
469
470
/**
471
 * \brief Single thread version of the netmap processing.
472
 */
473
int RunModeIdsNetmapSingle(void)
474
0
{
475
0
    SCEnter();
476
0
    FatalError("Netmap not configured");
477
0
    SCReturnInt(0);
478
0
}
479
480
/**
481
* \brief Workers version of the netmap processing.
482
*
483
* Start N threads with each thread doing all the work.
484
*
485
*/
486
int RunModeIdsNetmapWorkers(void)
487
0
{
488
0
    SCEnter();
489
0
    FatalError("Netmap not configured");
490
0
    SCReturnInt(0);
491
0
}
492
#endif // #ifdef HAVE_NETMAP
493
494
/**
495
* @}
496
*/