Coverage Report

Created: 2026-11-12 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata8/src/util-affinity.c
Line
Count
Source
1
/* Copyright (C) 2010-2016 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
/** \file
19
 *
20
 *  \author Eric Leblond <eric@regit.org>
21
 *
22
 *  CPU affinity related code and helper.
23
 */
24
25
#include "suricata-common.h"
26
#include "suricata.h"
27
#define _THREAD_AFFINITY
28
#include "util-affinity.h"
29
#include "conf.h"
30
#include "conf-yaml-loader.h"
31
#include "runmodes.h"
32
#include "util-cpu.h"
33
#include "util-byte.h"
34
#include "util-debug.h"
35
#include "util-dpdk.h"
36
#include "util-unittest.h"
37
38
ThreadsAffinityType thread_affinity[MAX_CPU_SET] = {
39
    {
40
            .name = "receive-cpu-set",
41
            .mode_flag = EXCLUSIVE_AFFINITY,
42
            .prio = PRIO_MEDIUM,
43
            .lcpu = { 0 },
44
    },
45
    {
46
            .name = "worker-cpu-set",
47
            .mode_flag = EXCLUSIVE_AFFINITY,
48
            .prio = PRIO_MEDIUM,
49
            .lcpu = { 0 },
50
    },
51
    {
52
            .name = "verdict-cpu-set",
53
            .mode_flag = BALANCED_AFFINITY,
54
            .prio = PRIO_MEDIUM,
55
            .lcpu = { 0 },
56
    },
57
    {
58
            .name = "management-cpu-set",
59
            .mode_flag = BALANCED_AFFINITY,
60
            .prio = PRIO_MEDIUM,
61
            .lcpu = { 0 },
62
    },
63
64
};
65
66
int thread_affinity_init_done = 0;
67
68
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
69
#ifdef HAVE_HWLOC
70
static hwloc_topology_t topology = NULL;
71
#endif /* HAVE_HWLOC */
72
#endif /* OS_WIN32 and __OpenBSD__ */
73
74
static ThreadsAffinityType *AllocAndInitAffinityType(
75
        const char *name, const char *interface_name, ThreadsAffinityType *parent)
76
0
{
77
0
    ThreadsAffinityType *new_affinity = SCCalloc(1, sizeof(ThreadsAffinityType));
78
0
    if (new_affinity == NULL) {
79
0
        FatalError("Unable to allocate memory for new CPU affinity type");
80
0
    }
81
82
0
    new_affinity->name = SCStrdup(interface_name);
83
0
    if (new_affinity->name == NULL) {
84
0
        FatalError("Unable to allocate memory for new CPU affinity type name");
85
0
    }
86
0
    new_affinity->parent = parent;
87
0
    new_affinity->mode_flag = EXCLUSIVE_AFFINITY;
88
0
    new_affinity->prio = PRIO_MEDIUM;
89
0
    for (int i = 0; i < MAX_NUMA_NODES; i++) {
90
0
        new_affinity->lcpu[i] = 0;
91
0
    }
92
93
0
    if (parent != NULL) {
94
0
        if (parent->nb_children == parent->nb_children_capacity) {
95
0
            if (parent->nb_children_capacity == 0) {
96
0
                parent->nb_children_capacity = 2;
97
0
            } else {
98
0
                parent->nb_children_capacity *= 2;
99
0
            }
100
0
            void *p = SCRealloc(
101
0
                    parent->children, parent->nb_children_capacity * sizeof(ThreadsAffinityType *));
102
0
            if (p == NULL) {
103
0
                FatalError("Unable to reallocate memory for children CPU affinity types");
104
0
            }
105
0
            parent->children = p;
106
0
        }
107
0
        parent->children[parent->nb_children++] = new_affinity;
108
0
    }
109
110
0
    return new_affinity;
111
0
}
112
113
ThreadsAffinityType *FindAffinityByInterface(
114
        ThreadsAffinityType *parent, const char *interface_name)
115
0
{
116
0
    if (parent == NULL || interface_name == NULL || parent->nb_children == 0 ||
117
0
            parent->children == NULL) {
118
0
        return NULL;
119
0
    }
120
121
0
    for (uint32_t i = 0; i < parent->nb_children; i++) {
122
0
        if (parent->children[i] && parent->children[i]->name &&
123
0
                strcmp(parent->children[i]->name, interface_name) == 0) {
124
0
            return parent->children[i];
125
0
        }
126
0
    }
127
0
    return NULL;
128
0
}
129
130
/**
131
 * \brief Find affinity by name (*-cpu-set name) and an interface name.
132
 * \param name the name of the affinity (e.g. worker-cpu-set, receive-cpu-set).
133
 * The name is required and cannot be NULL.
134
 * \param interface_name the name of the interface.
135
 * If NULL, the affinity is looked up by name only.
136
 *  \retval a pointer to the affinity or NULL if not found
137
 */
138
ThreadsAffinityType *GetAffinityTypeForNameAndIface(const char *name, const char *interface_name)
139
0
{
140
0
    if (name == NULL || *name == '\0') {
141
0
        return NULL;
142
0
    }
143
144
0
    ThreadsAffinityType *parent_affinity = NULL;
145
0
    for (int i = 0; i < MAX_CPU_SET; i++) {
146
0
        if (thread_affinity[i].name != NULL && strcmp(thread_affinity[i].name, name) == 0) {
147
0
            parent_affinity = &thread_affinity[i];
148
0
            break;
149
0
        }
150
0
    }
151
152
0
    if (parent_affinity == NULL) {
153
0
        SCLogError("CPU affinity with name \"%s\" not found", name);
154
0
        return NULL;
155
0
    }
156
157
0
    if (interface_name != NULL) {
158
0
        ThreadsAffinityType *child_affinity =
159
0
                FindAffinityByInterface(parent_affinity, interface_name);
160
        // found or not found, it is returned
161
0
        return child_affinity;
162
0
    }
163
164
0
    return parent_affinity;
165
0
}
166
167
/**
168
 * \brief Finds affinity by its name and interface name.
169
 * Interfaces are children of cpu-set names. If the queried interface is not
170
 * found, then it is allocated, initialized and assigned to the queried cpu-set.
171
 * \param name the name of the affinity (e.g. worker-cpu-set, receive-cpu-set).
172
 * The name is required and cannot be NULL.
173
 * \param interface_name the name of the interface.
174
 * If NULL, the affinity is looked up by name only.
175
 * \retval a pointer to the affinity or NULL if not found
176
 */
177
ThreadsAffinityType *GetOrAllocAffinityTypeForIfaceOfName(
178
        const char *name, const char *interface_name)
179
0
{
180
0
    int i;
181
0
    ThreadsAffinityType *parent_affinity = NULL;
182
183
0
    for (i = 0; i < MAX_CPU_SET; i++) {
184
0
        if (strcmp(thread_affinity[i].name, name) == 0) {
185
0
            parent_affinity = &thread_affinity[i];
186
0
            break;
187
0
        }
188
0
    }
189
190
0
    if (parent_affinity == NULL) {
191
0
        SCLogError("CPU affinity with name \"%s\" not found", name);
192
0
        return NULL;
193
0
    }
194
195
0
    if (interface_name != NULL) {
196
0
        ThreadsAffinityType *child_affinity =
197
0
                FindAffinityByInterface(parent_affinity, interface_name);
198
0
        if (child_affinity != NULL) {
199
0
            return child_affinity;
200
0
        }
201
202
        // If not found, allocate and initialize a new child affinity
203
0
        return AllocAndInitAffinityType(name, interface_name, parent_affinity);
204
0
    }
205
206
0
    return parent_affinity;
207
0
}
208
209
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
210
static void AffinitySetupInit(void)
211
0
{
212
0
    int i, j;
213
0
    int ncpu = UtilCpuGetNumProcessorsOnline();
214
215
0
    SCLogDebug("Initialize CPU affinity setup");
216
    /* be conservative relatively to OS: use all cpus by default */
217
0
    for (i = 0; i < MAX_CPU_SET; i++) {
218
0
        cpu_set_t *cs = &thread_affinity[i].cpu_set;
219
0
        CPU_ZERO(cs);
220
0
        for (j = 0; j < ncpu; j++) {
221
0
            CPU_SET(j, cs);
222
0
        }
223
0
        SCMutexInit(&thread_affinity[i].taf_mutex, NULL);
224
0
    }
225
0
}
226
227
int BuildCpusetWithCallback(
228
        const char *name, SCConfNode *node, void (*Callback)(int i, void *data), void *data)
229
0
{
230
0
    SCConfNode *lnode;
231
0
    TAILQ_FOREACH(lnode, &node->head, next) {
232
0
        uint32_t i;
233
0
        uint32_t a, b;
234
0
        uint32_t stop = 0;
235
0
        uint32_t max = UtilCpuGetNumProcessorsOnline();
236
0
        if (max > 0) {
237
0
            max--;
238
0
        }
239
0
        if (!strcmp(lnode->val, "all")) {
240
0
            a = 0;
241
0
            b = max;
242
0
            stop = 1;
243
0
        } else if (strchr(lnode->val, '-') != NULL) {
244
0
            char *sep = strchr(lnode->val, '-');
245
0
            if (StringParseUint32(&a, 10, sep - lnode->val, lnode->val) <= 0) {
246
0
                SCLogError("%s: invalid cpu range (start invalid): \"%s\"", name, lnode->val);
247
0
                return -1;
248
0
            }
249
0
            if (StringParseUint32(&b, 10, strlen(sep) - 1, sep + 1) <= 0) {
250
0
                SCLogError("%s: invalid cpu range (end invalid): \"%s\"", name, lnode->val);
251
0
                return -1;
252
0
            }
253
0
            if (a > b) {
254
0
                SCLogError("%s: invalid cpu range (bad order): \"%s\"", name, lnode->val);
255
0
                return -1;
256
0
            }
257
0
            if (b > max) {
258
0
                SCLogError("%s: upper bound (%d) of cpu set is too high, only %d cpu(s)", name, b,
259
0
                        max + 1);
260
0
                return -1;
261
0
            }
262
0
        } else {
263
0
            if (StringParseUint32(&a, 10, strlen(lnode->val), lnode->val) <= 0) {
264
0
                SCLogError("%s: invalid cpu range (not an integer): \"%s\"", name, lnode->val);
265
0
                return -1;
266
0
            }
267
0
            b = a;
268
0
        }
269
0
        for (i = a; i<= b; i++) {
270
0
            Callback(i, data);
271
0
        }
272
0
        if (stop) {
273
0
            break;
274
0
        }
275
0
    }
276
0
    return 0;
277
0
}
278
279
static void AffinityCallback(int i, void *data)
280
0
{
281
0
    CPU_SET(i, (cpu_set_t *)data);
282
0
}
283
284
static int BuildCpuset(const char *name, SCConfNode *node, cpu_set_t *cpu)
285
0
{
286
0
    return BuildCpusetWithCallback(name, node, AffinityCallback, (void *)cpu);
287
0
}
288
289
/**
290
 * \brief Get the appropriate set name for a given affinity value.
291
 */
