blob: 3115584cca4c932e1e3d5886f6d8fc6e0c97b978 [file] [log] [blame]
Yi Tsengfa4a1c72017-11-03 10:22:38 -07001/*
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.pipelines.fabric;
18
19import com.google.common.collect.ImmutableBiMap;
20import com.google.common.collect.ImmutableList;
Yi Tseng1d842672017-11-28 16:06:52 -080021import com.google.common.collect.ImmutableMap;
Yi Tsengfa4a1c72017-11-03 10:22:38 -070022import com.google.common.collect.ImmutableSet;
23import org.onlab.packet.DeserializationException;
24import org.onlab.packet.Ethernet;
25import org.onlab.util.ImmutableByteSequence;
26import org.onosproject.net.ConnectPoint;
27import org.onosproject.net.DeviceId;
28import 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.Instructions;
35import org.onosproject.net.packet.DefaultInboundPacket;
36import org.onosproject.net.packet.InboundPacket;
37import org.onosproject.net.packet.OutboundPacket;
Yi Tsengfa4a1c72017-11-03 10:22:38 -070038import org.onosproject.net.pi.model.PiMatchFieldId;
Carmelo Cascone6880ba62018-09-06 00:04:34 -070039import org.onosproject.net.pi.model.PiPipeconf;
40import org.onosproject.net.pi.model.PiPipeconfId;
Yi Tsengfa4a1c72017-11-03 10:22:38 -070041import org.onosproject.net.pi.model.PiPipelineInterpreter;
42import org.onosproject.net.pi.model.PiTableId;
43import org.onosproject.net.pi.runtime.PiAction;
44import org.onosproject.net.pi.runtime.PiControlMetadata;
45import org.onosproject.net.pi.runtime.PiPacketOperation;
Carmelo Cascone6880ba62018-09-06 00:04:34 -070046import org.onosproject.net.pi.service.PiPipeconfService;
47import org.slf4j.Logger;
Yi Tsengfa4a1c72017-11-03 10:22:38 -070048
Carmelo Cascone6880ba62018-09-06 00:04:34 -070049import java.io.BufferedReader;
50import java.io.IOException;
51import java.io.InputStream;
52import java.io.InputStreamReader;
Yi Tsengfa4a1c72017-11-03 10:22:38 -070053import java.nio.ByteBuffer;
54import java.util.Collection;
55import java.util.List;
56import java.util.Optional;
57import java.util.Set;
58
59import static java.lang.String.format;
60import static java.util.stream.Collectors.toList;
61import static org.onlab.util.ImmutableByteSequence.copyFrom;
Carmelo Cascone6880ba62018-09-06 00:04:34 -070062import static org.onosproject.net.PortNumber.CONTROLLER;
Yi Tsengfa4a1c72017-11-03 10:22:38 -070063import static org.onosproject.net.PortNumber.FLOOD;
64import static org.onosproject.net.flow.instructions.Instruction.Type.OUTPUT;
65import static org.onosproject.net.pi.model.PiPacketOperationType.PACKET_OUT;
Carmelo Cascone6880ba62018-09-06 00:04:34 -070066import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.CPU_PORT_TXT;
67import static org.slf4j.LoggerFactory.getLogger;
Yi Tsengfa4a1c72017-11-03 10:22:38 -070068
69/**
70 * Interpreter for fabric pipeline.
71 */
72public class FabricInterpreter extends AbstractHandlerBehaviour
73 implements PiPipelineInterpreter {
Carmelo Cascone1e8843f2018-07-19 19:01:12 +020074
Carmelo Cascone6880ba62018-09-06 00:04:34 -070075 private final Logger log = getLogger(getClass());
76
Carmelo Cascone1e8843f2018-07-19 19:01:12 +020077 public static final int PORT_BITWIDTH = 9;
78
Yi Tsengfa4a1c72017-11-03 10:22:38 -070079 private static final ImmutableBiMap<Integer, PiTableId> TABLE_ID_MAP =
80 ImmutableBiMap.<Integer, PiTableId>builder()
81 // Filtering
Yi Tseng43ee7e82018-04-12 16:37:34 +080082 .put(0, FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN)
83 .put(1, FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER)
Yi Tsengfa4a1c72017-11-03 10:22:38 -070084 // Forwarding
Yi Tseng43ee7e82018-04-12 16:37:34 +080085 .put(2, FabricConstants.FABRIC_INGRESS_FORWARDING_MPLS)
Charles Chan384aea22018-08-23 22:08:02 -070086 .put(3, FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4)
87 .put(4, FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6)
88 .put(5, FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING)
89 .put(6, FabricConstants.FABRIC_INGRESS_FORWARDING_ACL)
Yi Tsengfa4a1c72017-11-03 10:22:38 -070090 // Next
Charles Chan384aea22018-08-23 22:08:02 -070091 .put(7, FabricConstants.FABRIC_INGRESS_NEXT_VLAN_META)
92 .put(8, FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE)
93 .put(9, FabricConstants.FABRIC_INGRESS_NEXT_HASHED)
94 .put(10, FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST)
95 .put(11, FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN)
Yi Tsengfa4a1c72017-11-03 10:22:38 -070096 .build();
97
Yi Tseng43ee7e82018-04-12 16:37:34 +080098 private static final Set<PiTableId> FILTERING_CTRL_TBLS =
99 ImmutableSet.of(FabricConstants.FABRIC_INGRESS_FILTERING_INGRESS_PORT_VLAN,
100 FabricConstants.FABRIC_INGRESS_FILTERING_FWD_CLASSIFIER);
101 private static final Set<PiTableId> FORWARDING_CTRL_TBLS =
102 ImmutableSet.of(FabricConstants.FABRIC_INGRESS_FORWARDING_MPLS,
Charles Chan384aea22018-08-23 22:08:02 -0700103 FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V4,
104 FabricConstants.FABRIC_INGRESS_FORWARDING_ROUTING_V6,
Yi Tseng43ee7e82018-04-12 16:37:34 +0800105 FabricConstants.FABRIC_INGRESS_FORWARDING_BRIDGING,
106 FabricConstants.FABRIC_INGRESS_FORWARDING_ACL);
107 private static final Set<PiTableId> NEXT_CTRL_TBLS =
Yi Tsengbf0d4372018-06-21 23:55:58 +0800108 ImmutableSet.of(FabricConstants.FABRIC_INGRESS_NEXT_VLAN_META,
109 FabricConstants.FABRIC_INGRESS_NEXT_SIMPLE,
Yi Tseng43ee7e82018-04-12 16:37:34 +0800110 FabricConstants.FABRIC_INGRESS_NEXT_HASHED,
111 FabricConstants.FABRIC_INGRESS_NEXT_MULTICAST);
112 private static final Set<PiTableId> E_NEXT_CTRL_TBLS =
113 ImmutableSet.of(FabricConstants.FABRIC_EGRESS_EGRESS_NEXT_EGRESS_VLAN);
Yi Tsengfa4a1c72017-11-03 10:22:38 -0700114
Yi Tseng1d842672017-11-28 16:06:52 -0800115 private static final ImmutableMap<Criterion.Type, PiMatchFieldId> CRITERION_MAP =
116 ImmutableMap.<Criterion.Type, PiMatchFieldId>builder()
Yi Tseng43ee7e82018-04-12 16:37:34 +0800117 .put(Criterion.Type.IN_PORT, FabricConstants.STANDARD_METADATA_INGRESS_PORT)
Charles Chan384aea22018-08-23 22:08:02 -0700118 .put(Criterion.Type.ETH_DST_MASKED, FabricConstants.HDR_ETHERNET_DST_ADDR)
119 .put(Criterion.Type.ETH_SRC_MASKED, FabricConstants.HDR_ETHERNET_SRC_ADDR)
Yi Tseng8235a1a2018-07-24 20:57:28 +0800120 .put(Criterion.Type.ETH_TYPE, FabricConstants.HDR_VLAN_TAG_ETHER_TYPE)
Yi Tseng43ee7e82018-04-12 16:37:34 +0800121 .put(Criterion.Type.MPLS_LABEL, FabricConstants.HDR_MPLS_LABEL)
122 .put(Criterion.Type.VLAN_VID, FabricConstants.HDR_VLAN_TAG_VLAN_ID)
123 .put(Criterion.Type.IPV4_DST, FabricConstants.HDR_IPV4_DST_ADDR)
124 .put(Criterion.Type.IPV4_SRC, FabricConstants.HDR_IPV4_SRC_ADDR)
125 .put(Criterion.Type.IPV6_DST, FabricConstants.HDR_IPV6_DST_ADDR)
Yi Tseng43ee7e82018-04-12 16:37:34 +0800126 .put(Criterion.Type.IP_PROTO, FabricConstants.FABRIC_METADATA_IP_PROTO)
127 .put(Criterion.Type.ICMPV6_TYPE, FabricConstants.HDR_ICMP_ICMP_TYPE)
128 .put(Criterion.Type.ICMPV6_CODE, FabricConstants.HDR_ICMP_ICMP_CODE)
Yi Tsengfa4a1c72017-11-03 10:22:38 -0700129 .build();
130
Yi Tseng1d842672017-11-28 16:06:52 -0800131 private static final ImmutableMap<PiMatchFieldId, Criterion.Type> INVERSE_CRITERION_MAP =
132 ImmutableMap.<PiMatchFieldId, Criterion.Type>builder()
Yi Tseng43ee7e82018-04-12 16:37:34 +0800133 .put(FabricConstants.STANDARD_METADATA_INGRESS_PORT, Criterion.Type.IN_PORT)
Charles Chan384aea22018-08-23 22:08:02 -0700134 .put(FabricConstants.HDR_ETHERNET_DST_ADDR, Criterion.Type.ETH_DST_MASKED)
135 .put(FabricConstants.HDR_ETHERNET_SRC_ADDR, Criterion.Type.ETH_SRC_MASKED)
Yi Tseng8235a1a2018-07-24 20:57:28 +0800136 .put(FabricConstants.HDR_VLAN_TAG_ETHER_TYPE, Criterion.Type.ETH_TYPE)
Yi Tseng43ee7e82018-04-12 16:37:34 +0800137 .put(FabricConstants.HDR_MPLS_LABEL, Criterion.Type.MPLS_LABEL)
138 .put(FabricConstants.HDR_VLAN_TAG_VLAN_ID, Criterion.Type.VLAN_VID)
139 .put(FabricConstants.HDR_IPV4_DST_ADDR, Criterion.Type.IPV4_DST)
140 .put(FabricConstants.HDR_IPV4_SRC_ADDR, Criterion.Type.IPV4_SRC)
141 .put(FabricConstants.HDR_IPV6_DST_ADDR, Criterion.Type.IPV6_DST)
Yi Tseng1d842672017-11-28 16:06:52 -0800142 // FIXME: might be incorrect if we inverse the map....
Yi Tseng43ee7e82018-04-12 16:37:34 +0800143 .put(FabricConstants.FABRIC_METADATA_L4_SRC_PORT, Criterion.Type.UDP_SRC)
144 .put(FabricConstants.FABRIC_METADATA_L4_DST_PORT, Criterion.Type.UDP_DST)
145 .put(FabricConstants.FABRIC_METADATA_IP_PROTO, Criterion.Type.IP_PROTO)
146 .put(FabricConstants.HDR_ICMP_ICMP_TYPE, Criterion.Type.ICMPV6_TYPE)
147 .put(FabricConstants.HDR_ICMP_ICMP_CODE, Criterion.Type.ICMPV6_CODE)
Yi Tseng1d842672017-11-28 16:06:52 -0800148 .build();
149
Yi Tsengfa4a1c72017-11-03 10:22:38 -0700150 @Override
151 public Optional<PiMatchFieldId> mapCriterionType(Criterion.Type type) {
152 return Optional.ofNullable(CRITERION_MAP.get(type));
153 }
154
155 @Override
156 public Optional<Criterion.Type> mapPiMatchFieldId(PiMatchFieldId fieldId) {
Yi Tseng1d842672017-11-28 16:06:52 -0800157 return Optional.ofNullable(INVERSE_CRITERION_MAP.get(fieldId));
Yi Tsengfa4a1c72017-11-03 10:22:38 -0700158 }
159
160 @Override
161 public Optional<PiTableId> mapFlowRuleTableId(int flowRuleTableId) {
162 return Optional.ofNullable(TABLE_ID_MAP.get(flowRuleTableId));
163 }
164
165 @Override
166 public Optional<Integer> mapPiTableId(PiTableId piTableId) {
167 return Optional.ofNullable(TABLE_ID_MAP.inverse().get(piTableId));
168 }
169
170 @Override
171 public PiAction mapTreatment(TrafficTreatment treatment, PiTableId piTableId)
172 throws PiInterpreterException {
173
174 if (FILTERING_CTRL_TBLS.contains(piTableId)) {
Yi Tseng47eac892018-07-11 02:17:04 +0800175 return FabricTreatmentInterpreter.mapFilteringTreatment(treatment, piTableId);
Yi Tsengfa4a1c72017-11-03 10:22:38 -0700176 } else if (FORWARDING_CTRL_TBLS.contains(piTableId)) {
Yi Tseng47eac892018-07-11 02:17:04 +0800177 return FabricTreatmentInterpreter.mapForwardingTreatment(treatment, piTableId);
Yi Tsengfa4a1c72017-11-03 10:22:38 -0700178 } else if (NEXT_CTRL_TBLS.contains(piTableId)) {
Yi Tseng47eac892018-07-11 02:17:04 +0800179 return FabricTreatmentInterpreter.mapNextTreatment(treatment, piTableId);
Yi Tseng20f9e7b2018-05-24 23:27:39 +0800180 } else if (E_NEXT_CTRL_TBLS.contains(piTableId)) {
Yi Tseng47eac892018-07-11 02:17:04 +0800181 return FabricTreatmentInterpreter.mapEgressNextTreatment(treatment, piTableId);
Yi Tsengfa4a1c72017-11-03 10:22:38 -0700182 } else {
183 throw new PiInterpreterException(String.format("Table %s unsupported", piTableId));
184 }
185 }
186
Yi Tsengfa4a1c72017-11-03 10:22:38 -0700187 private PiPacketOperation createPiPacketOperation(DeviceId deviceId, ByteBuffer data, long portNumber)
188 throws PiInterpreterException {
189 PiControlMetadata metadata = createPacketMetadata(portNumber);
190 return PiPacketOperation.builder()
191 .forDevice(deviceId)
192 .withType(PACKET_OUT)
193 .withData(copyFrom(data))
194 .withMetadatas(ImmutableList.of(metadata))
195 .build();
196 }
197
198 private PiControlMetadata createPacketMetadata(long portNumber) throws PiInterpreterException {
199 try {
200 return PiControlMetadata.builder()
Yi Tseng43ee7e82018-04-12 16:37:34 +0800201 .withId(FabricConstants.EGRESS_PORT)
Carmelo Cascone1e8843f2018-07-19 19:01:12 +0200202 .withValue(copyFrom(portNumber).fit(PORT_BITWIDTH))
Yi Tsengfa4a1c72017-11-03 10:22:38 -0700203 .build();
204 } catch (ImmutableByteSequence.ByteSequenceTrimException e) {
205 throw new PiInterpreterException(format(
206 "Port number %d too big, %s", portNumber, e.getMessage()));
207 }
208 }
209
210 @Override
211 public Collection<PiPacketOperation> mapOutboundPacket(OutboundPacket packet)
212 throws PiInterpreterException {
213 DeviceId deviceId = packet.sendThrough();
214 TrafficTreatment treatment = packet.treatment();
215
216 // fabric.p4 supports only OUTPUT instructions.
217 List<Instructions.OutputInstruction> outInstructions = treatment
218 .allInstructions()
219 .stream()
220 .filter(i -> i.type().equals(OUTPUT))
221 .map(i -> (Instructions.OutputInstruction) i)
222 .collect(toList());
223
224 if (treatment.allInstructions().size() != outInstructions.size()) {
225 // There are other instructions that are not of type OUTPUT.
226 throw new PiInterpreterException("Treatment not supported: " + treatment);
227 }
228
229 ImmutableList.Builder<PiPacketOperation> builder = ImmutableList.builder();
230 for (Instructions.OutputInstruction outInst : outInstructions) {
231 if (outInst.port().isLogical() && !outInst.port().equals(FLOOD)) {
232 throw new PiInterpreterException(format(
233 "Output on logical port '%s' not supported", outInst.port()));
234 } else if (outInst.port().equals(FLOOD)) {
235 // Since fabric.p4 does not support flooding, we create a packet
236 // operation for each switch port.
237 final DeviceService deviceService = handler().get(DeviceService.class);
238 for (Port port : deviceService.getPorts(packet.sendThrough())) {
239 builder.add(createPiPacketOperation(deviceId, packet.data(), port.number().toLong()));
240 }
241 } else {
242 builder.add(createPiPacketOperation(deviceId, packet.data(), outInst.port().toLong()));
243 }
244 }
245 return builder.build();
246 }
247
248 @Override
249 public InboundPacket mapInboundPacket(PiPacketOperation packetIn) throws PiInterpreterException {
250 // Assuming that the packet is ethernet, which is fine since fabric.p4
251 // can deparse only ethernet packets.
252 DeviceId deviceId = packetIn.deviceId();
253 Ethernet ethPkt;
254 try {
255 ethPkt = Ethernet.deserializer().deserialize(packetIn.data().asArray(), 0,
256 packetIn.data().size());
257 } catch (DeserializationException dex) {
258 throw new PiInterpreterException(dex.getMessage());
259 }
260
261 // Returns the ingress port packet metadata.
262 Optional<PiControlMetadata> packetMetadata = packetIn.metadatas()
Yi Tseng43ee7e82018-04-12 16:37:34 +0800263 .stream().filter(m -> m.id().equals(FabricConstants.INGRESS_PORT))
Yi Tsengfa4a1c72017-11-03 10:22:38 -0700264 .findFirst();
265
266 if (packetMetadata.isPresent()) {
267 ImmutableByteSequence portByteSequence = packetMetadata.get().value();
268 short s = portByteSequence.asReadOnlyBuffer().getShort();
269 ConnectPoint receivedFrom = new ConnectPoint(deviceId, PortNumber.portNumber(s));
270 ByteBuffer rawData = ByteBuffer.wrap(packetIn.data().asArray());
271 return new DefaultInboundPacket(receivedFrom, ethPkt, rawData);
272 } else {
273 throw new PiInterpreterException(format(
274 "Missing metadata '%s' in packet-in received from '%s': %s",
Yi Tseng43ee7e82018-04-12 16:37:34 +0800275 FabricConstants.INGRESS_PORT, deviceId, packetIn));
Yi Tsengfa4a1c72017-11-03 10:22:38 -0700276 }
277 }
Carmelo Cascone6880ba62018-09-06 00:04:34 -0700278
279 @Override
280 public Optional<Integer> mapLogicalPortNumber(PortNumber port) {
281 if (!port.equals(CONTROLLER)) {
282 return Optional.empty();
283 }
284 // This is probably brittle, but needed to dynamically get the CPU port
285 // for different platforms.
286 final DeviceId deviceId = data().deviceId();
287 final PiPipeconfService pipeconfService = handler().get(
288 PiPipeconfService.class);
289 final PiPipeconfId pipeconfId = pipeconfService
290 .ofDevice(deviceId).orElse(null);
291 if (pipeconfId == null ||
292 !pipeconfService.getPipeconf(pipeconfId).isPresent()) {
293 log.error("Unable to get pipeconf of {} - BUG?");
294 return Optional.empty();
295 }
296 final PiPipeconf pipeconf = pipeconfService.getPipeconf(pipeconfId).get();
297 if (!pipeconf.extension(CPU_PORT_TXT).isPresent()) {
298 log.error("Missing {} extension from pipeconf {}",
299 CPU_PORT_TXT, pipeconfId);
300 return Optional.empty();
301 }
302 return Optional.ofNullable(
303 readCpuPort(pipeconf.extension(CPU_PORT_TXT).get(),
304 pipeconfId));
305 }
306
307 private Integer readCpuPort(InputStream stream, PiPipeconfId pipeconfId) {
308 try {
309 final BufferedReader buff = new BufferedReader(
310 new InputStreamReader(stream));
311 final String str = buff.readLine();
312 buff.close();
313 if (str == null) {
314 log.error("Empty CPU port file for {}", pipeconfId);
315 return null;
316 }
317 try {
318 return Integer.parseInt(str);
319 } catch (NumberFormatException e) {
320 log.error("Invalid CPU port for {}: {}", pipeconfId, str);
321 return null;
322 }
323 } catch (IOException e) {
324 log.error("Unable to read CPU port file of {}: {}",
325 pipeconfId, e.getMessage());
326 return null;
327 }
328 }
Yi Tsengfa4a1c72017-11-03 10:22:38 -0700329}