[ONOS-6856] Interpreter implementation for tor.p4
Change-Id: Id5bcb1433e66eafa5ef033670ac0eac0dcd9431b
diff --git a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java
index e6fc8d9..5bd3864 100644
--- a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java
+++ b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorInterpreter.java
@@ -16,38 +16,116 @@
package org.onosproject.pi.demo.app.tor;
+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.Collections;
+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 PiPipeline interpreter for TOR configuration.
+ * Implementation of a PiPipeline interpreter for tor.p4.
*/
-//TODO implement
public class TorInterpreter extends AbstractHandlerBehaviour implements PiPipelineInterpreter {
+ private static final boolean PACKET_IO_ENABLED = true;
- @Override
- public Optional<Integer> mapPiTableId(PiTableId piTableId) {
- return Optional.empty();
- }
+ //For this simple interpreter we need just the punt_table defined in punt.p4
+ private static final String PUNT = "punt";
+ private static final PiTableId PUNT_TABLE_ID = PiTableId.of(PUNT, "punt_table");
+ private static final String SEND_TO_CPU = PUNT + ".set_queue_and_send_to_cpu";
+
+ private static final String QUEUE_ID = "queue_id";
+
+ private static final String EGRESS_PORT = "egress_physical_port";
+ private static final String INGRESS_PORT = "ingress_physical_port";
+
+ // Set as per value in headers.p4 in packet_out_header
+ private static final int PORT_FIELD_BITWIDTH = 9;
+
+ private static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of("ethernet", "ether_type");
+
+ private static final ImmutableBiMap<Integer, PiTableId> TABLE_MAP = ImmutableBiMap.of(
+ // If we use the DefaultSingleTable pipeliner, then we need to map only table ID 0,
+ // which in this case will be the one used by proxyarp, lldpprovider, etc., i.e. the punt table.
+ 0, PUNT_TABLE_ID);
+
+ private ImmutableBiMap<Criterion.Type, PiHeaderFieldId> criterionMap =
+ new ImmutableBiMap.Builder<Criterion.Type, PiHeaderFieldId>()
+ .put(Criterion.Type.ETH_TYPE, ETH_TYPE_ID)
+ .build();
+
+ //FIXME figure out what queque id is, we set as all zeros for now.
+ private static final PiActionParam QUEUE_ID_PARAM = new PiActionParam(PiActionParamId.of(QUEUE_ID),
+ ImmutableByteSequence.copyFrom((byte) 0));
+ // Only action we need, can be defined statically.
+ private static final PiAction SEND_TO_CPU_ACTION = PiAction.builder()
+ .withId(PiActionId.of(SEND_TO_CPU))
+ .withParameter(QUEUE_ID_PARAM)
+ .build();
@Override
public PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId) throws PiInterpreterException {
- return null;
+ if (piTableId.equals(PUNT_TABLE_ID)) {
+ if (treatment.allInstructions().size() != 1) {
+ // We understand treatments with only 1 instruction.
+ throw new PiPipelineInterpreter.PiInterpreterException("Treatment must have only 1 instruction");
+ }
+
+ Instruction instruction = treatment.allInstructions().get(0);
+
+ switch (instruction.type()) {
+ case OUTPUT:
+ Instructions.OutputInstruction outInstruction = (Instructions.OutputInstruction) instruction;
+ PortNumber port = outInstruction.port();
+ if (port.equals(CONTROLLER)) {
+ return SEND_TO_CPU_ACTION;
+ } else {
+ throw new PiInterpreterException(format("Egress not supported on %s port", port));
+ }
+ default:
+ throw new PiInterpreterException(format("Instruction type '%s' not supported", instruction.type()));
+ }
+ } else {
+ throw new PiInterpreterException(format("Table '%s' not supported", piTableId.name()));
+ }
}
@Override
@@ -56,28 +134,110 @@
}
@Override
- public Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet) throws PiInterpreterException {
- return ImmutableList.of();
+ public Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet)
+ throws PiInterpreterException {
+
+ if (!PACKET_IO_ENABLED) {
+ return Collections.emptyList();
+ }
+
+ TrafficTreatment treatment = packet.treatment();
+
+ // tor.p4 supports only 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 default.p4 does not support flooding, we create a packet operation for each switch port.
+ for (Port port : handler().get(DeviceService.class).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 packetInOperation)
+ public InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetIn)
throws PiInterpreterException {
- return null;
+
+ if (!PACKET_IO_ENABLED) {
+ return null;
+ }
+
+ // FIXME Assuming that the packet is ethernet. is it fine ? tor.p4 can deparse also other 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));
+ ByteBuffer rawData = ByteBuffer.wrap(packetIn.data().asArray());
+ return new DefaultInboundPacket(receivedFrom, ethPkt, rawData);
+ } 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.empty();
+ return Optional.ofNullable(criterionMap.get(type));
}
@Override
public Optional<Criterion.Type> mapPiHeaderFieldId(PiHeaderFieldId headerFieldId) {
- return Optional.empty();
+ return Optional.ofNullable(criterionMap.inverse().get(headerFieldId));
}
@Override
public Optional<PiTableId> mapFlowRuleTableId(int flowRuleTableId) {
- return Optional.empty();
+ return Optional.ofNullable(TABLE_MAP.get(flowRuleTableId));
+ }
+
+ @Override
+ public Optional<Integer> mapPiTableId(PiTableId piTableId) {
+ return Optional.ofNullable(TABLE_MAP.inverse().get(piTableId));
}
}
\ No newline at end of file
diff --git a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorPipeconfFactory.java b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorPipeconfFactory.java
index e9dbba2..d6c635c 100644
--- a/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorPipeconfFactory.java
+++ b/apps/pi-demo/tor/src/main/java/org/onosproject/pi/demo/app/tor/TorPipeconfFactory.java
@@ -18,14 +18,15 @@
import com.google.common.collect.Lists;
import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
+import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
import org.onosproject.drivers.p4runtime.DefaultP4PortStatisticsDiscovery;
-import org.onosproject.drivers.p4runtime.NetcfgLinkDiscovery;
-import org.onosproject.net.behaviour.LinkDiscovery;
+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 java.net.URL;
import java.util.Collection;
@@ -42,18 +43,19 @@
final class TorPipeconfFactory {
private static final String BMV2_PIPECONF_ID = "bmv2-tor";
- private static final URL BMV2_P4INFO_URL = TorApp.class.getResource("/tor.p4info");
- private static final URL BMV2_JSON_URL = TorApp.class.getResource("/tor.json");
+ private static final URL BMV2_P4INFO_URL = TorApp.class.getResource("/p4c-out/tor.p4info");
+ private static final URL BMV2_JSON_URL = TorApp.class.getResource("/p4c-out/tor.json");
private static final String TOFINO_PIPECONF_ID_BASE = "tofino-tor-%s";
- private static final String TOFINO_JSON_PATH_BASE = "/tor-tofino/%s/tor.json";
- private static final String TOFINO_P4INFO_PATH_BASE = "/tor-tofino/%s/tor.p4info";
- private static final String TOFINO_CONTEXT_JSON_PATH_BASE = "/tor-tofino/%s/context/context.json";
- private static final String TOFINO_BIN_PATH_BASE = "/tor-tofino/%s/tofino.bin";
+ private static final String TOFINO_P4INFO_PATH_BASE = "/p4c-out/tofino/tor/%s/tor.p4info";
+ private static final String TOFINO_CONTEXT_JSON_PATH_BASE = "/p4c-out/tofino/tor/%s/context.json";
+ private static final String TOFINO_BIN_PATH_BASE = "/p4c-out/tofino/tor/%s/tofino.bin";
private static final String MAVERICKS = "mavericks";
private static final String MONTARA = "montara";
+ private static final PiPipelineModel PIPELINE_MODEL = Bmv2PipelineModelParser.parse(BMV2_JSON_URL);
+
private static final PiPipeconf BMV2_PIPECONF = buildBmv2Pipeconf();
private static final PiPipeconf MAVERICKS_PIPECONF = buildTofinoPipeconf(MAVERICKS);
private static final PiPipeconf MONTARA_PIPECONF = buildTofinoPipeconf(MONTARA);
@@ -66,21 +68,24 @@
return Lists.newArrayList(BMV2_PIPECONF, MAVERICKS_PIPECONF, MONTARA_PIPECONF);
}
+ private static DefaultPiPipeconf.Builder baseBuilder() {
+ return DefaultPiPipeconf.builder()
+ .withPipelineModel(PIPELINE_MODEL)
+ .addBehaviour(PiPipelineInterpreter.class, TorInterpreter.class)
+ // FIXME: add if Packet I/O is not working
+ // .addBehaviour(LinkDiscovery.class, NetcfgLinkDiscovery.class)
+ // FIXME: change underneath to proper TOR implementation
+ .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
+ .addBehaviour(PortStatisticsDiscovery.class, DefaultP4PortStatisticsDiscovery.class);
+ }
+
private static PiPipeconf buildTofinoPipeconf(String system) {
- final URL bmv2JsonUrl = TorPipeconfFactory.class.getResource(format(TOFINO_JSON_PATH_BASE, system));
final URL p4InfoUrl = TorPipeconfFactory.class.getResource(format(TOFINO_P4INFO_PATH_BASE, system));
final URL tofinoUrl = TorPipeconfFactory.class.getResource(format(TOFINO_BIN_PATH_BASE, system));
final URL contextUrl = TorPipeconfFactory.class.getResource(format(TOFINO_CONTEXT_JSON_PATH_BASE, system));
- return DefaultPiPipeconf.builder()
+ return baseBuilder()
.withId(new PiPipeconfId(format(TOFINO_PIPECONF_ID_BASE, system)))
- .withPipelineModel(Bmv2PipelineModelParser.parse(bmv2JsonUrl))
- .addBehaviour(PiPipelineInterpreter.class, TorInterpreter.class)
- //FIXME change underneath to proper TOR implementation
- //.addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
- .addBehaviour(PortStatisticsDiscovery.class, DefaultP4PortStatisticsDiscovery.class)
- // FIXME: can remove if Packet I/O is working
- .addBehaviour(LinkDiscovery.class, NetcfgLinkDiscovery.class)
.addExtension(P4_INFO_TEXT, p4InfoUrl)
.addExtension(TOFINO_BIN, tofinoUrl)
.addExtension(TOFINO_CONTEXT_JSON, contextUrl)
@@ -88,15 +93,8 @@
}
private static PiPipeconf buildBmv2Pipeconf() {
- return DefaultPiPipeconf.builder()
+ return baseBuilder()
.withId(new PiPipeconfId(BMV2_PIPECONF_ID))
- .withPipelineModel(Bmv2PipelineModelParser.parse(BMV2_JSON_URL))
- .addBehaviour(PiPipelineInterpreter.class, TorInterpreter.class)
- //FIXME change underneath to proper TOR implementation
- //.addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
- .addBehaviour(PortStatisticsDiscovery.class, DefaultP4PortStatisticsDiscovery.class)
- // FIXME: can remove if Packet I/O is working
- .addBehaviour(LinkDiscovery.class, NetcfgLinkDiscovery.class)
.addExtension(P4_INFO_TEXT, BMV2_P4INFO_URL)
.addExtension(BMV2_JSON, BMV2_JSON_URL)
.build();