Updates for p4runtime example (#71)

* Created p4runtime exercise directory with draft P4 program

* Updating VM

- Adding p4 to vboxsf group for VirtualBox Shared Folders
- Adding gRPC Python package for p4 runtime
- Setting up VM to use 2 CPUs

* Updating .gitignore for PyCharms and Mac OS

* Adding P4RuntimeSwitch type and support in run_exercises

If the grpc switch target is used, we will instantiate a P4RuntimeSwitch.
Ideally, this will get merged with BMv2's P4Switch and can be removed

* Adding p4 runtime and p4info browser libraries

Also, adding a Makefile for this project
This commit is contained in:
Brian O'Connor
2017-11-03 10:52:04 -07:00
committed by Robert Soule
parent c9151e767a
commit aa4298859f
12 changed files with 716 additions and 18 deletions

View File

@@ -0,0 +1,10 @@
include ../../utils/Makefile
# Override build method to use simple_switch_grpc target
run: build
sudo python $(RUN_SCRIPT) -t $(TOPO) -b simple_switch_grpc
# Override p4c step to also produce p4info file
P4INFO_ARGS = --p4runtime-file $(basename $@).p4info --p4runtime-format text
$(BUILD_DIR)/%.json: %.p4
$(P4C) --p4v 16 $(P4INFO_ARGS) -o $@ $<

View File

@@ -0,0 +1,236 @@
/* -*- P4_16 -*- */
#include <core.p4>
#include <v1model.p4>
const bit<16> TYPE_MYTUNNEL = 0x1212;
const bit<16> TYPE_IPV4 = 0x800;
/*************************************************************************
*********************** H E A D E R S ***********************************
*************************************************************************/
typedef bit<9> egressSpec_t;
typedef bit<48> macAddr_t;
typedef bit<32> ip4Addr_t;
header ethernet_t {
macAddr_t dstAddr;
macAddr_t srcAddr;
bit<16> etherType;
}
header myTunnel_t {
bit<16> proto_id;
bit<16> dst_id;
}
header ipv4_t {
bit<4> version;
bit<4> ihl;
bit<8> diffserv;
bit<16> totalLen;
bit<16> identification;
bit<3> flags;
bit<13> fragOffset;
bit<8> ttl;
bit<8> protocol;
bit<16> hdrChecksum;
ip4Addr_t srcAddr;
ip4Addr_t dstAddr;
}
struct metadata {
/* empty */
}
struct headers {
ethernet_t ethernet;
myTunnel_t myTunnel;
ipv4_t ipv4;
}
/*************************************************************************
*********************** P A R S E R ***********************************
*************************************************************************/
parser MyParser(packet_in packet,
out headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
state start {
transition parse_ethernet;
}
state parse_ethernet {
packet.extract(hdr.ethernet);
transition select(hdr.ethernet.etherType) {
TYPE_MYTUNNEL: parse_myTunnel;
TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_myTunnel {
packet.extract(hdr.myTunnel);
transition select(hdr.myTunnel.proto_id) {
TYPE_IPV4: parse_ipv4;
default: accept;
}
}
state parse_ipv4 {
packet.extract(hdr.ipv4);
transition accept;
}
}
/*************************************************************************
************ C H E C K S U M V E R I F I C A T I O N *************
*************************************************************************/
control MyVerifyChecksum(inout headers hdr, inout metadata meta) {
apply { }
}
/*************************************************************************
************** I N G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyIngress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
action drop() {
mark_to_drop();
}
action ipv4_forward(macAddr_t dstAddr, egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.ethernet.srcAddr = hdr.ethernet.dstAddr;
hdr.ethernet.dstAddr = dstAddr;
hdr.ipv4.ttl = hdr.ipv4.ttl - 1;
}
action myTunnel_ingress(bit<16> dst_id) {
hdr.myTunnel.setValid();
hdr.myTunnel.dst_id = dst_id;
hdr.myTunnel.proto_id = hdr.ethernet.etherType;
hdr.ethernet.etherType = TYPE_MYTUNNEL;
}
table ipv4_lpm {
key = {
hdr.ipv4.dstAddr: lpm;
}
actions = {
ipv4_forward;
myTunnel_ingress;
drop;
NoAction;
}
size = 1024;
default_action = NoAction();
}
direct_counter(CounterType.packets_and_bytes) tunnelCount;
action myTunnel_forward(egressSpec_t port) {
standard_metadata.egress_spec = port;
tunnelCount.count();
}
action myTunnel_egress(egressSpec_t port) {
standard_metadata.egress_spec = port;
hdr.myTunnel.setInvalid();
tunnelCount.count();
}
table myTunnel_exact {
key = {
hdr.myTunnel.dst_id: exact;
}
actions = {
myTunnel_forward;
myTunnel_egress;
drop;
}
size = 1024;
counters = tunnelCount;
default_action = drop();
}
apply {
if (hdr.ipv4.isValid() && !hdr.myTunnel.isValid()) {
// Process only non-tunneled IPv4 packets.
ipv4_lpm.apply();
}
if (hdr.myTunnel.isValid()) {
// Process all tunneled packets.
myTunnel_exact.apply();
}
}
}
/*************************************************************************
**************** E G R E S S P R O C E S S I N G *******************
*************************************************************************/
control MyEgress(inout headers hdr,
inout metadata meta,
inout standard_metadata_t standard_metadata) {
apply { }
}
/*************************************************************************
************* C H E C K S U M C O M P U T A T I O N **************
*************************************************************************/
control MyComputeChecksum(inout headers hdr, inout metadata meta) {
apply {
update_checksum(
hdr.ipv4.isValid(),
{ hdr.ipv4.version,
hdr.ipv4.ihl,
hdr.ipv4.diffserv,
hdr.ipv4.totalLen,
hdr.ipv4.identification,
hdr.ipv4.flags,
hdr.ipv4.fragOffset,
hdr.ipv4.ttl,
hdr.ipv4.protocol,
hdr.ipv4.srcAddr,
hdr.ipv4.dstAddr },
hdr.ipv4.hdrChecksum,
HashAlgorithm.csum16);
}
}
/*************************************************************************
*********************** D E P A R S E R *******************************
*************************************************************************/
control MyDeparser(packet_out packet, in headers hdr) {
apply {
packet.emit(hdr.ethernet);
packet.emit(hdr.myTunnel);
packet.emit(hdr.ipv4);
}
}
/*************************************************************************
*********************** S W I T C H *******************************
*************************************************************************/
V1Switch(
MyParser(),
MyVerifyChecksum(),
MyIngress(),
MyEgress(),
MyComputeChecksum(),
MyDeparser()
) main;

View File

@@ -0,0 +1,135 @@
# Copyright 2017-present Open Networking Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import re
import google.protobuf.text_format
from p4 import p4runtime_pb2
from p4.config import p4info_pb2
class P4InfoBrowser(object):
def __init__(self, p4_info_filepath):
p4info = p4info_pb2.P4Info()
# Load the p4info file into a skeleton P4Info object
with open(p4_info_filepath) as p4info_f:
google.protobuf.text_format.Merge(p4info_f.read(), p4info)
self.p4info = p4info
def get(self, entity_type, name=None, id=None):
if name is not None and id is not None:
raise AssertionError("name or id must be None")
for o in getattr(self.p4info, entity_type):
pre = o.preamble
if name:
if (pre.name == name or pre.alias == name):
return o
else:
if pre.id == id:
return o
if name:
raise AttributeError("Could not find %r of type %s" % (name, entity_type))
else:
raise AttributeError("Could not find id %r of type %s" % (id, entity_type))
def get_id(self, entity_type, name):
return self.get(entity_type, name=name).preamble.id
def get_name(self, entity_type, id):
return self.get(entity_type, id=id).preamble.name
def get_alias(self, entity_type, id):
return self.get(entity_type, id=id).preamble.alias
def __getattr__(self, attr):
# Synthesize convenience functions for name to id lookups for top-level entities
# e.g. get_table_id() or get_action_id()
m = re.search("^get_(\w+)_id$", attr)
if m:
primitive = m.group(1)
return lambda name: self.get_id(primitive, name)
# Synthesize convenience functions for id to name lookups
m = re.search("^get_(\w+)_name$", attr)
if m:
primitive = m.group(1)
return lambda id: self.get_name(primitive, id)
raise AttributeError("%r object has no attribute %r" % (self.__class__, attr))
# TODO remove
def get_table_entry(self, table_name):
t = self.get(table_name, "table")
entry = p4runtime_pb2.TableEntry()
entry.table_id = t.preamble.id
entry
pass
def get_match_field(self, table_name, match_field_name):
for t in self.p4info.tables:
pre = t.preamble
if pre.name == table_name:
for mf in t.match_fields:
if mf.name == match_field_name:
return mf
def get_match_field_id(self, table_name, match_field_name):
return self.get_match_field(table_name,match_field_name).id
def get_match_field_pb(self, table_name, match_field_name, value):
p4info_match = self.get_match_field(table_name, match_field_name)
bw = p4info_match.bitwidth
p4runtime_match = p4runtime_pb2.FieldMatch()
p4runtime_match.field_id = p4info_match.id
# TODO switch on match type and map the value into the appropriate message type
match_type = p4info_pb2._MATCHFIELD_MATCHTYPE.values_by_number[
p4info_match.match_type].name
if match_type == 'EXACT':
exact = p4runtime_match.exact
exact.value = value
elif match_type == 'LPM':
lpm = p4runtime_match.lpm
lpm.value = value[0]
lpm.prefix_len = value[1]
# TODO finish cases and validate types and bitwidth
# VALID = 1;
# EXACT = 2;
# LPM = 3;
# TERNARY = 4;
# RANGE = 5;
# and raise exception
return p4runtime_match
def get_action_param(self, action_name, param_name):
for a in self.p4info.actions:
pre = a.preamble
if pre.name == action_name:
for p in a.params:
if p.name == param_name:
return p
raise AttributeError("%r has no attribute %r" % (action_name, param_name))
def get_action_param_id(self, action_name, param_name):
return self.get_action_param(action_name, param_name).id
def get_action_param_pb(self, action_name, param_name, value):
p4info_param = self.get_action_param(action_name, param_name)
#bw = p4info_param.bitwidth
p4runtime_param = p4runtime_pb2.Action.Param()
p4runtime_param.param_id = p4info_param.id
p4runtime_param.value = value # TODO make sure it's the correct bitwidth
return p4runtime_param

View File

@@ -0,0 +1,31 @@
# Copyright 2017-present Open Networking Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from switch import SwitchConnection
from p4.tmp import p4config_pb2
def buildDeviceConfig(bmv2_json_file_path=None):
"Builds the device config for BMv2"
device_config = p4config_pb2.P4DeviceConfig()
device_config.reassign = True
# set device_config.extra to default instance
with open(bmv2_json_file_path) as f:
device_config.device_data = f.read()
return device_config
class Bmv2SwitchConnection(SwitchConnection):
def buildDeviceConfig(self, **kwargs):
return buildDeviceConfig(**kwargs)

View File

@@ -0,0 +1,130 @@
# Copyright 2017-present Open Networking Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from abc import abstractmethod
import grpc
from p4 import p4runtime_pb2
from p4.tmp import p4config_pb2
from p4info import p4browser
def buildSetPipelineRequest(p4info, device_config, device_id):
request = p4runtime_pb2.SetForwardingPipelineConfigRequest()
config = request.configs.add()
config.device_id = device_id
config.p4info.CopyFrom(p4info)
config.p4_device_config = device_config.SerializeToString()
request.action = p4runtime_pb2.SetForwardingPipelineConfigRequest.VERIFY_AND_COMMIT
return request
def buildTableEntry(p4info_browser,
table_name,
match_fields={},
action_name=None,
action_params={}):
table_entry = p4runtime_pb2.TableEntry()
table_entry.table_id = p4info_browser.get_tables_id(table_name)
if match_fields:
table_entry.match.extend([
p4info_browser.get_match_field_pb(table_name, match_field_name, value)
for match_field_name, value in match_fields.iteritems()
])
if action_name:
action = table_entry.action.action
action.action_id = p4info_browser.get_actions_id(action_name)
if action_params:
action.params.extend([
p4info_browser.get_action_param_pb(action_name, field_name, value)
for field_name, value in action_params.iteritems()
])
return table_entry
class SwitchConnection(object):
def __init__(self, name, address='127.0.0.1:50051', device_id=0):
self.name = name
self.address = address
self.device_id = device_id
self.p4info = None
self.channel = grpc.insecure_channel(self.address)
# TODO Do want to do a better job managing stub?
self.client_stub = p4runtime_pb2.P4RuntimeStub(self.channel)
@abstractmethod
def buildDeviceConfig(self, **kwargs):
return p4config_pb2.P4DeviceConfig()
def SetForwardingPipelineConfig(self, p4info_file_path, dry_run=False, **kwargs):
p4info_broswer = p4browser.P4InfoBrowser(p4info_file_path)
device_config = self.buildDeviceConfig(**kwargs)
request = buildSetPipelineRequest(p4info_broswer.p4info, device_config, self.device_id)
if dry_run:
print "P4 Runtime SetForwardingPipelineConfig:", request
else:
self.client_stub.SetForwardingPipelineConfig(request)
# Update the local P4 Info reference
self.p4info_broswer = p4info_broswer
def buildTableEntry(self,
table_name,
match_fields={},
action_name=None,
action_params={}):
return buildTableEntry(self.p4info_broswer, table_name, match_fields, action_name, action_params)
def WriteTableEntry(self, table_entry, dry_run=False):
request = p4runtime_pb2.WriteRequest()
request.device_id = self.device_id
update = request.updates.add()
update.type = p4runtime_pb2.Update.INSERT
update.entity.table_entry.CopyFrom(table_entry)
if dry_run:
print "P4 Runtime Write:", request
else:
print self.client_stub.Write(request)
def ReadTableEntries(self, table_name, dry_run=False):
request = p4runtime_pb2.ReadRequest()
request.device_id = self.device_id
entity = request.entities.add()
table_entry = entity.table_entry
table_entry.table_id = self.p4info_broswer.get_tables_id(table_name)
if dry_run:
print "P4 Runtime Read:", request
else:
for response in self.client_stub.Read(request):
yield response
def ReadDirectCounters(self, table_name=None, counter_name=None, table_entry=None, dry_run=False):
request = p4runtime_pb2.ReadRequest()
request.device_id = self.device_id
entity = request.entities.add()
counter_entry = entity.direct_counter_entry
if counter_name:
counter_entry.counter_id = self.p4info_broswer.get_direct_counters_id(counter_name)
else:
counter_entry.counter_id = 0
# TODO we may not need this table entry
if table_name:
table_entry.table_id = self.p4info_broswer.get_tables_id(table_name)
counter_entry.table_entry.CopyFrom(table_entry)
counter_entry.data.packet_count = 0
if dry_run:
print "P4 Runtime Read:", request
else:
for response in self.client_stub.Read(request):
print response