blob: 6bf4f774293729cf88db420ed3a7ea867d1434bd [file] [log] [blame]
/*
* 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.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.PiActionProfileId;
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.p4.
*/
public class TorInterpreter extends AbstractHandlerBehaviour implements PiPipelineInterpreter {
private static final boolean PACKET_IO_ENABLED = true;
//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";
static final PiTableId L3_FILTER_TBL_ID = PiTableId.of("l3_fwd", "l3_routing_classifier_table");
static final PiTableId L3_FWD_TBL_ID = PiTableId.of("l3_fwd", "l3_ipv4_override_table");
static final PiActionId SET_NEXT_HOP_ACT_ID = PiActionId.of("l3_fwd.set_nexthop");
static final PiActionParamId PORT_ACT_PRM_ID = PiActionParamId.of("port");
static final PiActionParamId SMAC_ACT_PRM_ID = PiActionParamId.of("smac");
static final PiActionParamId DMAC_ACT_PRM_ID = PiActionParamId.of("dmac");
static final PiActionProfileId WCMP_ACT_PROF_ID = PiActionProfileId.of("l3_fwd.wcmp_action_profile");
static final PiActionId NO_ACTION_ID = PiActionId.of("NoAction");
// 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 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 IPV4_DST_ID = PiHeaderFieldId.of("ipv4_base", "dst_addr");
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,
1, L3_FILTER_TBL_ID,
2, L3_FWD_TBL_ID);
private ImmutableBiMap<Criterion.Type, PiHeaderFieldId> criterionMap =
new ImmutableBiMap.Builder<Criterion.Type, PiHeaderFieldId>()
.put(Criterion.Type.ETH_DST, ETH_DST_ID)
.put(Criterion.Type.ETH_SRC, ETH_SRC_ID)
.put(Criterion.Type.ETH_TYPE, ETH_TYPE_ID)
.put(Criterion.Type.IPV4_DST, IPV4_DST_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 {
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
public Optional<PiCounterId> mapTableCounter(PiTableId piTableId) {
return Optional.empty();
}
@Override
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 packetIn)
throws PiInterpreterException {
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.ofNullable(criterionMap.get(type));
}
@Override
public Optional<Criterion.Type> mapPiHeaderFieldId(PiHeaderFieldId headerFieldId) {
return Optional.ofNullable(criterionMap.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));
}
}