-
Shudong Zhou authoredShudong Zhou authored
ForwardingBase.java 23.72 KiB
/**
* 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.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
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.routing.IRoutingService;
import net.floodlightcontroller.routing.IRoutingDecision;
import net.floodlightcontroller.routing.Link;
import net.floodlightcontroller.routing.Route;
import net.floodlightcontroller.topology.ITopologyService;
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.OFPort;
import org.openflow.protocol.OFType;
import org.openflow.protocol.action.OFAction;
import org.openflow.protocol.action.OFActionOutput;
import org.openflow.util.HexString;
import org.openflow.util.U16;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class ForwardingBase implements
IOFMessageListener,
IDeviceListener {
protected static Logger log =
LoggerFactory.getLogger(ForwardingBase.class);
public static final short FLOWMOD_DEFAULT_HARD_TIMEOUT = 5; // in seconds
protected IFloodlightProviderService floodlightProvider;
protected IDeviceService deviceManager;
protected IRoutingService routingEngine;
protected ITopologyService topology;
protected ICounterStoreService counterStore;
// for broadcast loop suppression
protected boolean broadcastCacheFeature = true;
public final int prime = 2633; // 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);
}
};
public void startUp() {
deviceManager.addListener(this);
floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
}
@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) {
switch (msg.getType()) {
case PACKET_IN:
IRoutingDecision decision = null;
if (cntx != null)
decision =
IRoutingDecision.rtStore.get(cntx,
IRoutingDecision.CONTEXT_DECISION);
return this.processPacketInMessage(sw,
(OFPacketIn) msg,
decision,
cntx);
}
log.error("received an unexpected message {} from switch {}",
msg,
sw);
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 bufferId BufferId of the original PacketIn
* @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
*/
public boolean pushRoute(Route route, OFMatch match,
Integer wildcard_hints,
SwitchPort srcSwPort,
SwitchPort dstSwPort, int bufferId,
IOFSwitch srcSwitch, OFPacketIn pi, 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();
List<OFAction> actions = new ArrayList<OFAction>();
actions.add(action);
fm.setIdleTimeout((short)5)
.setBufferId(OFPacketOut.BUFFER_ID_NONE)
.setCookie(cookie)
.setCommand(flowModCommand)
.setMatch(match)
.setActions(actions)
.setLengthU(OFFlowMod.MINIMUM_LENGTH+OFActionOutput.MINIMUM_LENGTH);
Map<Long, IOFSwitch> switches = floodlightProvider.getSwitches();
IOFSwitch sw = switches.get(dstSwPort.getSwitchDPID());
((OFActionOutput)fm.getActions().get(0)).
setPort((short)dstSwPort.getPort());
if (route != null) {
for (int routeIndx = route.getPath().size() - 1; routeIndx >= 0; --routeIndx) {
Link link = route.getPath().get(routeIndx);
fm.setMatch(wildcard(match, sw, wildcard_hints));
fm.getMatch().setInputPort(link.getDstPort());
try {
counterStore.updatePktOutFMCounterStore(sw, fm);
if (log.isTraceEnabled()) {
log.trace("Pushing Route flowmod routeIndx={} " +
"sw={} inPort={} outPort={}",
new Object[] {routeIndx,
sw,
fm.getMatch().getInputPort(),
((OFActionOutput)
fm.getActions()
.get(0)).getPort() });
}
sw.write(fm, cntx);
if (doFlush) {
sw.flush();
}
// Push the packet out the source switch
if (sw.getId() == srcSwitch.getId()) {
pushPacket(srcSwitch,
match,
pi,
((OFActionOutput) fm.getActions().get(0)).getPort(),
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);
}
// setup for the next loop iteration
((OFActionOutput)fm.getActions().get(0)).setPort(link.getSrcPort());
if (routeIndx > 0) {
sw =
floodlightProvider.getSwitches()
.get(route.getPath()
.get(routeIndx - 1)
.getDst());
} else {
sw =
floodlightProvider.getSwitches()
.get(route.getId().getSrc());
}
if (sw == null) {
if (log.isWarnEnabled()) {
log.warn("Unable to push route, switch at DPID {} not available",
(routeIndx > 0)
? HexString.toHexString(route.getPath()
.get(routeIndx - 1)
.getDst())
: HexString.toHexString(route.getId()
.getSrc()));
}
return srcSwitchIncluded;
}
}
}
// set the original match for the first switch, and buffer id
fm.setMatch(match);
fm.setBufferId(bufferId);
fm.setMatch(wildcard(match, sw, wildcard_hints));
fm.getMatch().setInputPort((short) srcSwPort.getPort());
// 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());
}
try {
counterStore.updatePktOutFMCounterStore(sw, fm);
if (log.isDebugEnabled()) {
log.debug("pushRoute flowmod sw={} inPort={} outPort={}",
new Object[] { sw, fm.getMatch().getInputPort(),
((OFActionOutput)fm.getActions().get(0)).getPort() });
}
sw.write(fm, cntx);
if (doFlush) {
sw.flush();
}
if (sw.getId() == srcSwitch.getId()) {
pushPacket(srcSwitch,
match,
pi,
((OFActionOutput) fm.getActions().get(0)).getPort(),
cntx);
srcSwitchIncluded = true;
}
match = fm.getMatch();
} catch (IOException e) {
log.error("Failure writing 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();
}
/**
* This function will push 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
*/
public void pushPacket(IOFSwitch sw, OFMatch match, OFPacketIn pi,
short outport, FloodlightContext cntx) {
if (pi == null) {
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("Attemping 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) 0));
po.setActions(actions)
.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
short poLength =
(short) (po.getActionsLength() + OFPacketOut.MINIMUM_LENGTH);
// set buffer_id, in_port
po.setBufferId(pi.getBufferId());
po.setInPort(pi.getInPort());
// set data - only if buffer_id == -1
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);
sw.write(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.
* Note that the packet in could be from a different switch.
* @param pi
* @param sw
* @param inPort
* @param ports
* @param cntx
*/
public void PacketOutMultiPort(OFPacketIn pi,
IOFSwitch sw,
short inPort,
HashSet<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);
byte[] packetData = pi.getPacketData();
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={} " +
"interaces={} packet-in={} packet-out={}",
new Object[] {sw.getId(), outPorts, pi, po});
}
sw.write(po, cntx);
} catch (IOException e) {
log.error("Failure writing packet out", e);
}
}
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())
* prime + 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);
// some FORWARD_OR_FLOOD packets are unicast with unknown destination mac
// if (eth.isBroadcast() || eth.isMulticast())
return sw.updateBroadcastCache(new Long(eth.hashCode()), pi.getInPort());
// return false;
}
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((short) 5)
.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 });
sw.write(fm, null);
} catch (IOException e) {
log.error("Failure writing deny flow mod", e);
return false;
}
return true;
}
/**
* @param floodlightProvider
* the floodlightProvider to set
*/
public
void
setFloodlightProvider(IFloodlightProviderService floodlightProvider) {
this.floodlightProvider = floodlightProvider;
}
/**
* @param routingEngine
* the routingEngine to set
*/
public void setRoutingEngine(IRoutingService routingEngine) {
this.routingEngine = routingEngine;
}
/**
* @param deviceManager
* the deviceManager to set
*/
public void setDeviceManager(IDeviceService deviceManager) {
this.deviceManager = deviceManager;
}
/**
* @param topology
* the topology to set
*/
public void setTopology(ITopologyService topology) {
this.topology = topology;
}
public ICounterStoreService getCounterStore() {
return counterStore;
}
public void setCounterStore(ICounterStoreService counterStore) {
this.counterStore = counterStore;
}
@Override
public void deviceAdded(IDevice device) {
// NOOP
}
@Override
public void deviceRemoved(IDevice device) {
// NOOP
}
@Override
public void deviceMoved(IDevice device) {
// Build flow mod to delete based on destination mac == device mac
OFMatch match = new OFMatch();
match.setDataLayerDestination(Ethernet.toByteArray(device.getMACAddress()));
match.setWildcards(OFMatch.OFPFW_ALL ^ OFMatch.OFPFW_DL_DST);
long cookie =
AppCookie.makeCookie(FORWARDING_APP_ID, 0);
OFMessage fm = ((OFFlowMod) floodlightProvider.getOFMessageFactory()
.getMessage(OFType.FLOW_MOD))
.setCommand(OFFlowMod.OFPFC_DELETE)
.setOutPort((short) OFPort.OFPP_NONE.getValue())
.setMatch(match)
.setCookie(cookie)
.setLength(U16.t(OFFlowMod.MINIMUM_LENGTH));
// Flush to all switches
for (IOFSwitch outSw : floodlightProvider.getSwitches().values()) {
try {
outSw.write(fm, null);
} catch (IOException e) {
log.error("Failure sending flow mod delete for moved device",
e);
}
}
}
@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;
}
}