P4: Tables and Pipelines

Match-action tables are the primary constructs that programmers use to specify packet processing in most P4 programs.

An action is a procedure containing block of commands that are executed in sequence. For example, the following declaration sets the output port for the packet to the value supplied as a parameter.

action set_output_port(bit<9> output_port) {
  std_meta.egress_spec = output_port;
}    

As with parsers, parameters can have direction annotations. Any parameter without a direction annotation indicates action data provided by the control plane, and must come after any parameters with direction annotations, which are supplied by other parts of the P4 program.

A table specifies the values that should be matched in the table, how those values should be matched, and the actions that can be applied to matching packets. For example, the following table might be used to implement destination-based forwarding:

table next_hop {
  key = {
     hdr.ipv4.dstAddr : lpm;
  }
  actions = {
     set_output_port;
     drop;
  }
  default = drop;
}    
    

Here the key declaration specifies that the IPv4 destination address should be matched in the table using longest-prefix matching (lpm). The actions declarations specifies the set of actions that may be used to process packets matching specific rules. The optional default action specifies the action to use for packets that do not match any rules.

Note that the rules themselves are populated by the control plane, which is not defined in P4.

Externs

Many P4 targets offer other forms of packet-processing functionality – both built-in primitives such as hash functions, and stateful objects such as registers. For example, a counter can be declared and used as follows:

counter(n, bytes) c;    // construct an array of byte counters of size n
...
bit<32> bkt;
hash(bkt, ... ); // compute bucket by hashing on header values
c.count(bkt);   // increment counter for bucket

Controls

To allow programmers to combine multiple tables and imperative statements into larger blocks of code, P4 provies a construct called a control. The declaration of a control is similar to that of a parser:

control C(inout headers_t headers, inout meta_t meta, inout standard_metadata_t std_meta) {
  ...
  apply {
     ...
  }     
}

The body of a control consists of declarations followed by an apply block, which is executed when the control is applied to values. Within a control, one can declare actions and tables, apply actions and tables to values,

tbl.apply(hdr.ipv4.dstAddr);    

and

set_output_port(511);    

or use conditionals:

if(hdr.ipv4.isValid())
  tbl.apply(hdr.ipv4.dstAddr);
else
  drop();        

Most P4 programs define top-level ingress and egress controls. It is also possible to factor them into smaller components—e.g., one control for Ethernet processing, and another for IPv4 processing. To construct a control and apply it to data-plane values, use the following notation:

C() c;
c.apply(hdrs, meta, std_meta);        

Architectures

P4 is designed to be a target-independent language and it is possible to use P4 to program traditional switch ASICs, programmable NICs, FPGAs, and software switches. To support this diversity of targets, P4 supports the notion of an architecture – an abstract specification of the capabilities and structure of the pipeline supported on that target.

In this course, we will mostly be using V1 Model, which is a simple architecture based on the design proposed in the original RMT paper.