Connection Tracking Example
Connection tracking is a stateful process that follows the a TCP/UDP session from establishment to termination. It consists of a 5 tuple match on <Source IP address, Destination IP address, IP protocol, L4 Source port, L4 Destination port>. The DPL datapath relies on a controller to insert entries for new 5 tuple flows that have not been seen yet.

Connection metadata is a struct that sent with a packet that is sent to the P4Runtime controller. It requires a special annotation with the label "packet_in". The user can define up to 32 bits of data in this data structure.
#include <doca_model.p4>
#include <doca_headers.p4>
#include <doca_externs.p4>
#include <doca_parser.p4>
@nv_controller_metadata
("packet_in"
)
struct connection_meta_t {
bit<2
> type;
bit<2
> _reserved;
bit<28
> zone;
}
In this sample, the packet processing pipeline examines the state of the connection based on the TCP flags and set in the connection metadata an enum type describing the state of the flow.
/* Meta connection type */
enum
bit<2
> ct_type {
PASS = 0
,
NEW = 1
,
RST = 2
,
FIN = 3
}
For readability, constants are used to assign P4 port IDs to more a more meaningful symbol name. The mapping of PFs and VFs to P4 port IDs is done separately in the Service Configuration.
/*
VF port to SW entity that manages the flow insertion,
deletion and expiry times
*/
const
bit<32
> WIRE_PORT = 32w0;
const
bit<32
> HAIRPIN_PORT = 32w1;
control conn_track(
inout nv_headers_t headers,
in nv_standard_metadata_t std_meta,
inout nv_empty_metadata_t user_meta,
inout nv_empty_metadata_t pkt_out_meta
) {
connection_meta_t ctrl_meta;
/* 5-tuple matching for L4 TCP/UDP flows */
NvDirectCounter(NvCounterType.PACKETS_AND_BYTES) table_t5_counter;
action set_connection_id_ct_table_t5(bit<28
> zone) {
table_t5_counter.count();
ctrl_meta.zone = zone;
ctrl_meta.type = ct_type.PASS;
}
action no_action_ct_table_t5() {
table_t5_counter.count();
}
table ct_table_t5 {
key = {
headers.ipv4.src_addr : exact;
headers.ipv4.dst_addr : exact;
headers.ipv4.protocol : exact;
headers.tcp.src_port : exact;
headers.tcp.dst_port : exact;
}
actions = {
set_connection_id_ct_table_t5;
no_action_ct_table_t5;
}
direct_counter = table_t5_counter;
// on hit table ct_table_known
// on miss table ct_table_tcp_miss
default_action = no_action_ct_table_t5;
size = 1048576
;
}
/*
* Known connections handling.
* Precondition - must be a TCP packet
* RST: TYPE_RST
* FIN: TYPE_FIN
* FINRST: TYPE_RST
*
* Match: tcp.flags
* Actions: meta.type, next PIPE
* MISS: next PIPE
*/
NvDirectCounter(NvCounterType.PACKETS_AND_BYTES) table_known_counter;
action set_connection_type_ct_table_known(bit<2
> type) {
table_known_counter.count();
ctrl_meta.type = type;
}
table ct_table_known {
key = {
headers.tcp.flags : exact;
}
actions = {
set_connection_type_ct_table_known;
}
direct_counter = table_known_counter;
default_action = set_connection_type_ct_table_known(ct_type.PASS);
const
entries = {
( 0x1
) : set_connection_type_ct_table_known(ct_type.FIN);
( 0x4
) : set_connection_type_ct_table_known(ct_type.RST);
(/*NV_TCP_PROTOCOL,*/
0x5
) : set_connection_type_ct_table_known(ct_type.RST); /* RST+FIN */
}
}
/*
* Unknown TCP connections handling
* Precondition - must be a TCP packet
* SYN: TYPE_NEW
*
* Match: tcp.flags
* Actions: PIPE, meta.type=NEW
* MISS: NEXT PIPE
*/
NvDirectCounter(NvCounterType.PACKETS_AND_BYTES) table_tcp_miss_counter;
action set_connection_type_ct_table_tcp(bit<2
> type) {
table_tcp_miss_counter.count();
ctrl_meta.type = type;
}
action no_action_ct_table_tcp() {
table_tcp_miss_counter.count();
}
table ct_table_tcp {
key = {
headers.tcp.flags : exact;
}
actions = {
set_connection_type_ct_table_tcp;
no_action_ct_table_tcp;
}
direct_counter = table_tcp_miss_counter;
default_action = no_action_ct_table_tcp;
const
entries = {
(0x02
) : set_connection_type_ct_table_tcp(ct_type.NEW); /* SYN=1 ACK=0 */
}
}
/*
* Unknown UDP connections handling
*
* Match: UDP
* Actions: PIPE, meta.type=NEW
* MISS: DROP
*/
NvDirectCounter(NvCounterType.PACKETS_AND_BYTES) table_udp_miss_counter;
action set_connection_type_ct_table_udp(bit<2
> type) {
table_udp_miss_counter.count();
ctrl_meta.type = type;
}
action drop_ct_table_udp() {
table_udp_miss_counter.count();
nv_drop();
}
table ct_table_udp {
key = {
std_meta.l4_type : exact;
}
actions = {
set_connection_type_ct_table_udp;
drop_ct_table_udp();
}
direct_counter = table_udp_miss_counter;
default_action = drop_ct_table_udp();
const
entries = {
(L4_TYPE_UDP) : set_connection_type_ct_table_udp(ct_type.NEW); /* SYN=1 ACK=0 */
}
}
/* user should add entries that correspond to the wire ports
* A hit means this is an RX packet, miss means a TX packet
*/
table direction_table {
key = {
std_meta.ingress_port : exact;
}
actions = {
NoAction;
}
default_action = NoAction;
const
entries = {
(WIRE_PORT) : NoAction();
}
}
apply {
ctrl_meta.type = ct_type.PASS;
ctrl_meta._reserved = 0
;
ctrl_meta.zone = 0
;
if
(direction_table.apply().hit) {
if
(std_meta.is_l4_ok == 1w1) {
if
(ct_table_t5.apply().hit) {
if
(ct_table_known.apply().hit) {
nv_send_to_controller(ctrl_meta);
}
} else
if
(headers.tcp.isValid()) {
if
(ct_table_tcp.apply().hit) {
nv_send_to_controller(ctrl_meta);
}
} else
{
if
(ct_table_udp.apply().hit) {
nv_send_to_controller(ctrl_meta);
}
}
}
}
nv_send_to_port(HAIRPIN_PORT);
}
}
// Instantiate the top-level DOCA Rx package
NvDocaPipeline(
nv_fixed_parser(),
conn_track()
) main;
See the full DPL example conn_track.p4