292
static const char *GetAffinitySetName(const char *val)
293
0
{
294
0
    if (val == NULL) {
295
0
        return NULL;
296
0
    }
297
0
    if (strcmp(val, "decode-cpu-set") == 0 || strcmp(val, "stream-cpu-set") == 0 ||
298
0
            strcmp(val, "reject-cpu-set") == 0 || strcmp(val, "output-cpu-set") == 0) {
299
0
        return NULL;
300
0
    }
301
302
0
    return (strcmp(val, "detect-cpu-set") == 0) ? "worker-cpu-set" : val;
303
0
}
304
305
/**
306
 * \brief Set up CPU sets for the given affinity type.
307
 */
308
static void SetupCpuSets(ThreadsAffinityType *taf, SCConfNode *affinity, const char *setname)
309
0
{
310
0
    CPU_ZERO(&taf->cpu_set);
311
0
    SCConfNode *cpu_node = SCConfNodeLookupChild(affinity, "cpu");
312
0
    if (cpu_node != NULL) {
313
0
        if (BuildCpuset(setname, cpu_node, &taf->cpu_set) < 0) {
314
0
            SCLogWarning("Failed to parse CPU set for %s", setname);
315
0
        }
316
0
    } else {
317
0
        SCLogWarning("Unable to find 'cpu' node for set %s", setname);
318
0
    }
319
0
}
320
321
/**
322
 * \brief Build a priority CPU set for the given priority level.
323
 */
324
static void BuildPriorityCpuset(ThreadsAffinityType *taf, SCConfNode *prio_node,
325
        const char *priority, cpu_set_t *cpuset, const char *setname)
326
0
{
327
0
    SCConfNode *node = SCConfNodeLookupChild(prio_node, priority);
328
0
    if (node != NULL) {
329
0
        if (BuildCpuset(setname, node, cpuset) < 0) {
330
0
            SCLogWarning("Failed to parse %s priority CPU set for %s", priority, setname);
331
0
        }
332
0
    } else {
333
0
        SCLogDebug("Unable to find '%s' priority for set %s", priority, setname);
334
0
    }
335
0
}
336
337
/**
338
 * \brief Set up the default priority for the given affinity type.
339
 * \retval 0 on success, -1 on error
340
 */
341
static int SetupDefaultPriority(
342
        ThreadsAffinityType *taf, SCConfNode *prio_node, const char *setname)
343
0
{
344
0
    SCConfNode *default_node = SCConfNodeLookupChild(prio_node, "default");
345
0
    if (default_node == NULL) {
346
0
        return 0;
347
0
    }
348
349
0
    if (strcmp(default_node->val, "low") == 0) {
350
0
        taf->prio = PRIO_LOW;
351
0
    } else if (strcmp(default_node->val, "medium") == 0) {
352
0
        taf->prio = PRIO_MEDIUM;
353
0
    } else if (strcmp(default_node->val, "high") == 0) {
354
0
        taf->prio = PRIO_HIGH;
355
0
    } else {
356
0
        SCLogError("Unknown default CPU affinity priority: %s", default_node->val);
357
0
        return -1;
358
0
    }
359
360
0
    SCLogConfig("Using default priority '%s' for set %s", default_node->val, setname);
361
0
    return 0;
362
0
}
363
364
/**
365
 * \brief Set up priority CPU sets for the given affinity type.
366
 * \retval 0 on success, -1 on error
367
 */
368
static int SetupAffinityPriority(
369
        ThreadsAffinityType *taf, SCConfNode *affinity, const char *setname)
370
0
{
371
0
    CPU_ZERO(&taf->lowprio_cpu);
372
0
    CPU_ZERO(&taf->medprio_cpu);
373
0
    CPU_ZERO(&taf->hiprio_cpu);
374
0
    SCConfNode *prio_node = SCConfNodeLookupChild(affinity, "prio");
375
0
    if (prio_node == NULL) {
376
0
        return 0;
377
0
    }
378
379
0
    BuildPriorityCpuset(taf, prio_node, "low", &taf->lowprio_cpu, setname);
380
0
    BuildPriorityCpuset(taf, prio_node, "medium", &taf->medprio_cpu, setname);
381
0
    BuildPriorityCpuset(taf, prio_node, "high", &taf->hiprio_cpu, setname);
382
0
    return SetupDefaultPriority(taf, prio_node, setname);
383
0
}
384
385
/**
386
 * \brief Set up CPU affinity mode for the given affinity type.
387
 * \retval 0 on success, -1 on error
388
 */
389
static int SetupAffinityMode(ThreadsAffinityType *taf, SCConfNode *affinity)
390
0
{
391
0
    SCConfNode *mode_node = SCConfNodeLookupChild(affinity, "mode");
392
0
    if (mode_node == NULL) {
393
0
        return 0;
394
0
    }
395
396
0
    if (strcmp(mode_node->val, "exclusive") == 0) {
397
0
        taf->mode_flag = EXCLUSIVE_AFFINITY;
398
0
    } else if (strcmp(mode_node->val, "balanced") == 0) {
399
0
        taf->mode_flag = BALANCED_AFFINITY;
400
0
    } else {
401
0
        SCLogError("Unknown CPU affinity mode: %s", mode_node->val);
402
0
        return -1;
403
0
    }
404
0
    return 0;
405
0
}
406
407
/**
408
 * \brief Set up the number of threads for the given affinity type.
409
 * \retval 0 on success, -1 on error
410
 */
411
static int SetupAffinityThreads(ThreadsAffinityType *taf, SCConfNode *affinity)
412
0
{
413
0
    SCConfNode *threads_node = SCConfNodeLookupChild(affinity, "threads");
414
0
    if (threads_node == NULL) {
415
0
        return 0;
416
0
    }
417
418
0
    if (StringParseUint32(&taf->nb_threads, 10, 0, threads_node->val) < 0 || taf->nb_threads == 0) {
419
0
        SCLogError("Invalid thread count: %s", threads_node->val);
420
0
        return -1;
421
0
    }
422
0
    return 0;
423
0
}
424
425
/**
426
 * \brief Get the YAML path for the given affinity type.
427
 * The path is built using the parent name (if available) and the affinity name.
428
 * Do not free the returned string.
429
 * \param taf the affinity type - if NULL, the path is built for the root node
430
 * \return a string containing the YAML path, or NULL if the path is too long
431
 */
432
char *AffinityGetYamlPath(ThreadsAffinityType *taf)
433
0
{
434
0
    static char rootpath[] = "threading.cpu-affinity";
435
0
    static char path[1024] = { 0 };
436
0
    char subpath[256] = { 0 };
437
438
0
    if (taf == NULL) {
439
0
        return rootpath;
440
0
    }
441
442
0
    if (taf->parent != NULL) {
443
0
        long r = snprintf(
444
0
                subpath, sizeof(subpath), "%s.interface-specific-cpu-set.", taf->parent->name);
445
0
        if (r < 0 || r >= (long)sizeof(subpath)) {
446
0
            SCLogError("Unable to build YAML path for CPU affinity %s.%s", taf->parent->name,
447
0
                    taf->name);
448
0
            return NULL;
449
0
        }
450
0
    } else {
451
0
        subpath[0] = '\0';
452
0
    }
453
454
0
    long r = snprintf(path, sizeof(path), "%s.%s%s", rootpath, subpath, taf->name);
455
0
    if (r < 0 || r >= (long)sizeof(path)) {
456
0
        SCLogError("Unable to build YAML path for CPU affinity %s", taf->name);
457
0
        return NULL;
458
0
    }
459
460
0
    return path;
461
0
}
462
463
static void ResetCPUs(ThreadsAffinityType *taf)
464
0
{
465
0
    for (int i = 0; i < MAX_NUMA_NODES; i++) {
466
0
        taf->lcpu[i] = 0;
467
0
    }
468
0
}
469
470
/**
471
 * \brief Check if the set name corresponds to a worker CPU set.
472
 */
473
static bool IsWorkerCpuSet(const char *setname)
474
0
{
475
0
    return (strcmp(setname, "worker-cpu-set") == 0);
476
0
}
477
478
/**
479
 * \brief Check if the set name corresponds to a receive CPU set.
480
 */
481
static bool IsReceiveCpuSet(const char *setname)
482
0
{
483
0
    return (strcmp(setname, "receive-cpu-set") == 0);
484
0
}
485
486
/**
487
 * \brief Set up affinity configuration for a single interface.
488
 */
489
/**
490
 * \brief Set up affinity configuration for a single interface.
491
 * \retval 0 on success, -1 on error
492
 */
493
static int SetupSingleIfaceAffinity(ThreadsAffinityType *taf, SCConfNode *iface_node)
494
0
{
495
    // offload to Setup function
496
0
    SCConfNode *child_node;
497
0
    const char *interface_name = NULL;
498
0
    TAILQ_FOREACH (child_node, &iface_node->head, next) {
499
0
        if (strcmp(child_node->name, "interface") == 0) {
500
0
            interface_name = child_node->val;
501
0
            break;
502
0
        }
503
0
    }
504
0
    if (interface_name == NULL) {
505
0
        return 0;
506
0
    }
507
508
0
    ThreadsAffinityType *iface_taf =
509
0
            GetOrAllocAffinityTypeForIfaceOfName(taf->name, interface_name);
510
0
    if (iface_taf == NULL) {
511
0
        SCLogError("Failed to allocate CPU affinity type for interface: %s", interface_name);
512
0
        return -1;
513
0
    }
514
515
0
    SetupCpuSets(iface_taf, iface_node, interface_name);
516
0
    if (SetupAffinityPriority(iface_taf, iface_node, interface_name) < 0) {
517
0
        return -1;
518
0
    }
519
0
    if (SetupAffinityMode(iface_taf, iface_node) < 0) {
520
0
        return -1;
521
0
    }
522
0
    if (SetupAffinityThreads(iface_taf, iface_node) < 0) {
523
0
        return -1;
524
0
    }
525
0
    return 0;
526
0
}
527
528
/**
529
 * \brief Set up per-interface affinity configurations.
530
 * \retval 0 on success, -1 on error
531
 */
