blob: 28369eb43829f853bb680d6c921a539747735ac4 [file] [log] [blame]
Umesh Krishnaswamy345ee992012-12-13 20:29:48 -08001/**
2* Copyright 2011, Big Switch Networks, Inc.
3* Originally created by David Erickson, Stanford University
4*
5* Licensed under the Apache License, Version 2.0 (the "License"); you may
6* not use this file except in compliance with the License. You may obtain
7* a copy of the License at
8*
9* http://www.apache.org/licenses/LICENSE-2.0
10*
11* Unless required by applicable law or agreed to in writing, software
12* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14* License for the specific language governing permissions and limitations
15* under the License.
16**/
17
18package net.floodlightcontroller.forwarding;
19
20import java.io.IOException;
21import java.util.ArrayList;
22import java.util.Arrays;
23import java.util.Collection;
24import java.util.List;
25import java.util.Map;
26
27import net.floodlightcontroller.core.FloodlightContext;
28import net.floodlightcontroller.core.IFloodlightProviderService;
29import net.floodlightcontroller.core.IOFSwitch;
30import net.floodlightcontroller.devicemanager.IDevice;
31import net.floodlightcontroller.devicemanager.IDeviceService;
32import net.floodlightcontroller.devicemanager.SwitchPort;
33import net.floodlightcontroller.core.annotations.LogMessageCategory;
34import net.floodlightcontroller.core.annotations.LogMessageDoc;
35import net.floodlightcontroller.core.annotations.LogMessageDocs;
36import net.floodlightcontroller.core.module.FloodlightModuleContext;
37import net.floodlightcontroller.core.module.FloodlightModuleException;
38import net.floodlightcontroller.core.module.IFloodlightModule;
39import net.floodlightcontroller.core.module.IFloodlightService;
40import net.floodlightcontroller.core.util.AppCookie;
41import net.floodlightcontroller.counter.ICounterStoreService;
42import net.floodlightcontroller.packet.Ethernet;
43import net.floodlightcontroller.routing.ForwardingBase;
44import net.floodlightcontroller.routing.IRoutingDecision;
45import net.floodlightcontroller.routing.IRoutingService;
46import net.floodlightcontroller.routing.Route;
47import net.floodlightcontroller.topology.ITopologyService;
48
49import org.openflow.protocol.OFFlowMod;
50import org.openflow.protocol.OFMatch;
51import org.openflow.protocol.OFPacketIn;
52import org.openflow.protocol.OFPacketOut;
53import org.openflow.protocol.OFPort;
54import org.openflow.protocol.OFType;
55import org.openflow.protocol.action.OFAction;
56import org.openflow.protocol.action.OFActionOutput;
57import org.slf4j.Logger;
58import org.slf4j.LoggerFactory;
59
60@LogMessageCategory("Flow Programming")
61public class Forwarding extends ForwardingBase implements IFloodlightModule {
Yuta HIGUCHI6ac8d182013-10-22 15:24:56 -070062 protected final static Logger log = LoggerFactory.getLogger(Forwarding.class);
Umesh Krishnaswamy345ee992012-12-13 20:29:48 -080063
64 @Override
65 @LogMessageDoc(level="ERROR",
66 message="Unexpected decision made for this packet-in={}",
67 explanation="An unsupported PacketIn decision has been " +
68 "passed to the flow programming component",
69 recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
70 public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision,
71 FloodlightContext cntx) {
72 Ethernet eth = IFloodlightProviderService.bcStore.get(cntx,
73 IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
74
75 // If a decision has been made we obey it
76 // otherwise we just forward
77 if (decision != null) {
78 if (log.isTraceEnabled()) {
79 log.trace("Forwaring decision={} was made for PacketIn={}",
80 decision.getRoutingAction().toString(),
81 pi);
82 }
83
84 switch(decision.getRoutingAction()) {
85 case NONE:
86 // don't do anything
87 return Command.CONTINUE;
88 case FORWARD_OR_FLOOD:
89 case FORWARD:
90 doForwardFlow(sw, pi, cntx, false);
91 return Command.CONTINUE;
92 case MULTICAST:
93 // treat as broadcast
94 doFlood(sw, pi, cntx);
95 return Command.CONTINUE;
96 case DROP:
97 doDropFlow(sw, pi, decision, cntx);
98 return Command.CONTINUE;
99 default:
100 log.error("Unexpected decision made for this packet-in={}",
101 pi, decision.getRoutingAction());
102 return Command.CONTINUE;
103 }
104 } else {
105 if (log.isTraceEnabled()) {
106 log.trace("No decision was made for PacketIn={}, forwarding",
107 pi);
108 }
109
110 if (eth.isBroadcast() || eth.isMulticast()) {
111 // For now we treat multicast as broadcast
112 doFlood(sw, pi, cntx);
113 } else {
114 doForwardFlow(sw, pi, cntx, false);
115 }
116 }
117
118 return Command.CONTINUE;
119 }
120
121 @LogMessageDoc(level="ERROR",
122 message="Failure writing drop flow mod",
123 explanation="An I/O error occured while trying to write a " +
124 "drop flow mod to a switch",
125 recommendation=LogMessageDoc.CHECK_SWITCH)
126 protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) {
127 // initialize match structure and populate it using the packet
128 OFMatch match = new OFMatch();
129 match.loadFromPacket(pi.getPacketData(), pi.getInPort());
130 if (decision.getWildcards() != null) {
131 match.setWildcards(decision.getWildcards());
132 }
133
134 // Create flow-mod based on packet-in and src-switch
135 OFFlowMod fm =
136 (OFFlowMod) floodlightProvider.getOFMessageFactory()
137 .getMessage(OFType.FLOW_MOD);
138 List<OFAction> actions = new ArrayList<OFAction>(); // Set no action to
139 // drop
140 long cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0);
141
142 fm.setCookie(cookie)
143 .setHardTimeout((short) 0)
144 .setIdleTimeout((short) 5)
145 .setBufferId(OFPacketOut.BUFFER_ID_NONE)
146 .setMatch(match)
147 .setActions(actions)
148 .setLengthU(OFFlowMod.MINIMUM_LENGTH); // +OFActionOutput.MINIMUM_LENGTH);
149
150 try {
151 if (log.isDebugEnabled()) {
152 log.debug("write drop flow-mod sw={} match={} flow-mod={}",
153 new Object[] { sw, match, fm });
154 }
155 messageDamper.write(sw, fm, cntx);
156 } catch (IOException e) {
157 log.error("Failure writing drop flow mod", e);
158 }
159 }
160
161 protected void doForwardFlow(IOFSwitch sw, OFPacketIn pi,
162 FloodlightContext cntx,
163 boolean requestFlowRemovedNotifn) {
164 OFMatch match = new OFMatch();
165 match.loadFromPacket(pi.getPacketData(), pi.getInPort());
166
167 // Check if we have the location of the destination
168 IDevice dstDevice =
169 IDeviceService.fcStore.
170 get(cntx, IDeviceService.CONTEXT_DST_DEVICE);
171
172 if (dstDevice != null) {
173 IDevice srcDevice =
174 IDeviceService.fcStore.
175 get(cntx, IDeviceService.CONTEXT_SRC_DEVICE);
176 Long srcIsland = topology.getL2DomainId(sw.getId());
177
178 if (srcDevice == null) {
179 log.debug("No device entry found for source device");
180 return;
181 }
182 if (srcIsland == null) {
183 log.debug("No openflow island found for source {}/{}",
184 sw.getStringId(), pi.getInPort());
185 return;
186 }
187
188 // Validate that we have a destination known on the same island
189 // Validate that the source and destination are not on the same switchport
190 boolean on_same_island = false;
191 boolean on_same_if = false;
192 for (SwitchPort dstDap : dstDevice.getAttachmentPoints()) {
193 long dstSwDpid = dstDap.getSwitchDPID();
194 Long dstIsland = topology.getL2DomainId(dstSwDpid);
195 if ((dstIsland != null) && dstIsland.equals(srcIsland)) {
196 on_same_island = true;
197 if ((sw.getId() == dstSwDpid) &&
198 (pi.getInPort() == dstDap.getPort())) {
199 on_same_if = true;
200 }
201 break;
202 }
203 }
204
205 if (!on_same_island) {
206 // Flood since we don't know the dst device
207 if (log.isTraceEnabled()) {
208 log.trace("No first hop island found for destination " +
209 "device {}, Action = flooding", dstDevice);
210 }
211 doFlood(sw, pi, cntx);
212 return;
213 }
214
215 if (on_same_if) {
216 if (log.isTraceEnabled()) {
217 log.trace("Both source and destination are on the same " +
218 "switch/port {}/{}, Action = NOP",
219 sw.toString(), pi.getInPort());
220 }
221 return;
222 }
223
224 // Install all the routes where both src and dst have attachment
225 // points. Since the lists are stored in sorted order we can
226 // traverse the attachment points in O(m+n) time
227 SwitchPort[] srcDaps = srcDevice.getAttachmentPoints();
228 Arrays.sort(srcDaps, clusterIdComparator);
229 SwitchPort[] dstDaps = dstDevice.getAttachmentPoints();
230 Arrays.sort(dstDaps, clusterIdComparator);
231
232 int iSrcDaps = 0, iDstDaps = 0;
233
234 while ((iSrcDaps < srcDaps.length) && (iDstDaps < dstDaps.length)) {
235 SwitchPort srcDap = srcDaps[iSrcDaps];
236 SwitchPort dstDap = dstDaps[iDstDaps];
237 Long srcCluster =
238 topology.getL2DomainId(srcDap.getSwitchDPID());
239 Long dstCluster =
240 topology.getL2DomainId(dstDap.getSwitchDPID());
241
242 int srcVsDest = srcCluster.compareTo(dstCluster);
243 if (srcVsDest == 0) {
244 if (!srcDap.equals(dstDap) &&
245 (srcCluster != null) &&
246 (dstCluster != null)) {
247 Route route =
248 routingEngine.getRoute(srcDap.getSwitchDPID(),
249 (short)srcDap.getPort(),
250 dstDap.getSwitchDPID(),
251 (short)dstDap.getPort());
252 if (route != null) {
253 if (log.isTraceEnabled()) {
254 log.trace("pushRoute match={} route={} " +
255 "destination={}:{}",
256 new Object[] {match, route,
257 dstDap.getSwitchDPID(),
258 dstDap.getPort()});
259 }
260 long cookie =
261 AppCookie.makeCookie(FORWARDING_APP_ID, 0);
262
263 // if there is prior routing decision use wildcard
264 Integer wildcard_hints = null;
265 IRoutingDecision decision = null;
266 if (cntx != null) {
267 decision = IRoutingDecision.rtStore
268 .get(cntx,
269 IRoutingDecision.CONTEXT_DECISION);
270 }
271 if (decision != null) {
272 wildcard_hints = decision.getWildcards();
273 } else {
274 // L2 only wildcard if there is no prior route decision
275 wildcard_hints = ((Integer) sw
276 .getAttribute(IOFSwitch.PROP_FASTWILDCARDS))
277 .intValue()
278 & ~OFMatch.OFPFW_IN_PORT
279 & ~OFMatch.OFPFW_DL_VLAN
280 & ~OFMatch.OFPFW_DL_SRC
281 & ~OFMatch.OFPFW_DL_DST
282 & ~OFMatch.OFPFW_NW_SRC_MASK
283 & ~OFMatch.OFPFW_NW_DST_MASK;
284 }
285
286 pushRoute(route, match, wildcard_hints, pi, sw.getId(), cookie,
287 cntx, requestFlowRemovedNotifn, false,
288 OFFlowMod.OFPFC_ADD);
289 }
290 }
291 iSrcDaps++;
292 iDstDaps++;
293 } else if (srcVsDest < 0) {
294 iSrcDaps++;
295 } else {
296 iDstDaps++;
297 }
298 }
299 } else {
300 // Flood since we don't know the dst device
301 doFlood(sw, pi, cntx);
302 }
303 }
304
305 /**
306 * Creates a OFPacketOut with the OFPacketIn data that is flooded on all ports unless
307 * the port is blocked, in which case the packet will be dropped.
308 * @param sw The switch that receives the OFPacketIn
309 * @param pi The OFPacketIn that came to the switch
310 * @param cntx The FloodlightContext associated with this OFPacketIn
311 */
312 @LogMessageDoc(level="ERROR",
313 message="Failure writing PacketOut " +
314 "switch={switch} packet-in={packet-in} " +
315 "packet-out={packet-out}",
316 explanation="An I/O error occured while writing a packet " +
317 "out message to the switch",
318 recommendation=LogMessageDoc.CHECK_SWITCH)
319 protected void doFlood(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) {
320 if (topology.isIncomingBroadcastAllowed(sw.getId(),
321 pi.getInPort()) == false) {
322 if (log.isTraceEnabled()) {
323 log.trace("doFlood, drop broadcast packet, pi={}, " +
324 "from a blocked port, srcSwitch=[{},{}], linkInfo={}",
325 new Object[] {pi, sw.getId(),pi.getInPort()});
326 }
327 return;
328 }
329
330 // Set Action to flood
331 OFPacketOut po =
332 (OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT);
333 List<OFAction> actions = new ArrayList<OFAction>();
334 if (sw.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_FLOOD)) {
335 actions.add(new OFActionOutput(OFPort.OFPP_FLOOD.getValue(),
336 (short)0xFFFF));
337 } else {
338 actions.add(new OFActionOutput(OFPort.OFPP_ALL.getValue(),
339 (short)0xFFFF));
340 }
341 po.setActions(actions);
342 po.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
343
344 // set buffer-id, in-port and packet-data based on packet-in
345 short poLength = (short)(po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH);
346 po.setBufferId(pi.getBufferId());
347 po.setInPort(pi.getInPort());
348 if (pi.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
349 byte[] packetData = pi.getPacketData();
350 poLength += packetData.length;
351 po.setPacketData(packetData);
352 }
353 po.setLength(poLength);
354
355 try {
356 if (log.isTraceEnabled()) {
357 log.trace("Writing flood PacketOut switch={} packet-in={} packet-out={}",
358 new Object[] {sw, pi, po});
359 }
360 messageDamper.write(sw, po, cntx);
361 } catch (IOException e) {
362 log.error("Failure writing PacketOut switch={} packet-in={} packet-out={}",
363 new Object[] {sw, pi, po}, e);
364 }
365
366 return;
367 }
368
369 // IFloodlightModule methods
370
371 @Override
372 public Collection<Class<? extends IFloodlightService>> getModuleServices() {
373 // We don't export any services
374 return null;
375 }
376
377 @Override
378 public Map<Class<? extends IFloodlightService>, IFloodlightService>
379 getServiceImpls() {
380 // We don't have any services
381 return null;
382 }
383
384 @Override
385 public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
386 Collection<Class<? extends IFloodlightService>> l =
387 new ArrayList<Class<? extends IFloodlightService>>();
388 l.add(IFloodlightProviderService.class);
389 l.add(IDeviceService.class);
390 l.add(IRoutingService.class);
391 l.add(ITopologyService.class);
392 l.add(ICounterStoreService.class);
393 return l;
394 }
395
396 @Override
397 @LogMessageDocs({
398 @LogMessageDoc(level="WARN",
399 message="Error parsing flow idle timeout, " +
400 "using default of {number} seconds",
401 explanation="The properties file contains an invalid " +
402 "flow idle timeout",
403 recommendation="Correct the idle timeout in the " +
404 "properties file."),
405 @LogMessageDoc(level="WARN",
406 message="Error parsing flow hard timeout, " +
407 "using default of {number} seconds",
408 explanation="The properties file contains an invalid " +
409 "flow hard timeout",
410 recommendation="Correct the hard timeout in the " +
411 "properties file.")
412 })
413 public void init(FloodlightModuleContext context) throws FloodlightModuleException {
414 super.init();
415 this.floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
416 this.deviceManager = context.getServiceImpl(IDeviceService.class);
417 this.routingEngine = context.getServiceImpl(IRoutingService.class);
418 this.topology = context.getServiceImpl(ITopologyService.class);
419 this.counterStore = context.getServiceImpl(ICounterStoreService.class);
420
421 // read our config options
422 Map<String, String> configOptions = context.getConfigParams(this);
423 try {
424 String idleTimeout = configOptions.get("idletimeout");
425 if (idleTimeout != null) {
426 FLOWMOD_DEFAULT_IDLE_TIMEOUT = Short.parseShort(idleTimeout);
427 }
428 } catch (NumberFormatException e) {
429 log.warn("Error parsing flow idle timeout, " +
430 "using default of {} seconds",
431 FLOWMOD_DEFAULT_IDLE_TIMEOUT);
432 }
433 try {
434 String hardTimeout = configOptions.get("hardtimeout");
435 if (hardTimeout != null) {
436 FLOWMOD_DEFAULT_HARD_TIMEOUT = Short.parseShort(hardTimeout);
437 }
438 } catch (NumberFormatException e) {
439 log.warn("Error parsing flow hard timeout, " +
440 "using default of {} seconds",
441 FLOWMOD_DEFAULT_HARD_TIMEOUT);
442 }
443 log.debug("FlowMod idle timeout set to {} seconds",
444 FLOWMOD_DEFAULT_IDLE_TIMEOUT);
445 log.debug("FlowMod hard timeout set to {} seconds",
446 FLOWMOD_DEFAULT_HARD_TIMEOUT);
447 }
448
449 @Override
450 public void startUp(FloodlightModuleContext context) {
451 super.startUp();
452 }
453}