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 
48 #include "device-storage.h"
49 #include "flow-storage.h"
50 #include "flow.h"
51 #include "flow-hash.h"
52 #include "tm-threads.h"
53 
54 #include <bpf/libbpf.h>
55 #include <bpf/bpf.h>
56 #include <net/if.h>
57 #include "autoconf.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  /* Sending the eBPF code to the kernel requires a large amount of
325  * locked memory so we set it to unlimited to avoid a ENOPERM error */
326  struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY};
327  if (setrlimit(RLIMIT_MEMLOCK, &r) != 0) {
328  SCLogError("Unable to lock memory: %s (%d)", strerror(errno), errno);
329  return -1;
330  }
331 
332  /* Open the eBPF file and parse it */
333  bpfobj = bpf_object__open(path);
334  long error = libbpf_get_error(bpfobj);
335  if (error) {
336  char err_buf[128];
337  libbpf_strerror(error, err_buf,
338  sizeof(err_buf));
339  SCLogError("Unable to load eBPF objects in '%s': %s", path, err_buf);
340  return -1;
341  }
342 
343  if (config->flags & EBPF_XDP_HW_MODE) {
344  unsigned int ifindex = if_nametoindex(iface);
345  bpf_object__for_each_program(bpfprog, bpfobj) {
346  bpf_program__set_ifindex(bpfprog, ifindex);
347  }
348  bpf_map__for_each(map, bpfobj) {
349  bpf_map__set_ifindex(map, ifindex);
350  }
351  }
352 
353  /* Let's check that our section is here */
354  bpf_object__for_each_program(bpfprog, bpfobj) {
355 #ifdef HAVE_BPF_PROGRAM__SECTION_NAME
356  const char *title = bpf_program__section_name(bpfprog);
357 #else
358  const char *title = bpf_program__title(bpfprog, 0);
359 #endif
360  if (!strcmp(title, section)) {
361  if (config->flags & EBPF_SOCKET_FILTER) {
362 #ifdef HAVE_BPF_PROGRAM__SET_TYPE
363  bpf_program__set_type(bpfprog, BPF_PROG_TYPE_SOCKET_FILTER);
364 #else
365  /* Fall back to legacy API */
366  bpf_program__set_socket_filter(bpfprog);
367 #endif
368  } else {
369 #ifdef HAVE_BPF_PROGRAM__SET_TYPE
370  bpf_program__set_type(bpfprog, BPF_PROG_TYPE_XDP);
371 #else
372  /* Fall back to legacy API */
373  bpf_program__set_xdp(bpfprog);
374 #endif
375  }
376  found = true;
377  break;
378  }
379  }
380 
381  if (!found) {
382  SCLogError("No section '%s' in '%s' file. Will not be able to use the file", section, path);
383  return -1;
384  }
385 
386  err = bpf_object__load(bpfobj);
387  if (err < 0) {
388  if (err == -EPERM) {
389  SCLogError("Permission issue when loading eBPF object"
390  " (check libbpf error on stdout)");
391  } else {
392  char buf[129];
393  libbpf_strerror(err, buf, sizeof(buf));
394  SCLogError("Unable to load eBPF object: %s (%d)", buf, err);
395  }
396  return -1;
397  }
398 
399  /* Kernel and userspace are sharing data via map. Userspace access to the
400  * map via a file descriptor. So we need to store the map to fd info. For
401  * that we use bpf_maps_info:: */
402  struct bpf_maps_info *bpf_map_data = SCCalloc(1, sizeof(*bpf_map_data));
403  if (bpf_map_data == NULL) {
404  SCLogError("Can't allocate bpf map array");
405  return -1;
406  }
407 
408  /* Store the maps in bpf_maps_info:: */
409  bpf_map__for_each(map, bpfobj) {
410  if (bpf_map_data->last == BPF_MAP_MAX_COUNT) {
411  SCLogError("Too many BPF maps in eBPF files");
412  break;
413  }
414  if (strcmp(bpf_map__name(map), "flow_table_v4") == 0) {
415  if (bpf_map__key_size(map) != sizeof(struct flowv4_keys)) {
416  SCLogError("Incompatible flow_table_v4");
417  break;
418  }
419  }
420  if (strcmp(bpf_map__name(map), "flow_table_v6") == 0) {
421  if (bpf_map__key_size(map) != sizeof(struct flowv6_keys)) {
422  SCLogError("Incompatible flow_table_v6");
423  break;
424  }
425  }
426  SCLogDebug("Got a map '%s' with fd '%d'", bpf_map__name(map), bpf_map__fd(map));
427  bpf_map_data->array[bpf_map_data->last].fd = bpf_map__fd(map);
428  bpf_map_data->array[bpf_map_data->last].name = SCStrdup(bpf_map__name(map));
429  snprintf(bpf_map_data->array[bpf_map_data->last].iface, IFNAMSIZ,
430  "%s", iface);
431  if (!bpf_map_data->array[bpf_map_data->last].name) {
432  SCLogError("Unable to duplicate map name");
433  BpfMapsInfoFree(bpf_map_data);
434  return -1;
435  }
436  bpf_map_data->array[bpf_map_data->last].to_unlink = 0;
437  if (config->flags & EBPF_PINNED_MAPS) {
438  SCLogConfig("Pinning: %d to %s", bpf_map_data->array[bpf_map_data->last].fd,
439  bpf_map_data->array[bpf_map_data->last].name);
440  char buf[1024];
441  snprintf(buf, sizeof(buf), "/sys/fs/bpf/suricata-%s-%s", iface,
442  bpf_map_data->array[bpf_map_data->last].name);
443  int ret = bpf_obj_pin(bpf_map_data->array[bpf_map_data->last].fd, buf);
444  if (ret != 0) {
445  SCLogWarning("Can not pin: %s", strerror(errno));
446  }
447  /* Don't unlink pinned maps in XDP mode to avoid a state reset */
448  if (config->flags & EBPF_XDP_CODE) {
449  bpf_map_data->array[bpf_map_data->last].to_unlink = 0;
450  } else {
451  bpf_map_data->array[bpf_map_data->last].to_unlink = 1;
452  }
453  }
454  bpf_map_data->last++;
455  }
456 
457  /* Attach the bpf_maps_info to the LiveDevice via the device storage */
458  SCLiveDevSetStorageById(livedev, g_livedev_storage_id, bpf_map_data);
459  LiveDevUseBypass(livedev);
460 
461  /* Finally we get the file descriptor for our eBPF program. We will use
462  * the fd to attach the program to the socket (eBPF case) or to the device
463  * (XDP case). */
464  pfd = bpf_program__fd(bpfprog);
465  if (pfd == -1) {
466  SCLogError("Unable to find %s section", section);
467  return -1;
468  }
469 
470  SCLogInfo("Successfully loaded eBPF file '%s' on '%s'", path, iface);
471  *val = pfd;
472  return 0;
473 }
474 
475 /**
476  * Attach a XDP program identified by its file descriptor to a device
477  *
478  * \param iface the name of interface
479  * \param fd the eBPF/XDP program file descriptor
480  * \param a flag to pass to attach function mostly used to set XDP mode
481  * \return -1 in case of error, 0 if success
482  */
483 int EBPFSetupXDP(const char *iface, int fd, uint8_t flags)
484 {
485 #ifdef HAVE_PACKET_XDP
486  unsigned int ifindex = if_nametoindex(iface);
487  if (ifindex == 0) {
488  SCLogError("Unknown interface '%s'", iface);
489  return -1;
490  }
491 #ifdef HAVE_BPF_XDP_ATTACH
492  int err = bpf_xdp_attach(ifindex, fd, flags, NULL);
493 #else
494  /* Fall back to legacy API */
495  int err = bpf_set_link_xdp_fd(ifindex, fd, flags);
496 #endif
497  if (err != 0) {
498  char buf[129];
499  libbpf_strerror(err, buf, sizeof(buf));
500  SCLogError("Unable to set XDP on '%s': %s (%d)", iface, buf, err);
501  return -1;
502  }
503 #endif
504  return 0;
505 }
506 
507 /**
508  * Create a Flow in the table for a Flowkey
509  *
510  * \return false (this create function never returns true)
511  */
512 static bool EBPFCreateFlowForKey(struct flows_stats *flowstats, LiveDevice *dev, void *key,
513  size_t skey, FlowKey *flow_key, struct timespec *ctime,
514  uint64_t pkts_cnt, uint64_t bytes_cnt,
515  int mapfd, int cpus_count)
516 {
517  Flow *f = NULL;
518  uint32_t hash = FlowKeyGetHash(flow_key);
519 
520  f = FlowGetFromFlowKey(flow_key, ctime, hash);
521  if (f == NULL)
522  return false;
523 
524  /* set accounting, we can't know the direction, so let's just start to
525  * serve them if we already have something from server to client. We need
526  * these numbers as we will use it to see if we have new traffic coming
527  * on the flow */
529  if (fc == NULL) {
530  fc = SCCalloc(sizeof(FlowBypassInfo), 1);
531  if (fc) {
532  FlowUpdateState(f, FLOW_STATE_CAPTURE_BYPASSED);
534  fc->BypassUpdate = EBPFBypassUpdate;
535  fc->BypassFree = EBPFBypassFree;
536  fc->todstpktcnt = pkts_cnt;
537  fc->todstbytecnt = bytes_cnt;
538  f->livedev_id = dev->id;
539  EBPFBypassData *eb = SCCalloc(1, sizeof(EBPFBypassData));
540  if (eb == NULL) {
541  SCFree(fc);
542  FLOWLOCK_UNLOCK(f);
543  return false;
544  }
545  void *mkey = SCCalloc(1, skey);
546  if (mkey == NULL) {
547  SCFree(fc);
548  SCFree(eb);
549  FLOWLOCK_UNLOCK(f);
550  return false;
551  }
552  memcpy(mkey, key, skey);
553  eb->key[0] = mkey;
554  eb->mapfd = mapfd;
555  eb->cpus_count = cpus_count;
556  fc->bypass_data = eb;
557  flowstats->count++;
558  } else {
559  FLOWLOCK_UNLOCK(f);
560  return false;
561  }
562  } else {
563  EBPFBypassData *eb = (EBPFBypassData *) fc->bypass_data;
564  if (eb == NULL) {
565  FLOWLOCK_UNLOCK(f);
566  return false;
567  }
568  /* if both keys are here, then it is a flow bypassed by this
569  * instance so we ignore it */
570  if (eb->key[0] && eb->key[1]) {
571  FLOWLOCK_UNLOCK(f);
572  return false;
573  }
574  fc->tosrcpktcnt = pkts_cnt;
575  fc->tosrcbytecnt = bytes_cnt;
576  void *mkey = SCCalloc(1, skey);
577  if (mkey == NULL) {
578  FLOWLOCK_UNLOCK(f);
579  return false;
580  }
581  memcpy(mkey, key, skey);
582  eb->key[1] = mkey;
583  }
584  f->livedev_id = dev->id;
585  FLOWLOCK_UNLOCK(f);
586  return false;
587 }
588 
589 void EBPFBypassFree(void *data)
590 {
591  EBPFBypassData *eb = (EBPFBypassData *)data;
592  if (eb == NULL)
593  return;
594  SCFree(eb->key[0]);
595  if (eb->key[1]) {
596  SCFree(eb->key[1]);
597  }
598  SCFree(eb);
599 }
600 
601 /**
602  *
603  * Compare eBPF half flow to Flow
604  *
605  * \return true if entries have activity, false if not
606  */
607 
608 static bool EBPFBypassCheckHalfFlow(Flow *f, FlowBypassInfo *fc,
609  EBPFBypassData *eb, void *key,
610  int index)
611 {
612  int i;
613  uint64_t pkts_cnt = 0;
614  uint64_t bytes_cnt = 0;
615  /* We use a per CPU structure so we will get a array of values. But if nr_cpus
616  * is 1 then we have a global hash. */
617  BPF_DECLARE_PERCPU(struct pair, values_array, eb->cpus_count);
618  memset(values_array, 0, sizeof(values_array));
619  int res = bpf_map_lookup_elem(eb->mapfd, key, values_array);
620  if (res < 0) {
621  SCLogDebug("errno: (%d) %s", errno, strerror(errno));
622  return false;
623  }
624  for (i = 0; i < eb->cpus_count; i++) {
625  /* let's start accumulating value so we can compute the counters */
626  SCLogDebug("%d: Adding pkts %lu bytes %lu", i,
627  BPF_PERCPU(values_array, i).packets,
628  BPF_PERCPU(values_array, i).bytes);
629  pkts_cnt += BPF_PERCPU(values_array, i).packets;
630  bytes_cnt += BPF_PERCPU(values_array, i).bytes;
631  }
632  if (index == 0) {
633  if (pkts_cnt != fc->todstpktcnt) {
634  fc->todstpktcnt = pkts_cnt;
635  fc->todstbytecnt = bytes_cnt;
636  return true;
637  }
638  } else {
639  if (pkts_cnt != fc->tosrcpktcnt) {
640  fc->tosrcpktcnt = pkts_cnt;
641  fc->tosrcbytecnt = bytes_cnt;
642  return true;
643  }
644  }
645 
646  return false;
647 }
648 
649 /** Check both half flows for update
650  *
651  * Update lastts in the flow and do accounting
652  *
653  * */
654 bool EBPFBypassUpdate(Flow *f, void *data, time_t tsec)
655 {
656  EBPFBypassData *eb = (EBPFBypassData *)data;
657  if (eb == NULL) {
658  return false;
659  }
661  if (fc == NULL) {
662  return false;
663  }
664  bool activity = EBPFBypassCheckHalfFlow(f, fc, eb, eb->key[0], 0);
665  activity |= EBPFBypassCheckHalfFlow(f, fc, eb, eb->key[1], 1);
666  if (!activity) {
667  SCLogDebug("Delete entry: %u (%" PRIu64 ")", FLOW_IS_IPV6(f), FlowGetId(f));
668  /* delete the entries if no time update */
669  EBPFDeleteKey(eb->mapfd, eb->key[0]);
670  EBPFDeleteKey(eb->mapfd, eb->key[1]);
671  SCLogDebug("Done delete entry: %u", FLOW_IS_IPV6(f));
672  } else {
673  f->lastts = SCTIME_FROM_SECS(tsec);
674  return true;
675  }
676  return false;
677 }
678 
679 typedef bool (*OpFlowForKey)(struct flows_stats * flowstats, LiveDevice*dev, void *key,
680  size_t skey, FlowKey *flow_key, struct timespec *ctime,
681  uint64_t pkts_cnt, uint64_t bytes_cnt,
682  int mapfd, int cpus_count);
683 
684 /**
685  * Bypassed flows iterator for IPv4
686  *
687  * This function iterates on all the flows of the IPv4 table
688  * running a callback function on each flow.
689  */
690 static int EBPFForEachFlowV4Table(ThreadVars *th_v, LiveDevice *dev, const char *name,
691  struct timespec *ctime,
692  struct ebpf_timeout_config *tcfg,
693  OpFlowForKey EBPFOpFlowForKey
694  )
695 {
696  struct flows_stats flowstats = { 0, 0, 0};
697  int mapfd = EBPFGetMapFDByName(dev->dev, name);
698  if (mapfd == -1)
699  return -1;
700 
701  struct flowv4_keys key = {}, next_key;
702  int found = 0;
703  unsigned int i;
704  uint64_t hash_cnt = 0;
705 
706  if (tcfg->cpus_count == 0) {
707  return 0;
708  }
709 
710  bool dead_flow = false;
711  while (bpf_map_get_next_key(mapfd, &key, &next_key) == 0) {
712  uint64_t bytes_cnt = 0;
713  uint64_t pkts_cnt = 0;
714  hash_cnt++;
715  if (dead_flow) {
716  EBPFDeleteKey(mapfd, &key);
717  dead_flow = false;
718  }
719  /* We use a per CPU structure so we will get a array of values. But if nr_cpus
720  * is 1 then we have a global hash. */
721  BPF_DECLARE_PERCPU(struct pair, values_array, tcfg->cpus_count);
722  memset(values_array, 0, sizeof(values_array));
723  int res = bpf_map_lookup_elem(mapfd, &next_key, values_array);
724  if (res < 0) {
725  SCLogDebug("no entry in v4 table for %d -> %d", key.port16[0], key.port16[1]);
726  SCLogDebug("errno: (%d) %s", errno, strerror(errno));
727  key = next_key;
728  continue;
729  }
730  for (i = 0; i < tcfg->cpus_count; i++) {
731  /* let's start accumulating value so we can compute the counters */
732  SCLogDebug("%d: Adding pkts %lu bytes %lu", i,
733  BPF_PERCPU(values_array, i).packets,
734  BPF_PERCPU(values_array, i).bytes);
735  pkts_cnt += BPF_PERCPU(values_array, i).packets;
736  bytes_cnt += BPF_PERCPU(values_array, i).bytes;
737  }
738  /* Get the corresponding Flow in the Flow table to compare and update
739  * its counters and lastseen if needed */
740  FlowKey flow_key;
741  if (tcfg->mode == AFP_MODE_XDP_BYPASS) {
742  flow_key.sp = ntohs(next_key.port16[0]);
743  flow_key.dp = ntohs(next_key.port16[1]);
744  flow_key.src.addr_data32[0] = next_key.src;
745  flow_key.dst.addr_data32[0] = next_key.dst;
746  } else {
747  flow_key.sp = next_key.port16[0];
748  flow_key.dp = next_key.port16[1];
749  flow_key.src.addr_data32[0] = ntohl(next_key.src);
750  flow_key.dst.addr_data32[0] = ntohl(next_key.dst);
751  }
752  flow_key.src.family = AF_INET;
753  flow_key.src.addr_data32[1] = 0;
754  flow_key.src.addr_data32[2] = 0;
755  flow_key.src.addr_data32[3] = 0;
756  flow_key.dst.family = AF_INET;
757  flow_key.dst.addr_data32[1] = 0;
758  flow_key.dst.addr_data32[2] = 0;
759  flow_key.dst.addr_data32[3] = 0;
760  flow_key.vlan_id[0] = next_key.vlan0;
761  flow_key.vlan_id[1] = next_key.vlan1;
762  if (next_key.ip_proto == 1) {
763  flow_key.proto = IPPROTO_TCP;
764  } else {
765  flow_key.proto = IPPROTO_UDP;
766  }
767  flow_key.recursion_level = 0;
768  flow_key.livedev_id = dev->id;
769  dead_flow = EBPFOpFlowForKey(&flowstats, dev, &next_key, sizeof(next_key), &flow_key,
770  ctime, pkts_cnt, bytes_cnt,
771  mapfd, tcfg->cpus_count);
772  if (dead_flow) {
773  found = 1;
774  }
775 
777  return 0;
778  }
779 
780  key = next_key;
781  }
782  if (dead_flow) {
783  EBPFDeleteKey(mapfd, &key);
784  found = 1;
785  }
786  SC_ATOMIC_ADD(dev->bypassed, flowstats.packets);
787 
788  LiveDevAddBypassStats(dev, flowstats.count, AF_INET);
789  SCLogInfo("IPv4 bypassed flow table size: %" PRIu64, hash_cnt);
790 
791  return found;
792 }
793 
794 /**
795  * Bypassed flows iterator for IPv6
796  *
797  * This function iterates on all the flows of the IPv4 table
798  * running a callback function on each flow.
799  */
800 static int EBPFForEachFlowV6Table(ThreadVars *th_v,
801  LiveDevice *dev, const char *name,
802  struct timespec *ctime,
803  struct ebpf_timeout_config *tcfg,
804  OpFlowForKey EBPFOpFlowForKey
805  )
806 {
807  struct flows_stats flowstats = { 0, 0, 0};
808  int mapfd = EBPFGetMapFDByName(dev->dev, name);
809  if (mapfd == -1)
810  return -1;
811 
812  struct flowv6_keys key = {}, next_key;
813  int found = 0;
814  unsigned int i;
815  uint64_t hash_cnt = 0;
816 
817  if (tcfg->cpus_count == 0) {
818  SCLogWarning("CPU count should not be 0");
819  return 0;
820  }
821 
822  uint64_t pkts_cnt = 0;
823  while (bpf_map_get_next_key(mapfd, &key, &next_key) == 0) {
824  uint64_t bytes_cnt = 0;
825  hash_cnt++;
826  if (pkts_cnt > 0) {
827  EBPFDeleteKey(mapfd, &key);
828  }
829  pkts_cnt = 0;
830  /* We use a per CPU structure so we will get a array of values. But if nr_cpus
831  * is 1 then we have a global hash. */
832  BPF_DECLARE_PERCPU(struct pair, values_array, tcfg->cpus_count);
833  memset(values_array, 0, sizeof(values_array));
834  int res = bpf_map_lookup_elem(mapfd, &next_key, values_array);
835  if (res < 0) {
836  SCLogDebug("no entry in v4 table for %d -> %d", key.port16[0], key.port16[1]);
837  key = next_key;
838  continue;
839  }
840  for (i = 0; i < tcfg->cpus_count; i++) {
841  /* let's start accumulating value so we can compute the counters */
842  SCLogDebug("%d: Adding pkts %lu bytes %lu", i,
843  BPF_PERCPU(values_array, i).packets,
844  BPF_PERCPU(values_array, i).bytes);
845  pkts_cnt += BPF_PERCPU(values_array, i).packets;
846  bytes_cnt += BPF_PERCPU(values_array, i).bytes;
847  }
848  /* Get the corresponding Flow in the Flow table to compare and update
849  * its counters and lastseen if needed */
850  FlowKey flow_key;
851  if (tcfg->mode == AFP_MODE_XDP_BYPASS) {
852  flow_key.sp = ntohs(next_key.port16[0]);
853  flow_key.dp = ntohs(next_key.port16[1]);
854  flow_key.src.family = AF_INET6;
855  flow_key.src.addr_data32[0] = next_key.src[0];
856  flow_key.src.addr_data32[1] = next_key.src[1];
857  flow_key.src.addr_data32[2] = next_key.src[2];
858  flow_key.src.addr_data32[3] = next_key.src[3];
859  flow_key.dst.family = AF_INET6;
860  flow_key.dst.addr_data32[0] = next_key.dst[0];
861  flow_key.dst.addr_data32[1] = next_key.dst[1];
862  flow_key.dst.addr_data32[2] = next_key.dst[2];
863  flow_key.dst.addr_data32[3] = next_key.dst[3];
864  } else {
865  flow_key.sp = next_key.port16[0];
866  flow_key.dp = next_key.port16[1];
867  flow_key.src.family = AF_INET6;
868  flow_key.src.addr_data32[0] = ntohl(next_key.src[0]);
869  flow_key.src.addr_data32[1] = ntohl(next_key.src[1]);
870  flow_key.src.addr_data32[2] = ntohl(next_key.src[2]);
871  flow_key.src.addr_data32[3] = ntohl(next_key.src[3]);
872  flow_key.dst.family = AF_INET6;
873  flow_key.dst.addr_data32[0] = ntohl(next_key.dst[0]);
874  flow_key.dst.addr_data32[1] = ntohl(next_key.dst[1]);
875  flow_key.dst.addr_data32[2] = ntohl(next_key.dst[2]);
876  flow_key.dst.addr_data32[3] = ntohl(next_key.dst[3]);
877  }
878  flow_key.vlan_id[0] = next_key.vlan0;
879  flow_key.vlan_id[1] = next_key.vlan1;
880  if (next_key.ip_proto == 1) {
881  flow_key.proto = IPPROTO_TCP;
882  } else {
883  flow_key.proto = IPPROTO_UDP;
884  }
885  flow_key.recursion_level = 0;
886  flow_key.livedev_id = dev->id;
887  pkts_cnt = EBPFOpFlowForKey(&flowstats, dev, &next_key, sizeof(next_key), &flow_key,
888  ctime, pkts_cnt, bytes_cnt,
889  mapfd, tcfg->cpus_count);
890  if (pkts_cnt > 0) {
891  found = 1;
892  }
893 
895  return 0;
896  }
897 
898  key = next_key;
899  }
900  if (pkts_cnt > 0) {
901  EBPFDeleteKey(mapfd, &key);
902  found = 1;
903  }
904  SC_ATOMIC_ADD(dev->bypassed, flowstats.packets);
905 
906  LiveDevAddBypassStats(dev, flowstats.count, AF_INET6);
907  SCLogInfo("IPv6 bypassed flow table size: %" PRIu64, hash_cnt);
908  return found;
909 }
910 
911 
912 int EBPFCheckBypassedFlowCreate(ThreadVars *th_v, struct timespec *curtime, void *data)
913 {
914  LiveDevice *ldev = NULL, *ndev;
915  struct ebpf_timeout_config *cfg = (struct ebpf_timeout_config *)data;
916  while(LiveDeviceForEach(&ldev, &ndev)) {
917  EBPFForEachFlowV4Table(th_v, ldev, "flow_table_v4",
918  curtime,
919  cfg, EBPFCreateFlowForKey);
920  EBPFForEachFlowV6Table(th_v, ldev, "flow_table_v6",
921  curtime,
922  cfg, EBPFCreateFlowForKey);
923  }
924 
925  return 0;
926 }
927 
928 void EBPFRegisterExtension(void)
929 {
930  g_livedev_storage_id = SCLiveDevStorageRegister("bpfmap", BpfMapsInfoFree);
931  g_flow_storage_id = SCFlowStorageRegister("bypassedlist", BypassedListFree);
932 }
933 
934 
935 #ifdef HAVE_PACKET_XDP
936 
937 static uint32_t g_redirect_iface_cpu_counter = 0;
938 
939 static int EBPFAddCPUToMap(const char *iface, uint32_t i)
940 {
941  int cpumap = EBPFGetMapFDByName(iface, "cpu_map");
942  uint32_t queue_size = 4096;
943  int ret;
944 
945  if (cpumap < 0) {
946  SCLogError("Can't find cpu_map");
947  return -1;
948  }
949  ret = bpf_map_update_elem(cpumap, &i, &queue_size, 0);
950  if (ret) {
951  SCLogError("Create CPU entry failed (err:%d)", ret);
952  return -1;
953  }
954  int cpus_available = EBPFGetMapFDByName(iface, "cpus_available");
955  if (cpus_available < 0) {
956  SCLogError("Can't find cpus_available map");
957  return -1;
958  }
959 
960  ret = bpf_map_update_elem(cpus_available, &g_redirect_iface_cpu_counter, &i, 0);
961  if (ret) {
962  SCLogError("Create CPU entry failed (err:%d)", ret);
963  return -1;
964  }
965  return 0;
966 }
967 
968 static void EBPFRedirectMapAddCPU(int i, void *data)
969 {
970  if (EBPFAddCPUToMap(data, i) < 0) {
971  SCLogError("Unable to add CPU %d to set", i);
972  } else {
973  g_redirect_iface_cpu_counter++;
974  }
975 }
976 
977 void EBPFBuildCPUSet(SCConfNode *node, char *iface)
978 {
979  uint32_t key0 = 0;
980  int mapfd = EBPFGetMapFDByName(iface, "cpus_count");
981  if (mapfd < 0) {
982  SCLogError("Unable to find 'cpus_count' map");
983  return;
984  }
985  g_redirect_iface_cpu_counter = 0;
986  if (node == NULL) {
987  bpf_map_update_elem(mapfd, &key0, &g_redirect_iface_cpu_counter,
988  BPF_ANY);
989  return;
990  }
991  if (BuildCpusetWithCallback("xdp-cpu-redirect", node, EBPFRedirectMapAddCPU, iface) < 0) {
992  SCLogWarning("Failed to parse XDP CPU redirect configuration");
993  return;
994  }
995  bpf_map_update_elem(mapfd, &key0, &g_redirect_iface_cpu_counter,
996  BPF_ANY);
997 }
998 
999 /**
1000  * Setup peer interface in XDP system
1001  *
1002  * Ths function set up the peer interface in the XDP maps used by the
1003  * bypass filter. The first map tx_peer has type device map and is
1004  * used to store the peer. The second map tx_peer_int is used by the
1005  * code to check if we have a peer defined for this interface.
1006  *
1007  * As the map are per device we just need maps with one single element.
1008  * In both case, we use the key 0 to enter element so XDP kernel code
1009  * is using the same key.
1010  */
1011 int EBPFSetPeerIface(const char *iface, const char *out_iface)
1012 {
1013  int mapfd = EBPFGetMapFDByName(iface, "tx_peer");
1014  if (mapfd < 0) {
1015  SCLogError("Unable to find 'tx_peer' map");
1016  return -1;
1017  }
1018  int intmapfd = EBPFGetMapFDByName(iface, "tx_peer_int");
1019  if (intmapfd < 0) {
1020  SCLogError("Unable to find 'tx_peer_int' map");
1021  return -1;
1022  }
1023 
1024  int key0 = 0;
1025  unsigned int peer_index = if_nametoindex(out_iface);
1026  if (peer_index == 0) {
1027  SCLogError("No iface '%s'", out_iface);
1028  return -1;
1029  }
1030  int ret = bpf_map_update_elem(mapfd, &key0, &peer_index, BPF_ANY);
1031  if (ret) {
1032  SCLogError("Create peer entry failed (err:%d)", ret);
1033  return -1;
1034  }
1035  ret = bpf_map_update_elem(intmapfd, &key0, &peer_index, BPF_ANY);
1036  if (ret) {
1037  SCLogError("Create peer entry failed (err:%d)", ret);
1038  return -1;
1039  }
1040  return 0;
1041 }
1042 
1043 /**
1044  * Bypass the flow on all ifaces it is seen on. This is used
1045  * in IPS mode.
1046  */
1047 
1048 int EBPFUpdateFlow(Flow *f, Packet *p, void *data)
1049 {
1050  BypassedIfaceList *ifl = (BypassedIfaceList *)SCFlowGetStorageById(f, g_flow_storage_id);
1051  if (ifl == NULL) {
1052  ifl = SCCalloc(1, sizeof(*ifl));
1053  if (ifl == NULL) {
1054  return 0;
1055  }
1056  ifl->dev = LiveDeviceGetById(p->livedev_id);
1057  SCFlowSetStorageById(f, g_flow_storage_id, ifl);
1058  return 1;
1059  }
1060  /* Look for packet iface in the list */
1061  BypassedIfaceList *ldev = ifl;
1062  while (ldev) {
1063  if (p->livedev_id == LiveDeviceGetId(ldev->dev)) {
1064  return 1;
1065  }
1066  ldev = ldev->next;
1067  }
1068  /* Call bypass function if ever not in the list */
1069  p->BypassPacketsFlow(p);
1070 
1071  /* Add iface to the list */
1072  BypassedIfaceList *nifl = SCCalloc(1, sizeof(*nifl));
1073  if (nifl == NULL) {
1074  return 0;
1075  }
1076  nifl->dev = LiveDeviceGetById(p->livedev_id);
1077  nifl->next = ifl;
1078  SCFlowSetStorageById(f, g_flow_storage_id, nifl);
1079  return 1;
1080 }
1081 
1082 #endif /* HAVE_PACKET_XDP */
1083 
1084 #endif
util-device-private.h
tm-threads.h
flow-bypass.h
FLOW_IS_IPV6
#define FLOW_IS_IPV6(f)
Definition: flow.h:170
LiveDevStorageId_
Definition: device-storage.h:31
FlowKey_::src
Address src
Definition: flow.h:308
FlowBypassInfo_
Definition: flow.h:529
SCFlowGetStorageById
void * SCFlowGetStorageById(const Flow *f, SCFlowStorageId id)
Definition: flow-storage.c:40
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:48
FlowKeyGetHash
uint32_t FlowKeyGetHash(FlowKey *fk)
Definition: flow-hash.c:301
Flow_
Flow data structure.
Definition: flow.h:354
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
th_v
ThreadVars * th_v
Definition: fuzz_iprep.c:20
LiveDevice_::id
uint16_t id
Definition: util-device-private.h:38
SCFlowStorageId
Definition: flow-storage.h:31
flow-hash.h
LiveDeviceGetById
LiveDevice * LiveDeviceGetById(const int id)
Definition: util-device.c:460
FlowBypassInfo_::tosrcbytecnt
uint64_t tosrcbytecnt
Definition: flow.h:534
LiveDeviceForEach
LiveDevice * LiveDeviceForEach(LiveDevice **ldev, LiveDevice **ndev)
Definition: util-device.c:468
device-storage.h
Flow_::livedev_id
uint16_t livedev_id
Definition: flow.h:399
p
Packet * p
Definition: fuzz_iprep.c:21
Packet_::BypassPacketsFlow
int(* BypassPacketsFlow)(struct Packet_ *)
Definition: decode.h:608
GetFlowBypassInfoID
SCFlowStorageId GetFlowBypassInfoID(void)
Definition: flow-util.c:223
FLOWLOCK_UNLOCK
#define FLOWLOCK_UNLOCK(fb)
Definition: flow.h:271
SCFlowStorageRegister
SCFlowStorageId SCFlowStorageRegister(const char *name, void(*Free)(void *))
Definition: flow-storage.c:61
SCTIME_FROM_SECS
#define SCTIME_FROM_SECS(s)
Definition: util-time.h:69
FlowBypassInfo_::todstbytecnt
uint64_t todstbytecnt
Definition: flow.h:536
FlowBypassInfo_::BypassUpdate
bool(* BypassUpdate)(Flow *f, void *data, time_t tsec)
Definition: flow.h:530
util-cpu.h
FlowBypassInfo_::BypassFree
void(* BypassFree)(void *data)
Definition: flow.h:531
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:418
FlowKey_::recursion_level
uint8_t recursion_level
Definition: flow.h:311
util-ebpf.h
ThreadVars_
Per thread variable structure.
Definition: threadvars.h:58
util-affinity.h
SCFlowSetStorageById
int SCFlowSetStorageById(Flow *f, SCFlowStorageId id, void *ptr)
Definition: flow-storage.c:45
THV_KILL
#define THV_KILL
Definition: threadvars.h:40
FlowBypassInfo_::todstpktcnt
uint64_t todstpktcnt
Definition: flow.h:535
LiveDevice_::dev
char * dev
Definition: util-device-private.h:33
FlowBypassInfo_::bypass_data
void * bypass_data
Definition: flow.h:532
SCLogWarning
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition: util-debug.h:262
FlowKey_::livedev_id
uint16_t livedev_id
Definition: flow.h:312
FlowKey_::sp
Port sp
Definition: flow.h:309
Packet_
Definition: decode.h:515
LiveDeviceGetId
uint16_t LiveDeviceGetId(const LiveDevice *dev)
Definition: util-device.c:452
LiveDevUseBypass
int LiveDevUseBypass(LiveDevice *dev)
Definition: util-device.c:541
LiveDevAddBypassStats
void LiveDevAddBypassStats(LiveDevice *dev, uint64_t cnt, int family)
Definition: util-device.c:563
FlowBypassInfo_::tosrcpktcnt
uint64_t tosrcpktcnt
Definition: flow.h:533
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:1192
SCLiveDevGetStorageById
void * SCLiveDevGetStorageById(LiveDevice *d, SCLiveDevStorageId id)
Get a value from a given LiveDevice storage.
Definition: device-storage.c:87
flow-storage.h
flags
uint8_t flags
Definition: decode-gre.h:0
suricata-common.h
FlowKey_::dst
Address dst
Definition: flow.h:308
Packet_::livedev_id
uint16_t livedev_id
Definition: decode.h:632
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:59
SCLogConfig
struct SCLogConfig_ SCLogConfig
Holds the config state used by the logging api.
FlowKey_::proto
uint8_t proto
Definition: flow.h:310
BuildCpusetWithCallback
int BuildCpusetWithCallback(const char *name, SCConfNode *node, void(*Callback)(int i, void *data), void *data)
Definition: util-affinity.c:227
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:74
FlowGetFromFlowKey
Flow * FlowGetFromFlowKey(FlowKey *key, struct timespec *ttime, const uint32_t hash)
Get or create a Flow using a FlowKey.
Definition: flow-hash.c:1085
FlowKey_
Definition: flow.h:307
Address_::family
char family
Definition: decode.h:114
SCFlowStorageId::id
int id
Definition: flow-storage.h:32
FlowKey_::vlan_id
uint16_t vlan_id[VLAN_MAX_LAYERS]
Definition: flow.h:313
flow.h
TmThreadsCheckFlag
int TmThreadsCheckFlag(ThreadVars *tv, uint32_t flag)
Check if a thread flag is set.
Definition: tm-threads.c:95
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:309
flows_stats::packets
uint64_t packets
Definition: flow-bypass.h:31
SCConfNode_
Definition: conf.h:37
flows_stats
Definition: flow-bypass.h:29