Skip to content

p4ctl ​

Reference CLI built on top of p4runtime-go-controller. Dogfood for the SDK and a starting point for your own controller.

Install ​

Use Go 1.26 or newer. Install the stable CLI:

sh
go install github.com/zhh2001/p4runtime-go-controller/v2/cmd/p4ctl@v2.0.1

For local development, run this from the repository root:

sh
go install ./cmd/p4ctl

The command name remains p4ctl. The unsuffixed module path installs the v1 CLI.

Global flags ​

Every subcommand honors these persistent flags:

--addr string            target address (default "127.0.0.1:9559")
--device-id uint         device ID (default 1)
--election-id uint       election ID, low 64 bits (default 1)
--role string            role name, empty = full access
--insecure               disable TLS (default true)
--tls-ca string          PEM CA bundle for TLS (default system roots)
--tls-server-name string  TLS server name to verify (default target hostname)
--tls-cert string        PEM client certificate for mutual TLS
--tls-key string         PEM client private key for mutual TLS
--config-file string     CLI settings file (default $HOME/.p4ctl.yaml)
--config string          legacy alias, except pipeline set (device config)
--output string          output format: table|json|yaml (default "table")

Settings use command-line flags first, then nonempty environment variables, then the configuration file, then defaults. An explicitly supplied flag, including --insecure=false or --role="", overrides other sources. Empty environment variables are ignored.

SettingEnvironment variable
addrP4CTL_ADDR
device-idP4CTL_DEVICE_ID
election-idP4CTL_ELECTION_ID
roleP4CTL_ROLE
insecureP4CTL_INSECURE
tls-caP4CTL_TLS_CA
tls-server-nameP4CTL_TLS_SERVER_NAME
tls-certP4CTL_TLS_CERT
tls-keyP4CTL_TLS_KEY
outputP4CTL_OUTPUT
CLI settings fileP4CTL_CONFIG_FILE

Environment settings work without a configuration file. Invalid booleans, negative or fractional IDs, IDs above uint64, and unsupported output formats return an error before connecting.

TLS ​

Use --insecure=false to enable TLS with server certificate verification. Without --tls-ca, the client uses the system trust store. With --tls-ca, it trusts the certificates in that PEM bundle. TLS requires version 1.2 or later.

For a target signed by a private CA:

sh
p4ctl connect --addr switch01.lab.internal:9559 --insecure=false \
    --tls-ca ./ca.pem

When connecting by IP address, --tls-server-name can supply the DNS name in the server certificate. That name is still verified:

sh
p4ctl connect --addr 192.0.2.10:9559 --insecure=false \
    --tls-ca ./ca.pem --tls-server-name switch01.lab.internal

For mutual TLS, provide the client certificate and private key together:

sh
p4ctl connect --addr switch01.lab.internal:9559 --insecure=false \
    --tls-ca ./ca.pem --tls-cert ./controller.pem --tls-key ./controller-key.pem

TLS flags require --insecure=false. Missing certificate pairs, unreadable files, CA bundles with no valid certificates, and invalid client certificate pairs are rejected before connecting. Certificate verification failures never cause a retry over plaintext.

Five common workflows ​

1. Sanity-check connectivity ​

sh
p4ctl connect

Expected output: connected: device_id=1 election_id=0:1 state=primary primary=true.

2. Push a pipeline ​

Compile the L2 P4Info and device config together before installing them:

sh
./scripts/compile-l2.sh
sh
p4ctl pipeline set \
    --p4info ./examples/testdata/l2.p4info.txt \
    --config ./examples/testdata/l2.bmv2.json

The CLI tries VERIFY_AND_COMMIT, then RECONCILE_AND_COMMIT if the first action is explicitly unsupported. It reports which action succeeded.

3. Insert a table entry ​

sh
p4ctl table insert \
    --p4info ./examples/testdata/l2.p4info.txt \
    --table MyIngress.t_l2 \
    --match "hdr.eth.dst=00:11:22:33:44:55" \
    --action MyIngress.forward \
    --param "port=1"

Match syntax:

  • field=value — EXACT
  • field=value/prefix — LPM (prefix is bits)
  • field=value&mask — TERNARY
  • field=low..high — RANGE
  • field=?value — OPTIONAL

Match values, masks, range endpoints and action parameters share these literal forms:

FormExample
Unsigned decimal511, 18446744073709551616
Hex with 0x or 0X0x1ff, 0X0001FF
Colon-separated hex bytes01:ff, 00:11:22:33:44:55
IPv4192.0.2.1
IPv6, including mapped addresses2001:db8::1, ::ffff:192.0.2.1

