Skip to content
Snippets Groups Projects
Commit 089df5c6 authored by Ryan Izard's avatar Ryan Izard
Browse files

Finally got around to fixing LearningSwitch.

parent dc40901e
No related branches found
No related tags found
No related merge requests found
/**
* 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.
**/
* 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
......@@ -51,6 +51,7 @@ import net.floodlightcontroller.core.types.MacVlanPair;
import net.floodlightcontroller.debugcounter.IDebugCounter;
import net.floodlightcontroller.debugcounter.IDebugCounterService;
import net.floodlightcontroller.debugcounter.IDebugCounterService.MetaData;
import net.floodlightcontroller.packet.Ethernet;
import net.floodlightcontroller.restserver.IRestApiService;
import org.projectfloodlight.openflow.protocol.OFFlowMod;
......@@ -65,7 +66,6 @@ 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.types.IpProtocol;
import org.projectfloodlight.openflow.types.MacAddress;
import org.projectfloodlight.openflow.types.OFBufferId;
import org.projectfloodlight.openflow.types.OFPort;
......@@ -77,571 +77,553 @@ 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 floodlightProviderService;
protected IRestApiService restApiService;
protected IDebugCounterService debugCounterService;
private IDebugCounter counterFlowMod;
private IDebugCounter counterPacketOut;
// Stores the learned state for each switch
protected Map<IOFSwitch, Map<MacVlanPair, OFPort>> 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 floodlightProviderService) {
this.floodlightProviderService = floodlightProviderService;
}
@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, MacAddress mac, VlanVid vlan, OFPort portVal) {
Map<MacVlanPair, OFPort> swMap = macVlanToSwitchPortMap.get(sw);
if (vlan == VlanVid.FULL_MASK) {
// 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 = VlanVid.ofVlan(0);
}
if (swMap == null) {
// May be accessed by REST API so we need to make it thread safe
swMap = Collections.synchronizedMap(new LRULinkedHashMap<MacVlanPair, OFPort>(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, MacAddress mac, VlanVid vlan) {
if (vlan == VlanVid.FULL_MASK) {
vlan = VlanVid.ofVlan(0);
}
Map<MacVlanPair, OFPort> 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 OFPort getFromPortMap(IOFSwitch sw, MacAddress mac, VlanVid vlan) {
if (vlan == VlanVid.FULL_MASK) {
vlan = VlanVid.ofVlan(0);
}
Map<MacVlanPair, OFPort> 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, OFPort> swMap = macVlanToSwitchPortMap.get(sw);
if (swMap != null) {
swMap.clear();
}
}
@Override
public synchronized Map<IOFSwitch, Map<MacVlanPair, OFPort>> 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, OFFlowModCommand command, OFBufferId bufferId,
Match match, OFPort 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.Builder fmb;
if (command == OFFlowModCommand.DELETE) {
fmb = sw.getOFFactory().buildFlowDelete();
} else {
fmb = sw.getOFFactory().buildFlowAdd();
}
fmb.setMatch(match);
fmb.setCookie((U64.of(LearningSwitch.LEARNING_SWITCH_COOKIE)));
fmb.setIdleTimeout(LearningSwitch.FLOWMOD_DEFAULT_IDLE_TIMEOUT);
fmb.setHardTimeout(LearningSwitch.FLOWMOD_DEFAULT_HARD_TIMEOUT);
fmb.setPriority(LearningSwitch.FLOWMOD_PRIORITY);
fmb.setBufferId(bufferId);
fmb.setOutPort((command == OFFlowModCommand.DELETE) ? OFPort.ANY : outPort);
Set<OFFlowModFlags> sfmf = new HashSet<OFFlowModFlags>();
if (command != OFFlowModCommand.DELETE) {
sfmf.add(OFFlowModFlags.SEND_FLOW_REM);
}
fmb.setFlags(sfmf);
// 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
List<OFAction> al = new ArrayList<OFAction>();
al.add(sw.getOFFactory().actions().buildOutput().setPort(outPort).setMaxLen(Integer.MAX_VALUE).build());
fmb.setActions(al);
if (log.isTraceEnabled()) {
log.trace("{} {} flow mod {}",
new Object[]{ sw, (command == OFFlowModCommand.DELETE) ? "deleting" : "adding", fmb.build() });
}
counterFlowMod.increment();
// and write it out
sw.write(fmb.build());
}
/**
* 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, Match match, OFPacketIn pi, OFPort outport) {
if (pi == null) {
return;
}
OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT));
// 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 (inPort.equals(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.Builder pob = sw.getOFFactory().buildPacketOut();
// set actions
List<OFAction> actions = new ArrayList<OFAction>();
actions.add(sw.getOFFactory().actions().buildOutput().setPort(outport).setMaxLen(Integer.MAX_VALUE).build());
pob.setActions(actions);
// 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 = pi.createBuilder().setBufferId(OFBufferId.NO_BUFFER).build();
pob.setBufferId(OFBufferId.NO_BUFFER);
} else {
pob.setBufferId(pi.getBufferId());
}
pob.setInPort(inPort);
// If the buffer id is none or the switch doesn's support buffering
// we send the data with the packet out
if (pi.getBufferId() == OFBufferId.NO_BUFFER) {
byte[] packetData = pi.getData();
pob.setData(packetData);
}
counterPacketOut.increment();
sw.write(pob.build());
}
/**
* 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, OFPort 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.Builder pob = sw.getOFFactory().buildPacketOut();
// Set buffer_id, in_port, actions_len
pob.setBufferId(packetInMessage.getBufferId());
pob.setInPort(packetInMessage.getVersion().compareTo(OFVersion.OF_12) < 0 ? packetInMessage.getInPort() : packetInMessage.getMatch().get(MatchField.IN_PORT));
// set actions
List<OFAction> actions = new ArrayList<OFAction>(1);
actions.add(sw.getOFFactory().actions().buildOutput().setPort(egressPort).setMaxLen(Integer.MAX_VALUE).build());
pob.setActions(actions);
// set data - only if buffer_id == -1
if (packetInMessage.getBufferId() == OFBufferId.NO_BUFFER) {
byte[] packetData = packetInMessage.getData();
pob.setData(packetData);
}
// and write it out
counterPacketOut.increment();
sw.write(pob.build());
}
/**
* 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
Match m = pi.getMatch();
OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT));
MacAddress sourceMac = m.get(MatchField.ETH_SRC);
MacAddress destMac = m.get(MatchField.ETH_DST);
OFVlanVidMatch vlan = m.get(MatchField.VLAN_VID);
if (sourceMac == null) {
sourceMac = MacAddress.NONE;
}
if (destMac == null) {
destMac = MacAddress.NONE;
}
if (vlan == null) {
vlan = OFVlanVidMatch.UNTAGGED;
}
if ((destMac.getLong() & 0xfffffffffff0L) == 0x0180c2000000L) {
if (log.isTraceEnabled()) {
log.trace("ignoring packet addressed to 802.1D/Q reserved addr: switch {} vlan {} dest MAC {}",
new Object[]{ sw, vlan, destMac.toString() });
}
return Command.STOP;
}
if ((sourceMac.getLong() & 0x010000000000L) == 0) {
// If source MAC is a unicast address, learn the port for this MAC/VLAN
this.addToPortMap(sw, sourceMac, vlan.getVlanVid(), inPort);
}
// Now output flow-mod and/or packet
OFPort outPort = getFromPortMap(sw, destMac, vlan.getVlanVid());
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.FLOOD);
} else if (outPort.equals(inPort)) {
log.trace("ignoring packet that arrived on same port as learned destination:"
+ " switch {} vlan {} dest MAC {} port {}",
new Object[]{ sw, vlan, destMac.toString(), outPort.getPortNumber() });
} 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
// We write FlowMods with Buffer ID none then explicitly PacketOut the buffered packet
this.pushPacket(sw, m, pi, outPort);
this.writeFlowMod(sw, OFFlowModCommand.ADD, OFBufferId.NO_BUFFER, m, outPort);
if (LEARNING_SWITCH_REVERSE_FLOW) {
Match.Builder mb2 = m.createBuilder();
mb2.setExact(MatchField.ETH_SRC, m.get(MatchField.ETH_DST))
.setExact(MatchField.ETH_DST, m.get(MatchField.ETH_SRC))
.setExact(MatchField.VLAN_VID, m.get(MatchField.VLAN_VID))
.setExact(MatchField.ETH_TYPE, m.get(MatchField.ETH_TYPE))
.setExact(MatchField.IPV4_SRC, m.get(MatchField.IPV4_DST))
.setExact(MatchField.IPV4_DST, m.get(MatchField.IPV4_SRC));
if (m.get(MatchField.IP_PROTO).equals(IpProtocol.TCP)) {
mb2.setExact(MatchField.IP_PROTO, IpProtocol.TCP)
.setExact(MatchField.TCP_SRC, m.get(MatchField.TCP_DST))
.setExact(MatchField.TCP_DST, m.get(MatchField.TCP_SRC));
} else if (m.get(MatchField.IP_PROTO).equals(IpProtocol.UDP)) {
mb2.setExact(MatchField.IP_PROTO, IpProtocol.UDP)
.setExact(MatchField.UDP_SRC, m.get(MatchField.UDP_DST))
.setExact(MatchField.UDP_DST, m.get(MatchField.UDP_SRC));
} else if (m.get(MatchField.IP_PROTO).equals(IpProtocol.SCTP)) {
mb2.setExact(MatchField.IP_PROTO, IpProtocol.SCTP)
.setExact(MatchField.SCTP_SRC, m.get(MatchField.SCTP_DST))
.setExact(MatchField.SCTP_DST, m.get(MatchField.SCTP_SRC));
} else {
log.debug("In writing reverse LS flow, could not determine L4 proto (was int " + m.get(MatchField.IP_PROTO).getIpProtocolNumber() + ")");
}
mb2.setExact(MatchField.IN_PORT, outPort);
this.writeFlowMod(sw, OFFlowModCommand.ADD, OFBufferId.NO_BUFFER, mb2.build(), inPort);
}
}
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().equals(U64.of(LearningSwitch.LEARNING_SWITCH_COOKIE))) {
return Command.CONTINUE;
}
if (log.isTraceEnabled()) {
log.trace("{} flow entry removed {}", sw, flowRemovedMessage);
}
Match 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, match.get(MatchField.ETH_SRC), match.get(MatchField.VLAN_VID).getVlanVid());
// 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.
Match.Builder mb = match.createBuilder();
mb.setExact(MatchField.ETH_SRC, match.get(MatchField.ETH_DST))
.setExact(MatchField.ETH_DST, match.get(MatchField.ETH_SRC))
.setExact(MatchField.IPV4_SRC, match.get(MatchField.IPV4_DST))
.setExact(MatchField.IPV4_DST, match.get(MatchField.IPV4_SRC));
if (match.get(MatchField.IP_PROTO).equals(IpProtocol.TCP)) {
mb.setExact(MatchField.IP_PROTO, IpProtocol.TCP)
.setExact(MatchField.TCP_SRC, match.get(MatchField.TCP_DST))
.setExact(MatchField.TCP_DST, match.get(MatchField.TCP_SRC));
} else if (match.get(MatchField.IP_PROTO).equals(IpProtocol.UDP)) {
mb.setExact(MatchField.IP_PROTO, IpProtocol.UDP)
.setExact(MatchField.UDP_SRC, match.get(MatchField.UDP_DST))
.setExact(MatchField.UDP_DST, match.get(MatchField.UDP_SRC));
} else if (match.get(MatchField.IP_PROTO).equals(IpProtocol.SCTP)) {
mb.setExact(MatchField.IP_PROTO, IpProtocol.SCTP)
.setExact(MatchField.SCTP_SRC, match.get(MatchField.SCTP_DST))
.setExact(MatchField.SCTP_DST, match.get(MatchField.SCTP_SRC));
} else {
log.debug("In writing reverse LS flow, could not determine L4 proto (was int " + mb.get(MatchField.IP_PROTO).getIpProtocolNumber() + ")");
}
this.writeFlowMod(sw, OFFlowModCommand.DELETE, OFBufferId.NO_BUFFER, mb.build(), match.get(MatchField.IN_PORT));
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(IDebugCounterService.class);
l.add(IRestApiService.class);
return l;
}
@Override
public void init(FloodlightModuleContext context) throws FloodlightModuleException {
macVlanToSwitchPortMap = new ConcurrentHashMap<IOFSwitch, Map<MacVlanPair, OFPort>>();
floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class);
debugCounterService = context.getServiceImpl(IDebugCounterService.class);
restApiService = context.getServiceImpl(IRestApiService.class);
}
@Override
public void startUp(FloodlightModuleContext context) {
floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this);
floodlightProviderService.addOFMessageListener(OFType.FLOW_REMOVED, this);
floodlightProviderService.addOFMessageListener(OFType.ERROR, this);
restApiService.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);
debugCounterService.registerModule(this.getName());
counterFlowMod = debugCounterService.registerCounter(this.getName(), "flow-mods-written", "Flow mods written to switches by LearningSwitch", MetaData.WARN);
counterPacketOut = debugCounterService.registerCounter(this.getName(), "packet-outs-written", "Packet outs written to switches by LearningSwitch", MetaData.WARN);
}
implements IFloodlightModule, ILearningSwitchService, IOFMessageListener {
protected static Logger log = LoggerFactory.getLogger(LearningSwitch.class);
// Module dependencies
protected IFloodlightProviderService floodlightProviderService;
protected IRestApiService restApiService;
protected IDebugCounterService debugCounterService;
private IDebugCounter counterFlowMod;
private IDebugCounter counterPacketOut;
// Stores the learned state for each switch
protected Map<IOFSwitch, Map<MacVlanPair, OFPort>> 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 floodlightProviderService) {
this.floodlightProviderService = floodlightProviderService;
}
@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, MacAddress mac, VlanVid vlan, OFPort portVal) {
Map<MacVlanPair, OFPort> swMap = macVlanToSwitchPortMap.get(sw);
if (vlan == VlanVid.FULL_MASK || vlan == null) {
vlan = VlanVid.ofVlan(0);
}
if (swMap == null) {
// May be accessed by REST API so we need to make it thread safe
swMap = Collections.synchronizedMap(new LRULinkedHashMap<MacVlanPair, OFPort>(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, MacAddress mac, VlanVid vlan) {
if (vlan == VlanVid.FULL_MASK) {
vlan = VlanVid.ofVlan(0);
}
Map<MacVlanPair, OFPort> 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 OFPort getFromPortMap(IOFSwitch sw, MacAddress mac, VlanVid vlan) {
if (vlan == VlanVid.FULL_MASK || vlan == null) {
vlan = VlanVid.ofVlan(0);
}
Map<MacVlanPair, OFPort> 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, OFPort> swMap = macVlanToSwitchPortMap.get(sw);
if (swMap != null) {
swMap.clear();
}
}
@Override
public synchronized Map<IOFSwitch, Map<MacVlanPair, OFPort>> 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, OFFlowModCommand command, OFBufferId bufferId,
Match match, OFPort 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.Builder fmb;
if (command == OFFlowModCommand.DELETE) {
fmb = sw.getOFFactory().buildFlowDelete();
} else {
fmb = sw.getOFFactory().buildFlowAdd();
}
fmb.setMatch(match);
fmb.setCookie((U64.of(LearningSwitch.LEARNING_SWITCH_COOKIE)));
fmb.setIdleTimeout(LearningSwitch.FLOWMOD_DEFAULT_IDLE_TIMEOUT);
fmb.setHardTimeout(LearningSwitch.FLOWMOD_DEFAULT_HARD_TIMEOUT);
fmb.setPriority(LearningSwitch.FLOWMOD_PRIORITY);
fmb.setBufferId(bufferId);
fmb.setOutPort((command == OFFlowModCommand.DELETE) ? OFPort.ANY : outPort);
Set<OFFlowModFlags> sfmf = new HashSet<OFFlowModFlags>();
if (command != OFFlowModCommand.DELETE) {
sfmf.add(OFFlowModFlags.SEND_FLOW_REM);
}
fmb.setFlags(sfmf);
// 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
List<OFAction> al = new ArrayList<OFAction>();
al.add(sw.getOFFactory().actions().buildOutput().setPort(outPort).setMaxLen(0xffFFffFF).build());
fmb.setActions(al);
if (log.isTraceEnabled()) {
log.trace("{} {} flow mod {}",
new Object[]{ sw, (command == OFFlowModCommand.DELETE) ? "deleting" : "adding", fmb.build() });
}
counterFlowMod.increment();
// and write it out
sw.write(fmb.build());
}
/**
* 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, Match match, OFPacketIn pi, OFPort outport) {
if (pi == null) {
return;
}
OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT));
// 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 (inPort.equals(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.Builder pob = sw.getOFFactory().buildPacketOut();
// set actions
List<OFAction> actions = new ArrayList<OFAction>();
actions.add(sw.getOFFactory().actions().buildOutput().setPort(outport).setMaxLen(0xffFFffFF).build());
pob.setActions(actions);
// 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 = pi.createBuilder().setBufferId(OFBufferId.NO_BUFFER).build();
pob.setBufferId(OFBufferId.NO_BUFFER);
} else {
pob.setBufferId(pi.getBufferId());
}
pob.setInPort(inPort);
// If the buffer id is none or the switch doesn's support buffering
// we send the data with the packet out
if (pi.getBufferId() == OFBufferId.NO_BUFFER) {
byte[] packetData = pi.getData();
pob.setData(packetData);
}
counterPacketOut.increment();
sw.write(pob.build());
}
/**
* 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, OFPort egressPort) {
OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut();
// Set buffer_id, in_port, actions_len
pob.setBufferId(packetInMessage.getBufferId());
pob.setInPort(packetInMessage.getVersion().compareTo(OFVersion.OF_12) < 0 ? packetInMessage.getInPort() : packetInMessage.getMatch().get(MatchField.IN_PORT));
// set actions
List<OFAction> actions = new ArrayList<OFAction>(1);
actions.add(sw.getOFFactory().actions().buildOutput().setPort(egressPort).setMaxLen(0xffFFffFF).build());
pob.setActions(actions);
// set data - only if buffer_id == -1
if (packetInMessage.getBufferId() == OFBufferId.NO_BUFFER) {
byte[] packetData = packetInMessage.getData();
pob.setData(packetData);
}
// and write it out
counterPacketOut.increment();
sw.write(pob.build());
}
protected Match createMatchFromPacket(IOFSwitch sw, OFPort inPort, FloodlightContext cntx) {
// The packet in match will only contain the port number.
// We need to add in specifics for the hosts we're routing between.
Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
VlanVid vlan = VlanVid.ofVlan(eth.getVlanID());
MacAddress srcMac = eth.getSourceMACAddress();
MacAddress dstMac = eth.getDestinationMACAddress();
Match.Builder mb = sw.getOFFactory().buildMatch();
mb.setExact(MatchField.IN_PORT, inPort)
.setExact(MatchField.ETH_SRC, srcMac)
.setExact(MatchField.ETH_DST, dstMac);
if (!vlan.equals(VlanVid.ZERO)) {
mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlanVid(vlan));
}
return mb.build();
}
/**
* 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) {
OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT));
/* Read packet header attributes into Match */
Match m = createMatchFromPacket(sw, inPort, cntx);
MacAddress sourceMac = m.get(MatchField.ETH_SRC);
MacAddress destMac = m.get(MatchField.ETH_DST);
VlanVid vlan = m.get(MatchField.VLAN_VID) == null ? VlanVid.ZERO : m.get(MatchField.VLAN_VID).getVlanVid();
if (sourceMac == null) {
sourceMac = MacAddress.NONE;
}
if (destMac == null) {
destMac = MacAddress.NONE;
}
if (vlan == null) {
vlan = VlanVid.ZERO;
}
if ((destMac.getLong() & 0xfffffffffff0L) == 0x0180c2000000L) {
if (log.isTraceEnabled()) {
log.trace("ignoring packet addressed to 802.1D/Q reserved addr: switch {} vlan {} dest MAC {}",
new Object[]{ sw, vlan, destMac.toString() });
}
return Command.STOP;
}
if ((sourceMac.getLong() & 0x010000000000L) == 0) {
// If source MAC is a unicast address, learn the port for this MAC/VLAN
this.addToPortMap(sw, sourceMac, vlan, inPort);
}
// Now output flow-mod and/or packet
OFPort 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.FLOOD);
} else if (outPort.equals(inPort)) {
log.trace("ignoring packet that arrived on same port as learned destination:"
+ " switch {} vlan {} dest MAC {} port {}",
new Object[]{ sw, vlan, destMac.toString(), outPort.getPortNumber() });
} 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
// We write FlowMods with Buffer ID none then explicitly PacketOut the buffered packet
this.pushPacket(sw, m, pi, outPort);
this.writeFlowMod(sw, OFFlowModCommand.ADD, OFBufferId.NO_BUFFER, m, outPort);
if (LEARNING_SWITCH_REVERSE_FLOW) {
Match.Builder mb = m.createBuilder();
mb.setExact(MatchField.ETH_SRC, m.get(MatchField.ETH_DST))
.setExact(MatchField.ETH_DST, m.get(MatchField.ETH_SRC))
.setExact(MatchField.IN_PORT, outPort);
if (m.get(MatchField.VLAN_VID) != null) {
mb.setExact(MatchField.VLAN_VID, m.get(MatchField.VLAN_VID));
}
this.writeFlowMod(sw, OFFlowModCommand.ADD, OFBufferId.NO_BUFFER, mb.build(), inPort);
}
}
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().equals(U64.of(LearningSwitch.LEARNING_SWITCH_COOKIE))) {
return Command.CONTINUE;
}
if (log.isTraceEnabled()) {
log.trace("{} flow entry removed {}", sw, flowRemovedMessage);
}
Match 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, match.get(MatchField.ETH_SRC),
match.get(MatchField.VLAN_VID) == null
? VlanVid.ZERO
: match.get(MatchField.VLAN_VID).getVlanVid());
// 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.
Match.Builder mb = sw.getOFFactory().buildMatch();
mb.setExact(MatchField.ETH_SRC, match.get(MatchField.ETH_DST))
.setExact(MatchField.ETH_DST, match.get(MatchField.ETH_SRC));
if (match.get(MatchField.VLAN_VID) != null) {
mb.setExact(MatchField.VLAN_VID, match.get(MatchField.VLAN_VID));
}
this.writeFlowMod(sw, OFFlowModCommand.DELETE, OFBufferId.NO_BUFFER, mb.build(), match.get(MatchField.IN_PORT));
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:
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(IDebugCounterService.class);
l.add(IRestApiService.class);
return l;
}
@Override
public void init(FloodlightModuleContext context) throws FloodlightModuleException {
macVlanToSwitchPortMap = new ConcurrentHashMap<IOFSwitch, Map<MacVlanPair, OFPort>>();
floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class);
debugCounterService = context.getServiceImpl(IDebugCounterService.class);
restApiService = context.getServiceImpl(IRestApiService.class);
}
@Override
public void startUp(FloodlightModuleContext context) {
floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this);
floodlightProviderService.addOFMessageListener(OFType.FLOW_REMOVED, this);
floodlightProviderService.addOFMessageListener(OFType.ERROR, this);
restApiService.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);
debugCounterService.registerModule(this.getName());
counterFlowMod = debugCounterService.registerCounter(this.getName(), "flow-mods-written", "Flow mods written to switches by LearningSwitch", MetaData.WARN);
counterPacketOut = debugCounterService.registerCounter(this.getName(), "packet-outs-written", "Packet outs written to switches by LearningSwitch", MetaData.WARN);
}
}
......@@ -65,7 +65,7 @@ public class StaticFlowEntryPusherResource extends ServerResource {
int eth_type = -1;
int nw_protocol = -1;
int icmp_type = -1;
//Determine the dl_type if set
if (rows.containsKey(StaticFlowEntryPusher.COLUMN_DL_TYPE)) {
if (((String) rows.get(StaticFlowEntryPusher.COLUMN_DL_TYPE)).startsWith("0x")) {
......
......@@ -26,3 +26,4 @@ net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl
net.floodlightcontroller.firewall.Firewall
net.floodlightcontroller.accesscontrollist.ACL
net.floodlightcontroller.dhcpserver.DHCPServer
net.floodlightcontroller.learningswitch.LearningSwitch
\ No newline at end of file
......@@ -19,6 +19,6 @@
<logger name="net.floodlightcontroller.forwarding" level="INFO"></logger>
<logger name="net.floodlightcontroller.routing" level="INFO"></logger>
<logger name="net.floodlightcontroller.core.internal" level="INFO"></logger>
<logger level="INFO" name="net.floodlightcontroller.firewall"></logger>
<logger level="INFO" name="net.floodlightcontroller.learningswitch"></logger>
<logger level="INFO" name="net.floodlightcontroller.staticflowentry"></logger>
</configuration>
......@@ -60,13 +60,10 @@ import org.projectfloodlight.openflow.protocol.OFPacketOut;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.EthType;
import org.projectfloodlight.openflow.types.IPv4Address;
import org.projectfloodlight.openflow.types.IpProtocol;
import org.projectfloodlight.openflow.types.MacAddress;
import org.projectfloodlight.openflow.types.OFBufferId;
import org.projectfloodlight.openflow.types.OFPort;
import org.projectfloodlight.openflow.types.OFVlanVidMatch;
import org.projectfloodlight.openflow.types.TransportPort;
import org.projectfloodlight.openflow.types.U64;
import org.projectfloodlight.openflow.types.VlanVid;
import org.projectfloodlight.openflow.protocol.OFType;
......@@ -149,15 +146,6 @@ public class LearningSwitchTest extends FloodlightTestCase {
this.packetIn = factory.buildPacketIn()
.setMatch(factory.buildMatch()
.setExact(MatchField.IN_PORT, OFPort.of(1))
.setExact(MatchField.ETH_SRC, MacAddress.of("00:44:33:22:11:00"))
.setExact(MatchField.ETH_DST, MacAddress.of("00:11:22:33:44:55"))
.setExact(MatchField.ETH_TYPE, EthType.IPv4)
.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlan(42))
.setExact(MatchField.IPV4_SRC, IPv4Address.of("192.168.1.1"))
.setExact(MatchField.IPV4_DST, IPv4Address.of("192.168.1.2"))
.setExact(MatchField.IP_PROTO, IpProtocol.UDP)
.setExact(MatchField.UDP_SRC, TransportPort.of(5000))
.setExact(MatchField.UDP_DST, TransportPort.of(5001))
.build()
)
.setBufferId(OFBufferId.NO_BUFFER)
......@@ -191,7 +179,7 @@ public class LearningSwitchTest extends FloodlightTestCase {
// build our expected flooded packetOut
OFPacketOut po = factory.buildPacketOut()
.setInPort(OFPort.of(1))
.setActions(Arrays.asList((OFAction)factory.actions().output(OFPort.FLOOD, Integer.MAX_VALUE)))
.setActions(Arrays.asList((OFAction)factory.actions().output(OFPort.FLOOD, 0xffFFffFF)))
.setBufferId(OFBufferId.NO_BUFFER)
.setData(this.testPacketSerialized)
.build();
......@@ -236,30 +224,29 @@ public class LearningSwitchTest extends FloodlightTestCase {
flags.add(OFFlowModFlags.SEND_FLOW_REM);
// build expected flow mods
OFFlowAdd fm1 = factory.buildFlowAdd()
.setActions(Arrays.asList((OFAction)factory.actions().output(OFPort.of(2), Integer.MAX_VALUE)))
.setActions(Arrays.asList((OFAction)factory.actions().output(OFPort.of(2), 0xffFFffFF)))
.setBufferId(OFBufferId.NO_BUFFER)
.setIdleTimeout((short) 5)
.setMatch(packetIn.getMatch())
.setMatch(factory.buildMatch()
.setExact(MatchField.IN_PORT, OFPort.of(1))
.setExact(MatchField.ETH_SRC, MacAddress.of("00:44:33:22:11:00"))
.setExact(MatchField.ETH_DST, MacAddress.of("00:11:22:33:44:55"))
.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlan(42))
.build())
.setOutPort(OFPort.of(2))
.setCookie(U64.of(1L << 52))
.setPriority((short) 100)
.setFlags(flags)
.build();
OFFlowAdd fm2 = factory.buildFlowAdd()
.setActions(Arrays.asList((OFAction)factory.actions().output(OFPort.of(1), Integer.MAX_VALUE)))
.setActions(Arrays.asList((OFAction)factory.actions().output(OFPort.of(1), 0xffFFffFF)))
.setBufferId(OFBufferId.NO_BUFFER)
.setIdleTimeout((short) 5)
.setMatch(factory.buildMatch()
.setExact(MatchField.IN_PORT, OFPort.of(2))
.setExact(MatchField.ETH_DST, MacAddress.of("00:44:33:22:11:00"))
.setExact(MatchField.ETH_SRC, MacAddress.of("00:11:22:33:44:55"))
.setExact(MatchField.ETH_TYPE, EthType.IPv4)
.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlan(42))
.setExact(MatchField.IPV4_DST, IPv4Address.of("192.168.1.1"))
.setExact(MatchField.IPV4_SRC, IPv4Address.of("192.168.1.2"))
.setExact(MatchField.IP_PROTO, IpProtocol.UDP)
.setExact(MatchField.UDP_DST, TransportPort.of(5000))
.setExact(MatchField.UDP_SRC, TransportPort.of(5001))
.build()
)
.setOutPort(OFPort.of(1))
......@@ -268,7 +255,7 @@ public class LearningSwitchTest extends FloodlightTestCase {
.setPriority((short) 100)
.build();
OFActionOutput ofAcOut = factory.actions().output(OFPort.of(2), Integer.MAX_VALUE);
OFActionOutput ofAcOut = factory.actions().output(OFPort.of(2), 0xffFFffFF);
OFPacketOut packetOut = factory.buildPacketOut()
.setActions(Arrays.asList((OFAction)ofAcOut))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment