snort: snort3 plugin and DAQ
[vpp.git] / src / plugins / snort / daq_vpp.c
1 /* SPDX-License-Identifier: Apache-2.0
2  * Copyright(c) 2021 Cisco Systems, Inc.
3  */
4
5 #define _GNU_SOURCE
6 #include <string.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <stdbool.h>
10 #include <sys/socket.h>
11 #include <sys/un.h>
12 #include <sys/mman.h>
13 #include <errno.h>
14 #include <sys/epoll.h>
15
16 #include <vppinfra/cache.h>
17 #include <daq_dlt.h>
18 #include <daq_module_api.h>
19
20 #include "daq_vpp.h"
21
22 #define DAQ_VPP_VERSION 1
23
24 #if __x86_64__
25 #define VPP_DAQ_PAUSE() __builtin_ia32_pause ()
26 #elif defined(__aarch64__) || defined(__arm__)
27 #define VPP_DAQ_PAUSE() __asm__("yield")
28 #else
29 #define VPP_DAQ_PAUSE()
30 #endif
31
32 static DAQ_VariableDesc_t vpp_variable_descriptions[] = {
33   { "debug", "Enable debugging output to stdout",
34     DAQ_VAR_DESC_FORBIDS_ARGUMENT },
35 };
36
37 static DAQ_BaseAPI_t daq_base_api;
38
39 #define SET_ERROR(modinst, ...) daq_base_api.set_errbuf (modinst, __VA_ARGS__)
40
41 typedef struct _vpp_msg_pool
42 {
43   DAQ_MsgPoolInfo_t info;
44 } VPPMsgPool;
45
46 typedef struct _vpp_desc_data
47 {
48   uint32_t index;
49   uint32_t qpair_index;
50   DAQ_Msg_t msg;
51   DAQ_PktHdr_t pkthdr;
52 } VPPDescData;
53
54 typedef struct _vpp_bpool
55 {
56   int fd;
57   uint32_t size;
58   void *base;
59 } VPPBufferPool;
60
61 typedef struct _vpp_qpair
62 {
63   CLIB_CACHE_LINE_ALIGN_MARK (cacheline0);
64   uint32_t queue_size;
65   daq_vpp_desc_t *descs;
66   uint32_t *enq_ring;
67   uint32_t *deq_ring;
68   volatile uint32_t *enq_head;
69   volatile uint32_t *deq_head;
70   uint32_t next_desc;
71   int enq_fd;
72   int deq_fd;
73   VPPDescData *desc_data;
74   volatile int lock;
75 } VPPQueuePair;
76
77 typedef enum
78 {
79   DAQ_VPP_INPUT_MODE_INTERRUPT = 0,
80   DAQ_VPP_INPUT_MODE_POLLING,
81 } daq_vpp_input_mode_t;
82
83 typedef struct _vpp_context
84 {
85   /* config */
86   bool debug;
87
88   /* state */
89   uint32_t intf_count;
90   DAQ_ModuleInstance_h modinst;
91   VPPMsgPool pool;
92
93   /* socket */
94   int sock_fd;
95
96   /* shared memory */
97   uint32_t shm_size;
98   void *shm_base;
99   int shm_fd;
100
101   /* queue pairs */
102   uint8_t num_qpairs;
103   VPPQueuePair *qpairs;
104   uint32_t next_qpair;
105
106   /* epoll */
107   struct epoll_event *epoll_events;
108   int epoll_fd;
109
110   /* buffer pools */
111   uint8_t num_bpools;
112   VPPBufferPool *bpools;
113
114   daq_vpp_input_mode_t input_mode;
115   const char *socket_name;
116 } VPP_Context_t;
117
118 static VPP_Context_t *global_vpp_ctx = 0;
119
120 static int
121 vpp_daq_qpair_lock (VPPQueuePair *p)
122 {
123   int free = 0;
124   while (!__atomic_compare_exchange_n (&p->lock, &free, 1, 0, __ATOMIC_ACQUIRE,
125                                        __ATOMIC_RELAXED))
126     {
127       while (__atomic_load_n (&p->lock, __ATOMIC_RELAXED))
128         VPP_DAQ_PAUSE ();
129       free = 0;
130     }
131   return 0;
132 }
133
134 static void
135 vpp_daq_qpair_unlock (VPPQueuePair *p)
136 {
137   __atomic_store_n (&p->lock, 0, __ATOMIC_RELEASE);
138 }
139
140 static int
141 vpp_daq_module_load (const DAQ_BaseAPI_t *base_api)
142 {
143   if (base_api->api_version != DAQ_BASE_API_VERSION ||
144       base_api->api_size != sizeof (DAQ_BaseAPI_t))
145     return DAQ_ERROR;
146
147   daq_base_api = *base_api;
148
149   return DAQ_SUCCESS;
150 }
151
152 static int
153 vpp_daq_module_unload (void)
154 {
155   memset (&daq_base_api, 0, sizeof (daq_base_api));
156   return DAQ_SUCCESS;
157 }
158
159 static int
160 vpp_daq_get_variable_descs (const DAQ_VariableDesc_t **var_desc_table)
161 {
162   *var_desc_table = vpp_variable_descriptions;
163
164   return sizeof (vpp_variable_descriptions) / sizeof (DAQ_VariableDesc_t);
165 }
166
167 static int
168 vpp_daq_recvmsg (int fd, daq_vpp_msg_t *msg, int n_fds, int *fds)
169 {
170   const int ctl_sz =
171     CMSG_SPACE (sizeof (int) * n_fds) + CMSG_SPACE (sizeof (struct ucred));
172   char ctl[ctl_sz];
173   struct msghdr mh = {};
174   struct iovec iov[1];
175   struct cmsghdr *cmsg;
176
177   iov[0].iov_base = (void *) msg;
178   iov[0].iov_len = sizeof (daq_vpp_msg_t);
179   mh.msg_iov = iov;
180   mh.msg_iovlen = 1;
181   mh.msg_control = ctl;
182   mh.msg_controllen = ctl_sz;
183
184   memset (ctl, 0, ctl_sz);
185
186   int rv;
187   if ((rv = recvmsg (fd, &mh, 0)) != sizeof (daq_vpp_msg_t))
188     return DAQ_ERROR_NODEV;
189
190   cmsg = CMSG_FIRSTHDR (&mh);
191   while (cmsg)
192     {
193       if (cmsg->cmsg_level == SOL_SOCKET)
194         {
195           if (cmsg->cmsg_type == SCM_CREDENTIALS)
196             {
197               /* Do nothing */;
198             }
199           else if (cmsg->cmsg_type == SCM_RIGHTS)
200             {
201               memcpy (fds, CMSG_DATA (cmsg), n_fds * sizeof (int));
202             }
203         }
204       cmsg = CMSG_NXTHDR (&mh, cmsg);
205     }
206
207   return DAQ_SUCCESS;
208 }
209
210 static void
211 vpp_daq_destroy (void *handle)
212 {
213   VPP_Context_t *vc = (VPP_Context_t *) handle;
214
215   if (vc->shm_base != MAP_FAILED)
216     munmap (vc->shm_base, vc->shm_size);
217
218   if (vc->shm_fd != -1)
219     close (vc->shm_fd);
220
221   if (vc->bpools)
222     {
223       for (int i = 0; i < vc->num_bpools; i++)
224         {
225           VPPBufferPool *bp = vc->bpools + i;
226           if (bp->fd != -1)
227             close (bp->fd);
228           if (bp->base && bp->base != MAP_FAILED)
229             munmap (bp->base, bp->size);
230         }
231       free (vc->bpools);
232     }
233
234   if (vc->qpairs)
235     {
236       for (int i = 0; i < vc->num_qpairs; i++)
237         {
238           VPPQueuePair *qp = vc->qpairs + i;
239           if (qp->enq_fd != -1)
240             close (qp->enq_fd);
241           if (qp->deq_fd != -1)
242             close (qp->deq_fd);
243           if (qp->desc_data)
244             free (qp->desc_data);
245         }
246       free (vc->qpairs);
247     }
248
249   free (vc->epoll_events);
250   close (vc->sock_fd);
251   if (vc->epoll_fd != -1)
252     close (vc->epoll_fd);
253   free (vc);
254 }
255
256 #define ERR(rv, ...)                                                          \
257   {                                                                           \
258     SET_ERROR (modinst, __VA_ARGS__);                                         \
259     rval = rv;                                                                \
260     goto err;                                                                 \
261   }
262
263 static int
264 vpp_daq_instantiate (const DAQ_ModuleConfig_h modcfg,
265                      DAQ_ModuleInstance_h modinst, void **ctxt_ptr)
266 {
267   VPP_Context_t *vc = 0;
268   int rval = DAQ_ERROR;
269   daq_vpp_msg_t msg;
270   struct sockaddr_un sun = { .sun_family = AF_UNIX };
271   int i, fd = -1, shm_fd = -1;
272   const char *input;
273   uint8_t *base;
274
275   if (global_vpp_ctx)
276     {
277       *ctxt_ptr = global_vpp_ctx;
278       return DAQ_SUCCESS;
279     }
280
281   vc = calloc (1, sizeof (VPP_Context_t));
282
283   if (!vc)
284     ERR (DAQ_ERROR_NOMEM,
285          "%s: Couldn't allocate memory for the new VPP context!", __func__);
286
287   const char *varKey, *varValue;
288   daq_base_api.config_first_variable (modcfg, &varKey, &varValue);
289   while (varKey)
290     {
291       if (!strcmp (varKey, "debug"))
292         vc->debug = true;
293       else if (!strcmp (varKey, "input_mode"))
294         {
295           if (!strcmp (varValue, "interrupt"))
296             vc->input_mode = DAQ_VPP_INPUT_MODE_INTERRUPT;
297           else if (!strcmp (varValue, "polling"))
298             vc->input_mode = DAQ_VPP_INPUT_MODE_POLLING;
299         }
300       else if (!strcmp (varKey, "socket_name"))
301         {
302           vc->socket_name = varValue;
303         }
304       daq_base_api.config_next_variable (modcfg, &varKey, &varValue);
305     }
306
307   input = daq_base_api.config_get_input (modcfg);
308
309   if (!vc->socket_name)
310     /* try to use default socket path */
311     vc->socket_name = DAQ_VPP_DEFAULT_SOCKET_PATH;
312
313   if ((fd = socket (AF_UNIX, SOCK_SEQPACKET, 0)) < 0)
314     ERR (DAQ_ERROR_NODEV, "%s: Couldn't create socket!", __func__);
315
316   strncpy (sun.sun_path, vc->socket_name, sizeof (sun.sun_path) - 1);
317
318   if (connect (fd, (struct sockaddr *) &sun, sizeof (struct sockaddr_un)) != 0)
319     ERR (DAQ_ERROR_NODEV, "%s: Couldn't connect to socket! '%s'", __func__,
320          vc->socket_name);
321
322   /* craft and send connect message */
323   msg.type = DAQ_VPP_MSG_TYPE_HELLO;
324   snprintf ((char *) &msg.hello.inst_name, DAQ_VPP_INST_NAME_LEN - 1, "%s",
325             input);
326
327   if (send (fd, &msg, sizeof (msg), 0) != sizeof (msg))
328     ERR (DAQ_ERROR_NODEV, "%s: Couldn't send connect message!", __func__);
329
330   /* receive config message */
331   rval = vpp_daq_recvmsg (fd, &msg, 1, &shm_fd);
332
333   if (rval != DAQ_SUCCESS || msg.type != DAQ_VPP_MSG_TYPE_CONFIG ||
334       shm_fd == -1)
335     ERR (DAQ_ERROR_NODEV, "%s: Couldn't receive config message!", __func__);
336
337   vc->modinst = modinst;
338   vc->sock_fd = fd;
339   vc->epoll_fd = -1;
340   vc->intf_count = 1;
341   vc->num_bpools = msg.config.num_bpools;
342   vc->num_qpairs = msg.config.num_qpairs;
343   vc->shm_size = msg.config.shm_size;
344   vc->shm_fd = shm_fd;
345
346   vc->bpools = calloc (vc->num_bpools, sizeof (VPPBufferPool));
347   vc->qpairs = calloc (vc->num_qpairs, sizeof (VPPQueuePair));
348   vc->epoll_events = calloc (vc->num_qpairs, sizeof (struct epoll_event));
349
350   if (vc->bpools == 0 || vc->qpairs == 0)
351     ERR (DAQ_ERROR_NOMEM,
352          "%s: Couldn't allocate memory for the new VPP context!", __func__);
353
354   for (i = 0; i < vc->num_bpools; i++)
355     vc->bpools[i].fd = -1;
356
357   base =
358     mmap (0, vc->shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, vc->shm_fd, 0);
359
360   if (base == MAP_FAILED)
361     ERR (DAQ_ERROR_NOMEM,
362          "%s: Couldn't map shared memory for the new VPP context!", __func__);
363
364   vc->shm_base = base;
365
366   if (vc->debug)
367     {
368       printf ("[%s]\n", input);
369       printf ("  Shared memory size: %u\n", vc->shm_size);
370       printf ("  Number of buffer pools: %u\n", vc->num_bpools);
371       printf ("  Number of queue pairs: %u\n", vc->num_qpairs);
372     }
373
374   /* receive buffer pools */
375   for (int i = 0; i < vc->num_bpools; i++)
376     {
377       VPPBufferPool *bp = vc->bpools + i;
378       rval = vpp_daq_recvmsg (fd, &msg, 1, &bp->fd);
379       if (rval != DAQ_SUCCESS || msg.type != DAQ_VPP_MSG_TYPE_BPOOL ||
380           bp->fd == -1)
381         ERR (DAQ_ERROR_NODEV,
382              "%s: Failed to receive buffer pool message for the new "
383              "VPP context!",
384              __func__);
385       bp->size = msg.bpool.size;
386       bp->base = mmap (0, bp->size, PROT_READ, MAP_SHARED, bp->fd, 0);
387
388       if (bp->base == MAP_FAILED)
389         ERR (DAQ_ERROR_NOMEM,
390              "%s: Couldn't map shared memory for the new VPP context!",
391              __func__);
392       if (vc->debug)
393         printf ("  Buffer pool %u size: %u\n", i, bp->size);
394     }
395
396   if ((vc->epoll_fd = epoll_create (1)) == -1)
397     ERR (DAQ_ERROR_NODEV,
398          "%s: Couldn't create epoll fd for the new VPP context!", __func__);
399
400   /* receive queue pairs */
401   for (int i = 0; i < vc->num_qpairs; i++)
402     {
403       struct epoll_event ev = { .events = EPOLLIN };
404       int fds[2] = { -1, -1 };
405       uint32_t qsz;
406       VPPQueuePair *qp = vc->qpairs + i;
407       rval = vpp_daq_recvmsg (fd, &msg, 2, fds);
408       if (rval != DAQ_SUCCESS || msg.type != DAQ_VPP_MSG_TYPE_QPAIR ||
409           fds[0] == -1 || fds[1] == -1)
410         ERR (DAQ_ERROR_NODEV,
411              "%s: Failed to receive queu pair message for the new "
412              "VPP context!",
413              __func__);
414       qp->queue_size = 1 << msg.qpair.log2_queue_size;
415       qp->descs = (daq_vpp_desc_t *) (base + msg.qpair.desc_table_offset);
416       qp->enq_ring = (uint32_t *) (base + msg.qpair.enq_ring_offset);
417       qp->deq_ring = (uint32_t *) (base + msg.qpair.deq_ring_offset);
418       qp->enq_head = (uint32_t *) (base + msg.qpair.enq_head_offset);
419       qp->deq_head = (uint32_t *) (base + msg.qpair.deq_head_offset);
420       qp->enq_fd = fds[0];
421       qp->deq_fd = fds[1];
422       ev.data.u32 = i;
423
424       if (epoll_ctl (vc->epoll_fd, EPOLL_CTL_ADD, qp->enq_fd, &ev) == -1)
425         ERR (DAQ_ERROR_NODEV,
426              "%s: Failed to dequeue fd to epoll instance for the new "
427              "VPP context!",
428              __func__);
429
430       qsz = qp->queue_size;
431
432       qp->desc_data = calloc (qsz, sizeof (VPPDescData));
433       if (!qp->desc_data)
434         ERR (DAQ_ERROR_NOMEM,
435              "%s: Couldn't allocate memory for the new VPP context!",
436              __func__);
437
438       for (int j = 0; j < qsz; j++)
439         {
440           VPPDescData *dd = qp->desc_data + j;
441           DAQ_PktHdr_t *pkthdr = &dd->pkthdr;
442           DAQ_Msg_t *msg = &dd->msg;
443
444           dd->index = j;
445           dd->qpair_index = i;
446
447           pkthdr->ingress_group = DAQ_PKTHDR_UNKNOWN;
448           pkthdr->egress_group = DAQ_PKTHDR_UNKNOWN;
449
450           msg->type = DAQ_MSG_TYPE_PACKET;
451           msg->hdr_len = sizeof (DAQ_PktHdr_t);
452           msg->hdr = pkthdr;
453           msg->owner = vc->modinst;
454           msg->priv = dd;
455         }
456
457       if (vc->debug)
458         {
459           printf ("  Queue pair %u:\n", i);
460           printf ("    Size: %u\n", qp->queue_size);
461           printf ("    Enqueue fd: %u\n", qp->enq_fd);
462           printf ("    Dequeue fd: %u\n", qp->deq_fd);
463         }
464     }
465
466   *ctxt_ptr = global_vpp_ctx = vc;
467   return DAQ_SUCCESS;
468 err:
469   if (vc)
470     vpp_daq_destroy (vc);
471   else if (fd != -1)
472     close (fd);
473   return rval;
474 }
475
476 static int
477 vpp_daq_start (void *handle)
478 {
479   return DAQ_SUCCESS;
480 }
481
482 static int
483 vpp_daq_get_stats (void *handle, DAQ_Stats_t *stats)
484 {
485   memset (stats, 0, sizeof (DAQ_Stats_t));
486   return DAQ_SUCCESS;
487 }
488
489 static void
490 vpp_daq_reset_stats (void *handle)
491 {
492 }
493
494 static uint32_t
495 vpp_daq_get_capabilities (void *handle)
496 {
497   uint32_t capabilities = DAQ_CAPA_BLOCK | DAQ_CAPA_UNPRIV_START;
498   return capabilities;
499 }
500
501 static int
502 vpp_daq_get_datalink_type (void *handle)
503 {
504   return DLT_IPV4;
505 }
506
507 static inline uint32_t
508 vpp_daq_msg_receive_one (VPP_Context_t *vc, VPPQueuePair *qp,
509                          const DAQ_Msg_t *msgs[], unsigned max_recv)
510 {
511   uint32_t n_recv, n_left;
512   uint32_t head, next, mask = qp->queue_size - 1;
513
514   if (max_recv == 0)
515     return 0;
516
517   vpp_daq_qpair_lock (qp);
518   next = qp->next_desc;
519   head = __atomic_load_n (qp->enq_head, __ATOMIC_ACQUIRE);
520   n_recv = n_left = head - next;
521
522   if (n_left > max_recv)
523     {
524       n_left = n_recv = max_recv;
525     }
526
527   while (n_left--)
528     {
529       uint32_t desc_index = qp->enq_ring[next & mask];
530       daq_vpp_desc_t *d = qp->descs + desc_index;
531       VPPDescData *dd = qp->desc_data + desc_index;
532       dd->pkthdr.pktlen = d->length;
533       dd->pkthdr.address_space_id = d->address_space_id;
534       dd->msg.data = vc->bpools[d->buffer_pool].base + d->offset;
535       next = next + 1;
536
537       msgs[0] = &dd->msg;
538       msgs++;
539     }
540
541   qp->next_desc = next;
542   vpp_daq_qpair_unlock (qp);
543
544   return n_recv;
545 }
546
547 static unsigned
548 vpp_daq_msg_receive (void *handle, const unsigned max_recv,
549                      const DAQ_Msg_t *msgs[], DAQ_RecvStatus *rstat)
550 {
551   VPP_Context_t *vc = (VPP_Context_t *) handle;
552   uint32_t n_qpairs_left = vc->num_qpairs;
553   uint32_t n, n_events, n_recv = 0;
554
555   /* first, we visit all qpairs. If we find any work there then we can give
556    * it back immediatelly. To avoid bias towards qpair 0 we remeber what
557    * next qpair */
558   while (n_qpairs_left)
559     {
560       VPPQueuePair *qp = vc->qpairs + vc->next_qpair;
561
562       if ((n = vpp_daq_msg_receive_one (vc, qp, msgs, max_recv - n_recv)))
563         {
564           msgs += n;
565           n_recv += n;
566         }
567
568       /* next */
569       vc->next_qpair++;
570       if (vc->next_qpair == vc->num_qpairs)
571         vc->next_qpair = 0;
572       n_qpairs_left--;
573     }
574
575   if (vc->input_mode == DAQ_VPP_INPUT_MODE_POLLING)
576     {
577       *rstat = DAQ_RSTAT_OK;
578       return n_recv;
579     }
580
581   if (n_recv)
582     {
583       *rstat = DAQ_RSTAT_OK;
584       return n_recv;
585     }
586
587   n_events = epoll_wait (vc->epoll_fd, vc->epoll_events, vc->num_qpairs, 1000);
588
589   if (n_events < 1)
590     {
591       *rstat = n_events == -1 ? DAQ_RSTAT_ERROR : DAQ_RSTAT_TIMEOUT;
592       return 0;
593     }
594
595   for (int i = 0; i < n_events; i++)
596     {
597       uint64_t ctr;
598       VPPQueuePair *qp = vc->qpairs + vc->epoll_events[i].data.u32;
599
600       if ((n = vpp_daq_msg_receive_one (vc, qp, msgs, max_recv - n_recv)))
601         {
602           msgs += n;
603           n_recv += n;
604         }
605
606       (void) read (qp->enq_fd, &ctr, sizeof (ctr));
607     }
608
609   *rstat = DAQ_RSTAT_OK;
610   return n_recv;
611 }
612
613 static int
614 vpp_daq_msg_finalize (void *handle, const DAQ_Msg_t *msg, DAQ_Verdict verdict)
615 {
616   VPP_Context_t *vc = (VPP_Context_t *) handle;
617   VPPDescData *dd = msg->priv;
618   VPPQueuePair *qp = vc->qpairs + dd->qpair_index;
619   daq_vpp_desc_t *d;
620   uint32_t mask, head;
621   uint64_t counter_increment = 1;
622   int rv, retv = DAQ_SUCCESS;
623
624   vpp_daq_qpair_lock (qp);
625   mask = qp->queue_size - 1;
626   head = *qp->deq_head;
627   d = qp->descs + dd->index;
628   if (verdict == DAQ_VERDICT_PASS)
629     d->action = DAQ_VPP_ACTION_FORWARD;
630   else
631     d->action = DAQ_VPP_ACTION_DROP;
632
633   qp->deq_ring[head & mask] = dd->index;
634   head = head + 1;
635   __atomic_store_n (qp->deq_head, head, __ATOMIC_RELEASE);
636
637   if (vc->input_mode == DAQ_VPP_INPUT_MODE_INTERRUPT)
638     {
639       rv = write (qp->deq_fd, &counter_increment, sizeof (counter_increment));
640
641       if (rv != sizeof (counter_increment))
642         retv = DAQ_ERROR;
643     }
644
645   vpp_daq_qpair_unlock (qp);
646   return retv;
647 }
648
649 static int
650 vpp_daq_get_msg_pool_info (void *handle, DAQ_MsgPoolInfo_t *info)
651 {
652   VPP_Context_t *vc = (VPP_Context_t *) handle;
653
654   vc->pool.info.available = 128;
655   vc->pool.info.size = 256;
656
657   *info = vc->pool.info;
658
659   return DAQ_SUCCESS;
660 }
661
662 DAQ_SO_PUBLIC
663 const DAQ_ModuleAPI_t DAQ_MODULE_DATA = {
664   /* .api_version = */ DAQ_MODULE_API_VERSION,
665   /* .api_size = */ sizeof (DAQ_ModuleAPI_t),
666   /* .module_version = */ DAQ_VPP_VERSION,
667   /* .name = */ "vpp",
668   /* .type = */ DAQ_TYPE_INTF_CAPABLE | DAQ_TYPE_INLINE_CAPABLE |
669     DAQ_TYPE_MULTI_INSTANCE,
670   /* .load = */ vpp_daq_module_load,
671   /* .unload = */ vpp_daq_module_unload,
672   /* .get_variable_descs = */ vpp_daq_get_variable_descs,
673   /* .instantiate = */ vpp_daq_instantiate,
674   /* .destroy = */ vpp_daq_destroy,
675   /* .set_filter = */ NULL,
676   /* .start = */ vpp_daq_start,
677   /* .inject = */ NULL,
678   /* .inject_relative = */ NULL,
679   /* .interrupt = */ NULL,
680   /* .stop = */ NULL,
681   /* .ioctl = */ NULL,
682   /* .get_stats = */ vpp_daq_get_stats,
683   /* .reset_stats = */ vpp_daq_reset_stats,
684   /* .get_snaplen = */ NULL,
685   /* .get_capabilities = */ vpp_daq_get_capabilities,
686   /* .get_datalink_type = */ vpp_daq_get_datalink_type,
687   /* .config_load = */ NULL,
688   /* .config_swap = */ NULL,
689   /* .config_free = */ NULL,
690   /* .msg_receive = */ vpp_daq_msg_receive,
691   /* .msg_finalize = */ vpp_daq_msg_finalize,
692   /* .get_msg_pool_info = */ vpp_daq_get_msg_pool_info,
693 };