Part II — Hands-on networking · Chapter 8

ONOS and P4Runtime: controlling a P4 data plane

~40 min read4 interactive widgets4 plates

In this chapter

  1. The big picture: from a P4 idea to an app
  2. Why yet another data-plane control API
  3. protobuf and gRPC in one page
  4. P4Runtime: the write request and the table entry
  5. P4Info: the contract between the planes
  6. SetForwardingPipelineConfig
  7. ONOS: a network operating system
  8. FlowRule versus FlowObjective
  9. Pipeconf: bring your own pipeline
  10. A worked controller: DDoS pre-filtering and digests
  11. Check your understanding

1. The big picture: from a P4 idea to an app

The ONOS+P4 tutorial (materials from the Open Networking Foundation) sets a concrete goal: acquire enough knowledge to build full-stack network applications — to go from a P4 idea to an end-to-end solution. The full stack is four layers, and the whole chapter is about the interfaces between them:

Four-layer stack: your app on top, then ONOS, then P4Runtime, then your P4 program on the switch Your App SDN apps ONOS SDN control plane P4Runtime runtime control Your P4 program P4 switch Chapters 6–7 built the bottom layer. This chapter builds the three interfaces above it.
Plate 8.1 — The full stack. Your P4 program defines the switch; P4Runtime controls it at runtime; ONOS is the control plane; your app rides on top.

The bottom is familiar: a P4 pipeline is a pipeline of match-action tables (chapter 6), realised on an ASIC, FPGA, NPU or CPU. What is new here is everything above the switch.

2. Why yet another data-plane control API

The tutorial asks the obvious question — can't we reuse OpenFlow or SAI? — and answers it with a table that is the clearest single justification for P4Runtime. Three kinds of independence are wanted, and only P4Runtime has all three:

Target-independentProtocol-independentPipeline-independent
OpenFlowyesno — headers and actions are hard-coded in the specyes, only with TTP
Switch Abstraction Interface (SAI)yesno — designed for legacy L2/L3/ACL pipelinesno — implicit fixed-function pipeline
P4Runtimeyesyesyes, with P4

The three independences read as: the same API works with different switches from different vendors (target); the same API controls any data-plane protocol, standard or custom (protocol); the same API controls many arbitrary pipelines, formally specified (pipeline). The properties of P4Runtime that deliver this: it is designed around the PSA architecture but extensible to others; it is community-developed (initial contribution by Google and Barefoot); it is gRPC/protobuf-based; it is P4 program-independent — the API does not change with the P4 program — and it enables field reconfigurability, the ability to push a new P4 program and re-configure the pipeline without recompiling the switch software stack.

Key idea

Read this table against chapter 3's OpenFlow field-count table. OpenFlow's "no" under protocol-independence is precisely the 12-to-41-fields problem: the protocol is baked into the specification. P4Runtime moves the protocol definition into the P4 program and out of the API — which is why the same API survives a program change.

3. protobuf and gRPC in one page

P4Runtime is built on two Google technologies, and the tutorial teaches just enough of each.

protobuf is a language for describing data for serialization in a structured way: strongly typed, platform-neutral, extensible and backwards compatible, with code generators for a long list of languages (C, C++, Java, Python, Go, Rust, …). You describe a message once and auto-generate the code to serialize and deserialize it.

syntax = "proto3";
message Person {
  string name = 1;
  int32  id   = 2;
  string email = 3;
}

gRPC uses protobuf to define a service API and its messages, and auto-generates native client and server code in many languages. It transports over HTTP/2.0 and TLS, an efficient single-TCP-connection implementation that supports bidirectional streaming. That streaming is what lets a controller and a switch exchange packet-in / packet-out and notifications over one channel.

The P4Runtime service itself, p4runtime.proto, is a gRPC service that lets a local or remote control plane load the pipeline, read and write pipeline state, exchange packets, and arbitrate mastership for distributed control planes:

service P4Runtime {
  rpc Write(WriteRequest) returns (WriteResponse) {}
  rpc Read(ReadRequest) returns (stream ReadResponse) {}
  rpc SetForwardingPipelineConfig(...) returns (...) {}
  rpc GetForwardingPipelineConfig(...) returns (...) {}
  rpc StreamChannel(stream StreamMessageRequest)
      returns (stream StreamMessageResponse) {}
}

4. P4Runtime: the write request and the table entry

Installing a rule means sending a WriteRequest containing one or more Updates, each an INSERT, MODIFY or DELETE of an Entity. The entity can be many things — a table entry, an action-profile member or group, a meter entry, a counter entry, a register entry, a packet-replication-engine entry — which is the API being pipeline-independent in one message definition.

message Update {
  enum Type { UNSPECIFIED = 0; INSERT = 1; MODIFY = 2; DELETE = 3; }
  Type   type = 1;
  Entity entity = 2;
}

