/**
* 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.util.EnumSet;
import java.util.ArrayList;
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.internal.IOFSwitchService;
import net.floodlightcontroller.core.types.NodePortTuple;
import net.floodlightcontroller.core.util.AppCookie;
import net.floodlightcontroller.debugcounter.IDebugCounterService;
import net.floodlightcontroller.devicemanager.IDeviceService;
import net.floodlightcontroller.devicemanager.SwitchPort;
import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
import net.floodlightcontroller.packet.IPacket;
import net.floodlightcontroller.routing.IRoutingService;
import net.floodlightcontroller.routing.IRoutingDecision;
import net.floodlightcontroller.routing.Path;
import net.floodlightcontroller.topology.ITopologyService;
import net.floodlightcontroller.util.FlowModUtils;
import net.floodlightcontroller.util.MatchUtils;
import net.floodlightcontroller.util.OFDPAUtils;
import net.floodlightcontroller.util.OFMessageDamper;
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.TableId;
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.
*/
public abstract class ForwardingBase implements IOFMessageListener {
protected static Logger log = LoggerFactory.getLogger(ForwardingBase.class);
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
protected static TableId FLOWMOD_DEFAULT_TABLE_ID = TableId.ZERO;
protected static boolean FLOWMOD_DEFAULT_SET_SEND_FLOW_REM_FLAG = false;
protected static boolean FLOWMOD_DEFAULT_MATCH_IN_PORT = true;
protected static boolean FLOWMOD_DEFAULT_MATCH_VLAN = true;
protected static boolean FLOWMOD_DEFAULT_MATCH_MAC = true;
protected static boolean FLOWMOD_DEFAULT_MATCH_IP = true;
protected static boolean FLOWMOD_DEFAULT_MATCH_TRANSPORT = true;
protected static boolean FLOWMOD_DEFAULT_MATCH_MAC_SRC = true;
protected static boolean FLOWMOD_DEFAULT_MATCH_MAC_DST = true;
protected static boolean FLOWMOD_DEFAULT_MATCH_IP_SRC = true;
protected static boolean FLOWMOD_DEFAULT_MATCH_IP_DST = true;
protected static boolean FLOWMOD_DEFAULT_MATCH_TRANSPORT_SRC = true;
protected static boolean FLOWMOD_DEFAULT_MATCH_TRANSPORT_DST = true;
protected static boolean FLOWMOD_DEFAULT_MATCH_TCP_FLAG = true;
protected static boolean FLOOD_ALL_ARP_PACKETS = false;
protected static boolean REMOVE_FLOWS_ON_LINK_OR_PORT_DOWN = true;
protected IFloodlightProviderService floodlightProviderService;
protected IOFSwitchService switchService;
protected IDeviceService deviceManagerService;
protected IRoutingService routingEngineService;
protected ITopologyService topologyService;
protected IDebugCounterService debugCounterService;
protected ILinkDiscoveryService linkService;
// flow-mod - for use in the cookie
public static final int FORWARDING_APP_ID = 2;
static {
AppCookie.registerApp(FORWARDING_APP_ID, "forwarding");
}
protected static final U64 DEFAULT_FORWARDING_COOKIE = AppCookie.makeCookie(FORWARDING_APP_ID, 0);
protected OFMessageDamper messageDamper;
private static int OFMESSAGE_DAMPER_CAPACITY = 10000;
private static int OFMESSAGE_DAMPER_TIMEOUT = 250; // ms
protected void init() {
messageDamper = new OFMessageDamper(OFMESSAGE_DAMPER_CAPACITY,
EnumSet.of(OFType.FLOW_MOD),
OFMESSAGE_DAMPER_TIMEOUT);
}
protected void startUp() {
floodlightProviderService.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 = 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 requestFlowRemovedNotification if set to true then the switch would
* send a flow mod removal notification when the flow mod expires
* @param flowModCommand flow mod. command to use, e.g. OFFlowMod.OFPFC_ADD,
* OFFlowMod.OFPFC_MODIFY etc.
* @return true if a packet out was sent on the first-hop switch of this route
*/
public boolean pushRoute(Path route, Match match, OFPacketIn pi,
DatapathId pinSwitch, U64 cookie, FloodlightContext cntx,
boolean requestFlowRemovedNotification, OFFlowModCommand flowModCommand) {
boolean packetOutSent = 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 packetOutSent;
}
// 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.convertToVersion(match, sw.getOFFactory().getVersion());
// set input and output ports on the switch
OFPort outPort = switchPortList.get(indx).getPortId();
OFPort inPort = switchPortList.get(indx - 1).getPortId();
if (FLOWMOD_DEFAULT_MATCH_IN_PORT) {
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 || requestFlowRemovedNotification) {
Set<OFFlowModFlags> flags = new HashSet<>();
flags.add(OFFlowModFlags.SEND_FLOW_REM);
fmb.setFlags(flags);
}
fmb.setMatch(mb.build())
.setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT)
.setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT)
.setBufferId(OFBufferId.NO_BUFFER)
.setCookie(cookie)
.setOutPort(outPort)
.setPriority(FLOWMOD_DEFAULT_PRIORITY);
FlowModUtils.setActions(fmb, actions, sw);
/* Configure for particular switch pipeline */
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) != 0) {
fmb.setTableId(FLOWMOD_DEFAULT_TABLE_ID);
}
if (log.isTraceEnabled()) {
log.trace("Pushing Route flowmod routeIndx={} " +
"sw={} inPort={} outPort={}",
new Object[] {indx,
sw,
fmb.getMatch().get(MatchField.IN_PORT),
outPort });
}
if (OFDPAUtils.isOFDPASwitch(sw)) {
OFDPAUtils.addLearningSwitchFlow(sw, cookie,
FLOWMOD_DEFAULT_PRIORITY,
FLOWMOD_DEFAULT_HARD_TIMEOUT,
FLOWMOD_DEFAULT_IDLE_TIMEOUT,
fmb.getMatch(),
null, // TODO how to determine output VLAN for lookup of L2 interface group
outPort);
} else {
messageDamper.write(sw, fmb.build());
}
/* Push the packet out the first hop switch */
if (sw.getId().equals(pinSwitch) &&
!fmb.getCommand().equals(OFFlowModCommand.DELETE) &&
!fmb.getCommand().equals(OFFlowModCommand.DELETE_STRICT)) {
/* Use the buffered packet at the switch, if there's one stored */
pushPacket(sw, pi, outPort, true, cntx);
packetOutSent = true;
}
}
return packetOutSent;
}
/**
* 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 outport output port
* @param useBufferedPacket use the packet buffered at the switch, if possible
* @param cntx context of the packet
*/
protected void pushPacket(IOFSwitch sw, OFPacketIn pi, OFPort outport, boolean useBufferedPacket, 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();
List<OFAction> actions = new ArrayList<OFAction>();
actions.add(sw.getOFFactory().actions().output(outport, Integer.MAX_VALUE));
pob.setActions(actions);
/* Use packet in buffer if there is a buffer ID set */
if (useBufferedPacket) {
pob.setBufferId(pi.getBufferId()); /* will be NO_BUFFER if there isn't one */
} else {
pob.setBufferId(OFBufferId.NO_BUFFER);
}
if (pob.getBufferId().equals(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)));
messageDamper.write(sw, pob.build());
}
/**
* 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);
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());
}
/**
* @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);
}
public 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.getNodeId());
if (sw == null) {
return false;
}
OFPort inputPort = sw_tup.getPortId();
if (log.isDebugEnabled()) {
log.debug("blockHost sw={} port={} mac={}",
new Object[] { sw, sw_tup.getPortId(), 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());
FlowModUtils.setActions(fmb, actions, sw);
if (log.isDebugEnabled()) {
log.debug("write drop flow-mod sw={} match={} flow-mod={}",
new Object[] { sw, mb.build(), fmb.build() });
}
messageDamper.write(sw, 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;
}
}