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 action
s and
table
s, 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.