/**
* Copyright 2011, Big Switch Networks, Inc.
* Originally created by David Erickson, Stanford University
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
**/
package net.floodlightcontroller.forwarding;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.annotations.LogMessageCategory;
import net.floodlightcontroller.core.annotations.LogMessageDoc;
import net.floodlightcontroller.core.annotations.LogMessageDocs;
import net.floodlightcontroller.core.module.FloodlightModuleContext;
import net.floodlightcontroller.core.module.FloodlightModuleException;
import net.floodlightcontroller.core.module.IFloodlightModule;
import net.floodlightcontroller.core.module.IFloodlightService;
import net.floodlightcontroller.core.util.AppCookie;
import net.floodlightcontroller.counter.ICounterStoreService;
import net.floodlightcontroller.devicemanager.IDevice;
import net.floodlightcontroller.devicemanager.IDeviceService;
import net.floodlightcontroller.devicemanager.SwitchPort;
import net.floodlightcontroller.packet.Ethernet;
import net.floodlightcontroller.routing.ForwardingBase;
import net.floodlightcontroller.routing.IRoutingDecision;
import net.floodlightcontroller.routing.IRoutingService;
import net.floodlightcontroller.routing.Route;
import net.floodlightcontroller.topology.ITopologyService;
import org.openflow.protocol.*;
import org.openflow.protocol.action.OFAction;
import org.openflow.protocol.action.OFActionOutput;
import java.io.IOException;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@LogMessageCategory("Flow Programming")
public class Forwarding extends ForwardingBase implements IFloodlightModule {
protected static Logger log = LoggerFactory.getLogger(Forwarding.class);
@Override
@LogMessageDoc(level="ERROR",
message="Unexpected decision made for this packet-in={}",
explanation="An unsupported PacketIn decision has been " +
"passed to the flow programming component",
recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG)
public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision,
FloodlightContext cntx) {
Ethernet eth = IFloodlightProviderService.bcStore.get(cntx,
IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
// If a decision has been made we obey it
// otherwise we just forward
if (decision != null) {
if (log.isTraceEnabled()) {
log.trace("Forwaring decision={} was made for PacketIn={}",
decision.getRoutingAction().toString(),
pi);
}
log.debug("routing action has been made");
switch(decision.getRoutingAction()) {
case NONE:
// don't do anything
return Command.CONTINUE;
case FORWARD_OR_FLOOD:
case FORWARD:
doForwardFlow(sw, pi, cntx, false);
return Command.CONTINUE;
case MULTICAST:
// treat as broadcast
doFlood(sw, pi, cntx);
return Command.CONTINUE;
case DROP:
doDropFlow(sw, pi, decision, cntx);
return Command.CONTINUE;
default:
log.error("Unexpected decision made for this packet-in={}",
pi, decision.getRoutingAction());
return Command.CONTINUE;
}
} else {
if (log.isTraceEnabled()) {
log.trace("No decision was made for PacketIn={}, forwarding",
pi);
}
log.debug("receive a PACKET_IN message, pi = " + pi);
if (eth.isBroadcast() || eth.isMulticast()) {
// For now we treat multicast as broadcast
log.debug("no target host information, then flood");
doFlood(sw, pi, cntx);
} else {
doForwardFlow(sw, pi, cntx, false);
}
}
return Command.CONTINUE;
}
@LogMessageDoc(level="ERROR",
message="Failure writing drop flow mod",
explanation="An I/O error occured while trying to write a " +
"drop flow mod to a switch",
recommendation=LogMessageDoc.CHECK_SWITCH)
protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) {
// initialize match structure and populate it using the packet
OFMatch match = new OFMatch();
match.loadFromPacket(pi.getPacketData(), pi.getInPort());
if (decision.getWildcards() != null) {
match.setWildcards(decision.getWildcards());
}
// Create flow-mod based on packet-in and src-switch
OFFlowMod fm =
(OFFlowMod) floodlightProvider.getOFMessageFactory()
.getMessage(OFType.FLOW_MOD);
List<OFAction> actions = new ArrayList<OFAction>(); // Set no action to
// drop
long cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0);
fm.setCookie(cookie)
.setHardTimeout((short) 0)
.setIdleTimeout((short) 5)
.setBufferId(OFPacketOut.BUFFER_ID_NONE)
.setMatch(match)
.setActions(actions)
.setLengthU(OFFlowMod.MINIMUM_LENGTH); // +OFActionOutput.MINIMUM_LENGTH);
try {
if (log.isDebugEnabled()) {
log.debug("write drop flow-mod sw={} match={} flow-mod={}",
new Object[] { sw, match, fm });
}
messageDamper.write(sw, fm, cntx);
} catch (IOException e) {
log.error("Failure writing drop flow mod", e);
}
}
protected void doForwardFlow(IOFSwitch sw, OFPacketIn pi,
FloodlightContext cntx,
boolean requestFlowRemovedNotifn) {
OFMatch match = new OFMatch();
match.loadFromPacket(pi.getPacketData(), pi.getInPort());
// Check if we have the location of the destination
IDevice dstDevice =
IDeviceService.fcStore.
get(cntx, IDeviceService.CONTEXT_DST_DEVICE);
if (dstDevice != null) {
IDevice srcDevice =
IDeviceService.fcStore.
get(cntx, IDeviceService.CONTEXT_SRC_DEVICE);
Long srcIsland = topology.getL2DomainId(sw.getId());
if (srcDevice == null) {
log.debug("No device entry found for source device");
return;
}
if (srcIsland == null) {
log.debug("No openflow island found for source {}/{}",
sw.getStringId(), pi.getInPort());
return;
}
// Validate that we have a destination known on the same island
// Validate that the source and destination are not on the same switchport
boolean on_same_island = false;
boolean on_same_if = false;
for (SwitchPort dstDap : dstDevice.getAttachmentPoints()) {
long dstSwDpid = dstDap.getSwitchDPID();
Long dstIsland = topology.getL2DomainId(dstSwDpid);
if ((dstIsland != null) && dstIsland.equals(srcIsland)) {
on_same_island = true;
if ((sw.getId() == dstSwDpid) &&
(pi.getInPort() == dstDap.getPort())) {
on_same_if = true;
}
break;
}
}
if (!on_same_island) {
log.debug("Flood since we don't know the dst device");
if (log.isTraceEnabled()) {
log.trace("No first hop island found for destination " +
"device {}, Action = flooding", dstDevice);
}
doFlood(sw, pi, cntx);
return;
}
if (on_same_if) {
if (log.isTraceEnabled()) {
log.trace("Both source and destination are on the same " +
"switch/port {}/{}, Action = NOP",
sw.toString(), pi.getInPort());
}
return;
}
// Install all the routes where both src and dst have attachment
// points. Since the lists are stored in sorted order we can
// traverse the attachment points in O(m+n) time
SwitchPort[] srcDaps = srcDevice.getAttachmentPoints();
Arrays.sort(srcDaps, clusterIdComparator);
SwitchPort[] dstDaps = dstDevice.getAttachmentPoints();
Arrays.sort(dstDaps, clusterIdComparator);
int iSrcDaps = 0, iDstDaps = 0;
while ((iSrcDaps < srcDaps.length) && (iDstDaps < dstDaps.length)) {
SwitchPort srcDap = srcDaps[iSrcDaps];
SwitchPort dstDap = dstDaps[iDstDaps];
Long srcCluster =
topology.getL2DomainId(srcDap.getSwitchDPID());
Long dstCluster =
topology.getL2DomainId(dstDap.getSwitchDPID());
int srcVsDest = srcCluster.compareTo(dstCluster);
if (srcVsDest == 0) {
if (!srcDap.equals(dstDap) &&
(srcCluster != null) &&
(dstCluster != null)) {
Route route =
routingEngine.getRoute(srcDap.getSwitchDPID(),
(short)srcDap.getPort(),
dstDap.getSwitchDPID(),
(short)dstDap.getPort());
if (route != null) {
if (log.isTraceEnabled()) {
log.trace("pushRoute match={} route={} " +
"destination={}:{}",
new Object[] {match, route,
dstDap.getSwitchDPID(),
dstDap.getPort()});
}
long cookie =
AppCookie.makeCookie(FORWARDING_APP_ID, 0);
// if there is prior routing decision use wildcard
Integer wildcard_hints = null;
IRoutingDecision decision = null;
if (cntx != null) {
decision = IRoutingDecision.rtStore
.get(cntx,
IRoutingDecision.CONTEXT_DECISION);
}
if (decision != null) {
wildcard_hints = decision.getWildcards();
} else {
// L2 only wildcard if there is no prior route decision
wildcard_hints = ((Integer) sw
.getAttribute(IOFSwitch.PROP_FASTWILDCARDS))
.intValue()
& ~OFMatch.OFPFW_IN_PORT
& ~OFMatch.OFPFW_DL_VLAN
& ~OFMatch.OFPFW_DL_SRC
& ~OFMatch.OFPFW_DL_DST
& ~OFMatch.OFPFW_NW_SRC_MASK
& ~OFMatch.OFPFW_NW_DST_MASK;
}
pushRoute(route, match, wildcard_hints, pi, sw.getId(), cookie,
cntx, requestFlowRemovedNotifn, false,
OFFlowMod.OFPFC_ADD);
}
}
iSrcDaps++;
iDstDaps++;
} else if (srcVsDest < 0) {
iSrcDaps++;
} else {
iDstDaps++;
}
}
} else {
// Flood since we don't know the dst device
log.debug("Flood since we don't know the dst device");
doFlood(sw, pi, cntx);
}
}
/**
* Creates a OFPacketOut with the OFPacketIn data that is flooded on all ports unless
* the port is blocked, in which case the packet will be dropped.
* @param sw The switch that receives the OFPacketIn
* @param pi The OFPacketIn that came to the switch
* @param cntx The FloodlightContext associated with this OFPacketIn
*/
@LogMessageDoc(level="ERROR",
message="Failure writing PacketOut " +
"switch={switch} packet-in={packet-in} " +
"packet-out={packet-out}",
explanation="An I/O error occured while writing a packet " +
"out message to the switch",
recommendation=LogMessageDoc.CHECK_SWITCH)
protected void doFlood(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) {
if (topology.isIncomingBroadcastAllowed(sw.getId(),
pi.getInPort()) == false) {
if (log.isTraceEnabled()) {
log.trace("doFlood, drop broadcast packet, pi={}, " +
"from a blocked port, srcSwitch=[{},{}], linkInfo={}",
new Object[] {pi, sw.getId(),pi.getInPort()});
}
log.debug("doFlood, drop broadcast packet, pi={}, " +
"from a blocked port, srcSwitch=[{} {}], linkInfo={}",
new Object[] {pi, sw.getId(), sw.getStringId(), pi.getInPort()});
return;
}
log.debug("doFlood, we have a packet_in :" + pi.toString());
// Set Action to flood
OFPacketOut po =
(OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT);
List<OFAction> actions = new ArrayList<OFAction>();
if (sw.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_FLOOD)) {
actions.add(new OFActionOutput(OFPort.OFPP_FLOOD.getValue(),
(short)0xFFFF));
} else {
actions.add(new OFActionOutput(OFPort.OFPP_ALL.getValue(),
(short)0xFFFF));
}
po.setActions(actions);
po.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
// set buffer-id, in-port and packet-data based on packet-in
short poLength = (short)(po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH);
po.setBufferId(pi.getBufferId());
po.setInPort(pi.getInPort());
if (pi.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
byte[] packetData = pi.getPacketData();
poLength += packetData.length;
po.setPacketData(packetData);
}
po.setLength(poLength);
try {
if (log.isTraceEnabled()) {
log.trace("Writing flood PacketOut switch={} packet-in={} packet-out={}",
new Object[] {sw, pi, po});
}
log.debug("write PACKET_OUT to the channel in flood:" + po.toString());
messageDamper.write(sw, po, cntx);
} catch (IOException e) {
log.error("Failure writing PacketOut switch={} packet-in={} packet-out={}",
new Object[] {sw, pi, po}, e);
}
return;
}
// IFloodlightModule methods
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
// We don't export any services
return null;
}
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService>
getServiceImpls() {
// We don't have any services
return null;
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
Collection<Class<? extends IFloodlightService>> l =
new ArrayList<Class<? extends IFloodlightService>>();
l.add(IFloodlightProviderService.class);
l.add(IDeviceService.class);
l.add(IRoutingService.class);
l.add(ITopologyService.class);
l.add(ICounterStoreService.class);
return l;
}
@Override
@LogMessageDocs({
@LogMessageDoc(level="WARN",
message="Error parsing flow idle timeout, " +
"using default of {number} seconds",
explanation="The properties file contains an invalid " +
"flow idle timeout",
recommendation="Correct the idle timeout in the " +
"properties file."),
@LogMessageDoc(level="WARN",
message="Error parsing flow hard timeout, " +
"using default of {number} seconds",
explanation="The properties file contains an invalid " +
"flow hard timeout",
recommendation="Correct the hard timeout in the " +
"properties file.")
})
public void init(FloodlightModuleContext context) throws FloodlightModuleException {
super.init();
this.floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
this.deviceManager = context.getServiceImpl(IDeviceService.class);
this.routingEngine = context.getServiceImpl(IRoutingService.class);
this.topology = context.getServiceImpl(ITopologyService.class);
this.counterStore = context.getServiceImpl(ICounterStoreService.class);
// read our config options
Map<String, String> configOptions = context.getConfigParams(this);
try {
String idleTimeout = configOptions.get("idletimeout");
if (idleTimeout != null) {
FLOWMOD_DEFAULT_IDLE_TIMEOUT = Short.parseShort(idleTimeout);
}
} catch (NumberFormatException e) {
log.warn("Error parsing flow idle timeout, " +
"using default of {} seconds",
FLOWMOD_DEFAULT_IDLE_TIMEOUT);
}
try {
String hardTimeout = configOptions.get("hardtimeout");
if (hardTimeout != null) {
FLOWMOD_DEFAULT_HARD_TIMEOUT = Short.parseShort(hardTimeout);
}
} catch (NumberFormatException e) {
log.warn("Error parsing flow hard timeout, " +
"using default of {} seconds",
FLOWMOD_DEFAULT_HARD_TIMEOUT);
}
log.debug("FlowMod idle timeout set to {} seconds",
FLOWMOD_DEFAULT_IDLE_TIMEOUT);
log.debug("FlowMod hard timeout set to {} seconds",
FLOWMOD_DEFAULT_HARD_TIMEOUT);
}
@Override
public void startUp(FloodlightModuleContext context) {
super.startUp();
}
}