532
static int SetupPerIfaceAffinity(ThreadsAffinityType *taf, SCConfNode *affinity)
533
0
{
534
0
    char if_af[] = "interface-specific-cpu-set";
535
0
    SCConfNode *per_iface_node = SCConfNodeLookupChild(affinity, if_af);
536
0
    if (per_iface_node == NULL) {
537
0
        return 0;
538
0
    }
539
540
0
    SCConfNode *iface_node;
541
0
    TAILQ_FOREACH (iface_node, &per_iface_node->head, next) {
542
0
        if (strcmp(iface_node->val, "interface") == 0) {
543
0
            if (SetupSingleIfaceAffinity(taf, iface_node) < 0) {
544
0
                return -1;
545
0
            }
546
0
        } else {
547
0
            SCLogWarning("Unknown node in %s: %s", if_af, iface_node->name);
548
0
        }
549
0
    }
550
0
    return 0;
551
0
}
552
553
/**
554
 * \brief Check if CPU affinity configuration node follows format used in Suricata 7 and below
555
 * \retval true if CPU affinity uses Suricata <=7.0, false if it uses the new format (Suricata
556
 * >=8.0)
557
 */
558
static bool AffinityConfigIsLegacy(void)
559
0
{
560
0
    static bool is_using_legacy_affinity_format = false;
561
0
    if (thread_affinity_init_done == 0) {
562
        // reset the flag
563
0
        is_using_legacy_affinity_format = false;
564
0
    } else {
565
0
        return is_using_legacy_affinity_format;
566
0
    }
567
568
0
    SCConfNode *root = SCConfGetNode(AffinityGetYamlPath(NULL));
569
0
    if (root == NULL) {
570
0
        return is_using_legacy_affinity_format;
571
0
    }
572
573
0
    SCConfNode *affinity;
574
0
    TAILQ_FOREACH (affinity, &root->head, next) {
575
        // If a child does not contain "-cpu-set", then the conf is legacy
576
        // Names in the legacy format (list of *-cpu-sets) contain
577
        // list item IDs - "0" : "management-cpu-set", "1" : "worker-cpu-set"
578
0
        if (strstr(affinity->name, "-cpu-set") == NULL) {
579
0
            is_using_legacy_affinity_format = true;
580
0
            return is_using_legacy_affinity_format;
581
0
        }
582
0
    }
583
584
0
    return is_using_legacy_affinity_format;
585
0
}
586
#endif /* OS_WIN32 and __OpenBSD__ */
587
588
/**
589
 * \brief Extract CPU affinity configuration from current config file
590
 */
591
void AffinitySetupLoadFromConfig(void)
592
0
{
593
0
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
594
0
    if (thread_affinity_init_done == 0) {
595
0
        AffinitySetupInit();
596
0
        AffinityConfigIsLegacy();
597
0
        thread_affinity_init_done = 1;
598
0
    }
599
600
0
    SCLogDebug("Loading %s from config", AffinityGetYamlPath(NULL));
601
0
    SCConfNode *root = SCConfGetNode(AffinityGetYamlPath(NULL));
602
0
    if (root == NULL) {
603
0
        SCLogInfo("Cannot find %s node in config", AffinityGetYamlPath(NULL));
604
0
        return;
605
0
    }
606
607
0
    SCConfNode *affinity;
608
0
    TAILQ_FOREACH(affinity, &root->head, next) {
609
0
        char *v = AffinityConfigIsLegacy() ? affinity->val : affinity->name;
610
0
        const char *setname = GetAffinitySetName(v);
611
0
        if (setname == NULL) {
612
0
            continue;
613
0
        }
614
615
0
        ThreadsAffinityType *taf = GetOrAllocAffinityTypeForIfaceOfName(setname, NULL);
616
0
        if (taf == NULL) {
617
0
            SCLogError("Failed to allocate CPU affinity type: %s", setname);
618
0
            continue;
619
0
        }
620
621
0
        SCLogConfig("Found CPU affinity definition for \"%s\"", setname);
622
623
0
        SCConfNode *aff_query_node = AffinityConfigIsLegacy() ? affinity->head.tqh_first : affinity;
624
0
        SetupCpuSets(taf, aff_query_node, setname);
625
0
        if (SetupAffinityPriority(taf, aff_query_node, setname) < 0) {
626
0
            SCLogError("Failed to setup priority for CPU affinity type: %s", setname);
627
0
            continue;
628
0
        }
629
0
        if (SetupAffinityMode(taf, aff_query_node) < 0) {
630
0
            SCLogError("Failed to setup mode for CPU affinity type: %s", setname);
631
0
            continue;
632
0
        }
633
0
        if (SetupAffinityThreads(taf, aff_query_node) < 0) {
634
0
            SCLogError("Failed to setup threads for CPU affinity type: %s", setname);
635
0
            continue;
636
0
        }
637
638
0
        if (!AffinityConfigIsLegacy() && (IsWorkerCpuSet(setname) || IsReceiveCpuSet(setname))) {
639
0
            if (SetupPerIfaceAffinity(taf, affinity) < 0) {
640
0
                SCLogError("Failed to setup per-interface affinity for CPU affinity type: %s",
641
0
                        setname);
642
0
                continue;
643
0
            }
644
0
        }
645
0
    }
646
0
#endif /* OS_WIN32 and __OpenBSD__ */
647
0
}
648
649
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
650
#ifdef HAVE_HWLOC
651
static int HwLocDeviceNumaGet(hwloc_topology_t topo, hwloc_obj_t obj)
652
{
653
#if HWLOC_VERSION_MAJOR >= 2 && HWLOC_VERSION_MINOR >= 5
654
    hwloc_obj_t nodes[MAX_NUMA_NODES];
655
    unsigned num_nodes = MAX_NUMA_NODES;
656
    struct hwloc_location location;
657
658
    location.type = HWLOC_LOCATION_TYPE_OBJECT;
659
    location.location.object = obj;
660
661
    int result = hwloc_get_local_numanode_objs(topo, &location, &num_nodes, nodes, 0);
662
    if (result == 0 && num_nodes > 0 && num_nodes <= MAX_NUMA_NODES) {
663
        return nodes[0]->logical_index;
664
    }
665
    return -1;
666
#else
667
    hwloc_obj_t non_io_ancestor = hwloc_get_non_io_ancestor_obj(topo, obj);
668
    if (non_io_ancestor == NULL) {
669
        return -1;
670
    }
671
672
    // Iterate over NUMA nodes and check their nodeset
673
    hwloc_obj_t numa_node = NULL;
674
    while ((numa_node = hwloc_get_next_obj_by_type(topo, HWLOC_OBJ_NUMANODE, numa_node)) != NULL) {
675
        if (hwloc_bitmap_isset(non_io_ancestor->nodeset, numa_node->os_index)) {
676
            return numa_node->logical_index;
677
        }
678
    }
679
680
    return -1;
681
#endif /* ! HWLOC_VERSION_MAJOR >= 2 && HWLOC_VERSION_MINOR >= 5 */
682
}
683
684
static hwloc_obj_t HwLocDeviceGetByKernelName(hwloc_topology_t topo, const char *interface_name)
685
{
686
    hwloc_obj_t obj = NULL;
687
688
    while ((obj = hwloc_get_next_osdev(topo, obj)) != NULL) {
689
        if (obj->attr->osdev.type == HWLOC_OBJ_OSDEV_NETWORK &&
690
                strcmp(obj->name, interface_name) == 0) {
691
            hwloc_obj_t parent = obj->parent;
692
            while (parent) {
693
                if (parent->type == HWLOC_OBJ_PCI_DEVICE) {
694
                    return parent;
695
                }
696
                parent = parent->parent;
697
            }
698
        }
699
    }
700
    return NULL;
701
}
702
703
// Static function to deparse PCIe interface string name to individual components
704
/**
705
 * \brief Parse PCIe address string to individual components
706
 * \param[in] pcie_address PCIe address string
707
 * \param[out] domain Domain component
708
 * \param[out] bus Bus component
709
 * \param[out] device Device component
710
 * \param[out] function Function component
711
 */
712
static int PcieAddressToComponents(const char *pcie_address, unsigned int *domain,
713
        unsigned int *bus, unsigned int *device, unsigned int *function)
714
{
715
    // Handle both full and short PCIe address formats
716
    if (sscanf(pcie_address, "%x:%x:%x.%x", domain, bus, device, function) != 4) {
717
        if (sscanf(pcie_address, "%x:%x.%x", bus, device, function) != 3) {
718
            return -1;
719
        }
720
        *domain = 0; // Default domain to 0 if not provided
721
    }
722
    return 0;
723
}
724
725
// Function to convert PCIe address to hwloc object
726
static hwloc_obj_t HwLocDeviceGetByPcie(hwloc_topology_t topo, const char *pcie_address)
727
{
728
    hwloc_obj_t obj = NULL;
729
    unsigned int domain, bus, device, function;
730
    int r = PcieAddressToComponents(pcie_address, &domain, &bus, &device, &function);
731
    if (r == 0) {
732
        while ((obj = hwloc_get_next_pcidev(topo, obj)) != NULL) {
733
            if (obj->attr->pcidev.domain == domain && obj->attr->pcidev.bus == bus &&
734
                    obj->attr->pcidev.dev == device && obj->attr->pcidev.func == function) {
735
                return obj;
736
            }
737
        }
738
    }
739
    return NULL;
740
}
741
742
static void HwlocObjectDump(hwloc_obj_t obj, const char *iface_name)
743
{
744
    if (!obj) {
745
        SCLogDebug("No object found for the given PCIe address.\n");
746
        return;
747
    }
748
749
    static char pcie_address[32];
750
    snprintf(pcie_address, sizeof(pcie_address), "%04x:%02x:%02x.%x", obj->attr->pcidev.domain,
751
            obj->attr->pcidev.bus, obj->attr->pcidev.dev, obj->attr->pcidev.func);
752
    SCLogDebug("Interface (%s / %s) has NUMA ID %d", iface_name, pcie_address,
753
            HwLocDeviceNumaGet(topology, obj));
754
755
    SCLogDebug("Object type: %s\n", hwloc_obj_type_string(obj->type));
756
    SCLogDebug("Logical index: %u\n", obj->logical_index);
757
    SCLogDebug("Depth: %u\n", obj->depth);
758
    SCLogDebug("Attributes:\n");
759
    if (obj->type == HWLOC_OBJ_PCI_DEVICE) {
760
        SCLogDebug("  Domain: %04x\n", obj->attr->pcidev.domain);
761
        SCLogDebug("  Bus: %02x\n", obj->attr->pcidev.bus);
762
        SCLogDebug("  Device: %02x\n", obj->attr->pcidev.dev);
763
        SCLogDebug("  Function: %01x\n", obj->attr->pcidev.func);
764
        SCLogDebug("  Class ID: %04x\n", obj->attr->pcidev.class_id);
765
        SCLogDebug("  Vendor ID: %04x\n", obj->attr->pcidev.vendor_id);
766
        SCLogDebug("  Device ID: %04x\n", obj->attr->pcidev.device_id);
767
        SCLogDebug("  Subvendor ID: %04x\n", obj->attr->pcidev.subvendor_id);
768
        SCLogDebug("  Subdevice ID: %04x\n", obj->attr->pcidev.subdevice_id);
769
        SCLogDebug("  Revision: %02x\n", obj->attr->pcidev.revision);
770
        SCLogDebug("  Link speed: %f GB/s\n", obj->attr->pcidev.linkspeed);
771
    } else {
772
        SCLogDebug("  No PCI device attributes available.\n");
773
    }
774
}
775
776
static bool TopologyShouldAutopin(ThreadVars *tv, ThreadsAffinityType *taf)
777
{
778
    bool cond;
779
    SCMutexLock(&taf->taf_mutex);
780
    cond = tv->type == TVT_PPT && tv->iface_name &&
781
           (strcmp(tv->iface_name, taf->name) == 0 ||
782
                   (strcmp("worker-cpu-set", taf->name) == 0 && RunmodeIsWorkers()) ||
783
                   (strcmp("receive-cpu-set", taf->name) == 0 && RunmodeIsAutofp()));
784
    SCMutexUnlock(&taf->taf_mutex);
785
    return cond;
786
}
787
788
/**
789
 * \brief Initialize the hardware topology.
790
 * \retval 0 on success, -1 on error
791
 */
