API: Learn a Flow

Stateful Flow Management

Platform
Napatech SmartNIC
Content Type
User Guide
Capture Software Version
Link™ Capture Software 12.8

Use this example to learn a flow.

NtFlow_s structure

Once a flow stream to the SmartNIC has been established, flow learning can be done using the NT_FlowWrite function with the NtFlow_s structure. NtFlow_s is defined as follows:
typedef struct NtFlow_s {
  uint8_t keyData[40];    /*!< Raw key data array as defined by the NTPL KeyDef command */
  uint64_t id;            /*!< 64-bit user defined ID */
  uint32_t color;         /*!< 32-bit flow color */
  uint8_t overwrite:1;    /*!< Enable overwrite filter action */
  uint8_t streamId:7;     /*!< Marks the stream id if overwrite filter action is enabled.
                              Unused if overwrite is not enabled */
  uint8_t ipProtocolField;/*!< Next-protocol field from the IP header. Can be extracted from
                              outer or inner IP header or not at all
                              (see @ref NtplKeyType "KeyType NTPL Command") */
  uint8_t keyId;          /*!< Key ID as used in the @ref NtplKeyType "KeyType NTPL Command" */
  uint8_t keySetId;       /*!< Ket Set id as used in the NTPL filter */
  uint8_t op:4;           /*!< Flow programming operation (1: learn, 0: un-learn) */
  uint8_t gfi:1;          /*!< Generate flow info record (1: generate, 0: do not generate) */
  uint8_t tau:1;          /*!< TCP auto unlearn (1: auto unlearn enable, 0: auto unlearn disable)*/
} NtFlow_t;
Note: If the gfi field is set to 1, a flow info record is generated when the flow is unlearned. See API: Read Flow Info of an Unlearned Flow for further information about flow info records.
Note: If the tau field is set to 1 for a TCP flow, the flow is automatically unlearned when the TCP session is terminated. The IpProtocol parameter must be set to Inner or Outer in NTPL to enable tracking of TCP sessions for automatic TCP flow unlearning. If IpProtocol is set to Inner, inner layer TCP sessions are tracked. If it is set to Outer, outer layer TCP sessions are tracked. If it is set to None (default), TCP sessions are not tracked.
Note: If the overwrite field is set to 1, it is possible to specify a stream for the flow using the streamId field. This feature can be enabled using the following NTPL commands:
  • Set StreamInfo to True in the KeyType command.
  • Set StreamId to Overwrite in the Assign command.
The following NTPL example shows how to enable this feature.
Define Upstream = Macro("Port==0 and Layer3Protocol==IPv4")
Define Downstream = Macro("Port==1 and Layer3Protocol==IPv4")
KeyType[Name=KT; StreamInfo=True] = {sw_32_32}
KeyDef[Name=KD; KeyType=KT; IpProtocolField=Outer] = (Layer3Header[12]/32/32)

Assign[StreamId=Overwrite(0..3)] = Upstream and Key(KD, KeyID=1) == 4
Assign[StreamId=Overwrite(0..3)] = Downstream and Key(KD, KeyID=1, FieldAction=Swap) == 4
Note: After a flow is learned, it is not possible to update any attributes of the flow calling NT_FlowWrite again, such as streamId, keyId and keysetId.
Note: Updating existing flows is not supported. Target flows must be learned as new flows after they are deleted.

Flow learning for the TAP configuration

The following code snippet shows how a flow record is filled from a received frame classified as missed in the TAP configuration.
  counter_miss += 1;
  // Get the relevant pointers from the packet data.
  const NtDyn4Descr_t* dyn4 = NT_NET_GET_PKT_DESCR_PTR_DYN4(net_buffer);
  const uint8_t* packet = reinterpret_cast<const uint8_t*>(dyn4) + dyn4->descrLength;
  auto flow = std::unique_ptr<NtFlow_t>(new NtFlow_t);
  std::memset(flow.get(), 0x0, sizeof(NtFlow_t));
  const uint8_t* ipv4_src = packet + dyn4->offset0;
  const uint8_t* ipv4_dst = packet + dyn4->offset0 + 4;
  const uint8_t* udp_src = packet + dyn4->offset1;
  const uint8_t* udp_dst = packet + dyn4->offset1 + 2;

  // Swap source and destination if the packet is received on port 1.
  // It is required if FieldAction=Swap is used on port 1 in the NTPL command.
  if (dyn4->rxport == 0)
  {
    std::memcpy(flow->keyData,      ipv4_src, 4);
    std::memcpy(flow->keyData +  4, ipv4_dst, 4);
    std::memcpy(flow->keyData +  8, udp_src,  2);
    std::memcpy(flow->keyData + 10, udp_dst,  2);
  }
  else if (dyn4->rxport == 1)
  {
    std::memcpy(flow->keyData,      ipv4_dst, 4);
    std::memcpy(flow->keyData +  4, ipv4_src, 4);
    std::memcpy(flow->keyData +  8, udp_dst,  2);
    std::memcpy(flow->keyData + 10, udp_src,  2);
  }
The offset0 and offset1 fields are used to obtain IPv4 addresses and layer 4 port numbers. In the Network TAP NTPL example, Offset0 and Offset1 of dynamic descriptor 4 are configured to point to IPv4 source address and layer 4 source port number respectively.

It is important to note that source and destination fields are swapped if a frame is received on port 1. The rxport field of dynamic descriptor 4 is used to obtain the RX port number.