To add a table entry the control plane must know three kinds of thing, and this list is the reason P4Info (next section) has to exist:

A logical table entry such as hdr.ipv4.dstAddr = 10.0.1.1/32 -> ipv4_forward(00:00:00:00:00:10, 7) becomes a protobuf TableEntry whose fields are all numeric IDs and raw bytes:

table_entry {
  table_id: 33581985
  match {
    field_id: 1
    lpm { value: "\n\000\001\001"  prefix_len: 32 }
  }
  action {
    action_id: 16786453
    params { param_id: 1  value: "\000\000\000\000\000\n" }
    params { param_id: 2  value: "\000\007" }
  }
}

5. P4Info: the contract between the planes

Notice that the TableEntry above contained no names, only numbers: table_id: 33581985, action_id: 16786453. Something has to translate the human names in the P4 program into those IDs, and that something is P4Info.

The P4 compiler generates two outputs, and this pairing is the heart of the whole architecture:

OutputRole
Target-specific binariesRealise the switch pipeline — a binary config for an ASIC, a bitstream for an FPGA, the BMv2 JSON for the software switch.
P4Info fileDescribes the "schema" of the pipeline for runtime control: the program's tables, actions and parameters. Protobuf-based and target-independent — the same P4Info for a software switch, an ASIC, anything.

The P4Info entry for the ipv4_forward action and the ipv4_lpm table records exactly the names, IDs, bit widths and match types the control plane needs:

actions {
  id: 16786453
  name: "ipv4_forward"
  params { id: 1  name: "dstAddr"  bitwidth: 48 }
  params { id: 2  name: "port"     bitwidth: 9  }
}
tables {
  id: 33581985
  name: "ipv4_lpm"
  match_fields { id: 1  name: "hdr.ipv4.dstAddr"  bitwidth: 32  match_type: LPM }
  action_ref_id: 16786453
}
For the exam

State the role of P4Info in one sentence: it is the contract between the control plane and the data plane, generated by the P4 compiler, needed by the control plane to format the body of P4Runtime messages (for example to add a table entry). It maps the names in the P4 program to the numeric IDs on the wire, and it is target-independent — the same P4Info describes the program on a BMv2 switch and on a Tofino ASIC.

The P4 compiler produces target binaries for the switch and a P4Info file for the control plane, which uses it to format P4Runtime messages test.p4 p4c test.p4info test.bin / json schema for control config for the switch Control plane P4Runtime server + driver reads P4Info to build messages Same P4Info for a software switch, an ASIC or an FPGA — it is target-independent.
Plate 8.2 — The two compiler outputs. The binary configures the switch; the P4Info lets the controller speak to it. Neither alone is enough.

6. SetForwardingPipelineConfig

Pushing a whole P4 program to a switch — the field reconfigurability that distinguishes P4Runtime — is done with SetForwardingPipelineConfig. The request bundles the P4Info and the target-specific configuration bits into one ForwardingPipelineConfig, and an Action field controls the commit semantics (VERIFY, VERIFY_AND_SAVE, VERIFY_AND_COMMIT, COMMIT, RECONCILE_AND_COMMIT).

message ForwardingPipelineConfig {
  config.P4Info p4info = 1;
  bytes p4_device_config = 2;   // target-specific P4 configuration
}

The tutorial's punchline is silicon-independent remote control: the same target-independent protobuf table entry, sent over gRPC, reaches a programmable Tofino from vendor A, a fixed-function switch from vendor B and another from vendor C — each with its own P4Runtime server and target driver, each carrying its own P4Info. One controller, many silicons, one message format.

7. ONOS: a network operating system

ONOS — the Open Network Operating System — provides the control plane for a software-defined network: a logically centralised remote controller, with APIs that make it easy to write apps to control a network. It focuses on service-provider access/edge applications, and it runs as a distributed system across many servers for scalability, high availability and performance. It is open source (created by ON.Lab, hosted by the Linux Foundation). P4Runtime support first arrived in the Loon (1.11.0) release, August 2017.

Its architecture is layered, and the layering is what lets one app control OpenFlow and P4Runtime devices alike:

ONOS layered architecture: apps on top, a northbound API, a distributed core, device drivers, and shared protocol libraries at the bottom including OpenFlow, P4Runtime and NETCONF Apps Northbound API — device/protocol-agnostic (Java, REST, CLI, gRPC) Topology · FlowRule · FlowObjective · Intent · Packet · ... Distributed core state management, notifications, high-availability, scale-out Device drivers — device-specific variants of standard protocols Shared protocol libraries OpenFlow P4Runtime NETCONF ...more control and configure with a global topology view, independent of device-specific details
Plate 8.3 — ONOS architecture. Apps talk to a device-agnostic northbound API; the distributed core provides the global view; drivers and shared protocol libraries (OpenFlow, P4Runtime, NETCONF) reach the devices.