792
static int TopologyInitialize(void)
793
{
794
    if (topology == NULL) {
795
        if (hwloc_topology_init(&topology) == -1) {
796
            SCLogError("Failed to initialize topology");
797
            return -1;
798
        }
799
800
        if (hwloc_topology_set_flags(topology, HWLOC_TOPOLOGY_FLAG_WHOLE_SYSTEM) == -1 ||
801
                hwloc_topology_set_io_types_filter(topology, HWLOC_TYPE_FILTER_KEEP_ALL) == -1 ||
802
                hwloc_topology_load(topology) == -1) {
803
            SCLogError("Failed to set/load topology");
804
            hwloc_topology_destroy(topology);
805
            topology = NULL;
806
            return -1;
807
        }
808
    }
809
    return 0;
810
}
811
812
void TopologyDestroy(void)
813
{
814
    if (topology != NULL) {
815
        hwloc_topology_destroy(topology);
816
        topology = NULL;
817
    }
818
}
819
820
static int InterfaceGetNumaNode(ThreadVars *tv)
821
{
822
    hwloc_obj_t if_obj = HwLocDeviceGetByKernelName(topology, tv->iface_name);
823
    if (if_obj == NULL) {
824
        if_obj = HwLocDeviceGetByPcie(topology, tv->iface_name);
825
    }
826
827
    if (if_obj != NULL && SCLogGetLogLevel() == SC_LOG_DEBUG) {
828
        HwlocObjectDump(if_obj, tv->iface_name);
829
    }
830
831
    int32_t numa_id = HwLocDeviceNumaGet(topology, if_obj);
832
    if (numa_id < 0 && SCRunmodeGet() == RUNMODE_DPDK) {
833
        // DPDK fallback for e.g. net_bonding (vdev) PMDs
834
        int32_t r = DPDKDeviceNameSetSocketID(tv->iface_name, &numa_id);
835
        if (r < 0) {
836
            numa_id = -1;
837
        }
838
    }
839
840
    if (numa_id < 0) {
841
        SCLogDebug("Unable to find NUMA node for interface %s", tv->iface_name);
842
    }
843
844
    return numa_id;
845
}
846
#endif /* HAVE_HWLOC */
847
848
static bool CPUIsFromNuma(uint16_t ncpu, uint16_t numa)
849
0
{
850
#ifdef HAVE_HWLOC
851
    int core_id = ncpu;
852
    int depth = hwloc_get_type_depth(topology, HWLOC_OBJ_NUMANODE);
853
    hwloc_obj_t numa_node = NULL;
854
    bool found = false;
855
    uint16_t found_numa = 0;
856
857
    // Invalid depth or no NUMA nodes available
858
    if (depth == HWLOC_TYPE_DEPTH_UNKNOWN) {
859
        return false;
860
    }
861
862
    while ((numa_node = hwloc_get_next_obj_by_depth(topology, depth, numa_node)) != NULL) {
863
        hwloc_cpuset_t cpuset = hwloc_bitmap_alloc();
864
        if (cpuset == NULL) {
865
            SCLogDebug("Failed to allocate cpuset");
866
            continue;
867
        }
868
        hwloc_bitmap_copy(cpuset, numa_node->cpuset);
869
870
        if (hwloc_bitmap_isset(cpuset, core_id)) {
871
            SCLogDebug("Core %d - NUMA %d", core_id, numa_node->logical_index);
872
            found = true;
873
            found_numa = numa_node->logical_index;
874
            hwloc_bitmap_free(cpuset);
875
            break;
876
        }
877
        hwloc_bitmap_free(cpuset);
878
    }
879
880
    // After loop, check if we found the CPU and match the requested NUMA node
881
    if (found && numa == found_numa) {
882
        return true;
883
    }
884
885
    // CPU was not found in any NUMA node or did not match requested NUMA
886
#endif /* HAVE_HWLOC */
887
888
0
    return false;
889
0
}
890
891
static int16_t FindCPUInNumaNode(int numa_node, ThreadsAffinityType *taf)
892
0
{
893
0
    if (numa_node < 0) {
894
0
        return -1;
895
0
    }
896
897
0
    if (taf->lcpu[numa_node] >= UtilCpuGetNumProcessorsOnline()) {
898
0
        return -1;
899
0
    }
900
901
0
    uint16_t cpu = taf->lcpu[numa_node];
902
0
    while (cpu < UtilCpuGetNumProcessorsOnline() &&
903
0
            (!CPU_ISSET(cpu, &taf->cpu_set) || !CPUIsFromNuma(cpu, (uint16_t)numa_node))) {
904
0
        cpu++;
905
0
    }
906
907
0
    taf->lcpu[numa_node] =
908
0
            (CPU_ISSET(cpu, &taf->cpu_set) && CPUIsFromNuma(cpu, (uint16_t)numa_node))
909
0
                    ? cpu + 1
910
0
                    : UtilCpuGetNumProcessorsOnline();
911
0
    return (CPU_ISSET(cpu, &taf->cpu_set) && CPUIsFromNuma(cpu, (uint16_t)numa_node)) ? (int16_t)cpu
912
0
                                                                                      : -1;
913
0
}
914
915
static int16_t CPUSelectFromNuma(int iface_numa, ThreadsAffinityType *taf)
916
0
{
917
0
    if (iface_numa != -1) {
918
0
        return FindCPUInNumaNode(iface_numa, taf);
919
0
    }
920
0
    return -1;
921
0
}
922
923
static int16_t CPUSelectAlternative(int iface_numa, ThreadsAffinityType *taf)
924
0
{
925
0
    for (int nid = 0; nid < MAX_NUMA_NODES; nid++) {
926
0
        if (iface_numa == nid) {
927
0
            continue;
928
0
        }
929
930
0
        int16_t cpu = FindCPUInNumaNode(nid, taf);
931
0
        if (cpu != -1) {
932
0
            SCLogPerf("CPU %d from NUMA %d assigned to a network interface located on NUMA %d", cpu,
933
0
                    nid, iface_numa);
934
0
            return cpu;
935
0
        }
936
0
    }
937
0
    return -1;
938
0
}
939
940
/**
941
 * \brief Select the next available CPU for the given affinity type.
942
 * taf->cpu_set is a bit array where each bit represents a CPU core.
943
 * The function iterates over the bit array and returns the first available CPU.
944
 * If last used CPU core index is higher than the indexes of available cores,
945
 * we reach the end of the array, and we reset the CPU selection.
946
 * On the second reset attempt, the function bails out with a default value.
947
 * The second attempt should only happen with an empty CPU set.
948
 */
949
static uint16_t CPUSelectDefault(ThreadsAffinityType *taf)
950
0
{
951
0
    uint16_t cpu = taf->lcpu[0];
952
0
    int attempts = 0;
953
0
    uint16_t num_procs = UtilCpuGetNumProcessorsOnline();
954
0
    if (num_procs > 0) {
955
0
        while (!CPU_ISSET(cpu, &taf->cpu_set) && attempts < 2) {
956
0
            cpu = (cpu + 1) % num_procs;
957
0
            if (cpu == 0) {
958
0
                attempts++;
959
0
            }
960
0
        }
961
0
    }
962
963
0
    taf->lcpu[0] = cpu + 1;
964
0
    return cpu;
965
0
}
966
967
static uint16_t CPUSelectFromNumaOrDefault(int iface_numa, ThreadsAffinityType *taf)
968
0
{
969
0
    uint16_t attempts = 0;
970
0
    int16_t cpu = -1;
971
0
    while (attempts < 2) {
972
0
        cpu = CPUSelectFromNuma(iface_numa, taf);
973
0
        if (cpu == -1) {
974
0
            cpu = CPUSelectAlternative(iface_numa, taf);
975
0
            if (cpu == -1) {
976
                // All CPUs from all NUMAs are used at this point
977
0
                ResetCPUs(taf);
978
0
                attempts++;
979
0
            }
980
0
        }
981
982
0
        if (cpu >= 0) {
983
0
            return (uint16_t)cpu;
984
0
        }
985
0
    }
986
0
    return CPUSelectDefault(taf);
987
0
}
988
989
static uint16_t GetNextAvailableCPU(int iface_numa, ThreadsAffinityType *taf)
990
0
{
991
0
    if (iface_numa < 0) {
992
0
        return CPUSelectDefault(taf);
993
0
    }
994
995
0
    return CPUSelectFromNumaOrDefault(iface_numa, taf);
996
0
}
997
998
static bool AutopinEnabled(void)
999
0
{
1000
0
    int autopin = 0;
1001
0
    if (SCConfGetBool("threading.autopin", &autopin) != 1) {
1002
0
        return false;
1003
0
    }
1004
0
    return (bool)autopin;
1005
0
}
1006
1007
#endif /* OS_WIN32 and __OpenBSD__ */
1008
1009
uint16_t AffinityGetNextCPU(ThreadVars *tv, ThreadsAffinityType *taf)
1010
0
{
1011
0
    uint16_t ncpu = 0;
1012
0
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
1013
0
    int iface_numa = -1;
1014
0
    if (AutopinEnabled()) {
1015
#ifdef HAVE_HWLOC
1016
        if (TopologyShouldAutopin(tv, taf)) {
1017
            if (TopologyInitialize() < 0) {
1018
                SCLogError("Failed to initialize topology for CPU affinity");
1019
                return ncpu;
1020
            }
1021
            iface_numa = InterfaceGetNumaNode(tv);
1022
        }
1023
#else
1024
0
        static bool printed = false;
1025
0
        if (!printed) {
1026
0
            printed = true;
1027
0
            SCLogWarning(
1028
0
                    "threading.autopin option is enabled but hwloc support is not compiled in. "
1029
0
                    "Make sure to pass --enable-hwloc to configure when building Suricata.");
1030
0
        }
1031
0
#endif /* HAVE_HWLOC */
1032
0
    }
1033
1034
0
    SCMutexLock(&taf->taf_mutex);
1035
0
    ncpu = GetNextAvailableCPU(iface_numa, taf);
1036
0
    SCLogDebug("Setting affinity on CPU %d", ncpu);
1037
0
    SCMutexUnlock(&taf->taf_mutex);
1038
0
#endif /* OS_WIN32 and __OpenBSD__ */
1039
0
    return ncpu;
1040
0
}
1041
1042
/**
1043
 * \brief Return the total number of CPUs in a given affinity
1044
 * \retval the number of affined CPUs
1045
 */
