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