Every value must fit the field or parameter's P4Info bit width. Decimal values can exceed 64 bits when the declared width allows it. Leading zeros are accepted and removed from the wire encoding. Zero is sent as 00. An IPv4 or IPv6 literal is encoded as its big-endian address value, without a port, zone or brackets.

A valid IPv6 literal takes precedence over colon-separated bytes. To send eight raw bytes instead of the IPv6 address 01:02:03:04:05:06:07:08, use 0x01:02:03:04:05:06:07:08. Each colon-separated byte must have two hex digits. Unseparated hex may have an odd number of digits, such as 0x1ff.

Empty values, signed numbers, fractions, unprefixed hex text, malformed literals and values outside the declared width return an error before connecting. Plain text such as port=x is not accepted. Encode byte values explicitly with 0x.

Delete an entry using its match fields, without an action or parameters:

sh
p4ctl table delete \
    --p4info ./examples/testdata/l2.p4info.txt \
    --table MyIngress.t_l2 \
    --match "hdr.eth.dst=00:11:22:33:44:55"

For tables with TERNARY, RANGE, or OPTIONAL fields, pass the same positive --priority used for insertion. EXACT and LPM tables require priority zero. Every EXACT field must be supplied. Wildcard fields identify a wildcard entry with that key and priority, so omitting them does not delete all matching entries.

4. Send a packet out a specific port ​

sh
p4ctl packet send --p4info ./examples/testdata/l2.p4info.txt \
    --hex 0011223344550066778899aa88b568656c6c6f20776f726c6400000000000000000000000000000000000000000000000000000000000000000000000000 --port 1

The payload is a complete Ethernet frame. The L2 program uses CPU port 255 for Packet I/O, and BMv2 must bind a host-facing interface to output port 1.

The command requires packet_out metadata with an egress_port field. It uses that field's declared bit width and supplies zero for _pad when present. Port zero is sent as 00; an out-of-range port returns an error. Pipelines with additional metadata fields require an SDK application that supplies all fields.

The command waits for gRPC to send the request, half-closes the stream, and waits for the target's final RPC status before exiting. Its 10-second deadline also covers connection setup. A timeout or failed final status returns an error. Successful stream completion does not acknowledge individual packet forwarding. Use example 03 for a session that continues receiving PacketIn.

5. Read indirect counters ​

sh
p4ctl counter read --p4info ./examples/testdata/l2.p4info.txt \
    --counter MyIngress.pkt_counter

Configuration file ​

p4ctl looks for $HOME/.p4ctl.yaml by default. A missing default file is optional. An unreadable or malformed file returns an error. Select another file with --config-file or P4CTL_CONFIG_FILE. An explicit flag overrides the environment path, and an explicitly selected file must exist.

The legacy global --config still selects CLI settings for commands other than pipeline set. Supply only one global settings flag. For pipeline set, --config always refers to the compiled device configuration, so use --config-file for CLI settings:

sh
p4ctl --config-file ./controller.yaml pipeline set \
    --p4info ./examples/testdata/l2.p4info.txt \
    --config ./examples/testdata/l2.bmv2.json

YAML and JSON settings files accept the same keys as the global flags. Example with mutual TLS:

yaml
addr: switch01.lab.internal:9559
device-id: 1
election-id: 1
role: ""
insecure: false
tls-ca: ./ca.pem
tls-server-name: switch01.lab.internal
tls-cert: ./controller.pem
tls-key: ./controller-key.pem
output: json

Device and election IDs retain their full 64-bit precision in both formats. Relative certificate paths are resolved from the current working directory.

Output formats ​

The default --output=table keeps the human-readable text. Use --output=json or --output=yaml for these commands:

CommandOutput
connectObject with device_id, election_id, state, and primary
pipeline setObject with the successful action and ordered attempted actions
pipeline getFull P4Info object
table readArray of TableEntry objects
counter readArray of objects with name, id, index, packets, and bytes

Empty read results are []. P4Info and TableEntry use the official protobuf JSON mapping, including camelCase field names, base64 bytes, and strings for 64-bit integers. YAML preserves the same values and types. Counter indices and counts, and the connection's device ID, are also strings to preserve precision. The election ID uses high:low notation.

sh
p4ctl --output=json pipeline get
p4ctl --output=yaml counter read \
    --p4info ./examples/testdata/l2.p4info.txt --counter MyIngress.pkt_counter --index 1

Diagnostics go to stderr, leaving structured stdout ready for parsing. Packet sniffing, version, and help keep their text output. Commands that produce no result remain silent.

Released under the Apache License 2.0.