blob: 6bf4f774293729cf88db420ed3a7ea867d1434bd [file] [log] [blame]
Andrea Campanella31bcdcd2017-09-19 16:35:44 +09001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.pi.demo.app.tor;
18
Andrea Campanella81c10b32017-09-27 18:53:20 +020019import com.google.common.collect.ImmutableBiMap;
Andrea Campanella31bcdcd2017-09-19 16:35:44 +090020import com.google.common.collect.ImmutableList;
Andrea Campanella81c10b32017-09-27 18:53:20 +020021import org.onlab.packet.Ethernet;
22import org.onlab.util.ImmutableByteSequence;
23import org.onosproject.net.ConnectPoint;
Andrea Campanella31bcdcd2017-09-19 16:35:44 +090024import org.onosproject.net.DeviceId;
Andrea Campanella81c10b32017-09-27 18:53:20 +020025import org.onosproject.net.Port;
26import org.onosproject.net.PortNumber;
27import org.onosproject.net.device.DeviceService;
Andrea Campanella31bcdcd2017-09-19 16:35:44 +090028import org.onosproject.net.driver.AbstractHandlerBehaviour;
29import org.onosproject.net.flow.TrafficTreatment;
30import org.onosproject.net.flow.criteria.Criterion;
Andrea Campanella81c10b32017-09-27 18:53:20 +020031import org.onosproject.net.flow.instructions.Instruction;
32import org.onosproject.net.flow.instructions.Instructions;
33import org.onosproject.net.packet.DefaultInboundPacket;
Andrea Campanella31bcdcd2017-09-19 16:35:44 +090034import org.onosproject.net.packet.InboundPacket;
35import org.onosproject.net.packet.OutboundPacket;
36import org.onosproject.net.pi.model.PiPipelineInterpreter;
37import org.onosproject.net.pi.runtime.PiAction;
Andrea Campanella81c10b32017-09-27 18:53:20 +020038import org.onosproject.net.pi.runtime.PiActionId;
39import org.onosproject.net.pi.runtime.PiActionParam;
40import org.onosproject.net.pi.runtime.PiActionParamId;
Carmelo Cascone724ed122017-10-03 16:36:45 +020041import org.onosproject.net.pi.runtime.PiActionProfileId;
Andrea Campanella31bcdcd2017-09-19 16:35:44 +090042import org.onosproject.net.pi.runtime.PiCounterId;
43import org.onosproject.net.pi.runtime.PiHeaderFieldId;
Andrea Campanella81c10b32017-09-27 18:53:20 +020044import org.onosproject.net.pi.runtime.PiPacketMetadata;
45import org.onosproject.net.pi.runtime.PiPacketMetadataId;
Andrea Campanella31bcdcd2017-09-19 16:35:44 +090046import org.onosproject.net.pi.runtime.PiPacketOperation;
47import org.onosproject.net.pi.runtime.PiTableId;
48
Andrea Campanella81c10b32017-09-27 18:53:20 +020049import java.nio.ByteBuffer;
Andrea Campanella31bcdcd2017-09-19 16:35:44 +090050import java.util.Collection;
Andrea Campanella81c10b32017-09-27 18:53:20 +020051import java.util.Collections;
52import java.util.List;
Andrea Campanella31bcdcd2017-09-19 16:35:44 +090053import java.util.Optional;
54
Andrea Campanella81c10b32017-09-27 18:53:20 +020055import static java.lang.String.format;
56import static java.util.stream.Collectors.toList;
57import static org.onlab.util.ImmutableByteSequence.copyFrom;
58import static org.onlab.util.ImmutableByteSequence.fit;
59import static org.onosproject.net.PortNumber.CONTROLLER;
60import static org.onosproject.net.PortNumber.FLOOD;
61import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
62import static org.onosproject.net.pi.runtime.PiPacketOperation.Type.PACKET_OUT;
63
Andrea Campanella31bcdcd2017-09-19 16:35:44 +090064/**
Andrea Campanella81c10b32017-09-27 18:53:20 +020065 * Implementation of a PiPipeline interpreter for tor.p4.
Andrea Campanella31bcdcd2017-09-19 16:35:44 +090066 */
Andrea Campanella31bcdcd2017-09-19 16:35:44 +090067public class TorInterpreter extends AbstractHandlerBehaviour implements PiPipelineInterpreter {
68
Andrea Campanella81c10b32017-09-27 18:53:20 +020069 private static final boolean PACKET_IO_ENABLED = true;
Andrea Campanella31bcdcd2017-09-19 16:35:44 +090070
Andrea Campanella81c10b32017-09-27 18:53:20 +020071 //For this simple interpreter we need just the punt_table defined in punt.p4
72 private static final String PUNT = "punt";
73 private static final PiTableId PUNT_TABLE_ID = PiTableId.of(PUNT, "punt_table");
74 private static final String SEND_TO_CPU = PUNT + ".set_queue_and_send_to_cpu";
75
76 private static final String QUEUE_ID = "queue_id";
77
78 private static final String EGRESS_PORT = "egress_physical_port";
79 private static final String INGRESS_PORT = "ingress_physical_port";
80
Carmelo Casconef22e8e42017-09-28 20:22:37 +020081 static final PiTableId L3_FILTER_TBL_ID = PiTableId.of("l3_fwd", "l3_routing_classifier_table");
82 static final PiTableId L3_FWD_TBL_ID = PiTableId.of("l3_fwd", "l3_ipv4_override_table");
83 static final PiActionId SET_NEXT_HOP_ACT_ID = PiActionId.of("l3_fwd.set_nexthop");
84 static final PiActionParamId PORT_ACT_PRM_ID = PiActionParamId.of("port");
85 static final PiActionParamId SMAC_ACT_PRM_ID = PiActionParamId.of("smac");
86 static final PiActionParamId DMAC_ACT_PRM_ID = PiActionParamId.of("dmac");
Carmelo Cascone724ed122017-10-03 16:36:45 +020087 static final PiActionProfileId WCMP_ACT_PROF_ID = PiActionProfileId.of("l3_fwd.wcmp_action_profile");
88 static final PiActionId NO_ACTION_ID = PiActionId.of("NoAction");
Carmelo Casconef22e8e42017-09-28 20:22:37 +020089
Andrea Campanella81c10b32017-09-27 18:53:20 +020090 // Set as per value in headers.p4 in packet_out_header
91 private static final int PORT_FIELD_BITWIDTH = 9;
92
93 private static final PiHeaderFieldId ETH_TYPE_ID = PiHeaderFieldId.of("ethernet", "ether_type");
Carmelo Casconef22e8e42017-09-28 20:22:37 +020094 private static final PiHeaderFieldId ETH_DST_ID = PiHeaderFieldId.of("ethernet", "dst_addr");
95 private static final PiHeaderFieldId ETH_SRC_ID = PiHeaderFieldId.of("ethernet", "src_addr");
96 private static final PiHeaderFieldId IPV4_DST_ID = PiHeaderFieldId.of("ipv4_base", "dst_addr");
Andrea Campanella81c10b32017-09-27 18:53:20 +020097
98 private static final ImmutableBiMap<Integer, PiTableId> TABLE_MAP = ImmutableBiMap.of(
99 // If we use the DefaultSingleTable pipeliner, then we need to map only table ID 0,
100 // which in this case will be the one used by proxyarp, lldpprovider, etc., i.e. the punt table.
Carmelo Casconef22e8e42017-09-28 20:22:37 +0200101 0, PUNT_TABLE_ID,
102 1, L3_FILTER_TBL_ID,
103 2, L3_FWD_TBL_ID);
Andrea Campanella81c10b32017-09-27 18:53:20 +0200104
105 private ImmutableBiMap<Criterion.Type, PiHeaderFieldId> criterionMap =
106 new ImmutableBiMap.Builder<Criterion.Type, PiHeaderFieldId>()
Carmelo Casconef22e8e42017-09-28 20:22:37 +0200107 .put(Criterion.Type.ETH_DST, ETH_DST_ID)
108 .put(Criterion.Type.ETH_SRC, ETH_SRC_ID)
Andrea Campanella81c10b32017-09-27 18:53:20 +0200109 .put(Criterion.Type.ETH_TYPE, ETH_TYPE_ID)
Carmelo Casconef22e8e42017-09-28 20:22:37 +0200110 .put(Criterion.Type.IPV4_DST, IPV4_DST_ID)
Andrea Campanella81c10b32017-09-27 18:53:20 +0200111 .build();
112
113 //FIXME figure out what queque id is, we set as all zeros for now.
114 private static final PiActionParam QUEUE_ID_PARAM = new PiActionParam(PiActionParamId.of(QUEUE_ID),
115 ImmutableByteSequence.copyFrom((byte) 0));
116 // Only action we need, can be defined statically.
117 private static final PiAction SEND_TO_CPU_ACTION = PiAction.builder()
118 .withId(PiActionId.of(SEND_TO_CPU))
119 .withParameter(QUEUE_ID_PARAM)
120 .build();
Andrea Campanella31bcdcd2017-09-19 16:35:44 +0900121
122 @Override
123 public PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId) throws PiInterpreterException {
Andrea Campanella81c10b32017-09-27 18:53:20 +0200124 if (piTableId.equals(PUNT_TABLE_ID)) {
125 if (treatment.allInstructions().size() != 1) {
126 // We understand treatments with only 1 instruction.
127 throw new PiPipelineInterpreter.PiInterpreterException("Treatment must have only 1 instruction");
128 }
129
130 Instruction instruction = treatment.allInstructions().get(0);
131
132 switch (instruction.type()) {
133 case OUTPUT:
134 Instructions.OutputInstruction outInstruction = (Instructions.OutputInstruction) instruction;
135 PortNumber port = outInstruction.port();
136 if (port.equals(CONTROLLER)) {
137 return SEND_TO_CPU_ACTION;
138 } else {
139 throw new PiInterpreterException(format("Egress not supported on %s port", port));
140 }
141 default:
142 throw new PiInterpreterException(format("Instruction type '%s' not supported", instruction.type()));
143 }
144 } else {
145 throw new PiInterpreterException(format("Table '%s' not supported", piTableId.name()));
146 }
Andrea Campanella31bcdcd2017-09-19 16:35:44 +0900147 }
148
149 @Override
150 public Optional<PiCounterId> mapTableCounter(PiTableId piTableId) {
151 return Optional.empty();
152 }
153
154 @Override
Andrea Campanella81c10b32017-09-27 18:53:20 +0200155 public Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet)
156 throws PiInterpreterException {
157
158 if (!PACKET_IO_ENABLED) {
159 return Collections.emptyList();
160 }
161
162 TrafficTreatment treatment = packet.treatment();
163
164 // tor.p4 supports only OUTPUT instructions.
165 List<Instructions.OutputInstruction> outInstructions = treatment.allInstructions()
166 .stream()
167 .filter(i -> i.type().equals(OUTPUT))
168 .map(i -> (Instructions.OutputInstruction) i)
169 .collect(toList());
170
171 if (treatment.allInstructions().size() != outInstructions.size()) {
172 // There are other instructions that are not of type OUTPUT.
173 throw new PiInterpreterException("Treatment not supported: " + treatment);
174 }
175
176 ImmutableList.Builder<PiPacketOperation> builder = ImmutableList.builder();
177 for (Instructions.OutputInstruction outInst : outInstructions) {
178 if (outInst.port().isLogical() && !outInst.port().equals(FLOOD)) {
179 throw new PiInterpreterException(format("Output on logical port '%s' not supported", outInst.port()));
180 } else if (outInst.port().equals(FLOOD)) {
181 // Since default.p4 does not support flooding, we create a packet operation for each switch port.
182 for (Port port : handler().get(DeviceService.class).getPorts(packet.sendThrough())) {
183 builder.add(createPiPacketOperation(packet.data(), port.number().toLong()));
184 }
185 } else {
186 builder.add(createPiPacketOperation(packet.data(), outInst.port().toLong()));
187 }
188 }
189 return builder.build();
Andrea Campanella31bcdcd2017-09-19 16:35:44 +0900190 }
191
192 @Override
Andrea Campanella81c10b32017-09-27 18:53:20 +0200193 public InboundPacket mapInboundPacket(DeviceId deviceId, PiPacketOperation packetIn)
Andrea Campanella31bcdcd2017-09-19 16:35:44 +0900194 throws PiInterpreterException {
Andrea Campanella81c10b32017-09-27 18:53:20 +0200195
196 if (!PACKET_IO_ENABLED) {
197 return null;
198 }
199
200 // FIXME Assuming that the packet is ethernet. is it fine ? tor.p4 can deparse also other packets.
201 Ethernet ethPkt = new Ethernet();
202
203 ethPkt.deserialize(packetIn.data().asArray(), 0, packetIn.data().size());
204
205 // Returns the ingress port packet metadata.
206 Optional<PiPacketMetadata> packetMetadata = packetIn.metadatas()
207 .stream().filter(metadata -> metadata.id().name().equals(INGRESS_PORT))
208 .findFirst();
209
210 if (packetMetadata.isPresent()) {
211 ImmutableByteSequence portByteSequence = packetMetadata.get().value();
212 short s = portByteSequence.asReadOnlyBuffer().getShort();
213 ConnectPoint receivedFrom = new ConnectPoint(deviceId, PortNumber.portNumber(s));
214 ByteBuffer rawData = ByteBuffer.wrap(packetIn.data().asArray());
215 return new DefaultInboundPacket(receivedFrom, ethPkt, rawData);
216 } else {
217 throw new PiInterpreterException(format(
218 "Missing metadata '%s' in packet-in received from '%s': %s", INGRESS_PORT, deviceId, packetIn));
219 }
220 }
221
222 private PiPacketOperation createPiPacketOperation(ByteBuffer data, long portNumber) throws PiInterpreterException {
223 PiPacketMetadata metadata = createPacketMetadata(portNumber);
224 return PiPacketOperation.builder()
225 .withType(PACKET_OUT)
226 .withData(copyFrom(data))
227 .withMetadatas(ImmutableList.of(metadata))
228 .build();
229 }
230
231 private PiPacketMetadata createPacketMetadata(long portNumber) throws PiInterpreterException {
232 try {
233 return PiPacketMetadata.builder()
234 .withId(PiPacketMetadataId.of(EGRESS_PORT))
235 .withValue(fit(copyFrom(portNumber), PORT_FIELD_BITWIDTH))
236 .build();
237 } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
238 throw new PiInterpreterException(format("Port number %d too big, %s", portNumber, e.getMessage()));
239 }
Andrea Campanella31bcdcd2017-09-19 16:35:44 +0900240 }
241
242 @Override
243 public Optional<PiHeaderFieldId> mapCriterionType(Criterion.Type type) {
Andrea Campanella81c10b32017-09-27 18:53:20 +0200244 return Optional.ofNullable(criterionMap.get(type));
Andrea Campanella31bcdcd2017-09-19 16:35:44 +0900245 }
246
247 @Override
248 public Optional<Criterion.Type> mapPiHeaderFieldId(PiHeaderFieldId headerFieldId) {
Andrea Campanella81c10b32017-09-27 18:53:20 +0200249 return Optional.ofNullable(criterionMap.inverse().get(headerFieldId));
Andrea Campanella31bcdcd2017-09-19 16:35:44 +0900250 }
251
252 @Override
253 public Optional<PiTableId> mapFlowRuleTableId(int flowRuleTableId) {
Andrea Campanella81c10b32017-09-27 18:53:20 +0200254 return Optional.ofNullable(TABLE_MAP.get(flowRuleTableId));
255 }
256
257 @Override
258 public Optional<Integer> mapPiTableId(PiTableId piTableId) {
259 return Optional.ofNullable(TABLE_MAP.inverse().get(piTableId));
Andrea Campanella31bcdcd2017-09-19 16:35:44 +0900260 }
261}