Package: p4runtime-shell
A shell for P4RuntimeThis is still a work in progress. Feedback is welcome.p4runtime-sh is an interactive Python shell forP4Runtimebased onIPython.A shell for P4RuntimeUsing the shellRun with DockerRun without DockerAvailable commandsCanonical representation of bytestringsExample usageUsing p4runtime-shell in scriptsTarget-specific supportP4.org Bmv2Barefoot TofinoTLS AuthenticationServer AuthenticationMutual Authentication (with client certificate)A Mutual Authentication example with bmv2 and self-signed certificatesUsing the shellRun with DockerWe recommend that you download the Docker image (~142MB) and use it, but you can
also build the image directly with:gitclonehttps://github.com/p4lang/p4runtime-shellcdp4runtime-shell
dockerbuild-tp4lang/p4runtime-sh.Run the shell as follows:[sudo]dockerrun-tip4lang/p4runtime-sh\--grpc-addr<serverIP>:<serverport>\--device-id0--election-id0,1The above command will retrieve the forwarding pipeline configuration from the
P4Runtime server. You can also push a forwarding pipeline configuration with the
shell (you will need to mount the directory containing the P4Info and binary
device config in the docker):[sudo]dockerrun-ti-v/tmp/:/tmp/p4lang/p4runtime-sh\--grpc-addr<serverIP>:<serverport>\--device-id0--election-id0,1--config/tmp/p4info.txt,/tmp/bmv2.jsonThe above command assumes that the P4Info (p4info.txt) and the binary device
config (bmv2.json) are under /tmp/.To make the process more convenient, we provide a wrapper script, which takes
care of running the docker (including mounting the P4Info and binary device
config files in the docker if needed):[sudo]./p4runtime-sh-docker--grpc-addr<serverIP>:<serverport>\--device-id0--election-id0,1\--config<pathtop4info>,<pathtobinaryconfig>If you are a Linux user, you can follow thisguideto run Docker
commands withoutsudo. You will be able to usep4runtime-sh-dockeras a
non-privileged user.If you are using the Docker image to run p4runtime-shell and you are trying to
connect to a P4Runtime server running natively on the same system and listening
on the localhost interface, you will not be able to connect to the server using--grpc-addr localhost:<port>or--grpc-addr 127.0.0.1:<port>. Instead, you
should have your P4Runtime server listen on all interfaces (0.0.0.0) and you
will need to use the IP address assigned to the Docker bridge (docker0by
default) or the IP address assigned to the local network management interface
(e.g.eth0).Run without DockerYou can also install P4Runtime shell viapip3and run it directly.# (optional) Set up virtual environmentpython3-mvenvvenvsourcevenv/bin/activate# Install p4runtime-shell package and run itpip3installp4runtime-shell
python3-mp4runtime_sh--grpc-addr<serverIP>:<serverport>\--device-id0--election-id0,1--config<p4info.txt>,<pipelineconfig>Available commandstables,actions,action_profiles,counters,direct_counters,meters,direct_meters(named after the P4Info message fields) to query information
about P4Info objects.table_entry,action_profile_member,action_profile_group,counter_entry,direct_counter_entry,meter_entry,direct_meter_entry(named after the
P4RuntimeEntityfields), along withmulticast_group_entryandclone_session_entry, for runtime entity programming.packet_inandpacket_outare commands for packet IO, see theusagefor more information.TheWritecommand can be used to read aWriteRequestmessage from a file
(for now, Protobuf text format only) and send it to a server:Write <path to file encoding WriteRequest message in text format>Type the command name followed by?for information on each command,
e.g.table_entry?.Canonical representation of bytestringsTheP4Runtime
specificationdefines a canonical representation for binary strings, which all P4Runtime
servers must support. This representation can be used to format all binary
strings (match fields, action parameters, ...) in P4Runtime messages exchanged
between the client and the server. For legacy reasons, some P4Runtime servers do
not support the canonical representation and require binary strings to be
byte-padded according to the bitwidth specified in the P4Info message. While all
P4Runtime-conformant servers must also accept this legacy format, it can lead to
read-write asymmetry for P4Runtime entities. For example a client may insert a
TableEntry using the legacy format for match fields, but when reading the same
TableEntry back, the server may return a message with match field values in the
canonical representation. When a client uses the canonical representation,
read-write symmetry is always guaranteed.If you are dealing with a legacy server which rejects binary strings formatted
using the canonical representation (making this server non conformant to the
specification), you can revert to the byte-padded format by typing the following
command in the shell:P4Runtimesh>>>global_options["canonical_bytestrings"]=FalseExample usageHere is some of what you can do when using p4runtime-sh with ONF'sfabric.p4.More examples of usage can be found in theusage/ folder.***WelcometotheIPythonshellforP4Runtime***P4Runtimesh>>>tablesFabricEgress.egress_next.egress_vlanFabricIngress.acl.aclFabricIngress.filtering.fwd_classifierFabricIngress.filtering.ingress_port_vlanFabricIngress.forwarding.bridgingFabricIngress.forwarding.mplsFabricIngress.forwarding.routing_v4FabricIngress.next.hashedFabricIngress.next.multicastFabricIngress.next.next_vlanFabricIngress.next.xconnectP4Runtimesh>>>tables["FabricIngress.forwarding.routing_v4"]Out[2]:preamble{id:33562650name:"FabricIngress.forwarding.routing_v4"alias:"routing_v4"}match_fields{id:1name:"ipv4_dst"bitwidth:32match_type:LPM}action_refs{id:16777434("FabricIngress.forwarding.set_next_id_routing_v4")}action_refs{id:16804187("FabricIngress.forwarding.nop_routing_v4")}action_refs{id:16819938("nop")annotations:"@defaultonly"scope:DEFAULT_ONLY}const_default_action_id:16819938("nop")direct_resource_ids:318811107("FabricIngress.forwarding.routing_v4_counter")size:1024P4Runtimesh>>>te=table_entry["FabricIngress.forwarding.routing_v4"](action="set_next_id_routing_v4")P4Runtimesh>>>te?Signature:te(**kwargs)Type:TableEntryStringform:table_id:33562650("FabricIngress.forwarding.routing_v4")action{action{action_id:16777434("FabricIngress.forwarding.set_next_id_routing_v4")}}File:/p4runtime-sh/p4runtime_sh/shell.pyDocstring:Anentryfortable'FabricIngress.forwarding.routing_v4'Use<self>.infotodisplaytheP4Infoentryforthistable.Tosetthematchkey,use<self>.match['<field name>']=<expr>.Type<self>.match?formoredetails.Tosettheactionspecification<self>.action=<instanceoftypeAction>.Tosetthevalueofactionparameters,use<self>.action['<param name>']=<expr>.Type<self>.action?formoredetails.Tosetthepriority,use<self>.priority=<expr>.Tomarktheentryasdefault,use<self>.is_default=True.Typicalusagetoinsertatableentry:t=table_entry['<table_name>'](action='<action_name>')t.match['<f1>']=......t.match['<fN>']=...# OR t.match.set(f1=..., ..., fN=...)t.action['<p1>']=......t.action['<pM>']=...# OR t.action.set(p1=..., ..., pM=...)t.insertTypicalusagetosetthedefaultentry:t=table_entry['<table_name>'](is_default=True)t.action['<p1>']=......t.action['<pM>']=...# OR t.action.set(p1=..., ..., pM=...)t.modifyForinformationabouthowtoreadtableentries,use<self>.read?P4Runtimesh>>>te.match?Type:MatchKeyFile:/p4runtime-sh/p4runtime_sh/shell.pyDocstring:Matchkeyfieldsfortable'FabricIngress.forwarding.routing_v4':id:1name:"ipv4_dst"bitwidth:32match_type:LPMSetafieldvaluewith<self>['<field_name>']='...'*Forexactmatch:<self>['<f>']='<value>'*Forternarymatch:<self>['<f>']='<value>&&&<mask>'*ForLPMmatch:<self>['<f>']='<value>/<mask>'*Forrangematch:<self>['<f>']='<value>..<mask>'*Foroptionalmatch:<self>['<f>']='<value>'Ifit's inconvenient to use the whole field name, you can use a unique suffix.Youmayalsouse<self>.set(<f>='<value>')(<f>mustnotincludea'.'inthiscase,butrememberthatyoucanuseauniquesuffix)P4Runtimesh>>>te.match["ipv4_dst"]="10.0.0.0/16"field_id:1lpm{value:"\n\000\000\000"prefix_len:16}P4Runtimesh>>>te.action?Type:ActionFile:/p4runtime-sh/p4runtime_sh/shell.pyDocstring:Actionparametersforaction'set_next_id_routing_v4':id:1name:"next_id"bitwidth:32Setaparamvaluewith<self>['<param_name>']='<value>'Youmayalsouse<self>.set(<param_name>='<value>')P4Runtimesh>>>te.action["next_id"]="10"param_id:1value:"\000\000\000\n"P4Runtimesh>>>te.insertP4Runtimesh>>>forteintable_entry["FabricIngress.forwarding.routing_v4"].read():...:print(te)...:table_id:33562650("FabricIngress.forwarding.routing_v4")match{field_id:1("ipv4_dst")lpm{value:"\\x0a\\x00\\x00\\x00"prefix_len:16}}action{action{action_id:16777434("FabricIngress.forwarding.set_next_id_routing_v4")params{param_id:1("next_id")value:"\\x00\\x00\\x00\\x0a"}}}P4Runtimesh>>>table_entry["FabricIngress.forwarding.routing_v4"].read(lambdate:te.delete())P4Runtimesh>>>table_entry["FabricIngress.forwarding.routing_v4"].read(lambdate:print(te))P4Runtimesh>>>Using p4runtime-shell in scriptsYou can also leverage this project as a convenient P4Runtime wrapper to
programmatically program switches using Python scripts:importp4runtime_sh.shellassh# you can omit the config argument if the switch is already configured with the# correct P4 dataplane.sh.setup(device_id=1,grpc_addr='localhost:9559',election_id=(0,1),# (high, low)config=sh.FwdPipeConfig('config/p4info.pb.txt','config/device_config.bin'))# see p4runtime_sh/test.py for more exampleste=sh.TableEntry('<table_name>')(action='<action_name>')te.match['<name>']='<value>'te.action['<name>']='<value>'te.insert()# ...sh.teardown()Note that at the moment the P4Runtime client object is a global variable, which
means that we only support one P4Runtime connection to a single switch.Target-specific supportP4.org Bmv2Just use the bmv2 JSON file generated by thecompileras the binary device config.Barefoot TofinoWe provide a script which can be used to "pack" the Barefoot p4c compiler output
(part of the Barefoot SDE) into one binary file, to be used as the binary device
config../config_builders/tofino.py--ctx-json<pathtocontextJSON>\--tofino-bin<pathtotofino.bin>-p<programname>-oout.binYou can then useout.binwhen invokingp4runtime-sh-docker:[sudo]./p4runtime-sh-docker--grpc-addr<serverIP>:<serverport>\--device-id0--election-id0,1\--config<pathtop4info>,out.binTLS AuthenticationBy default, the shell opens an insecure gRPC channel to the P4Runtime
server. This will only work if the P4Runtime server is itself insecure and does
not use TLS. Note that by default, bmv2 simple_switch_grpc uses an insecure
server and no TLS configuration is required for the shell. For general
information about authentication and encryption with gRPC, please refer to thedocumentation.If you are connecting to a P4Runtime server which is secured with TLS, please
keep reading.Server AuthenticationYou will need to enable TLS in the shell by starting it with--ssl. If you are
using a self-signed certificate for the P4Runtime server, you must also provide
a CA certificate with--cacert.[sudo]./p4runtime-sh-docker--grpc-addr<serveraddress>:9559\--device-id0--election-id0,1--config/tmp/p4info.txt,/tmp/bmv2.json\--ssl[--cacert<pathtoPEMcertificate>].The<server address>needs to match the Common Name (CN) or one of the Subject
Alternative Names (SAN) in the server certificate. In some cases, a matching SAN
is required (the Common Name is considered deprecated).Mutual Authentication (with client certificate)If the P4Runtime server requires clients to present a certificate for client
authentication, you will need to provide the appropriate certificate with--certand the appropriate private key with--private-key.[sudo]./p4runtime-sh-docker--grpc-addr<serveraddress>:9559\--device-id0--election-id0,1--config/tmp/p4info.txt,/tmp/bmv2.json\--ssl\--cacert<pathtoPEMcertificateforserververification>\--cert<pathtoPEMclientcertificate>\--private-key<pathtoPEMclientprivatekey>Note that the appropriate CA certificate (the one used to sign the client
certificate) needs to be provided to the P4Runtime server. How to do that will
be dependent on the P4Runtime implementation.Make sure that the private key file is not password-protected or gRPC client
initialization will fail.A Mutual Authentication example with bmv2 and self-signed certificatesIn this example, we will use the same CA for both server and client certificates
but this is of course not a requirement. Make sure you are using recent versions
of PI, bmv2 and p4runtime-shell for this to work.password=abcd# Generate CAopensslgenrsa-passoutpass:$password-des3-outca.key4096opensslreq-passinpass:$password-new-x509-days365-keyca.key-outca.crt\-subj"/C=US/ST=CA/L=PaloAlto/O=Test/OU=Test/CN=Root CA"# Generate server certificate and keyopensslgenrsa-passoutpass:$password-des3-outserver.key4096opensslreq-passinpass:$password-new-keyserver.key-outserver.csr\-subj"/C=US/ST=CA/L=PaloAlto/O=Test/OU=Server/CN=localhost"opensslx509-req-passinpass:$password\-extfile<(printf"subjectAltName=DNS:localhost,IP:127.0.0.1")\-days365-inserver.csr-CAca.crt-CAkeyca.key-set_serial01-outserver.crt# Remove passphrase from the server keyopensslrsa-passinpass:$password-inserver.key-outserver.key# Generate client certificate and keyopensslgenrsa-passoutpass:$password-des3-outclient.key4096opensslreq-passinpass:$password-new-keyclient.key-outclient.csr-subj"/C=US/ST=CA/L=PaloAlto/O=Test/OU=Client/CN=p4runtime-sh"opensslx509-passinpass:$password-req-days365-inclient.csr-CAca.crt-CAkeyca.key-set_serial01-outclient.crt# Remove passphrase from the client keyopensslrsa-passinpass:$password-inclient.key-outclient.keyTo run simple_switch_grpc:simple_switch_grpc--log-console--no-p4--device-id1--\--grpc-server-ssl--grpc-server-cacertca.crt\--grpc-server-certserver.crt--grpc-server-keyserver.key\--grpc-server-with-client-authTo run the shell:python3-mp4runtime_sh--grpc-addrlocalhost:9559--device-id1\--configmeter.proto.txt,meter.json\--ssl--cacertca.crt--certclient.crt--private-keyclient.keyNote that we do not usep4runtime-sh-dockerin this example. This is because
if we run the shell inside a Docker container, we will not be able to connect to
the P4Runtime server usinglocalhostat the address, and this is the name we
used to issue the server certificate.