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 manually 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.
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
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 packet descriptor 4 structure. 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.