Use this example to program a flow.
NtFlow_s structure
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;
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.
If the tau field is set to 1 for a TCP flow, the flow is automatically unlearned when the TCP session is terminated.
- 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
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; } }
Learning a flow
// 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)});