ONOS-7000 P4 tutorial application and P4 program
Change-Id: Ia0a6befa6374a1950485c1fba0cfacb5ff4ce52c
diff --git a/apps/p4-tutorial/icmpdropper/BUCK b/apps/p4-tutorial/icmpdropper/BUCK
new file mode 100644
index 0000000..026827d
--- /dev/null
+++ b/apps/p4-tutorial/icmpdropper/BUCK
@@ -0,0 +1,25 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//apps/p4-tutorial/pipeconf:onos-apps-p4-tutorial-pipeconf',
+]
+
+osgi_jar (
+ deps = COMPILE_DEPS,
+)
+
+BUNDLES = [
+ '//apps/p4-tutorial/icmpdropper:onos-apps-p4-tutorial-icmpdropper',
+]
+
+onos_app (
+ app_name = 'org.onosproject.p4tutorial.icmpdropper',
+ title = 'ICMP Dropper (P4 Tutorial)',
+ category = 'Security',
+ url = 'http://onosproject.org',
+ description = 'Inhibits forwarding of ICMP packets for PI-enabled devices using the ' +
+ 'p4-tutorial-pipeconf.',
+ included_bundles = BUNDLES,
+ required_apps = [
+ 'org.onosproject.p4tutorial.pipeconf',
+ ]
+)
diff --git a/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java
new file mode 100644
index 0000000..8914ee7
--- /dev/null
+++ b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/IcmpDropper.java
@@ -0,0 +1,167 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.p4tutorial.icmpdropper;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.app.ApplicationAdminService;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.criteria.PiCriterion;
+import org.onosproject.net.pi.model.PiPipelineProgrammable;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiHeaderFieldId;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+import org.onosproject.net.pi.runtime.PiTableId;
+import org.onosproject.p4tutorial.pipeconf.PipeconfFactory;
+import org.slf4j.Logger;
+
+import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_ADDED;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Simple application that drops all ICMP packets.
+ */
+@Component(immediate = true)
+public class IcmpDropper {
+
+ private static final Logger log = getLogger(IcmpDropper.class);
+
+ private static final String APP_NAME = "org.onosproject.p4tutorial.icmpdropper";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private FlowRuleService flowRuleService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private ApplicationAdminService appService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private PiPipeconfService piPipeconfService;
+
+ private final DeviceListener deviceListener = new InternalDeviceListener();
+
+ private ApplicationId appId;
+
+ @Activate
+ public void activate() {
+ log.info("Starting...");
+
+ appId = coreService.registerApplication(APP_NAME);
+ // Register listener for handling new devices.
+ deviceService.addListener(deviceListener);
+ // Install rules to existing devices.
+ deviceService.getDevices()
+ .forEach(device -> installDropRule(device.id()));
+
+ log.info("STARTED", appId.id());
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopping...");
+
+ deviceService.removeListener(deviceListener);
+ flowRuleService.removeFlowRulesById(appId);
+
+ log.info("STOPPED");
+ }
+
+ private boolean checkPipeconf(Device device) {
+ if (!device.is(PiPipelineProgrammable.class)) {
+ // Device is not PI-pipeline programmable. Ignore.
+ return false;
+ }
+ if (!piPipeconfService.ofDevice(device.id()).isPresent() ||
+ !piPipeconfService.ofDevice(device.id()).get().equals(PipeconfFactory.PIPECONF_ID)) {
+ log.warn("Device {} has pipeconf {} instead of {}, can't install flow rule for this device",
+ device.id(), piPipeconfService.ofDevice(device.id()).get(), PipeconfFactory.PIPECONF_ID);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void installDropRule(DeviceId deviceId) {
+ PiHeaderFieldId ipv4ProtoFieldId = PiHeaderFieldId.of("ipv4", "protocol");
+ PiActionId dropActionId = PiActionId.of("_drop");
+
+ PiCriterion piCriterion = PiCriterion.builder()
+ .matchExact(ipv4ProtoFieldId, (byte) 0x01)
+ .build();
+ PiAction dropAction = PiAction.builder()
+ .withId(dropActionId)
+ .build();
+
+ FlowRule flowRule = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .forTable(PiTableId.of("ip_proto_filter_table"))
+ .fromApp(appId)
+ .makePermanent()
+ .withPriority(1000)
+ .withSelector(DefaultTrafficSelector.builder()
+ .matchPi(piCriterion)
+ .build())
+ .withTreatment(
+ DefaultTrafficTreatment.builder()
+ .piTableAction(dropAction)
+ .build())
+ .build();
+
+ log.warn("Installing ICMP drop rule to {}", deviceId);
+
+ flowRuleService.applyFlowRules(flowRule);
+ }
+
+ /**
+ * A listener of device events that installs a rule to drop packet for each new device.
+ */
+ private class InternalDeviceListener implements DeviceListener {
+ @Override
+ public void event(DeviceEvent event) {
+ Device device = event.subject();
+ if (checkPipeconf(device)) {
+ installDropRule(device.id());
+ }
+ }
+
+ @Override
+ public boolean isRelevant(DeviceEvent event) {
+ // Reacts only to new devices.
+ return event.type() == DEVICE_ADDED;
+ }
+ }
+}
diff --git a/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/package-info.java b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/package-info.java
new file mode 100644
index 0000000..5ea1180
--- /dev/null
+++ b/apps/p4-tutorial/icmpdropper/src/main/java/org/onosproject/p4tutorial/icmpdropper/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+/**
+ * P4 tutorial application classes.
+ */
+package org.onosproject.p4tutorial.icmpdropper;
\ No newline at end of file
diff --git a/apps/p4-tutorial/pipeconf/BUCK b/apps/p4-tutorial/pipeconf/BUCK
new file mode 100644
index 0000000..038736f
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/BUCK
@@ -0,0 +1,29 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:minimal-json',
+ '//incubator/bmv2/model:onos-incubator-bmv2-model',
+ '//drivers/default:onos-drivers-default',
+ '//protocols/p4runtime/api:onos-protocols-p4runtime-api',
+]
+
+osgi_jar (
+ deps = COMPILE_DEPS,
+)
+
+BUNDLES = [
+ '//apps/p4-tutorial/pipeconf:onos-apps-p4-tutorial-pipeconf',
+ '//drivers/default:onos-drivers-default',
+ '//incubator/bmv2/model:onos-incubator-bmv2-model',
+]
+
+onos_app (
+ app_name = 'org.onosproject.p4tutorial.pipeconf',
+ title = 'P4 Tutorial Pipeconf',
+ category = 'Pipeconf',
+ url = 'http://onosproject.org',
+ description = 'Provides pipeconf for the ONOS-P4 Tutorial',
+ included_bundles = BUNDLES,
+ required_apps = [
+ 'org.onosproject.drivers.p4runtime',
+ ]
+)
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java
new file mode 100644
index 0000000..b284eaa
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipeconfFactory.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.p4tutorial.pipeconf;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
+import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
+import org.onosproject.net.behaviour.Pipeliner;
+import org.onosproject.net.device.PortStatisticsDiscovery;
+import org.onosproject.net.pi.model.DefaultPiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.model.PiPipeconfId;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.model.PiPipelineModel;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+
+import java.net.URL;
+
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
+import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
+
+/**
+ * Component that produces and registers a pipeconf when loaded.
+ */
+@Component(immediate = true)
+public final class PipeconfFactory {
+
+ public static final PiPipeconfId PIPECONF_ID = new PiPipeconfId("p4-tutorial-pipeconf");
+ private static final URL P4INFO_URL = PipeconfFactory.class.getResource("/main.p4info");
+ private static final URL BMV2_JSON_URL = PipeconfFactory.class.getResource("/main.json");
+ private static final PiPipelineModel PIPELINE_MODEL = Bmv2PipelineModelParser.parse(BMV2_JSON_URL);
+
+ private static final PiPipeconf PIPECONF = DefaultPiPipeconf.builder()
+ .withId(PIPECONF_ID)
+ .withPipelineModel(PIPELINE_MODEL)
+ .addBehaviour(PiPipelineInterpreter.class, PipelineInterpreterImpl.class)
+ .addBehaviour(PortStatisticsDiscovery.class, PortStatisticsDiscoveryImpl.class)
+ // Since main.p4 defines only 1 table, we re-use the existing single-table pipeliner.
+ .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
+ .addExtension(P4_INFO_TEXT, P4INFO_URL)
+ .addExtension(BMV2_JSON, BMV2_JSON_URL)
+ .build();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ private PiPipeconfService piPipeconfService;
+
+ @Activate
+ public void activate() {
+ // Registers the pipeconf at component activation.
+ piPipeconfService.register(PIPECONF);
+ }
+
+ @Deactivate
+ public void deactivate() {
+ piPipeconfService.remove(PIPECONF.id());
+ }
+}
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java
new file mode 100644
index 0000000..c783236
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PipelineInterpreterImpl.java
@@ -0,0 +1,241 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.p4tutorial.pipeconf;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
+import com.google.common.collect.ImmutableList;
+import org.onlab.packet.Ethernet;
+import org.onlab.util.ImmutableByteSequence;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.packet.DefaultInboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.pi.model.PiPipelineInterpreter;
+import org.onosproject.net.pi.runtime.PiAction;
+import org.onosproject.net.pi.runtime.PiActionId;
+import org.onosproject.net.pi.runtime.PiActionParam;
+import org.onosproject.net.pi.runtime.PiActionParamId;
+import org.onosproject.net.pi.runtime.PiCounterId;
+import org.onosproject.net.pi.runtime.PiHeaderFieldId;
+import org.onosproject.net.pi.runtime.PiPacketMetadata;
+import org.onosproject.net.pi.runtime.PiPacketMetadataId;
+import org.onosproject.net.pi.runtime.PiPacketOperation;
+import org.onosproject.net.pi.runtime.PiTableId;
+
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+import static java.lang.String.format;
+import static java.util.stream.Collectors.toList;
+import static org.onlab.util.ImmutableByteSequence.copyFrom;
+import static org.onlab.util.ImmutableByteSequence.fit;
+import static org.onosproject.net.PortNumber.CONTROLLER;
+import static org.onosproject.net.PortNumber.FLOOD;
+import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
+import static org.onosproject.net.pi.runtime.PiPacketOperation.Type.PACKET_OUT;
+
+/**
+ * Implementation of a PI interpreter for the main.p4 program.
+ */
+public final class PipelineInterpreterImpl extends AbstractHandlerBehaviour implements PiPipelineInterpreter {
+
+ private static final String TABLE0 = "table0";
+ private static final String IP_PROTO_FILTER_TABLE = "ip_proto_filter_table";
+ private static final String SEND_TO_CPU = "send_to_cpu";
+ private static final String PORT = "port";
+ private static final String DROP = "_drop";
+ private static final String SET_EGRESS_PORT = "set_egress_port";
+ private static final String EGRESS_PORT = "egress_port";
+ private static final String INGRESS_PORT = "ingress_port";
+ private static final String ETHERNET = "ethernet";
+ private static final String STANDARD_METADATA = "standard_metadata";
+ private static final int PORT_FIELD_BITWIDTH = 9;
+
+ private static final PiHeaderFieldId INGRESS_PORT_ID = PiHeaderFieldId.of(STANDARD_METADATA, "ingress_port");
+ private static final PiHeaderFieldId ETH_DST_ID = PiHeaderFieldId.of(ETHERNET, "dst_addr");
+ private static final PiHeaderFieldId ETH_SRC_ID = PiHeaderFieldId.of(ETHERNET, "src_addr");
+ private static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of(ETHERNET, "ether_type");
+ private static final PiTableId TABLE0_ID = PiTableId.of(TABLE0);
+ private static final PiTableId IP_PROTO_FILTER_TABLE_ID = PiTableId.of(IP_PROTO_FILTER_TABLE);
+
+ private static final BiMap<Integer, PiTableId> TABLE_MAP = ImmutableBiMap.of(
+ 0, TABLE0_ID,
+ 1, IP_PROTO_FILTER_TABLE_ID);
+
+ private static final BiMap<Criterion.Type, PiHeaderFieldId> CRITERION_MAP =
+ new ImmutableBiMap.Builder<Criterion.Type, PiHeaderFieldId>()
+ .put(Criterion.Type.IN_PORT, INGRESS_PORT_ID)
+ .put(Criterion.Type.ETH_DST, ETH_DST_ID)
+ .put(Criterion.Type.ETH_SRC, ETH_SRC_ID)
+ .put(Criterion.Type.ETH_TYPE, ETH_TYPE_ID)
+ .build();
+
+ @Override
+ public PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId)
+ throws PiInterpreterException {
+
+ if (treatment.allInstructions().size() == 0) {
+ // No instructions means drop for us.
+ return PiAction.builder()
+ .withId(PiActionId.of(DROP))
+ .build();
+ } else if (treatment.allInstructions().size() > 1) {
+ // We understand treatments with only 1 instruction.
+ throw new PiInterpreterException("Treatment has multiple instructions");
+ }
+
+ // Get the first and only instruction.
+ Instruction instruction = treatment.allInstructions().get(0);
+
+ switch (instruction.type()) {
+ case OUTPUT:
+ // We understand only instructions of type OUTPUT.
+ Instructions.OutputInstruction outInstruction = (Instructions.OutputInstruction) instruction;
+ PortNumber port = outInstruction.port();
+ if (!port.isLogical()) {
+ return PiAction.builder()
+ .withId(PiActionId.of(SET_EGRESS_PORT))
+ .withParameter(new PiActionParam(PiActionParamId.of(PORT), copyFrom(port.toLong())))
+ .build();
+ } else if (port.equals(CONTROLLER)) {
+ return PiAction.builder()
+ .withId(PiActionId.of(SEND_TO_CPU))
+ .build();
+ } else {
+ throw new PiInterpreterException(format("Output on logical port '%s' not supported", port));
+ }
+ default:
+ throw new PiInterpreterException(format("Instruction of type '%s' not supported", instruction.type()));
+ }
+ }
+
+ @Override
+ public Optional<PiCounterId> mapTableCounter(PiTableId piTableId) {
+ return Optional.empty();
+ }
+
+ @Override
+ public Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet)
+ throws PiInterpreterException {
+
+ TrafficTreatment treatment = packet.treatment();
+
+ // We support only packet-out with OUTPUT instructions.
+ List<Instructions.OutputInstruction> outInstructions = treatment.allInstructions().stream()
+ .filter(i -> i.type().equals(OUTPUT))
+ .map(i -> (Instructions.OutputInstruction) i)
+ .collect(toList());
+
+ if (treatment.allInstructions().size() != outInstructions.size()) {
+ // There are other instructions that are not of type OUTPUT.
+ throw new PiInterpreterException("Treatment not supported: " + treatment);
+ }
+
+ ImmutableList.Builder<PiPacketOperation> builder = ImmutableList.builder();
+
+ for (Instructions.OutputInstruction outInst : outInstructions) {
+ if (outInst.port().isLogical() && !outInst.port().equals(FLOOD)) {
+ throw new PiInterpreterException(format("Output on logical port '%s' not supported", outInst.port()));
+ } else if (outInst.port().equals(FLOOD)) {
+ // Since main.p4 does not support flooding, we create a packet operation for each switch port.
+ DeviceService deviceService = handler().get(DeviceService.class);
+ for (Port port : deviceService.getPorts(packet.sendThrough())) {
+ builder.add(createPiPacketOperation(packet.data(), port.number().toLong()));
+ }
+ } else {
+ builder.add(createPiPacketOperation(packet.data(), outInst.port().toLong()));
+ }
+ }
+ return builder.build();
+ }
+
+ @Override
+ public InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetIn)
+ throws PiInterpreterException {
+ // We assume that the packet is ethernet, which is fine since default.p4 can deparse only ethernet packets.
+ Ethernet ethPkt = new Ethernet();
+
+ ethPkt.deserialize(packetIn.data().asArray(), 0, packetIn.data().size());
+
+ // Returns the ingress port packet metadata.
+ Optional<PiPacketMetadata> packetMetadata = packetIn.metadatas().stream()
+ .filter(metadata -> metadata.id().name().equals(INGRESS_PORT))
+ .findFirst();
+
+ if (packetMetadata.isPresent()) {
+ ImmutableByteSequence portByteSequence = packetMetadata.get().value();
+ short s = portByteSequence.asReadOnlyBuffer().getShort();
+ ConnectPoint receivedFrom = new ConnectPoint(deviceId, PortNumber.portNumber(s));
+ return new DefaultInboundPacket(receivedFrom, ethPkt, packetIn.data().asReadOnlyBuffer());
+ } else {
+ throw new PiInterpreterException(format(
+ "Missing metadata '%s' in packet-in received from '%s': %s", INGRESS_PORT, deviceId, packetIn));
+ }
+ }
+
+ private PiPacketOperation createPiPacketOperation(ByteBuffer data, long portNumber) throws PiInterpreterException {
+ PiPacketMetadata metadata = createPacketMetadata(portNumber);
+ return PiPacketOperation.builder()
+ .withType(PACKET_OUT)
+ .withData(copyFrom(data))
+ .withMetadatas(ImmutableList.of(metadata))
+ .build();
+ }
+
+ private PiPacketMetadata createPacketMetadata(long portNumber) throws PiInterpreterException {
+ try {
+ return PiPacketMetadata.builder()
+ .withId(PiPacketMetadataId.of(EGRESS_PORT))
+ .withValue(fit(copyFrom(portNumber), PORT_FIELD_BITWIDTH))
+ .build();
+ } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
+ throw new PiInterpreterException(format("Port number %d too big, %s", portNumber, e.getMessage()));
+ }
+ }
+
+ @Override
+ public Optional<PiHeaderFieldId> mapCriterionType(Criterion.Type type) {
+ return Optional.ofNullable(CRITERION_MAP.get(type));
+ }
+
+ @Override
+ public Optional<Criterion.Type> mapPiHeaderFieldId(PiHeaderFieldId headerFieldId) {
+ return Optional.ofNullable(CRITERION_MAP.inverse().get(headerFieldId));
+ }
+
+ @Override
+ public Optional<PiTableId> mapFlowRuleTableId(int flowRuleTableId) {
+ return Optional.ofNullable(TABLE_MAP.get(flowRuleTableId));
+ }
+
+ @Override
+ public Optional<Integer> mapPiTableId(PiTableId piTableId) {
+ return Optional.ofNullable(TABLE_MAP.inverse().get(piTableId));
+ }
+}
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java
new file mode 100644
index 0000000..7d715ee1
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/PortStatisticsDiscoveryImpl.java
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+package org.onosproject.p4tutorial.pipeconf;
+
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DefaultPortStatistics;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.PortStatistics;
+import org.onosproject.net.device.PortStatisticsDiscovery;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.pi.model.PiPipeconf;
+import org.onosproject.net.pi.runtime.PiCounterCellData;
+import org.onosproject.net.pi.runtime.PiCounterCellId;
+import org.onosproject.net.pi.runtime.PiCounterId;
+import org.onosproject.net.pi.runtime.PiIndirectCounterCellId;
+import org.onosproject.net.pi.runtime.PiPipeconfService;
+import org.onosproject.p4runtime.api.P4RuntimeClient;
+import org.onosproject.p4runtime.api.P4RuntimeController;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+import static org.onosproject.net.pi.runtime.PiCounterType.INDIRECT;
+
+/**
+ * Implementation of the PortStatisticsDiscovery behaviour for the main.p4 program. This behaviour works by using a
+ * P4Runtime client to read the values of the ingress/egress port counters defined in the P4 program.
+ */
+public final class PortStatisticsDiscoveryImpl extends AbstractHandlerBehaviour implements PortStatisticsDiscovery {
+
+ private static final Logger log = LoggerFactory.getLogger(PortStatisticsDiscoveryImpl.class);
+
+ private static final PiCounterId INGRESS_COUNTER_ID = PiCounterId.of("igr_port_counter", INDIRECT);
+ private static final PiCounterId EGRESS_COUNTER_ID = PiCounterId.of("egr_port_counter", INDIRECT);
+
+ @Override
+ public Collection<PortStatistics> discoverPortStatistics() {
+
+ DeviceService deviceService = this.handler().get(DeviceService.class);
+ DeviceId deviceId = this.data().deviceId();
+
+ // Get a client for this device.
+ P4RuntimeController controller = handler().get(P4RuntimeController.class);
+ if (!controller.hasClient(deviceId)) {
+ log.warn("Unable to find client for {}, aborting operation", deviceId);
+ return Collections.emptyList();
+ }
+ P4RuntimeClient client = controller.getClient(deviceId);
+
+ // Get the pipeconf of this device.
+ PiPipeconfService piPipeconfService = handler().get(PiPipeconfService.class);
+ if (!piPipeconfService.ofDevice(deviceId).isPresent() ||
+ !piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).isPresent()) {
+ log.warn("Unable to get the pipeconf of {}, aborting operation", deviceId);
+ return Collections.emptyList();
+ }
+ PiPipeconf pipeconf = piPipeconfService.getPipeconf(piPipeconfService.ofDevice(deviceId).get()).get();
+
+ // Prepare PortStatistics objects to return, one per port of this device.
+ Map<Long, DefaultPortStatistics.Builder> portStatBuilders = Maps.newHashMap();
+ deviceService
+ .getPorts(deviceId)
+ .forEach(p -> portStatBuilders.put(p.number().toLong(),
+ DefaultPortStatistics.builder()
+ .setPort(p.number())
+ .setDeviceId(deviceId)));
+
+ // Generate the counter cell IDs.
+ Set<PiCounterCellId> counterCellIds = Sets.newHashSet();
+ portStatBuilders.keySet().forEach(p -> {
+ // Counter cell/index = port number.
+ counterCellIds.add(PiIndirectCounterCellId.of(INGRESS_COUNTER_ID, p));
+ counterCellIds.add(PiIndirectCounterCellId.of(EGRESS_COUNTER_ID, p));
+ });
+
+ // Query the device.
+ Collection<PiCounterCellData> counterEntryResponse;
+ try {
+ counterEntryResponse = client.readCounterCells(counterCellIds, pipeconf).get();
+ } catch (InterruptedException | ExecutionException e) {
+ log.warn("Exception while reading port counters from {}: {}", deviceId, e.toString());
+ log.debug("", e);
+ return Collections.emptyList();
+ }
+
+ // Process response.
+ counterEntryResponse.forEach(counterData -> {
+ if (counterData.cellId().type() != INDIRECT) {
+ log.warn("Invalid counter data type {}, skipping", counterData.cellId().type());
+ return;
+ }
+ PiIndirectCounterCellId indCellId = (PiIndirectCounterCellId) counterData.cellId();
+ if (!portStatBuilders.containsKey(indCellId.index())) {
+ log.warn("Unrecognized counter index {}, skipping", counterData);
+ return;
+ }
+ DefaultPortStatistics.Builder statsBuilder = portStatBuilders.get(indCellId.index());
+ if (counterData.cellId().counterId().equals(INGRESS_COUNTER_ID)) {
+ statsBuilder.setPacketsReceived(counterData.packets());
+ statsBuilder.setBytesReceived(counterData.bytes());
+ } else if (counterData.cellId().counterId().equals(EGRESS_COUNTER_ID)) {
+ statsBuilder.setPacketsSent(counterData.packets());
+ statsBuilder.setBytesSent(counterData.bytes());
+ } else {
+ log.warn("Unrecognized counter ID {}, skipping", counterData);
+ }
+ });
+
+ return portStatBuilders
+ .values()
+ .stream()
+ .map(DefaultPortStatistics.Builder::build)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/package-info.java b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/package-info.java
new file mode 100644
index 0000000..3adbcda
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/java/org/onosproject/p4tutorial/pipeconf/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * P4 tutorial pipeconf classes.
+ */
+package org.onosproject.p4tutorial.pipeconf;
\ No newline at end of file
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/Makefile b/apps/p4-tutorial/pipeconf/src/main/resources/Makefile
new file mode 100644
index 0000000..9a6a8d0
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/Makefile
@@ -0,0 +1,6 @@
+all: main
+
+main: main.p4
+ p4c-bm2-ss -o main.json --p4runtime-file main.p4info --p4runtime-format text main.p4
+ # Fix for BMv2/p4c bug...
+ sed -i -e 's/"value" : "0xff"/"value" : "0x00ff"/g' main.json
\ No newline at end of file
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/main.json b/apps/p4-tutorial/pipeconf/src/main/resources/main.json
new file mode 100644
index 0000000..3d059bb
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/main.json
@@ -0,0 +1,931 @@
+{
+ "program" : "main.p4",
+ "__meta__" : {
+ "version" : [2, 7],
+ "compiler" : "https://github.com/p4lang/p4c"
+ },
+ "header_types" : [
+ {
+ "name" : "scalars_0",
+ "id" : 0,
+ "fields" : [
+ ["tmp", 32, false],
+ ["tmp_0", 32, false]
+ ]
+ },
+ {
+ "name" : "ethernet_t",
+ "id" : 1,
+ "fields" : [
+ ["dst_addr", 48, false],
+ ["src_addr", 48, false],
+ ["ether_type", 16, false]
+ ]
+ },
+ {
+ "name" : "ipv4_t",
+ "id" : 2,
+ "fields" : [
+ ["version", 4, false],
+ ["ihl", 4, false],
+ ["diffserv", 8, false],
+ ["len", 16, false],
+ ["identification", 16, false],
+ ["flags", 3, false],
+ ["frag_offset", 13, false],
+ ["ttl", 8, false],
+ ["protocol", 8, false],
+ ["hdr_checksum", 16, false],
+ ["src_addr", 32, false],
+ ["dst_addr", 32, false]
+ ]
+ },
+ {
+ "name" : "packet_out_header_t",
+ "id" : 3,
+ "fields" : [
+ ["egress_port", 9, false],
+ ["_padding", 7, false]
+ ]
+ },
+ {
+ "name" : "packet_in_header_t",
+ "id" : 4,
+ "fields" : [
+ ["ingress_port", 9, false],
+ ["_padding_0", 7, false]
+ ]
+ },
+ {
+ "name" : "standard_metadata",
+ "id" : 5,
+ "fields" : [
+ ["ingress_port", 9, false],
+ ["egress_spec", 9, false],
+ ["egress_port", 9, false],
+ ["clone_spec", 32, false],
+ ["instance_type", 32, false],
+ ["drop", 1, false],
+ ["recirculate_port", 16, false],
+ ["packet_length", 32, false],
+ ["enq_timestamp", 32, false],
+ ["enq_qdepth", 19, false],
+ ["deq_timedelta", 32, false],
+ ["deq_qdepth", 19, false],
+ ["ingress_global_timestamp", 48, false],
+ ["lf_field_list", 32, false],
+ ["mcast_grp", 16, false],
+ ["resubmit_flag", 1, false],
+ ["egress_rid", 16, false],
+ ["_padding_1", 5, false]
+ ]
+ }
+ ],
+ "headers" : [
+ {
+ "name" : "scalars",
+ "id" : 0,
+ "header_type" : "scalars_0",
+ "metadata" : true,
+ "pi_omit" : true
+ },
+ {
+ "name" : "standard_metadata",
+ "id" : 1,
+ "header_type" : "standard_metadata",
+ "metadata" : true,
+ "pi_omit" : true
+ },
+ {
+ "name" : "ethernet",
+ "id" : 2,
+ "header_type" : "ethernet_t",
+ "metadata" : false,
+ "pi_omit" : true
+ },
+ {
+ "name" : "ipv4",
+ "id" : 3,
+ "header_type" : "ipv4_t",
+ "metadata" : false,
+ "pi_omit" : true
+ },
+ {
+ "name" : "packet_out",
+ "id" : 4,
+ "header_type" : "packet_out_header_t",
+ "metadata" : false,
+ "pi_omit" : true
+ },
+ {
+ "name" : "packet_in",
+ "id" : 5,
+ "header_type" : "packet_in_header_t",
+ "metadata" : false,
+ "pi_omit" : true
+ }
+ ],
+ "header_stacks" : [],
+ "header_union_types" : [],
+ "header_unions" : [],
+ "header_union_stacks" : [],
+ "field_lists" : [],
+ "errors" : [
+ ["NoError", 1],
+ ["PacketTooShort", 2],
+ ["NoMatch", 3],
+ ["StackOutOfBounds", 4],
+ ["HeaderTooShort", 5],
+ ["ParserTimeout", 6]
+ ],
+ "enums" : [],
+ "parsers" : [
+ {
+ "name" : "parser",
+ "id" : 0,
+ "init_state" : "start",
+ "parse_states" : [
+ {
+ "name" : "start",
+ "id" : 0,
+ "parser_ops" : [],
+ "transitions" : [
+ {
+ "value" : "0x00ff",
+ "mask" : null,
+ "next_state" : "parse_packet_out"
+ },
+ {
+ "value" : "default",
+ "mask" : null,
+ "next_state" : "parse_ethernet"
+ }
+ ],
+ "transition_key" : [
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ }
+ ]
+ },
+ {
+ "name" : "parse_packet_out",
+ "id" : 1,
+ "parser_ops" : [
+ {
+ "parameters" : [
+ {
+ "type" : "regular",
+ "value" : "packet_out"
+ }
+ ],
+ "op" : "extract"
+ }
+ ],
+ "transitions" : [
+ {
+ "value" : "default",
+ "mask" : null,
+ "next_state" : "parse_ethernet"
+ }
+ ],
+ "transition_key" : []
+ },
+ {
+ "name" : "parse_ethernet",
+ "id" : 2,
+ "parser_ops" : [
+ {
+ "parameters" : [
+ {
+ "type" : "regular",
+ "value" : "ethernet"
+ }
+ ],
+ "op" : "extract"
+ }
+ ],
+ "transitions" : [
+ {
+ "value" : "0x0800",
+ "mask" : null,
+ "next_state" : "parse_ipv4"
+ },
+ {
+ "value" : "default",
+ "mask" : null,
+ "next_state" : null
+ }
+ ],
+ "transition_key" : [
+ {
+ "type" : "field",
+ "value" : ["ethernet", "ether_type"]
+ }
+ ]
+ },
+ {
+ "name" : "parse_ipv4",
+ "id" : 3,
+ "parser_ops" : [
+ {
+ "parameters" : [
+ {
+ "type" : "regular",
+ "value" : "ipv4"
+ }
+ ],
+ "op" : "extract"
+ }
+ ],
+ "transitions" : [
+ {
+ "value" : "default",
+ "mask" : null,
+ "next_state" : null
+ }
+ ],
+ "transition_key" : []
+ }
+ ]
+ }
+ ],
+ "deparsers" : [
+ {
+ "name" : "deparser",
+ "id" : 0,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 264,
+ "column" : 8,
+ "source_fragment" : "DeparserImpl"
+ },
+ "order" : ["packet_in", "ethernet", "ipv4"]
+ }
+ ],
+ "meter_arrays" : [],
+ "counter_arrays" : [
+ {
+ "name" : "egr_port_counter",
+ "id" : 0,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 181,
+ "column" : 38,
+ "source_fragment" : "egr_port_counter"
+ },
+ "size" : 511,
+ "is_direct" : false
+ },
+ {
+ "name" : "igr_port_counter",
+ "id" : 1,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 182,
+ "column" : 38,
+ "source_fragment" : "igr_port_counter"
+ },
+ "size" : 511,
+ "is_direct" : false
+ }
+ ],
+ "register_arrays" : [],
+ "calculations" : [],
+ "learn_lists" : [],
+ "actions" : [
+ {
+ "name" : "NoAction",
+ "id" : 0,
+ "runtime_data" : [],
+ "primitives" : []
+ },
+ {
+ "name" : "send_to_cpu",
+ "id" : 1,
+ "runtime_data" : [],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ {
+ "type" : "hexstr",
+ "value" : "0x00ff"
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 24,
+ "column" : 24,
+ "source_fragment" : "255; ..."
+ }
+ },
+ {
+ "op" : "add_header",
+ "parameters" : [
+ {
+ "type" : "header",
+ "value" : "packet_in"
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 137,
+ "column" : 8,
+ "source_fragment" : "hdr.packet_in.setValid()"
+ }
+ },
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["packet_in", "ingress_port"]
+ },
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 138,
+ "column" : 8,
+ "source_fragment" : "hdr.packet_in.ingress_port = standard_metadata.ingress_port"
+ }
+ }
+ ]
+ },
+ {
+ "name" : "set_egress_port",
+ "id" : 2,
+ "runtime_data" : [
+ {
+ "name" : "port",
+ "bitwidth" : 9
+ }
+ ],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ {
+ "type" : "runtime_data",
+ "value" : 0
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 142,
+ "column" : 8,
+ "source_fragment" : "standard_metadata.egress_spec = port"
+ }
+ }
+ ]
+ },
+ {
+ "name" : "_drop",
+ "id" : 3,
+ "runtime_data" : [],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ {
+ "type" : "hexstr",
+ "value" : "0x01ff"
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 25,
+ "column" : 25,
+ "source_fragment" : "511; ..."
+ }
+ }
+ ]
+ },
+ {
+ "name" : "_drop",
+ "id" : 4,
+ "runtime_data" : [],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ {
+ "type" : "hexstr",
+ "value" : "0x01ff"
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 25,
+ "column" : 25,
+ "source_fragment" : "511; ..."
+ }
+ }
+ ]
+ },
+ {
+ "name" : "act",
+ "id" : 5,
+ "runtime_data" : [],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ {
+ "type" : "field",
+ "value" : ["packet_out", "egress_port"]
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 195,
+ "column" : 12,
+ "source_fragment" : "standard_metadata.egress_spec = hdr.packet_out.egress_port"
+ }
+ },
+ {
+ "op" : "remove_header",
+ "parameters" : [
+ {
+ "type" : "header",
+ "value" : "packet_out"
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 196,
+ "column" : 12,
+ "source_fragment" : "hdr.packet_out.setInvalid()"
+ }
+ }
+ ]
+ },
+ {
+ "name" : "act_0",
+ "id" : 6,
+ "runtime_data" : [],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp"]
+ },
+ {
+ "type" : "expression",
+ "value" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "&",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0xffffffff"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "op" : "count",
+ "parameters" : [
+ {
+ "type" : "counter_array",
+ "value" : "egr_port_counter"
+ },
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp"]
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 218,
+ "column" : 12,
+ "source_fragment" : "egr_port_counter.count((bit<32>) standard_metadata.egress_spec)"
+ }
+ }
+ ]
+ },
+ {
+ "name" : "act_1",
+ "id" : 7,
+ "runtime_data" : [],
+ "primitives" : [
+ {
+ "op" : "assign",
+ "parameters" : [
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp_0"]
+ },
+ {
+ "type" : "expression",
+ "value" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "&",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0xffffffff"
+ }
+ }
+ }
+ }
+ ]
+ },
+ {
+ "op" : "count",
+ "parameters" : [
+ {
+ "type" : "counter_array",
+ "value" : "igr_port_counter"
+ },
+ {
+ "type" : "field",
+ "value" : ["scalars", "tmp_0"]
+ }
+ ],
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 221,
+ "column" : 12,
+ "source_fragment" : "igr_port_counter.count((bit<32>) standard_metadata.ingress_port)"
+ }
+ }
+ ]
+ }
+ ],
+ "pipelines" : [
+ {
+ "name" : "ingress",
+ "id" : 0,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 126,
+ "column" : 8,
+ "source_fragment" : "IngressImpl"
+ },
+ "init_table" : "node_2",
+ "tables" : [
+ {
+ "name" : "tbl_act",
+ "id" : 0,
+ "key" : [],
+ "match_type" : "exact",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [5],
+ "actions" : ["act"],
+ "base_default_next" : "node_7",
+ "next_tables" : {
+ "act" : "node_7"
+ },
+ "default_entry" : {
+ "action_id" : 5,
+ "action_const" : true,
+ "action_data" : [],
+ "action_entry_const" : true
+ }
+ },
+ {
+ "name" : "table0",
+ "id" : 1,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 149,
+ "column" : 10,
+ "source_fragment" : "table0"
+ },
+ "key" : [
+ {
+ "match_type" : "ternary",
+ "target" : ["standard_metadata", "ingress_port"],
+ "mask" : null
+ },
+ {
+ "match_type" : "ternary",
+ "target" : ["ethernet", "dst_addr"],
+ "mask" : null
+ },
+ {
+ "match_type" : "ternary",
+ "target" : ["ethernet", "src_addr"],
+ "mask" : null
+ },
+ {
+ "match_type" : "ternary",
+ "target" : ["ethernet", "ether_type"],
+ "mask" : null
+ }
+ ],
+ "match_type" : "ternary",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [2, 1, 3],
+ "actions" : ["set_egress_port", "send_to_cpu", "_drop"],
+ "base_default_next" : "node_7",
+ "next_tables" : {
+ "set_egress_port" : "node_5",
+ "send_to_cpu" : "node_7",
+ "_drop" : "node_7"
+ },
+ "default_entry" : {
+ "action_id" : 3,
+ "action_const" : false,
+ "action_data" : [],
+ "action_entry_const" : false
+ }
+ },
+ {
+ "name" : "ip_proto_filter_table",
+ "id" : 2,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 164,
+ "column" : 10,
+ "source_fragment" : "ip_proto_filter_table"
+ },
+ "key" : [
+ {
+ "match_type" : "ternary",
+ "target" : ["ipv4", "src_addr"],
+ "mask" : null
+ },
+ {
+ "match_type" : "exact",
+ "target" : ["ipv4", "protocol"],
+ "mask" : null
+ }
+ ],
+ "match_type" : "ternary",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [4, 0],
+ "actions" : ["_drop", "NoAction"],
+ "base_default_next" : "node_7",
+ "next_tables" : {
+ "_drop" : "node_7",
+ "NoAction" : "node_7"
+ },
+ "default_entry" : {
+ "action_id" : 0,
+ "action_const" : false,
+ "action_data" : [],
+ "action_entry_const" : false
+ }
+ },
+ {
+ "name" : "tbl_act_0",
+ "id" : 3,
+ "key" : [],
+ "match_type" : "exact",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [6],
+ "actions" : ["act_0"],
+ "base_default_next" : "node_9",
+ "next_tables" : {
+ "act_0" : "node_9"
+ },
+ "default_entry" : {
+ "action_id" : 6,
+ "action_const" : true,
+ "action_data" : [],
+ "action_entry_const" : true
+ }
+ },
+ {
+ "name" : "tbl_act_1",
+ "id" : 4,
+ "key" : [],
+ "match_type" : "exact",
+ "type" : "simple",
+ "max_size" : 1024,
+ "with_counters" : false,
+ "support_timeout" : false,
+ "direct_meters" : null,
+ "action_ids" : [7],
+ "actions" : ["act_1"],
+ "base_default_next" : null,
+ "next_tables" : {
+ "act_1" : null
+ },
+ "default_entry" : {
+ "action_id" : 7,
+ "action_const" : true,
+ "action_data" : [],
+ "action_entry_const" : true
+ }
+ }
+ ],
+ "action_profiles" : [],
+ "conditionals" : [
+ {
+ "name" : "node_2",
+ "id" : 0,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 188,
+ "column" : 12,
+ "source_fragment" : "standard_metadata.ingress_port == CPU_PORT"
+ },
+ "expression" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "==",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0x00ff"
+ }
+ }
+ },
+ "true_next" : "tbl_act",
+ "false_next" : "table0"
+ },
+ {
+ "name" : "node_5",
+ "id" : 1,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 205,
+ "column" : 24,
+ "source_fragment" : "hdr.ipv4.isValid()"
+ },
+ "expression" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "==",
+ "left" : {
+ "type" : "field",
+ "value" : ["ipv4", "$valid$"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0x01"
+ }
+ }
+ },
+ "true_next" : "ip_proto_filter_table",
+ "false_next" : "node_7"
+ },
+ {
+ "name" : "node_7",
+ "id" : 2,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 217,
+ "column" : 12,
+ "source_fragment" : "standard_metadata.egress_spec < 511"
+ },
+ "expression" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "<",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "egress_spec"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0x01ff"
+ }
+ }
+ },
+ "true_next" : "tbl_act_0",
+ "false_next" : "node_9"
+ },
+ {
+ "name" : "node_9",
+ "id" : 3,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 220,
+ "column" : 12,
+ "source_fragment" : "standard_metadata.ingress_port < 511"
+ },
+ "expression" : {
+ "type" : "expression",
+ "value" : {
+ "op" : "<",
+ "left" : {
+ "type" : "field",
+ "value" : ["standard_metadata", "ingress_port"]
+ },
+ "right" : {
+ "type" : "hexstr",
+ "value" : "0x01ff"
+ }
+ }
+ },
+ "false_next" : null,
+ "true_next" : "tbl_act_1"
+ }
+ ]
+ },
+ {
+ "name" : "egress",
+ "id" : 1,
+ "source_info" : {
+ "filename" : "main.p4",
+ "line" : 230,
+ "column" : 8,
+ "source_fragment" : "EgressImpl"
+ },
+ "init_table" : null,
+ "tables" : [],
+ "action_profiles" : [],
+ "conditionals" : []
+ }
+ ],
+ "checksums" : [],
+ "force_arith" : [],
+ "extern_instances" : [],
+ "field_aliases" : [
+ [
+ "queueing_metadata.enq_timestamp",
+ ["standard_metadata", "enq_timestamp"]
+ ],
+ [
+ "queueing_metadata.enq_qdepth",
+ ["standard_metadata", "enq_qdepth"]
+ ],
+ [
+ "queueing_metadata.deq_timedelta",
+ ["standard_metadata", "deq_timedelta"]
+ ],
+ [
+ "queueing_metadata.deq_qdepth",
+ ["standard_metadata", "deq_qdepth"]
+ ],
+ [
+ "intrinsic_metadata.ingress_global_timestamp",
+ ["standard_metadata", "ingress_global_timestamp"]
+ ],
+ [
+ "intrinsic_metadata.lf_field_list",
+ ["standard_metadata", "lf_field_list"]
+ ],
+ [
+ "intrinsic_metadata.mcast_grp",
+ ["standard_metadata", "mcast_grp"]
+ ],
+ [
+ "intrinsic_metadata.resubmit_flag",
+ ["standard_metadata", "resubmit_flag"]
+ ],
+ [
+ "intrinsic_metadata.egress_rid",
+ ["standard_metadata", "egress_rid"]
+ ]
+ ]
+}
\ No newline at end of file
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/main.p4 b/apps/p4-tutorial/pipeconf/src/main/resources/main.p4
new file mode 100644
index 0000000..9f0b809
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/main.p4
@@ -0,0 +1,284 @@
+/*
+ * 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.
+ */
+
+#include <core.p4>
+#include <v1model.p4>
+
+#define ETH_TYPE_IPV4 0x0800
+#define MAX_PORTS 511
+
+typedef bit<9> port_t;
+const port_t CPU_PORT = 255;
+const port_t DROP_PORT = 511;
+
+//------------------------------------------------------------------------------
+// HEADERS
+//------------------------------------------------------------------------------
+
+header ethernet_t {
+ bit<48> dst_addr;
+ bit<48> src_addr;
+ bit<16> ether_type;
+}
+
+header ipv4_t {
+ bit<4> version;
+ bit<4> ihl;
+ bit<8> diffserv;
+ bit<16> len;
+ bit<16> identification;
+ bit<3> flags;
+ bit<13> frag_offset;
+ bit<8> ttl;
+ bit<8> protocol;
+ bit<16> hdr_checksum;
+ bit<32> src_addr;
+ bit<32> dst_addr;
+}
+
+/*
+Packet-in header. Prepended to packets sent to the controller and used to carry
+the original ingress port where the packet was received.
+ */
+@controller_header("packet_in")
+header packet_in_header_t {
+ bit<9> ingress_port;
+}
+
+/*
+Packet-out header. Prepended to packets received by the controller and used to
+tell the switch on which physical port this packet should be forwarded.
+ */
+@controller_header("packet_out")
+header packet_out_header_t {
+ bit<9> egress_port;
+}
+
+/*
+For convenience we collect all headers under the same struct.
+ */
+struct headers_t {
+ ethernet_t ethernet;
+ ipv4_t ipv4;
+ packet_out_header_t packet_out;
+ packet_in_header_t packet_in;
+}
+
+/*
+Metadata can be used to carry information from one table to another.
+ */
+struct metadata_t {
+ /* Empty. We don't use it in this program. */
+}
+
+//------------------------------------------------------------------------------
+// PARSER
+//------------------------------------------------------------------------------
+
+parser ParserImpl(packet_in packet,
+ out headers_t hdr,
+ inout metadata_t meta,
+ inout standard_metadata_t standard_metadata) {
+
+ state start {
+ transition select(standard_metadata.ingress_port) {
+ CPU_PORT: parse_packet_out;
+ default: parse_ethernet;
+ }
+ }
+
+ state parse_packet_out {
+ packet.extract(hdr.packet_out);
+ transition parse_ethernet;
+ }
+
+ state parse_ethernet {
+ packet.extract(hdr.ethernet);
+ transition select(hdr.ethernet.ether_type) {
+ ETH_TYPE_IPV4: parse_ipv4;
+ default: accept;
+ }
+ }
+
+ state parse_ipv4 {
+ packet.extract(hdr.ipv4);
+ transition accept;
+ }
+}
+
+//------------------------------------------------------------------------------
+// INGRESS PIPELINE
+//------------------------------------------------------------------------------
+
+control IngressImpl(inout headers_t hdr,
+ inout metadata_t meta,
+ inout standard_metadata_t standard_metadata) {
+
+ action send_to_cpu() {
+ standard_metadata.egress_spec = CPU_PORT;
+ /*
+ Packets sent to the controller needs to be prepended with the packet-in
+ header. By setting it valid we make sure it will be deparsed before the
+ ethernet header (see DeparserImpl).
+ */
+ hdr.packet_in.setValid();
+ hdr.packet_in.ingress_port = standard_metadata.ingress_port;
+ }
+
+ action set_egress_port(port_t port) {
+ standard_metadata.egress_spec = port;
+ }
+
+ action _drop() {
+ standard_metadata.egress_spec = DROP_PORT;
+ }
+
+ table table0 {
+ key = {
+ standard_metadata.ingress_port : ternary;
+ hdr.ethernet.dst_addr : ternary;
+ hdr.ethernet.src_addr : ternary;
+ hdr.ethernet.ether_type : ternary;
+ }
+ actions = {
+ set_egress_port();
+ send_to_cpu();
+ _drop();
+ }
+ default_action = _drop();
+ }
+
+ table ip_proto_filter_table {
+ key = {
+ hdr.ipv4.src_addr : ternary;
+ hdr.ipv4.protocol : exact;
+ }
+ actions = {
+ _drop();
+ }
+ }
+
+ /*
+ Port counters.
+ We use these counter instances to count packets/bytes received/sent on each
+ port. BMv2 always counts both packets and bytes, even if the counter is
+ instantiated as "packets". For each counter we instantiate a number of cells
+ equal to MAX_PORTS.
+ */
+ counter(MAX_PORTS, CounterType.packets) egr_port_counter;
+ counter(MAX_PORTS, CounterType.packets) igr_port_counter;
+
+ /*
+ We define here the processing to be executed by this ingress pipeline.
+ */
+ apply {
+ if (standard_metadata.ingress_port == CPU_PORT) {
+ /*
+ Packet received from CPU_PORT, this is a packet-out sent by the
+ controller. Skip pipeline processing, set the egress port as
+ requested by the controller (packet_out header) and remove the
+ packet_out header.
+ */
+ standard_metadata.egress_spec = hdr.packet_out.egress_port;
+ hdr.packet_out.setInvalid();
+ } else {
+ /*
+ Packet received from switch port. Apply table0, if action is
+ set_egress_port and packet is IPv4, then apply
+ ip_proto_filter_table.
+ */
+ switch(table0.apply().action_run) {
+ set_egress_port: {
+ if (hdr.ipv4.isValid()) {
+ ip_proto_filter_table.apply();
+ }
+ }
+ }
+ }
+
+ /*
+ For each port counter, we update the cell at index = ingress/egress
+ port. We avoid counting packets sent/received on CPU_PORT or dropped
+ (DROP_PORT).
+ */
+ if (standard_metadata.egress_spec < MAX_PORTS) {
+ egr_port_counter.count((bit<32>) standard_metadata.egress_spec);
+ }
+ if (standard_metadata.ingress_port < MAX_PORTS) {
+ igr_port_counter.count((bit<32>) standard_metadata.ingress_port);
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+// EGRESS PIPELINE
+//------------------------------------------------------------------------------
+
+control EgressImpl(inout headers_t hdr,
+ inout metadata_t meta,
+ inout standard_metadata_t standard_metadata) {
+ apply {
+ /*
+ Nothing to do on the egress pipeline.
+ */
+ }
+}
+
+//------------------------------------------------------------------------------
+// CHECKSUM HANDLING
+//------------------------------------------------------------------------------
+
+control VerifyChecksumImpl(in headers_t hdr, inout metadata_t meta) {
+ apply {
+ /*
+ Nothing to do here, we assume checksum is always correct.
+ */
+ }
+}
+
+control ComputeChecksumImpl(inout headers_t hdr, inout metadata_t meta) {
+ apply {
+ /*
+ Nothing to do here, as we do not modify packet headers.
+ */
+ }
+}
+
+//------------------------------------------------------------------------------
+// DEPARSER
+//------------------------------------------------------------------------------
+
+control DeparserImpl(packet_out packet, in headers_t hdr) {
+ apply {
+ /*
+ Deparse headers in order. Only valid headers are emitted.
+ */
+ packet.emit(hdr.packet_in);
+ packet.emit(hdr.ethernet);
+ packet.emit(hdr.ipv4);
+ }
+}
+
+//------------------------------------------------------------------------------
+// SWITCH INSTANTIATION
+//------------------------------------------------------------------------------
+
+V1Switch(ParserImpl(),
+ VerifyChecksumImpl(),
+ IngressImpl(),
+ EgressImpl(),
+ ComputeChecksumImpl(),
+ DeparserImpl()) main;
diff --git a/apps/p4-tutorial/pipeconf/src/main/resources/main.p4info b/apps/p4-tutorial/pipeconf/src/main/resources/main.p4info
new file mode 100644
index 0000000..c4882d2
--- /dev/null
+++ b/apps/p4-tutorial/pipeconf/src/main/resources/main.p4info
@@ -0,0 +1,147 @@
+tables {
+ preamble {
+ id: 33617813
+ name: "table0"
+ alias: "table0"
+ }
+ match_fields {
+ id: 1
+ name: "standard_metadata.ingress_port"
+ bitwidth: 9
+ match_type: TERNARY
+ }
+ match_fields {
+ id: 2
+ name: "hdr.ethernet.dst_addr"
+ bitwidth: 48
+ match_type: TERNARY
+ }
+ match_fields {
+ id: 3
+ name: "hdr.ethernet.src_addr"
+ bitwidth: 48
+ match_type: TERNARY
+ }
+ match_fields {
+ id: 4
+ name: "hdr.ethernet.ether_type"
+ bitwidth: 16
+ match_type: TERNARY
+ }
+ action_refs {
+ id: 16794308
+ }
+ action_refs {
+ id: 16829080
+ }
+ action_refs {
+ id: 16784184
+ }
+ size: 1024
+}
+tables {
+ preamble {
+ id: 33573361
+ name: "ip_proto_filter_table"
+ alias: "ip_proto_filter_table"
+ }
+ match_fields {
+ id: 1
+ name: "hdr.ipv4.src_addr"
+ bitwidth: 32
+ match_type: TERNARY
+ }
+ match_fields {
+ id: 2
+ name: "hdr.ipv4.protocol"
+ bitwidth: 8
+ match_type: EXACT
+ }
+ action_refs {
+ id: 16784184
+ }
+ action_refs {
+ id: 16800567
+ annotations: "@defaultonly()"
+ }
+ size: 1024
+}
+actions {
+ preamble {
+ id: 16800567
+ name: "NoAction"
+ alias: "NoAction"
+ }
+}
+actions {
+ preamble {
+ id: 16829080
+ name: "send_to_cpu"
+ alias: "send_to_cpu"
+ }
+}
+actions {
+ preamble {
+ id: 16794308
+ name: "set_egress_port"
+ alias: "set_egress_port"
+ }
+ params {
+ id: 1
+ name: "port"
+ bitwidth: 9
+ }
+}
+actions {
+ preamble {
+ id: 16784184
+ name: "_drop"
+ alias: "_drop"
+ }
+}
+counters {
+ preamble {
+ id: 302012419
+ name: "egr_port_counter"
+ alias: "egr_port_counter"
+ }
+ spec {
+ unit: PACKETS
+ }
+ size: 511
+}
+counters {
+ preamble {
+ id: 302054463
+ name: "igr_port_counter"
+ alias: "igr_port_counter"
+ }
+ spec {
+ unit: PACKETS
+ }
+ size: 511
+}
+controller_packet_metadata {
+ preamble {
+ id: 2868941301
+ name: "packet_in"
+ annotations: "@controller_header(\"packet_in\")"
+ }
+ metadata {
+ id: 1
+ name: "ingress_port"
+ bitwidth: 9
+ }
+}
+controller_packet_metadata {
+ preamble {
+ id: 2868916615
+ name: "packet_out"
+ annotations: "@controller_header(\"packet_out\")"
+ }
+ metadata {
+ id: 1
+ name: "egress_port"
+ bitwidth: 9
+ }
+}
diff --git a/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java b/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
index 487380f..d0a4732 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/criteria/PiCriterion.java
@@ -342,7 +342,7 @@
*
* @return PiCriterion
*/
- public Criterion build() {
+ public PiCriterion build() {
ImmutableMap<PiHeaderFieldId, PiFieldMatch> fieldMatchMap = fieldMatchMapBuilder.build();
checkArgument(fieldMatchMap.size() > 0, "Cannot build PI criterion with 0 field matches");
return new PiCriterion(fieldMatchMap);
diff --git a/modules.defs b/modules.defs
index 4f67048..98bf292 100644
--- a/modules.defs
+++ b/modules.defs
@@ -219,6 +219,8 @@
'//apps/evpn-route-service:onos-apps-evpn-route-service-oar',
'//incubator/protobuf/registry:onos-incubator-protobuf-registry-oar',
'//apps/openstacknetworkingui:onos-apps-openstacknetworkingui-oar',
+ '//apps/p4-tutorial/pipeconf:onos-apps-p4-tutorial-pipeconf-oar',
+ '//apps/p4-tutorial/icmpdropper:onos-apps-p4-tutorial-icmpdropper-oar',
]
PROTOCOL_APPS = [