1046
uint16_t UtilAffinityGetAffinedCPUNum(ThreadsAffinityType *taf)
1047
0
{
1048
0
    uint16_t ncpu = 0;
1049
0
#if !defined __CYGWIN__ && !defined OS_WIN32 && !defined __OpenBSD__ && !defined sun
1050
0
    SCMutexLock(&taf->taf_mutex);
1051
0
    for (int i = UtilCpuGetNumProcessorsOnline(); i >= 0; i--)
1052
0
        if (CPU_ISSET(i, &taf->cpu_set)) {
1053
0
            ncpu++;
1054
0
        }
1055
0
    SCMutexUnlock(&taf->taf_mutex);
1056
0
#endif
1057
0
    return ncpu;
1058
0
}
1059
1060
#ifdef HAVE_DPDK
1061
/**
1062
 * Find if CPU sets overlap
1063
 * \return 1 if CPUs overlap, 0 otherwise
1064
 */
1065
uint16_t UtilAffinityCpusOverlap(ThreadsAffinityType *taf1, ThreadsAffinityType *taf2)
1066
{
1067
    ThreadsAffinityType tmptaf;
1068
    CPU_ZERO(&tmptaf);
1069
    SCMutexInit(&tmptaf.taf_mutex, NULL);
1070
1071
    cpu_set_t tmpcset;
1072
1073
    SCMutexLock(&taf1->taf_mutex);
1074
    SCMutexLock(&taf2->taf_mutex);
1075
    CPU_AND(&tmpcset, &taf1->cpu_set, &taf2->cpu_set);
1076
    SCMutexUnlock(&taf2->taf_mutex);
1077
    SCMutexUnlock(&taf1->taf_mutex);
1078
1079
    for (int i = UtilCpuGetNumProcessorsOnline(); i >= 0; i--)
1080
        if (CPU_ISSET(i, &tmpcset)) {
1081
            return 1;
1082
        }
1083
    return 0;
1084
}
1085
1086
/**
1087
 * Function makes sure that CPUs of different types don't overlap by excluding
1088
 * one affinity type from the other
1089
 * \param mod_taf - CPU set to be modified
1090
 * \param static_taf - static CPU set to be used only for evaluation
1091
 */
1092
void UtilAffinityCpusExclude(ThreadsAffinityType *mod_taf, ThreadsAffinityType *static_taf)
1093
{
1094
    SCMutexLock(&mod_taf->taf_mutex);
1095
    SCMutexLock(&static_taf->taf_mutex);
1096
    int max_cpus = UtilCpuGetNumProcessorsOnline();
1097
    for (int cpu = 0; cpu < max_cpus; cpu++) {
1098
        if (CPU_ISSET(cpu, &mod_taf->cpu_set) && CPU_ISSET(cpu, &static_taf->cpu_set)) {
1099
            CPU_CLR(cpu, &mod_taf->cpu_set);
1100
        }
1101
    }
1102
    SCMutexUnlock(&static_taf->taf_mutex);
1103
    SCMutexUnlock(&mod_taf->taf_mutex);
1104
}
1105
#endif /* HAVE_DPDK */
1106
1107
#ifdef UNITTESTS
1108
// avoiding Darwin/MacOS as it does not support bitwise CPU affinity
1109
#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
1110
1111
/**
1112
 * \brief Helper function to reset affinity state for unit tests
1113
 * This properly clears CPU sets without destroying initialized mutexes
1114
 */
1115
static void ResetAffinityForTest(void)
1116
{
1117
    thread_affinity_init_done = 0;
1118
    for (int i = 0; i < MAX_CPU_SET; i++) {
1119
        ThreadsAffinityType *taf = &thread_affinity[i];
1120
        CPU_ZERO(&taf->cpu_set);
1121
        CPU_ZERO(&taf->lowprio_cpu);
1122
        CPU_ZERO(&taf->medprio_cpu);
1123
        CPU_ZERO(&taf->hiprio_cpu);
1124
        taf->nb_threads = 0;
1125
        taf->prio = PRIO_LOW;
1126
        taf->mode_flag = BALANCED_AFFINITY;
1127
1128
        for (int j = 0; j < MAX_NUMA_NODES; j++) {
1129
            taf->lcpu[j] = 0;
1130
        }
1131
1132
        if (taf->children) {
1133
            for (uint32_t j = 0; j < taf->nb_children; j++) {
1134
                if (taf->children[j]) {
1135
                    SCFree((void *)taf->children[j]->name);
1136
                    SCFree(taf->children[j]);
1137
                }
1138
            }
1139
            SCFree(taf->children);
1140
            taf->children = NULL;
1141
        }
1142
        taf->nb_children = 0;
1143
        taf->nb_children_capacity = 0;
1144
1145
        if (i == MANAGEMENT_CPU_SET) {
1146
            taf->name = "management-cpu-set";
1147
        } else if (i == WORKER_CPU_SET) {
1148
            taf->name = "worker-cpu-set";
1149
        } else if (i == VERDICT_CPU_SET) {
1150
            taf->name = "verdict-cpu-set";
1151
        } else if (i == RECEIVE_CPU_SET) {
1152
            taf->name = "receive-cpu-set";
1153
        } else {
1154
            taf->name = NULL;
1155
        }
1156
1157
        // Don't touch the mutex - it should remain initialized
1158
    }
1159
}
1160
1161
/**
1162
 * \brief Test basic CPU affinity parsing in new format
1163
 */
1164
static int ThreadingAffinityTest01(void)
1165
{
1166
    SCConfCreateContextBackup();
1167
    SCConfInit();
1168
    ResetAffinityForTest();
1169
    const char *config = "%YAML 1.1\n"
1170
                         "---\n"
1171
                         "threading:\n"
1172
                         "  cpu-affinity:\n"
1173
                         "    management-cpu-set:\n"
1174
                         "      cpu: [ 0 ]\n"
1175
                         "    worker-cpu-set:\n"
1176
                         "      cpu: [ 1, 2, 3 ]\n";
1177
1178
    SCConfYamlLoadString(config, strlen(config));
1179
1180
    AffinitySetupLoadFromConfig();
1181
    FAIL_IF_NOT(AffinityConfigIsLegacy() == false);
1182
1183
    ThreadsAffinityType *mgmt_taf = &thread_affinity[MANAGEMENT_CPU_SET];
1184
    FAIL_IF_NOT(CPU_ISSET(0, &mgmt_taf->cpu_set));
1185
    FAIL_IF_NOT(CPU_COUNT(&mgmt_taf->cpu_set) == 1);
1186
1187
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1188
    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->cpu_set));
1189
    FAIL_IF_NOT(CPU_ISSET(2, &worker_taf->cpu_set));
1190
    FAIL_IF_NOT(CPU_ISSET(3, &worker_taf->cpu_set));
1191
    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set));
1192
1193
    SCConfRestoreContextBackup();
1194
    PASS;
1195
}
1196
1197
/**
1198
 * \brief Test deprecated CPU affinity format parsing
1199
 */
1200
static int ThreadingAffinityTest02(void)
1201
{
1202
    SCConfCreateContextBackup();
1203
    SCConfInit();
1204
    ResetAffinityForTest();
1205
1206
    const char *config = "%YAML 1.1\n"
1207
                         "---\n"
1208
                         "threading:\n"
1209
                         "  cpu-affinity:\n"
1210
                         "    - worker-cpu-set:\n"
1211
                         "        cpu: [ 1, 2 ]\n";
1212
1213
    SCConfYamlLoadString(config, strlen(config));
1214
    AffinitySetupLoadFromConfig();
1215
    FAIL_IF_NOT(AffinityConfigIsLegacy() == true);
1216
1217
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1218
    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->cpu_set));
1219
    FAIL_IF_NOT(CPU_ISSET(2, &worker_taf->cpu_set));
1220
    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == 2);
1221
1222
    SCConfRestoreContextBackup();
1223
    PASS;
1224
}
1225
1226
/**
1227
 * \brief Test CPU range parsing ("0-3")
1228
 */
1229
static int ThreadingAffinityTest03(void)
1230
{
1231
    SCConfCreateContextBackup();
1232
    SCConfInit();
1233
    ResetAffinityForTest();
1234
1235
    const char *config = "%YAML 1.1\n"
1236
                         "---\n"
1237
                         "threading:\n"
1238
                         "  cpu-affinity:\n"
1239
                         "    worker-cpu-set:\n"
1240
                         "      cpu: [ \"0-3\" ]\n";
1241
1242
    SCConfYamlLoadString(config, strlen(config));
1243
    AffinitySetupLoadFromConfig();
1244
1245
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1246
    FAIL_IF_NOT(CPU_ISSET(0, &worker_taf->cpu_set));
1247
    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->cpu_set));
1248
    FAIL_IF_NOT(CPU_ISSET(2, &worker_taf->cpu_set));
1249
    FAIL_IF_NOT(CPU_ISSET(3, &worker_taf->cpu_set));
1250
    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == 4);
1251
1252
    SCConfRestoreContextBackup();
1253
    PASS;
1254
}
1255
1256
/**
1257
 * \brief Test mixed CPU specification parsing (individual CPUs in list)
1258
 */
1259
static int ThreadingAffinityTest04(void)
1260
{
1261
    SCConfCreateContextBackup();
1262
    SCConfInit();
1263
    ResetAffinityForTest();
1264
1265
    const char *config = "%YAML 1.1\n"
1266
                         "---\n"
1267
                         "threading:\n"
1268
                         "  cpu-affinity:\n"
1269
                         "    worker-cpu-set:\n"
1270
                         "      cpu: [ 1, 3, 5 ]\n";
1271
1272
    SCConfYamlLoadString(config, strlen(config));
1273
    AffinitySetupLoadFromConfig();
1274
1275
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1276
    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->cpu_set));