ONOS interacts with devices through behaviors — Java interfaces — bundled into drivers. Named behaviors include DeviceDescriptionDiscovery (read device info and ports), FlowRuleProgrammable (write/read flow rules), PortStatisticsDiscovery (port packet/byte counters) and Pipeliner (FlowObjective-to-FlowRules mapping). A driver is a collection of behavior implementations that use ONOS's protocol libraries to reach the device — and the key takeaway is that apps are independent of the switch control protocol: the same app can work with OpenFlow and P4Runtime devices.

8. FlowRule versus FlowObjective

ONOS offers two network-programming APIs, and choosing between them is choosing between control and portability:

FlowRule APIFlowObjective API
Pipeline-dependent. You write a flow rule for a specific pipeline; you get precise control, but the rule is tied to how that pipeline is structured. Pipeline-independent. You state an objective; drivers translate one FlowObjective into many FlowRules via the pipeline's Pipeliner. The same objective works on switches with different pipelines — including switches running different P4 programs.

The FlowObjective API is what enables application portability: an app using FlowObjectives can work with switches whose pipelines differ. The mechanism is a three-phase translation, and it is worth knowing as three concrete rewrites:

  1. Flow Objective → Flow Rule. One objective maps to many flow rules (the Pipeliner).
  2. Flow Rule → Table Entry. Standard headers/actions are mapped to the P4-defined ones — for example ETH_DSThdr.ethernet.dst_addr (the Pipeline Interpreter).
  3. Table Entry → P4Runtime message. P4 names are mapped to P4Info numeric IDs.
Editor's note

Step 2 exposes a genuine impedance mismatch the tutorial flags: P4 allows only one action per table entry, while ONOS (like OpenFlow) allows many. A rule that rewrites a header and outputs on a port is two actions in ONOS but must become one P4 action with two parameters. Resolving that needs interpretation logic — actual Java code in the Pipeline Interpreter — which is why "bring your own pipeline" is a programming task, not a configuration file.

9. Pipeconf: bring your own pipeline

How does ONOS learn to control an arbitrary P4 pipeline? Through a pipeconf: a package that bundles everything ONOS needs to understand, control and deploy a pipeline, provided to ONOS as an app (distributable as an .oar binary). A pipeconf has three parts:

PartWhat it is
1. Pipeline modelThe description of the pipeline understood by ONOS, automatically derived from P4Info.
2. Target-specific binariesTo deploy the pipeline to the device — BMv2 JSON, Tofino binary, FPGA bitstream.
3. Pipeline-specific driver behaviorsThe Pipeliner (map FlowObjectives to the P4 pipeline) and the Pipeline Interpreter (map ONOS known headers/actions to P4 program entities).

The workflow recap ties the whole chapter together: write the P4 program and compile it (obtaining P4Info and target binaries); create the pipeconf (implement the pipeliner and interpreter behaviors in Java); then either use existing pipeline-agnostic apps that program the network with FlowObjectives, or write new pipeline-aware apps that use the same string names of tables, headers and actions as in the P4 program.

A note of realism from the ONOS 1.14 (Owl) support matrix: not everything was supported. Table entries, packet-in/out, action profiles, meters (indirect) and counters mapped to ONOS services, but parser value sets, registers, digests and clone sessions were listed as unsupported, "community help needed" — a useful reminder that the stack is real software with real gaps.

10. A worked controller: DDoS pre-filtering and digests

The CCNC tutorial guide walks through a concrete P4Runtime controller application (a Go P4Runtime client) that combines the data plane and a machine-learning back end. It is the clearest demonstration in the course of why you would push work into the data plane at all.

Exercise 1: DDoS detection with and without data-plane pre-filtering

The setup runs a data plane, a controller, and a CNN (a convolutional neural network detector, "lucid"), then launches an attack from the CIC2017 dataset — which contains a single malicious flow, attacker 172.16.0.1, victim 192.168.10.50. The result is inspected at localhost:3333/ddos.

Exercise 1a — no pre-filteringExercise 1b — count-min pre-filtering in P4
Despite there being only a few genuinely suspect flows, an unexpectedly high number of flows are deemed suspect or malicious. The cause named in the guide: the traffic is not pre-filtered on the data plane, so noise reaches the detector. A count-min-filtered P4 program (p4_packet_management_countmin_filtered.p4) filters on the data plane first. With the pre-filtering stage the number of false positives drops drastically.
Key idea

This is the whole thesis of the programmable data plane made measurable. The neural network did not get smarter between 1a and 1b; the data plane got a count-min sketch that shed noise at line rate before it ever reached the detector. Moving a little state into the switch (a sketch — a register array, chapter 7) improved an ML pipeline's accuracy. Speed and visibility in the data plane translate directly into quality upstream.