Flow learning for the SPAN port configuration

The following code snippet illustrates how a flow record is filled from a received frame classified as missed in the SPAN port configuration.

  counter_miss += 1;
  // Get the relevant pointers from the packet data.
  const NtDyn4Descr_t* dyn4 = NT_NET_GET_PKT_DESCR_PTR_DYN4(net_buffer);
  const uint8_t* packet = reinterpret_cast<const uint8_t*>(dyn4) + dyn4->descrLength;
  auto flow = std::unique_ptr<NtFlow_t>(new NtFlow_t);
  std::memset(flow.get(), 0x0, sizeof(NtFlow_t));
  const uint8_t* ipv4_src = packet + dyn4->offset0;
  const uint8_t* ipv4_dst = packet + dyn4->offset0 + 4;
  const uint8_t* udp_src = packet + dyn4->offset1;
  const uint8_t* udp_dst = packet + dyn4->offset1 + 2;

  // Sort the IPv4 addresses and UDP ports for the packet. This is only required
  //   when the option "KeySort=Sorted" is used in the KeyDef NTPL command.
  // Because of endianness the comparison is actually not trivial,
  //   but the std algorithms library makes it a bit easier.
  if (std::lexicographical_compare(ipv4_src, ipv4_src + 4, ipv4_dst, ipv4_dst + 4) ||
    (std::equal(ipv4_src, ipv4_src + 4, ipv4_dst) &&
    std::lexicographical_compare(udp_src, udp_src + 2, udp_dst, udp_dst + 2))) {
    // Translates to: ipv4_src < ipv4_dst || (ipv4_src == ipv4_dst && udp_src < udp_dst)
    std::memcpy(flow->keyData,      ipv4_src, 4);
    std::memcpy(flow->keyData +  4, ipv4_dst, 4);
    std::memcpy(flow->keyData +  8, udp_src,  2);
    std::memcpy(flow->keyData + 10, udp_dst,  2);
  }
  else {
    std::memcpy(flow->keyData,      ipv4_dst, 4);
    std::memcpy(flow->keyData +  4, ipv4_src, 4);
    std::memcpy(flow->keyData +  8, udp_dst,  2);
    std::memcpy(flow->keyData + 10, udp_src,  2);
  }
The offset0 and offset1 fields are used to obtain IP addresses and layer 4 port numbers. In the SPAN port NTPL example, Offset0 and Offset1 of dynamic descriptor 4 are configured to point to IPv4 source address and layer 4 source port number respectively.

It is important to note that source and destination fields are sorted before copying the fields to the flow record.

Programming Key ID, key set ID and flow ID

The following code snippet shows how other relevant fields of the flow record are filled. In the NTPL examples KeyID is set to 1 and the key set ID is set to 4. These values are used in this code snippet.

  flow->ipProtocolField = 0x11; // Layer 4 is UDP.
  flow->keyId           = 1;  // Value used in the Key-test in the NTPL filter "Key(kd, KeyID=1) == 4"
  flow->keySetId        = 4;  // Value used to compare the Key-test in the NTPL filter "Key(kd, KeyID=1) == 4"
  flow->op              = 1;  // 1 means learn, 0 means unlearn
  flow->gfi = 1;              // Generate flow info record
  flow->id = flow_hash(flow.get());
  // Check if the unique key-value pair exists, and return if it does.
  // The check would be a lot simpler if std::unordered_map was used instead of
  //   std::unordered_multimap, but when duplicate hashes would not be handled correctly.
  auto range = flow_map.equal_range(flow->id);
  for (auto it = range.first; it != range.second; ++it) {
    if (std::equal(flow->keyData, flow->keyData + 40, it->second->keyData) &&
      flow->ipProtocolField == it->second->ipProtocolField &&
      flow->keyId == it->second->keyId && flow->keySetId == it->second->keySetId) {
      return;
    }
  }
The 64-bit id field of NtFlow_s can be used to store the user-defined flow identification of a flow. If you have locally managed flow database that contain metadata of flows, the pointer or the hash value of a flow can be programmed in the id field so as to identify the flow of a frame and a flow info record. In this example the hash value of a flow is used as the flow ID.
Note: The 64-bit Color1 field of dynamic packet descriptor 4 contains the flow ID value which is specified when learning flows. See Dynamic descriptor 4. In the NTPL examples, the ColorBits parameter is set to FlowID to enable the flow ID information of received frames in the application. See the Network TAP NTPL example and the SPAN port NTPL example.
Note: It is important to note the gfi field is set to 1; this will cause flow info records to be generated when the flow is unlearned. See API: Read Flow Info of an Unlearned Flow.

Learning a flow

Finally learn the flow using the NT_FlowWrite function.
  // Learn the flow and insert it into the map.
  int status = NT_FlowWrite(flow_stream, flow.get(), -1);
  handle_error_status(status, "NT_FlowWrite() failed");
  flow_map.insert(std::pair<uint64_t, std::unique_ptr<NtFlow_t>>{flow->id, std::move(flow)});
Note: The return value of the NT_FlowWrite function does not indicate whether the flow was learned successfully but indicates whether delivering the flow record to the SmartNIC was successful or failed. It is possible to check whether the flow was successfully learned using the NT_FlowStatusRead function. See API: Read Flow Learn/Unlearn Result for more information about NT_FlowStatusRead.