1277
    FAIL_IF_NOT(CPU_ISSET(3, &worker_taf->cpu_set));
1278
    FAIL_IF_NOT(CPU_ISSET(5, &worker_taf->cpu_set));
1279
    FAIL_IF(CPU_ISSET(0, &worker_taf->cpu_set));
1280
    FAIL_IF(CPU_ISSET(2, &worker_taf->cpu_set));
1281
    FAIL_IF(CPU_ISSET(4, &worker_taf->cpu_set));
1282
    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == 3);
1283
1284
    SCConfRestoreContextBackup();
1285
    PASS;
1286
}
1287
1288
/**
1289
 * \brief Test "all" CPU specification
1290
 */
1291
static int ThreadingAffinityTest05(void)
1292
{
1293
    SCConfCreateContextBackup();
1294
    SCConfInit();
1295
    ResetAffinityForTest();
1296
1297
    const char *config = "%YAML 1.1\n"
1298
                         "---\n"
1299
                         "threading:\n"
1300
                         "  cpu-affinity:\n"
1301
                         "    worker-cpu-set:\n"
1302
                         "      cpu: [ \"all\" ]\n";
1303
1304
    SCConfYamlLoadString(config, strlen(config));
1305
    AffinitySetupLoadFromConfig();
1306
1307
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1308
    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == UtilCpuGetNumProcessorsOnline());
1309
1310
    SCConfRestoreContextBackup();
1311
    PASS;
1312
}
1313
1314
/**
1315
 * \brief Test priority settings parsing
1316
 */
1317
static int ThreadingAffinityTest06(void)
1318
{
1319
    SCConfCreateContextBackup();
1320
    SCConfInit();
1321
    ResetAffinityForTest();
1322
1323
    const char *config = "%YAML 1.1\n"
1324
                         "---\n"
1325
                         "threading:\n"
1326
                         "  cpu-affinity:\n"
1327
                         "    worker-cpu-set:\n"
1328
                         "      cpu: [ 0, 1, 2, 3 ]\n"
1329
                         "      prio:\n"
1330
                         "        low: [ 0 ]\n"
1331
                         "        medium: [ \"1-2\" ]\n"
1332
                         "        high: [ 3 ]\n"
1333
                         "        default: \"medium\"\n";
1334
1335
    SCConfYamlLoadString(config, strlen(config));
1336
    AffinitySetupLoadFromConfig();
1337
1338
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1339
    FAIL_IF_NOT(CPU_ISSET(0, &worker_taf->lowprio_cpu));
1340
    FAIL_IF_NOT(CPU_ISSET(1, &worker_taf->medprio_cpu));
1341
    FAIL_IF_NOT(CPU_ISSET(2, &worker_taf->medprio_cpu));
1342
    FAIL_IF_NOT(CPU_ISSET(3, &worker_taf->hiprio_cpu));
1343
    FAIL_IF_NOT(worker_taf->prio == PRIO_MEDIUM);
1344
1345
    SCConfRestoreContextBackup();
1346
    PASS;
1347
}
1348
1349
/**
1350
 * \brief Test mode settings (exclusive/balanced)
1351
 */
1352
static int ThreadingAffinityTest07(void)
1353
{
1354
    SCConfCreateContextBackup();
1355
    SCConfInit();
1356
    ResetAffinityForTest();
1357
1358
    const char *config = "%YAML 1.1\n"
1359
                         "---\n"
1360
                         "threading:\n"
1361
                         "  cpu-affinity:\n"
1362
                         "    worker-cpu-set:\n"
1363
                         "      cpu: [ 0, 1 ]\n"
1364
                         "      mode: \"exclusive\"\n";
1365
1366
    SCConfYamlLoadString(config, strlen(config));
1367
    AffinitySetupLoadFromConfig();
1368
1369
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1370
    FAIL_IF_NOT(worker_taf->mode_flag == EXCLUSIVE_AFFINITY);
1371
1372
    SCConfRestoreContextBackup();
1373
    PASS;
1374
}
1375
1376
/**
1377
 * \brief Test threads count parsing
1378
 */
1379
static int ThreadingAffinityTest08(void)
1380
{
1381
    SCConfCreateContextBackup();
1382
    SCConfInit();
1383
    ResetAffinityForTest();
1384
1385
    const char *config = "%YAML 1.1\n"
1386
                         "---\n"
1387
                         "threading:\n"
1388
                         "  cpu-affinity:\n"
1389
                         "    worker-cpu-set:\n"
1390
                         "      cpu: [ 0, 1, 2 ]\n"
1391
                         "      threads: 4\n";
1392
1393
    SCConfYamlLoadString(config, strlen(config));
1394
    AffinitySetupLoadFromConfig();
1395
1396
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1397
    FAIL_IF_NOT(worker_taf->nb_threads == 4);
1398
1399
    SCConfRestoreContextBackup();
1400
    PASS;
1401
}
1402
1403
/**
1404
 * \brief Test interface-specific CPU set parsing
1405
 */
1406
static int ThreadingAffinityTest09(void)
1407
{
1408
    SCConfCreateContextBackup();
1409
    SCConfInit();
1410
    ResetAffinityForTest();
1411
1412
    const char *config = "%YAML 1.1\n"
1413
                         "---\n"
1414
                         "threading:\n"
1415
                         "  cpu-affinity:\n"
1416
                         "    worker-cpu-set:\n"
1417
                         "      cpu: [ 0, 1 ]\n"
1418
                         "      interface-specific-cpu-set:\n"
1419
                         "        - interface: \"eth0\"\n"
1420
                         "          cpu: [ 2, 3 ]\n"
1421
                         "          mode: \"exclusive\"\n";
1422
1423
    SCConfYamlLoadString(config, strlen(config));
1424
    AffinitySetupLoadFromConfig();
1425
1426
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1427
    FAIL_IF_NOT(worker_taf->nb_children == 1);
1428
1429
    ThreadsAffinityType *iface_taf = worker_taf->children[0];
1430
    FAIL_IF_NOT(strcmp(iface_taf->name, "eth0") == 0);
1431
    FAIL_IF_NOT(CPU_ISSET(2, &iface_taf->cpu_set));
1432
    FAIL_IF_NOT(CPU_ISSET(3, &iface_taf->cpu_set));
1433
    FAIL_IF_NOT(CPU_COUNT(&iface_taf->cpu_set) == 2);
1434
    FAIL_IF_NOT(iface_taf->mode_flag == EXCLUSIVE_AFFINITY);
1435
1436
    SCConfRestoreContextBackup();
1437
    PASS;
1438
}
1439
1440
/**
1441
 * \brief Test multiple interface-specific CPU sets
1442
 */
1443
static int ThreadingAffinityTest10(void)
1444
{
1445
    SCConfCreateContextBackup();
1446
    SCConfInit();
1447
    ResetAffinityForTest();
1448
1449
    const char *config = "%YAML 1.1\n"
1450
                         "---\n"
1451
                         "threading:\n"
1452
                         "  cpu-affinity:\n"
1453
                         "    receive-cpu-set:\n"
1454
                         "      cpu: [ 0 ]\n"
1455
                         "      interface-specific-cpu-set:\n"
1456
                         "        - interface: \"eth0\"\n"
1457
                         "          cpu: [ 1, 2 ]\n"
1458
                         "        - interface: \"eth1\"\n"
1459
                         "          cpu: [ 3, 4 ]\n";
1460
1461
    SCConfYamlLoadString(config, strlen(config));
1462
    AffinitySetupLoadFromConfig();
1463
1464
    ThreadsAffinityType *receive_taf = &thread_affinity[RECEIVE_CPU_SET];
1465
    FAIL_IF_NOT(receive_taf->nb_children == 2);
1466
1467
    bool eth0_found = false, eth1_found = false;
1468
1469
    for (uint32_t i = 0; i < receive_taf->nb_children; i++) {
1470
        ThreadsAffinityType *iface_taf = receive_taf->children[i];
1471
        if (strcmp(iface_taf->name, "eth0") == 0) {
1472
            if (CPU_ISSET(1, &iface_taf->cpu_set) && CPU_ISSET(2, &iface_taf->cpu_set) &&
1473
                    CPU_COUNT(&iface_taf->cpu_set) == 2) {
1474
                eth0_found = true;
1475
            }
1476
        } else if (strcmp(iface_taf->name, "eth1") == 0) {
1477
            if (CPU_ISSET(3, &iface_taf->cpu_set) && CPU_ISSET(4, &iface_taf->cpu_set) &&
1478
                    CPU_COUNT(&iface_taf->cpu_set) == 2) {
1479
                eth1_found = true;
1480
            }
1481
        }
1482
    }
1483
1484
    FAIL_IF_NOT(eth0_found && eth1_found);
1485
1486
    SCConfRestoreContextBackup();
1487
    PASS;
1488
}
1489
1490
/**
1491
 * \brief Test interface-specific priority settings
1492
 */
1493
static int ThreadingAffinityTest11(void)
1494
{
1495
    SCConfCreateContextBackup();
1496
    SCConfInit();
1497
    ResetAffinityForTest();
1498
1499
    const char *config = "%YAML 1.1\n"
1500
                         "---\n"
1501
                         "threading:\n"
1502
                         "  cpu-affinity:\n"
1503
                         "    worker-cpu-set:\n"
1504
                         "      cpu: [ 0 ]\n"
1505
                         "      interface-specific-cpu-set:\n"
1506
                         "        - interface: \"eth0\"\n"
1507
                         "          cpu: [ 1, 2, 3 ]\n"
1508
                         "          prio:\n"
1509
                         "            high: [ \"all\" ]\n"
1510
                         "            default: \"high\"\n";
1511
1512
    SCConfYamlLoadString(config, strlen(config));
1513
    AffinitySetupLoadFromConfig();
1514
1515
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1516
    FAIL_IF_NOT(worker_taf->nb_children == 1);
1517
1518
    ThreadsAffinityType *iface_taf = worker_taf->children[0];
1519
    FAIL_IF_NOT(strcmp(iface_taf->name, "eth0") == 0);
1520
    FAIL_IF_NOT(CPU_ISSET(1, &iface_taf->hiprio_cpu));
1521
    FAIL_IF_NOT(CPU_ISSET(2, &iface_taf->hiprio_cpu));
1522
    FAIL_IF_NOT(CPU_ISSET(3, &iface_taf->hiprio_cpu));
1523
    FAIL_IF_NOT(iface_taf->prio == PRIO_HIGH);
1524
1525
    SCConfRestoreContextBackup();
1526
    PASS;
1527
}
1528
1529
/**
1530
 * \brief Test complete configuration with all CPU sets
1531
 */
