suricata
util-ebpf.c
Go to the documentation of this file.
1 /* Copyright (C) 2018-2021 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  * eBPF utility
30  *
31  */
32 
33 #define SC_PCAP_DONT_INCLUDE_PCAP_H 1
34 
35 #include "suricata-common.h"
36 #include "flow-bypass.h"
37 
38 #ifdef HAVE_PACKET_EBPF
39 
40 #include <sys/time.h>
41 #include <sys/resource.h>
42 
43 #include "util-ebpf.h"
44 #include "util-affinity.h"
45 #include "util-cpu.h"
46 #include "util-device-private.h"
47 #include "util-host-info.h"
48 
49 #include "device-storage.h"
50 #include "flow-storage.h"
51 #include "flow.h"
52 #include "flow-hash.h"
53 #include "tm-threads.h"
54 
55 #include <bpf/libbpf.h>
56 #include <bpf/bpf.h>
57 #include <net/if.h>
58 
59 #define BPF_MAP_MAX_COUNT 16
60 
61 static SCLiveDevStorageId g_livedev_storage_id = { .id = -1 };
62 static SCFlowStorageId g_flow_storage_id = { .id = -1 };
63 
64 struct bpf_map_item {
65  char iface[IFNAMSIZ];
66  char * name;
67  int fd;
68  uint8_t to_unlink;
69 };
70 
71 struct bpf_maps_info {
72  struct bpf_map_item array[BPF_MAP_MAX_COUNT];
73  int last;
74 };
75 
76 typedef struct BypassedIfaceList_ {
77  LiveDevice *dev;
78  struct BypassedIfaceList_ *next;
79 } BypassedIfaceList;
80 
81 static void BpfMapsInfoFree(void *bpf)
82 {
83  struct bpf_maps_info *bpfinfo = (struct bpf_maps_info *)bpf;
84  int i;
85  for (i = 0; i < bpfinfo->last; i ++) {
86  if (bpfinfo->array[i].name) {
87  if (bpfinfo->array[i].to_unlink) {
88  char pinnedpath[PATH_MAX];
89  int ret = snprintf(pinnedpath, sizeof(pinnedpath),
90  "/sys/fs/bpf/suricata-%s-%s",
91  bpfinfo->array[i].iface,
92  bpfinfo->array[i].name);
93  if (ret > 0) {
94  /* Unlink the pinned entry */
95  ret = unlink(pinnedpath);
96  if (ret == -1) {
97  int error = errno;
99  "Unable to remove %s: %s (%d)", pinnedpath, strerror(error), error);
100  }
101  } else {
102  SCLogWarning("Unable to remove map %s", bpfinfo->array[i].name);
103  }
104  }
105  SCFree(bpfinfo->array[i].name);
106  }
107  }
108  SCFree(bpfinfo);
109 }
110 
111 static void BypassedListFree(void *ifl)
112 {
113  BypassedIfaceList *mifl = (BypassedIfaceList *)ifl;
114  BypassedIfaceList *nifl;
115  while (mifl) {
116  nifl = mifl->next;
117  SCFree(mifl);
118  mifl = nifl;
119  }
120 }
121 
122 void EBPFDeleteKey(int fd, void *key)
123 {
124  int ret = bpf_map_delete_elem(fd, key);
125  if (ret < 0) {
126  SCLogWarning("Unable to delete entry: %s (%d)", strerror(errno), errno);
127  }
128 }
129 
130 static struct bpf_maps_info *EBPFGetBpfMap(const char *iface)
131 {
132  LiveDevice *livedev = LiveGetDevice(iface);
133  if (livedev == NULL)
134  return NULL;
135  void *data = SCLiveDevGetStorageById(livedev, g_livedev_storage_id);
136 
137  return (struct bpf_maps_info *)data;
138 }
139 
140 /**
141  * Get file descriptor of a map in the scope of a interface
142  *
143  * \param iface the interface where the map need to be looked for
144  * \param name the name of the map
145  * \return the file descriptor or -1 in case of error
146  */
147 int EBPFGetMapFDByName(const char *iface, const char *name)
148 {
149  int i;
150 
151  if (iface == NULL || name == NULL)
152  return -1;
153  struct bpf_maps_info *bpf_maps = EBPFGetBpfMap(iface);
154  if (bpf_maps == NULL)
155  return -1;
156 
157  for (i = 0; i < BPF_MAP_MAX_COUNT; i++) {
158  if (!bpf_maps->array[i].name)
159  continue;
160  if (!strcmp(bpf_maps->array[i].name, name)) {
161  SCLogDebug("Got fd %d for eBPF map '%s'", bpf_maps->array[i].fd, name);
162  return bpf_maps->array[i].fd;
163  }
164  }
165 
166  return -1;
167 }
168 
169 static int EBPFLoadPinnedMapsFile(LiveDevice *livedev, const char *file)
170 {
171  char pinnedpath[1024];
172  snprintf(pinnedpath, sizeof(pinnedpath),
173  "/sys/fs/bpf/suricata-%s-%s",
174  livedev->dev,
175  file);
176 
177  return bpf_obj_get(pinnedpath);
178 }
179 
180 static int EBPFLoadPinnedMaps(LiveDevice *livedev, struct ebpf_timeout_config *config)
181 {
182  int fd_v4 = -1, fd_v6 = -1;
183 
184  /* First try to load the eBPF check map and return if found */
185  if (config->pinned_maps_name) {
186  int ret = EBPFLoadPinnedMapsFile(livedev, config->pinned_maps_name);
187  if (ret == 0) {
188  /* pinned maps found, let's just exit as XDP filter is in place */
189  return ret;
190  }
191  }
192 
193  if (config->mode == AFP_MODE_XDP_BYPASS) {
194  /* Get flow v4 table */
195  fd_v4 = EBPFLoadPinnedMapsFile(livedev, "flow_table_v4");
196  if (fd_v4 < 0) {
197  return fd_v4;
198  }
199 
200  /* Get flow v6 table */
201  fd_v6 = EBPFLoadPinnedMapsFile(livedev, "flow_table_v6");
202  if (fd_v6 < 0) {
203  SCLogWarning("Found a flow_table_v4 map but no flow_table_v6 map");
204  return fd_v6;
205  }
206  }
207 
208  struct bpf_maps_info *bpf_map_data = SCCalloc(1, sizeof(*bpf_map_data));
209  if (bpf_map_data == NULL) {
210  SCLogError("Can't allocate bpf map array");
211  return -1;
212  }
213 
214  if (config->mode == AFP_MODE_XDP_BYPASS) {
215  bpf_map_data->array[0].fd = fd_v4;
216  bpf_map_data->array[0].name = SCStrdup("flow_table_v4");
217  if (bpf_map_data->array[0].name == NULL) {
218  goto alloc_error;
219  }
220  bpf_map_data->array[1].fd = fd_v6;
221  bpf_map_data->array[1].name = SCStrdup("flow_table_v6");
222  if (bpf_map_data->array[1].name == NULL) {
223  goto alloc_error;
224  }
225  bpf_map_data->last = 2;
226  } else {
227  bpf_map_data->last = 0;
228  }
229 
230  /* Load other known maps: cpu_map, cpus_available, tx_peer, tx_peer_int */
231  int fd = EBPFLoadPinnedMapsFile(livedev, "cpu_map");
232  if (fd >= 0) {
233  bpf_map_data->array[bpf_map_data->last].fd = fd;
234  bpf_map_data->array[bpf_map_data->last].name = SCStrdup("cpu_map");
235  if (bpf_map_data->array[bpf_map_data->last].name == NULL) {
236  goto alloc_error;
237  }
238  bpf_map_data->last++;
239  }
240  fd = EBPFLoadPinnedMapsFile(livedev, "cpus_available");
241  if (fd >= 0) {
242  bpf_map_data->array[bpf_map_data->last].fd = fd;
243  bpf_map_data->array[bpf_map_data->last].name = SCStrdup("cpus_available");
244  if (bpf_map_data->array[bpf_map_data->last].name == NULL) {
245  goto alloc_error;
246  }
247  bpf_map_data->last++;
248  }
249  fd = EBPFLoadPinnedMapsFile(livedev, "tx_peer");
250  if (fd >= 0) {
251  bpf_map_data->array[bpf_map_data->last].fd = fd;
252  bpf_map_data->array[bpf_map_data->last].name = SCStrdup("tx_peer");
253  if (bpf_map_data->array[bpf_map_data->last].name == NULL) {
254  goto alloc_error;
255  }
256  bpf_map_data->last++;
257  }
258  fd = EBPFLoadPinnedMapsFile(livedev, "tx_peer_int");
259  if (fd >= 0) {
260  bpf_map_data->array[bpf_map_data->last].fd = fd;
261  bpf_map_data->array[bpf_map_data->last].name = SCStrdup("tx_peer_int");
262  if (bpf_map_data->array[bpf_map_data->last].name == NULL) {
263  goto alloc_error;
264  }
265  bpf_map_data->last++;
266  }
267 
268  /* Attach the bpf_maps_info to the LiveDevice via the device storage */
269  SCLiveDevSetStorageById(livedev, g_livedev_storage_id, bpf_map_data);
270  /* Declare that device will use bypass stats */
271  LiveDevUseBypass(livedev);
272 
273  return 0;
274 
275 alloc_error:
276  for (int i = 0; i < bpf_map_data->last; i++) {
277  SCFree(bpf_map_data->array[i].name);
278  }
279  bpf_map_data->last = 0;
280  SCLogError("Can't allocate bpf map name");
281  return -1;
282 }
283 
284 /**
285  * Load a section of an eBPF file
286  *
287  * This function loads a section inside an eBPF and return
288  * via the parameter val the file descriptor that will be used to
289  * inject the eBPF code into the kernel via a syscall.
290  *
291  * \param path the path of the eBPF file to load
292  * \param section the section in the eBPF file to load
293  * \param val a pointer to an integer that will be the file desc
294  * \return -1 in case of error, 0 in case of success, 1 if pinned maps is loaded
295  */
296 int EBPFLoadFile(const char *iface, const char *path, const char * section,
297  int *val, struct ebpf_timeout_config *config)
298 {
299  int err, pfd;
300  bool found = false;
301  struct bpf_object *bpfobj = NULL;
302  struct bpf_program *bpfprog = NULL;
303  struct bpf_map *map = NULL;
304 
305  if (iface == NULL)
306  return -1;
307  LiveDevice *livedev = LiveGetDevice(iface);
308  if (livedev == NULL)
309  return -1;
310 
311  if (config->flags & EBPF_XDP_CODE && config->flags & EBPF_PINNED_MAPS) {
312  /* We try to get our flow table maps and if we have them we can simply return */
313  if (EBPFLoadPinnedMaps(livedev, config) == 0) {
314  SCLogInfo("Loaded pinned maps, will use already loaded eBPF filter");
315  return 1;
316  }
317  }
318 
319  if (! path) {
320  SCLogError("No file defined to load eBPF from");
321  return -1;
322  }
323 
324  /* Since kernel 5.11 BPF map memory is memcg-accounted and no longer
325  * charged against RLIMIT_MEMLOCK (https://lwn.net/Articles/829307/), so
326  * raising the limit is only needed on older kernels. Raising it requires CAP_SYS_RESOURCE */
327  if (!SCKernelVersionIsAtLeast(5, 11)) {
328  struct rlimit r = { RLIM_INFINITY, RLIM_INFINITY };
329  if (setrlimit(RLIMIT_MEMLOCK, &r) != 0) {
330  SCLogError("Unable to lock memory: %s (%d)", strerror(errno), errno);
331  return -1;
332  }
333  }
334 
335  /* Open the eBPF file and parse it */
336  bpfobj = bpf_object__open(path);
337  long error = libbpf_get_error(bpfobj);
338  if (error) {
339  char err_buf[128];
340  libbpf_strerror(error, err_buf,
341  sizeof(err_buf));
342  SCLogError("Unable to load eBPF objects in '%s': %s", path, err_buf);
343  return -1;
344  }
345 
346  if (config->flags & EBPF_XDP_HW_MODE) {
347  unsigned int ifindex = if_nametoindex(iface);
348  bpf_object__for_each_program(bpfprog, bpfobj) {
349  bpf_program__set_ifindex(bpfprog, ifindex);
350  }
351  bpf_map__for_each(map, bpfobj) {
352  bpf_map__set_ifindex(map, ifindex);
353  }
354  }
355 
356  /* Let's check that our section is here */
357  bpf_object__for_each_program(bpfprog, bpfobj) {
358 #ifdef HAVE_BPF_PROGRAM__SECTION_NAME
359  const char *title = bpf_program__section_name(bpfprog);
360 #else
361  const char *title = bpf_program__title(bpfprog, 0);
362 #endif
363  if (!strcmp(title, section)) {
364  if (config->flags & EBPF_SOCKET_FILTER) {
365 #ifdef HAVE_BPF_PROGRAM__SET_TYPE
366  bpf_program__set_type(bpfprog, BPF_PROG_TYPE_SOCKET_FILTER);
367 #else
368  /* Fall back to legacy API */
369  bpf_program__set_socket_filter(bpfprog);
370 #endif
371  } else {
372 #ifdef HAVE_BPF_PROGRAM__SET_TYPE
373  bpf_program__set_type(bpfprog, BPF_PROG_TYPE_XDP);
374 #else
375  /* Fall back to legacy API */
376  bpf_program__set_xdp(bpfprog);
377 #endif
378  }
379  found = true;
380  break;
381  }
382  }
383 
384  if (!found) {
385  SCLogError("No section '%s' in '%s' file. Will not be able to use the file", section, path);
386  return -1;
387  }
388 
389  err = bpf_object__load(bpfobj);
390  if (err < 0) {
391  if (err == -EPERM) {
392  SCLogError("Permission issue when loading eBPF object"
393  " (check libbpf error on stdout)");
394  } else {
395  char buf[129];
396  libbpf_strerror(err, buf, sizeof(buf));
397  SCLogError("Unable to load eBPF object: %s (%d)", buf, err);
398  }
399  return -1;
400  }
401 
402  /* Kernel and userspace are sharing data via map. Userspace access to the
403  * map via a file descriptor. So we need to store the map to fd info. For
404  * that we use bpf_maps_info:: */
405  struct bpf_maps_info *bpf_map_data = SCCalloc(1, sizeof(*bpf_map_data));
406  if (bpf_map_data == NULL) {
407  SCLogError("Can't allocate bpf map array");
408  return -1;
409  }
410 
411  /* Store the maps in bpf_maps_info:: */
412  bpf_map__for_each(map, bpfobj) {
413  if (bpf_map_data->last == BPF_MAP_MAX_COUNT) {
414  SCLogError("Too many BPF maps in eBPF files");
415  break;
416  }
417  if (strcmp(bpf_map__name(map), "flow_table_v4") == 0) {
418  if (bpf_map__key_size(map) != sizeof(struct flowv4_keys)) {
419  SCLogError("Incompatible flow_table_v4");
420  break;
421  }
422  }
423  if (strcmp(bpf_map__name(map), "flow_table_v6") == 0) {
424  if (bpf_map__key_size(map) != sizeof(struct flowv6_keys)) {
425  SCLogError("Incompatible flow_table_v6");
426  break;
427  }
428  }
429  SCLogDebug("Got a map '%s' with fd '%d'", bpf_map__name(map), bpf_map__fd(map));
430  bpf_map_data->array[bpf_map_data->last].fd = bpf_map__fd(map);
431  bpf_map_data->array[bpf_map_data->last].name = SCStrdup(bpf_map__name(map));
432  snprintf(bpf_map_data->array[bpf_map_data->last].iface, IFNAMSIZ,
433  "%s", iface);
434  if (!bpf_map_data->array[bpf_map_data->last].name) {
435  SCLogError("Unable to duplicate map name");
436  BpfMapsInfoFree(bpf_map_data);
437  return -1;
438  }
439  bpf_map_data->array[bpf_map_data->last].to_unlink = 0;
440  if (config->flags & EBPF_PINNED_MAPS) {
441  SCLogConfig("Pinning: %d to %s", bpf_map_data->array[bpf_map_data->last].fd,
442  bpf_map_data->array[bpf_map_data->last].name);
443  char buf[1024];
444  snprintf(buf, sizeof(buf), "/sys/fs/bpf/suricata-%s-%s", iface,
445  bpf_map_data->array[bpf_map_data->last].name);
446  int ret = bpf_obj_pin(bpf_map_data->array[bpf_map_data->last].fd, buf);
447  if (ret != 0) {
448  SCLogWarning("Can not pin: %s", strerror(errno));
449  }
450  /* Don't unlink pinned maps in XDP mode to avoid a state reset */
451  if (config->flags & EBPF_XDP_CODE) {
452  bpf_map_data->array[bpf_map_data->last].to_unlink = 0;
453  } else {
454  bpf_map_data->array[bpf_map_data->last].to_unlink = 1;
455  }
456  }
457  bpf_map_data->last++;
458  }
459 
460  /* Attach the bpf_maps_info to the LiveDevice via the device storage */
461  SCLiveDevSetStorageById(livedev, g_livedev_storage_id, bpf_map_data);
462  LiveDevUseBypass(livedev);
463 
464  /* Finally we get the file descriptor for our eBPF program. We will use
465  * the fd to attach the program to the socket (eBPF case) or to the device
466  * (XDP case). */
467  pfd = bpf_program__fd(bpfprog);
468  if (pfd == -1) {
469  SCLogError("Unable to find %s section", section);
470  return -1;
471  }
472 
473  SCLogInfo("Successfully loaded eBPF file '%s' on '%s'", path, iface);
474  *val = pfd;
475  return 0;
476 }
477 
478 /**
479  * Attach a XDP program identified by its file descriptor to a device
480  *
481  * \param iface the name of interface
482  * \param fd the eBPF/XDP program file descriptor
483  * \param a flag to pass to attach function mostly used to set XDP mode
484  * \return -1 in case of error, 0 if success
485  */
486 int EBPFSetupXDP(const char *iface, int fd, uint8_t flags)
487 {
488 #ifdef HAVE_PACKET_XDP
489  unsigned int ifindex = if_nametoindex(iface);
490  if (ifindex == 0) {
491  SCLogError("Unknown interface '%s'", iface);
492  return -1;
493  }
494 #ifdef HAVE_BPF_XDP_ATTACH
495  int err = bpf_xdp_attach(ifindex, fd, flags, NULL);
496 #else
497  /* Fall back to legacy API */
498  int err = bpf_set_link_xdp_fd(ifindex, fd, flags);
499 #endif
500  if (err != 0) {
501  char buf[129];
502  libbpf_strerror(err, buf, sizeof(buf));
503  SCLogError("Unable to set XDP on '%s': %s (%d)", iface, buf, err);
504  return -1;
505  }
506 #endif
507  return 0;
508 }
509 
510 /**
511  * Create a Flow in the table for a Flowkey
512  *
513  * \return false (this create function never returns true)
514  */
515 static bool EBPFCreateFlowForKey(struct flows_stats *flowstats, LiveDevice *dev, void *key,
516  size_t skey, FlowKey *flow_key, struct timespec *ctime,
517  uint64_t pkts_cnt, uint64_t bytes_cnt,
518  int mapfd, int cpus_count)
519 {
520  Flow *f = NULL;
521  uint32_t hash = FlowKeyGetHash(flow_key);
522 
523  f = FlowGetFromFlowKey(flow_key, ctime, hash);
524  if (f == NULL)
525  return false;
526 
527  /* set accounting, we can't know the direction, so let's just start to
528  * serve them if we already have something from server to client. We need
529  * these numbers as we will use it to see if we have new traffic coming
530  * on the flow */
532  if (fc == NULL) {
533  fc = SCCalloc(sizeof(FlowBypassInfo), 1);
534  if (fc) {
535  FlowUpdateState(f, FLOW_STATE_CAPTURE_BYPASSED);
537  fc->BypassUpdate = EBPFBypassUpdate;
538  fc->BypassFree = EBPFBypassFree;
539  fc->todstpktcnt = pkts_cnt;
540  fc->todstbytecnt = bytes_cnt;
541  f->livedev_id = dev->id;
542  EBPFBypassData *eb = SCCalloc(1, sizeof(EBPFBypassData));
543  if (eb == NULL) {
544  SCFree(fc);
546  return false;
547  }
548  void *mkey = SCCalloc(1, skey);
549  if (mkey == NULL) {
550  SCFree(fc);
551  SCFree(eb);
553  return false;
554  }
555  memcpy(mkey, key, skey);
556  eb->key[0] = mkey;
557  eb->mapfd = mapfd;
558  eb->cpus_count = cpus_count;
559  fc->bypass_data = eb;
560  flowstats->count++;
561  } else {
563  return false;
564  }
565  } else {
566  EBPFBypassData *eb = (EBPFBypassData *) fc->bypass_data;
567  if (eb == NULL) {
569  return false;
570  }
571  /* if both keys are here, then it is a flow bypassed by this
572  * instance so we ignore it */
573  if (eb->key[0] && eb->key[1]) {
575  return false;
576  }
577  fc->tosrcpktcnt = pkts_cnt;
578  fc->tosrcbytecnt = bytes_cnt;
579  void *mkey = SCCalloc(1, skey);
580  if (mkey == NULL) {
582  return false;
583  }
584  memcpy(mkey, key, skey);
585  eb->key[1] = mkey;
586  }
587  f->livedev_id = dev->id;
589  return false;
590 }
591 
592 void EBPFBypassFree(void *data)
593 {
594  EBPFBypassData *eb = (EBPFBypassData *)data;
595  if (eb == NULL)
596  return;
597  SCFree(eb->key[0]);
598  if (eb->key[1]) {
599  SCFree(eb->key[1]);
600  }
601  SCFree(eb);
602 }
603 
604 /**
605  *
606  * Compare eBPF half flow to Flow
607  *
608  * \return true if entries have activity, false if not
609  */
610 
611 static bool EBPFBypassCheckHalfFlow(Flow *f, FlowBypassInfo *fc,
612  EBPFBypassData *eb, void *key,
613  int index)
614 {
615  int i;
616  uint64_t pkts_cnt = 0;
617  uint64_t bytes_cnt = 0;
618  /* We use a per CPU structure so we will get a array of values. But if nr_cpus
619  * is 1 then we have a global hash. */
620  BPF_DECLARE_PERCPU(struct pair, values_array, eb->cpus_count);
621  memset(values_array, 0, sizeof(values_array));
622  int res = bpf_map_lookup_elem(eb->mapfd, key, values_array);
623  if (res < 0) {
624  SCLogDebug("errno: (%d) %s", errno, strerror(errno));
625  return false;
626  }
627  for (i = 0; i < eb->cpus_count; i++) {
628  /* let's start accumulating value so we can compute the counters */
629  SCLogDebug("%d: Adding pkts %lu bytes %lu", i,
630  BPF_PERCPU(values_array, i).packets,
631  BPF_PERCPU(values_array, i).bytes);
632  pkts_cnt += BPF_PERCPU(values_array, i).packets;
633  bytes_cnt += BPF_PERCPU(values_array, i).bytes;
634  }
635  if (index == 0) {
636  if (pkts_cnt != fc->todstpktcnt) {
637  fc->todstpktcnt = pkts_cnt;
638  fc->todstbytecnt = bytes_cnt;
639  return true;
640  }
641  } else {
642  if (pkts_cnt != fc->tosrcpktcnt) {
643  fc->tosrcpktcnt = pkts_cnt;
644  fc->tosrcbytecnt = bytes_cnt;
645  return true;
646  }
647  }
648 
649  return false;
650 }
651 
652 /** Check both half flows for update
653  *
654  * Update lastts in the flow and do accounting
655  *
656  * */
657 bool EBPFBypassUpdate(Flow *f, void *data, time_t tsec)
658 {
659  EBPFBypassData *eb = (EBPFBypassData *)data;
660  if (eb == NULL) {
661  return false;
662  }
664  if (fc == NULL) {
665  return false;
666  }
667  bool activity = EBPFBypassCheckHalfFlow(f, fc, eb, eb->key[0], 0);
668  activity |= EBPFBypassCheckHalfFlow(f, fc, eb, eb->key[1], 1);
669  if (!activity) {
670  SCLogDebug("Delete entry: %u (%" PRIu64 ")", FLOW_IS_IPV6(f), FlowGetId(f));
671  /* delete the entries if no time update */
672  EBPFDeleteKey(eb->mapfd, eb->key[0]);
673  EBPFDeleteKey(eb->mapfd, eb->key[1]);
674  SCLogDebug("Done delete entry: %u", FLOW_IS_IPV6(f));
675  } else {
676  f->lastts = SCTIME_FROM_SECS(tsec);
677  return true;
678  }
679  return false;
680 }
681 
682 typedef bool (*OpFlowForKey)(struct flows_stats * flowstats, LiveDevice*dev, void *key,
683  size_t skey, FlowKey *flow_key, struct timespec *ctime,
684  uint64_t pkts_cnt, uint64_t bytes_cnt,
685  int mapfd, int cpus_count);
686 
687 /**
688  * Bypassed flows iterator for IPv4
689  *
690  * This function iterates on all the flows of the IPv4 table
691  * running a callback function on each flow.
692  */
693 static int EBPFForEachFlowV4Table(ThreadVars *th_v, LiveDevice *dev, const char *name,
694  struct timespec *ctime,
695  struct ebpf_timeout_config *tcfg,
696  OpFlowForKey EBPFOpFlowForKey
697  )
698 {
699  struct flows_stats flowstats = { 0, 0, 0};
700  int mapfd = EBPFGetMapFDByName(dev->dev, name);
701  if (mapfd == -1)
702  return -1;
703 
704  struct flowv4_keys key = {}, next_key;
705  int found = 0;
706  unsigned int i;
707  uint64_t hash_cnt = 0;
708 
709  if (tcfg->cpus_count == 0) {
710  return 0;
711  }
712 
713  bool dead_flow = false;
714  while (bpf_map_get_next_key(mapfd, &key, &next_key) == 0) {
715  uint64_t bytes_cnt = 0;
716  uint64_t pkts_cnt = 0;
717  hash_cnt++;
718  if (dead_flow) {
719  EBPFDeleteKey(mapfd, &key);
720  dead_flow = false;
721  }
722  /* We use a per CPU structure so we will get a array of values. But if nr_cpus
723  * is 1 then we have a global hash. */
724  BPF_DECLARE_PERCPU(struct pair, values_array, tcfg->cpus_count);
725  memset(values_array, 0, sizeof(values_array));
726  int res = bpf_map_lookup_elem(mapfd, &next_key, values_array);
727  if (res < 0) {
728  SCLogDebug("no entry in v4 table for %d -> %d", key.port16[0], key.port16[1]);
729  SCLogDebug("errno: (%d) %s", errno, strerror(errno));
730  key = next_key;
731  continue;
732  }
733  for (i = 0; i < tcfg->cpus_count; i++) {
734  /* let's start accumulating value so we can compute the counters */
735  SCLogDebug("%d: Adding pkts %lu bytes %lu", i,
736  BPF_PERCPU(values_array, i).packets,
737  BPF_PERCPU(values_array, i).bytes);
738  pkts_cnt += BPF_PERCPU(values_array, i).packets;
739  bytes_cnt += BPF_PERCPU(values_array, i).bytes;
740  }
741  /* Get the corresponding Flow in the Flow table to compare and update
742  * its counters and lastseen if needed */
743  FlowKey flow_key;
744  if (tcfg->mode == AFP_MODE_XDP_BYPASS) {
745  flow_key.sp = ntohs(next_key.port16[0]);
746  flow_key.dp = ntohs(next_key.port16[1]);
747  flow_key.src.addr_data32[0] = next_key.src;
748  flow_key.dst.addr_data32[0] = next_key.dst;
749  } else {
750  flow_key.sp = next_key.port16[0];
751  flow_key.dp = next_key.port16[1];
752  flow_key.src.addr_data32[0] = ntohl(next_key.src);
753  flow_key.dst.addr_data32[0] = ntohl(next_key.dst);
754  }
755  flow_key.src.family = AF_INET;
756  flow_key.src.addr_data32[1] = 0;
757  flow_key.src.addr_data32[2] = 0;
758  flow_key.src.addr_data32[3] = 0;
759  flow_key.dst.family = AF_INET;
760  flow_key.dst.addr_data32[1] = 0;
761  flow_key.dst.addr_data32[2] = 0;
762  flow_key.dst.addr_data32[3] = 0;
763  flow_key.vlan_id[0] = next_key.vlan0;
764  flow_key.vlan_id[1] = next_key.vlan1;
765  if (next_key.ip_proto == 1) {
766  flow_key.proto = IPPROTO_TCP;
767  } else {
768  flow_key.proto = IPPROTO_UDP;
769  }
770  flow_key.recursion_level = 0;
771  flow_key.livedev_id = dev->id;
772  dead_flow = EBPFOpFlowForKey(&flowstats, dev, &next_key, sizeof(next_key), &flow_key,
773  ctime, pkts_cnt, bytes_cnt,
774  mapfd, tcfg->cpus_count);
775  if (dead_flow) {
776  found = 1;
777  }
778 
780  return 0;
781  }
782 
783  key = next_key;
784  }
785  if (dead_flow) {
786  EBPFDeleteKey(mapfd, &key);
787  found = 1;
788  }
789  SC_ATOMIC_ADD(dev->bypassed, flowstats.packets);
790 
791  LiveDevAddBypassStats(dev, flowstats.count, AF_INET);
792  SCLogInfo("IPv4 bypassed flow table size: %" PRIu64, hash_cnt);
793 
794  return found;
795 }
796 
797 /**
798  * Bypassed flows iterator for IPv6
799  *
800  * This function iterates on all the flows of the IPv4 table
801  * running a callback function on each flow.
802  */
803 static int EBPFForEachFlowV6Table(ThreadVars *th_v,
804  LiveDevice *dev, const char *name,
805  struct timespec *ctime,
806  struct ebpf_timeout_config *tcfg,
807  OpFlowForKey EBPFOpFlowForKey
808  )
809 {
810  struct flows_stats flowstats = { 0, 0, 0};
811  int mapfd = EBPFGetMapFDByName(dev->dev, name);
812  if (mapfd == -1)
813  return -1;
814 
815  struct flowv6_keys key = {}, next_key;
816  int found = 0;
817  unsigned int i;
818  uint64_t hash_cnt = 0;
819 
820  if (tcfg->cpus_count == 0) {
821  SCLogWarning("CPU count should not be 0");
822  return 0;
823  }
824 
825  uint64_t pkts_cnt = 0;
826  while (bpf_map_get_next_key(mapfd, &key, &next_key) == 0) {
827  uint64_t bytes_cnt = 0;
828  hash_cnt++;
829  if (pkts_cnt > 0) {
830  EBPFDeleteKey(mapfd, &key);
831  }
832  pkts_cnt = 0;
833  /* We use a per CPU structure so we will get a array of values. But if nr_cpus
834  * is 1 then we have a global hash. */
835  BPF_DECLARE_PERCPU(struct pair, values_array, tcfg->cpus_count);
836  memset(values_array, 0, sizeof(values_array));
837  int res = bpf_map_lookup_elem(mapfd, &next_key, values_array);
838  if (res < 0) {
839  SCLogDebug("no entry in v4 table for %d -> %d", key.port16[0], key.port16[1]);
840  key = next_key;
841  continue;
842  }
843  for (i = 0; i < tcfg->cpus_count; i++) {
844  /* let's start accumulating value so we can compute the counters */
845  SCLogDebug("%d: Adding pkts %lu bytes %lu", i,
846  BPF_PERCPU(values_array, i).packets,
847  BPF_PERCPU(values_array, i).bytes);
848  pkts_cnt += BPF_PERCPU(values_array, i).packets;
849  bytes_cnt += BPF_PERCPU(values_array, i).bytes;
850  }
851  /* Get the corresponding Flow in the Flow table to compare and update
852  * its counters and lastseen if needed */
853  FlowKey flow_key;
854  if (tcfg->mode == AFP_MODE_XDP_BYPASS) {
855  flow_key.sp = ntohs(next_key.port16[0]);
856  flow_key.dp = ntohs(next_key.port16[1]);
857  flow_key.src.family = AF_INET6;
858  flow_key.src.addr_data32[0] = next_key.src[0];
859  flow_key.src.addr_data32[1] = next_key.src[1];
860  flow_key.src.addr_data32[2] = next_key.src[2];
861  flow_key.src.addr_data32[3] = next_key.src[3];
862  flow_key.dst.family = AF_INET6;
863  flow_key.dst.addr_data32[0] = next_key.dst[0];
864  flow_key.dst.addr_data32[1] = next_key.dst[1];
865  flow_key.dst.addr_data32[2] = next_key.dst[2];
866  flow_key.dst.addr_data32[3] = next_key.dst[3];
867  } else {
868  flow_key.sp = next_key.port16[0];
869  flow_key.dp = next_key.port16[1];
870  flow_key.src.family = AF_INET6;
871  flow_key.src.addr_data32[0] = ntohl(next_key.src[0]);
872  flow_key.src.addr_data32[1] = ntohl(next_key.src[1]);
873  flow_key.src.addr_data32[2] = ntohl(next_key.src[2]);
874  flow_key.src.addr_data32[3] = ntohl(next_key.src[3]);
875  flow_key.dst.family = AF_INET6;
876  flow_key.dst.addr_data32[0] = ntohl(next_key.dst[0]);
877  flow_key.dst.addr_data32[1] = ntohl(next_key.dst[1]);
878  flow_key.dst.addr_data32[2] = ntohl(next_key.dst[2]);
879  flow_key.dst.addr_data32[3] = ntohl(next_key.dst[3]);
880  }
881  flow_key.vlan_id[0] = next_key.vlan0;
882  flow_key.vlan_id[1] = next_key.vlan1;
883  if (next_key.ip_proto == 1) {
884  flow_key.proto = IPPROTO_TCP;
885  } else {
886  flow_key.proto = IPPROTO_UDP;
887  }
888  flow_key.recursion_level = 0;
889  flow_key.livedev_id = dev->id;
890  pkts_cnt = EBPFOpFlowForKey(&flowstats, dev, &next_key, sizeof(next_key), &flow_key,
891  ctime, pkts_cnt, bytes_cnt,
892  mapfd, tcfg->cpus_count);
893  if (pkts_cnt > 0) {
894  found = 1;
895  }
896 
898  return 0;
899  }
900 
901  key = next_key;
902  }
903  if (pkts_cnt > 0) {
904  EBPFDeleteKey(mapfd, &key);
905  found = 1;
906  }
907  SC_ATOMIC_ADD(dev->bypassed, flowstats.packets);
908 
909  LiveDevAddBypassStats(dev, flowstats.count, AF_INET6);
910  SCLogInfo("IPv6 bypassed flow table size: %" PRIu64, hash_cnt);
911  return found;
912 }
913 
914 
915 int EBPFCheckBypassedFlowCreate(ThreadVars *th_v, struct timespec *curtime, void *data)
916 {
917  LiveDevice *ldev = NULL, *ndev;
918  struct ebpf_timeout_config *cfg = (struct ebpf_timeout_config *)data;
919  while(LiveDeviceForEach(&ldev, &ndev)) {
920  EBPFForEachFlowV4Table(th_v, ldev, "flow_table_v4",
921  curtime,
922  cfg, EBPFCreateFlowForKey);
923  EBPFForEachFlowV6Table(th_v, ldev, "flow_table_v6",
924  curtime,
925  cfg, EBPFCreateFlowForKey);
926  }
927 
928  return 0;
929 }
930 
931 void EBPFRegisterExtension(void)
932 {
933  g_livedev_storage_id = SCLiveDevStorageRegister("bpfmap", BpfMapsInfoFree);
934  g_flow_storage_id = SCFlowStorageRegister("bypassedlist", BypassedListFree);
935 }
936 
937 
938 #ifdef HAVE_PACKET_XDP
939 
940 static uint32_t g_redirect_iface_cpu_counter = 0;
941 
942 static int EBPFAddCPUToMap(const char *iface, uint32_t i)
943 {
944  int cpumap = EBPFGetMapFDByName(iface, "cpu_map");
945  uint32_t queue_size = 4096;
946  int ret;
947 
948  if (cpumap < 0) {
949  SCLogError("Can't find cpu_map");
950  return -1;
951  }
952  ret = bpf_map_update_elem(cpumap, &i, &queue_size, 0);
953  if (ret) {
954  SCLogError("Create CPU entry failed (err:%d)", ret);
955  return -1;
956  }
957  int cpus_available = EBPFGetMapFDByName(iface, "cpus_available");
958  if (cpus_available < 0) {
959  SCLogError("Can't find cpus_available map");
960  return -1;
961  }
962 
963  ret = bpf_map_update_elem(cpus_available, &g_redirect_iface_cpu_counter, &i, 0);
964  if (ret) {
965  SCLogError("Create CPU entry failed (err:%d)", ret);
966  return -1;
967  }
968  return 0;
969 }
970 
971 static void EBPFRedirectMapAddCPU(int i, void *data)
972 {
973  if (EBPFAddCPUToMap(data, i) < 0) {
974  SCLogError("Unable to add CPU %d to set", i);
975  } else {
976  g_redirect_iface_cpu_counter++;
977  }
978 }
979 
980 void EBPFBuildCPUSet(SCConfNode *node, char *iface)
981 {
982  uint32_t key0 = 0;
983  int mapfd = EBPFGetMapFDByName(iface, "cpus_count");
984  if (mapfd < 0) {
985  SCLogError("Unable to find 'cpus_count' map");
986  return;
987  }
988  g_redirect_iface_cpu_counter = 0;
989  if (node == NULL) {
990  bpf_map_update_elem(mapfd, &key0, &g_redirect_iface_cpu_counter,
991  BPF_ANY);
992  return;
993  }
994  if (BuildCpusetWithCallback("xdp-cpu-redirect", node, EBPFRedirectMapAddCPU, iface) < 0) {
995  SCLogWarning("Failed to parse XDP CPU redirect configuration");
996  return;
997  }
998  bpf_map_update_elem(mapfd, &key0, &g_redirect_iface_cpu_counter,
999  BPF_ANY);
1000 }
1001 
1002 /**
1003  * Setup peer interface in XDP system
1004  *
1005  * Ths function set up the peer interface in the XDP maps used by the
1006  * bypass filter. The first map tx_peer has type device map and is
1007  * used to store the peer. The second map tx_peer_int is used by the
1008  * code to check if we have a peer defined for this interface.
1009  *
1010  * As the map are per device we just need maps with one single element.
1011  * In both case, we use the key 0 to enter element so XDP kernel code
1012  * is using the same key.
1013  */
1014 int EBPFSetPeerIface(const char *iface, const char *out_iface)
1015 {
1016  int mapfd = EBPFGetMapFDByName(iface, "tx_peer");
1017  if (mapfd < 0) {
1018  SCLogError("Unable to find 'tx_peer' map");
1019  return -1;
1020  }
1021  int intmapfd = EBPFGetMapFDByName(iface, "tx_peer_int");
1022  if (intmapfd < 0) {
1023  SCLogError("Unable to find 'tx_peer_int' map");
1024  return -1;
1025  }
1026 
1027  int key0 = 0;
1028  unsigned int peer_index = if_nametoindex(out_iface);
1029  if (peer_index == 0) {
1030  SCLogError("No iface '%s'", out_iface);
1031  return -1;
1032  }
1033  int ret = bpf_map_update_elem(mapfd, &key0, &peer_index, BPF_ANY);
1034  if (ret) {
1035  SCLogError("Create peer entry failed (err:%d)", ret);
1036  return -1;
1037  }
1038  ret = bpf_map_update_elem(intmapfd, &key0, &peer_index, BPF_ANY);
1039  if (ret) {
1040  SCLogError("Create peer entry failed (err:%d)", ret);
1041  return -1;
1042  }
1043  return 0;
1044 }
1045 
1046 /**
1047  * Bypass the flow on all ifaces it is seen on. This is used
1048  * in IPS mode.
1049  */
1050 
1051 int EBPFUpdateFlow(Flow *f, Packet *p, void *data)
1052 {
1053  BypassedIfaceList *ifl = (BypassedIfaceList *)SCFlowGetStorageById(f, g_flow_storage_id);
1054  if (ifl == NULL) {
1055  ifl = SCCalloc(1, sizeof(*ifl));
1056  if (ifl == NULL) {
1057  return 0;
1058  }
1059  ifl->dev = LiveDeviceGetById(p->livedev_id);
1060  SCFlowSetStorageById(f, g_flow_storage_id, ifl);
1061  return 1;
1062  }
1063  /* Look for packet iface in the list */
1064  BypassedIfaceList *ldev = ifl;
1065  while (ldev) {
1066  if (p->livedev_id == LiveDeviceGetId(ldev->dev)) {
1067  return 1;
1068  }
1069  ldev = ldev->next;
1070  }
1071  /* Call bypass function if ever not in the list */
1072  p->BypassPacketsFlow(p);
1073 
1074  /* Add iface to the list */
1075  BypassedIfaceList *nifl = SCCalloc(1, sizeof(*nifl));
1076  if (nifl == NULL) {
1077  return 0;
1078  }
1079  nifl->dev = LiveDeviceGetById(p->livedev_id);
1080  nifl->next = ifl;
1081  SCFlowSetStorageById(f, g_flow_storage_id, nifl);
1082  return 1;
1083 }
1084 
1085 #endif /* HAVE_PACKET_XDP */
1086 
1087 #endif
util-device-private.h
tm-threads.h
flow-bypass.h
FLOW_IS_IPV6
#define FLOW_IS_IPV6(f)
Definition: flow.h:175
LiveDevStorageId_
Definition: device-storage.h:31
FlowKey_::src
Address src
Definition: flow.h:313
FlowBypassInfo_
Definition: flow.h:534
SCFlowGetStorageById
void * SCFlowGetStorageById(const Flow *f, SCFlowStorageId id)
Definition: flow-storage.c:37
SCLogDebug
#define SCLogDebug(...)
Definition: util-debug.h:282
next
struct HtpBodyChunk_ * next
Definition: app-layer-htp.h:0
flows_stats::count
uint64_t count
Definition: flow-bypass.h:30
name
const char * name
Definition: detect-engine-proto.c:47
FlowKeyGetHash
uint32_t FlowKeyGetHash(FlowKey *fk)
Definition: flow-hash.c:314
Flow_
Flow data structure.
Definition: flow.h:359
LiveDevice_
Definition: util-device-private.h:32
SC_ATOMIC_ADD
#define SC_ATOMIC_ADD(name, val)
add a value to our atomic variable
Definition: util-atomic.h:332
LiveDevice_::id
uint16_t id
Definition: util-device-private.h:38
SCFlowStorageId
Definition: flow-storage.h:33
flow-hash.h
LiveDeviceGetById
LiveDevice * LiveDeviceGetById(const int id)
Definition: util-device.c:460
SCKernelVersionIsAtLeast
int SCKernelVersionIsAtLeast(int major, int minor)
Definition: util-host-info.c:37
FlowBypassInfo_::tosrcbytecnt
uint64_t tosrcbytecnt
Definition: flow.h:539
LiveDeviceForEach
LiveDevice * LiveDeviceForEach(LiveDevice **ldev, LiveDevice **ndev)
Definition: util-device.c:468
device-storage.h
p
Packet * p
Definition: fuzz_dataset.c:30
Flow_::livedev_id
uint16_t livedev_id
Definition: flow.h:404
Packet_::BypassPacketsFlow
int(* BypassPacketsFlow)(struct Packet_ *)
Definition: decode.h:609
GetFlowBypassInfoID
SCFlowStorageId GetFlowBypassInfoID(void)
Definition: flow-util.c:223
FLOWLOCK_UNLOCK
#define FLOWLOCK_UNLOCK(fb)
Definition: flow.h:276
SCFlowStorageRegister
SCFlowStorageId SCFlowStorageRegister(const char *name, void(*Free)(void *))
Definition: flow-storage.c:58
SCTIME_FROM_SECS
#define SCTIME_FROM_SECS(s)
Definition: util-time.h:69
FlowBypassInfo_::todstbytecnt
uint64_t todstbytecnt
Definition: flow.h:541
FlowBypassInfo_::BypassUpdate
bool(* BypassUpdate)(Flow *f, void *data, time_t tsec)
Definition: flow.h:535
util-cpu.h
FlowBypassInfo_::BypassFree
void(* BypassFree)(void *data)
Definition: flow.h:536
LiveGetDevice
LiveDevice * LiveGetDevice(const char *name)
Get a pointer to the device at idx.
Definition: util-device.c:269
Flow_::lastts
SCTime_t lastts
Definition: flow.h:423
FlowKey_::recursion_level
uint8_t recursion_level
Definition: flow.h:316
util-ebpf.h
ThreadVars_
Per thread variable structure.
Definition: threadvars.h:57
util-affinity.h
SCFlowSetStorageById
int SCFlowSetStorageById(Flow *f, SCFlowStorageId id, void *ptr)
Definition: flow-storage.c:42
THV_KILL
#define THV_KILL
Definition: threadvars.h:39
FlowBypassInfo_::todstpktcnt
uint64_t todstpktcnt
Definition: flow.h:540
LiveDevice_::dev
char * dev
Definition: util-device-private.h:33
FlowBypassInfo_::bypass_data
void * bypass_data
Definition: flow.h:537
SCLogWarning
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition: util-debug.h:262
FlowKey_::livedev_id
uint16_t livedev_id
Definition: flow.h:317
FlowKey_::sp
Port sp
Definition: flow.h:314
Packet_
Definition: decode.h:516
LiveDeviceGetId
uint16_t LiveDeviceGetId(const LiveDevice *dev)
Definition: util-device.c:452
LiveDevUseBypass
int LiveDevUseBypass(LiveDevice *dev)
Definition: util-device.c:541
util-host-info.h
LiveDevAddBypassStats
void LiveDevAddBypassStats(LiveDevice *dev, uint64_t cnt, int family)
Definition: util-device.c:563
FlowBypassInfo_::tosrcpktcnt
uint64_t tosrcpktcnt
Definition: flow.h:538
SCLogInfo
#define SCLogInfo(...)
Macro used to log INFORMATIONAL messages.
Definition: util-debug.h:232
FlowUpdateState
void FlowUpdateState(Flow *f, const enum FlowState s)
Definition: flow.c:1194
SCLiveDevGetStorageById
void * SCLiveDevGetStorageById(LiveDevice *d, SCLiveDevStorageId id)
Get a value from a given LiveDevice storage.
Definition: device-storage.c:86
th_v
ThreadVars * th_v
Definition: fuzz_dataset.c:29
flow-storage.h
flags
uint8_t flags
Definition: decode-gre.h:0
suricata-common.h
FlowKey_::dst
Address dst
Definition: flow.h:313
Packet_::livedev_id
uint16_t livedev_id
Definition: decode.h:633
SCStrdup
#define SCStrdup(s)
Definition: util-mem.h:56
SCLiveDevStorageRegister
SCLiveDevStorageId SCLiveDevStorageRegister(const char *name, void(*Free)(void *))
Register a LiveDevice storage.
Definition: device-storage.c:58
SCLogConfig
struct SCLogConfig_ SCLogConfig
Holds the config state used by the logging api.
FlowKey_::proto
uint8_t proto
Definition: flow.h:315
BuildCpusetWithCallback
int BuildCpusetWithCallback(const char *name, SCConfNode *node, void(*Callback)(int i, void *data), void *data)
Definition: util-affinity.c:226
SCLogError
#define SCLogError(...)
Macro used to log ERROR messages.
Definition: util-debug.h:274
SCFree
#define SCFree(p)
Definition: util-mem.h:61
SCLiveDevSetStorageById
int SCLiveDevSetStorageById(LiveDevice *d, SCLiveDevStorageId id, void *ptr)
Store a pointer in a given LiveDevice storage.
Definition: device-storage.c:73
FlowGetFromFlowKey
Flow * FlowGetFromFlowKey(FlowKey *key, struct timespec *ttime, const uint32_t hash)
Get or create a Flow using a FlowKey.
Definition: flow-hash.c:1100
FlowKey_
Definition: flow.h:312
Address_::family
char family
Definition: decode.h:114
SCFlowStorageId::id
int id
Definition: flow-storage.h:34
FlowKey_::vlan_id
uint16_t vlan_id[VLAN_MAX_LAYERS]
Definition: flow.h:318
flow.h
TmThreadsCheckFlag
int TmThreadsCheckFlag(ThreadVars *tv, uint32_t flag)
Check if a thread flag is set.
Definition: tm-threads.c:94
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
LiveDevStorageId_::id
int id
Definition: device-storage.h:32
FlowKey_::dp
Port dp
Definition: flow.h:314
flows_stats::packets
uint64_t packets
Definition: flow-bypass.h:31
SCConfNode_
Definition: conf.h:37
flows_stats
Definition: flow-bypass.h:29
f
Flow f
Definition: fuzz_dataset.c:32