-
Banse, Christian authored
SEND_FLOW_REM in flow mod
Banse, Christian authoredSEND_FLOW_REM in flow mod
ForwardingBase.java 18.12 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.EnumSet;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
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.internal.IOFSwitchService;
import net.floodlightcontroller.core.util.AppCookie;
import net.floodlightcontroller.debugcounter.IDebugCounterService;
import net.floodlightcontroller.devicemanager.IDeviceService;
import net.floodlightcontroller.devicemanager.SwitchPort;
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.MatchUtils;
import net.floodlightcontroller.util.OFMessageDamper;
import net.floodlightcontroller.util.TimedCache;
import org.projectfloodlight.openflow.protocol.OFFlowMod;
import org.projectfloodlight.openflow.protocol.match.Match;
import org.projectfloodlight.openflow.protocol.match.MatchField;
import org.projectfloodlight.openflow.protocol.OFFlowModCommand;
import org.projectfloodlight.openflow.protocol.OFFlowModFlags;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFPacketOut;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.action.OFAction;
import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.MacAddress;
import org.projectfloodlight.openflow.types.OFBufferId;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.U64;
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 {
protected static Logger log =
LoggerFactory.getLogger(ForwardingBase.class);
protected static int OFMESSAGE_DAMPER_CAPACITY = 10000; // TODO: find sweet spot
protected static int OFMESSAGE_DAMPER_TIMEOUT = 250; // ms
public static int FLOWMOD_DEFAULT_IDLE_TIMEOUT = 5; // in seconds
public static int FLOWMOD_DEFAULT_HARD_TIMEOUT = 0; // infinite
public static int FLOWMOD_DEFAULT_PRIORITY = 1; // 0 is the default table-miss flow in OF1.3+, so we need to use 1
public static boolean FLOWMOD_DEFAULT_SET_SEND_FLOW_REM_FLAG = false;
public static boolean FLOWMOD_DEFAULT_MATCH_VLAN = true;
public static boolean FLOWMOD_DEFAULT_MATCH_MAC = true;
public static boolean FLOWMOD_DEFAULT_MATCH_IP_ADDR = true;
public static boolean FLOWMOD_DEFAULT_MATCH_TRANSPORT = true;
public static final short FLOWMOD_DEFAULT_IDLE_TIMEOUT_CONSTANT = 5;
public static final short FLOWMOD_DEFAULT_HARD_TIMEOUT_CONSTANT = 0;
protected IFloodlightProviderService floodlightProviderService;
protected IOFSwitchService switchService;
protected IDeviceService deviceManagerService;
protected IRoutingService routingEngineService;
protected ITopologyService topologyService;
protected IDebugCounterService debugCounterService;
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
static {
AppCookie.registerApp(FORWARDING_APP_ID, "Forwarding");
}
public static final U64 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) {
DatapathId d1ClusterId = topologyService.getL2DomainId(d1.getSwitchDPID());
DatapathId d2ClusterId = topologyService.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() {
floodlightProviderService.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) {
switch (msg.getType()) {
case PACKET_IN:
IRoutingDecision decision = null;
if (cntx != null) {
decision = RoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION);
}
return this.processPacketInMessage(sw, (OFPacketIn) msg, decision, cntx);
default:
break;
}
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 srcSwitchIncluded 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, Match match, OFPacketIn pi,
DatapathId pinSwitch, U64 cookie, FloodlightContext cntx,
boolean reqeustFlowRemovedNotifn, boolean doFlush,
OFFlowModCommand flowModCommand) {
boolean srcSwitchIncluded = false;
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.
DatapathId switchDPID = switchPortList.get(indx).getNodeId();
IOFSwitch sw = switchService.getSwitch(switchDPID);
if (sw == null) {
if (log.isWarnEnabled()) {
log.warn("Unable to push route, switch at DPID {} " + "not available", switchDPID);
}
return srcSwitchIncluded;
}
// need to build flow mod based on what type it is. Cannot set command later
OFFlowMod.Builder fmb;
switch (flowModCommand) {
case ADD:
fmb = sw.getOFFactory().buildFlowAdd();
break;
case DELETE:
fmb = sw.getOFFactory().buildFlowDelete();
break;
case DELETE_STRICT:
fmb = sw.getOFFactory().buildFlowDeleteStrict();
break;
case MODIFY:
fmb = sw.getOFFactory().buildFlowModify();
break;
default:
log.error("Could not decode OFFlowModCommand. Using MODIFY_STRICT. (Should another be used as the default?)");
case MODIFY_STRICT:
fmb = sw.getOFFactory().buildFlowModifyStrict();
break;
}
OFActionOutput.Builder aob = sw.getOFFactory().actions().buildOutput();
List<OFAction> actions = new ArrayList<OFAction>();
Match.Builder mb = MatchUtils.createRetentiveBuilder(match);
// set input and output ports on the switch
OFPort outPort = switchPortList.get(indx).getPortId();
OFPort inPort = switchPortList.get(indx - 1).getPortId();
mb.setExact(MatchField.IN_PORT, inPort);
aob.setPort(outPort);
aob.setMaxLen(Integer.MAX_VALUE);
actions.add(aob.build());
if(FLOWMOD_DEFAULT_SET_SEND_FLOW_REM_FLAG) {
Set<OFFlowModFlags> flags = new HashSet<>();
flags.add(OFFlowModFlags.SEND_FLOW_REM);
fmb.setFlags(flags);
}
// compile
fmb.setMatch(mb.build()) // was match w/o modifying input port
.setActions(actions)
.setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT)
.setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT)
.setBufferId(OFBufferId.NO_BUFFER)
.setCookie(cookie)
.setOutPort(outPort)
.setPriority(FLOWMOD_DEFAULT_PRIORITY);
try {
if (log.isTraceEnabled()) {
log.trace("Pushing Route flowmod routeIndx={} " +
"sw={} inPort={} outPort={}",
new Object[] {indx,
sw,
fmb.getMatch().get(MatchField.IN_PORT),
outPort });
}
messageDamper.write(sw, fmb.build());
if (doFlush) {
sw.flush();
}
// Push the packet out the source switch
if (sw.getId().equals(pinSwitch)) {
// TODO: Instead of doing a packetOut here we could also
// send a flowMod with bufferId set....
pushPacket(sw, pi, false, outPort, cntx);
srcSwitchIncluded = true;
}
} catch (IOException e) {
log.error("Failure writing flow mod", e);
}
}
return srcSwitchIncluded;
}
/**
* 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. In this case the packet can be null
* 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)
})
/**
* 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 pi packet-in
* @param useBufferId if true, use the bufferId from the packet in and
* do not add the packetIn's payload. If false set bufferId to
* BUFFER_ID_NONE and use the packetIn's payload
* @param outport output port
* @param cntx context of the packet
*/
protected void pushPacket(IOFSwitch sw, OFPacketIn pi, boolean useBufferId,
OFPort 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.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)).equals(outport)) {
if (log.isDebugEnabled()) {
log.debug("Attempting to do packet-out to the same " +
"interface as packet-in. Dropping packet. " +
" SrcSwitch={}, pi={}",
new Object[]{sw, pi});
return;
}
}
if (log.isTraceEnabled()) {
log.trace("PacketOut srcSwitch={} pi={}",
new Object[] {sw, pi});
}
OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut();
// set actions
List<OFAction> actions = new ArrayList<OFAction>();
actions.add(sw.getOFFactory().actions().output(outport, Integer.MAX_VALUE));
pob.setActions(actions);
if (useBufferId) {
pob.setBufferId(pi.getBufferId());
} else {
pob.setBufferId(OFBufferId.NO_BUFFER);
}
if (pob.getBufferId() == OFBufferId.NO_BUFFER) {
byte[] packetData = pi.getData();
pob.setData(packetData);
}
pob.setInPort((pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)));
try {
messageDamper.write(sw, pob.build());
} 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,
OFPort inPort, Set<OFPort> outPorts, FloodlightContext cntx) {
//setting actions
List<OFAction> actions = new ArrayList<OFAction>();
Iterator<OFPort> j = outPorts.iterator();
while (j.hasNext()) {
actions.add(sw.getOFFactory().actions().output(j.next(), 0));
}
OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut();
pob.setActions(actions);
pob.setBufferId(OFBufferId.NO_BUFFER);
pob.setInPort(inPort);
pob.setData(packetData);
try {
if (log.isTraceEnabled()) {
log.trace("write broadcast packet on switch-id={} " +
"interfaces={} packet-out={}",
new Object[] {sw.getId(), outPorts, pob.build()});
}
messageDamper.write(sw, pob.build());
} 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,
OFPort inPort, Set<OFPort> outPorts, FloodlightContext cntx) {
packetOutMultiPort(pi.getData(), 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,
OFPort inPort, Set<OFPort> outPorts, FloodlightContext cntx) {
packetOutMultiPort(packet.serialize(), sw, inPort, outPorts, cntx);
}
@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(IOFSwitchService switchService,
SwitchPort sw_tup, MacAddress host_mac, short hardTimeout, U64 cookie) {
if (sw_tup == null) {
return false;
}
IOFSwitch sw = switchService.getSwitch(sw_tup.getSwitchDPID());
if (sw == null) {
return false;
}
OFPort inputPort = sw_tup.getPort();
log.debug("blockHost sw={} port={} mac={}",
new Object[] { sw, sw_tup.getPort(), host_mac.getLong() });
// Create flow-mod based on packet-in and src-switch
OFFlowMod.Builder fmb = sw.getOFFactory().buildFlowAdd();
Match.Builder mb = sw.getOFFactory().buildMatch();
List<OFAction> actions = new ArrayList<OFAction>(); // Set no action to drop
mb.setExact(MatchField.IN_PORT, inputPort);
if (host_mac.getLong() != -1L) {
mb.setExact(MatchField.ETH_SRC, host_mac);
}
fmb.setCookie(cookie)
.setHardTimeout(hardTimeout)
.setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT)
.setPriority(FLOWMOD_DEFAULT_PRIORITY)
.setBufferId(OFBufferId.NO_BUFFER)
.setMatch(mb.build())
.setActions(actions);
log.debug("write drop flow-mod sw={} match={} flow-mod={}",
new Object[] { sw, mb.build(), fmb.build() });
// TODO: can't use the message damper sine this method is static
sw.write(fmb.build());
return true;
}
@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;
}
}