capture_threads_example.c Source File

Reference Documentation

Platform
Napatech SmartNIC
Content Type
Reference Information
Capture Software Version
Link™ Capture Software 12.15
Napatech Software Suite: examples/net/capture_threads/capture_threads_example.c Source File
capture_threads_example.c
Go to the documentation of this file.
1 /*
2  *
3  * Copyright 2025 Napatech A/S. All Rights Reserved.
4  *
5  * 1. Copying, modification, and distribution of this file, or executable
6  * versions of this file, is governed by the terms of the Napatech Software
7  * license agreement under which this file was made available. If you do not
8  * agree to the terms of the license do not install, copy, access or
9  * otherwise use this file.
10  *
11  * 2. Under the Napatech Software license agreement you are granted a
12  * limited, non-exclusive, non-assignable, copyright license to copy, modify
13  * and distribute this file in conjunction with Napatech SmartNIC's and
14  * similar hardware manufactured or supplied by Napatech A/S.
15  *
16  * 3. The full Napatech Software License Agreement is included in this
17  * distribution, please see "NA-0009 Software License Agreement.pdf"
18  *
19  * 4. Redistributions of source code must retain this copyright notice,
20  * list of conditions and the following disclaimer.
21  *
22  * THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTIES, EXPRESS OR
23  * IMPLIED, AND NAPATECH DISCLAIMS ALL IMPLIED WARRANTIES INCLUDING ANY
24  * IMPLIED WARRANTY OF TITLE, MERCHANTABILITY, NONINFRINGEMENT, OR OF
25  * FITNESS FOR A PARTICULAR PURPOSE. TO THE EXTENT NOT PROHIBITED BY
26  * APPLICABLE LAW, IN NO EVENT SHALL NAPATECH BE LIABLE FOR PERSONAL INJURY,
27  * OR ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES WHATSOEVER,
28  * INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF PROFITS, CORRUPTION OR
29  * LOSS OF DATA, FAILURE TO TRANSMIT OR RECEIVE ANY DATA OR INFORMATION,
30  * BUSINESS INTERRUPTION OR ANY OTHER COMMERCIAL DAMAGES OR LOSSES, ARISING
31  * OUT OF OR RELATED TO YOUR USE OR INABILITY TO USE NAPATECH SOFTWARE OR
32  * SERVICES OR ANY THIRD PARTY SOFTWARE OR APPLICATIONS IN CONJUNCTION WITH
33  * THE NAPATECH SOFTWARE OR SERVICES, HOWEVER CAUSED, REGARDLESS OF THE THEORY
34  * OF LIABILITY (CONTRACT, TORT OR OTHERWISE) AND EVEN IF NAPATECH HAS BEEN
35  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME JURISDICTIONS DO NOT ALLOW
36  * THE EXCLUSION OR LIMITATION OF LIABILITY FOR PERSONAL INJURY, OR OF
37  * INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY TO YOU.
38  *
39  *
40 
41  */
42 
43 /**
44  * @example net/capture/capture_threads_example.c
45  * @section capture_threads_example_description Description
46  *
47  * This source file is an example of how to do capture to disk via the
48  * segment interface using NTAPI. This example shows the possibility
49  * of processing multiple streams within multiple threads.
50  *
51  * Each thread processes one stream. Within a thread, the first loop waits
52  * for a packet corresponding to the time of the last NTPL command execution.
53  * The next loop then processes the received segments for a specific stream.
54  *
55  * The following NTAPI functions are used:
56  * - @ref NT_Init()
57  * - @ref NT_NetRxOpen()
58  * - @ref NT_NTPL()
59  * - @ref NT_NetRxRead()
60  * - @ref NT_NetRxGet()
61  * - @ref NT_NET_GET_SEGMENT_PTR()
62  * - @ref NT_NET_GET_SEGMENT_LENGTH()
63  * - @ref NT_NET_GET_SEGMENT_TIMESTAMP()
64  * - @ref NT_NetRxRelease()
65  * - @ref NT_NetRxClose()
66  * - @ref NT_Done()
67  * - @ref NT_ExplainError()
68  *
69  * @section capture_threads_example_prerequisites Prerequisites
70  * A Napatech capture accelerator is need to run this example. The ntservice.ini
71  * must have at least one HostBuffersRx defined. Below is an example
72  * of a minimum ini-file. It will create a 32MB RX hostbuffer from
73  * NUMA node 0.
74  * @code
75  * [System]
76  * TimestampFormat = NATIVE
77  *
78  * [Adapter0]
79  * AdapterType = NT20E
80  * BusId = 00:0a:00.00
81  * HostBuffersRx = [1,32,0]
82  * @endcode
83  *
84  * @section capture_threads_example_flow Program flow
85  * @{
86  * The following is required to perform capture of segments to disk:
87  * - \#include/nt.h - Applications/Tools only need to include @ref
88  * nt.h to obtain prototypes, macros etc. from NTAPI.
89  * - @ref NT_Init(@ref NTAPI_VERSION) - Initialize the NTAPI
90  * library. @ref NTAPI_VERSION is a define that describes the version
91  * of the API described in the header files included by @ref
92  * nt.h. NT_Init() will ask the NTAPI library to convert return data
93  * to the @ref NTAPI_VERSION if possible. This will ensure that
94  * applications can run on NTAPI libraries of newer versions.
95  * - @ref NT_NTPL() - Assign traffic to the stream. A stream does not
96  * return data until traffic is assigned to it by a filter. Stream
97  * IDs can be shared between other streams.
98  * - @ref NT_NetRxOpen() - Open the stream using a stream ID.
99  * The stream ID must match the one used when creating the
100  * filter.
101  * - NT_NetRxRead() - Get the file header. A NT file header must be
102  * written to the beginning of the file after the last NT_NTPL()
103  * call has been made. Set @ref NtNetRx_s::cmd "NtNetRx_s.cmd"=@ref
104  * NT_NETRX_READ_CMD_GET_FILE_HEADER and issue the NT_NetRxRead()
105  * call. The fileheader is returned in @ref
106  * NtNetRxFileHeader_s::data
107  * "NtNetRx_s.u.fileheader.data".
108  * - Create the capture file and write NT header. Use the OS specific
109  * functions to create a new capture file.
110  * - Optional step. Wait until we start seeing segments that are hit
111  * by the NTPL assign command. This is done to avoid getting
112  * segments that are not fully classified by the stream.
113  * NT_NetRxGet() is called with a timeout of 1000ms and will return
114  * NT_STATUS_TIMEOUT in case nothing is received within 1000ms and
115  * will return NT_SUCCESS when a segment is returned. Segments with NT_NET_GET_SEGMENTLENGTH()==0
116  * can be returned so it is needed to check for the segment length before using data within
117  * the segment. The NT_NET_GET_SEGMENT_TIMESTAMP() macro can still be used on the empty segments.
118  * Return values different from that is an indication of an error. Segments that
119  * are prior to the expected time are released via NT_NetRxRelease().
120  * - NT_NetRxGet(), write to file and NT_NetRxRelease() - Receive
121  * segments, write to disk and release segments. The @ref
122  * SegmentMacros are used to find the segment and length and
123  * timestamp of the segment:
124  * - @ref NT_NET_GET_SEGMENT_PTR() - Get a pointer to the segment.
125  * - @ref NT_NET_GET_SEGMENT_LENGTH() - Get length of the segment to store.
126  * - @ref NT_NET_GET_SEGMENT_TIMESTAMP() - The time the segment was delivered.
127  * - @ref _nt_net_build_pkt_netbuf() and @ref _nt_net_get_next_packet() are used to traverse
128  * packets inside a segment. This is usefull if inspection is needed before saving the
129  * segment.
130  * - NT_NetRxClose() - Close the stream when terminating.
131  * This will close the stream and release the NTPL assignment made on the hostbuffer.
132  * - Close captured file
133  * - @ref NT_Done() - Close down the NTAPI library.
134  *
135  *<hr>
136  * @section capture_threads_example_code Code
137  * @}
138  */
139 
140 // Include this in order to access the Napatech API
141 #include <nt.h>
142 
143 #if defined(__linux__) || defined(__FreeBSD__)
144  #include <unistd.h>
145  #include <signal.h>
146  #include <pthread.h>
147  #include <stdatomic.h>
148 #elif defined(WIN32) || defined (WIN64)
149  #include <process.h> // threading
150 #endif
151 
152 #define ARRAY_SIZE(a) (sizeof((a))/sizeof(*(a)))
153 
154 #if defined(WIN32) || defined (WIN64)
155  #define snprintf(dst, ...) _snprintf_s((dst), _countof(dst), __VA_ARGS__)
156 #endif
157 
158 #if defined(_MSC_VER)
159  // Threading
160  typedef HANDLE pthread_t;
161  typedef unsigned (__stdcall *start_address_t)(void *parameter);
162  int pthread_create(HANDLE *thread, DWORD *attr, start_address_t start_routine, void *parameter);
163  int pthread_join(HANDLE thread, void **value_ptr);
164 
165  void usleep(unsigned long usec);
166 #endif
167 
168 #if defined(WIN32) || defined (WIN64)
169  volatile int appRunning = 0; // The application will run as long as appRunning == 1
170  volatile int fileHeaderExists = 0; // Write file header only once
171  volatile int fileHeaderWritten = 0; // File header is written
172 
173  volatile int numSegments = 0; // The number of segments received
174  volatile int numPackets = 0; // The number of packets received
175  volatile uint64_t numBytes = 0; // The number of bytes received
176  volatile uint64_t numBytesWire = 0; // The number of bytes received on the wire
177 #else
178  atomic_int appRunning = 0; // The application will run as long as appRunning == 1
179  atomic_int fileHeaderExists = 0; // Write file header only once
180  atomic_int fileHeaderWritten = 0; // File header is written
181 
182  atomic_int numSegments = 0; // The number of segments received
183  atomic_int numPackets = 0; // The number of packets received
184  atomic_ullong numBytes = 0; // The number of bytes received
185  atomic_ullong numBytesWire = 0; // The number of bytes received on the wire
186 #endif
187 
188 struct ntpcap_ts_s {
189  uint32_t sec;
190  uint32_t usec;
191 };
192 
193 struct ntpcap_hdr_s {
194  struct ntpcap_ts_s ts;
195  uint32_t caplen;
196  uint32_t wirelen;
197 };
198 
199 typedef struct RxThread_s {
200  pthread_t thread; // This thread
201  uint32_t streamId; // NT Stream Id to service by this thread
202  uint64_t ntplTs; // Time when the NTPL command is in effect
203  FILE *hf; // File handle
204 } RxThread_t;
205 
206 /*****************************************************************************
207  Ctrl-C signal handler routine.
208 ******************************************************************************/
209 #if defined(WIN32) || defined (WIN64)
210 static BOOL WINAPI StopApplication(int sig)
211 {
212  (void) sig;
213  appRunning = 0;
214  return TRUE;
215 }
216 #else
217 static void StopApplication(int sig)
218 {
219  if (sig == SIGINT)
220  appRunning = 0;
221 }
222 #endif
223 
224 static void HandleErrorStatus(int status, const char *message) {
225  if (status != NT_SUCCESS) {
226  char errorBuffer[NT_ERRBUF_SIZE]; // Error buffer
227  NT_ExplainError(status, errorBuffer, sizeof(errorBuffer));
228  fprintf(stderr, "%s: %s\n", message, errorBuffer);
229  exit(EXIT_FAILURE);
230  }
231 }
232 
233 static int NtplConfigure(NtNtplInfo_t *ntplInfo, const char **commands, int count) {
234  int status;
235  NtConfigStream_t hCfgStream; // Handle to a config stream
236 
237  status = NT_ConfigOpen(&hCfgStream, "TestStream");
238  HandleErrorStatus(status, "NT_ConfigOpen() failed");
239 
240  for (int i = 0; i < count; ++i) {
241  status = NT_NTPL(hCfgStream, commands[i], ntplInfo, NT_NTPL_PARSER_VALIDATE_NORMAL);
242  HandleErrorStatus(status, "NT_NTPL() failed");
243  }
244 
245  status = NT_ConfigClose(hCfgStream);
246  HandleErrorStatus(status, "NT_ConfigClose() failed");
247 
248  return status;
249 }
250 
251 
252 /*****************************************************************************
253  * Open a RX stream and receiving packets until an end-task signal is received.
254 ******************************************************************************/
255 #if defined(WIN32) || defined (WIN64)
256  static unsigned __stdcall RxTask(void *arg)
257 #else
258  static void *RxTask(void *arg)
259 #endif
260 {
261  int status = 0;
262  RxThread_t *pRxThread = (RxThread_t *)arg; // this pointer for this thread
263  char streamName[20]; // Stream name
264  char errorBuffer[NT_ERRBUF_SIZE]; // Error buffer
265  NtNetStreamRx_t hNetRx = NULL; // Handle to the RX stream
266  NtNetBuf_t hNetBuf = NULL; // Net buffer container. Segment data is returned in this when calling NT_NetRxGet().
267  NtNetRx_t readCmd; // NetRx read command structure.
268  struct NtNetBuf_s pktNetBuf; // Packet netbuf structure.
269  struct ntpcap_ts_s* pTs = (struct ntpcap_ts_s *)(void *)&(pRxThread->ntplTs); // Used if PCAP header is used
270 
271  // Create a stream name
272  snprintf(streamName, sizeof(streamName), "RxStream_%i", pRxThread->streamId);
273  printf("Starting RxStream thread: %s\n", streamName);
274 
275  // Get a stream handle with stream ID. NT_NET_INTERFACE_SEGMENT specify that we will receive data in a segment based matter.
276  status = NT_NetRxOpen(&hNetRx, streamName, NT_NET_INTERFACE_SEGMENT, pRxThread->streamId, -1);
277  if (status != NT_SUCCESS) {
278  appRunning = 0;
279  NT_ExplainError(status, errorBuffer, sizeof(errorBuffer));
280  fprintf(stderr, "RxStream [%i]: NT_NetRxOpen() failed: %s\n", pRxThread->streamId, errorBuffer);
281  goto thread_end;
282  }
283 
284  if (fileHeaderExists == 0) {
285  fileHeaderExists = 1;
286  // Read the file header.
288  status = NT_NetRxRead(hNetRx, &readCmd);
289  if (status != NT_SUCCESS) {
290  appRunning = 0;
291  NT_ExplainError(status, errorBuffer, sizeof(errorBuffer));
292  fprintf(stderr, "RxStream [%i]: NT_NetRxRead() failed: %s\n", pRxThread->streamId, errorBuffer);
293  goto thread_end;
294  }
295 
296  // Write the file header
297  if (fwrite(readCmd.u.fileheader.data, (size_t)readCmd.u.fileheader.size, 1, pRxThread->hf) <= 0) {
298  appRunning = 0;
299  fprintf(stderr, "RxStream [%i]: Failed writing file header\n", pRxThread->streamId);
300  goto thread_end;
301  }
302  fileHeaderWritten = 1;
303  }
304 
305  // Wait until file header is written to the file
306  while (appRunning == 1 && fileHeaderWritten == 0) {
307  usleep(1000);
308  }
309 
310  // Tell main function that this is ready to receive packets, and then start the loop
311  // ready->fetch_add(1);
312 
313  // Optional step. Wait for the first packet that hit the NTPL assign command
314  while (appRunning == 1) {
315  // Get next packet or continue.
316  status = NT_NetRxGetNextPacket(hNetRx, &hNetBuf, 1000);
317  if (status != NT_SUCCESS) {
318  if (status == NT_STATUS_TIMEOUT || status == NT_STATUS_TRYAGAIN) {
319  // Timeouts are ok, we just need to wait a little longer for a segment
320  continue;
321  }
322  appRunning = 0;
323  NT_ExplainError(status, errorBuffer, sizeof(errorBuffer));
324  fprintf(stderr, "RxStream [%i]: NT_NetRxRead() failed: %s\n", pRxThread->streamId, errorBuffer);
325  goto thread_end;
326  }
327 
328  // We got a segment. Check if the timestamp is newer than when the NTPL assign command was applied
329  // If PCAP header configured, we need to convert the timestamps received
331  if ((((struct ntpcap_ts_s *)NT_NET_GET_SEGMENT_PTR(hNetBuf))->sec * 1000000 + ((struct ntpcap_ts_s *)NT_NET_GET_SEGMENT_PTR(hNetBuf))->usec) >
332  (pTs->sec * 1000000 + pTs->usec)) {
333  break; // Break out, we have received a segment that is received after the NTPL assign command was applied
334  }
335  } else
337  if ((((struct ntpcap_ts_s *)NT_NET_GET_SEGMENT_PTR(hNetBuf))->sec * 1000000000 + ((struct ntpcap_ts_s *)NT_NET_GET_SEGMENT_PTR(hNetBuf))->usec) >
338  (pTs->sec * 1000000000 + pTs->usec)) {
339  break; // Break out, we have received a segment that is received after the NTPL assign command was applied
340  }
341  } else {
342  if (NT_NET_GET_SEGMENT_TIMESTAMP(hNetBuf) > pRxThread->ntplTs) {
343  break; // Break out, we have received a segment that is received after the NTPL assign command was applied
344  }
345  }
346 
347  // Release the segment as it is too "old".
348  status = NT_NetRxRelease(hNetRx, hNetBuf);
349  if (status != NT_SUCCESS) {
350  appRunning = 0;
351  NT_ExplainError(status, errorBuffer, sizeof(errorBuffer));
352  fprintf(stderr, "RxStream [%i]: NT_NetRxRelease() failed: %s\n", pRxThread->streamId, errorBuffer);
353  goto thread_end;
354  }
355  }
356 
357  // Write 10 segments to disk
358  while (appRunning == 1 && hNetBuf != NULL && numSegments < 10) {
359  if (NT_NET_GET_SEGMENT_LENGTH(hNetBuf)) {
360  // Increment the number of segments processed.
361  numSegments++;
362 
363  // Increment the bytes received
365  printf("%016llx - RxStream [%i]: Received segment of %lu bytes.\n",
366  (unsigned long long)NT_NET_GET_SEGMENT_TIMESTAMP(hNetBuf), pRxThread->streamId, NT_NET_GET_SEGMENT_LENGTH(hNetBuf));
367 
368  // Optional step. Here all packets are inspected before storing
369  // Start by building a packet netbuf structure
372  {
373  struct ntpcap_hdr_s* pHdr = (struct ntpcap_hdr_s *)NT_NET_GET_SEGMENT_PTR(hNetBuf);
374  uint64_t segLength = NT_NET_GET_SEGMENT_LENGTH(hNetBuf);
375  uint64_t totSegmentBytes = 0;
376  while (totSegmentBytes < segLength) {
377  if (pHdr->wirelen) {
378  numPackets++;
379  numBytesWire += pHdr->wirelen;
380  }
381  totSegmentBytes += pHdr->caplen + (uint32_t)sizeof(struct ntpcap_hdr_s);
382  pHdr = (struct ntpcap_hdr_s *)((uint8_t *)pHdr + pHdr->caplen + sizeof(struct ntpcap_hdr_s));
383  }
384  } else {
385  _nt_net_build_pkt_netbuf(hNetBuf, &pktNetBuf);
386  do {
387  // Just count the amount of packets and wire length
388  numPackets++;
389  numBytesWire += NT_NET_GET_PKT_WIRE_LENGTH((&pktNetBuf));
390  } while (_nt_net_get_next_packet(hNetBuf, NT_NET_GET_SEGMENT_LENGTH(hNetBuf), &pktNetBuf)>0);
391  }
392 
393  if (fwrite(NT_NET_GET_SEGMENT_PTR(hNetBuf), NT_NET_GET_SEGMENT_LENGTH(hNetBuf), 1, pRxThread->hf) <= 0) {
394  appRunning = 0;
395  fprintf(stderr, "RxStream [%i]: Failed writing segment\n", pRxThread->streamId);
396  }
397  }
398 
399  // Release the current segment
400  status = NT_NetRxRelease(hNetRx, hNetBuf);
401  if (status != NT_SUCCESS) {
402  appRunning = 0;
403  NT_ExplainError(status, errorBuffer, sizeof(errorBuffer));
404  fprintf(stderr, "RxStream [%i]: NT_NetRxRelease() failed: %s\n", pRxThread->streamId, errorBuffer);
405  goto thread_end;
406  }
407 
408  if (numSegments == 10) {
409  appRunning = 0;
410  break;
411  }
412 
413  // Get the next segment
414  while (appRunning == 1) {
415  status = NT_NetRxGet(hNetRx, &hNetBuf, 1000);
416  if (status != NT_SUCCESS) {
417  if ((status == NT_STATUS_TIMEOUT) || (status == NT_STATUS_TRYAGAIN)) {
418  // Timeouts are ok, we just need to wait a little longer for a segment
419  continue;
420  }
421  appRunning = 0;
422  NT_ExplainError(status, errorBuffer, sizeof(errorBuffer));
423  fprintf(stderr, "RxStream [%i]: NT_NetRxGet() failed: %s\n", pRxThread->streamId, errorBuffer);
424  goto thread_end;
425  }
426  break; // We got a segment
427  }
428  }
429 
430  // Close the RX stream.
431  if (hNetRx != NULL) {
432  status = NT_NetRxClose(hNetRx);
433  if (status != NT_SUCCESS) {
434  appRunning = 0;
435  NT_ExplainError(status, errorBuffer, sizeof(errorBuffer));
436  fprintf(stderr, "RxStream [%i]: NT_NetRxClose() failed: %s\n", pRxThread->streamId, errorBuffer);
437  }
438  }
439 
440 thread_end:
441 #if defined(WIN32) || defined (WIN64)
442  { _endthreadex(0); return 0; }
443 #else
444  return (void *)NULL;
445 #endif
446 }
447 
448 #define numStreams 3
449 
450 /*****************************************************************************
451  Main routine. It initializes, configures FPGA, starts needed threads.
452  On Ctrl-C it stops looping and begins the cleanup process.
453 ******************************************************************************/
454 int main(void)
455 {
456  FILE* hf = NULL; // File handle
457  int status = 0; // Status variable
458  NtNtplInfo_t ntplInfo; // Return data structure from the NT_NTPL() call.
459 
460  // Set up ctrl+c handler
461 #if defined(WIN32) || defined (WIN64)
462  SetConsoleCtrlHandler((PHANDLER_ROUTINE)StopApplication, TRUE);
463 #else
464  struct sigaction newaction; // Ctrl+c handle
465  memset(&newaction, 0, sizeof(newaction));
466  newaction.sa_handler = StopApplication;
467  if (sigaction(SIGINT, &newaction, NULL) < 0) {
468  fprintf(stderr, "Failed to register SIGINT sigaction.\n");
469  exit(EXIT_FAILURE);
470  }
471 #endif
472 
473  // Initialize the NTAPI library and thereby check if NTAPI_VERSION can be used together with this library
474  status = NT_Init(NTAPI_VERSION);
475  HandleErrorStatus(status, "NT_Init() failed");
476 
477  // Assign traffic to stream ID and mask all traffic matching the assign statement color
478  const char *streamsAssign[] = { "Assign[streamid=(0..2);color=7] = port == 0", };
479  status = NtplConfigure(&ntplInfo, streamsAssign, ARRAY_SIZE(streamsAssign));
480 
481  // Open stat stream
482  NtStatStream_t hStat = NULL;
483  status = NT_StatOpen(&hStat, "hStat");
484  HandleErrorStatus(status, "NT_StatOpen() failed");
485 
486  // Reset stats
487  static NtStatistics_t statSet;
489  statSet.u.query_v4.poll = 1;
490  statSet.u.query_v4.clear = 1;
491  status = NT_StatRead(hStat, &statSet);
492  HandleErrorStatus(status, "NT_StatRead() failed");
493 
494  // Create the capture file
495  hf = fopen("capfile.ntcap", "w+b");
496  if (hf == NULL) {
497  perror("Failed to open capfile.ntcap");
498  return -1;
499  }
500 
501  appRunning = 1;
502 
503  // Start Rx threads
504  // Note: RxThread.streamId must be the same as those used in the NTPL Assign command
505  RxThread_t RxThread[numStreams];
506  for (uint32_t i = 0; i < numStreams; ++i) {
507  RxThread[i].streamId = i;
508  RxThread[i].ntplTs = ntplInfo.ts;
509  RxThread[i].hf = hf;
510  status = pthread_create(&RxThread[i].thread, NULL, RxTask, (void*)&RxThread[i]);
511  if (status != 0) {
512  fprintf(stderr, "Unable to create RxStream [%i] thread\n", RxThread[i].streamId);
513  return -1;
514  }
515  }
516 
517  // Waiting for work to finish
518  while (appRunning == 1) {
519  usleep(1000);
520  }
521 
522  // Wait for threads to terminate
523  for (int i = 0; i < numStreams; ++i) {
524  pthread_join(RxThread[i].thread, NULL);
525  }
526 
527  // Close the capture file
528  fclose(hf);
529 
530  // Request stats
532  statSet.u.query_v4.poll = 1;
533  statSet.u.query_v4.clear = 0;
534  status = NT_StatRead(hStat, &statSet);
535  HandleErrorStatus(status, "NT_StatRead() failed");
536 
537  // Read drop counters for streamid 1
538  uint64_t totDropsPkts = statSet.u.query_v4.data.stream.streamid[1].drop.pkts;
539  uint64_t totDropsBytes = statSet.u.query_v4.data.stream.streamid[1].drop.octets;
540 
541  // Close stat stream
542  NT_StatClose(hStat);
543 
544  // Delete the filter
545  const char *streamsDelete[] = { "delete=all" };
546  status = NtplConfigure(&ntplInfo, streamsDelete, ARRAY_SIZE(streamsDelete));
547 
548  printf("Drop: %16lu packets, %16lu bytes\n", totDropsPkts, totDropsBytes);
549  printf("Done: %16d segments, %16d packets, %16llu bytes, %16llu bytes on wire\n", numSegments, numPackets, numBytes, numBytesWire);
550 
551  // Close down the NTAPI library
552  NT_Done();
553 
554  return 0;
555 }
556 
557 //
558 // EOF
559 //