/src/suricata7/src/runmode-af-xdp.c
Line | Count | Source |
1 | | /* Copyright (C) 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 afxdppacket |
20 | | * |
21 | | * @{ |
22 | | */ |
23 | | |
24 | | /** |
25 | | * \file |
26 | | * |
27 | | * \author Richard McConnell <richard_mcconnell@rapid7.com> |
28 | | * |
29 | | * AF_XDP socket runmode |
30 | | * |
31 | | */ |
32 | | #define PCAP_DONT_INCLUDE_PCAP_BPF_H 1 |
33 | | #define SC_PCAP_DONT_INCLUDE_PCAP_H 1 |
34 | | #include "suricata-common.h" |
35 | | #include "tm-threads.h" |
36 | | #include "conf.h" |
37 | | #include "runmodes.h" |
38 | | #include "runmode-af-xdp.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-xdp.h" |
60 | | |
61 | | #ifdef HAVE_AF_XDP |
62 | | #include <linux/if_xdp.h> |
63 | | #include <linux/if_link.h> |
64 | | #include <xdp/xsk.h> |
65 | | #endif |
66 | | |
67 | | const char *RunModeAFXDPGetDefaultMode(void) |
68 | 0 | { |
69 | 0 | return "workers"; |
70 | 0 | } |
71 | | |
72 | | void RunModeIdsAFXDPRegister(void) |
73 | 37 | { |
74 | 37 | RunModeRegisterNewRunMode(RUNMODE_AFXDP_DEV, "single", "Single threaded af-xdp mode", |
75 | 37 | RunModeIdsAFXDPSingle, NULL); |
76 | 37 | RunModeRegisterNewRunMode(RUNMODE_AFXDP_DEV, "workers", |
77 | 37 | "Workers af-xdp mode, each thread does all" |
78 | 37 | " tasks from acquisition to logging", |
79 | 37 | RunModeIdsAFXDPWorkers, NULL); |
80 | | |
81 | 37 | return; |
82 | 37 | } |
83 | | |
84 | | #ifdef HAVE_AF_XDP |
85 | | |
86 | | #define DEFAULT_BUSY_POLL_TIME 20 |
87 | | #define DEFAULT_BUSY_POLL_BUDGET 64 |
88 | | #define DEFAULT_GRO_FLUSH_TIMEOUT 2000000 |
89 | | #define DEFAULT_NAPI_HARD_IRQS 2 |
90 | | |
91 | | static void AFXDPDerefConfig(void *conf) |
92 | | { |
93 | | AFXDPIfaceConfig *pfp = (AFXDPIfaceConfig *)conf; |
94 | | /* Pcap config is used only once but cost of this low. */ |
95 | | if (SC_ATOMIC_SUB(pfp->ref, 1) <= 1) { |
96 | | SCFree(pfp); |
97 | | } |
98 | | } |
99 | | |
100 | | static TmEcode ConfigSetThreads(AFXDPIfaceConfig *aconf, const char *entry_str) |
101 | | { |
102 | | SCEnter(); |
103 | | const char *active_runmode = RunmodeGetActive(); |
104 | | |
105 | | if (active_runmode && !strcmp("single", active_runmode)) { |
106 | | aconf->threads = 1; |
107 | | SCReturnInt(0); |
108 | | } |
109 | | |
110 | | if (entry_str == NULL) { |
111 | | SCLogError("Number of threads for interface \"%s\" not specified", aconf->iface); |
112 | | SCReturnInt(TM_ECODE_FAILED); |
113 | | } |
114 | | |
115 | | const int nr_queues = GetIfaceRSSQueuesNum(aconf->iface); |
116 | | |
117 | | if (strcmp(entry_str, "auto") == 0) { |
118 | | |
119 | | const int nr_cores = (int)UtilCpuGetNumProcessorsOnline(); |
120 | | |
121 | | /* Threads limited to MIN(cores vs queues) */ |
122 | | aconf->threads = (nr_cores <= nr_queues) ? nr_cores : nr_queues; |
123 | | const char *sys_type = nr_cores <= nr_queues ? "cores" : "queues"; |
124 | | |
125 | | SCLogPerf("%u %s, so using %u threads", aconf->threads, sys_type, aconf->threads); |
126 | | SCReturnInt(TM_ECODE_OK); |
127 | | } |
128 | | |
129 | | if (StringParseInt32(&aconf->threads, 10, 0, entry_str) < 0) { |
130 | | SCLogError("Threads entry for interface %s contain non-numerical characters - \"%s\"", |
131 | | aconf->iface, entry_str); |
132 | | SCReturnInt(TM_ECODE_FAILED); |
133 | | } |
134 | | |
135 | | if (aconf->threads < 0) { |
136 | | SCLogError("Interface %s has a negative number of threads", aconf->iface); |
137 | | SCReturnInt(TM_ECODE_FAILED); |
138 | | } |
139 | | |
140 | | if (aconf->threads > nr_queues) { |
141 | | SCLogWarning( |
142 | | "Selected threads greater than configured queues, using: %d thread(s)", nr_queues); |
143 | | aconf->threads = nr_queues; |
144 | | } |
145 | | |
146 | | SCReturnInt(TM_ECODE_OK); |
147 | | } |
148 | | |
149 | | /** |
150 | | * \brief extract information from config file |
151 | | * |
152 | | * The returned structure will be freed by the thread init function. |
153 | | * This is thus necessary to copy the structure before giving it |
154 | | * to thread or to reparse the file for each thread (and thus have |
155 | | * new structure. |
156 | | * |
157 | | * \return a AFXDPIfaceConfig corresponding to the interface name |
158 | | */ |
159 | | static void *ParseAFXDPConfig(const char *iface) |
160 | | { |
161 | | const char *confstr = NULL; |
162 | | ConfNode *if_root; |
163 | | ConfNode *if_default = NULL; |
164 | | ConfNode *af_xdp_node = NULL; |
165 | | int conf_val = 0; |
166 | | intmax_t conf_val_int = 0; |
167 | | bool boolval = false; |
168 | | |
169 | | if (iface == NULL) { |
170 | | return NULL; |
171 | | } |
172 | | |
173 | | AFXDPIfaceConfig *aconf = SCCalloc(1, sizeof(*aconf)); |
174 | | if (unlikely(aconf == NULL)) { |
175 | | return NULL; |
176 | | } |
177 | | |
178 | | /* default/basic config setup */ |
179 | | strlcpy(aconf->iface, iface, sizeof(aconf->iface)); |
180 | | aconf->DerefFunc = AFXDPDerefConfig; |
181 | | aconf->threads = 1; |
182 | | aconf->promisc = 1; |
183 | | aconf->enable_busy_poll = true; |
184 | | aconf->busy_poll_time = DEFAULT_BUSY_POLL_TIME; |
185 | | aconf->busy_poll_budget = DEFAULT_BUSY_POLL_BUDGET; |
186 | | aconf->mode = XDP_FLAGS_UPDATE_IF_NOEXIST; |
187 | | aconf->gro_flush_timeout = DEFAULT_GRO_FLUSH_TIMEOUT; |
188 | | aconf->napi_defer_hard_irqs = DEFAULT_NAPI_HARD_IRQS; |
189 | | aconf->mem_alignment = XSK_UMEM__DEFAULT_FLAGS; |
190 | | |
191 | | /* Find initial node */ |
192 | | af_xdp_node = ConfGetNode("af-xdp"); |
193 | | if (af_xdp_node == NULL) { |
194 | | SCLogInfo("unable to find af-xdp config using default values"); |
195 | | goto finalize; |
196 | | } |
197 | | |
198 | | if_root = ConfFindDeviceConfig(af_xdp_node, iface); |
199 | | if_default = ConfFindDeviceConfig(af_xdp_node, "default"); |
200 | | |
201 | | if (if_root == NULL && if_default == NULL) { |
202 | | SCLogInfo("unable to find af-xdp config for " |
203 | | "interface \"%s\" or \"default\", using default values", |
204 | | iface); |
205 | | goto finalize; |
206 | | } |
207 | | |
208 | | /* If there is no setting for current interface use default one as main iface */ |
209 | | if (if_root == NULL) { |
210 | | if_root = if_default; |
211 | | if_default = NULL; |
212 | | } |
213 | | |
214 | | /* Threading */ |
215 | | confstr = "auto"; |
216 | | (void)ConfGetChildValueWithDefault(if_root, if_default, "threads", &confstr); |
217 | | if (ConfigSetThreads(aconf, confstr) != TM_ECODE_OK) { |
218 | | aconf->DerefFunc(aconf); |
219 | | return NULL; |
220 | | } |
221 | | |
222 | | SC_ATOMIC_RESET(aconf->ref); |
223 | | (void)SC_ATOMIC_ADD(aconf->ref, aconf->threads); |
224 | | |
225 | | /* Promisc Mode */ |
226 | | (void)ConfGetChildValueBoolWithDefault(if_root, if_default, "disable-promisc", (int *)&boolval); |
227 | | if (boolval) { |
228 | | SCLogConfig("Disabling promiscuous mode on iface %s", aconf->iface); |
229 | | aconf->promisc = 0; |
230 | | } |
231 | | |
232 | | #ifdef HAVE_AF_XDP |
233 | | /* AF_XDP socket mode options */ |
234 | | if (ConfGetChildValueWithDefault(if_root, if_default, "force-xdp-mode", &confstr) == 1) { |
235 | | if (strncasecmp(confstr, "drv", 3) == 0) { |
236 | | aconf->mode |= XDP_FLAGS_DRV_MODE; |
237 | | } else if (strncasecmp(confstr, "skb", 3) == 0) { |
238 | | aconf->mode |= XDP_FLAGS_SKB_MODE; |
239 | | } else if (strncasecmp(confstr, "none", 4) == 0) { |
240 | | } else { |
241 | | SCLogWarning("Incorrect af-xdp xdp-mode setting, default (none) shall be applied"); |
242 | | } |
243 | | } |
244 | | |
245 | | /* copy and zerocopy binding options */ |
246 | | if (ConfGetChildValueWithDefault(if_root, if_default, "force-bind-mode", &confstr) == 1) { |
247 | | if (strncasecmp(confstr, "zero", 4) == 0) { |
248 | | aconf->bind_flags |= XDP_ZEROCOPY; |
249 | | } else if (strncasecmp(confstr, "copy", 4) == 0) { |
250 | | aconf->bind_flags |= XDP_COPY; |
251 | | } else if (strncasecmp(confstr, "none", 4) == 0) { |
252 | | } else { |
253 | | SCLogWarning("Incorrect af-xdp copy-mode setting, default (none) shall be applied"); |
254 | | } |
255 | | } |
256 | | |
257 | | /* memory alignment mode selection */ |
258 | | if (ConfGetChildValueWithDefault(if_root, if_default, "mem-unaligned", &confstr) == 1) { |
259 | | if (strncasecmp(confstr, "yes", 3) == 0) { |
260 | | aconf->mem_alignment = XDP_UMEM_UNALIGNED_CHUNK_FLAG; |
261 | | } |
262 | | } |
263 | | |
264 | | /* Busy polling options */ |
265 | | if (ConfGetChildValueBoolWithDefault(if_root, if_default, "enable-busy-poll", &conf_val) == 1) { |
266 | | if (conf_val == 0) { |
267 | | aconf->enable_busy_poll = false; |
268 | | } |
269 | | } |
270 | | |
271 | | if (aconf->enable_busy_poll) { |
272 | | if (ConfGetChildValueIntWithDefault(if_root, if_default, "busy-poll-time", &conf_val_int) == |
273 | | 1) { |
274 | | if (conf_val_int) { |
275 | | aconf->busy_poll_time = conf_val_int; |
276 | | } |
277 | | } |
278 | | |
279 | | if (ConfGetChildValueIntWithDefault( |
280 | | if_root, if_default, "busy-poll-budget", &conf_val_int) == 1) { |
281 | | if (conf_val_int) { |
282 | | aconf->busy_poll_budget = conf_val_int; |
283 | | } |
284 | | } |
285 | | |
286 | | /* 0 value is valid for these Linux tunable's */ |
287 | | if (ConfGetChildValueIntWithDefault( |
288 | | if_root, if_default, "gro-flush-timeout", &conf_val_int) == 1) { |
289 | | aconf->gro_flush_timeout = conf_val_int; |
290 | | } |
291 | | |
292 | | if (ConfGetChildValueIntWithDefault( |
293 | | if_root, if_default, "napi-defer-hard-irq", &conf_val_int) == 1) { |
294 | | aconf->napi_defer_hard_irqs = conf_val_int; |
295 | | } |
296 | | } |
297 | | #endif |
298 | | |
299 | | finalize: |
300 | | if (LiveGetOffload() == 0) { |
301 | | if (GetIfaceOffloading(iface, 0, 1) == 1) { |
302 | | SCLogWarning("Using AF_XDP with offloading activated leads to capture problems"); |
303 | | } |
304 | | } else { |
305 | | DisableIfaceOffloading(LiveGetDevice(iface), 0, 1); |
306 | | } |
307 | | |
308 | | return aconf; |
309 | | } |
310 | | |
311 | | static int AFXDPConfigGetThreadsCount(void *conf) |
312 | | { |
313 | | if (conf == NULL) |
314 | | FatalError("Configuration file is NULL"); |
315 | | |
316 | | AFXDPIfaceConfig *afxdp_conf = (AFXDPIfaceConfig *)conf; |
317 | | return afxdp_conf->threads; |
318 | | } |
319 | | |
320 | | #endif /* HAVE_AF_XDP */ |
321 | | |
322 | | /** |
323 | | * \brief Single thread version of the AF_XDP processing. |
324 | | */ |
325 | | int RunModeIdsAFXDPSingle(void) |
326 | 0 | { |
327 | 0 | SCEnter(); |
328 | |
|
329 | | #ifdef HAVE_AF_XDP |
330 | | int ret; |
331 | | const char *live_dev = NULL; |
332 | | |
333 | | TimeModeSetLive(); |
334 | | |
335 | | (void)ConfGet("af-xdp.live-interface", &live_dev); |
336 | | |
337 | | if (AFXDPQueueProtectionInit() != TM_ECODE_OK) { |
338 | | FatalError("Unable to init AF_XDP queue protection."); |
339 | | } |
340 | | |
341 | | ret = RunModeSetLiveCaptureSingle(ParseAFXDPConfig, AFXDPConfigGetThreadsCount, "ReceiveAFXDP", |
342 | | "DecodeAFXDP", thread_name_single, live_dev); |
343 | | if (ret != 0) { |
344 | | FatalError("Unable to start runmode"); |
345 | | } |
346 | | |
347 | | SCLogDebug("RunModeIdsAFXDPSingle initialised"); |
348 | | |
349 | | #endif /* HAVE_AF_XDP */ |
350 | 0 | SCReturnInt(0); |
351 | 0 | } |
352 | | |
353 | | /** |
354 | | * \brief Workers version of the AF_XDP processing. |
355 | | * |
356 | | * Start N threads with each thread doing all the work. |
357 | | * |
358 | | */ |
359 | | int RunModeIdsAFXDPWorkers(void) |
360 | 0 | { |
361 | 0 | SCEnter(); |
362 | |
|
363 | | #ifdef HAVE_AF_XDP |
364 | | int ret; |
365 | | const char *live_dev = NULL; |
366 | | |
367 | | TimeModeSetLive(); |
368 | | |
369 | | (void)ConfGet("af-xdp.live-interface", &live_dev); |
370 | | |
371 | | if (AFXDPQueueProtectionInit() != TM_ECODE_OK) { |
372 | | FatalError("Unable to init AF_XDP queue protection."); |
373 | | } |
374 | | |
375 | | ret = RunModeSetLiveCaptureWorkers(ParseAFXDPConfig, AFXDPConfigGetThreadsCount, "ReceiveAFXDP", |
376 | | "DecodeAFXDP", thread_name_workers, live_dev); |
377 | | if (ret != 0) { |
378 | | FatalError("Unable to start runmode"); |
379 | | } |
380 | | |
381 | | SCLogDebug("RunModeIdsAFXDPWorkers initialised"); |
382 | | |
383 | | #endif /* HAVE_AF_XDP */ |
384 | 0 | SCReturnInt(0); |
385 | 0 | } |
386 | | /** |
387 | | * @} |
388 | | */ |