/**
* 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.routing;
import java.io.IOException;
import java.util.EnumSet;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IOFMessageListener;
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.util.AppCookie;
import net.floodlightcontroller.counter.ICounterStoreService;
import net.floodlightcontroller.devicemanager.IDevice;
import net.floodlightcontroller.devicemanager.IDeviceListener;
import net.floodlightcontroller.devicemanager.IDeviceService;
import net.floodlightcontroller.devicemanager.SwitchPort;
import net.floodlightcontroller.packet.Ethernet;
import net.floodlightcontroller.packet.IPacket;
import net.floodlightcontroller.routing.IRoutingService;
import net.floodlightcontroller.routing.IRoutingDecision;
import net.floodlightcontroller.routing.Route;
import net.floodlightcontroller.topology.ITopologyService;
import net.floodlightcontroller.topology.NodePortTuple;
import net.floodlightcontroller.util.OFMessageDamper;
import net.floodlightcontroller.util.TimedCache;
import org.openflow.protocol.OFFlowMod;
import org.openflow.protocol.OFMatch;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFPacketIn;
import org.openflow.protocol.OFPacketOut;
import org.openflow.protocol.OFType;
import org.openflow.protocol.action.OFAction;
import org.openflow.protocol.action.OFActionOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract base class for implementing a forwarding module. Forwarding is
* responsible for programming flows to a switch in response to a policy
* decision.
*/
@LogMessageCategory("Flow Programming")
public abstract class ForwardingBase
implements IOFMessageListener, IDeviceListener {
protected static Logger log =
LoggerFactory.getLogger(ForwardingBase.class);
protected static int OFMESSAGE_DAMPER_CAPACITY = 50000; // TODO: find sweet spot
protected static int OFMESSAGE_DAMPER_TIMEOUT = 250; // ms
public static short FLOWMOD_DEFAULT_IDLE_TIMEOUT = 5; // in seconds
public static short FLOWMOD_DEFAULT_HARD_TIMEOUT = 0; // infinite
protected IFloodlightProviderService floodlightProvider;
protected IDeviceService deviceManager;
protected IRoutingService routingEngine;
protected ITopologyService topology;
protected ICounterStoreService counterStore;
protected OFMessageDamper messageDamper;
// for broadcast loop suppression
protected boolean broadcastCacheFeature = true;
public final int prime1 = 2633; // for hash calculation
public final static int prime2 = 4357; // for hash calculation
public TimedCache<Long> broadcastCache =
new TimedCache<Long>(100, 5*1000); // 5 seconds interval;
// flow-mod - for use in the cookie
public static final int FORWARDING_APP_ID = 2; // TODO: This must be managed
// by a global APP_ID class
public long appCookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0);
// Comparator for sorting by SwitchCluster
public Comparator<SwitchPort> clusterIdComparator =
new Comparator<SwitchPort>() {
@Override
public int compare(SwitchPort d1, SwitchPort d2) {
Long d1ClusterId =
topology.getL2DomainId(d1.getSwitchDPID());
Long d2ClusterId =
topology.getL2DomainId(d2.getSwitchDPID());
return d1ClusterId.compareTo(d2ClusterId);
}
};
/**
* init data structures
*
*/
protected void init() {
messageDamper = new OFMessageDamper(OFMESSAGE_DAMPER_CAPACITY,
EnumSet.of(OFType.FLOW_MOD),
OFMESSAGE_DAMPER_TIMEOUT);
}
/**
* Adds a listener for devicemanager and registers for PacketIns.
*/
protected void startUp() {
deviceManager.addListener(this);
floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
}
/**
* Returns the application name "forwarding".
*/
@Override
public String getName() {
return "forwarding";
}
/**
* All subclasses must define this function if they want any specific
* forwarding action
*
* @param sw
* Switch that the packet came in from
* @param pi
* The packet that came in
* @param decision
* Any decision made by a policy engine
*/
public abstract Command
processPacketInMessage(IOFSwitch sw, OFPacketIn pi,
IRoutingDecision decision,
FloodlightContext cntx);
@Override
public Command receive(IOFSwitch sw, OFMessage msg,
FloodlightContext cntx) {
log.debug("enter into receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx)");
switch (msg.getType()) {
case PACKET_IN:
log.debug("get a PACKET_IN in receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx)");
IRoutingDecision decision = null;
if (cntx != null)
decision =
IRoutingDecision.rtStore.get(cntx,
IRoutingDecision.CONTEXT_DECISION);
return this.processPacketInMessage(sw,
(OFPacketIn) msg,
decision,
cntx);
default:
log.debug("non-PACKET_IN message, but find in OFMessage");
break;
}
log.debug("non-PACKET_IN message, but find in OFMessage");
return Command.CONTINUE;
}
/**
* Push routes from back to front
* @param route Route to push
* @param match OpenFlow fields to match on
* @param srcSwPort Source switch port for the first hop
* @param dstSwPort Destination switch port for final hop
* @param cookie The cookie to set in each flow_mod
* @param cntx The floodlight context
* @param reqeustFlowRemovedNotifn if set to true then the switch would
* send a flow mod removal notification when the flow mod expires
* @param doFlush if set to true then the flow mod would be immediately
* written to the switch
* @param flowModCommand flow mod. command to use, e.g. OFFlowMod.OFPFC_ADD,
* OFFlowMod.OFPFC_MODIFY etc.
* @return srcSwitchIincluded True if the source switch is included in this route
*/
@LogMessageDocs({
@LogMessageDoc(level="WARN",
message="Unable to push route, switch at DPID {dpid} not available",
explanation="A switch along the calculated path for the " +
"flow has disconnected.",
recommendation=LogMessageDoc.CHECK_SWITCH),
@LogMessageDoc(level="ERROR",
message="Failure writing flow mod",
explanation="An I/O error occurred while writing a " +
"flow modification to a switch",
recommendation=LogMessageDoc.CHECK_SWITCH)
})
public boolean pushRoute(Route route, OFMatch match,
Integer wildcard_hints,
OFPacketIn pi,
long pinSwitch,
long cookie,
FloodlightContext cntx,
boolean reqeustFlowRemovedNotifn,
boolean doFlush,
short flowModCommand) {
boolean srcSwitchIncluded = false;
OFFlowMod fm =
(OFFlowMod) floodlightProvider.getOFMessageFactory()
.getMessage(OFType.FLOW_MOD);
OFActionOutput action = new OFActionOutput();
action.setMaxLength((short)0xffff);
List<OFAction> actions = new ArrayList<OFAction>();
actions.add(action);
fm.setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT)
.setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT)
.setBufferId(OFPacketOut.BUFFER_ID_NONE)
.setCookie(cookie)
.setCommand(flowModCommand)
.setMatch(match)
.setActions(actions)
.setLengthU(OFFlowMod.MINIMUM_LENGTH+OFActionOutput.MINIMUM_LENGTH);
List<NodePortTuple> switchPortList = route.getPath();
for (int indx = switchPortList.size()-1; indx > 0; indx -= 2) {
// indx and indx-1 will always have the same switch DPID.
long switchDPID = switchPortList.get(indx).getNodeId();
IOFSwitch sw = floodlightProvider.getSwitches().get(switchDPID);
if (sw == null) {
if (log.isWarnEnabled()) {
log.warn("Unable to push route, switch at DPID {} " +
"not available", switchDPID);
}
return srcSwitchIncluded;
}
// set the match.
fm.setMatch(wildcard(match, sw, wildcard_hints));
// set buffer id if it is the source switch
if (1 == indx) {
// Set the flag to request flow-mod removal notifications only for the
// source switch. The removal message is used to maintain the flow
// cache. Don't set the flag for ARP messages - TODO generalize check
if ((reqeustFlowRemovedNotifn)
&& (match.getDataLayerType() != Ethernet.TYPE_ARP)) {
fm.setFlags(OFFlowMod.OFPFF_SEND_FLOW_REM);
match.setWildcards(fm.getMatch().getWildcards());
}
}
short outPort = switchPortList.get(indx).getPortId();
short inPort = switchPortList.get(indx-1).getPortId();
// set input and output ports on the switch
fm.getMatch().setInputPort(inPort);
((OFActionOutput)fm.getActions().get(0)).setPort(outPort);
try {
counterStore.updatePktOutFMCounterStore(sw, fm);
if (log.isTraceEnabled()) {
log.trace("Pushing Route flowmod routeIndx={} " +
"sw={} inPort={} outPort={}",
new Object[] {indx,
sw,
fm.getMatch().getInputPort(),
outPort });
}
messageDamper.write(sw, fm, cntx);
if (doFlush) {
sw.flush();
}
// Push the packet out the source switch
if (sw.getId() == pinSwitch) {
// TODO: Instead of doing a packetOut here we could also
// send a flowMod with bufferId set....
pushPacket(sw, match, pi, outPort, cntx);
srcSwitchIncluded = true;
}
} catch (IOException e) {
log.error("Failure writing flow mod", e);
}
try {
fm = fm.clone();
} catch (CloneNotSupportedException e) {
log.error("Failure cloning flow mod", e);
}
}
return srcSwitchIncluded;
}
protected OFMatch wildcard(OFMatch match, IOFSwitch sw,
Integer wildcard_hints) {
if (wildcard_hints != null) {
return match.clone().setWildcards(wildcard_hints.intValue());
}
return match.clone();
}
/**
* Pushes a packet-out to a switch. If bufferId != BUFFER_ID_NONE we
* assume that the packetOut switch is the same as the packetIn switch
* and we will use the bufferId
* Caller needs to make sure that inPort and outPort differs
* @param packet packet data to send
* @param sw switch from which packet-out is sent
* @param bufferId bufferId
* @param inPort input port
* @param outPort output port
* @param cntx context of the packet
* @param flush force to flush the packet.
*/
@LogMessageDocs({
@LogMessageDoc(level="ERROR",
message="BufferId is not and packet data is null. " +
"Cannot send packetOut. " +
"srcSwitch={dpid} inPort={port} outPort={port}",
explanation="The switch send a malformed packet-in." +
"The packet will be dropped",
recommendation=LogMessageDoc.REPORT_SWITCH_BUG),
@LogMessageDoc(level="ERROR",
message="Failure writing packet out",
explanation="An I/O error occurred while writing a " +
"packet out to a switch",
recommendation=LogMessageDoc.CHECK_SWITCH)
})
public void pushPacket(IPacket packet,
IOFSwitch sw,
int bufferId,
short inPort,
short outPort,
FloodlightContext cntx,
boolean flush) {
if (log.isTraceEnabled()) {
log.trace("PacketOut srcSwitch={} inPort={} outPort={}",
new Object[] {sw, inPort, outPort});
}
OFPacketOut po =
(OFPacketOut) floodlightProvider.getOFMessageFactory()
.getMessage(OFType.PACKET_OUT);
// set actions
List<OFAction> actions = new ArrayList<OFAction>();
actions.add(new OFActionOutput(outPort, (short) 0xffff));
po.setActions(actions)
.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
short poLength =
(short) (po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH);
// set buffer_id, in_port
po.setBufferId(bufferId);
po.setInPort(inPort);
// set data - only if buffer_id == -1
if (po.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
if (packet == null) {
log.error("BufferId is not set and packet data is null. " +
"Cannot send packetOut. " +
"srcSwitch={} inPort={} outPort={}",
new Object[] {sw, inPort, outPort});
return;
}
byte[] packetData = packet.serialize();
poLength += packetData.length;
po.setPacketData(packetData);
}
po.setLength(poLength);
try {
counterStore.updatePktOutFMCounterStore(sw, po);
messageDamper.write(sw, po, cntx, flush);
} catch (IOException e) {
log.error("Failure writing packet out", e);
}
}
/**
* Pushes a packet-out to a switch. The assumption here is that
* the packet-in was also generated from the same switch. Thus, if the input
* port of the packet-in and the outport are the same, the function will not
* push the packet-out.
* @param sw switch that generated the packet-in, and from which packet-out is sent
* @param match OFmatch
* @param pi packet-in
* @param outport output port
* @param cntx context of the packet
*/
protected void pushPacket(IOFSwitch sw, OFMatch match, OFPacketIn pi,
short outport, FloodlightContext cntx) {
if (pi == null) {
return;
} else if (pi.getInPort() == outport){
log.warn("Packet out not sent as the outport matches inport. {}",
pi);
return;
}
// The assumption here is (sw) is the switch that generated the
// packet-in. If the input port is the same as output port, then
// the packet-out should be ignored.
if (pi.getInPort() == outport) {
if (log.isDebugEnabled()) {
log.debug("Attempting to do packet-out to the same " +
"interface as packet-in. Dropping packet. " +
" SrcSwitch={}, match = {}, pi={}",
new Object[]{sw, match, pi});
return;
}
}
if (log.isTraceEnabled()) {
log.trace("PacketOut srcSwitch={} match={} pi={}",
new Object[] {sw, match, pi});
}
OFPacketOut po =
(OFPacketOut) floodlightProvider.getOFMessageFactory()
.getMessage(OFType.PACKET_OUT);
// set actions
List<OFAction> actions = new ArrayList<OFAction>();
actions.add(new OFActionOutput(outport, (short) 0xffff));
po.setActions(actions)
.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
short poLength =
(short) (po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH);
// If the switch doens't support buffering set the buffer id to be none
// otherwise it'll be the the buffer id of the PacketIn
if (sw.getBuffers() == 0) {
// We set the PI buffer id here so we don't have to check again below
pi.setBufferId(OFPacketOut.BUFFER_ID_NONE);
po.setBufferId(OFPacketOut.BUFFER_ID_NONE);
} else {
po.setBufferId(pi.getBufferId());
}
po.setInPort(pi.getInPort());
// If the buffer id is none or the switch doesn's support buffering
// we send the data with the packet out
if (pi.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
byte[] packetData = pi.getPacketData();
poLength += packetData.length;
po.setPacketData(packetData);
}
po.setLength(poLength);
try {
counterStore.updatePktOutFMCounterStore(sw, po);
messageDamper.write(sw, po, cntx);
} catch (IOException e) {
log.error("Failure writing packet out", e);
}
}
/**
* Write packetout message to sw with output actions to one or more
* output ports with inPort/outPorts passed in.
* @param packetData
* @param sw
* @param inPort
* @param ports
* @param cntx
*/
public void packetOutMultiPort(byte[] packetData,
IOFSwitch sw,
short inPort,
Set<Integer> outPorts,
FloodlightContext cntx) {
//setting actions
List<OFAction> actions = new ArrayList<OFAction>();
Iterator<Integer> j = outPorts.iterator();
while (j.hasNext())
{
actions.add(new OFActionOutput(j.next().shortValue(),
(short) 0));
}
OFPacketOut po =
(OFPacketOut) floodlightProvider.getOFMessageFactory().
getMessage(OFType.PACKET_OUT);
po.setActions(actions);
po.setActionsLength((short) (OFActionOutput.MINIMUM_LENGTH *
outPorts.size()));
// set buffer-id to BUFFER_ID_NONE, and set in-port to OFPP_NONE
po.setBufferId(OFPacketOut.BUFFER_ID_NONE);
po.setInPort(inPort);
// data (note buffer_id is always BUFFER_ID_NONE) and length
short poLength = (short)(po.getActionsLength() +
OFPacketOut.MINIMUM_LENGTH);
poLength += packetData.length;
po.setPacketData(packetData);
po.setLength(poLength);
try {
counterStore.updatePktOutFMCounterStore(sw, po);
if (log.isTraceEnabled()) {
log.trace("write broadcast packet on switch-id={} " +
"interfaces={} packet-out={}",
new Object[] {sw.getId(), outPorts, po});
}
messageDamper.write(sw, po, cntx);
} catch (IOException e) {
log.error("Failure writing packet out", e);
}
}
/**
* @see packetOutMultiPort
* Accepts a PacketIn instead of raw packet data. Note that the inPort
* and switch can be different than the packet in switch/port
*/
public void packetOutMultiPort(OFPacketIn pi,
IOFSwitch sw,
short inPort,
Set<Integer> outPorts,
FloodlightContext cntx) {
packetOutMultiPort(pi.getPacketData(), sw, inPort, outPorts, cntx);
}
/**
* @see packetOutMultiPort
* Accepts an IPacket instead of raw packet data. Note that the inPort
* and switch can be different than the packet in switch/port
*/
public void packetOutMultiPort(IPacket packet,
IOFSwitch sw,
short inPort,
Set<Integer> outPorts,
FloodlightContext cntx) {
packetOutMultiPort(packet.serialize(), sw, inPort, outPorts, cntx);
}
protected boolean isInBroadcastCache(IOFSwitch sw, OFPacketIn pi,
FloodlightContext cntx) {
// Get the cluster id of the switch.
// Get the hash of the Ethernet packet.
if (sw == null) return true;
// If the feature is disabled, always return false;
if (!broadcastCacheFeature) return false;
Ethernet eth =
IFloodlightProviderService.bcStore.get(cntx,
IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
Long broadcastHash;
broadcastHash = topology.getL2DomainId(sw.getId()) * prime1 +
pi.getInPort() * prime2 + eth.hashCode();
if (broadcastCache.update(broadcastHash)) {
sw.updateBroadcastCache(broadcastHash, pi.getInPort());
return true;
} else {
return false;
}
}
protected boolean isInSwitchBroadcastCache(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) {
if (sw == null) return true;
// If the feature is disabled, always return false;
if (!broadcastCacheFeature) return false;
// Get the hash of the Ethernet packet.
Ethernet eth =
IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
long hash = pi.getInPort() * prime2 + eth.hashCode();
// some FORWARD_OR_FLOOD packets are unicast with unknown destination mac
return sw.updateBroadcastCache(hash, pi.getInPort());
}
@LogMessageDocs({
@LogMessageDoc(level="ERROR",
message="Failure writing deny flow mod",
explanation="An I/O error occurred while writing a " +
"deny flow mod to a switch",
recommendation=LogMessageDoc.CHECK_SWITCH)
})
public static boolean
blockHost(IFloodlightProviderService floodlightProvider,
SwitchPort sw_tup, long host_mac,
short hardTimeout, long cookie) {
if (sw_tup == null) {
return false;
}
IOFSwitch sw =
floodlightProvider.getSwitches().get(sw_tup.getSwitchDPID());
if (sw == null) return false;
int inputPort = sw_tup.getPort();
log.debug("blockHost sw={} port={} mac={}",
new Object[] { sw, sw_tup.getPort(), new Long(host_mac) });
// Create flow-mod based on packet-in and src-switch
OFFlowMod fm =
(OFFlowMod) floodlightProvider.getOFMessageFactory()
.getMessage(OFType.FLOW_MOD);
OFMatch match = new OFMatch();
List<OFAction> actions = new ArrayList<OFAction>(); // Set no action to
// drop
match.setDataLayerSource(Ethernet.toByteArray(host_mac))
.setInputPort((short)inputPort)
.setWildcards(OFMatch.OFPFW_ALL & ~OFMatch.OFPFW_DL_SRC
& ~OFMatch.OFPFW_IN_PORT);
fm.setCookie(cookie)
.setHardTimeout((short) hardTimeout)
.setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT)
.setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT)
.setBufferId(OFPacketOut.BUFFER_ID_NONE)
.setMatch(match)
.setActions(actions)
.setLengthU(OFFlowMod.MINIMUM_LENGTH); // +OFActionOutput.MINIMUM_LENGTH);
try {
log.debug("write drop flow-mod sw={} match={} flow-mod={}",
new Object[] { sw, match, fm });
// TODO: can't use the message damper sine this method is static
sw.write(fm, null);
} catch (IOException e) {
log.error("Failure writing deny flow mod", e);
return false;
}
return true;
}
@Override
public void deviceAdded(IDevice device) {
// NOOP
}
@Override
public void deviceRemoved(IDevice device) {
// NOOP
}
@Override
public void deviceMoved(IDevice device) {
}
@Override
public void deviceIPV4AddrChanged(IDevice device) {
}
@Override
public void deviceVlanChanged(IDevice device) {
}
@Override
public boolean isCallbackOrderingPrereq(OFType type, String name) {
return (type.equals(OFType.PACKET_IN) &&
(name.equals("topology") ||
name.equals("devicemanager")));
}
@Override
public boolean isCallbackOrderingPostreq(OFType type, String name) {
return false;
}
}