Exercise 2: digests

The second exercise (simple_digest.p4) sends aggregated data from the data plane to the control plane: the details of 1 out of every 10 packets are sent to the controller — here just the source IP and port and the destination IP and port, though more could be included. The guide names it precisely: this is the building component that makes the interaction between the data plane and the CNN possible. A digest is P4's answer to chapter 6's limitation "pure P4 cannot communicate to the control plane" — an external object, a learning provider, that samples and reports.

Check your understanding

What are the four layers of the ONOS+P4 full stack?

Your app (SDN apps) on top, ONOS (SDN control plane), P4Runtime (runtime control), and your P4 program on the P4 switch at the bottom. Chapters 6–7 built the bottom layer; this chapter builds the three interfaces above it.

Why is P4Runtime preferred over OpenFlow and SAI?

Because it is target-, protocol- and pipeline-independent all at once. OpenFlow is not protocol-independent (headers and actions are hard-coded in the spec); SAI is neither protocol- nor pipeline-independent (legacy L2/L3/ACL, implicit fixed pipeline). P4Runtime achieves all three, with pipeline-independence "with P4".

On what technologies is P4Runtime built, and what does each contribute?

protobuf — a strongly-typed, platform-neutral language for describing serializable messages, with multi-language code generation. gRPC — uses protobuf to define the service API, auto-generates client/server code, and transports over HTTP/2 and TLS with bidirectional streaming (used for packet-in/out and notifications).

What three kinds of information must the control plane know to add a table entry?

The IDs of the P4 entities (tables, field matches, actions, parameters); the field matches for the particular table (match type, bit width); and the parameters for the particular action. All of these come from the P4Info file.

What is P4Info, and why is it target-independent?

P4Info is one of the two P4-compiler outputs (the other being target-specific binaries). It is a protobuf describing the pipeline schema — tables, actions, parameters, with names, IDs, bit widths and match types — and is the contract the control plane uses to format P4Runtime messages. It is target-independent because it describes the program, not the hardware: the same P4Info applies whether the program runs on BMv2 or a Tofino ASIC.

What does SetForwardingPipelineConfig do?

It pushes a whole P4 program to a switch — the field-reconfigurability feature. The request carries a ForwardingPipelineConfig bundling the P4Info and the target-specific config bits, plus an Action controlling commit semantics (VERIFY, VERIFY_AND_SAVE, VERIFY_AND_COMMIT, COMMIT, RECONCILE_AND_COMMIT).

What is ONOS, and how does it stay device-agnostic?

ONOS is the Open Network Operating System: a logically centralised, distributed SDN control plane with app-friendly APIs. It stays device-agnostic through a layered architecture — a device/protocol-agnostic northbound API, a distributed core with a global topology view, device drivers made of behaviors, and shared protocol libraries (OpenFlow, P4Runtime, NETCONF). The same app can control OpenFlow and P4Runtime devices.

Contrast the FlowRule and FlowObjective APIs.

FlowRule is pipeline-dependent: precise control, tied to a specific pipeline. FlowObjective is pipeline-independent: one objective is translated by the Pipeliner into many flow rules, so the same app works on switches with different pipelines (including different P4 programs). FlowObjective enables application portability.

Describe the three-phase FlowObjective translation and the one-action mismatch.

Flow Objective → Flow Rule (Pipeliner, one-to-many); Flow Rule → Table Entry (Pipeline Interpreter maps standard headers/actions to P4 names, e.g. ETH_DST → hdr.ethernet.dst_addr); Table Entry → P4Runtime message (P4 names to P4Info IDs). The mismatch: P4 allows only one action per entry, while ONOS/OpenFlow allow many, so a rewrite-plus-output becomes one P4 action with two parameters — needing interpretation logic in Java.

What are the three parts of a pipeconf?

(1) the pipeline model, automatically derived from P4Info; (2) target-specific binaries to deploy the pipeline (BMv2 JSON, Tofino binary, FPGA bitstream); (3) pipeline-specific driver behaviors — the Pipeliner and the Pipeline Interpreter. It is delivered to ONOS as an app, distributable as an .oar.

In the DDoS exercise, why did data-plane pre-filtering reduce false positives?

Because in exercise 1a the traffic was not pre-filtered on the data plane, so noise reached the CNN and inflated the count of suspect/malicious flows. In 1b a count-min-filtered P4 program shed that noise at line rate before it reached the detector, drastically reducing false positives — the detector was unchanged; the data plane did the cleaning.

What is a digest, and what problem does it solve?

A digest sends aggregated data from the data plane to the control plane — in the exercise, the source/destination IP and port of 1 in every 10 packets. It is the building component that makes data-plane-to-CNN interaction possible, and it is P4's external-object answer to the language limitation that pure P4 cannot communicate with the control plane.