Umesh Krishnaswamy | 345ee99 | 2012-12-13 20:29:48 -0800 | [diff] [blame] | 1 | /** |
| 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 | |
| 18 | package net.floodlightcontroller.forwarding; |
| 19 | |
| 20 | import java.io.IOException; |
| 21 | import java.util.ArrayList; |
| 22 | import java.util.Arrays; |
| 23 | import java.util.Collection; |
| 24 | import java.util.List; |
| 25 | import java.util.Map; |
| 26 | |
| 27 | import net.floodlightcontroller.core.FloodlightContext; |
| 28 | import net.floodlightcontroller.core.IFloodlightProviderService; |
| 29 | import net.floodlightcontroller.core.IOFSwitch; |
| 30 | import net.floodlightcontroller.devicemanager.IDevice; |
| 31 | import net.floodlightcontroller.devicemanager.IDeviceService; |
| 32 | import net.floodlightcontroller.devicemanager.SwitchPort; |
| 33 | import net.floodlightcontroller.core.annotations.LogMessageCategory; |
| 34 | import net.floodlightcontroller.core.annotations.LogMessageDoc; |
| 35 | import net.floodlightcontroller.core.annotations.LogMessageDocs; |
| 36 | import net.floodlightcontroller.core.module.FloodlightModuleContext; |
| 37 | import net.floodlightcontroller.core.module.FloodlightModuleException; |
| 38 | import net.floodlightcontroller.core.module.IFloodlightModule; |
| 39 | import net.floodlightcontroller.core.module.IFloodlightService; |
| 40 | import net.floodlightcontroller.core.util.AppCookie; |
| 41 | import net.floodlightcontroller.counter.ICounterStoreService; |
| 42 | import net.floodlightcontroller.packet.Ethernet; |
| 43 | import net.floodlightcontroller.routing.ForwardingBase; |
| 44 | import net.floodlightcontroller.routing.IRoutingDecision; |
| 45 | import net.floodlightcontroller.routing.IRoutingService; |
| 46 | import net.floodlightcontroller.routing.Route; |
| 47 | import net.floodlightcontroller.topology.ITopologyService; |
| 48 | |
| 49 | import org.openflow.protocol.OFFlowMod; |
| 50 | import org.openflow.protocol.OFMatch; |
| 51 | import org.openflow.protocol.OFPacketIn; |
| 52 | import org.openflow.protocol.OFPacketOut; |
| 53 | import org.openflow.protocol.OFPort; |
| 54 | import org.openflow.protocol.OFType; |
| 55 | import org.openflow.protocol.action.OFAction; |
| 56 | import org.openflow.protocol.action.OFActionOutput; |
| 57 | import org.slf4j.Logger; |
| 58 | import org.slf4j.LoggerFactory; |
| 59 | |
| 60 | @LogMessageCategory("Flow Programming") |
| 61 | public class Forwarding extends ForwardingBase implements IFloodlightModule { |
Yuta HIGUCHI | 6ac8d18 | 2013-10-22 15:24:56 -0700 | [diff] [blame] | 62 | protected final static Logger log = LoggerFactory.getLogger(Forwarding.class); |
Umesh Krishnaswamy | 345ee99 | 2012-12-13 20:29:48 -0800 | [diff] [blame] | 63 | |
| 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 | } |