From 089df5c642e779fc1359f258f5c8321d08ab2324 Mon Sep 17 00:00:00 2001
From: Ryan Izard <rizard@g.clemson.edu>
Date: Fri, 17 Apr 2015 15:40:51 -0400
Subject: [PATCH] Finally got around to fixing LearningSwitch.

---
 .../learningswitch/LearningSwitch.java        | 1148 ++++++++---------
 .../web/StaticFlowEntryPusherResource.java    |    2 +-
 ...htcontroller.core.module.IFloodlightModule |    1 +
 src/main/resources/logback-test.xml           |    2 +-
 .../learningswitch/LearningSwitchTest.java    |   33 +-
 5 files changed, 578 insertions(+), 608 deletions(-)

diff --git a/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java b/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java
index b2169e196..bbc1a0104 100644
--- a/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java
+++ b/src/main/java/net/floodlightcontroller/learningswitch/LearningSwitch.java
@@ -1,19 +1,19 @@
 /**
-*    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);
+	}
 }
diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryPusherResource.java b/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryPusherResource.java
index 1f953b0e0..cfce15d2b 100644
--- a/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryPusherResource.java
+++ b/src/main/java/net/floodlightcontroller/staticflowentry/web/StaticFlowEntryPusherResource.java
@@ -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")) {
diff --git a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
index 803441cf8..e11880981 100644
--- a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
+++ b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule
@@ -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
diff --git a/src/main/resources/logback-test.xml b/src/main/resources/logback-test.xml
index 656805f36..7c9e3ea4d 100644
--- a/src/main/resources/logback-test.xml
+++ b/src/main/resources/logback-test.xml
@@ -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>
diff --git a/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java b/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java
index fa21c208d..b2754b2ea 100644
--- a/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java
+++ b/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java
@@ -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))
-- 
GitLab