1532
static int ThreadingAffinityTest12(void)
1533
{
1534
    SCConfCreateContextBackup();
1535
    SCConfInit();
1536
    ResetAffinityForTest();
1537
1538
    const char *config = "%YAML 1.1\n"
1539
                         "---\n"
1540
                         "threading:\n"
1541
                         "  cpu-affinity:\n"
1542
                         "    management-cpu-set:\n"
1543
                         "      cpu: [ 0 ]\n"
1544
                         "    receive-cpu-set:\n"
1545
                         "      cpu: [ 1 ]\n"
1546
                         "    worker-cpu-set:\n"
1547
                         "      cpu: [ 2, 3 ]\n"
1548
                         "      interface-specific-cpu-set:\n"
1549
                         "        - interface: \"eth0\"\n"
1550
                         "          cpu: [ \"5-7\" ]\n"
1551
                         "          prio:\n"
1552
                         "            high: [ \"all\" ]\n"
1553
                         "            default: \"high\"\n"
1554
                         "    verdict-cpu-set:\n"
1555
                         "      cpu: [ 4 ]\n";
1556
1557
    SCConfYamlLoadString(config, strlen(config));
1558
    AffinitySetupLoadFromConfig();
1559
1560
    FAIL_IF_NOT(CPU_ISSET(0, &thread_affinity[MANAGEMENT_CPU_SET].cpu_set));
1561
    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[MANAGEMENT_CPU_SET].cpu_set) == 1);
1562
    FAIL_IF_NOT(CPU_ISSET(1, &thread_affinity[RECEIVE_CPU_SET].cpu_set));
1563
    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[RECEIVE_CPU_SET].cpu_set) == 1);
1564
    FAIL_IF_NOT(CPU_ISSET(4, &thread_affinity[VERDICT_CPU_SET].cpu_set));
1565
    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[VERDICT_CPU_SET].cpu_set) == 1);
1566
    FAIL_IF_NOT(CPU_ISSET(2, &thread_affinity[WORKER_CPU_SET].cpu_set));
1567
    FAIL_IF_NOT(CPU_ISSET(3, &thread_affinity[WORKER_CPU_SET].cpu_set));
1568
1569
    FAIL_IF_NOT(thread_affinity[WORKER_CPU_SET].nb_children == 1);
1570
    ThreadsAffinityType *iface_taf = thread_affinity[WORKER_CPU_SET].children[0];
1571
    FAIL_IF_NOT(strcmp(iface_taf->name, "eth0") == 0);
1572
    FAIL_IF_NOT(CPU_ISSET(1, &iface_taf->hiprio_cpu));
1573
    FAIL_IF_NOT(CPU_ISSET(2, &iface_taf->hiprio_cpu));
1574
    FAIL_IF_NOT(CPU_ISSET(3, &iface_taf->hiprio_cpu));
1575
    FAIL_IF_NOT(iface_taf->prio == PRIO_HIGH);
1576
    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[WORKER_CPU_SET].cpu_set) == 2);
1577
1578
    SCConfRestoreContextBackup();
1579
    PASS;
1580
}
1581
1582
/**
1583
 * \brief Test error handling for malformed CPU specification
1584
 */
1585
static int ThreadingAffinityTest13(void)
1586
{
1587
    SCConfCreateContextBackup();
1588
    SCConfInit();
1589
    ResetAffinityForTest();
1590
1591
    const char *config = "%YAML 1.1\n"
1592
                         "---\n"
1593
                         "threading:\n"
1594
                         "  cpu-affinity:\n"
1595
                         "    worker-cpu-set:\n"
1596
                         "      cpu: [ \"invalid-cpu\" ]\n";
1597
1598
    SCConfYamlLoadString(config, strlen(config));
1599
    AffinitySetupLoadFromConfig();
1600
1601
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1602
    FAIL_IF_NOT(CPU_COUNT(&worker_taf->cpu_set) == 0);
1603
1604
    SCConfRestoreContextBackup();
1605
    PASS;
1606
}
1607
1608
/**
1609
 * \brief Test empty configuration handling
1610
 */
1611
static int ThreadingAffinityTest14(void)
1612
{
1613
    SCConfCreateContextBackup();
1614
    SCConfInit();
1615
    ResetAffinityForTest();
1616
1617
    const char *config = "%YAML 1.1\n"
1618
                         "---\n"
1619
                         "threading:\n"
1620
                         "  cpu-affinity:\n";
1621
1622
    SCConfYamlLoadString(config, strlen(config));
1623
    AffinitySetupLoadFromConfig();
1624
1625
    FAIL_IF_NOT(
1626
            CPU_COUNT(&thread_affinity[WORKER_CPU_SET].cpu_set) == UtilCpuGetNumProcessorsOnline());
1627
1628
    SCConfRestoreContextBackup();
1629
    PASS;
1630
}
1631
1632
/**
1633
 * \brief Test CPU range parsing with invalid order (high-low)
1634
 * CPU ranges specified in reverse order should be handled
1635
 */
1636
static int ThreadingAffinityTest15(void)
1637
{
1638
    SCConfCreateContextBackup();
1639
    SCConfInit();
1640
    ResetAffinityForTest();
1641
1642
    const char *config = "%YAML 1.1\n"
1643
                         "---\n"
1644
                         "threading:\n"
1645
                         "  cpu-affinity:\n"
1646
                         "    - management-cpu-set:\n"
1647
                         "        cpu: [ \"3-1\" ]\n"; // Invalid reverse range
1648
1649
    SCConfYamlLoadString(config, strlen(config));
1650
    AffinitySetupLoadFromConfig();
1651
1652
    FAIL_IF_NOT(CPU_COUNT(&thread_affinity[MANAGEMENT_CPU_SET].cpu_set) == 0);
1653
1654
    SCConfRestoreContextBackup();
1655
    PASS;
1656
}
1657
1658
/**
1659
 * \brief Test invalid priority values in SetupDefaultPriority
1660
 * Invalid priority strings should return errors but pass
1661
 */
1662
static int ThreadingAffinityTest16(void)
1663
{
1664
    SCConfCreateContextBackup();
1665
    SCConfInit();
1666
    ResetAffinityForTest();
1667
1668
    const char *config = "%YAML 1.1\n"
1669
                         "---\n"
1670
                         "threading:\n"
1671
                         "  cpu-affinity:\n"
1672
                         "    - management-cpu-set:\n"
1673
                         "        prio:\n"
1674
                         "          default: invalid_priority\n";
1675
1676
    SCConfYamlLoadString(config, strlen(config));
1677
    AffinitySetupLoadFromConfig();
1678
1679
    SCConfRestoreContextBackup();
1680
    PASS;
1681
}
1682
1683
/**
1684
 * \brief Test invalid CPU affinity mode values
1685
 * Invalid mode strings should return errors but pass
1686
 */
1687
static int ThreadingAffinityTest17(void)
1688
{
1689
    SCConfCreateContextBackup();
1690
    SCConfInit();
1691
    ResetAffinityForTest();
1692
1693
    const char *config = "%YAML 1.1\n"
1694
                         "---\n"
1695
                         "threading:\n"
1696
                         "  cpu-affinity:\n"
1697
                         "    - management-cpu-set:\n"
1698
                         "        mode: invalid_mode\n";
1699
1700
    SCConfYamlLoadString(config, strlen(config));
1701
    AffinitySetupLoadFromConfig();
1702
1703
    SCConfRestoreContextBackup();
1704
    PASS;
1705
}
1706
1707
/**
1708
 * \brief Test invalid thread count values
1709
 * Invalid thread counts
1710
 */
1711
static int ThreadingAffinityTest18(void)
1712
{
1713
    SCConfCreateContextBackup();
1714
    SCConfInit();
1715
    ResetAffinityForTest();
1716
1717
    const char *config = "%YAML 1.1\n"
1718
                         "---\n"
1719
                         "threading:\n"
1720
                         "  cpu-affinity:\n"
1721
                         "    - management-cpu-set:\n"
1722
                         "        threads: 0\n";
1723
1724
    SCConfYamlLoadString(config, strlen(config));
1725
    AffinitySetupLoadFromConfig();
1726
1727
    SCConfRestoreContextBackup();
1728
    PASS;
1729
}
1730
1731
/**
1732
 * \brief Test CPU specification with negative numbers
1733
 * Negative CPU numbers should be rejected
1734
 */
1735
static int ThreadingAffinityTest19(void)
1736
{
1737
    SCConfCreateContextBackup();
1738
    SCConfInit();
1739
    ResetAffinityForTest();
1740
1741
    const char *config = "%YAML 1.1\n"
1742
                         "---\n"
1743
                         "threading:\n"
1744
                         "  cpu-affinity:\n"
1745
                         "    - management-cpu-set:\n"
1746
                         "        cpu: [ -1 ]\n";
1747
1748
    SCConfYamlLoadString(config, strlen(config));
1749
    AffinitySetupLoadFromConfig();
1750
1751
    SCConfRestoreContextBackup();
1752
    PASS;
1753
}
1754
1755
/**
1756
 * \brief Test invalid thread count with non-numeric values
1757
 * Non-numeric thread counts should be handled
1758
 */
1759
static int ThreadingAffinityTest20(void)
1760
{
1761
    SCConfCreateContextBackup();
1762
    SCConfInit();
1763
    ResetAffinityForTest();
1764
1765
    const char *config = "%YAML 1.1\n"
1766
                         "---\n"
1767
                         "threading:\n"
1768
                         "  cpu-affinity:\n"
1769
                         "    - management-cpu-set:\n"
1770
                         "        threads: invalid_number\n";
1771
1772
    SCConfYamlLoadString(config, strlen(config));
1773
    AffinitySetupLoadFromConfig();
1774
1775
    SCConfRestoreContextBackup();
1776
    PASS;
1777
}
1778
1779
/**
1780
 * \brief Test extremely large CPU ranges
1781
 * Very large CPU range specifications should be handled
1782
 */
1783
static int ThreadingAffinityTest21(void)
1784
{
1785
    SCConfCreateContextBackup();
1786
    SCConfInit();
1787
    ResetAffinityForTest();
1788
1789
    const char *config = "%YAML 1.1\n"
1790
                         "---\n"
1791
                         "threading:\n"
1792
                         "  cpu-affinity:\n"
1793
                         "    - management-cpu-set:\n"
1794
                         "        cpu: [ 0-99999 ]\n";
1795
1796
    SCConfYamlLoadString(config, strlen(config));
1797
    AffinitySetupLoadFromConfig();
1798
1799
    SCConfRestoreContextBackup();
1800
    PASS;
1801
}
1802
1803
/**
1804
 * \brief Test deeply nested interface configurations
1805
 * Prevent infinite loops in configuration parsing
1806
 */
