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