blob: 77a6dc39f42d05b860280c8f7046df535b78c5a9 [file] [log] [blame]
Carmelo Cascone770507f2017-09-14 20:58:04 +02001/*
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.p4tutorial.pipeconf;
18
19import com.google.common.collect.BiMap;
20import com.google.common.collect.ImmutableBiMap;
21import com.google.common.collect.ImmutableList;
Carmelo Cascone700648c2018-04-11 12:02:16 -070022import com.google.common.collect.Lists;
Ray Milkeyf0c47612017-09-28 11:29:38 -070023import org.onlab.packet.DeserializationException;
Carmelo Cascone770507f2017-09-14 20:58:04 +020024import org.onlab.packet.Ethernet;
25import org.onlab.util.ImmutableByteSequence;
26import org.onosproject.net.ConnectPoint;
Carmelo Cascone700648c2018-04-11 12:02:16 -070027import org.onosproject.net.DeviceId;
Carmelo Cascone770507f2017-09-14 20:58:04 +020028import org.onosproject.net.Port;
29import org.onosproject.net.PortNumber;
30import org.onosproject.net.device.DeviceService;
31import org.onosproject.net.driver.AbstractHandlerBehaviour;
32import org.onosproject.net.flow.TrafficTreatment;
33import org.onosproject.net.flow.criteria.Criterion;
34import org.onosproject.net.flow.instructions.Instruction;
Carmelo Cascone700648c2018-04-11 12:02:16 -070035import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
Carmelo Cascone770507f2017-09-14 20:58:04 +020036import org.onosproject.net.packet.DefaultInboundPacket;
37import org.onosproject.net.packet.InboundPacket;
38import org.onosproject.net.packet.OutboundPacket;
Carmelo Cascone87892e22017-11-13 16:01:29 -080039import org.onosproject.net.pi.model.PiActionId;
40import org.onosproject.net.pi.model.PiActionParamId;
41import org.onosproject.net.pi.model.PiControlMetadataId;
Carmelo Cascone87892e22017-11-13 16:01:29 -080042import org.onosproject.net.pi.model.PiMatchFieldId;
Carmelo Cascone770507f2017-09-14 20:58:04 +020043import org.onosproject.net.pi.model.PiPipelineInterpreter;
Carmelo Cascone87892e22017-11-13 16:01:29 -080044import org.onosproject.net.pi.model.PiTableId;
Carmelo Cascone770507f2017-09-14 20:58:04 +020045import org.onosproject.net.pi.runtime.PiAction;
Carmelo Cascone770507f2017-09-14 20:58:04 +020046import org.onosproject.net.pi.runtime.PiActionParam;
Carmelo Cascone87892e22017-11-13 16:01:29 -080047import org.onosproject.net.pi.runtime.PiControlMetadata;
Carmelo Cascone770507f2017-09-14 20:58:04 +020048import org.onosproject.net.pi.runtime.PiPacketOperation;
Carmelo Cascone770507f2017-09-14 20:58:04 +020049
50import java.nio.ByteBuffer;
51import java.util.Collection;
52import java.util.List;
53import java.util.Optional;
54
55import static java.lang.String.format;
Carmelo Cascone770507f2017-09-14 20:58:04 +020056import static org.onlab.util.ImmutableByteSequence.copyFrom;
Carmelo Cascone770507f2017-09-14 20:58:04 +020057import static org.onosproject.net.PortNumber.CONTROLLER;
58import static org.onosproject.net.PortNumber.FLOOD;
59import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
Carmelo Cascone87892e22017-11-13 16:01:29 -080060import static org.onosproject.net.pi.model.PiPacketOperationType.PACKET_OUT;
Carmelo Cascone770507f2017-09-14 20:58:04 +020061
62/**
Carmelo Cascone700648c2018-04-11 12:02:16 -070063 * Implementation of a pipeline interpreter for the mytunnel.p4 program.
Carmelo Cascone770507f2017-09-14 20:58:04 +020064 */
Carmelo Cascone700648c2018-04-11 12:02:16 -070065public final class PipelineInterpreterImpl
66 extends AbstractHandlerBehaviour
67 implements PiPipelineInterpreter {
Carmelo Cascone770507f2017-09-14 20:58:04 +020068
Carmelo Cascone700648c2018-04-11 12:02:16 -070069 private static final String DOT = ".";
Kevin Chuangd79bfd02017-11-14 10:34:35 -080070 private static final String HDR = "hdr";
Carmelo Cascone700648c2018-04-11 12:02:16 -070071 private static final String C_INGRESS = "c_ingress";
72 private static final String T_L2_FWD = "t_l2_fwd";
Carmelo Cascone770507f2017-09-14 20:58:04 +020073 private static final String EGRESS_PORT = "egress_port";
74 private static final String INGRESS_PORT = "ingress_port";
75 private static final String ETHERNET = "ethernet";
76 private static final String STANDARD_METADATA = "standard_metadata";
77 private static final int PORT_FIELD_BITWIDTH = 9;
78
Carmelo Cascone700648c2018-04-11 12:02:16 -070079 private static final PiMatchFieldId INGRESS_PORT_ID =
80 PiMatchFieldId.of(STANDARD_METADATA + DOT + "ingress_port");
81 private static final PiMatchFieldId ETH_DST_ID =
82 PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + "dst_addr");
83 private static final PiMatchFieldId ETH_SRC_ID =
84 PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + "src_addr");
85 private static final PiMatchFieldId ETH_TYPE_ID =
86 PiMatchFieldId.of(HDR + DOT + ETHERNET + DOT + "ether_type");
Carmelo Cascone770507f2017-09-14 20:58:04 +020087
Carmelo Cascone700648c2018-04-11 12:02:16 -070088 private static final PiTableId TABLE_L2_FWD_ID =
89 PiTableId.of(C_INGRESS + DOT + T_L2_FWD);
90
91 private static final PiActionId ACT_ID_NOP =
92 PiActionId.of("NoAction");
93 private static final PiActionId ACT_ID_SEND_TO_CPU =
94 PiActionId.of(C_INGRESS + DOT + "send_to_cpu");
95 private static final PiActionId ACT_ID_SET_EGRESS_PORT =
Carmelo Cascone9bc6a142018-04-16 17:35:15 -070096 PiActionId.of(C_INGRESS + DOT + "set_out_port");
Carmelo Cascone700648c2018-04-11 12:02:16 -070097
98 private static final PiActionParamId ACT_PARAM_ID_PORT =
99 PiActionParamId.of("port");
100
101 private static final BiMap<Integer, PiTableId> TABLE_MAP =
102 new ImmutableBiMap.Builder<Integer, PiTableId>()
103 .put(0, TABLE_L2_FWD_ID)
104 .build();
Carmelo Cascone770507f2017-09-14 20:58:04 +0200105
Carmelo Cascone87892e22017-11-13 16:01:29 -0800106 private static final BiMap<Criterion.Type, PiMatchFieldId> CRITERION_MAP =
107 new ImmutableBiMap.Builder<Criterion.Type, PiMatchFieldId>()
Carmelo Cascone770507f2017-09-14 20:58:04 +0200108 .put(Criterion.Type.IN_PORT, INGRESS_PORT_ID)
109 .put(Criterion.Type.ETH_DST, ETH_DST_ID)
110 .put(Criterion.Type.ETH_SRC, ETH_SRC_ID)
111 .put(Criterion.Type.ETH_TYPE, ETH_TYPE_ID)
112 .build();
113
114 @Override
Carmelo Cascone87892e22017-11-13 16:01:29 -0800115 public Optional<PiMatchFieldId> mapCriterionType(Criterion.Type type) {
Carmelo Cascone770507f2017-09-14 20:58:04 +0200116 return Optional.ofNullable(CRITERION_MAP.get(type));
117 }
118
119 @Override
Carmelo Cascone87892e22017-11-13 16:01:29 -0800120 public Optional<Criterion.Type> mapPiMatchFieldId(PiMatchFieldId headerFieldId) {
Carmelo Cascone770507f2017-09-14 20:58:04 +0200121 return Optional.ofNullable(CRITERION_MAP.inverse().get(headerFieldId));
122 }
123
124 @Override
125 public Optional<PiTableId> mapFlowRuleTableId(int flowRuleTableId) {
126 return Optional.ofNullable(TABLE_MAP.get(flowRuleTableId));
127 }
128
129 @Override
130 public Optional<Integer> mapPiTableId(PiTableId piTableId) {
131 return Optional.ofNullable(TABLE_MAP.inverse().get(piTableId));
132 }
Carmelo Cascone700648c2018-04-11 12:02:16 -0700133
134 @Override
135 public PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId)
136 throws PiInterpreterException {
137
138 if (piTableId != TABLE_L2_FWD_ID) {
139 throw new PiInterpreterException(
140 "Can map treatments only for 't_l2_fwd' table");
141 }
142
143 if (treatment.allInstructions().size() == 0) {
144 // 0 instructions means "NoAction"
145 return PiAction.builder().withId(ACT_ID_NOP).build();
146 } else if (treatment.allInstructions().size() > 1) {
147 // We understand treatments with only 1 instruction.
148 throw new PiInterpreterException("Treatment has multiple instructions");
149 }
150
151 // Get the first and only instruction.
152 Instruction instruction = treatment.allInstructions().get(0);
153
154 if (instruction.type() != OUTPUT) {
155 // We can map only instructions of type OUTPUT.
156 throw new PiInterpreterException(format(
157 "Instruction of type '%s' not supported", instruction.type()));
158 }
159
160 OutputInstruction outInstruction = (OutputInstruction) instruction;
161 PortNumber port = outInstruction.port();
162 if (!port.isLogical()) {
163 return PiAction.builder()
164 .withId(ACT_ID_SET_EGRESS_PORT)
165 .withParameter(new PiActionParam(
166 ACT_PARAM_ID_PORT, copyFrom(port.toLong())))
167 .build();
168 } else if (port.equals(CONTROLLER)) {
169 return PiAction.builder()
170 .withId(ACT_ID_SEND_TO_CPU)
171 .build();
172 } else {
173 throw new PiInterpreterException(format(
174 "Output on logical port '%s' not supported", port));
175 }
176 }
177
178 @Override
179 public Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet)
180 throws PiInterpreterException {
181
182 TrafficTreatment treatment = packet.treatment();
183
184 // We support only packet-out with OUTPUT instructions.
185 if (treatment.allInstructions().size() != 1 &&
186 treatment.allInstructions().get(0).type() != OUTPUT) {
187 throw new PiInterpreterException(
188 "Treatment not supported: " + treatment.toString());
189 }
190
191 Instruction instruction = treatment.allInstructions().get(0);
192 PortNumber port = ((OutputInstruction) instruction).port();
193 List<PiPacketOperation> piPacketOps = Lists.newArrayList();
194
195 if (!port.isLogical()) {
196 piPacketOps.add(createPiPacketOp(packet.data(), port.toLong()));
197 } else if (port.equals(FLOOD)) {
198 // Since mytunnel.p4 does not support flooding, we create a packet
199 // operation for each switch port.
200 DeviceService deviceService = handler().get(DeviceService.class);
201 DeviceId deviceId = packet.sendThrough();
202 for (Port p : deviceService.getPorts(deviceId)) {
203 piPacketOps.add(createPiPacketOp(packet.data(), p.number().toLong()));
204 }
205 } else {
206 throw new PiInterpreterException(format(
207 "Output on logical port '%s' not supported", port));
208 }
209
210 return piPacketOps;
211 }
212
213 @Override
214 public InboundPacket mapInboundPacket(PiPacketOperation packetIn)
215 throws PiInterpreterException {
216 // We assume that the packet is ethernet, which is fine since mytunnel.p4
217 // can deparse only ethernet packets.
218 Ethernet ethPkt;
219
220 try {
221 ethPkt = Ethernet.deserializer().deserialize(
222 packetIn.data().asArray(), 0, packetIn.data().size());
223 } catch (DeserializationException dex) {
224 throw new PiInterpreterException(dex.getMessage());
225 }
226
227 // Returns the ingress port packet metadata.
228 Optional<PiControlMetadata> packetMetadata = packetIn.metadatas().stream()
229 .filter(metadata -> metadata.id().toString().equals(INGRESS_PORT))
230 .findFirst();
231
232 if (packetMetadata.isPresent()) {
233 short s = packetMetadata.get().value().asReadOnlyBuffer().getShort();
234 ConnectPoint receivedFrom = new ConnectPoint(
235 packetIn.deviceId(), PortNumber.portNumber(s));
236 return new DefaultInboundPacket(
237 receivedFrom, ethPkt, packetIn.data().asReadOnlyBuffer());
238 } else {
239 throw new PiInterpreterException(format(
240 "Missing metadata '%s' in packet-in received from '%s': %s",
241 INGRESS_PORT, packetIn.deviceId(), packetIn));
242 }
243 }
244
245 private PiPacketOperation createPiPacketOp(ByteBuffer data, long portNumber)
246 throws PiInterpreterException {
247 PiControlMetadata metadata = createControlMetadata(portNumber);
248 return PiPacketOperation.builder()
249 .forDevice(this.data().deviceId())
250 .withType(PACKET_OUT)
251 .withData(copyFrom(data))
252 .withMetadatas(ImmutableList.of(metadata))
253 .build();
254 }
255
256 private PiControlMetadata createControlMetadata(long portNumber)
257 throws PiInterpreterException {
258 try {
259 return PiControlMetadata.builder()
260 .withId(PiControlMetadataId.of(EGRESS_PORT))
261 .withValue(copyFrom(portNumber).fit(PORT_FIELD_BITWIDTH))
262 .build();
263 } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
264 throw new PiInterpreterException(format(
265 "Port number %d too big, %s", portNumber, e.getMessage()));
266 }
267 }
Carmelo Cascone770507f2017-09-14 20:58:04 +0200268}