flow_learn_span_example.cpp Source File

Reference Documentation

Platform
Napatech SmartNIC
Content Type
Reference Information
Capture Software Version
Link™ Capture Software 12.15
Napatech Software Suite: examples/flow/flow_learn_span/flow_learn_span_example.cpp Source File
flow_learn_span_example.cpp
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 flow/flow_learn_span/flow_learn_span_example.cpp
45  * @section flow_learn_span_example Description
46  *
47  * This example is an application that receives incoming IPv4 UDP packets,
48  * and then program, aka learn, them as new flows. The flows are learned with sorted
49  * IP addresses and port, enabling a single physical port to handle packets in
50  * both directions of the flow. See SPAN port.
51  *
52  * This application also maintains a lookup table of programmed flows, to ensure
53  * that the same flow isn't learned more than once.
54  *
55  * This application doesn't transmit packets, and doesn't monitor traffic in a
56  * significant way. Running this application alongside of the NT tools "pktgen"
57  * and "monitoring" will help making the functionality of this application clear.
58  *
59  * The example program uses the following NTAPI functions:
60  * - @ref NT_Init()
61  * - @ref NT_Done()
62  * - @ref NT_ExplainError()
63  * - @ref NT_ConfigOpen()
64  * - @ref NT_ConfigClose()
65  * - @ref NT_NTPL()
66  * - @ref NT_NetRxOpen()
67  * - @ref NT_NetRxClose()
68  * - @ref NT_NetRxGetNextPacket()
69  * - @ref NT_NET_GET_PKT_DESCR_PTR_DYN4()
70  * - @ref NT_FlowOpenAttrInit()
71  * - @ref NT_FlowOpenAttrSetAdapterNo()
72  * - @ref NT_FlowOpen_Attr()
73  * - @ref NT_FlowGetVersion()
74  * - @ref NT_FlowWrite()
75  * - @ref NT_FlowClose()
76  *
77  * This example is for adapters with flowmatcher functionality.
78  */
79 
80 // Include this in order to access the Napatech API
81 #include <nt.h>
82 
83 #include <algorithm>
84 #include <array>
85 #include <atomic>
86 #include <cstdint>
87 #include <cstring>
88 #include <functional>
89 #include <iostream>
90 #include <memory>
91 #include <string>
92 #include <thread>
93 #include <unordered_map>
94 #include <vector>
95 
96 #define STR_INNER(A) #A
97 #define STR(A) STR_INNER(A)
98 
99 uint8_t gGfi = 0;
100 
101 namespace {
102 
103 ////////////////////////////////////////////////////////////////////////////////
104 // Utility functions
105 ////////////////////////////////////////////////////////////////////////////////
106 
107 /**
108  * Handle errors by printing an error and exit the application.
109  */
110 void handle_error_status(int status, const char* message)
111 {
112  if (status != NT_SUCCESS) {
113  char error_buffer[NT_ERRBUF_SIZE];
114  NT_ExplainError(status, error_buffer, sizeof(error_buffer));
115  std::cerr << message << ": " << error_buffer << std::endl;
116  std::exit(EXIT_FAILURE);
117  }
118 }
119 
120 /**
121  * Opens a config stream and programs the input NTPL.
122  * When the NTPL has been programmed, the config stream is closed again.
123  */
124 void ntpl_multicall(const std::vector<std::string>& ntpls)
125 {
126  int status;
127  NtConfigStream_t config_stream;
128 
129  status = NT_ConfigOpen(&config_stream, "flow_learn_span NT_ConfigOpen");
130  handle_error_status(status, "NT_ConfigOpen() failed");
131 
132  for (const auto& ntpl : ntpls) {
133  NtNtplInfo_t ntpl_info;
134  status = NT_NTPL(config_stream, ntpl.c_str(), &ntpl_info, NT_NTPL_PARSER_VALIDATE_NORMAL);
135  handle_error_status(status, "NT_NTPL() failed");
136  }
137 
138  status = NT_ConfigClose(config_stream);
139  handle_error_status(status, "NT_ConfigClose() failed");
140 }
141 
142 /**
143  * Quick and dirty hashing function for some fields in NtFlow_t.
144  * The hash uniqueness in this function is not great, but since std::unordered_multimap
145  * is used to store the flows, the application is able to handle duplicate hashes.
146  */
147 uint64_t flow_hash(const NtFlow_t* flow)
148 {
149  const uint64_t* ptr = reinterpret_cast<const uint64_t*>(flow->keyData);
150  uint64_t meta = static_cast<uint64_t>((flow->ipProtocolField << 16) | (flow->keyId << 8) | flow->keySetId);
151 
152  // Multiply data with some random primes.
153  return (ptr[0] * 45684803) ^
154  (ptr[1] * 198138211) ^
155  (ptr[2] * 994229) ^
156  (ptr[3] * 8349871) ^
157  (ptr[4] * 3294876479) ^
158  (meta * 663664226587);
159 }
160 
161 ////////////////////////////////////////////////////////////////////////////////
162 // The general thread main function used for all StreamID
163 ////////////////////////////////////////////////////////////////////////////////
164 
165 /**
166  * Opens a RX stream and received packets until an end-task signal is received.
167  */
168 void rx_task(std::atomic<int>* ready, std::atomic<bool>* end_task,
169  uint32_t stream_id, std::function<void(const NtNetBuf_t&)> handle)
170 {
171  int status;
172  NtNetStreamRx_t net_stream;
173  NtNetBuf_t net_buffer;
174 
175  // Open RX stream
176  status = NT_NetRxOpen(&net_stream, "flow_learn_span NT_NetRxOpen", NT_NET_INTERFACE_PACKET, stream_id, -1);
177  handle_error_status(status, "NT_NetRxOpen() failed");
178 
179  // Tell main function that this is ready to receive packets, and then start the loop.
180  ready->fetch_add(1);
181 
182  while (!end_task->load()) {
183  // Get next packet or continue.
184  status = NT_NetRxGetNextPacket(net_stream, &net_buffer, 100);
185  if (status == NT_STATUS_TIMEOUT || status == NT_STATUS_TRYAGAIN) continue;
186  handle_error_status(status, "NT_NetRxGetNextPacket() failed");
187 
188  // See the handle functions.
189  handle(net_buffer);
190  }
191 
192  // Close the RX stream.
193  status = NT_NetRxClose(net_stream);
194  handle_error_status(status, "NT_NetRxClose() failed");
195 }
196 
197 ////////////////////////////////////////////////////////////////////////////////
198 // Global variables
199 ////////////////////////////////////////////////////////////////////////////////
200 
201 #define BLACKLIST 3
202 #define WHITELIST 4
203 
204 struct context {
205  uint64_t data[8];
206 };
207 
211 
213 std::unordered_multimap<uint64_t, std::unique_ptr<NtFlow_t>> flow_map;
214 
215 std::atomic<uint64_t> flow_learn_queue_read_index {0};
216 std::atomic<uint64_t> flow_learn_queue_write_index {0};
217 std::array<NtFlow_t*, 0x100000> flow_learn_queue;
218 constexpr uint64_t flow_learn_queue_index_mask = 0xfffff;
219 
220 ////////////////////////////////////////////////////////////////////////////////
221 // Main thread function for programming flows
222 ////////////////////////////////////////////////////////////////////////////////
223 
224 /**
225  * Separate task to program flows
226  */
227 void flow_program_task(std::atomic<int>* ready, std::atomic<bool>* end_task)
228 {
229 
230  // Tell main function that this is ready to receive packets, and then start the loop.
231  ready->fetch_add(1);
232 
233  while (!end_task->load()) {
234  // If a new flow has been added to the queue, the pop and learn it.
236  auto flow_raw_ptr = flow_learn_queue[flow_learn_queue_read_index];
237  flow_learn_queue_read_index.store((flow_learn_queue_read_index + 1) & flow_learn_queue_index_mask);
238 
239  // Learn the flow and insert it into the map.
240  int status = NT_FlowWrite(flow_stream, flow_raw_ptr, -1);
241  handle_error_status(status, "NT_FlowWrite() failed");
242  }
243  }
244 }
245 
246 ////////////////////////////////////////////////////////////////////////////////
247 // The handle functions for packets for each StreamID
248 ////////////////////////////////////////////////////////////////////////////////
249 
250 /**
251  * Handle used for the missed packets RX thread.
252  *
253  * Whenever a missed packet is received, the function calculates the flow learn
254  * data, and checks if it is a new flow, or a previously known flow. If the flow
255  * is new, then the flow is programmed.
256  */
257 void handle_stream_miss(const NtNetBuf_t& net_buffer)
258 {
259  // As this is an example program, we will just use an incrementing flow ID
260  static uint32_t flow_id = 0;
261  static uint8_t key_set_id = BLACKLIST;
262 
263  ++context_miss.data[0];
264 
265  // Get the relevant pointers from the packet data.
266  const NtDyn4Descr_t* dyn4 = NT_NET_GET_PKT_DESCR_PTR_DYN4(net_buffer);
267  const uint8_t* packet = reinterpret_cast<const uint8_t*>(dyn4) + dyn4->descrLength;
268 
269  auto flow = std::unique_ptr<NtFlow_t>(new NtFlow_t);
270  std::memset(flow.get(), 0x0, sizeof(NtFlow_t));
271 
272  const uint8_t* ipv4_src = packet + dyn4->offset0;
273  const uint8_t* ipv4_dst = packet + dyn4->offset0 + 4;
274 
275  const uint8_t* udp_src = packet + dyn4->offset1;
276  const uint8_t* udp_dst = packet + dyn4->offset1 + 2;
277 
278  // Sort the IPv4 addresses and UDP ports for the packet. This is only required
279  // when the option "KeySort=Sorted" is used in the KeyDef NTPL command.
280  // Because of endianness the comparison is actually not trivial,
281  // but the std algorithms library makes it a bit easier.
282  if (std::lexicographical_compare(ipv4_src, ipv4_src + 4, ipv4_dst, ipv4_dst + 4) ||
283  (std::equal(ipv4_src, ipv4_src + 4, ipv4_dst) &&
284  std::lexicographical_compare(udp_src, udp_src + 2, udp_dst, udp_dst + 2))) {
285  // Translates to: ipv4_src < ipv4_dst || (ipv4_src == ipv4_dst && udp_src < udp_dst)
286  std::memcpy(flow->keyData, ipv4_src, 4);
287  std::memcpy(flow->keyData + 4, ipv4_dst, 4);
288  std::memcpy(flow->keyData + 8, udp_src, 2);
289  std::memcpy(flow->keyData + 10, udp_dst, 2);
290  }
291  else {
292  std::memcpy(flow->keyData, ipv4_dst, 4);
293  std::memcpy(flow->keyData + 4, ipv4_src, 4);
294  std::memcpy(flow->keyData + 8, udp_dst, 2);
295  std::memcpy(flow->keyData + 10, udp_src, 2);
296  }
297 
298  // Some packet inspection can be added here to decide whether the new flow
299  // should be added to the WHITELIST or the BLACKLIST.
300  // This example just alternates between WHITELIST and BLACKLIST
301  key_set_id = (key_set_id == WHITELIST) ? BLACKLIST : WHITELIST;
302 
303  // Set other relevant fields.
304  flow->ipProtocolField = 0x11; // Layer 4 is UDP.
305  flow->keyId = 1; // Value used in the Key-test in the NTPL filter "Key(kd, KeyID=1) == WHITELIST / BLACKLIST".
306  flow->keySetId = key_set_id; // Value used to compare the Key-test in the NTPL filter "Key(kd, KeyID=1) == WHITELIST / BLACKLIST".
307  flow->op = 1; // 1 means learn, 0 means unlearn.
308  flow->gfi = 0x1 & gGfi;
309 
310  // Calculate a locally used hash. The "id" field is user defined and is used to
311  // store a hash in this example. For data structures other than std::unordered_multimap
312  // it could make sense to store the value of the pointer to NtFlow_t; this
313  // would enable instant and easy access to the locally stored flow data.
314  auto flow_raw_ptr = flow.get();
315  auto hash_key = flow_hash(flow_raw_ptr);
316 
317  // Check if the unique key-value pair exists, and return if it does.
318  // The check would be a lot simpler if std::unordered_map was used instead of
319  // std::unordered_multimap, but when duplicate hashes would not be handled correctly.
320  auto range = flow_map.equal_range(hash_key);
321  for (auto it = range.first; it != range.second; ++it) {
322  if (std::equal(flow->keyData, flow->keyData + 40, it->second->keyData) &&
323  flow->ipProtocolField == it->second->ipProtocolField &&
324  flow->keyId == it->second->keyId && flow->keySetId == it->second->keySetId) {
325  return;
326  }
327  }
328 
329  flow->id = flow_id++;
330  flow_map.insert(std::pair<uint64_t, std::unique_ptr<NtFlow_t>>{hash_key, std::move(flow)});
331 
332  // Compared to the number of packets that can be received per second on a RX stream,
333  // the learn rate is very slow, which can cause packet drops if the RX has to do
334  // that task as well. Thus it can be a good idea to implement a queue, and perform
335  // the learning from another thread.
336  flow_learn_queue[flow_learn_queue_write_index] = flow_raw_ptr;
337  flow_learn_queue_write_index.store((flow_learn_queue_write_index + 1) & flow_learn_queue_index_mask);
338 }
339 
340 /**
341  * Handle used for the blacklist packets RX thread.
342  *
343  * In this example, packets that hit a programmed flow are only counted.
344  */
346 {
347  ++context_blacklist.data[0];
348 }
349 
350 /**
351  * Handle used for the whitelist packets RX thread.
352  *
353  * In this example, packets that hit a programmed flow are only counted.
354  */
356 {
357  ++context_whitelist.data[0];
358 }
359 
360 } // unnamed namespace
361 
362 ////////////////////////////////////////////////////////////////////////////////
363 // Main
364 ////////////////////////////////////////////////////////////////////////////////
365 
366 int main(int argc, char* argv[])
367 {
368  constexpr uint8_t adapter_no = 0;
369 
370  if (argc == 2 && strcmp(argv[1], "gfi") == 0)
371  {
372  std::cout << "Flow information will be generated" << std::endl;
373  gGfi = 1;
374  }
375  else
376  {
377  std::cout << "Flow information will not be generated" << std::endl;
378  }
379 
380  // Start NTAPI.
381  int status = NT_Init(NTAPI_VERSION);
382  handle_error_status(status, "NT_Init() failed");
383 
384  // Setup NTPL using a utility function.
386  // Delete previous filters and programmed flow before starting the new filters.
387  "Delete=All",
388 
389  // Macros for simplifying following NTPL commands.
390  "DefineMacro(\"FilterCheck\", \"Port==$1 and Layer3Protocol==IPv4 and Layer4Protocol==UDP\")",
391  "DefineMacro(\"KeyTypeProtoSpecs\", \"(layer3header[12]/32, layer3header[16]/32, layer4header[0]/16,layer4header[2]/16)\")",
392 
393  // Specify the packet field sizes in the 5-tuple. The 5-tuple contains the
394  // IPv4 addresses, IP protocol field, and the UDP ports. The IP protocol
395  // field is implicit when the "IpProtocolField=Outer" option is used.
396  // In this example sorting is used to receive two-way traffic on one port.
397  "KeyType[Name=kt] = {32, 32, 16, 16}",
398  "KeyDef[Name=kd; KeyType=kt; IpProtocolField=Outer; KeySort=Sorted] = KeyTypeProtoSpecs",
399 
400  // Set up filters with a Key test.
401  "Assign[StreamId=0; Descriptor=DYN4, Offset0=Layer3Header[12], Offset1=Layer4Header[0]] = FilterCheck(0) and Key(kd, KeyID=1) == MISS",
402  "Assign[StreamId=1; Descriptor=DYN4, Offset0=Layer3Header[12], Offset1=Layer4Header[0]] = FilterCheck(0) and Key(kd, KeyID=1) == " STR(BLACKLIST),
403  "Assign[StreamId=2; Descriptor=DYN4, Offset0=Layer3Header[12], Offset1=Layer4Header[0]] = FilterCheck(0) and Key(kd, KeyID=1) == " STR(WHITELIST),
404 
405  // Set up a low priority catch-all filter where packets will be dropped if
406  // they do not match any of the previous three filters.
407  "Assign[StreamId=Drop; Priority=10]=All"
408  });
409 
410  // Start the flow stream for learning and unlearning flows.
411  NtFlowAttr_t flow_attr;
412 
413  NT_FlowOpenAttrInit(&flow_attr);
414  NT_FlowOpenAttrSetAdapterNo(&flow_attr, adapter_no);
415 
416  status = NT_FlowOpen_Attr(&flow_stream, "flow_learn_span NT_FlowOpen_Attr", &flow_attr);
417  handle_error_status(status, "NT_FlowOpen_Attr() failed");
418 
419  uint32_t FlowStreamVersion = NT_FlowGetVersion(flow_stream);
420  std::cout << "Detected Flow Stream API verion " << FlowStreamVersion << std::endl;
421 
422  // Start RX and flow learn tasks
423  std::atomic<bool> end_task {false};
424  std::atomic<int> ready {0};
425 
426  std::thread rx_task0(rx_task, &ready, &end_task, 0, handle_stream_miss);
427  std::thread rx_task1(rx_task, &ready, &end_task, 1, handle_stream_blacklist_hit);
428  std::thread rx_task2(rx_task, &ready, &end_task, 2, handle_stream_whitelist_hit);
429  std::thread flow_program_task0(flow_program_task, &ready, &end_task);
430 
431  // Wait for RX tasks to be ready, then wait for user to stop the application.
432  while(ready.load() < 4) std::this_thread::yield();
433 
434  std::cout << "Press enter to end application..." << std::endl;
435  std::cin.get();
436 
437  // End application.
438  end_task.store(true);
439 
440  if (flow_program_task0.joinable()) flow_program_task0.join();
441  if (rx_task2.joinable()) rx_task2.join();
442  if (rx_task1.joinable()) rx_task1.join();
443  if (rx_task0.joinable()) rx_task0.join();
444 
445  std::cout << "counter_miss: " << context_miss.data[0] << std::endl;
446  std::cout << "counter_blacklist_hit: " << context_blacklist.data[0] << std::endl;
447  std::cout << "counter_whitelist_hit: " << context_whitelist.data[0] << std::endl;
448 
449  // Unlearn all the programmed flows.
450  for (auto it = flow_map.begin(); it != flow_map.end(); ++it) {
451  NtFlow_t* flow = it->second.get();
452  flow->op = 0;
453 
454  status = NT_FlowWrite(flow_stream, flow, -1);
455  handle_error_status(status, "NT_FlowWrite() failed");
456  }
457 
458  // Close the flow stream
459  status = NT_FlowClose(flow_stream);
460  handle_error_status(status, "NT_FlowClose() failed");
461 
462  // End NTAPI.
463  NT_Done();
464 
465  return 0;
466 }