1807
static int ThreadingAffinityTest22(void)
1808
{
1809
    SCConfCreateContextBackup();
1810
    SCConfInit();
1811
    ResetAffinityForTest();
1812
1813
    const char *config = "%YAML 1.1\n"
1814
                         "---\n"
1815
                         "threading:\n"
1816
                         "  cpu-affinity:\n"
1817
                         "    worker-cpu-set:\n"
1818
                         "      interface-specific-cpu-set:\n"
1819
                         "        - interface: eth0\n"
1820
                         "          cpu: [ 1 ]\n"
1821
                         "          interface-specific-cpu-set:\n" // Nested interface-specific
1822
                         "            - interface: eth1\n"
1823
                         "              cpu: [ 2 ]\n";
1824
1825
    SCConfYamlLoadString(config, strlen(config));
1826
    AffinitySetupLoadFromConfig();
1827
1828
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1829
    FAIL_IF_NOT(worker_taf->nb_children == 1);
1830
    ThreadsAffinityType *iface_taf = worker_taf->children[0];
1831
    FAIL_IF_NOT(strcmp(iface_taf->name, "eth0") == 0);
1832
    FAIL_IF_NOT(CPU_ISSET(1, &iface_taf->cpu_set));
1833
    FAIL_IF_NOT(iface_taf->nb_children == 0);
1834
1835
    SCConfRestoreContextBackup();
1836
    PASS;
1837
}
1838
1839
/**
1840
 * \brief Test GetAffinityTypeForNameAndIface with NULL and empty string parameters
1841
 * Comprehensive NULL parameter testing
1842
 */
1843
static int ThreadingAffinityTest23(void)
1844
{
1845
    SCConfCreateContextBackup();
1846
    SCConfInit();
1847
    ResetAffinityForTest();
1848
1849
    const char *config = "%YAML 1.1\n"
1850
                         "---\n"
1851
                         "threading:\n"
1852
                         "  cpu-affinity:\n"
1853
                         "    worker-cpu-set:\n"
1854
                         "      cpu: [ 1, 2, 3 ]\n";
1855
1856
    SCConfYamlLoadString(config, strlen(config));
1857
    AffinitySetupLoadFromConfig();
1858
1859
    ThreadsAffinityType *result = GetAffinityTypeForNameAndIface(NULL, "eth0");
1860
    FAIL_IF_NOT(result == NULL);
1861
1862
    result = GetAffinityTypeForNameAndIface("", "eth0");
1863
    FAIL_IF_NOT(result == NULL);
1864
1865
    result = GetAffinityTypeForNameAndIface("worker-cpu-set", NULL);
1866
    FAIL_IF(result == NULL);
1867
    FAIL_IF_NOT(strcmp(result->name, "worker-cpu-set") == 0);
1868
1869
    result = GetAffinityTypeForNameAndIface("worker-cpu-set", "");
1870
    FAIL_IF_NOT(result == NULL); // Returns NULL as no child with an empty name exists
1871
1872
    SCConfRestoreContextBackup();
1873
    PASS;
1874
}
1875
1876
/**
1877
 * \brief Test interface-specific configuration with missing interface field
1878
 * Interface-specific configs with malformed structure
1879
 */
1880
static int ThreadingAffinityTest24(void)
1881
{
1882
    SCConfCreateContextBackup();
1883
    SCConfInit();
1884
    ResetAffinityForTest();
1885
1886
    const char *config = "%YAML 1.1\n"
1887
                         "---\n"
1888
                         "threading:\n"
1889
                         "  cpu-affinity:\n"
1890
                         "    - worker-cpu-set:\n"
1891
                         "        interface-specific-cpu-set:\n"
1892
                         "          - cpu: [ 1 ]\n" // Missing interface field
1893
                         "            mode: exclusive\n"
1894
                         "          - interface_name: eth0\n" // Wrong field name
1895
                         "            cpu: [ 2 ]\n";
1896
1897
    SCConfYamlLoadString(config, strlen(config));
1898
    AffinitySetupLoadFromConfig();
1899
1900
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1901
    FAIL_IF_NOT(worker_taf->nb_children == 0);
1902
1903
    SCConfRestoreContextBackup();
1904
    PASS;
1905
}
1906
1907
/**
1908
 * \brief Test AllocAndInitAffinityType with multiple allocations to test realloc paths
1909
 * Test dynamic array reallocation in parent-child relationships
1910
 */
1911
static int ThreadingAffinityTest25(void)
1912
{
1913
    ResetAffinityForTest();
1914
1915
    ThreadsAffinityType *parent = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", NULL);
1916
    FAIL_IF(parent == NULL);
1917
1918
    ThreadsAffinityType *child1 = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface1");
1919
    ThreadsAffinityType *child2 = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface2");
1920
    ThreadsAffinityType *child3 = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface3");
1921
    ThreadsAffinityType *child4 = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface4");
1922
1923
    FAIL_IF_NOT(child1 && child2 && child3 && child4);
1924
    FAIL_IF_NOT(parent->nb_children == 4);
1925
1926
    ThreadsAffinityType *found = FindAffinityByInterface(parent, "iface2");
1927
    FAIL_IF_NOT(found == child2);
1928
1929
    found = GetOrAllocAffinityTypeForIfaceOfName("worker-cpu-set", "iface2");
1930
    FAIL_IF_NOT(found == child2);
1931
1932
    PASS;
1933
}
1934
1935
/**
1936
 * \brief Test AffinityGetYamlPath with very long name
1937
 * Path building with long string lengths to test for buffer overflows
1938
 */
1939
static int ThreadingAffinityTest26(void)
1940
{
1941
    ResetAffinityForTest();
1942
1943
    ThreadsAffinityType test_taf;
1944
    memset(&test_taf, 0, sizeof(test_taf));
1945
1946
    char long_name[1024];
1947
    memset(long_name, 'a', sizeof(long_name) - 1);
1948
    long_name[sizeof(long_name) - 1] = '\0';
1949
    test_taf.name = long_name;
1950
1951
    char *path = AffinityGetYamlPath(&test_taf);
1952
    FAIL_IF_NOT(path == NULL); // overflows the internal buffer and return NULL
1953
1954
    path = AffinityGetYamlPath(NULL); // returns root path
1955
    FAIL_IF(path == NULL || strcmp(path, "threading.cpu-affinity") != 0);
1956
1957
    PASS;
1958
}
1959
1960
/**
1961
 * \brief Test mixed format configurations in same file
1962
 * Combination of new and deprecated formats
1963
 */
1964
static int ThreadingAffinityTest27(void)
1965
{
1966
    SCConfCreateContextBackup();
1967
    SCConfInit();
1968
    ResetAffinityForTest();
1969
1970
    const char *config = "%YAML 1.1\n"
1971
                         "---\n"
1972
                         "threading:\n"
1973
                         "  cpu-affinity:\n"
1974
                         "    management-cpu-set:\n" // New format
1975
                         "      cpu: [ 0 ]\n"
1976
                         "    - worker-cpu-set:\n" // Deprecated format
1977
                         "        cpu: [ 1, 2 ]\n";
1978
1979
    SCConfYamlLoadString(config, strlen(config));
1980
    AffinitySetupLoadFromConfig();
1981
1982
    ThreadsAffinityType *mgmt_taf = &thread_affinity[MANAGEMENT_CPU_SET];
1983
    ThreadsAffinityType *worker_taf = &thread_affinity[WORKER_CPU_SET];
1984
    // The first format should be picked-up and the other should be ignored
1985
    // For ignored formats, CPU_SET is initliazed as all cores
1986
    FAIL_IF(CPU_COUNT(&mgmt_taf->cpu_set) != 1 ||
1987
            CPU_COUNT(&worker_taf->cpu_set) != UtilCpuGetNumProcessorsOnline());
1988
1989
    SCConfRestoreContextBackup();
1990
    PASS;
1991
}
1992
1993
#endif /* defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) */
1994
1995
/**
1996
 * \brief Register all threading affinity unit tests
1997
 */
1998
void ThreadingAffinityRegisterTests(void)
1999
{
2000
#if defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
2001
    UtRegisterTest("ThreadingAffinityTest01", ThreadingAffinityTest01);
2002
    UtRegisterTest("ThreadingAffinityTest02", ThreadingAffinityTest02);
2003
    UtRegisterTest("ThreadingAffinityTest03", ThreadingAffinityTest03);
2004
    UtRegisterTest("ThreadingAffinityTest04", ThreadingAffinityTest04);
2005
    UtRegisterTest("ThreadingAffinityTest05", ThreadingAffinityTest05);
2006
    UtRegisterTest("ThreadingAffinityTest06", ThreadingAffinityTest06);
2007
    UtRegisterTest("ThreadingAffinityTest07", ThreadingAffinityTest07);
2008
    UtRegisterTest("ThreadingAffinityTest08", ThreadingAffinityTest08);
2009
    UtRegisterTest("ThreadingAffinityTest09", ThreadingAffinityTest09);
2010
    UtRegisterTest("ThreadingAffinityTest10", ThreadingAffinityTest10);
2011
    UtRegisterTest("ThreadingAffinityTest11", ThreadingAffinityTest11);
2012
    UtRegisterTest("ThreadingAffinityTest12", ThreadingAffinityTest12);
2013
    UtRegisterTest("ThreadingAffinityTest13", ThreadingAffinityTest13);
2014
    UtRegisterTest("ThreadingAffinityTest14", ThreadingAffinityTest14);
2015
    UtRegisterTest("ThreadingAffinityTest15", ThreadingAffinityTest15);
2016
    UtRegisterTest("ThreadingAffinityTest16", ThreadingAffinityTest16);
2017
    UtRegisterTest("ThreadingAffinityTest17", ThreadingAffinityTest17);
2018
    UtRegisterTest("ThreadingAffinityTest18", ThreadingAffinityTest18);
2019
    UtRegisterTest("ThreadingAffinityTest19", ThreadingAffinityTest19);
2020
    UtRegisterTest("ThreadingAffinityTest20", ThreadingAffinityTest20);
2021
    UtRegisterTest("ThreadingAffinityTest21", ThreadingAffinityTest21);
2022
    UtRegisterTest("ThreadingAffinityTest22", ThreadingAffinityTest22);
2023
    UtRegisterTest("ThreadingAffinityTest23", ThreadingAffinityTest23);
2024
    UtRegisterTest("ThreadingAffinityTest24", ThreadingAffinityTest24);
2025
    UtRegisterTest("ThreadingAffinityTest25", ThreadingAffinityTest25);
2026
    UtRegisterTest("ThreadingAffinityTest26", ThreadingAffinityTest26);
2027
    UtRegisterTest("ThreadingAffinityTest27", ThreadingAffinityTest27);
2028
#endif /* defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) */
2029
}
2030
2031
#endif /* UNITTESTS */