/**
* 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.
**/
/**
* Floodlight
* A BSD licensed, Java based OpenFlow controller
*
* Floodlight is a Java based OpenFlow controller originally written by David Erickson at Stanford
* University. It is available under the BSD license.
*
* For documentation, forums, issue tracking and more visit:
*
* http://www.openflowhub.org/display/Floodlight/Floodlight+Home
**/
package net.floodlightcontroller.learningswitch;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
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.types.MacVlanPair;
import net.floodlightcontroller.counter.ICounterStoreService;
import net.floodlightcontroller.packet.Ethernet;
import net.floodlightcontroller.restserver.IRestApiService;
import org.openflow.protocol.OFFlowMod;
import org.openflow.protocol.OFFlowRemoved;
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.LRULinkedHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LearningSwitch
implements IFloodlightModule, ILearningSwitchService, IOFMessageListener {
protected static Logger log = LoggerFactory.getLogger(LearningSwitch.class);
// Module dependencies
protected IFloodlightProviderService floodlightProvider;
protected ICounterStoreService counterStore;
protected IRestApiService restApi;
// Stores the learned state for each switch
protected Map<IOFSwitch, Map<MacVlanPair,Short>> macVlanToSwitchPortMap;
// flow-mod - for use in the cookie
public static final int LEARNING_SWITCH_APP_ID = 1;
// LOOK! This should probably go in some class that encapsulates
// the app cookie management
public static final int APP_ID_BITS = 12;
public static final int APP_ID_SHIFT = (64 - APP_ID_BITS);
public static final long LEARNING_SWITCH_COOKIE = (long) (LEARNING_SWITCH_APP_ID & ((1 << APP_ID_BITS) - 1)) << APP_ID_SHIFT;
// more flow-mod defaults
protected static short FLOWMOD_DEFAULT_IDLE_TIMEOUT = 5; // in seconds
protected static short FLOWMOD_DEFAULT_HARD_TIMEOUT = 0; // infinite
protected static short FLOWMOD_PRIORITY = 100;
// for managing our map sizes
protected static final int MAX_MACS_PER_SWITCH = 1000;
// normally, setup reverse flow as well. Disable only for using cbench for comparison with NOX etc.
protected static final boolean LEARNING_SWITCH_REVERSE_FLOW = true;
/**
* @param floodlightProvider the floodlightProvider to set
*/
public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) {
this.floodlightProvider = floodlightProvider;
}
@Override
public String getName() {
return "learningswitch";
}
/**
* Adds a host to the MAC/VLAN->SwitchPort mapping
* @param sw The switch to add the mapping to
* @param mac The MAC address of the host to add
* @param vlan The VLAN that the host is on
* @param portVal The switchport that the host is on
*/
protected void addToPortMap(IOFSwitch sw, long mac, short vlan, short portVal) {
Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw);
if (vlan == (short) 0xffff) {
// OFMatch.loadFromPacket sets VLAN ID to 0xffff if the packet contains no VLAN tag;
// for our purposes that is equivalent to the default VLAN ID 0
vlan = 0;
}
if (swMap == null) {
// May be accessed by REST API so we need to make it thread safe
swMap = Collections.synchronizedMap(new LRULinkedHashMap<MacVlanPair,Short>(MAX_MACS_PER_SWITCH));
macVlanToSwitchPortMap.put(sw, swMap);
}
swMap.put(new MacVlanPair(mac, vlan), portVal);
}
/**
* Removes a host from the MAC/VLAN->SwitchPort mapping
* @param sw The switch to remove the mapping from
* @param mac The MAC address of the host to remove
* @param vlan The VLAN that the host is on
*/
protected void removeFromPortMap(IOFSwitch sw, long mac, short vlan) {
if (vlan == (short) 0xffff) {
vlan = 0;
}
Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw);
if (swMap != null)
swMap.remove(new MacVlanPair(mac, vlan));
}
/**
* Get the port that a MAC/VLAN pair is associated with
* @param sw The switch to get the mapping from
* @param mac The MAC address to get
* @param vlan The VLAN number to get
* @return The port the host is on
*/
public Short getFromPortMap(IOFSwitch sw, long mac, short vlan) {
if (vlan == (short) 0xffff) {
vlan = 0;
}
Map<MacVlanPair,Short> swMap = macVlanToSwitchPortMap.get(sw);
if (swMap != null)
return swMap.get(new MacVlanPair(mac, vlan));
// if none found
return null;
}
/**
* Clears the MAC/VLAN -> SwitchPort map for all switches
*/
public void clearLearnedTable() {
macVlanToSwitchPortMap.clear();
}
/**
* Clears the MAC/VLAN -> SwitchPort map for a single switch
* @param sw The switch to clear the mapping for
*/
public void clearLearnedTable(IOFSwitch sw) {
Map<MacVlanPair, Short> swMap = macVlanToSwitchPortMap.get(sw);
if (swMap != null)
swMap.clear();
}
@Override
public synchronized Map<IOFSwitch, Map<MacVlanPair,Short>> getTable() {
return macVlanToSwitchPortMap;
}
/**
* Writes a OFFlowMod to a switch.
* @param sw The switch tow rite the flowmod to.
* @param command The FlowMod actions (add, delete, etc).
* @param bufferId The buffer ID if the switch has buffered the packet.
* @param match The OFMatch structure to write.
* @param outPort The switch port to output it to.
*/
private void writeFlowMod(IOFSwitch sw, short command, int bufferId,
OFMatch match, short outPort) {
// from openflow 1.0 spec - need to set these on a struct ofp_flow_mod:
// struct ofp_flow_mod {
// struct ofp_header header;
// struct ofp_match match; /* Fields to match */
// uint64_t cookie; /* Opaque controller-issued identifier. */
//
// /* Flow actions. */
// uint16_t command; /* One of OFPFC_*. */
// uint16_t idle_timeout; /* Idle time before discarding (seconds). */
// uint16_t hard_timeout; /* Max time before discarding (seconds). */
// uint16_t priority; /* Priority level of flow entry. */
// uint32_t buffer_id; /* Buffered packet to apply to (or -1).
// Not meaningful for OFPFC_DELETE*. */
// uint16_t out_port; /* For OFPFC_DELETE* commands, require
// matching entries to include this as an
// output port. A value of OFPP_NONE
// indicates no restriction. */
// uint16_t flags; /* One of OFPFF_*. */
// struct ofp_action_header actions[0]; /* The action length is inferred
// from the length field in the
// header. */
// };
OFFlowMod flowMod = (OFFlowMod) floodlightProvider.getOFMessageFactory().getMessage(OFType.FLOW_MOD);
flowMod.setMatch(match);
flowMod.setCookie(LearningSwitch.LEARNING_SWITCH_COOKIE);
flowMod.setCommand(command);
flowMod.setIdleTimeout(LearningSwitch.FLOWMOD_DEFAULT_IDLE_TIMEOUT);
flowMod.setHardTimeout(LearningSwitch.FLOWMOD_DEFAULT_HARD_TIMEOUT);
flowMod.setPriority(LearningSwitch.FLOWMOD_PRIORITY);
flowMod.setBufferId(bufferId);
flowMod.setOutPort((command == OFFlowMod.OFPFC_DELETE) ? outPort : OFPort.OFPP_NONE.getValue());
flowMod.setFlags((command == OFFlowMod.OFPFC_DELETE) ? 0 : (short) (1 << 0)); // OFPFF_SEND_FLOW_REM
// set the ofp_action_header/out actions:
// from the openflow 1.0 spec: need to set these on a struct ofp_action_output:
// uint16_t type; /* OFPAT_OUTPUT. */
// uint16_t len; /* Length is 8. */
// uint16_t port; /* Output port. */
// uint16_t max_len; /* Max length to send to controller. */
// type/len are set because it is OFActionOutput,
// and port, max_len are arguments to this constructor
flowMod.setActions(Arrays.asList((OFAction) new OFActionOutput(outPort, (short) 0xffff)));
flowMod.setLength((short) (OFFlowMod.MINIMUM_LENGTH + OFActionOutput.MINIMUM_LENGTH));
if (log.isTraceEnabled()) {
log.trace("{} {} flow mod {}",
new Object[]{ sw, (command == OFFlowMod.OFPFC_DELETE) ? "deleting" : "adding", flowMod });
}
counterStore.updatePktOutFMCounterStoreLocal(sw, flowMod);
// and write it out
try {
sw.write(flowMod, null);
} catch (IOException e) {
log.error("Failed to write {} to switch {}", new Object[]{ flowMod, sw }, 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
*/
private void pushPacket(IOFSwitch sw, OFMatch match, OFPacketIn pi, short outport) {
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("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.updatePktOutFMCounterStoreLocal(sw, po);
sw.write(po, null);
} catch (IOException e) {
log.error("Failure writing packet out", e);
}
}
/**
* Writes an OFPacketOut message to a switch.
* @param sw The switch to write the PacketOut to.
* @param packetInMessage The corresponding PacketIn.
* @param egressPort The switchport to output the PacketOut.
*/
private void writePacketOutForPacketIn(IOFSwitch sw,
OFPacketIn packetInMessage,
short egressPort) {
// from openflow 1.0 spec - need to set these on a struct ofp_packet_out:
// uint32_t buffer_id; /* ID assigned by datapath (-1 if none). */
// uint16_t in_port; /* Packet's input port (OFPP_NONE if none). */
// uint16_t actions_len; /* Size of action array in bytes. */
// struct ofp_action_header actions[0]; /* Actions. */
/* uint8_t data[0]; */ /* Packet data. The length is inferred
from the length field in the header.
(Only meaningful if buffer_id == -1.) */
OFPacketOut packetOutMessage = (OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT);
short packetOutLength = (short)OFPacketOut.MINIMUM_LENGTH; // starting length
// Set buffer_id, in_port, actions_len
packetOutMessage.setBufferId(packetInMessage.getBufferId());
packetOutMessage.setInPort(packetInMessage.getInPort());
packetOutMessage.setActionsLength((short)OFActionOutput.MINIMUM_LENGTH);
packetOutLength += OFActionOutput.MINIMUM_LENGTH;
// set actions
List<OFAction> actions = new ArrayList<OFAction>(1);
actions.add(new OFActionOutput(egressPort, (short) 0));
packetOutMessage.setActions(actions);
// set data - only if buffer_id == -1
if (packetInMessage.getBufferId() == OFPacketOut.BUFFER_ID_NONE) {
byte[] packetData = packetInMessage.getPacketData();
packetOutMessage.setPacketData(packetData);
packetOutLength += (short)packetData.length;
}
// finally, set the total length
packetOutMessage.setLength(packetOutLength);
// and write it out
try {
counterStore.updatePktOutFMCounterStoreLocal(sw, packetOutMessage);
sw.write(packetOutMessage, null);
} catch (IOException e) {
log.error("Failed to write {} to switch {}: {}", new Object[]{ packetOutMessage, sw, e });
}
}
/**
* Processes a OFPacketIn message. If the switch has learned the MAC/VLAN to port mapping
* for the pair it will write a FlowMod for. If the mapping has not been learned the
* we will flood the packet.
* @param sw
* @param pi
* @param cntx
* @return
*/
private Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) {
// Read in packet data headers by using OFMatch
OFMatch match = new OFMatch();
match.loadFromPacket(pi.getPacketData(), pi.getInPort());
Long sourceMac = Ethernet.toLong(match.getDataLayerSource());
Long destMac = Ethernet.toLong(match.getDataLayerDestination());
Short vlan = match.getDataLayerVirtualLan();
if ((destMac & 0xfffffffffff0L) == 0x0180c2000000L) {
if (log.isTraceEnabled()) {
log.trace("ignoring packet addressed to 802.1D/Q reserved addr: switch {} vlan {} dest MAC {}",
new Object[]{ sw, vlan, HexString.toHexString(destMac) });
}
return Command.STOP;
}
if ((sourceMac & 0x010000000000L) == 0) {
// If source MAC is a unicast address, learn the port for this MAC/VLAN
this.addToPortMap(sw, sourceMac, vlan, pi.getInPort());
}
// Now output flow-mod and/or packet
Short outPort = getFromPortMap(sw, destMac, vlan);
if (outPort == null) {
// If we haven't learned the port for the dest MAC/VLAN, flood it
// Don't flood broadcast packets if the broadcast is disabled.
// XXX For LearningSwitch this doesn't do much. The sourceMac is removed
// from port map whenever a flow expires, so you would still see
// a lot of floods.
this.writePacketOutForPacketIn(sw, pi, OFPort.OFPP_FLOOD.getValue());
} else if (outPort == match.getInputPort()) {
log.trace("ignoring packet that arrived on same port as learned destination:"
+ " switch {} vlan {} dest MAC {} port {}",
new Object[]{ sw, vlan, HexString.toHexString(destMac), outPort });
} else {
// Add flow table entry matching source MAC, dest MAC, VLAN and input port
// that sends to the port we previously learned for the dest MAC/VLAN. Also
// add a flow table entry with source and destination MACs reversed, and
// input and output ports reversed. When either entry expires due to idle
// timeout, remove the other one. This ensures that if a device moves to
// a different port, a constant stream of packets headed to the device at
// its former location does not keep the stale entry alive forever.
// FIXME: current HP switches ignore DL_SRC and DL_DST fields, so we have to match on
// NW_SRC and NW_DST as well
match.setWildcards(((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);
// We write FlowMods with Buffer ID none then explicitly PacketOut the buffered packet
this.pushPacket(sw, match, pi, outPort);
this.writeFlowMod(sw, OFFlowMod.OFPFC_ADD, OFPacketOut.BUFFER_ID_NONE, match, outPort);
if (LEARNING_SWITCH_REVERSE_FLOW) {
this.writeFlowMod(sw, OFFlowMod.OFPFC_ADD, -1, match.clone()
.setDataLayerSource(match.getDataLayerDestination())
.setDataLayerDestination(match.getDataLayerSource())
.setNetworkSource(match.getNetworkDestination())
.setNetworkDestination(match.getNetworkSource())
.setTransportSource(match.getTransportDestination())
.setTransportDestination(match.getTransportSource())
.setInputPort(outPort),
match.getInputPort());
}
}
return Command.CONTINUE;
}
/**
* Processes a flow removed message. We will delete the learned MAC/VLAN mapping from
* the switch's table.
* @param sw The switch that sent the flow removed message.
* @param flowRemovedMessage The flow removed message.
* @return Whether to continue processing this message or stop.
*/
private Command processFlowRemovedMessage(IOFSwitch sw, OFFlowRemoved flowRemovedMessage) {
if (flowRemovedMessage.getCookie() != LearningSwitch.LEARNING_SWITCH_COOKIE) {
return Command.CONTINUE;
}
if (log.isTraceEnabled()) {
log.trace("{} flow entry removed {}", sw, flowRemovedMessage);
}
OFMatch match = flowRemovedMessage.getMatch();
// When a flow entry expires, it means the device with the matching source
// MAC address and VLAN either stopped sending packets or moved to a different
// port. If the device moved, we can't know where it went until it sends
// another packet, allowing us to re-learn its port. Meanwhile we remove
// it from the macVlanToPortMap to revert to flooding packets to this device.
this.removeFromPortMap(sw, Ethernet.toLong(match.getDataLayerSource()),
match.getDataLayerVirtualLan());
// Also, if packets keep coming from another device (e.g. from ping), the
// corresponding reverse flow entry will never expire on its own and will
// send the packets to the wrong port (the matching input port of the
// expired flow entry), so we must delete the reverse entry explicitly.
this.writeFlowMod(sw, OFFlowMod.OFPFC_DELETE, -1, match.clone()
.setWildcards(((Integer)sw.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).intValue()
& ~OFMatch.OFPFW_DL_VLAN & ~OFMatch.OFPFW_DL_SRC & ~OFMatch.OFPFW_DL_DST
& ~OFMatch.OFPFW_NW_SRC_MASK & ~OFMatch.OFPFW_NW_DST_MASK)
.setDataLayerSource(match.getDataLayerDestination())
.setDataLayerDestination(match.getDataLayerSource())
.setNetworkSource(match.getNetworkDestination())
.setNetworkDestination(match.getNetworkSource())
.setTransportSource(match.getTransportDestination())
.setTransportDestination(match.getTransportSource()),
match.getInputPort());
return Command.CONTINUE;
}
// IOFMessageListener
@Override
public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
switch (msg.getType()) {
case PACKET_IN:
return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx);
case FLOW_REMOVED:
return this.processFlowRemovedMessage(sw, (OFFlowRemoved) msg);
case ERROR:
log.info("received an error {} from switch {}", msg, sw);
return Command.CONTINUE;
default:
break;
}
log.error("received an unexpected message {} from switch {}", msg, sw);
return Command.CONTINUE;
}
@Override
public boolean isCallbackOrderingPrereq(OFType type, String name) {
return false;
}
@Override
public boolean isCallbackOrderingPostreq(OFType type, String name) {
return false;
}
// IFloodlightModule
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
Collection<Class<? extends IFloodlightService>> l =
new ArrayList<Class<? extends IFloodlightService>>();
l.add(ILearningSwitchService.class);
return l;
}
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService>
getServiceImpls() {
Map<Class<? extends IFloodlightService>,
IFloodlightService> m =
new HashMap<Class<? extends IFloodlightService>,
IFloodlightService>();
m.put(ILearningSwitchService.class, this);
return m;
}
@Override
public Collection<Class<? extends IFloodlightService>>
getModuleDependencies() {
Collection<Class<? extends IFloodlightService>> l =
new ArrayList<Class<? extends IFloodlightService>>();
l.add(IFloodlightProviderService.class);
l.add(ICounterStoreService.class);
l.add(IRestApiService.class);
return l;
}
@Override
public void init(FloodlightModuleContext context)
throws FloodlightModuleException {
macVlanToSwitchPortMap =
new ConcurrentHashMap<IOFSwitch, Map<MacVlanPair,Short>>();
floodlightProvider =
context.getServiceImpl(IFloodlightProviderService.class);
counterStore =
context.getServiceImpl(ICounterStoreService.class);
restApi =
context.getServiceImpl(IRestApiService.class);
}
@Override
public void startUp(FloodlightModuleContext context) {
floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
floodlightProvider.addOFMessageListener(OFType.FLOW_REMOVED, this);
floodlightProvider.addOFMessageListener(OFType.ERROR, this);
restApi.addRestletRoutable(new LearningSwitchWebRoutable());
// 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);
}
try {
String priority = configOptions.get("priority");
if (priority != null) {
FLOWMOD_PRIORITY = Short.parseShort(priority);
}
} catch (NumberFormatException e) {
log.warn("Error parsing flow priority, " +
"using default of {}",
FLOWMOD_PRIORITY);
}
log.debug("FlowMod idle timeout set to {} seconds",
FLOWMOD_DEFAULT_IDLE_TIMEOUT);
log.debug("FlowMod hard timeout set to {} seconds",
FLOWMOD_DEFAULT_HARD_TIMEOUT);
log.debug("FlowMod priority set to {}",
FLOWMOD_PRIORITY);
}
}