/src/suricata8/src/source-af-xdp.c
Line | Count | Source |
1 | | /* Copyright (C) 2011-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 | | * \defgroup afxdppacket AF_XDP running mode |
20 | | * |
21 | | * @{ |
22 | | */ |
23 | | |
24 | | /** |
25 | | * \file |
26 | | * |
27 | | * \author Richard McConnell <richard_mcconnell@rapid7.com> |
28 | | * |
29 | | * AF_XDP socket acquisition support |
30 | | * |
31 | | */ |
32 | | #define SC_PCAP_DONT_INCLUDE_PCAP_H 1 |
33 | | #include "suricata-common.h" |
34 | | #include "suricata.h" |
35 | | #include "decode.h" |
36 | | #include "packet-queue.h" |
37 | | #include "threads.h" |
38 | | #include "threadvars.h" |
39 | | #include "tm-queuehandlers.h" |
40 | | #include "tm-modules.h" |
41 | | #include "tm-threads.h" |
42 | | #include "tm-threads-common.h" |
43 | | #include "conf.h" |
44 | | #include "util-cpu.h" |
45 | | #include "util-datalink.h" |
46 | | #include "util-debug.h" |
47 | | #include "util-device-private.h" |
48 | | #include "util-ebpf.h" |
49 | | #include "util-error.h" |
50 | | #include "util-privs.h" |
51 | | #include "util-optimize.h" |
52 | | #include "util-checksum.h" |
53 | | #include "util-ioctl.h" |
54 | | #include "util-host-info.h" |
55 | | #include "util-sysfs.h" |
56 | | #include "tmqh-packetpool.h" |
57 | | #include "source-af-xdp.h" |
58 | | #include "runmodes.h" |
59 | | #include "flow-storage.h" |
60 | | #include "util-validate.h" |
61 | | |
62 | | #ifdef HAVE_AF_XDP |
63 | | #include <net/if.h> |
64 | | #include <bpf/libbpf.h> |
65 | | #include <xdp/xsk.h> |
66 | | #include <xdp/libxdp.h> |
67 | | #endif |
68 | | |
69 | | #if HAVE_LINUX_IF_ETHER_H |
70 | | #include <linux/if_ether.h> |
71 | | #endif |
72 | | |
73 | | #ifndef HAVE_AF_XDP |
74 | | |
75 | | TmEcode NoAFXDPSupportExit(ThreadVars *, const void *, void **); |
76 | | |
77 | | void TmModuleReceiveAFXDPRegister(void) |
78 | 78 | { |
79 | 78 | tmm_modules[TMM_RECEIVEAFXDP].name = "ReceiveAFXDP"; |
80 | 78 | tmm_modules[TMM_RECEIVEAFXDP].ThreadInit = NoAFXDPSupportExit; |
81 | 78 | tmm_modules[TMM_RECEIVEAFXDP].Func = NULL; |
82 | 78 | tmm_modules[TMM_RECEIVEAFXDP].ThreadExitPrintStats = NULL; |
83 | 78 | tmm_modules[TMM_RECEIVEAFXDP].ThreadDeinit = NULL; |
84 | 78 | tmm_modules[TMM_RECEIVEAFXDP].cap_flags = 0; |
85 | 78 | tmm_modules[TMM_RECEIVEAFXDP].flags = TM_FLAG_RECEIVE_TM; |
86 | 78 | } |
87 | | |
88 | | /** |
89 | | * \brief Registration Function for DecodeAFXDP. |
90 | | */ |
91 | | void TmModuleDecodeAFXDPRegister(void) |
92 | 78 | { |
93 | 78 | tmm_modules[TMM_DECODEAFXDP].name = "DecodeAFXDP"; |
94 | 78 | tmm_modules[TMM_DECODEAFXDP].ThreadInit = NoAFXDPSupportExit; |
95 | 78 | tmm_modules[TMM_DECODEAFXDP].Func = NULL; |
96 | 78 | tmm_modules[TMM_DECODEAFXDP].ThreadExitPrintStats = NULL; |
97 | 78 | tmm_modules[TMM_DECODEAFXDP].ThreadDeinit = NULL; |
98 | 78 | tmm_modules[TMM_DECODEAFXDP].cap_flags = 0; |
99 | 78 | tmm_modules[TMM_DECODEAFXDP].flags = TM_FLAG_DECODE_TM; |
100 | 78 | } |
101 | | |
102 | | /** |
103 | | * \brief this function prints an error message and exits. |
104 | | */ |
105 | | TmEcode NoAFXDPSupportExit(ThreadVars *tv, const void *initdata, void **data) |
106 | 0 | { |
107 | 0 | SCLogError("Error creating thread %s: you do not have " |
108 | 0 | "support for AF_XDP enabled, on Linux host please recompile " |
109 | 0 | "with --enable-af-xdp", |
110 | 0 | tv->name); |
111 | | exit(EXIT_FAILURE); |
112 | 0 | } |
113 | | |
114 | | #else /* We have AF_XDP support */ |
115 | | |
116 | | #define POLL_TIMEOUT 100 |
117 | | #define NUM_FRAMES_PROD XSK_RING_PROD__DEFAULT_NUM_DESCS |
118 | | #define NUM_FRAMES_CONS XSK_RING_CONS__DEFAULT_NUM_DESCS |
119 | | #define NUM_FRAMES NUM_FRAMES_PROD |
120 | | #define FRAME_SIZE XSK_UMEM__DEFAULT_FRAME_SIZE |
121 | | #define MEM_BYTES (NUM_FRAMES * FRAME_SIZE * 2) |
122 | | #define RECONNECT_TIMEOUT 500000 |
123 | | |
124 | | /* Interface state */ |
125 | | enum state { AFXDP_STATE_DOWN, AFXDP_STATE_UP }; |
126 | | |
127 | | struct XskInitProtect { |
128 | | SCMutex queue_protect; |
129 | | SC_ATOMIC_DECLARE(uint8_t, queue_num); |
130 | | } xsk_protect; |
131 | | |
132 | | struct UmemInfo { |
133 | | void *buf; |
134 | | struct xsk_umem *umem; |
135 | | struct xsk_ring_prod fq; |
136 | | struct xsk_ring_cons cq; |
137 | | struct xsk_umem_config cfg; |
138 | | int mmap_alignment_flag; |
139 | | }; |
140 | | |
141 | | struct QueueAssignment { |
142 | | uint32_t queue_num; |
143 | | bool assigned; |
144 | | }; |
145 | | |
146 | | struct XskSockInfo { |
147 | | struct xsk_ring_cons rx; |
148 | | struct xsk_ring_prod tx; |
149 | | struct xsk_socket *xsk; |
150 | | |
151 | | /* Queue assignment structure */ |
152 | | struct QueueAssignment queue; |
153 | | |
154 | | /* Configuration items */ |
155 | | struct xsk_socket_config cfg; |
156 | | bool enable_busy_poll; |
157 | | uint32_t busy_poll_time; |
158 | | uint32_t busy_poll_budget; |
159 | | |
160 | | struct pollfd fd; |
161 | | }; |
162 | | |
163 | | /** |
164 | | * \brief Structure to hold thread specific variables. |
165 | | */ |
166 | | typedef struct AFXDPThreadVars_ { |
167 | | ThreadVars *tv; |
168 | | TmSlot *slot; |
169 | | LiveDevice *livedev; |
170 | | |
171 | | /* thread specific socket */ |
172 | | int promisc; |
173 | | int threads; |
174 | | |
175 | | char iface[AFXDP_IFACE_NAME_LENGTH]; |
176 | | uint32_t ifindex; |
177 | | |
178 | | /* AF_XDP structure */ |
179 | | struct UmemInfo umem; |
180 | | struct XskSockInfo xsk; |
181 | | uint32_t gro_flush_timeout; |
182 | | uint32_t napi_defer_hard_irqs; |
183 | | uint32_t prog_id; |
184 | | |
185 | | /* Handle state */ |
186 | | uint8_t afxdp_state; |
187 | | |
188 | | /* Stats parameters */ |
189 | | uint64_t pkts; |
190 | | uint64_t bytes; |
191 | | uint16_t capture_afxdp_packets; |
192 | | uint16_t capture_kernel_drops; |
193 | | uint16_t capture_afxdp_poll; |
194 | | uint16_t capture_afxdp_poll_timeout; |
195 | | uint16_t capture_afxdp_poll_failed; |
196 | | uint16_t capture_afxdp_empty_reads; |
197 | | uint16_t capture_afxdp_failed_reads; |
198 | | uint16_t capture_afxdp_acquire_pkt_failed; |
199 | | } AFXDPThreadVars; |
200 | | |
201 | | static TmEcode ReceiveAFXDPThreadInit(ThreadVars *, const void *, void **); |
202 | | static void ReceiveAFXDPThreadExitStats(ThreadVars *, void *); |
203 | | static TmEcode ReceiveAFXDPThreadDeinit(ThreadVars *, void *); |
204 | | static TmEcode ReceiveAFXDPLoop(ThreadVars *tv, void *data, void *slot); |
205 | | |
206 | | static TmEcode DecodeAFXDPThreadInit(ThreadVars *, const void *, void **); |
207 | | static TmEcode DecodeAFXDPThreadDeinit(ThreadVars *tv, void *data); |
208 | | static TmEcode DecodeAFXDP(ThreadVars *, Packet *, void *); |
209 | | |
210 | | /** |
211 | | * \brief Registration Function for RecieveAFXDP. |
212 | | * \todo Unit tests are needed for this module. |
213 | | */ |
214 | | void TmModuleReceiveAFXDPRegister(void) |
215 | | { |
216 | | tmm_modules[TMM_RECEIVEAFXDP].name = "ReceiveAFXDP"; |
217 | | tmm_modules[TMM_RECEIVEAFXDP].ThreadInit = ReceiveAFXDPThreadInit; |
218 | | tmm_modules[TMM_RECEIVEAFXDP].Func = NULL; |
219 | | tmm_modules[TMM_RECEIVEAFXDP].PktAcqLoop = ReceiveAFXDPLoop; |
220 | | tmm_modules[TMM_RECEIVEAFXDP].PktAcqBreakLoop = NULL; |
221 | | tmm_modules[TMM_RECEIVEAFXDP].ThreadExitPrintStats = ReceiveAFXDPThreadExitStats; |
222 | | tmm_modules[TMM_RECEIVEAFXDP].ThreadDeinit = ReceiveAFXDPThreadDeinit; |
223 | | tmm_modules[TMM_RECEIVEAFXDP].cap_flags = SC_CAP_NET_RAW; |
224 | | tmm_modules[TMM_RECEIVEAFXDP].flags = TM_FLAG_RECEIVE_TM; |
225 | | } |
226 | | |
227 | | /** |
228 | | * \brief Registration Function for DecodeAFXDP. |
229 | | * \todo Unit tests are needed for this module. |
230 | | */ |
231 | | void TmModuleDecodeAFXDPRegister(void) |
232 | | { |
233 | | tmm_modules[TMM_DECODEAFXDP].name = "DecodeAFXDP"; |
234 | | tmm_modules[TMM_DECODEAFXDP].ThreadInit = DecodeAFXDPThreadInit; |
235 | | tmm_modules[TMM_DECODEAFXDP].Func = DecodeAFXDP; |
236 | | tmm_modules[TMM_DECODEAFXDP].ThreadExitPrintStats = NULL; |
237 | | tmm_modules[TMM_DECODEAFXDP].ThreadDeinit = DecodeAFXDPThreadDeinit; |
238 | | tmm_modules[TMM_DECODEAFXDP].cap_flags = 0; |
239 | | tmm_modules[TMM_DECODEAFXDP].flags = TM_FLAG_DECODE_TM; |
240 | | } |
241 | | |
242 | | static inline void AFXDPDumpCounters(AFXDPThreadVars *ptv) |
243 | | { |
244 | | struct xdp_statistics stats; |
245 | | socklen_t len = sizeof(struct xdp_statistics); |
246 | | int fd = xsk_socket__fd(ptv->xsk.xsk); |
247 | | |
248 | | if (getsockopt(fd, SOL_XDP, XDP_STATISTICS, &stats, &len) >= 0) { |
249 | | uint64_t rx_dropped = stats.rx_dropped + stats.rx_invalid_descs + stats.rx_ring_full; |
250 | | |
251 | | StatsAddUI64(ptv->tv, ptv->capture_kernel_drops, |
252 | | rx_dropped - StatsGetLocalCounterValue(ptv->tv, ptv->capture_kernel_drops)); |
253 | | StatsAddUI64(ptv->tv, ptv->capture_afxdp_packets, ptv->pkts); |
254 | | |
255 | | (void)SC_ATOMIC_SET(ptv->livedev->drop, rx_dropped); |
256 | | (void)SC_ATOMIC_ADD(ptv->livedev->pkts, ptv->pkts); |
257 | | |
258 | | SCLogDebug("(%s) Kernel: Packets %" PRIu64 ", bytes %" PRIu64 ", dropped %" PRIu64 "", |
259 | | ptv->tv->name, StatsGetLocalCounterValue(ptv->tv, ptv->capture_afxdp_packets), |
260 | | ptv->bytes, StatsGetLocalCounterValue(ptv->tv, ptv->capture_kernel_drops)); |
261 | | |
262 | | ptv->pkts = 0; |
263 | | } |
264 | | } |
265 | | |
266 | | /** |
267 | | * \brief Init function for socket creation. |
268 | | * |
269 | | * Mutex used to synchronise initialisation - each socket opens a |
270 | | * different queue. The specific order in which each queue is |
271 | | * opened is not important, but it is vital the queue_num's |
272 | | * are different. |
273 | | * |
274 | | * \param tv pointer to ThreadVars |
275 | | */ |
276 | | TmEcode AFXDPQueueProtectionInit(void) |
277 | | { |
278 | | SCEnter(); |
279 | | |
280 | | SCMutexInit(&xsk_protect.queue_protect, NULL); |
281 | | SC_ATOMIC_SET(xsk_protect.queue_num, 0); |
282 | | SCReturnInt(TM_ECODE_OK); |
283 | | } |
284 | | |
285 | | static TmEcode AFXDPAssignQueueID(AFXDPThreadVars *ptv) |
286 | | { |
287 | | if (!ptv->xsk.queue.assigned) { |
288 | | ptv->xsk.queue.queue_num = SC_ATOMIC_GET(xsk_protect.queue_num); |
289 | | SC_ATOMIC_ADD(xsk_protect.queue_num, 1); |
290 | | |
291 | | /* Queue only needs assigned once, on startup */ |
292 | | ptv->xsk.queue.assigned = true; |
293 | | } |
294 | | SCReturnInt(TM_ECODE_OK); |
295 | | } |
296 | | |
297 | | static void AFXDPAllThreadsRunning(AFXDPThreadVars *ptv) |
298 | | { |
299 | | SCMutexLock(&xsk_protect.queue_protect); |
300 | | if ((ptv->threads - 1) == (int)ptv->xsk.queue.queue_num) { |
301 | | SCLogDebug("All AF_XDP capture threads are running."); |
302 | | } |
303 | | SCMutexUnlock(&xsk_protect.queue_protect); |
304 | | } |
305 | | |
306 | | static TmEcode AcquireBuffer(AFXDPThreadVars *ptv) |
307 | | { |
308 | | int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | ptv->umem.mmap_alignment_flag; |
309 | | ptv->umem.buf = mmap(NULL, MEM_BYTES, PROT_READ | PROT_WRITE, mmap_flags, -1, 0); |
310 | | |
311 | | if (ptv->umem.buf == MAP_FAILED) { |
312 | | SCLogError("mmap: failed to acquire memory"); |
313 | | SCReturnInt(TM_ECODE_FAILED); |
314 | | } |
315 | | |
316 | | SCReturnInt(TM_ECODE_OK); |
317 | | } |
318 | | |
319 | | static TmEcode ConfigureXSKUmem(AFXDPThreadVars *ptv) |
320 | | { |
321 | | if (xsk_umem__create(&ptv->umem.umem, ptv->umem.buf, MEM_BYTES, &ptv->umem.fq, &ptv->umem.cq, |
322 | | &ptv->umem.cfg)) { |
323 | | SCLogError("failed to create umem: %s", strerror(errno)); |
324 | | SCReturnInt(TM_ECODE_FAILED); |
325 | | } |
326 | | |
327 | | SCReturnInt(TM_ECODE_OK); |
328 | | } |
329 | | |
330 | | static TmEcode InitFillRing(AFXDPThreadVars *ptv, const uint32_t cnt) |
331 | | { |
332 | | uint32_t idx_fq = 0; |
333 | | |
334 | | uint32_t ret = xsk_ring_prod__reserve(&ptv->umem.fq, cnt, &idx_fq); |
335 | | if (ret != cnt) { |
336 | | SCLogError("Failed to initialise the fill ring."); |
337 | | SCReturnInt(TM_ECODE_FAILED); |
338 | | } |
339 | | |
340 | | for (uint32_t i = 0; i < cnt; i++) { |
341 | | *xsk_ring_prod__fill_addr(&ptv->umem.fq, idx_fq++) = i * FRAME_SIZE; |
342 | | } |
343 | | |
344 | | xsk_ring_prod__submit(&ptv->umem.fq, cnt); |
345 | | SCReturnInt(TM_ECODE_OK); |
346 | | } |
347 | | |
348 | | /** |
349 | | * \brief Linux knobs are tuned to enable a NAPI polling context |
350 | | * |
351 | | * \param tv pointer to AFXDPThreadVars |
352 | | */ |
353 | | static TmEcode WriteLinuxTunables(AFXDPThreadVars *ptv) |
354 | | { |
355 | | char fname[SYSFS_MAX_FILENAME_SIZE]; |
356 | | |
357 | | if (snprintf(fname, SYSFS_MAX_FILENAME_SIZE, "class/net/%s/gro_flush_timeout", ptv->iface) < |
358 | | 0) { |
359 | | SCReturnInt(TM_ECODE_FAILED); |
360 | | } |
361 | | |
362 | | if (SysFsWriteValue(fname, ptv->gro_flush_timeout) != TM_ECODE_OK) { |
363 | | SCReturnInt(TM_ECODE_FAILED); |
364 | | } |
365 | | |
366 | | if (snprintf(fname, SYSFS_MAX_FILENAME_SIZE, "class/net/%s/napi_defer_hard_irqs", ptv->iface) < |
367 | | 0) { |
368 | | SCReturnInt(TM_ECODE_FAILED); |
369 | | } |
370 | | |
371 | | if (SysFsWriteValue(fname, ptv->napi_defer_hard_irqs) != TM_ECODE_OK) { |
372 | | SCReturnInt(TM_ECODE_FAILED); |
373 | | } |
374 | | |
375 | | SCReturnInt(TM_ECODE_OK); |
376 | | } |
377 | | |
378 | | static TmEcode ConfigureBusyPolling(AFXDPThreadVars *ptv) |
379 | | { |
380 | | if (!ptv->xsk.enable_busy_poll) { |
381 | | SCReturnInt(TM_ECODE_OK); |
382 | | } |
383 | | |
384 | | /* Kernel version must be >= 5.11 to avail of SO_PREFER_BUSY_POLL |
385 | | * see linux commit: 7fd3253a7de6a317a0683f83739479fb880bffc8 |
386 | | */ |
387 | | if (!SCKernelVersionIsAtLeast(5, 11)) { |
388 | | SCLogWarning("Kernel version older than required: v5.11," |
389 | | " upgrade kernel version to use 'enable-busy-poll' option."); |
390 | | SCReturnInt(TM_ECODE_FAILED); |
391 | | } |
392 | | |
393 | | #if defined SO_PREFER_BUSY_POLL && defined SO_BUSY_POLL && defined SO_BUSY_POLL_BUDGET |
394 | | const int fd = xsk_socket__fd(ptv->xsk.xsk); |
395 | | int sock_opt = 1; |
396 | | |
397 | | if (WriteLinuxTunables(ptv) != TM_ECODE_OK) { |
398 | | SCReturnInt(TM_ECODE_FAILED); |
399 | | } |
400 | | |
401 | | if (setsockopt(fd, SOL_SOCKET, SO_PREFER_BUSY_POLL, (void *)&sock_opt, sizeof(sock_opt)) < 0) { |
402 | | SCReturnInt(TM_ECODE_FAILED); |
403 | | } |
404 | | |
405 | | sock_opt = ptv->xsk.busy_poll_time; |
406 | | if (setsockopt(fd, SOL_SOCKET, SO_BUSY_POLL, (void *)&sock_opt, sizeof(sock_opt)) < 0) { |
407 | | SCReturnInt(TM_ECODE_FAILED); |
408 | | } |
409 | | |
410 | | sock_opt = ptv->xsk.busy_poll_budget; |
411 | | if (setsockopt(fd, SOL_SOCKET, SO_BUSY_POLL_BUDGET, (void *)&sock_opt, sizeof(sock_opt)) < 0) { |
412 | | SCReturnInt(TM_ECODE_FAILED); |
413 | | } |
414 | | |
415 | | SCReturnInt(TM_ECODE_OK); |
416 | | #else |
417 | | SCLogWarning( |
418 | | "Kernel does not support busy poll, upgrade kernel or disable \"enable-busy-poll\"."); |
419 | | SCReturnInt(TM_ECODE_FAILED); |
420 | | #endif |
421 | | } |
422 | | |
423 | | static void AFXDPSwitchState(AFXDPThreadVars *ptv, int state) |
424 | | { |
425 | | ptv->afxdp_state = state; |
426 | | } |
427 | | |
428 | | static TmEcode OpenXSKSocket(AFXDPThreadVars *ptv) |
429 | | { |
430 | | int ret; |
431 | | |
432 | | SCMutexLock(&xsk_protect.queue_protect); |
433 | | |
434 | | if (AFXDPAssignQueueID(ptv) != TM_ECODE_OK) { |
435 | | SCLogError("Failed to assign queue ID"); |
436 | | SCReturnInt(TM_ECODE_FAILED); |
437 | | } |
438 | | |
439 | | if ((ret = xsk_socket__create(&ptv->xsk.xsk, ptv->livedev->dev, ptv->xsk.queue.queue_num, |
440 | | ptv->umem.umem, &ptv->xsk.rx, &ptv->xsk.tx, &ptv->xsk.cfg))) { |
441 | | SCLogError("Failed to create socket: %s", strerror(-ret)); |
442 | | SCMutexUnlock(&xsk_protect.queue_protect); |
443 | | SCReturnInt(TM_ECODE_FAILED); |
444 | | } |
445 | | SCLogDebug("bind to %s on queue %u", ptv->iface, ptv->xsk.queue.queue_num); |
446 | | |
447 | | /* For polling and socket options */ |
448 | | ptv->xsk.fd.fd = xsk_socket__fd(ptv->xsk.xsk); |
449 | | ptv->xsk.fd.events = POLLIN; |
450 | | |
451 | | /* Set state */ |
452 | | AFXDPSwitchState(ptv, AFXDP_STATE_UP); |
453 | | |
454 | | SCMutexUnlock(&xsk_protect.queue_protect); |
455 | | SCReturnInt(TM_ECODE_OK); |
456 | | } |
457 | | |
458 | | static void AFXDPCloseSocket(AFXDPThreadVars *ptv) |
459 | | { |
460 | | if (ptv->xsk.xsk) { |
461 | | xsk_socket__delete(ptv->xsk.xsk); |
462 | | ptv->xsk.xsk = NULL; |
463 | | } |
464 | | |
465 | | if (ptv->umem.umem) { |
466 | | xsk_umem__delete(ptv->umem.umem); |
467 | | ptv->umem.umem = NULL; |
468 | | } |
469 | | |
470 | | memset(&ptv->umem.fq, 0, sizeof(struct xsk_ring_prod)); |
471 | | memset(&ptv->umem.cq, 0, sizeof(struct xsk_ring_cons)); |
472 | | } |
473 | | |
474 | | static TmEcode AFXDPSocketCreation(AFXDPThreadVars *ptv) |
475 | | { |
476 | | if (ConfigureXSKUmem(ptv) != TM_ECODE_OK) { |
477 | | SCReturnInt(TM_ECODE_FAILED); |
478 | | } |
479 | | |
480 | | if (InitFillRing(ptv, NUM_FRAMES * 2) != TM_ECODE_OK) { |
481 | | SCReturnInt(TM_ECODE_FAILED); |
482 | | } |
483 | | |
484 | | /* Open AF_XDP socket */ |
485 | | if (OpenXSKSocket(ptv) != TM_ECODE_OK) { |
486 | | SCReturnInt(TM_ECODE_FAILED); |
487 | | } |
488 | | |
489 | | if (ConfigureBusyPolling(ptv) != TM_ECODE_OK) { |
490 | | SCLogWarning("Failed to configure busy polling" |
491 | | " performance may be reduced."); |
492 | | } |
493 | | |
494 | | /* Has the eBPF program successfully bound? */ |
495 | | #ifdef HAVE_BPF_XDP_QUERY_ID |
496 | | if (bpf_xdp_query_id(ptv->ifindex, ptv->xsk.cfg.xdp_flags, &ptv->prog_id)) { |
497 | | SCLogError("Failed to attach eBPF program to interface: %s", ptv->livedev->dev); |
498 | | SCReturnInt(TM_ECODE_FAILED); |
499 | | } |
500 | | #else |
501 | | if (bpf_get_link_xdp_id(ptv->ifindex, &ptv->prog_id, ptv->xsk.cfg.xdp_flags)) { |
502 | | SCLogError("Failed to attach eBPF program to interface: %s", ptv->livedev->dev); |
503 | | SCReturnInt(TM_ECODE_FAILED); |
504 | | } |
505 | | #endif |
506 | | |
507 | | SCReturnInt(TM_ECODE_OK); |
508 | | } |
509 | | |
510 | | /** |
511 | | * \brief Try to reopen AF_XDP socket |
512 | | * |
513 | | * \retval: TM_ECODE_OK in case of success |
514 | | * TM_ECODE_FAILED if error occurs or a condition is not met. |
515 | | */ |
516 | | static TmEcode AFXDPTryReopen(AFXDPThreadVars *ptv) |
517 | | { |
518 | | AFXDPCloseSocket(ptv); |
519 | | usleep(RECONNECT_TIMEOUT); |
520 | | |
521 | | int if_flags = GetIfaceFlags(ptv->iface); |
522 | | if (if_flags == -1) { |
523 | | SCLogDebug("Couldn't get flags for interface '%s'", ptv->iface); |
524 | | goto sock_err; |
525 | | } else if ((if_flags & (IFF_UP | IFF_RUNNING)) == 0) { |
526 | | SCLogDebug("Interface '%s' is down", ptv->iface); |
527 | | goto sock_err; |
528 | | } |
529 | | |
530 | | if (AFXDPSocketCreation(ptv) != TM_ECODE_OK) { |
531 | | SCReturnInt(TM_ECODE_FAILED); |
532 | | } |
533 | | |
534 | | SCLogInfo("Interface '%s' is back", ptv->iface); |
535 | | SCReturnInt(TM_ECODE_OK); |
536 | | |
537 | | sock_err: |
538 | | SCReturnInt(TM_ECODE_FAILED); |
539 | | } |
540 | | |
541 | | /** |
542 | | * \brief Write packet entry to the fill ring, freeing |
543 | | * this slot for re/fill with inbound packet descriptor |
544 | | * \param pointer to Packet |
545 | | * \retval: None |
546 | | */ |
547 | | static void AFXDPReleasePacket(Packet *p) |
548 | | { |
549 | | *xsk_ring_prod__fill_addr((struct xsk_ring_prod *)p->afxdp_v.fq, p->afxdp_v.fq_idx) = |
550 | | p->afxdp_v.orig; |
551 | | |
552 | | PacketFreeOrRelease(p); |
553 | | } |
554 | | |
555 | | static inline int DumpStatsEverySecond(AFXDPThreadVars *ptv, time_t *last_dump) |
556 | | { |
557 | | int stats_dumped = 0; |
558 | | time_t current_time = time(NULL); |
559 | | |
560 | | if (current_time != *last_dump) { |
561 | | AFXDPDumpCounters(ptv); |
562 | | *last_dump = current_time; |
563 | | stats_dumped = 1; |
564 | | } |
565 | | |
566 | | StatsSyncCountersIfSignalled(ptv->tv); |
567 | | |
568 | | return stats_dumped; |
569 | | } |
570 | | |
571 | | static inline ssize_t WakeupSocket(void *data) |
572 | | { |
573 | | ssize_t res = 0; |
574 | | AFXDPThreadVars *ptv = (AFXDPThreadVars *)data; |
575 | | |
576 | | /* Assuming kernel >= 5.11 in use if xdp_busy_poll is enabled */ |
577 | | if (ptv->xsk.enable_busy_poll || xsk_ring_prod__needs_wakeup(&ptv->umem.fq)) { |
578 | | // cppcheck-suppress nullPointer |
579 | | res = recvfrom(xsk_socket__fd(ptv->xsk.xsk), NULL, 0, MSG_DONTWAIT, NULL, NULL); |
580 | | } |
581 | | |
582 | | return res; |
583 | | } |
584 | | |
585 | | /** |
586 | | * \brief Init function for ReceiveAFXDP. |
587 | | * |
588 | | * \param tv pointer to ThreadVars |
589 | | * \param initdata pointer to the interface passed from the user |
590 | | * \param data pointer gets populated with AFPThreadVars |
591 | | * |
592 | | * \todo Create a general AFP setup function. |
593 | | */ |
594 | | static TmEcode ReceiveAFXDPThreadInit(ThreadVars *tv, const void *initdata, void **data) |
595 | | { |
596 | | SCEnter(); |
597 | | |
598 | | AFXDPIfaceConfig *afxdpconfig = (AFXDPIfaceConfig *)initdata; |
599 | | |
600 | | if (initdata == NULL) { |
601 | | SCLogError("initdata == NULL"); |
602 | | SCReturnInt(TM_ECODE_FAILED); |
603 | | } |
604 | | |
605 | | AFXDPThreadVars *ptv = SCCalloc(1, sizeof(AFXDPThreadVars)); |
606 | | if (unlikely(ptv == NULL)) { |
607 | | afxdpconfig->DerefFunc(afxdpconfig); |
608 | | SCReturnInt(TM_ECODE_FAILED); |
609 | | } |
610 | | |
611 | | ptv->tv = tv; |
612 | | |
613 | | strlcpy(ptv->iface, afxdpconfig->iface, AFXDP_IFACE_NAME_LENGTH); |
614 | | ptv->iface[AFXDP_IFACE_NAME_LENGTH - 1] = '\0'; |
615 | | ptv->ifindex = if_nametoindex(ptv->iface); |
616 | | |
617 | | ptv->livedev = LiveGetDevice(ptv->iface); |
618 | | if (ptv->livedev == NULL) { |
619 | | SCLogError("Unable to find Live device"); |
620 | | SCFree(ptv); |
621 | | SCReturnInt(TM_ECODE_FAILED); |
622 | | } |
623 | | |
624 | | ptv->promisc = afxdpconfig->promisc; |
625 | | if (ptv->promisc != 0) { |
626 | | /* Force promiscuous mode */ |
627 | | if (SetIfaceFlags(ptv->iface, IFF_PROMISC | IFF_UP) != 0) { |
628 | | SCLogError("Failed to switch interface (%s) to promiscuous, error %s", ptv->iface, |
629 | | strerror(errno)); |
630 | | SCFree(ptv); |
631 | | SCReturnInt(TM_ECODE_FAILED); |
632 | | } |
633 | | } |
634 | | |
635 | | ptv->threads = afxdpconfig->threads; |
636 | | |
637 | | /* Socket configuration */ |
638 | | ptv->xsk.cfg.rx_size = NUM_FRAMES_CONS; |
639 | | ptv->xsk.cfg.tx_size = NUM_FRAMES_PROD; |
640 | | ptv->xsk.cfg.xdp_flags = afxdpconfig->mode; |
641 | | ptv->xsk.cfg.bind_flags = afxdpconfig->bind_flags; |
642 | | |
643 | | /* UMEM configuration */ |
644 | | ptv->umem.cfg.fill_size = NUM_FRAMES_PROD * 2; |
645 | | ptv->umem.cfg.comp_size = NUM_FRAMES_CONS; |
646 | | ptv->umem.cfg.frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE; |
647 | | ptv->umem.cfg.frame_headroom = XSK_UMEM__DEFAULT_FRAME_HEADROOM; |
648 | | ptv->umem.cfg.flags = afxdpconfig->mem_alignment; |
649 | | |
650 | | /* Use hugepages if unaligned chunk mode */ |
651 | | if (ptv->umem.cfg.flags == XDP_UMEM_UNALIGNED_CHUNK_FLAG) { |
652 | | ptv->umem.mmap_alignment_flag = MAP_HUGETLB; |
653 | | } |
654 | | |
655 | | /* Busy polling configuration */ |
656 | | ptv->xsk.enable_busy_poll = afxdpconfig->enable_busy_poll; |
657 | | ptv->xsk.busy_poll_budget = afxdpconfig->busy_poll_budget; |
658 | | ptv->xsk.busy_poll_time = afxdpconfig->busy_poll_time; |
659 | | ptv->gro_flush_timeout = afxdpconfig->gro_flush_timeout; |
660 | | ptv->napi_defer_hard_irqs = afxdpconfig->napi_defer_hard_irqs; |
661 | | |
662 | | /* Stats registration */ |
663 | | ptv->capture_afxdp_packets = StatsRegisterCounter("capture.afxdp_packets", ptv->tv); |
664 | | ptv->capture_kernel_drops = StatsRegisterCounter("capture.kernel_drops", ptv->tv); |
665 | | ptv->capture_afxdp_poll = StatsRegisterCounter("capture.afxdp.poll", ptv->tv); |
666 | | ptv->capture_afxdp_poll_timeout = StatsRegisterCounter("capture.afxdp.poll_timeout", ptv->tv); |
667 | | ptv->capture_afxdp_poll_failed = StatsRegisterCounter("capture.afxdp.poll_failed", ptv->tv); |
668 | | ptv->capture_afxdp_empty_reads = StatsRegisterCounter("capture.afxdp.empty_reads", ptv->tv); |
669 | | ptv->capture_afxdp_failed_reads = StatsRegisterCounter("capture.afxdp.failed_reads", ptv->tv); |
670 | | ptv->capture_afxdp_acquire_pkt_failed = |
671 | | StatsRegisterCounter("capture.afxdp.acquire_pkt_failed", ptv->tv); |
672 | | |
673 | | /* Reserve memory for umem */ |
674 | | if (AcquireBuffer(ptv) != TM_ECODE_OK) { |
675 | | SCFree(ptv); |
676 | | SCReturnInt(TM_ECODE_FAILED); |
677 | | } |
678 | | |
679 | | if (AFXDPSocketCreation(ptv) != TM_ECODE_OK) { |
680 | | ReceiveAFXDPThreadDeinit(tv, ptv); |
681 | | SCReturnInt(TM_ECODE_FAILED); |
682 | | } |
683 | | |
684 | | *data = (void *)ptv; |
685 | | afxdpconfig->DerefFunc(afxdpconfig); |
686 | | SCReturnInt(TM_ECODE_OK); |
687 | | } |
688 | | |
689 | | /** |
690 | | * \brief Main AF_XDP reading Loop function |
691 | | */ |
692 | | static TmEcode ReceiveAFXDPLoop(ThreadVars *tv, void *data, void *slot) |
693 | | { |
694 | | SCEnter(); |
695 | | |
696 | | Packet *p; |
697 | | time_t last_dump = 0; |
698 | | struct timeval ts; |
699 | | uint32_t idx_rx = 0, idx_fq = 0, rcvd; |
700 | | int r; |
701 | | AFXDPThreadVars *ptv = (AFXDPThreadVars *)data; |
702 | | TmSlot *s = (TmSlot *)slot; |
703 | | |
704 | | ptv->slot = s->slot_next; |
705 | | |
706 | | AFXDPAllThreadsRunning(ptv); |
707 | | |
708 | | // Indicate that the thread is actually running its application level code (i.e., it can poll |
709 | | // packets) |
710 | | TmThreadsSetFlag(tv, THV_RUNNING); |
711 | | |
712 | | PacketPoolWait(); |
713 | | while (1) { |
714 | | /* Start by checking the state of our interface */ |
715 | | if (unlikely(ptv->afxdp_state == AFXDP_STATE_DOWN)) { |
716 | | do { |
717 | | usleep(RECONNECT_TIMEOUT); |
718 | | if (unlikely(suricata_ctl_flags != 0)) { |
719 | | break; |
720 | | } |
721 | | r = AFXDPTryReopen(ptv); |
722 | | } while (r != TM_ECODE_OK); |
723 | | } |
724 | | |
725 | | if (unlikely(suricata_ctl_flags != 0)) { |
726 | | SCLogDebug("Stopping Suricata!"); |
727 | | AFXDPDumpCounters(ptv); |
728 | | break; |
729 | | } |
730 | | |
731 | | /* Busy polling is not set, using poll() to maintain (relatively) decent |
732 | | * performance. xdp_busy_poll must be disabled for kernels < 5.11 |
733 | | */ |
734 | | if (!ptv->xsk.enable_busy_poll) { |
735 | | StatsIncr(ptv->tv, ptv->capture_afxdp_poll); |
736 | | |
737 | | r = poll(&ptv->xsk.fd, 1, POLL_TIMEOUT); |
738 | | |
739 | | /* Report poll results */ |
740 | | if (r <= 0) { |
741 | | if (r == 0) { |
742 | | StatsIncr(ptv->tv, ptv->capture_afxdp_poll_timeout); |
743 | | } else if (r < 0) { |
744 | | StatsIncr(ptv->tv, ptv->capture_afxdp_poll_failed); |
745 | | SCLogWarning("poll failed with retval %d", r); |
746 | | AFXDPSwitchState(ptv, AFXDP_STATE_DOWN); |
747 | | } |
748 | | |
749 | | DumpStatsEverySecond(ptv, &last_dump); |
750 | | continue; |
751 | | } |
752 | | } |
753 | | |
754 | | rcvd = xsk_ring_cons__peek(&ptv->xsk.rx, ptv->xsk.busy_poll_budget, &idx_rx); |
755 | | if (!rcvd) { |
756 | | StatsIncr(ptv->tv, ptv->capture_afxdp_empty_reads); |
757 | | ssize_t ret = WakeupSocket(ptv); |
758 | | if (ret < 0) { |
759 | | SCLogWarning("recv failed with retval %ld", ret); |
760 | | AFXDPSwitchState(ptv, AFXDP_STATE_DOWN); |
761 | | } |
762 | | DumpStatsEverySecond(ptv, &last_dump); |
763 | | continue; |
764 | | } |
765 | | |
766 | | uint32_t res = xsk_ring_prod__reserve(&ptv->umem.fq, rcvd, &idx_fq); |
767 | | while (res != rcvd) { |
768 | | StatsIncr(ptv->tv, ptv->capture_afxdp_failed_reads); |
769 | | ssize_t ret = WakeupSocket(ptv); |
770 | | if (ret < 0) { |
771 | | SCLogWarning("recv failed with retval %ld", ret); |
772 | | AFXDPSwitchState(ptv, AFXDP_STATE_DOWN); |
773 | | continue; |
774 | | } |
775 | | res = xsk_ring_prod__reserve(&ptv->umem.fq, rcvd, &idx_fq); |
776 | | } |
777 | | |
778 | | gettimeofday(&ts, NULL); |
779 | | ptv->pkts += rcvd; |
780 | | for (uint32_t i = 0; i < rcvd; i++) { |
781 | | p = PacketGetFromQueueOrAlloc(); |
782 | | if (unlikely(p == NULL)) { |
783 | | StatsIncr(ptv->tv, ptv->capture_afxdp_acquire_pkt_failed); |
784 | | continue; |
785 | | } |
786 | | |
787 | | PKT_SET_SRC(p, PKT_SRC_WIRE); |
788 | | p->datalink = LINKTYPE_ETHERNET; |
789 | | p->livedev = ptv->livedev; |
790 | | p->ReleasePacket = AFXDPReleasePacket; |
791 | | p->flags |= PKT_IGNORE_CHECKSUM; |
792 | | |
793 | | p->ts = SCTIME_FROM_TIMEVAL(&ts); |
794 | | |
795 | | uint64_t addr = xsk_ring_cons__rx_desc(&ptv->xsk.rx, idx_rx)->addr; |
796 | | uint32_t len = xsk_ring_cons__rx_desc(&ptv->xsk.rx, idx_rx++)->len; |
797 | | uint64_t orig = xsk_umem__extract_addr(addr); |
798 | | addr = xsk_umem__add_offset_to_addr(addr); |
799 | | |
800 | | uint8_t *pkt_data = xsk_umem__get_data(ptv->umem.buf, addr); |
801 | | |
802 | | ptv->bytes += len; |
803 | | |
804 | | p->afxdp_v.fq_idx = idx_fq++; |
805 | | p->afxdp_v.orig = orig; |
806 | | p->afxdp_v.fq = &ptv->umem.fq; |
807 | | |
808 | | PacketSetData(p, pkt_data, len); |
809 | | |
810 | | if (TmThreadsSlotProcessPkt(ptv->tv, ptv->slot, p) != TM_ECODE_OK) { |
811 | | TmqhOutputPacketpool(ptv->tv, p); |
812 | | SCReturnInt(EXIT_FAILURE); |
813 | | } |
814 | | } |
815 | | |
816 | | xsk_ring_prod__submit(&ptv->umem.fq, rcvd); |
817 | | xsk_ring_cons__release(&ptv->xsk.rx, rcvd); |
818 | | |
819 | | /* Trigger one dump of stats every second */ |
820 | | DumpStatsEverySecond(ptv, &last_dump); |
821 | | } |
822 | | |
823 | | SCReturnInt(TM_ECODE_OK); |
824 | | } |
825 | | |
826 | | /** |
827 | | * \brief function to unload an AF_XDP program |
828 | | * |
829 | | */ |
830 | | static void RunModeAFXDPRemoveProg(char *iface_name) |
831 | | { |
832 | | unsigned int ifindex = if_nametoindex(iface_name); |
833 | | |
834 | | struct xdp_multiprog *progs = xdp_multiprog__get_from_ifindex(ifindex); |
835 | | if (progs == NULL) { |
836 | | return; |
837 | | } |
838 | | enum xdp_attach_mode mode = xdp_multiprog__attach_mode(progs); |
839 | | |
840 | | struct xdp_program *prog = NULL; |
841 | | |
842 | | // loop through the multiprogram struct, removing all the programs |
843 | | for (prog = xdp_multiprog__next_prog(NULL, progs); prog; |
844 | | prog = xdp_multiprog__next_prog(prog, progs)) { |
845 | | int ret = xdp_program__detach(prog, ifindex, mode, 0); |
846 | | if (ret) { |
847 | | SCLogDebug("Error: cannot detatch XDP program: %s\n", strerror(errno)); |
848 | | } |
849 | | } |
850 | | |
851 | | prog = xdp_multiprog__main_prog(progs); |
852 | | if (xdp_program__is_attached(prog, ifindex) != XDP_MODE_UNSPEC) { |
853 | | int ret = xdp_program__detach(prog, ifindex, mode, 0); |
854 | | if (ret) { |
855 | | SCLogDebug("Error: cannot detatch XDP program: %s\n", strerror(errno)); |
856 | | } |
857 | | } |
858 | | } |
859 | | |
860 | | /** |
861 | | * \brief DeInit function closes af-xdp socket at exit. |
862 | | * \param tv pointer to ThreadVars |
863 | | * \param data pointer that gets cast into AFXDPPThreadVars for ptv |
864 | | */ |
865 | | static SCMutex sync_deinit = SCMUTEX_INITIALIZER; |
866 | | |
867 | | static TmEcode ReceiveAFXDPThreadDeinit(ThreadVars *tv, void *data) |
868 | | { |
869 | | AFXDPThreadVars *ptv = (AFXDPThreadVars *)data; |
870 | | |
871 | | /* |
872 | | * If AF_XDP is enabled, the program must be detached before the AF_XDP sockets |
873 | | * are closed to mitigate a bug that causes an IO_PAGEFAULT in linux kernel |
874 | | * version 5.19, unknown as of now what other versions this affects. |
875 | | */ |
876 | | SCMutexLock(&sync_deinit); |
877 | | RunModeAFXDPRemoveProg(ptv->iface); |
878 | | SCMutexUnlock(&sync_deinit); |
879 | | |
880 | | if (ptv->xsk.xsk) { |
881 | | xsk_socket__delete(ptv->xsk.xsk); |
882 | | ptv->xsk.xsk = NULL; |
883 | | } |
884 | | |
885 | | if (ptv->umem.umem) { |
886 | | xsk_umem__delete(ptv->umem.umem); |
887 | | ptv->umem.umem = NULL; |
888 | | } |
889 | | munmap(ptv->umem.buf, MEM_BYTES); |
890 | | |
891 | | SCFree(ptv); |
892 | | SCReturnInt(TM_ECODE_OK); |
893 | | } |
894 | | |
895 | | /** |
896 | | * \brief This function prints stats to the screen at exit. |
897 | | * \param tv pointer to ThreadVars |
898 | | * \param data pointer that gets cast into AFXDPThreadVars for ptv |
899 | | */ |
900 | | static void ReceiveAFXDPThreadExitStats(ThreadVars *tv, void *data) |
901 | | { |
902 | | SCEnter(); |
903 | | AFXDPThreadVars *ptv = (AFXDPThreadVars *)data; |
904 | | |
905 | | AFXDPDumpCounters(ptv); |
906 | | |
907 | | SCLogPerf("(%s) Kernel: Packets %" PRIu64 ", bytes %" PRIu64 ", dropped %" PRIu64 "", tv->name, |
908 | | StatsGetLocalCounterValue(tv, ptv->capture_afxdp_packets), ptv->bytes, |
909 | | StatsGetLocalCounterValue(tv, ptv->capture_kernel_drops)); |
910 | | } |
911 | | |
912 | | /** |
913 | | * \brief This function passes off to link type decoders. |
914 | | * |
915 | | * DecodeAFXDP decodes packets from AF_XDP and passes |
916 | | * them off to the proper link type decoder. |
917 | | * |
918 | | * \param t pointer to ThreadVars |
919 | | * \param p pointer to the current packet |
920 | | * \param data pointer that gets cast into AFXDPThreadVars for ptv |
921 | | */ |
922 | | static TmEcode DecodeAFXDP(ThreadVars *tv, Packet *p, void *data) |
923 | | { |
924 | | SCEnter(); |
925 | | |
926 | | DecodeThreadVars *dtv = (DecodeThreadVars *)data; |
927 | | |
928 | | DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p)); |
929 | | |
930 | | /* update counters */ |
931 | | DecodeUpdatePacketCounters(tv, dtv, p); |
932 | | |
933 | | /* If suri has set vlan during reading, we increase vlan counter */ |
934 | | if (p->vlan_idx) { |
935 | | StatsIncr(tv, dtv->counter_vlan); |
936 | | } |
937 | | |
938 | | /* call the decoder */ |
939 | | DecodeLinkLayer(tv, dtv, p->datalink, p, GET_PKT_DATA(p), GET_PKT_LEN(p)); |
940 | | |
941 | | PacketDecodeFinalize(tv, dtv, p); |
942 | | |
943 | | SCReturnInt(TM_ECODE_OK); |
944 | | } |
945 | | |
946 | | static TmEcode DecodeAFXDPThreadInit(ThreadVars *tv, const void *initdata, void **data) |
947 | | { |
948 | | SCEnter(); |
949 | | DecodeThreadVars *dtv = DecodeThreadVarsAlloc(tv); |
950 | | if (dtv == NULL) |
951 | | SCReturnInt(TM_ECODE_FAILED); |
952 | | |
953 | | DecodeRegisterPerfCounters(dtv, tv); |
954 | | |
955 | | *data = (void *)dtv; |
956 | | |
957 | | SCReturnInt(TM_ECODE_OK); |
958 | | } |
959 | | |
960 | | static TmEcode DecodeAFXDPThreadDeinit(ThreadVars *tv, void *data) |
961 | | { |
962 | | if (data != NULL) |
963 | | DecodeThreadVarsFree(tv, data); |
964 | | SCReturnInt(TM_ECODE_OK); |
965 | | } |
966 | | |
967 | | #endif /* HAVE_AF_XDP */ |
968 | | /* eof */ |
969 | | /** |
970 | | * @} |
971 | | */ |