capture_multi_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_multi/capture_multi_example.c Source File
capture_multi_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_multi_example.c
45  * @section capture_multi_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 the main loop.
50  *
51  * The first loop waits for a packet that corresponds to the time of the last
52  * NTPL command execution. The subsequent loop then processes the received
53  * segments in a cycle that includes set of streams set up using the NTPL command.
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_multi_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_multi_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_multi_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 <signal.h>
145  #include <stdatomic.h>
146 #endif
147 
148 #define ARRAY_SIZE(a) (sizeof((a))/sizeof(*(a)))
149 
150 #if defined(WIN32) || defined (WIN64)
151  #define snprintf(dst, ...) _snprintf_s((dst), _countof(dst), __VA_ARGS__)
152 #endif
153 
154 #if defined(WIN32) || defined (WIN64)
155  volatile int appRunning = 0; // The application will run as long as appRunning == 1
156  volatile int firstPacket = 0; // Signaling that we can start processing data
157 #else
158  atomic_int appRunning = 0; // The application will run as long as appRunning == 1
159  atomic_int firstPacket = 0; // Signaling that we can start processing data
160 #endif
161 
162 struct ntpcap_ts_s {
163  uint32_t sec;
164  uint32_t usec;
165 };
166 
167 struct ntpcap_hdr_s {
168  struct ntpcap_ts_s ts;
169  uint32_t caplen;
170  uint32_t wirelen;
171 };
172 
173 /*****************************************************************************
174  Ctrl-C signal handler routine.
175 ******************************************************************************/
176 #if defined(WIN32) || defined (WIN64)
177 static BOOL WINAPI StopApplication(int sig)
178 {
179  (void) sig;
180  appRunning = 0;
181  return TRUE;
182 }
183 #else
184 static void StopApplication(int sig)
185 {
186  if (sig == SIGINT)
187  appRunning = 0;
188 }
189 #endif
190 
191 static void HandleErrorStatus(int status, const char *message) {
192  if (status != NT_SUCCESS) {
193  char errorBuffer[NT_ERRBUF_SIZE]; // Error buffer
194  NT_ExplainError(status, errorBuffer, sizeof(errorBuffer));
195  fprintf(stderr, "%s: %s\n", message, errorBuffer);
196  exit(EXIT_FAILURE);
197  }
198 }
199 
200 static int NtplConfigure(NtNtplInfo_t *ntplInfo, const char **commands, int count) {
201  int status;
202  NtConfigStream_t hCfgStream; // Handle to a config stream
203 
204  status = NT_ConfigOpen(&hCfgStream, "TestStream");
205  HandleErrorStatus(status, "NT_ConfigOpen() failed");
206 
207  for (int i = 0; i < count; ++i) {
208  status = NT_NTPL(hCfgStream, commands[i], ntplInfo, NT_NTPL_PARSER_VALIDATE_NORMAL);
209  HandleErrorStatus(status, "NT_NTPL() failed");
210  }
211 
212  status = NT_ConfigClose(hCfgStream);
213  HandleErrorStatus(status, "NT_ConfigClose() failed");
214 
215  return status;
216 }
217 
218 #define numStreams 3
219 
220 /*****************************************************************************
221  Main routine. It initializes, configures FPGA, starts needed threads.
222  On Ctrl-C it stops looping and begins the cleanup process.
223 ******************************************************************************/
224 int main(void)
225 {
226  FILE* hf = NULL; // File handle
227  int numSegments = 0; // The number of segments received
228  int numPackets = 0; // The number of packets received
229  uint64_t numBytes = 0; // The number of bytes received
230  uint64_t numBytesWire = 0; // The number of bytes received on the wire
231  int status; // Status variable
232  NtNetStreamRx_t hNetRx[numStreams] = {NULL}; // Handle to the RX stream
233  NtNtplInfo_t ntplInfo; // Return data structure from the NT_NTPL() call.
234  NtNetBuf_t hNetBuf = NULL; // Net buffer container. Segment data is returned in this when calling NT_NetRxGet().
235  NtNetRx_t readCmd; // NetRx read command structure.
236  struct NtNetBuf_s pktNetBuf; // Packet netbuf structure.
237  struct ntpcap_ts_s* pTs = NULL;
238  int currentStream = 0; // Currently processed stream
239 
240  // Set up ctrl+c handler
241 #if defined(WIN32) || defined (WIN64)
242  SetConsoleCtrlHandler((PHANDLER_ROUTINE)StopApplication, TRUE);
243 #else
244  struct sigaction newaction; // Ctrl+c handle
245  memset(&newaction, 0, sizeof(newaction));
246  newaction.sa_handler = StopApplication;
247  if (sigaction(SIGINT, &newaction, NULL) < 0) {
248  fprintf(stderr, "Failed to register SIGINT sigaction.\n");
249  exit(EXIT_FAILURE);
250  }
251 #endif
252 
253  // Initialize the NTAPI library and thereby check if NTAPI_VERSION can be used together with this library
254  status = NT_Init(NTAPI_VERSION);
255  HandleErrorStatus(status, "NT_Init() failed");
256 
257  // Assign traffic to stream ID and mask all traffic matching the assign statement color
258  const char *streamsAssign[] = { "Assign[streamid=(0..2);color=7] = port == 0", };
259  status = NtplConfigure(&ntplInfo, streamsAssign, ARRAY_SIZE(streamsAssign));
260 
261  // Used if PCAP header used
262  pTs = (struct ntpcap_ts_s *)(void *)&(ntplInfo.ts);
263 
264  // Open stat stream
265  NtStatStream_t hStat = NULL;
266  status = NT_StatOpen(&hStat, "hStat");
267  HandleErrorStatus(status, "NT_StatOpen() failed");
268 
269  // Reset stats
270  static NtStatistics_t statSet;
272  statSet.u.query_v4.poll = 1;
273  statSet.u.query_v4.clear = 1;
274  status = NT_StatRead(hStat, &statSet);
275  HandleErrorStatus(status, "NT_StatRead() failed");
276 
277  // Get a stream handle with stream ID. NT_NET_INTERFACE_SEGMENT specify that we will receive data in a segment based matter.
278  for (uint32_t i = 0; i < numStreams; ++i) {
279  status = NT_NetRxOpen(&hNetRx[i], "TestStream", NT_NET_INTERFACE_SEGMENT, i, -1);
280  HandleErrorStatus(status, "NT_NetRxOpen() failed");
281  }
282 
283  // Read the file header.
285  status = NT_NetRxRead(hNetRx[0], &readCmd);
286  HandleErrorStatus(status, "NT_NetRxRead() failed");
287 
288  // Create the capture file
289  hf = fopen("capfile.ntcap", "w+b");
290  if (hf == NULL) {
291  perror("Failed to open capfile.ntcap");
292  return EXIT_FAILURE;
293  }
294 
295  // Write the file header
296  if (fwrite(readCmd.u.fileheader.data, (size_t)readCmd.u.fileheader.size, 1, hf) <= 0) {
297  perror("Failed writing file header");
298  return EXIT_FAILURE;
299  }
300 
301  // Setting that the application can start processing data
302  appRunning = 1;
303  firstPacket = 0;
304 
305  // Optional step. Wait for the first packet that hit the NTPL assign command
306  printf("Waiting for the first segment\n");
307  while (appRunning == 1 && firstPacket == 0) {
308  for (int i = 0; i < numStreams; ++i) {
309  if ((status = NT_NetRxGet(hNetRx[i], &hNetBuf, 1000)) != NT_SUCCESS) {
310  if ((status == NT_STATUS_TIMEOUT) || (status == NT_STATUS_TRYAGAIN)) {
311  // Timeouts are ok, we just need to wait a little longer for a segment
312  continue;
313  }
314  // Get the status code as text
315  fclose(hf);
316  HandleErrorStatus(status, "NT_NetRxGet() failed");
317  }
318  // We got a segment. Check if the timestamp is newer than when the NTPL assign command was applied
319  // If PCAP header configured, we need to convert the timestamps received
321  if ((((struct ntpcap_ts_s *)NT_NET_GET_SEGMENT_PTR(hNetBuf))->sec * 1000000 + ((struct ntpcap_ts_s *)NT_NET_GET_SEGMENT_PTR(hNetBuf))->usec) >
322  (pTs->sec * 1000000 + pTs->usec)) {
323  firstPacket = 1;
324  currentStream = i;
325  break; // Break out, we have received a segment that is received after the NTPL assign command was applied
326  }
327  } else
329  if ((((struct ntpcap_ts_s *)NT_NET_GET_SEGMENT_PTR(hNetBuf))->sec * 1000000000 + ((struct ntpcap_ts_s *)NT_NET_GET_SEGMENT_PTR(hNetBuf))->usec) >
330  (pTs->sec * 1000000000 + pTs->usec)) {
331  firstPacket = 1;
332  currentStream = i;
333  break; // Break out, we have received a segment that is received after the NTPL assign command was applied
334  }
335  } else {
336  if (NT_NET_GET_SEGMENT_TIMESTAMP(hNetBuf) > ntplInfo.ts) {
337  firstPacket = 1;
338  currentStream = i;
339  break; // Break out, we have received a segment that is received after the NTPL assign command was applied
340  }
341  }
342 
343  // Release the segment as it is too "old".
344  if (firstPacket == 0) {
345  status = NT_NetRxRelease(hNetRx[i], hNetBuf);
346  if (status != NT_SUCCESS) {
347  fclose(hf);
348  HandleErrorStatus(status, "NT_NetRxRelease() failed");
349  }
350  }
351  }
352  }
353 
354  // Write 10 segments to disk
355  while (appRunning == 1 && hNetBuf != NULL && numSegments < 10) {
356  if (NT_NET_GET_SEGMENT_LENGTH(hNetBuf)) {
357  // Increment the number of segments processed.
358  numSegments++;
359 
360  // Increment the bytes received
361  numBytes += NT_NET_GET_SEGMENT_LENGTH(hNetBuf);
362  printf("%016llx - RxStream [%i]: Received segment of %lu bytes.\n",
363  (unsigned long long)NT_NET_GET_SEGMENT_TIMESTAMP(hNetBuf), currentStream, NT_NET_GET_SEGMENT_LENGTH(hNetBuf));
364 
365  // Optional step. Here all packets are inspected before storing
366  // Start by building a packet netbuf structure
369  {
370  struct ntpcap_hdr_s* pHdr = (struct ntpcap_hdr_s *)NT_NET_GET_SEGMENT_PTR(hNetBuf);
371  uint64_t segLength = NT_NET_GET_SEGMENT_LENGTH(hNetBuf);
372  uint64_t totSegmentBytes = 0;
373  while (totSegmentBytes < segLength) {
374  if (pHdr->wirelen) {
375  numPackets++;
376  numBytesWire += pHdr->wirelen;
377  }
378  totSegmentBytes += pHdr->caplen + (uint32_t)sizeof(struct ntpcap_hdr_s);
379  pHdr = (struct ntpcap_hdr_s *)((uint8_t *)pHdr + pHdr->caplen + sizeof(struct ntpcap_hdr_s));
380  }
381  } else {
382  _nt_net_build_pkt_netbuf(hNetBuf, &pktNetBuf);
383  do {
384  // Just count the amount of packets and wire length
385  numPackets++;
386  numBytesWire += NT_NET_GET_PKT_WIRE_LENGTH((&pktNetBuf));
387  } while (_nt_net_get_next_packet(hNetBuf, NT_NET_GET_SEGMENT_LENGTH(hNetBuf), &pktNetBuf)>0);
388  }
389 
390  if (fwrite(NT_NET_GET_SEGMENT_PTR(hNetBuf), NT_NET_GET_SEGMENT_LENGTH(hNetBuf), 1, hf) <= 0) {
391  perror("Failed writing segment");
392  fclose(hf);
393  return EXIT_FAILURE;
394  }
395  }
396 
397  // Release the current segment
398  status = NT_NetRxRelease(hNetRx[currentStream], hNetBuf);
399  if (status != NT_SUCCESS) {
400  fclose(hf);
401  HandleErrorStatus(status, "NT_NetRxRelease() failed");
402  }
403 
404  if (numSegments == 10) {
405  break;
406  }
407 
408  // Get the next segment
409  while (appRunning == 1) {
410  // try to process next stream
411  currentStream++;
412  if (currentStream + 1 > numStreams) {
413  currentStream = 0;
414  }
415 
416  if ((status = NT_NetRxGet(hNetRx[currentStream], &hNetBuf, 1000)) != 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  // Get the status code as text
422  fclose(hf);
423  HandleErrorStatus(status, "NT_NetRxget() failed");
424  }
425  break;
426  }
427  }
428 
429  // Close the stream and release the hostbuffer. This will also remove the NTPL assignments performed.
430  for (int i = 0; i < numStreams; ++i) {
431  NT_NetRxClose(hNetRx[i]);
432  }
433 
434  // Close the file
435  fclose(hf);
436 
437  // Request stats
439  statSet.u.query_v4.poll = 1;
440  statSet.u.query_v4.clear = 0;
441  status = NT_StatRead(hStat, &statSet);
442  HandleErrorStatus(status, "NT_StatRead() failed");
443 
444  // Read drop counters for streamid 1
445  uint64_t totDropsPkts = statSet.u.query_v4.data.stream.streamid[1].drop.pkts;
446  uint64_t totDropsBytes = statSet.u.query_v4.data.stream.streamid[1].drop.octets;
447 
448  // Close stat stream
449  NT_StatClose(hStat);
450 
451  // Delete the filter
452  const char *streamsDelete[] = { "delete=all" };
453  status = NtplConfigure(&ntplInfo, streamsDelete, ARRAY_SIZE(streamsDelete));
454 
455  printf("Drop: %16lu packets, %16lu bytes\n", totDropsPkts, totDropsBytes);
456  printf("Done: %16d segments, %16d packets, %16lu bytes, %16lu bytes on wire\n", numSegments, numPackets, numBytes, numBytesWire);
457 
458  // Close down the NTAPI library
459  NT_Done();
460 
461  return 0;
462 }
463 
464 //
465 // EOF
466 //