/src/suricata7/src/runmode-af-packet.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* Copyright (C) 2011-2020 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 afppacket |
20 | | * |
21 | | * @{ |
22 | | */ |
23 | | |
24 | | /** |
25 | | * \file |
26 | | * |
27 | | * \author Eric Leblond <eric@regit.org> |
28 | | * |
29 | | * AF_PACKET socket runmode |
30 | | * |
31 | | */ |
32 | | |
33 | | #include "suricata-common.h" |
34 | | #include "suricata.h" |
35 | | #include "tm-threads.h" |
36 | | #include "conf.h" |
37 | | #include "runmodes.h" |
38 | | #include "runmode-af-packet.h" |
39 | | #include "output.h" |
40 | | #include "log-httplog.h" |
41 | | #include "detect-engine-mpm.h" |
42 | | |
43 | | #include "alert-fastlog.h" |
44 | | #include "alert-debuglog.h" |
45 | | |
46 | | #include "flow-bypass.h" |
47 | | |
48 | | #include "util-conf.h" |
49 | | #include "util-debug.h" |
50 | | #include "util-time.h" |
51 | | #include "util-cpu.h" |
52 | | #include "util-affinity.h" |
53 | | #include "util-device.h" |
54 | | #include "util-runmodes.h" |
55 | | #include "util-ioctl.h" |
56 | | #include "util-ebpf.h" |
57 | | #include "util-byte.h" |
58 | | |
59 | | #include "source-af-packet.h" |
60 | | #include "util-bpf.h" |
61 | | |
62 | | extern uint16_t max_pending_packets; |
63 | | |
64 | | const char *RunModeAFPGetDefaultMode(void) |
65 | 0 | { |
66 | 0 | return "workers"; |
67 | 0 | } |
68 | | |
69 | | static int AFPRunModeIsIPS(void) |
70 | 0 | { |
71 | 0 | int nlive = LiveGetDeviceCount(); |
72 | 0 | int ldev; |
73 | 0 | ConfNode *if_root; |
74 | 0 | ConfNode *if_default = NULL; |
75 | 0 | ConfNode *af_packet_node; |
76 | 0 | int has_ips = 0; |
77 | 0 | int has_ids = 0; |
78 | | |
79 | | /* Find initial node */ |
80 | 0 | af_packet_node = ConfGetNode("af-packet"); |
81 | 0 | if (af_packet_node == NULL) { |
82 | 0 | return 0; |
83 | 0 | } |
84 | | |
85 | 0 | if_default = ConfNodeLookupKeyValue(af_packet_node, "interface", "default"); |
86 | |
|
87 | 0 | for (ldev = 0; ldev < nlive; ldev++) { |
88 | 0 | const char *live_dev = LiveGetDeviceName(ldev); |
89 | 0 | if (live_dev == NULL) { |
90 | 0 | SCLogError("Problem with config file"); |
91 | 0 | return 0; |
92 | 0 | } |
93 | 0 | if_root = ConfFindDeviceConfig(af_packet_node, live_dev); |
94 | |
|
95 | 0 | if (if_root == NULL) { |
96 | 0 | if (if_default == NULL) { |
97 | 0 | SCLogError("Problem with config file"); |
98 | 0 | return 0; |
99 | 0 | } |
100 | 0 | if_root = if_default; |
101 | 0 | } |
102 | | |
103 | 0 | const char *copymodestr = NULL; |
104 | 0 | const char *copyifacestr = NULL; |
105 | 0 | if (ConfGetChildValueWithDefault(if_root, if_default, "copy-mode", ©modestr) == 1 && |
106 | 0 | ConfGetChildValue(if_root, "copy-iface", ©ifacestr) == 1) { |
107 | 0 | if (strcmp(copymodestr, "ips") == 0) { |
108 | 0 | has_ips = 1; |
109 | 0 | } else { |
110 | 0 | has_ids = 1; |
111 | 0 | } |
112 | 0 | } else { |
113 | 0 | has_ids = 1; |
114 | 0 | } |
115 | 0 | } |
116 | | |
117 | 0 | if (has_ids && has_ips) { |
118 | 0 | SCLogWarning("AF_PACKET using both IPS and TAP/IDS mode, this will not " |
119 | 0 | "be allowed in Suricata 8 due to undefined behavior. See ticket #5588."); |
120 | 0 | for (ldev = 0; ldev < nlive; ldev++) { |
121 | 0 | const char *live_dev = LiveGetDeviceName(ldev); |
122 | 0 | if (live_dev == NULL) { |
123 | 0 | SCLogError("Problem with config file"); |
124 | 0 | return 0; |
125 | 0 | } |
126 | 0 | if_root = ConfNodeLookupKeyValue(af_packet_node, "interface", live_dev); |
127 | 0 | const char *copymodestr = NULL; |
128 | |
|
129 | 0 | if (if_root == NULL) { |
130 | 0 | if (if_default == NULL) { |
131 | 0 | SCLogError("Problem with config file"); |
132 | 0 | return 0; |
133 | 0 | } |
134 | 0 | if_root = if_default; |
135 | 0 | } |
136 | | |
137 | 0 | if (!((ConfGetChildValueWithDefault(if_root, if_default, "copy-mode", ©modestr) == |
138 | 0 | 1) && |
139 | 0 | (strcmp(copymodestr, "ips") == 0))) { |
140 | 0 | SCLogError("AF_PACKET IPS mode used and interface '%s' is in IDS or TAP mode. " |
141 | 0 | "Sniffing '%s' but expect bad result as stream-inline is activated.", |
142 | 0 | live_dev, live_dev); |
143 | 0 | } |
144 | 0 | } |
145 | 0 | } |
146 | | |
147 | 0 | return has_ips; |
148 | 0 | } |
149 | | |
150 | | static void AFPRunModeEnableIPS(void) |
151 | 0 | { |
152 | 0 | if (AFPRunModeIsIPS()) { |
153 | 0 | SCLogInfo("Setting IPS mode"); |
154 | 0 | EngineModeSetIPS(); |
155 | 0 | } |
156 | 0 | } |
157 | | |
158 | | void RunModeIdsAFPRegister(void) |
159 | 37 | { |
160 | 37 | RunModeRegisterNewRunMode(RUNMODE_AFP_DEV, "single", "Single threaded af-packet mode", |
161 | 37 | RunModeIdsAFPSingle, AFPRunModeEnableIPS); |
162 | 37 | RunModeRegisterNewRunMode(RUNMODE_AFP_DEV, "workers", |
163 | 37 | "Workers af-packet mode, each thread does all" |
164 | 37 | " tasks from acquisition to logging", |
165 | 37 | RunModeIdsAFPWorkers, AFPRunModeEnableIPS); |
166 | 37 | RunModeRegisterNewRunMode(RUNMODE_AFP_DEV, "autofp", |
167 | 37 | "Multi socket AF_PACKET mode. Packets from " |
168 | 37 | "each flow are assigned to a single detect " |
169 | 37 | "thread.", |
170 | 37 | RunModeIdsAFPAutoFp, AFPRunModeEnableIPS); |
171 | 37 | return; |
172 | 37 | } |
173 | | |
174 | | |
175 | | #ifdef HAVE_AF_PACKET |
176 | | |
177 | | static void AFPDerefConfig(void *conf) |
178 | 0 | { |
179 | 0 | AFPIfaceConfig *pfp = (AFPIfaceConfig *)conf; |
180 | | /* Pcap config is used only once but cost of this low. */ |
181 | 0 | if (SC_ATOMIC_SUB(pfp->ref, 1) == 1) { |
182 | 0 | SCFree(pfp); |
183 | 0 | } |
184 | 0 | } |
185 | | |
186 | | /* if cluster id is not set, assign it automagically, uniq value per |
187 | | * interface. */ |
188 | | static int cluster_id_auto = 1; |
189 | | |
190 | | /** |
191 | | * \brief extract information from config file |
192 | | * |
193 | | * The returned structure will be freed by the thread init function. |
194 | | * This is thus necessary to or copy the structure before giving it |
195 | | * to thread or to reparse the file for each thread (and thus have |
196 | | * new structure. |
197 | | * |
198 | | * \return a AFPIfaceConfig corresponding to the interface name |
199 | | */ |
200 | | static void *ParseAFPConfig(const char *iface) |
201 | 0 | { |
202 | 0 | const char *threadsstr = NULL; |
203 | 0 | ConfNode *if_root; |
204 | 0 | ConfNode *if_default = NULL; |
205 | 0 | ConfNode *af_packet_node; |
206 | 0 | const char *tmpclusterid; |
207 | 0 | const char *tmpctype; |
208 | 0 | const char *copymodestr; |
209 | 0 | intmax_t value; |
210 | 0 | int boolval; |
211 | 0 | const char *out_iface = NULL; |
212 | 0 | int cluster_type = PACKET_FANOUT_HASH; |
213 | 0 | const char *ebpf_file = NULL; |
214 | 0 | const char *active_runmode = RunmodeGetActive(); |
215 | |
|
216 | 0 | if (iface == NULL) { |
217 | 0 | return NULL; |
218 | 0 | } |
219 | | |
220 | 0 | AFPIfaceConfig *aconf = SCCalloc(1, sizeof(*aconf)); |
221 | 0 | if (unlikely(aconf == NULL)) { |
222 | 0 | return NULL; |
223 | 0 | } |
224 | | |
225 | 0 | strlcpy(aconf->iface, iface, sizeof(aconf->iface)); |
226 | 0 | aconf->threads = 0; |
227 | 0 | SC_ATOMIC_INIT(aconf->ref); |
228 | 0 | (void) SC_ATOMIC_ADD(aconf->ref, 1); |
229 | 0 | aconf->buffer_size = 0; |
230 | 0 | aconf->cluster_id = 1; |
231 | 0 | aconf->cluster_type = cluster_type | PACKET_FANOUT_FLAG_DEFRAG; |
232 | 0 | aconf->promisc = 1; |
233 | 0 | aconf->checksum_mode = CHECKSUM_VALIDATION_KERNEL; |
234 | 0 | aconf->DerefFunc = AFPDerefConfig; |
235 | 0 | aconf->flags = 0; |
236 | 0 | aconf->bpf_filter = NULL; |
237 | 0 | aconf->ebpf_lb_file = NULL; |
238 | 0 | aconf->ebpf_lb_fd = -1; |
239 | 0 | aconf->ebpf_filter_file = NULL; |
240 | 0 | aconf->ebpf_filter_fd = -1; |
241 | 0 | aconf->out_iface = NULL; |
242 | 0 | aconf->copy_mode = AFP_COPY_MODE_NONE; |
243 | 0 | aconf->block_timeout = 10; |
244 | 0 | aconf->block_size = getpagesize() << AFP_BLOCK_SIZE_DEFAULT_ORDER; |
245 | 0 | aconf->v2_block_size = 0; |
246 | | #ifdef HAVE_PACKET_EBPF |
247 | | aconf->ebpf_t_config.cpus_count = UtilCpuGetNumProcessorsConfigured(); |
248 | | #endif |
249 | | |
250 | | /* Find initial node */ |
251 | 0 | af_packet_node = ConfGetNode("af-packet"); |
252 | 0 | if (af_packet_node == NULL) { |
253 | 0 | SCLogInfo("%s: unable to find af-packet config using default values", iface); |
254 | 0 | goto finalize; |
255 | 0 | } |
256 | | |
257 | 0 | if_root = ConfFindDeviceConfig(af_packet_node, iface); |
258 | 0 | if_default = ConfFindDeviceConfig(af_packet_node, "default"); |
259 | |
|
260 | 0 | if (if_root == NULL && if_default == NULL) { |
261 | 0 | SCLogInfo("%s: unable to find af-packet config for " |
262 | 0 | "interface \"%s\" or \"default\", using default values", |
263 | 0 | iface, iface); |
264 | 0 | goto finalize; |
265 | 0 | } |
266 | | |
267 | | /* If there is no setting for current interface use default one as main iface */ |
268 | 0 | if (if_root == NULL) { |
269 | 0 | if_root = if_default; |
270 | 0 | if_default = NULL; |
271 | 0 | } |
272 | |
|
273 | 0 | if (active_runmode && !strcmp("single", active_runmode)) { |
274 | 0 | aconf->threads = 1; |
275 | 0 | } else if (ConfGetChildValueWithDefault(if_root, if_default, "threads", &threadsstr) != 1) { |
276 | 0 | aconf->threads = 0; |
277 | 0 | } else { |
278 | 0 | if (threadsstr != NULL) { |
279 | 0 | if (strcmp(threadsstr, "auto") == 0) { |
280 | 0 | aconf->threads = 0; |
281 | 0 | } else { |
282 | 0 | if (StringParseInt32(&aconf->threads, 10, 0, (const char *)threadsstr) < 0) { |
283 | 0 | SCLogWarning("%s: invalid number of " |
284 | 0 | "threads, resetting to default", |
285 | 0 | iface); |
286 | 0 | aconf->threads = 0; |
287 | 0 | } |
288 | 0 | } |
289 | 0 | } |
290 | 0 | } |
291 | |
|
292 | 0 | if (ConfGetChildValueWithDefault(if_root, if_default, "copy-iface", &out_iface) == 1) { |
293 | 0 | if (out_iface != NULL) { |
294 | 0 | if (strlen(out_iface) > 0) { |
295 | 0 | aconf->out_iface = out_iface; |
296 | 0 | if (strcmp(iface, out_iface) == 0) { |
297 | 0 | FatalError( |
298 | 0 | "Invalid config: interface (%s) and copy-iface (%s) can't be the same", |
299 | 0 | iface, out_iface); |
300 | 0 | } |
301 | 0 | } |
302 | 0 | } else { |
303 | 0 | SCLogWarning("copy-iface corresponding to %s interface cannot be NULL", iface); |
304 | 0 | } |
305 | 0 | } |
306 | | |
307 | 0 | if (ConfGetChildValueBoolWithDefault(if_root, if_default, "use-mmap", (int *)&boolval) == 1) { |
308 | 0 | if (!boolval) { |
309 | 0 | SCLogWarning( |
310 | 0 | "%s: \"use-mmap\" option is obsolete: mmap is always enabled", aconf->iface); |
311 | 0 | } |
312 | 0 | } |
313 | |
|
314 | 0 | (void)ConfGetChildValueBoolWithDefault(if_root, if_default, "mmap-locked", (int *)&boolval); |
315 | 0 | if (boolval) { |
316 | 0 | SCLogConfig("%s: enabling locked memory for mmap", aconf->iface); |
317 | 0 | aconf->flags |= AFP_MMAP_LOCKED; |
318 | 0 | } |
319 | |
|
320 | 0 | if (ConfGetChildValueBoolWithDefault(if_root, if_default, "tpacket-v3", (int *)&boolval) == 1) { |
321 | 0 | if (boolval) { |
322 | 0 | if (strcasecmp(RunmodeGetActive(), "workers") == 0) { |
323 | 0 | #ifdef HAVE_TPACKET_V3 |
324 | 0 | SCLogConfig("%s: enabling tpacket v3", aconf->iface); |
325 | 0 | aconf->flags |= AFP_TPACKET_V3; |
326 | | #else |
327 | | SCLogWarning("%s: system too old for tpacket v3 switching to v2", iface); |
328 | | aconf->flags &= ~AFP_TPACKET_V3; |
329 | | #endif |
330 | 0 | } else { |
331 | 0 | SCLogWarning("%s: tpacket v3 is only implemented for 'workers' runmode." |
332 | 0 | " Switching to tpacket v2.", |
333 | 0 | iface); |
334 | 0 | aconf->flags &= ~AFP_TPACKET_V3; |
335 | 0 | } |
336 | 0 | } else { |
337 | 0 | aconf->flags &= ~AFP_TPACKET_V3; |
338 | 0 | } |
339 | 0 | } |
340 | |
|
341 | 0 | (void)ConfGetChildValueBoolWithDefault( |
342 | 0 | if_root, if_default, "use-emergency-flush", (int *)&boolval); |
343 | 0 | if (boolval) { |
344 | 0 | SCLogConfig("%s: using emergency ring flush", aconf->iface); |
345 | 0 | aconf->flags |= AFP_EMERGENCY_MODE; |
346 | 0 | } |
347 | |
|
348 | 0 | if (ConfGetChildValueWithDefault(if_root, if_default, "copy-mode", ©modestr) == 1) { |
349 | 0 | if (aconf->out_iface == NULL) { |
350 | 0 | SCLogWarning("%s: copy mode activated but no destination" |
351 | 0 | " iface. Disabling feature", |
352 | 0 | iface); |
353 | 0 | } else if (strlen(copymodestr) <= 0) { |
354 | 0 | aconf->out_iface = NULL; |
355 | 0 | } else if (strcmp(copymodestr, "ips") == 0) { |
356 | 0 | SCLogInfo("%s: AF_PACKET IPS mode activated %s->%s", iface, iface, aconf->out_iface); |
357 | 0 | aconf->copy_mode = AFP_COPY_MODE_IPS; |
358 | 0 | if (aconf->flags & AFP_TPACKET_V3) { |
359 | 0 | SCLogWarning("%s: using tpacket_v3 in IPS mode will result in high latency", iface); |
360 | 0 | } |
361 | 0 | } else if (strcmp(copymodestr, "tap") == 0) { |
362 | 0 | SCLogInfo("%s: AF_PACKET TAP mode activated %s->%s", iface, iface, aconf->out_iface); |
363 | 0 | aconf->copy_mode = AFP_COPY_MODE_TAP; |
364 | 0 | if (aconf->flags & AFP_TPACKET_V3) { |
365 | 0 | SCLogWarning("%s: using tpacket_v3 in TAP mode will result in high latency", iface); |
366 | 0 | } |
367 | 0 | } else { |
368 | 0 | SCLogWarning("Invalid 'copy-mode' (not in tap, ips)"); |
369 | 0 | } |
370 | 0 | } |
371 | |
|
372 | 0 | if (ConfGetChildValueWithDefault(if_root, if_default, "cluster-id", &tmpclusterid) != 1) { |
373 | 0 | aconf->cluster_id = (uint16_t)(cluster_id_auto++); |
374 | 0 | } else { |
375 | 0 | if (StringParseUint16(&aconf->cluster_id, 10, 0, (const char *)tmpclusterid) < 0) { |
376 | 0 | SCLogWarning("%s: invalid cluster_id, resetting to 0", iface); |
377 | 0 | aconf->cluster_id = 0; |
378 | 0 | } |
379 | 0 | SCLogDebug("Going to use cluster-id %" PRIu16, aconf->cluster_id); |
380 | 0 | } |
381 | |
|
382 | 0 | if (ConfGetChildValueWithDefault(if_root, if_default, "cluster-type", &tmpctype) != 1) { |
383 | | /* Default to our safest choice: flow hashing + defrag |
384 | | * enabled, unless defrag has been disabled by the user. */ |
385 | 0 | uint16_t defrag = PACKET_FANOUT_FLAG_DEFRAG; |
386 | 0 | int conf_val = 0; |
387 | 0 | SCLogConfig("%s: using flow cluster mode for AF_PACKET", aconf->iface); |
388 | 0 | if (ConfGetChildValueBoolWithDefault(if_root, if_default, "defrag", &conf_val)) { |
389 | 0 | if (!conf_val) { |
390 | 0 | SCLogConfig( |
391 | 0 | "%s: disabling defrag kernel functionality for AF_PACKET", aconf->iface); |
392 | 0 | defrag = 0; |
393 | 0 | } |
394 | 0 | } |
395 | 0 | aconf->cluster_type = PACKET_FANOUT_HASH | defrag; |
396 | 0 | cluster_type = PACKET_FANOUT_HASH; |
397 | 0 | } else if (strcmp(tmpctype, "cluster_round_robin") == 0) { |
398 | 0 | SCLogConfig("%s: using round-robin cluster mode for AF_PACKET", aconf->iface); |
399 | 0 | aconf->cluster_type = PACKET_FANOUT_LB; |
400 | 0 | cluster_type = PACKET_FANOUT_LB; |
401 | 0 | } else if (strcmp(tmpctype, "cluster_flow") == 0 || strcmp(tmpctype, "cluster_rollover") == 0) { |
402 | 0 | if (strcmp(tmpctype, "cluster_rollover") == 0) { |
403 | 0 | SCLogWarning("%s: cluster_rollover deprecated; using \"cluster_flow\" instead. See " |
404 | 0 | "ticket #6128", |
405 | 0 | aconf->iface); |
406 | 0 | } |
407 | | /* In hash mode, we also ask for defragmentation needed to |
408 | | * compute the hash */ |
409 | 0 | uint16_t defrag = 0; |
410 | 0 | int conf_val = 0; |
411 | 0 | SCLogConfig("%s: using flow cluster mode for AF_PACKET", aconf->iface); |
412 | 0 | ConfGetChildValueBoolWithDefault(if_root, if_default, "defrag", &conf_val); |
413 | 0 | if (conf_val) { |
414 | 0 | SCLogConfig("%s: using defrag kernel functionality for AF_PACKET", aconf->iface); |
415 | 0 | defrag = PACKET_FANOUT_FLAG_DEFRAG; |
416 | 0 | } |
417 | 0 | aconf->cluster_type = PACKET_FANOUT_HASH | defrag; |
418 | 0 | cluster_type = PACKET_FANOUT_HASH; |
419 | 0 | } else if (strcmp(tmpctype, "cluster_cpu") == 0) { |
420 | 0 | SCLogConfig("%s: using cpu cluster mode for AF_PACKET", aconf->iface); |
421 | 0 | aconf->cluster_type = PACKET_FANOUT_CPU; |
422 | 0 | cluster_type = PACKET_FANOUT_CPU; |
423 | 0 | } else if (strcmp(tmpctype, "cluster_qm") == 0) { |
424 | 0 | SCLogConfig("%s: using queue based cluster mode for AF_PACKET", aconf->iface); |
425 | 0 | aconf->cluster_type = PACKET_FANOUT_QM; |
426 | 0 | cluster_type = PACKET_FANOUT_QM; |
427 | 0 | } else if (strcmp(tmpctype, "cluster_random") == 0) { |
428 | 0 | SCLogConfig("%s: using random based cluster mode for AF_PACKET", aconf->iface); |
429 | 0 | aconf->cluster_type = PACKET_FANOUT_RND; |
430 | 0 | cluster_type = PACKET_FANOUT_RND; |
431 | | #ifdef HAVE_PACKET_EBPF |
432 | | } else if (strcmp(tmpctype, "cluster_ebpf") == 0) { |
433 | | SCLogInfo("%s: using ebpf based cluster mode for AF_PACKET", aconf->iface); |
434 | | aconf->cluster_type = PACKET_FANOUT_EBPF; |
435 | | cluster_type = PACKET_FANOUT_EBPF; |
436 | | #endif |
437 | 0 | } else { |
438 | 0 | SCLogWarning("invalid cluster-type %s", tmpctype); |
439 | 0 | } |
440 | |
|
441 | 0 | int conf_val = 0; |
442 | 0 | ConfGetChildValueBoolWithDefault(if_root, if_default, "rollover", &conf_val); |
443 | 0 | if (conf_val) { |
444 | 0 | SCLogConfig("%s: Rollover requested for AF_PACKET but ignored -- see ticket #6128.", |
445 | 0 | aconf->iface); |
446 | 0 | SCLogWarning("%s: rollover option has been deprecated and will be ignored as it can cause " |
447 | 0 | "severe flow " |
448 | 0 | "tracking issues; see ticket #6128.", |
449 | 0 | iface); |
450 | 0 | } |
451 | |
|
452 | 0 | ConfSetBPFFilter(if_root, if_default, iface, &aconf->bpf_filter); |
453 | |
|
454 | 0 | if (ConfGetChildValueWithDefault(if_root, if_default, "ebpf-lb-file", &ebpf_file) != 1) { |
455 | 0 | aconf->ebpf_lb_file = NULL; |
456 | 0 | } else { |
457 | | #ifdef HAVE_PACKET_EBPF |
458 | | SCLogConfig("%s: af-packet will use '%s' as eBPF load balancing file", iface, ebpf_file); |
459 | | aconf->ebpf_lb_file = ebpf_file; |
460 | | aconf->ebpf_t_config.flags |= EBPF_SOCKET_FILTER; |
461 | | #endif |
462 | 0 | } |
463 | |
|
464 | | #ifdef HAVE_PACKET_EBPF |
465 | | boolval = false; |
466 | | if (ConfGetChildValueBoolWithDefault(if_root, if_default, "pinned-maps", (int *)&boolval) == 1) { |
467 | | if (boolval) { |
468 | | SCLogConfig("%s: using pinned maps", aconf->iface); |
469 | | aconf->ebpf_t_config.flags |= EBPF_PINNED_MAPS; |
470 | | } |
471 | | const char *pinned_maps_name = NULL; |
472 | | if (ConfGetChildValueWithDefault(if_root, if_default, |
473 | | "pinned-maps-name", |
474 | | &pinned_maps_name) != 1) { |
475 | | aconf->ebpf_t_config.pinned_maps_name = pinned_maps_name; |
476 | | } else { |
477 | | aconf->ebpf_t_config.pinned_maps_name = NULL; |
478 | | } |
479 | | } else { |
480 | | aconf->ebpf_t_config.pinned_maps_name = NULL; |
481 | | } |
482 | | #endif |
483 | |
|
484 | | #ifdef HAVE_PACKET_EBPF |
485 | | /* One shot loading of the eBPF file */ |
486 | | if (aconf->ebpf_lb_file && cluster_type == PACKET_FANOUT_EBPF) { |
487 | | int ret = EBPFLoadFile(aconf->iface, aconf->ebpf_lb_file, "loadbalancer", |
488 | | &aconf->ebpf_lb_fd, |
489 | | &aconf->ebpf_t_config); |
490 | | if (ret != 0) { |
491 | | SCLogWarning("%s: failed to load eBPF lb file", iface); |
492 | | } |
493 | | } |
494 | | #else |
495 | 0 | if (aconf->ebpf_lb_file) { |
496 | 0 | SCLogError("%s: eBPF support is not built-in", iface); |
497 | 0 | } |
498 | 0 | #endif |
499 | |
|
500 | 0 | if (ConfGetChildValueWithDefault(if_root, if_default, "ebpf-filter-file", &ebpf_file) != 1) { |
501 | 0 | aconf->ebpf_filter_file = NULL; |
502 | 0 | } else { |
503 | | #ifdef HAVE_PACKET_EBPF |
504 | | SCLogConfig("%s: af-packet will use '%s' as eBPF filter file", iface, ebpf_file); |
505 | | aconf->ebpf_filter_file = ebpf_file; |
506 | | aconf->ebpf_t_config.mode = AFP_MODE_EBPF_BYPASS; |
507 | | aconf->ebpf_t_config.flags |= EBPF_SOCKET_FILTER; |
508 | | #endif |
509 | 0 | ConfGetChildValueBoolWithDefault(if_root, if_default, "bypass", &conf_val); |
510 | 0 | if (conf_val) { |
511 | | #ifdef HAVE_PACKET_EBPF |
512 | | SCLogConfig("%s: using bypass kernel functionality for AF_PACKET", aconf->iface); |
513 | | aconf->flags |= AFP_BYPASS; |
514 | | BypassedFlowManagerRegisterUpdateFunc(EBPFUpdateFlow, NULL); |
515 | | #else |
516 | 0 | SCLogError("%s: bypass set but eBPF support is not built-in", iface); |
517 | 0 | #endif |
518 | 0 | } |
519 | 0 | } |
520 | | |
521 | | /* One shot loading of the eBPF file */ |
522 | 0 | if (aconf->ebpf_filter_file) { |
523 | | #ifdef HAVE_PACKET_EBPF |
524 | | int ret = EBPFLoadFile(aconf->iface, aconf->ebpf_filter_file, "filter", |
525 | | &aconf->ebpf_filter_fd, |
526 | | &aconf->ebpf_t_config); |
527 | | if (ret != 0) { |
528 | | SCLogWarning("%s: failed to load eBPF filter file", iface); |
529 | | } |
530 | | #else |
531 | 0 | SCLogError("%s: eBPF support is not built-in", iface); |
532 | 0 | #endif |
533 | 0 | } |
534 | |
|
535 | 0 | if (ConfGetChildValueWithDefault(if_root, if_default, "xdp-filter-file", &ebpf_file) != 1) { |
536 | 0 | aconf->xdp_filter_file = NULL; |
537 | 0 | } else { |
538 | | #ifdef HAVE_PACKET_XDP |
539 | | aconf->ebpf_t_config.mode = AFP_MODE_XDP_BYPASS; |
540 | | aconf->ebpf_t_config.flags |= EBPF_XDP_CODE; |
541 | | aconf->xdp_filter_file = ebpf_file; |
542 | | ConfGetChildValueBoolWithDefault(if_root, if_default, "bypass", &conf_val); |
543 | | if (conf_val) { |
544 | | SCLogConfig("%s: using bypass kernel functionality for AF_PACKET", aconf->iface); |
545 | | aconf->flags |= AFP_XDPBYPASS; |
546 | | /* if maps are pinned we need to read them at start */ |
547 | | if (aconf->ebpf_t_config.flags & EBPF_PINNED_MAPS) { |
548 | | RunModeEnablesBypassManager(); |
549 | | struct ebpf_timeout_config *ebt = SCCalloc(1, sizeof(struct ebpf_timeout_config)); |
550 | | if (ebt == NULL) { |
551 | | SCLogError("%s: flow bypass alloc error", iface); |
552 | | } else { |
553 | | memcpy(ebt, &(aconf->ebpf_t_config), sizeof(struct ebpf_timeout_config)); |
554 | | BypassedFlowManagerRegisterCheckFunc(NULL, |
555 | | EBPFCheckBypassedFlowCreate, |
556 | | (void *)ebt); |
557 | | } |
558 | | } |
559 | | BypassedFlowManagerRegisterUpdateFunc(EBPFUpdateFlow, NULL); |
560 | | } |
561 | | #else |
562 | 0 | SCLogWarning("%s: XDP filter set but XDP support is not built-in", iface); |
563 | 0 | #endif |
564 | | #ifdef HAVE_PACKET_XDP |
565 | | const char *xdp_mode; |
566 | | if (ConfGetChildValueWithDefault(if_root, if_default, "xdp-mode", &xdp_mode) != 1) { |
567 | | aconf->xdp_mode = XDP_FLAGS_SKB_MODE; |
568 | | } else { |
569 | | if (!strcmp(xdp_mode, "soft")) { |
570 | | aconf->xdp_mode = XDP_FLAGS_SKB_MODE; |
571 | | } else if (!strcmp(xdp_mode, "driver")) { |
572 | | aconf->xdp_mode = XDP_FLAGS_DRV_MODE; |
573 | | } else if (!strcmp(xdp_mode, "hw")) { |
574 | | aconf->xdp_mode = XDP_FLAGS_HW_MODE; |
575 | | aconf->ebpf_t_config.flags |= EBPF_XDP_HW_MODE; |
576 | | } else { |
577 | | SCLogWarning("Invalid xdp-mode value: '%s'", xdp_mode); |
578 | | } |
579 | | } |
580 | | |
581 | | boolval = true; |
582 | | if (ConfGetChildValueBoolWithDefault(if_root, if_default, "use-percpu-hash", (int *)&boolval) == 1) { |
583 | | if (boolval == false) { |
584 | | SCLogConfig("%s: not using percpu hash", aconf->iface); |
585 | | aconf->ebpf_t_config.cpus_count = 1; |
586 | | } |
587 | | } |
588 | | #endif |
589 | 0 | } |
590 | | |
591 | | /* One shot loading of the eBPF file */ |
592 | 0 | if (aconf->xdp_filter_file) { |
593 | | #ifdef HAVE_PACKET_XDP |
594 | | int ret = EBPFLoadFile(aconf->iface, aconf->xdp_filter_file, "xdp", |
595 | | &aconf->xdp_filter_fd, |
596 | | &aconf->ebpf_t_config); |
597 | | switch (ret) { |
598 | | case 1: |
599 | | SCLogInfo("%s: loaded pinned maps from sysfs", iface); |
600 | | break; |
601 | | case -1: |
602 | | SCLogWarning("%s: failed to load XDP filter file", iface); |
603 | | break; |
604 | | case 0: |
605 | | ret = EBPFSetupXDP(aconf->iface, aconf->xdp_filter_fd, aconf->xdp_mode); |
606 | | if (ret != 0) { |
607 | | SCLogWarning("%s: failed to set up XDP", iface); |
608 | | } else { |
609 | | /* Try to get the xdp-cpu-redirect key */ |
610 | | const char *cpuset; |
611 | | if (ConfGetChildValueWithDefault(if_root, if_default, |
612 | | "xdp-cpu-redirect", &cpuset) == 1) { |
613 | | SCLogConfig("%s: Setting up CPU map XDP", iface); |
614 | | ConfNode *node = ConfGetChildWithDefault(if_root, if_default, "xdp-cpu-redirect"); |
615 | | if (node == NULL) { |
616 | | SCLogError("Previously found node has disappeared"); |
617 | | } else { |
618 | | EBPFBuildCPUSet(node, aconf->iface); |
619 | | } |
620 | | } else { |
621 | | /* It will just set CPU count to 0 */ |
622 | | EBPFBuildCPUSet(NULL, aconf->iface); |
623 | | } |
624 | | } |
625 | | /* we have a peer and we use bypass so we can set up XDP iface redirect */ |
626 | | if (aconf->out_iface) { |
627 | | EBPFSetPeerIface(aconf->iface, aconf->out_iface); |
628 | | } |
629 | | } |
630 | | #else |
631 | 0 | SCLogError("%s: XDP support is not built-in", iface); |
632 | 0 | #endif |
633 | 0 | } |
634 | |
|
635 | 0 | if ((ConfGetChildValueIntWithDefault(if_root, if_default, "buffer-size", &value)) == 1) { |
636 | 0 | aconf->buffer_size = value; |
637 | 0 | } else { |
638 | 0 | aconf->buffer_size = 0; |
639 | 0 | } |
640 | 0 | if ((ConfGetChildValueIntWithDefault(if_root, if_default, "ring-size", &value)) == 1) { |
641 | 0 | aconf->ring_size = value; |
642 | 0 | } |
643 | |
|
644 | 0 | if ((ConfGetChildValueIntWithDefault(if_root, if_default, "block-size", &value)) == 1) { |
645 | 0 | if (value % getpagesize()) { |
646 | 0 | SCLogWarning("%s: block-size %" PRIuMAX " must be a multiple of pagesize (%u).", iface, |
647 | 0 | value, getpagesize()); |
648 | 0 | } else { |
649 | 0 | aconf->block_size = value; |
650 | 0 | } |
651 | 0 | } |
652 | |
|
653 | 0 | if ((ConfGetChildValueIntWithDefault(if_root, if_default, "block-timeout", &value)) == 1) { |
654 | 0 | aconf->block_timeout = value; |
655 | 0 | } else { |
656 | 0 | aconf->block_timeout = 10; |
657 | 0 | } |
658 | |
|
659 | 0 | if ((ConfGetChildValueIntWithDefault(if_root, if_default, "v2-block-size", &value)) == 1) { |
660 | 0 | if (value % getpagesize()) { |
661 | 0 | SCLogWarning("%s: v2-block-size %" PRIuMAX " must be a multiple of pagesize (%u).", |
662 | 0 | iface, value, getpagesize()); |
663 | 0 | } else { |
664 | 0 | aconf->v2_block_size = value; |
665 | 0 | } |
666 | 0 | } |
667 | |
|
668 | 0 | (void)ConfGetChildValueBoolWithDefault(if_root, if_default, "disable-promisc", (int *)&boolval); |
669 | 0 | if (boolval) { |
670 | 0 | SCLogConfig("%s: disabling promiscuous mode", aconf->iface); |
671 | 0 | aconf->promisc = 0; |
672 | 0 | } |
673 | |
|
674 | 0 | if (ConfGetChildValueWithDefault(if_root, if_default, "checksum-checks", &tmpctype) == 1) { |
675 | 0 | if (strcmp(tmpctype, "auto") == 0) { |
676 | 0 | aconf->checksum_mode = CHECKSUM_VALIDATION_AUTO; |
677 | 0 | } else if (ConfValIsTrue(tmpctype)) { |
678 | 0 | aconf->checksum_mode = CHECKSUM_VALIDATION_ENABLE; |
679 | 0 | } else if (ConfValIsFalse(tmpctype)) { |
680 | 0 | aconf->checksum_mode = CHECKSUM_VALIDATION_DISABLE; |
681 | 0 | } else if (strcmp(tmpctype, "kernel") == 0) { |
682 | 0 | aconf->checksum_mode = CHECKSUM_VALIDATION_KERNEL; |
683 | 0 | } else { |
684 | 0 | SCLogWarning("%s: invalid value for checksum-checks", aconf->iface); |
685 | 0 | } |
686 | 0 | } |
687 | |
|
688 | 0 | finalize: |
689 | | |
690 | | /* if the number of threads is not 1, we need to first check if fanout |
691 | | * functions on this system. */ |
692 | 0 | if (aconf->threads != 1) { |
693 | 0 | if (AFPIsFanoutSupported(aconf->cluster_id) == 0) { |
694 | 0 | if (aconf->threads != 0) { |
695 | 0 | SCLogNotice("%s: fanout not supported on this system, falling " |
696 | 0 | "back to 1 capture thread", |
697 | 0 | iface); |
698 | 0 | } |
699 | 0 | aconf->threads = 1; |
700 | 0 | } |
701 | 0 | } |
702 | | |
703 | | /* try to automagically set the proper number of threads */ |
704 | 0 | if (aconf->threads == 0) { |
705 | | /* for cluster_flow use core count */ |
706 | 0 | if (cluster_type == PACKET_FANOUT_HASH) { |
707 | 0 | aconf->threads = (int)UtilCpuGetNumProcessorsOnline(); |
708 | 0 | SCLogPerf("%s: cluster_flow: %u cores, using %u threads", iface, aconf->threads, |
709 | 0 | aconf->threads); |
710 | | |
711 | | /* for cluster_qm use RSS queue count */ |
712 | 0 | } else if (cluster_type == PACKET_FANOUT_QM) { |
713 | 0 | int rss_queues = GetIfaceRSSQueuesNum(iface); |
714 | 0 | if (rss_queues > 0) { |
715 | 0 | aconf->threads = rss_queues; |
716 | 0 | SCLogPerf("%s: cluster_qm: %d RSS queues, using %u threads", iface, rss_queues, |
717 | 0 | aconf->threads); |
718 | 0 | } |
719 | 0 | } |
720 | |
|
721 | 0 | if (aconf->threads) { |
722 | 0 | SCLogDebug("using %d threads for interface %s", aconf->threads, iface); |
723 | 0 | } |
724 | 0 | } |
725 | 0 | if (aconf->threads <= 0) { |
726 | 0 | aconf->threads = 1; |
727 | 0 | } |
728 | 0 | SC_ATOMIC_RESET(aconf->ref); |
729 | 0 | (void) SC_ATOMIC_ADD(aconf->ref, aconf->threads); |
730 | |
|
731 | 0 | if (aconf->ring_size != 0) { |
732 | 0 | if (aconf->ring_size * aconf->threads < max_pending_packets) { |
733 | 0 | aconf->ring_size = max_pending_packets / aconf->threads + 1; |
734 | 0 | SCLogWarning("%s: inefficient setup: ring-size < max_pending_packets. " |
735 | 0 | "Resetting to decent value %d.", |
736 | 0 | iface, aconf->ring_size); |
737 | | /* We want at least that max_pending_packets packets can be handled by the |
738 | | * interface. This is generous if we have multiple interfaces listening. */ |
739 | 0 | } |
740 | 0 | } else { |
741 | | /* We want that max_pending_packets packets can be handled by suricata |
742 | | * for this interface. To take burst into account we multiply the obtained |
743 | | * size by 2. */ |
744 | 0 | aconf->ring_size = max_pending_packets * 2 / aconf->threads; |
745 | 0 | } |
746 | |
|
747 | 0 | int ltype = AFPGetLinkType(iface); |
748 | 0 | switch (ltype) { |
749 | 0 | case LINKTYPE_ETHERNET: |
750 | | /* af-packet can handle csum offloading */ |
751 | 0 | if (LiveGetOffload() == 0) { |
752 | 0 | if (GetIfaceOffloading(iface, 0, 1) == 1) { |
753 | 0 | SCLogWarning( |
754 | 0 | "%s: using AF_PACKET with offloads enabled leads to capture problems", |
755 | 0 | iface); |
756 | 0 | } |
757 | 0 | } else { |
758 | 0 | DisableIfaceOffloading(LiveGetDevice(iface), 0, 1); |
759 | 0 | } |
760 | 0 | break; |
761 | 0 | case -1: |
762 | 0 | default: |
763 | 0 | break; |
764 | 0 | } |
765 | | |
766 | 0 | if (active_runmode == NULL || strcmp("workers", active_runmode) != 0) { |
767 | | /* If we are using copy mode we need a lock */ |
768 | 0 | aconf->flags |= AFP_SOCK_PROTECT; |
769 | 0 | aconf->flags |= AFP_NEED_PEER; |
770 | 0 | } |
771 | | |
772 | | /* Warn if inline and defrag is enabled. */ |
773 | 0 | if (aconf->copy_mode != AFP_COPY_MODE_NONE && aconf->cluster_type & PACKET_FANOUT_FLAG_DEFRAG) { |
774 | 0 | SCLogWarning( |
775 | 0 | "%s: AF_PACKET defrag is not recommended for inline use, please disable", iface); |
776 | 0 | } |
777 | | |
778 | | /* Warn if not inline and defrag is disabled. */ |
779 | 0 | if (aconf->copy_mode == AFP_COPY_MODE_NONE && cluster_type == PACKET_FANOUT_HASH && |
780 | 0 | ((aconf->cluster_type & PACKET_FANOUT_FLAG_DEFRAG) == 0)) { |
781 | 0 | SCLogWarning("%s: AF_PACKET defrag is recommended for IDS cluster_flow", iface); |
782 | 0 | } |
783 | | |
784 | | /* For tpacket-v2, warn if defrag is enabled and block-size is |
785 | | * less than max defragmented packet size. */ |
786 | 0 | if ((aconf->flags & AFP_TPACKET_V3) == 0 && (aconf->cluster_type & PACKET_FANOUT_FLAG_DEFRAG) && |
787 | 0 | aconf->v2_block_size > 0 && aconf->v2_block_size < MAX_PACKET_SIZE) { |
788 | 0 | SCLogWarning("%s: AF_PACKET v2-block-size is not large enough for max fragmented IP packet " |
789 | 0 | "size (%u)", |
790 | 0 | iface, MAX_PACKET_SIZE); |
791 | 0 | } |
792 | | |
793 | | /* For tpacket-v3, warn if defrag is enabled and block-block-size |
794 | | * is less than max defragmented packet size. */ |
795 | 0 | if ((aconf->flags & AFP_TPACKET_V3) && (aconf->cluster_type & PACKET_FANOUT_FLAG_DEFRAG) && |
796 | 0 | (aconf->block_size < MAX_PACKET_SIZE)) { |
797 | 0 | SCLogWarning("%s: AF_PACKET block-size is not large enough for max fragmented IP packet " |
798 | 0 | "size (%u)", |
799 | 0 | iface, MAX_PACKET_SIZE); |
800 | 0 | } |
801 | | |
802 | | /* Warn that if not-inline, tpacket-v3 is the better choice. */ |
803 | 0 | if (aconf->copy_mode == AFP_COPY_MODE_NONE && (aconf->flags & AFP_TPACKET_V3) == 0) { |
804 | 0 | SCLogWarning("%s: AF_PACKET tpacket-v3 is recommended for non-inline operation", iface); |
805 | 0 | } |
806 | |
|
807 | 0 | return aconf; |
808 | 0 | } |
809 | | |
810 | | static int AFPConfigGeThreadsCount(void *conf) |
811 | 0 | { |
812 | 0 | AFPIfaceConfig *afp = (AFPIfaceConfig *)conf; |
813 | 0 | return afp->threads; |
814 | 0 | } |
815 | | |
816 | | #endif /* HAVE_AF_PACKET */ |
817 | | |
818 | | int RunModeIdsAFPAutoFp(void) |
819 | 0 | { |
820 | 0 | SCEnter(); |
821 | | |
822 | | /* We include only if AF_PACKET is enabled */ |
823 | 0 | #ifdef HAVE_AF_PACKET |
824 | 0 | int ret; |
825 | 0 | const char *live_dev = NULL; |
826 | |
|
827 | 0 | TimeModeSetLive(); |
828 | |
|
829 | 0 | (void)ConfGet("af-packet.live-interface", &live_dev); |
830 | |
|
831 | 0 | SCLogDebug("live_dev %s", live_dev); |
832 | |
|
833 | 0 | if (AFPPeersListInit() != TM_ECODE_OK) { |
834 | 0 | FatalError("Unable to init peers list."); |
835 | 0 | } |
836 | | |
837 | 0 | ret = RunModeSetLiveCaptureAutoFp(ParseAFPConfig, AFPConfigGeThreadsCount, "ReceiveAFP", |
838 | 0 | "DecodeAFP", thread_name_autofp, live_dev); |
839 | 0 | if (ret != 0) { |
840 | 0 | FatalError("Unable to start runmode"); |
841 | 0 | } |
842 | | |
843 | | /* In IPS mode each threads must have a peer */ |
844 | 0 | if (AFPPeersListCheck() != TM_ECODE_OK) { |
845 | 0 | FatalError("Some IPS capture threads did not peer."); |
846 | 0 | } |
847 | | |
848 | 0 | SCLogDebug("RunModeIdsAFPAutoFp initialised"); |
849 | 0 | #endif /* HAVE_AF_PACKET */ |
850 | |
|
851 | 0 | SCReturnInt(0); |
852 | 0 | } |
853 | | |
854 | | /** |
855 | | * \brief Single thread version of the AF_PACKET processing. |
856 | | */ |
857 | | int RunModeIdsAFPSingle(void) |
858 | 0 | { |
859 | 0 | SCEnter(); |
860 | 0 | #ifdef HAVE_AF_PACKET |
861 | 0 | int ret; |
862 | 0 | const char *live_dev = NULL; |
863 | |
|
864 | 0 | TimeModeSetLive(); |
865 | |
|
866 | 0 | (void)ConfGet("af-packet.live-interface", &live_dev); |
867 | |
|
868 | 0 | if (AFPPeersListInit() != TM_ECODE_OK) { |
869 | 0 | FatalError("Unable to init peers list."); |
870 | 0 | } |
871 | | |
872 | 0 | ret = RunModeSetLiveCaptureSingle(ParseAFPConfig, |
873 | 0 | AFPConfigGeThreadsCount, |
874 | 0 | "ReceiveAFP", |
875 | 0 | "DecodeAFP", thread_name_single, |
876 | 0 | live_dev); |
877 | 0 | if (ret != 0) { |
878 | 0 | FatalError("Unable to start runmode"); |
879 | 0 | } |
880 | | |
881 | | /* In IPS mode each threads must have a peer */ |
882 | 0 | if (AFPPeersListCheck() != TM_ECODE_OK) { |
883 | 0 | FatalError("Some IPS capture threads did not peer."); |
884 | 0 | } |
885 | | |
886 | 0 | SCLogDebug("RunModeIdsAFPSingle initialised"); |
887 | |
|
888 | 0 | #endif /* HAVE_AF_PACKET */ |
889 | 0 | SCReturnInt(0); |
890 | 0 | } |
891 | | |
892 | | /** |
893 | | * \brief Workers version of the AF_PACKET processing. |
894 | | * |
895 | | * Start N threads with each thread doing all the work. |
896 | | * |
897 | | */ |
898 | | int RunModeIdsAFPWorkers(void) |
899 | 0 | { |
900 | 0 | SCEnter(); |
901 | 0 | #ifdef HAVE_AF_PACKET |
902 | 0 | int ret; |
903 | 0 | const char *live_dev = NULL; |
904 | |
|
905 | 0 | TimeModeSetLive(); |
906 | |
|
907 | 0 | (void)ConfGet("af-packet.live-interface", &live_dev); |
908 | |
|
909 | 0 | if (AFPPeersListInit() != TM_ECODE_OK) { |
910 | 0 | FatalError("Unable to init peers list."); |
911 | 0 | } |
912 | | |
913 | 0 | ret = RunModeSetLiveCaptureWorkers(ParseAFPConfig, AFPConfigGeThreadsCount, "ReceiveAFP", |
914 | 0 | "DecodeAFP", thread_name_workers, live_dev); |
915 | 0 | if (ret != 0) { |
916 | 0 | FatalError("Unable to start runmode"); |
917 | 0 | } |
918 | | |
919 | | /* In IPS mode each threads must have a peer */ |
920 | 0 | if (AFPPeersListCheck() != TM_ECODE_OK) { |
921 | 0 | FatalError("Some IPS capture threads did not peer."); |
922 | 0 | } |
923 | | |
924 | 0 | SCLogDebug("RunModeIdsAFPWorkers initialised"); |
925 | |
|
926 | 0 | #endif /* HAVE_AF_PACKET */ |
927 | 0 | SCReturnInt(0); |
928 | 0 | } |
929 | | |
930 | | /** |
931 | | * @} |
932 | | */ |