diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java b/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java index 4e2ffb1dbe11c3ce4d89fa110adbc42287c611e7..1ad6f4320866479f5f0ef3dcfb1001d3ef9e3c86 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/ILinkDiscoveryService.java @@ -1,7 +1,7 @@ /** -* Copyright 2011, Big Switch Networks, Inc. +* 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 @@ -57,7 +57,7 @@ public interface ILinkDiscoveryService extends IFloodlightService { boolean isReverse); /** - * Returns an unmodifiable map from switch id to a set of all links with it + * Returns an unmodifiable map from switch id to a set of all links with it * as an endpoint. */ public Map<Long, Set<Link>> getSwitchLinks(); @@ -74,7 +74,8 @@ public interface ILinkDiscoveryService extends IFloodlightService { public Set<NodePortTuple> getSuppressLLDPsInfo(); /** - * Adds a switch port to suppress lldp set + * Adds a switch port to suppress lldp set. LLDPs and BDDPs will not be sent + * out, and if any are received on this port then they will be dropped. */ public void AddToSuppressLLDPs(long sw, short port); @@ -105,8 +106,13 @@ public interface ILinkDiscoveryService extends IFloodlightService { public Map<NodePortTuple, Set<Link>> getPortLinks(); /** + * addMACToIgnoreList is a service provided by LinkDiscovery to ignore + * certain packets early in the packet-in processing chain. Since LinkDiscovery + * is first in the packet-in processing pipeline, it can efficiently drop these + * packets. Currently these packets are identified only by their source MAC address. + * * Add a MAC address range to ignore list. All packet ins from this range - * will be dropped + * will be dropped - use with care! * @param mac The base MAC address that is to be ignored * @param ignoreBits The number of LSBs to ignore. A value of 0 will add * only one MAC address 'mac' to ignore list. A value of 48 will add diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java index d2060e726443d0f9cd495cd175c3ab8bf7aeecd3..b0bdba0356210b0e49e758186e4c6bce58e722c3 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java @@ -143,7 +143,8 @@ public class LinkDiscoveryManager implements IOFMessageListener, protected IRestApiService restApi; // LLDP and BDDP fields - private static final byte[] LLDP_STANDARD_DST_MAC_STRING = HexString.fromHexString("01:80:c2:00:00:0e"); + private static final byte[] LLDP_STANDARD_DST_MAC_STRING = + HexString.fromHexString("01:80:c2:00:00:0e"); private static final long LINK_LOCAL_MASK = 0xfffffffffff0L; private static final long LINK_LOCAL_VALUE = 0x0180c2000000L; protected static int EVENT_HISTORY_SIZE = 1024; // in seconds @@ -159,12 +160,12 @@ public class LinkDiscoveryManager implements IOFMessageListener, private static final short TLV_DIRECTION_LENGTH = 1; // 1 byte private static final byte TLV_DIRECTION_VALUE_FORWARD[] = { 0x01 }; private static final byte TLV_DIRECTION_VALUE_REVERSE[] = { 0x02 }; - private static final LLDPTLV forwardTLV = new LLDPTLV().setType((byte) TLV_DIRECTION_TYPE) - .setLength((short) TLV_DIRECTION_LENGTH) + private static final LLDPTLV forwardTLV = new LLDPTLV().setType(TLV_DIRECTION_TYPE) + .setLength(TLV_DIRECTION_LENGTH) .setValue(TLV_DIRECTION_VALUE_FORWARD); - private static final LLDPTLV reverseTLV = new LLDPTLV().setType((byte) TLV_DIRECTION_TYPE) - .setLength((short) TLV_DIRECTION_LENGTH) + private static final LLDPTLV reverseTLV = new LLDPTLV().setType(TLV_DIRECTION_TYPE) + .setLength(TLV_DIRECTION_LENGTH) .setValue(TLV_DIRECTION_VALUE_REVERSE); // Link discovery task details. @@ -252,9 +253,123 @@ public class LinkDiscoveryManager implements IOFMessageListener, } protected Set<MACRange> ignoreMACSet; + //********************* + // ILinkDiscoveryService + //********************* + + @Override + public OFPacketOut generateLLDPMessage(long sw, short port, + boolean isStandard, boolean isReverse) { + + IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); + OFPhysicalPort ofpPort = iofSwitch.getPort(port); + + if (log.isTraceEnabled()) { + log.trace("Sending LLDP packet out of swich: {}, port: {}", + HexString.toHexString(sw), port); + } + + // using "nearest customer bridge" MAC address for broadest possible + // propagation + // through provider and TPMR bridges (see IEEE 802.1AB-2009 and + // 802.1Q-2011), + // in particular the Linux bridge which behaves mostly like a provider + // bridge + byte[] chassisId = new byte[] { 4, 0, 0, 0, 0, 0, 0 }; // filled in + // later + byte[] portId = new byte[] { 2, 0, 0 }; // filled in later + byte[] ttlValue = new byte[] { 0, 0x78 }; + // OpenFlow OUI - 00-26-E1 + byte[] dpidTLVValue = new byte[] { 0x0, 0x26, (byte) 0xe1, 0, 0, 0, + 0, 0, 0, 0, 0, 0 }; + LLDPTLV dpidTLV = new LLDPTLV().setType((byte) 127) + .setLength((short) dpidTLVValue.length) + .setValue(dpidTLVValue); + + byte[] dpidArray = new byte[8]; + ByteBuffer dpidBB = ByteBuffer.wrap(dpidArray); + ByteBuffer portBB = ByteBuffer.wrap(portId, 1, 2); + + Long dpid = sw; + dpidBB.putLong(dpid); + // set the chassis id's value to last 6 bytes of dpid + System.arraycopy(dpidArray, 2, chassisId, 1, 6); + // set the optional tlv to the full dpid + System.arraycopy(dpidArray, 0, dpidTLVValue, 4, 8); + + // TODO: Consider remove this block of code. + // It's evil to overwrite port object. The the old code always + // overwrote mac address, we now only overwrite zero macs and + // log a warning, mostly for paranoia. + byte[] srcMac = ofpPort.getHardwareAddress(); + byte[] zeroMac = { 0, 0, 0, 0, 0, 0 }; + if (Arrays.equals(srcMac, zeroMac)) { + log.warn("Port {}/{} has zero hareware address" + + "overwrite with lower 6 bytes of dpid", + HexString.toHexString(dpid), ofpPort.getPortNumber()); + System.arraycopy(dpidArray, 2, srcMac, 0, 6); + } + + // set the portId to the outgoing port + portBB.putShort(port); + if (log.isTraceEnabled()) { + log.trace("Sending LLDP out of interface: {}/{}", + HexString.toHexString(sw), port); + } + + LLDP lldp = new LLDP(); + lldp.setChassisId(new LLDPTLV().setType((byte) 1) + .setLength((short) chassisId.length) + .setValue(chassisId)); + lldp.setPortId(new LLDPTLV().setType((byte) 2) + .setLength((short) portId.length) + .setValue(portId)); + lldp.setTtl(new LLDPTLV().setType((byte) 3) + .setLength((short) ttlValue.length) + .setValue(ttlValue)); + lldp.getOptionalTLVList().add(dpidTLV); + + // Add the controller identifier to the TLV value. + lldp.getOptionalTLVList().add(controllerTLV); + if (isReverse) { + lldp.getOptionalTLVList().add(reverseTLV); + } else { + lldp.getOptionalTLVList().add(forwardTLV); + } + + Ethernet ethernet; + if (isStandard) { + ethernet = new Ethernet().setSourceMACAddress(ofpPort.getHardwareAddress()) + .setDestinationMACAddress(LLDP_STANDARD_DST_MAC_STRING) + .setEtherType(Ethernet.TYPE_LLDP); + ethernet.setPayload(lldp); + } else { + BSN bsn = new BSN(BSN.BSN_TYPE_BDDP); + bsn.setPayload(lldp); + + ethernet = new Ethernet().setSourceMACAddress(ofpPort.getHardwareAddress()) + .setDestinationMACAddress(LLDP_BSN_DST_MAC_STRING) + .setEtherType(Ethernet.TYPE_BSN); + ethernet.setPayload(bsn); + } + + // serialize and wrap in a packet out + byte[] data = ethernet.serialize(); + OFPacketOut po = (OFPacketOut) floodlightProvider.getOFMessageFactory() + .getMessage(OFType.PACKET_OUT); + po.setBufferId(OFPacketOut.BUFFER_ID_NONE); + po.setInPort(OFPort.OFPP_NONE); + + // set data and data length + po.setLengthU(OFPacketOut.MINIMUM_LENGTH + data.length); + po.setPacketData(data); + + return po; + } + /** * Get the LLDP sending period in seconds. - * + * * @return LLDP sending period in seconds. */ public int getLldpFrequency() { @@ -263,17 +378,19 @@ public class LinkDiscoveryManager implements IOFMessageListener, /** * Get the LLDP timeout value in seconds - * + * * @return LLDP timeout value in seconds */ public int getLldpTimeout() { return LINK_TIMEOUT; } + @Override public Map<NodePortTuple, Set<Link>> getPortLinks() { return portLinks; } + @Override public Set<NodePortTuple> getSuppressLLDPsInfo() { return suppressLinkDiscovery; } @@ -282,6 +399,7 @@ public class LinkDiscoveryManager implements IOFMessageListener, * Add a switch port to the suppressed LLDP list. Remove any known links on * the switch port. */ + @Override public void AddToSuppressLLDPs(long sw, short port) { NodePortTuple npt = new NodePortTuple(sw, port); this.suppressLinkDiscovery.add(npt); @@ -292,6 +410,7 @@ public class LinkDiscoveryManager implements IOFMessageListener, * Remove a switch port from the suppressed LLDP list. Discover links on * that switchport. */ + @Override public void RemoveFromSuppressLLDPs(long sw, short port) { NodePortTuple npt = new NodePortTuple(sw, port); this.suppressLinkDiscovery.remove(npt); @@ -302,10 +421,12 @@ public class LinkDiscoveryManager implements IOFMessageListener, return shuttingDown; } + @Override public boolean isTunnelPort(long sw, short port) { return false; } + @Override public ILinkDiscovery.LinkType getLinkType(Link lt, LinkInfo info) { if (info.getUnicastValidTime() != null) { return ILinkDiscovery.LinkType.DIRECT_LINK; @@ -315,807 +436,892 @@ public class LinkDiscoveryManager implements IOFMessageListener, return ILinkDiscovery.LinkType.INVALID_LINK; } - @LogMessageDoc(level = "ERROR", - message = "Error in link discovery updates loop", - explanation = "An unknown error occured while dispatching " - + "link update notifications", - recommendation = LogMessageDoc.GENERIC_ACTION) - private - void doUpdatesThread() throws InterruptedException { - do { - LDUpdate update = updates.take(); - List<LDUpdate> updateList = new ArrayList<LDUpdate>(); - updateList.add(update); - - // Add all the pending updates to the list. - while (updates.peek() != null) { - updateList.add(updates.remove()); - } + @Override + public Set<Short> getQuarantinedPorts(long sw) { + Set<Short> qPorts = new HashSet<Short>(); - if (linkDiscoveryAware != null) { - if (log.isTraceEnabled()) { - log.trace("Dispatching link discovery update {} {} {} {} {} for {}", - new Object[] { - update.getOperation(), - HexString.toHexString(update.getSrc()), - update.getSrcPort(), - HexString.toHexString(update.getDst()), - update.getDstPort(), - linkDiscoveryAware }); - } - try { - for (ILinkDiscoveryListener lda : linkDiscoveryAware) { // order - // maintained - lda.linkDiscoveryUpdate(updateList); - } - } catch (Exception e) { - log.error("Error in link discovery updates loop", e); - } + Iterator<NodePortTuple> iter = quarantineQueue.iterator(); + while (iter.hasNext()) { + NodePortTuple npt = iter.next(); + if (npt.getNodeId() == sw) { + qPorts.add(npt.getPortId()); } - } while (updates.peek() != null); + } + return qPorts; } - protected boolean isLinkDiscoverySuppressed(long sw, short portNumber) { - return this.suppressLinkDiscovery.contains(new NodePortTuple(sw, - portNumber)); + @Override + public Map<Long, Set<Link>> getSwitchLinks() { + return this.switchLinks; } - protected void discoverLinks() { + @Override + public void addMACToIgnoreList(long mac, int ignoreBits) { + MACRange range = new MACRange(); + range.baseMAC = mac; + range.ignoreBits = ignoreBits; + ignoreMACSet.add(range); + } - // timeout known links. - timeoutLinks(); + @Override + public boolean isAutoPortFastFeature() { + return autoPortFastFeature; + } - // increment LLDP clock - lldpClock = (lldpClock + 1) % LLDP_TO_ALL_INTERVAL; + @Override + public void setAutoPortFastFeature(boolean autoPortFastFeature) { + this.autoPortFastFeature = autoPortFastFeature; + } - if (lldpClock == 0) { - log.debug("Sending LLDP out on all ports."); - discoverOnAllPorts(); - } + @Override + public void addListener(ILinkDiscoveryListener listener) { + linkDiscoveryAware.add(listener); } - /** - * Quarantine Ports. - */ - protected class QuarantineWorker implements Runnable { - @Override - public void run() { - try { - processBDDPLists(); - } catch (Exception e) { - log.error("Error in quarantine worker thread", e); - } finally { - bddpTask.reschedule(BDDP_TASK_INTERVAL, - TimeUnit.MILLISECONDS); - } + @Override + public Map<Link, LinkInfo> getLinks() { + lock.readLock().lock(); + Map<Link, LinkInfo> result; + try { + result = new HashMap<Link, LinkInfo>(links); + } finally { + lock.readLock().unlock(); } + return result; } - /** - * Add a switch port to the quarantine queue. Schedule the quarantine task - * if the quarantine queue was empty before adding this switch port. - * - * @param npt - */ - protected void addToQuarantineQueue(NodePortTuple npt) { - if (quarantineQueue.contains(npt) == false) - quarantineQueue.add(npt); + // currently not called + public Map<NodePortTuple, Set<Link>> getPortBroadcastDomainLinks() { + return portBroadcastDomainLinks; } - /** - * Remove a switch port from the quarantine queue. - */ - protected void removeFromQuarantineQueue(NodePortTuple npt) { - // Remove all occurrences of the node port tuple from the list. - while (quarantineQueue.remove(npt)) - ; + @Override + public String getName() { + return "linkdiscovery"; } - /** - * Add a switch port to maintenance queue. - * - * @param npt - */ - protected void addToMaintenanceQueue(NodePortTuple npt) { - // TODO We are not checking if the switch port tuple is already - // in the maintenance list or not. This will be an issue for - // really large number of switch ports in the network. - if (maintenanceQueue.contains(npt) == false) - maintenanceQueue.add(npt); - } + //********************* + // OFMessage Listener + //********************* - /** - * Remove a switch port from maintenance queue. - * - * @param npt - */ - protected void removeFromMaintenanceQueue(NodePortTuple npt) { - // Remove all occurrences of the node port tuple from the queue. - while (maintenanceQueue.remove(npt)) - ; + @Override + public Command receive(IOFSwitch sw, OFMessage msg, + FloodlightContext cntx) { + switch (msg.getType()) { + case PACKET_IN: + return this.handlePacketIn(sw.getId(), (OFPacketIn) msg, + cntx); + case PORT_STATUS: + return this.handlePortStatus(sw.getId(), (OFPortStatus) msg); + default: + break; + } + return Command.CONTINUE; } - /** - * This method processes the quarantine list in bursts. The task is at most - * once per BDDP_TASK_INTERVAL. One each call, BDDP_TASK_SIZE number of - * switch ports are processed. Once the BDDP packets are sent out through - * the switch ports, the ports are removed from the quarantine list. - */ + @Override + public boolean isCallbackOrderingPrereq(OFType type, String name) { + return false; + } - protected void processBDDPLists() { - int count = 0; - Set<NodePortTuple> nptList = new HashSet<NodePortTuple>(); + @Override + public boolean isCallbackOrderingPostreq(OFType type, String name) { + return false; + } - while (count < BDDP_TASK_SIZE && quarantineQueue.peek() != null) { - NodePortTuple npt; - npt = quarantineQueue.remove(); - sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, - false); - nptList.add(npt); - count++; - } + //*********************************** + // Internal Methods - Packet-in Processing Related + //*********************************** - count = 0; - while (count < BDDP_TASK_SIZE && maintenanceQueue.peek() != null) { - NodePortTuple npt; - npt = maintenanceQueue.remove(); - sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, - false); - count++; + protected Command handlePacketIn(long sw, OFPacketIn pi, + FloodlightContext cntx) { + Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, + IFloodlightProviderService.CONTEXT_PI_PAYLOAD); + + if (eth.getEtherType() == Ethernet.TYPE_BSN) { + BSN bsn = (BSN) eth.getPayload(); + if (bsn == null) return Command.STOP; + if (bsn.getPayload() == null) return Command.STOP; + // It could be a packet other than BSN LLDP, therefore + // continue with the regular processing. + if (bsn.getPayload() instanceof LLDP == false) + return Command.CONTINUE; + return handleLldp((LLDP) bsn.getPayload(), sw, pi.getInPort(), false, cntx); + } else if (eth.getEtherType() == Ethernet.TYPE_LLDP) { + return handleLldp((LLDP) eth.getPayload(), sw, pi.getInPort(), true, cntx); + } else if (eth.getEtherType() < 1500) { + long destMac = eth.getDestinationMAC().toLong(); + if ((destMac & LINK_LOCAL_MASK) == LINK_LOCAL_VALUE) { + if (log.isTraceEnabled()) { + log.trace("Ignoring packet addressed to 802.1D/Q " + + "reserved address."); + } + return Command.STOP; + } } - for (NodePortTuple npt : nptList) { - generateSwitchPortStatusUpdate(npt.getNodeId(), npt.getPortId()); + if (ignorePacketInFromSource(eth.getSourceMAC().toLong())) { + return Command.STOP; } - } - public Set<Short> getQuarantinedPorts(long sw) { - Set<Short> qPorts = new HashSet<Short>(); + // If packet-in is from a quarantine port, stop processing. + NodePortTuple npt = new NodePortTuple(sw, pi.getInPort()); + if (quarantineQueue.contains(npt)) return Command.STOP; - Iterator<NodePortTuple> iter = quarantineQueue.iterator(); - while (iter.hasNext()) { - NodePortTuple npt = iter.next(); - if (npt.getNodeId() == sw) { - qPorts.add(npt.getPortId()); + return Command.CONTINUE; + } + + private boolean ignorePacketInFromSource(long srcMAC) { + Iterator<MACRange> it = ignoreMACSet.iterator(); + while (it.hasNext()) { + MACRange range = it.next(); + long mask = ~0; + if (range.ignoreBits >= 0 && range.ignoreBits <= 48) { + mask = mask << range.ignoreBits; + if ((range.baseMAC & mask) == (srcMAC & mask)) { + return true; + } } } - return qPorts; + return false; } - private void generateSwitchPortStatusUpdate(long sw, short port) { - UpdateOperation operation; - + private Command handleLldp(LLDP lldp, long sw, short inPort, + boolean isStandard, FloodlightContext cntx) { + // If LLDP is suppressed on this port, ignore received packet as well IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); - if (iofSwitch == null) return; - - OFPhysicalPort ofp = iofSwitch.getPort(port); - if (ofp == null) return; - - int srcPortState = ofp.getState(); - boolean portUp = ((srcPortState & OFPortState.OFPPS_STP_MASK.getValue()) != OFPortState.OFPPS_STP_BLOCK.getValue()); - - if (portUp) - operation = UpdateOperation.PORT_UP; - else - operation = UpdateOperation.PORT_DOWN; - - updates.add(new LDUpdate(sw, port, operation)); - } - /** - * Send LLDP on known ports - */ - protected void discoverOnKnownLinkPorts() { - // Copy the port set. - Set<NodePortTuple> nptSet = new HashSet<NodePortTuple>(); - nptSet.addAll(portLinks.keySet()); + if (!isIncomingDiscoveryAllowed(sw, inPort, isStandard)) + return Command.STOP; - // Send LLDP from each of them. - for (NodePortTuple npt : nptSet) { - discover(npt); + // If this is a malformed LLDP, or not from us, exit + if (lldp.getPortId() == null || lldp.getPortId().getLength() != 3) { + return Command.CONTINUE; } - } - - protected void discover(NodePortTuple npt) { - discover(npt.getNodeId(), npt.getPortId()); - } - protected void discover(long sw, short port) { - sendDiscoveryMessage(sw, port, true, false); - } + long myId = ByteBuffer.wrap(controllerTLV.getValue()).getLong(); + long otherId = 0; + boolean myLLDP = false; + Boolean isReverse = null; - /** - * This method is used to specifically ignore/consider specific - * links. - * @param src - * @param srcPort - * @param dst - * @param dstPort - * @return - */ - protected boolean isLinkAllowed(long src, short srcPort, - long dst, short dstPort) { - return true; - } + ByteBuffer portBB = ByteBuffer.wrap(lldp.getPortId().getValue()); + portBB.position(1); - /** - * Check if incoming discovery messages are enabled or not. - * @param sw - * @param port - * @param isStandard - * @return - */ - protected boolean isIncomingDiscoveryAllowed(long sw, short port, - boolean isStandard) { + Short remotePort = portBB.getShort(); + IOFSwitch remoteSwitch = null; - if (isLinkDiscoverySuppressed(sw, port)) { - /* Do not process LLDPs from this port as suppressLLDP is set */ - return false; + // Verify this LLDP packet matches what we're looking for + for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) { + if (lldptlv.getType() == 127 && lldptlv.getLength() == 12 + && lldptlv.getValue()[0] == 0x0 + && lldptlv.getValue()[1] == 0x26 + && lldptlv.getValue()[2] == (byte) 0xe1 + && lldptlv.getValue()[3] == 0x0) { + ByteBuffer dpidBB = ByteBuffer.wrap(lldptlv.getValue()); + remoteSwitch = floodlightProvider.getSwitches() + .get(dpidBB.getLong(4)); + } else if (lldptlv.getType() == 12 && lldptlv.getLength() == 8) { + otherId = ByteBuffer.wrap(lldptlv.getValue()).getLong(); + if (myId == otherId) myLLDP = true; + } else if (lldptlv.getType() == TLV_DIRECTION_TYPE + && lldptlv.getLength() == TLV_DIRECTION_LENGTH) { + if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_FORWARD[0]) + isReverse = false; + else if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_REVERSE[0]) + isReverse = true; + } } - IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); - if (iofSwitch == null) { - return false; + if (myLLDP == false) { + // This is not the LLDP sent by this controller. + // If the LLDP message has multicast bit set, then we need to + // broadcast + // the packet as a regular packet. + if (isStandard) { + if (log.isTraceEnabled()) { + log.trace("Got a standard LLDP=[{}]. Not fowarding it.", lldp.toString()); + } + return Command.STOP; + } else if (myId < otherId) { + if (log.isTraceEnabled()) { + log.trace("Getting BDDP packets from a different controller" + + "and letting it go through normal processing chain."); + } + return Command.CONTINUE; + } } - if (port == OFPort.OFPP_LOCAL.getValue()) return false; + if (remoteSwitch == null) { + // Ignore LLDPs not generated by Floodlight, or from a switch that + // has recently + // disconnected, or from a switch connected to another Floodlight + // instance + if (log.isTraceEnabled()) { + log.trace("Received LLDP from remote switch not connected to the controller"); + } + return Command.STOP; + } - OFPhysicalPort ofpPort = iofSwitch.getPort(port); - if (ofpPort == null) { + if (!remoteSwitch.portEnabled(remotePort)) { if (log.isTraceEnabled()) { - log.trace("Null physical port. sw={}, port={}", - HexString.toHexString(sw), port); + log.trace("Ignoring link with disabled source port: switch {} port {}", + remoteSwitch.getStringId(), remotePort); } - return false; + return Command.STOP; + } + if (suppressLinkDiscovery.contains(new NodePortTuple( + remoteSwitch.getId(), + remotePort))) { + if (log.isTraceEnabled()) { + log.trace("Ignoring link with suppressed src port: switch {} port {}", + remoteSwitch.getStringId(), remotePort); + } + return Command.STOP; + } + if (!iofSwitch.portEnabled(inPort)) { + if (log.isTraceEnabled()) { + log.trace("Ignoring link with disabled dest port: switch {} port {}", + HexString.toHexString(sw), inPort); + } + return Command.STOP; } - return true; - } + OFPhysicalPort physicalPort = remoteSwitch.getPort(remotePort); + int srcPortState = (physicalPort != null) ? physicalPort.getState() + : 0; + physicalPort = iofSwitch.getPort(inPort); + int dstPortState = (physicalPort != null) ? physicalPort.getState() + : 0; - /** - * Check if outgoing discovery messages are enabled or not. - * @param sw - * @param port - * @param isStandard - * @param isReverse - * @return - */ - protected boolean isOutgoingDiscoveryAllowed(long sw, short port, - boolean isStandard, - boolean isReverse) { - - if (isLinkDiscoverySuppressed(sw, port)) { - /* Dont send LLDPs out of this port as suppressLLDP is set */ - return false; - } - - IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); - if (iofSwitch == null) { - return false; - } - - if (port == OFPort.OFPP_LOCAL.getValue()) return false; - - OFPhysicalPort ofpPort = iofSwitch.getPort(port); - if (ofpPort == null) { - if (log.isTraceEnabled()) { - log.trace("Null physical port. sw={}, port={}", - HexString.toHexString(sw), port); - } - return false; - } + // Store the time of update to this link, and push it out to + // routingEngine + Link lt = new Link(remoteSwitch.getId(), remotePort, + iofSwitch.getId(), inPort); - // For fast ports, do not send forward LLDPs or BDDPs. - if (!isReverse && autoPortFastFeature && iofSwitch.isFastPort(port)) - return false; - return true; - } + if (!isLinkAllowed(lt.getSrc(), lt.getSrcPort(), + lt.getDst(), lt.getDstPort())) + return Command.STOP; - /** - * Get the actions for packet-out corresponding to a specific port. - * This is a placeholder for adding actions if any port-specific - * actions are desired. The default action is simply to output to - * the given port. - * @param port - * @return - */ - protected List<OFAction> getDiscoveryActions (IOFSwitch sw, OFPhysicalPort port){ - // set actions - List<OFAction> actions = new ArrayList<OFAction>(); - actions.add(new OFActionOutput(port.getPortNumber(), (short) 0)); - return actions; - } + // Continue only if link is allowed. + Long lastLldpTime = null; + Long lastBddpTime = null; - public OFPacketOut generateLLDPMessage(long sw, short port, - boolean isStandard, boolean isReverse) { + Long firstSeenTime = System.currentTimeMillis(); - IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); - OFPhysicalPort ofpPort = iofSwitch.getPort(port); + if (isStandard) + lastLldpTime = System.currentTimeMillis(); + else + lastBddpTime = System.currentTimeMillis(); - if (log.isTraceEnabled()) { - log.trace("Sending LLDP packet out of swich: {}, port: {}", - HexString.toHexString(sw), port); - } + LinkInfo newLinkInfo = new LinkInfo(firstSeenTime, lastLldpTime, + lastBddpTime, srcPortState, + dstPortState); - // using "nearest customer bridge" MAC address for broadest possible - // propagation - // through provider and TPMR bridges (see IEEE 802.1AB-2009 and - // 802.1Q-2011), - // in particular the Linux bridge which behaves mostly like a provider - // bridge - byte[] chassisId = new byte[] { 4, 0, 0, 0, 0, 0, 0 }; // filled in - // later - byte[] portId = new byte[] { 2, 0, 0 }; // filled in later - byte[] ttlValue = new byte[] { 0, 0x78 }; - // OpenFlow OUI - 00-26-E1 - byte[] dpidTLVValue = new byte[] { 0x0, 0x26, (byte) 0xe1, 0, 0, 0, - 0, 0, 0, 0, 0, 0 }; - LLDPTLV dpidTLV = new LLDPTLV().setType((byte) 127) - .setLength((short) dpidTLVValue.length) - .setValue(dpidTLVValue); - byte[] dpidArray = new byte[8]; - ByteBuffer dpidBB = ByteBuffer.wrap(dpidArray); - ByteBuffer portBB = ByteBuffer.wrap(portId, 1, 2); + addOrUpdateLink(lt, newLinkInfo); - Long dpid = sw; - dpidBB.putLong(dpid); - // set the chassis id's value to last 6 bytes of dpid - System.arraycopy(dpidArray, 2, chassisId, 1, 6); - // set the optional tlv to the full dpid - System.arraycopy(dpidArray, 0, dpidTLVValue, 4, 8); + // Continue only if addOrUpdateLink was successful. - // TODO: Consider remove this block of code. - // It's evil to overwrite port object. The the old code always - // overwrote mac address, we now only overwrite zero macs and - // log a warning, mostly for paranoia. - byte[] srcMac = ofpPort.getHardwareAddress(); - byte[] zeroMac = { 0, 0, 0, 0, 0, 0 }; - if (Arrays.equals(srcMac, zeroMac)) { - log.warn("Port {}/{} has zero hareware address" - + "overwrite with lower 6 bytes of dpid", - HexString.toHexString(dpid), ofpPort.getPortNumber()); - System.arraycopy(dpidArray, 2, srcMac, 0, 6); - } + // Check if reverse link exists. + // If it doesn't exist and if the forward link was seen + // first seen within a small interval, send probe on the + // reverse link. - // set the portId to the outgoing port - portBB.putShort(port); - if (log.isTraceEnabled()) { - log.trace("Sending LLDP out of interface: {}/{}", - HexString.toHexString(sw), port); + newLinkInfo = links.get(lt); + if (newLinkInfo != null && isStandard && isReverse == false) { + Link reverseLink = new Link(lt.getDst(), lt.getDstPort(), + lt.getSrc(), lt.getSrcPort()); + LinkInfo reverseInfo = links.get(reverseLink); + if (reverseInfo == null) { + // the reverse link does not exist. + if (newLinkInfo.getFirstSeenTime() > System.currentTimeMillis() + - LINK_TIMEOUT) { + this.sendDiscoveryMessage(lt.getDst(), lt.getDstPort(), + isStandard, true); + } + } } - LLDP lldp = new LLDP(); - lldp.setChassisId(new LLDPTLV().setType((byte) 1) - .setLength((short) chassisId.length) - .setValue(chassisId)); - lldp.setPortId(new LLDPTLV().setType((byte) 2) - .setLength((short) portId.length) - .setValue(portId)); - lldp.setTtl(new LLDPTLV().setType((byte) 3) - .setLength((short) ttlValue.length) - .setValue(ttlValue)); - lldp.getOptionalTLVList().add(dpidTLV); - - // Add the controller identifier to the TLV value. - lldp.getOptionalTLVList().add(controllerTLV); - if (isReverse) { - lldp.getOptionalTLVList().add(reverseTLV); - } else { - lldp.getOptionalTLVList().add(forwardTLV); - } + // If the received packet is a BDDP packet, then create a reverse BDDP + // link as well. + if (!isStandard) { + Link reverseLink = new Link(lt.getDst(), lt.getDstPort(), + lt.getSrc(), lt.getSrcPort()); - Ethernet ethernet; - if (isStandard) { - ethernet = new Ethernet().setSourceMACAddress(ofpPort.getHardwareAddress()) - .setDestinationMACAddress(LLDP_STANDARD_DST_MAC_STRING) - .setEtherType(Ethernet.TYPE_LLDP); - ethernet.setPayload(lldp); - } else { - BSN bsn = new BSN(BSN.BSN_TYPE_BDDP); - bsn.setPayload(lldp); + // srcPortState and dstPort state are reversed. + LinkInfo reverseInfo = new LinkInfo(firstSeenTime, lastLldpTime, + lastBddpTime, dstPortState, + srcPortState); - ethernet = new Ethernet().setSourceMACAddress(ofpPort.getHardwareAddress()) - .setDestinationMACAddress(LLDP_BSN_DST_MAC_STRING) - .setEtherType(Ethernet.TYPE_BSN); - ethernet.setPayload(bsn); + addOrUpdateLink(reverseLink, reverseInfo); } - // serialize and wrap in a packet out - byte[] data = ethernet.serialize(); - OFPacketOut po = (OFPacketOut) floodlightProvider.getOFMessageFactory() - .getMessage(OFType.PACKET_OUT); - po.setBufferId(OFPacketOut.BUFFER_ID_NONE); - po.setInPort(OFPort.OFPP_NONE); - - // set data and data length - po.setLengthU(OFPacketOut.MINIMUM_LENGTH + data.length); - po.setPacketData(data); + // Remove the node ports from the quarantine and maintenance queues. + NodePortTuple nptSrc = new NodePortTuple(lt.getSrc(), + lt.getSrcPort()); + NodePortTuple nptDst = new NodePortTuple(lt.getDst(), + lt.getDstPort()); + removeFromQuarantineQueue(nptSrc); + removeFromMaintenanceQueue(nptSrc); + removeFromQuarantineQueue(nptDst); + removeFromMaintenanceQueue(nptDst); - return po; + // Consume this message + return Command.STOP; } + //*********************************** + // Internal Methods - Port Status/ New Port Processing Related + //*********************************** + /** - * Send link discovery message out of a given switch port. The discovery - * message may be a standard LLDP or a modified LLDP, where the dst mac - * address is set to :ff. TODO: The modified LLDP will updated in the future - * and may use a different eth-type. + * Handles an OFPortStatus message from a switch. We will add or delete + * LinkTupes as well re-compute the topology if needed. * * @param sw - * @param port - * @param isStandard - * indicates standard or modified LLDP - * @param isReverse - * indicates whether the LLDP was sent as a response + * The IOFSwitch that sent the port status message + * @param ps + * The OFPortStatus message + * @return The Command to continue or stop after we process this message */ - @LogMessageDoc(level = "ERROR", - message = "Failure sending LLDP out port {port} on switch {switch}", - explanation = "An I/O error occured while sending LLDP message " - + "to the switch.", - recommendation = LogMessageDoc.CHECK_SWITCH) - protected - void sendDiscoveryMessage(long sw, short port, - boolean isStandard, boolean isReverse) { - - // Takes care of all checks including null pointer checks. - if (!isOutgoingDiscoveryAllowed(sw, port, isStandard, isReverse)) - return; + protected Command handlePortStatus(long sw, OFPortStatus ps) { IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); - OFPhysicalPort ofpPort = iofSwitch.getPort(port); + if (iofSwitch == null) return Command.CONTINUE; if (log.isTraceEnabled()) { - log.trace("Sending LLDP packet out of swich: {}, port: {}", - HexString.toHexString(sw), port); - } - OFPacketOut po = generateLLDPMessage(sw, port, isStandard, isReverse); - - // Add actions - List<OFAction> actions = getDiscoveryActions(iofSwitch, ofpPort); - po.setActions(actions); - short actionLength = 0; - Iterator <OFAction> actionIter = actions.iterator(); - while (actionIter.hasNext()) { - actionLength += actionIter.next().getLength(); + log.trace("handlePortStatus: Switch {} port #{} reason {}; " + + "config is {} state is {}", + new Object[] { iofSwitch.getStringId(), + ps.getDesc().getPortNumber(), + ps.getReason(), + ps.getDesc().getConfig(), + ps.getDesc().getState() }); } - po.setActionsLength(actionLength); - // po already has the minimum length + data length set - // simply add the actions length to this. - po.setLengthU(po.getLengthU() + po.getActionsLength()); + short port = ps.getDesc().getPortNumber(); + NodePortTuple npt = new NodePortTuple(sw, port); + boolean linkDeleted = false; + boolean linkInfoChanged = false; - // send + lock.writeLock().lock(); try { - iofSwitch.write(po, null); - iofSwitch.flush(); - } catch (IOException e) { - log.error("Failure sending LLDP out port {} on switch {}", - new Object[] { port, iofSwitch.getStringId() }, e); + // if ps is a delete, or a modify where the port is down or + // configured down + if ((byte) OFPortReason.OFPPR_DELETE.ordinal() == ps.getReason() + || ((byte) OFPortReason.OFPPR_MODIFY.ordinal() == ps.getReason() && !portEnabled(ps.getDesc()))) { + deleteLinksOnPort(npt, "Port Status Changed"); + LDUpdate update = new LDUpdate(sw, port, + UpdateOperation.PORT_DOWN); + updates.add(update); + linkDeleted = true; + } else if (ps.getReason() == (byte) OFPortReason.OFPPR_MODIFY.ordinal()) { + // If ps is a port modification and the port state has changed + // that affects links in the topology + + if (this.portLinks.containsKey(npt)) { + for (Link lt : this.portLinks.get(npt)) { + LinkInfo linkInfo = links.get(lt); + assert (linkInfo != null); + Integer updatedSrcPortState = null; + Integer updatedDstPortState = null; + if (lt.getSrc() == npt.getNodeId() + && lt.getSrcPort() == npt.getPortId() + && (linkInfo.getSrcPortState() != ps.getDesc() + .getState())) { + updatedSrcPortState = ps.getDesc().getState(); + linkInfo.setSrcPortState(updatedSrcPortState); + } + if (lt.getDst() == npt.getNodeId() + && lt.getDstPort() == npt.getPortId() + && (linkInfo.getDstPortState() != ps.getDesc() + .getState())) { + updatedDstPortState = ps.getDesc().getState(); + linkInfo.setDstPortState(updatedDstPortState); + } + if ((updatedSrcPortState != null) + || (updatedDstPortState != null)) { + // The link is already known to link discovery + // manager and the status has changed, therefore + // send an LDUpdate. + UpdateOperation operation = getUpdateOperation(linkInfo.getSrcPortState(), + linkInfo.getDstPortState()); + updates.add(new LDUpdate(lt.getSrc(), + lt.getSrcPort(), + lt.getDst(), + lt.getDstPort(), + getLinkType(lt, + linkInfo), + operation)); + writeLinkToStorage(lt, linkInfo); + linkInfoChanged = true; + } + } + } + + UpdateOperation operation = getUpdateOperation(ps.getDesc() + .getState()); + updates.add(new LDUpdate(sw, port, operation)); + } + + if (!linkDeleted && !linkInfoChanged) { + if (log.isTraceEnabled()) { + log.trace("handlePortStatus: Switch {} port #{} reason {};" + + " no links to update/remove", + new Object[] { HexString.toHexString(sw), + ps.getDesc().getPortNumber(), + ps.getReason() }); + } + } + } finally { + lock.writeLock().unlock(); + } + + if (!linkDeleted) { + // Send LLDP right away when port state is changed for faster + // cluster-merge. If it is a link delete then there is not need + // to send the LLDPs right away and instead we wait for the LLDPs + // to be sent on the timer as it is normally done + // do it outside the write-lock + // sendLLDPTask.reschedule(1000, TimeUnit.MILLISECONDS); + processNewPort(npt.getNodeId(), npt.getPortId()); } + return Command.CONTINUE; } /** - * Send LLDPs to all switch-ports + * Process a new port. If link discovery is disabled on the port, then do + * nothing. If autoportfast feature is enabled and the port is a fast port, + * then do nothing. Otherwise, send LLDP message. Add the port to + * quarantine. + * + * @param sw + * @param p */ - protected void discoverOnAllPorts() { - if (log.isTraceEnabled()) { - log.trace("Sending LLDP packets out of all the enabled ports on switch {}"); + private void processNewPort(long sw, short p) { + if (isLinkDiscoverySuppressed(sw, p)) { + // Do nothing as link discovery is suppressed. + return; } - Set<Long> switches = floodlightProvider.getSwitches().keySet(); - // Send standard LLDPs - for (long sw : switches) { - IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); - if (iofSwitch == null) continue; - if (iofSwitch.getEnabledPorts() != null) { - for (OFPhysicalPort ofp : iofSwitch.getEnabledPorts()) { - if (isLinkDiscoverySuppressed(sw, ofp.getPortNumber())) - continue; - if (autoPortFastFeature - && iofSwitch.isFastPort(ofp.getPortNumber())) - continue; - // sends forward LLDP only non-fastports. - sendDiscoveryMessage(sw, ofp.getPortNumber(), true, - false); + IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); + if (iofSwitch == null) return; - // If the switch port is not alreayd in the maintenance - // queue, add it. - NodePortTuple npt = new NodePortTuple( - sw, - ofp.getPortNumber()); - addToMaintenanceQueue(npt); - } - } + if (autoPortFastFeature && iofSwitch.isFastPort(p)) { + // Do nothing as the port is a fast port. + return; + } + NodePortTuple npt = new NodePortTuple(sw, p); + discover(sw, p); + // if it is not a fast port, add it to quarantine. + if (!iofSwitch.isFastPort(p)) { + addToQuarantineQueue(npt); + } else { + // Add to maintenance queue to ensure that BDDP packets + // are sent out. + addToMaintenanceQueue(npt); } } - protected void setControllerTLV() { - // Setting the controllerTLVValue based on current nano time, - // controller's IP address, and the network interface object hash - // the corresponding IP address. + private boolean portEnabled(OFPhysicalPort port) { + if (port == null) return false; + if ((OFPortConfig.OFPPC_PORT_DOWN.getValue() & port.getConfig()) > 0) + return false; + if ((OFPortState.OFPPS_LINK_DOWN.getValue() & port.getState()) > 0) + return false; + // Port STP state doesn't work with multiple VLANs, so ignore it for now + // if ((port.getState() & OFPortState.OFPPS_STP_MASK.getValue()) == + // OFPortState.OFPPS_STP_BLOCK.getValue()) + // return false; + return true; + } - final int prime = 7867; - InetAddress localIPAddress = null; - NetworkInterface localInterface = null; + //*********************************** + // Internal Methods - Discovery Related + //*********************************** - byte[] controllerTLVValue = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; // 8 - // byte - // value. - ByteBuffer bb = ByteBuffer.allocate(10); + @LogMessageDoc(level = "ERROR", + message = "Error in link discovery updates loop", + explanation = "An unknown error occured while dispatching " + + "link update notifications", + recommendation = LogMessageDoc.GENERIC_ACTION) + private void doUpdatesThread() throws InterruptedException { + do { + LDUpdate update = updates.take(); + List<LDUpdate> updateList = new ArrayList<LDUpdate>(); + updateList.add(update); - try { - localIPAddress = java.net.InetAddress.getLocalHost(); - localInterface = NetworkInterface.getByInetAddress(localIPAddress); - } catch (Exception e) { - e.printStackTrace(); - } + // Add all the pending updates to the list. + while (updates.peek() != null) { + updateList.add(updates.remove()); + } - long result = System.nanoTime(); - if (localIPAddress != null) - result = result - * prime - + IPv4.toIPv4Address(localIPAddress.getHostAddress()); - if (localInterface != null) - result = result * prime - + localInterface.hashCode(); - // set the first 4 bits to 0. - result = result & (0x0fffffffffffffffL); + if (linkDiscoveryAware != null) { + if (log.isTraceEnabled()) { + log.trace("Dispatching link discovery update {} {} {} {} {} for {}", + new Object[] { + update.getOperation(), + HexString.toHexString(update.getSrc()), + update.getSrcPort(), + HexString.toHexString(update.getDst()), + update.getDstPort(), + linkDiscoveryAware }); + } + try { + for (ILinkDiscoveryListener lda : linkDiscoveryAware) { // order + // maintained + lda.linkDiscoveryUpdate(updateList); + } + } catch (Exception e) { + log.error("Error in link discovery updates loop", e); + } + } + } while (updates.peek() != null); + } - bb.putLong(result); + protected boolean isLinkDiscoverySuppressed(long sw, short portNumber) { + return this.suppressLinkDiscovery.contains(new NodePortTuple(sw, + portNumber)); + } - bb.rewind(); - bb.get(controllerTLVValue, 0, 8); + protected void discoverLinks() { - this.controllerTLV = new LLDPTLV().setType((byte) 0x0c) - .setLength((short) controllerTLVValue.length) - .setValue(controllerTLVValue); - } + // timeout known links. + timeoutLinks(); - @Override - public String getName() { - return "linkdiscovery"; - } + // increment LLDP clock + lldpClock = (lldpClock + 1) % LLDP_TO_ALL_INTERVAL; - @Override - public Command receive(IOFSwitch sw, OFMessage msg, - FloodlightContext cntx) { - switch (msg.getType()) { - case PACKET_IN: - return this.handlePacketIn(sw.getId(), (OFPacketIn) msg, - cntx); - case PORT_STATUS: - return this.handlePortStatus(sw.getId(), (OFPortStatus) msg); - default: - break; + if (lldpClock == 0) { + log.debug("Sending LLDP out on all ports."); + discoverOnAllPorts(); } - return Command.CONTINUE; } - private Command handleLldp(LLDP lldp, long sw, short inPort, - boolean isStandard, FloodlightContext cntx) { - // If LLDP is suppressed on this port, ignore received packet as well - IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); - - if (!isIncomingDiscoveryAllowed(sw, inPort, isStandard)) - return Command.STOP; - - // If this is a malformed LLDP, or not from us, exit - if (lldp.getPortId() == null || lldp.getPortId().getLength() != 3) { - return Command.CONTINUE; + /** + * Quarantine Ports. + */ + protected class QuarantineWorker implements Runnable { + @Override + public void run() { + try { + processBDDPLists(); + } catch (Exception e) { + log.error("Error in quarantine worker thread", e); + } finally { + bddpTask.reschedule(BDDP_TASK_INTERVAL, + TimeUnit.MILLISECONDS); + } } + } - long myId = ByteBuffer.wrap(controllerTLV.getValue()).getLong(); - long otherId = 0; - boolean myLLDP = false; - Boolean isReverse = null; + /** + * Add a switch port to the quarantine queue. Schedule the quarantine task + * if the quarantine queue was empty before adding this switch port. + * + * @param npt + */ + protected void addToQuarantineQueue(NodePortTuple npt) { + if (quarantineQueue.contains(npt) == false) + quarantineQueue.add(npt); + } - ByteBuffer portBB = ByteBuffer.wrap(lldp.getPortId().getValue()); - portBB.position(1); + /** + * Remove a switch port from the quarantine queue. + */ + protected void removeFromQuarantineQueue(NodePortTuple npt) { + // Remove all occurrences of the node port tuple from the list. + while (quarantineQueue.remove(npt)) + ; + } - Short remotePort = portBB.getShort(); - IOFSwitch remoteSwitch = null; + /** + * Add a switch port to maintenance queue. + * + * @param npt + */ + protected void addToMaintenanceQueue(NodePortTuple npt) { + // TODO We are not checking if the switch port tuple is already + // in the maintenance list or not. This will be an issue for + // really large number of switch ports in the network. + if (maintenanceQueue.contains(npt) == false) + maintenanceQueue.add(npt); + } - // Verify this LLDP packet matches what we're looking for - for (LLDPTLV lldptlv : lldp.getOptionalTLVList()) { - if (lldptlv.getType() == 127 && lldptlv.getLength() == 12 - && lldptlv.getValue()[0] == 0x0 - && lldptlv.getValue()[1] == 0x26 - && lldptlv.getValue()[2] == (byte) 0xe1 - && lldptlv.getValue()[3] == 0x0) { - ByteBuffer dpidBB = ByteBuffer.wrap(lldptlv.getValue()); - remoteSwitch = floodlightProvider.getSwitches() - .get(dpidBB.getLong(4)); - } else if (lldptlv.getType() == 12 && lldptlv.getLength() == 8) { - otherId = ByteBuffer.wrap(lldptlv.getValue()).getLong(); - if (myId == otherId) myLLDP = true; - } else if (lldptlv.getType() == TLV_DIRECTION_TYPE - && lldptlv.getLength() == TLV_DIRECTION_LENGTH) { - if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_FORWARD[0]) - isReverse = false; - else if (lldptlv.getValue()[0] == TLV_DIRECTION_VALUE_REVERSE[0]) - isReverse = true; - } - } + /** + * Remove a switch port from maintenance queue. + * + * @param npt + */ + protected void removeFromMaintenanceQueue(NodePortTuple npt) { + // Remove all occurrences of the node port tuple from the queue. + while (maintenanceQueue.remove(npt)) + ; + } - if (myLLDP == false) { - // This is not the LLDP sent by this controller. - // If the LLDP message has multicast bit set, then we need to - // broadcast - // the packet as a regular packet. - if (isStandard) { - if (log.isTraceEnabled()) { - log.trace("Got a standard LLDP=[{}]. Not fowarding it.", lldp.toString()); - } - return Command.STOP; - } else if (myId < otherId) { - if (log.isTraceEnabled()) { - log.trace("Getting BDDP packets from a different controller" - + "and letting it go through normal processing chain."); - } - return Command.CONTINUE; - } - } + /** + * This method processes the quarantine list in bursts. The task is at most + * once per BDDP_TASK_INTERVAL. One each call, BDDP_TASK_SIZE number of + * switch ports are processed. Once the BDDP packets are sent out through + * the switch ports, the ports are removed from the quarantine list. + */ + protected void processBDDPLists() { + int count = 0; + Set<NodePortTuple> nptList = new HashSet<NodePortTuple>(); - if (remoteSwitch == null) { - // Ignore LLDPs not generated by Floodlight, or from a switch that - // has recently - // disconnected, or from a switch connected to another Floodlight - // instance - if (log.isTraceEnabled()) { - log.trace("Received LLDP from remote switch not connected to the controller"); - } - return Command.STOP; + while (count < BDDP_TASK_SIZE && quarantineQueue.peek() != null) { + NodePortTuple npt; + npt = quarantineQueue.remove(); + sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, + false); + nptList.add(npt); + count++; } - if (!remoteSwitch.portEnabled(remotePort)) { - if (log.isTraceEnabled()) { - log.trace("Ignoring link with disabled source port: switch {} port {}", - remoteSwitch.getStringId(), remotePort); - } - return Command.STOP; - } - if (suppressLinkDiscovery.contains(new NodePortTuple( - remoteSwitch.getId(), - remotePort))) { - if (log.isTraceEnabled()) { - log.trace("Ignoring link with suppressed src port: switch {} port {}", - remoteSwitch.getStringId(), remotePort); - } - return Command.STOP; - } - if (!iofSwitch.portEnabled(inPort)) { - if (log.isTraceEnabled()) { - log.trace("Ignoring link with disabled dest port: switch {} port {}", - HexString.toHexString(sw), inPort); - } - return Command.STOP; + count = 0; + while (count < BDDP_TASK_SIZE && maintenanceQueue.peek() != null) { + NodePortTuple npt; + npt = maintenanceQueue.remove(); + sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, + false); + count++; } - OFPhysicalPort physicalPort = remoteSwitch.getPort(remotePort); - int srcPortState = (physicalPort != null) ? physicalPort.getState() - : 0; - physicalPort = iofSwitch.getPort(inPort); - int dstPortState = (physicalPort != null) ? physicalPort.getState() - : 0; + for (NodePortTuple npt : nptList) { + generateSwitchPortStatusUpdate(npt.getNodeId(), npt.getPortId()); + } + } - // Store the time of update to this link, and push it out to - // routingEngine - Link lt = new Link(remoteSwitch.getId(), remotePort, - iofSwitch.getId(), inPort); + private void generateSwitchPortStatusUpdate(long sw, short port) { + UpdateOperation operation; - if (!isLinkAllowed(lt.getSrc(), lt.getSrcPort(), - lt.getDst(), lt.getDstPort())) - return Command.STOP; + IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); + if (iofSwitch == null) return; - // Continue only if link is allowed. - Long lastLldpTime = null; - Long lastBddpTime = null; + OFPhysicalPort ofp = iofSwitch.getPort(port); + if (ofp == null) return; - Long firstSeenTime = System.currentTimeMillis(); + int srcPortState = ofp.getState(); + boolean portUp = ((srcPortState & OFPortState.OFPPS_STP_MASK.getValue()) + != OFPortState.OFPPS_STP_BLOCK.getValue()); - if (isStandard) - lastLldpTime = System.currentTimeMillis(); + if (portUp) + operation = UpdateOperation.PORT_UP; else - lastBddpTime = System.currentTimeMillis(); + operation = UpdateOperation.PORT_DOWN; - LinkInfo newLinkInfo = new LinkInfo(firstSeenTime, lastLldpTime, - lastBddpTime, srcPortState, - dstPortState); + updates.add(new LDUpdate(sw, port, operation)); + } + /** + * Send LLDP on known ports + */ + protected void discoverOnKnownLinkPorts() { + // Copy the port set. + Set<NodePortTuple> nptSet = new HashSet<NodePortTuple>(); + nptSet.addAll(portLinks.keySet()); - addOrUpdateLink(lt, newLinkInfo); + // Send LLDP from each of them. + for (NodePortTuple npt : nptSet) { + discover(npt); + } + } - // Continue only if addOrUpdateLink was successful. + protected void discover(NodePortTuple npt) { + discover(npt.getNodeId(), npt.getPortId()); + } - // Check if reverse link exists. - // If it doesn't exist and if the forward link was seen - // first seen within a small interval, send probe on the - // reverse link. + protected void discover(long sw, short port) { + sendDiscoveryMessage(sw, port, true, false); + } - newLinkInfo = links.get(lt); - if (newLinkInfo != null && isStandard && isReverse == false) { - Link reverseLink = new Link(lt.getDst(), lt.getDstPort(), - lt.getSrc(), lt.getSrcPort()); - LinkInfo reverseInfo = links.get(reverseLink); - if (reverseInfo == null) { - // the reverse link does not exist. - if (newLinkInfo.getFirstSeenTime() > System.currentTimeMillis() - - LINK_TIMEOUT) { - this.sendDiscoveryMessage(lt.getDst(), lt.getDstPort(), - isStandard, true); - } + /** + * Check if incoming discovery messages are enabled or not. + * @param sw + * @param port + * @param isStandard + * @return + */ + protected boolean isIncomingDiscoveryAllowed(long sw, short port, + boolean isStandard) { + + if (isLinkDiscoverySuppressed(sw, port)) { + /* Do not process LLDPs from this port as suppressLLDP is set */ + return false; + } + + IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); + if (iofSwitch == null) { + return false; + } + + if (port == OFPort.OFPP_LOCAL.getValue()) return false; + + OFPhysicalPort ofpPort = iofSwitch.getPort(port); + if (ofpPort == null) { + if (log.isTraceEnabled()) { + log.trace("Null physical port. sw={}, port={}", + HexString.toHexString(sw), port); } + return false; } - // If the received packet is a BDDP packet, then create a reverse BDDP - // link as well. - if (!isStandard) { - Link reverseLink = new Link(lt.getDst(), lt.getDstPort(), - lt.getSrc(), lt.getSrcPort()); + return true; + } - // srcPortState and dstPort state are reversed. - LinkInfo reverseInfo = new LinkInfo(firstSeenTime, lastLldpTime, - lastBddpTime, dstPortState, - srcPortState); + /** + * Check if outgoing discovery messages are enabled or not. + * @param sw + * @param port + * @param isStandard + * @param isReverse + * @return + */ + protected boolean isOutgoingDiscoveryAllowed(long sw, short port, + boolean isStandard, + boolean isReverse) { - addOrUpdateLink(reverseLink, reverseInfo); + if (isLinkDiscoverySuppressed(sw, port)) { + /* Dont send LLDPs out of this port as suppressLLDP is set */ + return false; } - // Remove the node ports from the quarantine and maintenance queues. - NodePortTuple nptSrc = new NodePortTuple(lt.getSrc(), - lt.getSrcPort()); - NodePortTuple nptDst = new NodePortTuple(lt.getDst(), - lt.getDstPort()); - removeFromQuarantineQueue(nptSrc); - removeFromMaintenanceQueue(nptSrc); - removeFromQuarantineQueue(nptDst); - removeFromMaintenanceQueue(nptDst); + IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); + if (iofSwitch == null) { + return false; + } - // Consume this message - return Command.STOP; + if (port == OFPort.OFPP_LOCAL.getValue()) return false; + + OFPhysicalPort ofpPort = iofSwitch.getPort(port); + if (ofpPort == null) { + if (log.isTraceEnabled()) { + log.trace("Null physical port. sw={}, port={}", + HexString.toHexString(sw), port); + } + return false; + } + + // For fast ports, do not send forward LLDPs or BDDPs. + if (!isReverse && autoPortFastFeature && iofSwitch.isFastPort(port)) + return false; + return true; } - protected Command handlePacketIn(long sw, OFPacketIn pi, - FloodlightContext cntx) { - Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, - IFloodlightProviderService.CONTEXT_PI_PAYLOAD); + /** + * Get the actions for packet-out corresponding to a specific port. + * This is a placeholder for adding actions if any port-specific + * actions are desired. The default action is simply to output to + * the given port. + * @param port + * @return + */ + protected List<OFAction> getDiscoveryActions (IOFSwitch sw, OFPhysicalPort port){ + // set actions + List<OFAction> actions = new ArrayList<OFAction>(); + actions.add(new OFActionOutput(port.getPortNumber(), (short) 0)); + return actions; + } - if (eth.getEtherType() == Ethernet.TYPE_BSN) { - BSN bsn = (BSN) eth.getPayload(); - if (bsn == null) return Command.STOP; - if (bsn.getPayload() == null) return Command.STOP; - // It could be a packet other than BSN LLDP, therefore - // continue with the regular processing. - if (bsn.getPayload() instanceof LLDP == false) - return Command.CONTINUE; - return handleLldp((LLDP) bsn.getPayload(), sw, pi.getInPort(), false, cntx); - } else if (eth.getEtherType() == Ethernet.TYPE_LLDP) { - return handleLldp((LLDP) eth.getPayload(), sw, pi.getInPort(), true, cntx); - } else if (eth.getEtherType() < 1500) { - long destMac = eth.getDestinationMAC().toLong(); - if ((destMac & LINK_LOCAL_MASK) == LINK_LOCAL_VALUE) { - if (log.isTraceEnabled()) { - log.trace("Ignoring packet addressed to 802.1D/Q " - + "reserved address."); - } - return Command.STOP; - } + /** + * Send link discovery message out of a given switch port. The discovery + * message may be a standard LLDP or a modified LLDP, where the dst mac + * address is set to :ff. TODO: The modified LLDP will updated in the future + * and may use a different eth-type. + * + * @param sw + * @param port + * @param isStandard + * indicates standard or modified LLDP + * @param isReverse + * indicates whether the LLDP was sent as a response + */ + @LogMessageDoc(level = "ERROR", + message = "Failure sending LLDP out port {port} on switch {switch}", + explanation = "An I/O error occured while sending LLDP message " + + "to the switch.", + recommendation = LogMessageDoc.CHECK_SWITCH) + protected void sendDiscoveryMessage(long sw, short port, + boolean isStandard, boolean isReverse) { + + // Takes care of all checks including null pointer checks. + if (!isOutgoingDiscoveryAllowed(sw, port, isStandard, isReverse)) + return; + + IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); + OFPhysicalPort ofpPort = iofSwitch.getPort(port); + + if (log.isTraceEnabled()) { + log.trace("Sending LLDP packet out of swich: {}, port: {}", + HexString.toHexString(sw), port); } + OFPacketOut po = generateLLDPMessage(sw, port, isStandard, isReverse); - if (ignorePacketInFromSource(eth.getSourceMAC().toLong())) { - return Command.STOP; + // Add actions + List<OFAction> actions = getDiscoveryActions(iofSwitch, ofpPort); + po.setActions(actions); + short actionLength = 0; + Iterator <OFAction> actionIter = actions.iterator(); + while (actionIter.hasNext()) { + actionLength += actionIter.next().getLength(); + } + po.setActionsLength(actionLength); + + // po already has the minimum length + data length set + // simply add the actions length to this. + po.setLengthU(po.getLengthU() + po.getActionsLength()); + + // send + try { + iofSwitch.write(po, null); + iofSwitch.flush(); + } catch (IOException e) { + log.error("Failure sending LLDP out port {} on switch {}", + new Object[] { port, iofSwitch.getStringId() }, e); + } + } + + /** + * Send LLDPs to all switch-ports + */ + protected void discoverOnAllPorts() { + if (log.isTraceEnabled()) { + log.trace("Sending LLDP packets out of all the enabled ports on switch {}"); } + Set<Long> switches = floodlightProvider.getSwitches().keySet(); + // Send standard LLDPs + for (long sw : switches) { + IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); + if (iofSwitch == null) continue; + if (iofSwitch.getEnabledPorts() != null) { + for (OFPhysicalPort ofp : iofSwitch.getEnabledPorts()) { + if (isLinkDiscoverySuppressed(sw, ofp.getPortNumber())) + continue; + if (autoPortFastFeature + && iofSwitch.isFastPort(ofp.getPortNumber())) + continue; - // If packet-in is from a quarantine port, stop processing. - NodePortTuple npt = new NodePortTuple(sw, pi.getInPort()); - if (quarantineQueue.contains(npt)) return Command.STOP; + // sends forward LLDP only non-fastports. + sendDiscoveryMessage(sw, ofp.getPortNumber(), true, + false); - return Command.CONTINUE; + // If the switch port is not alreayd in the maintenance + // queue, add it. + NodePortTuple npt = new NodePortTuple( + sw, + ofp.getPortNumber()); + addToMaintenanceQueue(npt); + } + } + } } protected UpdateOperation getUpdateOperation(int srcPortState, int dstPortState) { - boolean added = (((srcPortState & OFPortState.OFPPS_STP_MASK.getValue()) != OFPortState.OFPPS_STP_BLOCK.getValue()) && ((dstPortState & OFPortState.OFPPS_STP_MASK.getValue()) != OFPortState.OFPPS_STP_BLOCK.getValue())); + boolean added = (((srcPortState & OFPortState.OFPPS_STP_MASK.getValue()) + != OFPortState.OFPPS_STP_BLOCK.getValue()) && + ((dstPortState & OFPortState.OFPPS_STP_MASK.getValue()) + != OFPortState.OFPPS_STP_BLOCK.getValue())); if (added) return UpdateOperation.LINK_UPDATED; return UpdateOperation.LINK_REMOVED; @@ -1130,6 +1336,17 @@ public class LinkDiscoveryManager implements IOFMessageListener, return UpdateOperation.PORT_DOWN; } + //************************************ + // Internal Methods - Link Operations Related + //************************************ + + /** + * This method is used to specifically ignore/consider specific links. + */ + protected boolean isLinkAllowed(long src, short srcPort, + long dst, short dstPort) { + return true; + } protected boolean addOrUpdateLink(Link lt, LinkInfo newInfo) { NodePortTuple srcNpt, dstNpt; @@ -1271,13 +1488,9 @@ public class LinkDiscoveryManager implements IOFMessageListener, return linkChanged; } - public Map<Long, Set<Link>> getSwitchLinks() { - return this.switchLinks; - } - /** * Removes links from memory and storage. - * + * * @param links * The List of @LinkTuple to delete. */ @@ -1287,7 +1500,7 @@ public class LinkDiscoveryManager implements IOFMessageListener, /** * Removes links from memory and storage. - * + * * @param links * The List of @LinkTuple to delete. */ @@ -1341,243 +1554,22 @@ public class LinkDiscoveryManager implements IOFMessageListener, removeLinkFromStorage(lt); // TODO Whenever link is removed, it has to checked if - // the switchports must be added to quarantine. - - if (log.isTraceEnabled()) { - log.trace("Deleted link {}", lt); - } - } - } finally { - if (updateList != null) linkUpdateList.addAll(updateList); - updates.addAll(linkUpdateList); - lock.writeLock().unlock(); - } - } - - /** - * Handles an OFPortStatus message from a switch. We will add or delete - * LinkTupes as well re-compute the topology if needed. - * - * @param sw - * The IOFSwitch that sent the port status message - * @param ps - * The OFPortStatus message - * @return The Command to continue or stop after we process this message - */ - protected Command handlePortStatus(long sw, OFPortStatus ps) { - - IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); - if (iofSwitch == null) return Command.CONTINUE; - - if (log.isTraceEnabled()) { - log.trace("handlePortStatus: Switch {} port #{} reason {}; " - + "config is {} state is {}", - new Object[] { iofSwitch.getStringId(), - ps.getDesc().getPortNumber(), - ps.getReason(), - ps.getDesc().getConfig(), - ps.getDesc().getState() }); - } - - short port = ps.getDesc().getPortNumber(); - NodePortTuple npt = new NodePortTuple(sw, port); - boolean linkDeleted = false; - boolean linkInfoChanged = false; - - lock.writeLock().lock(); - try { - // if ps is a delete, or a modify where the port is down or - // configured down - if ((byte) OFPortReason.OFPPR_DELETE.ordinal() == ps.getReason() - || ((byte) OFPortReason.OFPPR_MODIFY.ordinal() == ps.getReason() && !portEnabled(ps.getDesc()))) { - deleteLinksOnPort(npt, "Port Status Changed"); - LDUpdate update = new LDUpdate(sw, port, - UpdateOperation.PORT_DOWN); - updates.add(update); - linkDeleted = true; - } else if (ps.getReason() == (byte) OFPortReason.OFPPR_MODIFY.ordinal()) { - // If ps is a port modification and the port state has changed - // that affects links in the topology - - if (this.portLinks.containsKey(npt)) { - for (Link lt : this.portLinks.get(npt)) { - LinkInfo linkInfo = links.get(lt); - assert (linkInfo != null); - Integer updatedSrcPortState = null; - Integer updatedDstPortState = null; - if (lt.getSrc() == npt.getNodeId() - && lt.getSrcPort() == npt.getPortId() - && (linkInfo.getSrcPortState() != ps.getDesc() - .getState())) { - updatedSrcPortState = ps.getDesc().getState(); - linkInfo.setSrcPortState(updatedSrcPortState); - } - if (lt.getDst() == npt.getNodeId() - && lt.getDstPort() == npt.getPortId() - && (linkInfo.getDstPortState() != ps.getDesc() - .getState())) { - updatedDstPortState = ps.getDesc().getState(); - linkInfo.setDstPortState(updatedDstPortState); - } - if ((updatedSrcPortState != null) - || (updatedDstPortState != null)) { - // The link is already known to link discovery - // manager and the status has changed, therefore - // send an LDUpdate. - UpdateOperation operation = getUpdateOperation(linkInfo.getSrcPortState(), - linkInfo.getDstPortState()); - updates.add(new LDUpdate(lt.getSrc(), - lt.getSrcPort(), - lt.getDst(), - lt.getDstPort(), - getLinkType(lt, - linkInfo), - operation)); - writeLinkToStorage(lt, linkInfo); - linkInfoChanged = true; - } - } - } - - UpdateOperation operation = getUpdateOperation(ps.getDesc() - .getState()); - updates.add(new LDUpdate(sw, port, operation)); - } - - if (!linkDeleted && !linkInfoChanged) { - if (log.isTraceEnabled()) { - log.trace("handlePortStatus: Switch {} port #{} reason {};" - + " no links to update/remove", - new Object[] { HexString.toHexString(sw), - ps.getDesc().getPortNumber(), - ps.getReason() }); - } - } - } finally { - lock.writeLock().unlock(); - } - - if (!linkDeleted) { - // Send LLDP right away when port state is changed for faster - // cluster-merge. If it is a link delete then there is not need - // to send the LLDPs right away and instead we wait for the LLDPs - // to be sent on the timer as it is normally done - // do it outside the write-lock - // sendLLDPTask.reschedule(1000, TimeUnit.MILLISECONDS); - processNewPort(npt.getNodeId(), npt.getPortId()); - } - return Command.CONTINUE; - } - - /** - * Process a new port. If link discovery is disabled on the port, then do - * nothing. If autoportfast feature is enabled and the port is a fast port, - * then do nothing. Otherwise, send LLDP message. Add the port to - * quarantine. - * - * @param sw - * @param p - */ - private void processNewPort(long sw, short p) { - if (isLinkDiscoverySuppressed(sw, p)) { - // Do nothing as link discovery is suppressed. - return; - } - - IOFSwitch iofSwitch = floodlightProvider.getSwitches().get(sw); - if (iofSwitch == null) return; - - if (autoPortFastFeature && iofSwitch.isFastPort(p)) { - // Do nothing as the port is a fast port. - return; - } - NodePortTuple npt = new NodePortTuple(sw, p); - discover(sw, p); - // if it is not a fast port, add it to quarantine. - if (!iofSwitch.isFastPort(p)) { - addToQuarantineQueue(npt); - } else { - // Add to maintenance queue to ensure that BDDP packets - // are sent out. - addToMaintenanceQueue(npt); - } - } - - /** - * We send out LLDP messages when a switch is added to discover the topology - * - * @param sw - * The IOFSwitch that connected to the controller - */ - @Override - public void addedSwitch(IOFSwitch sw) { - - if (sw.getEnabledPortNumbers() != null) { - for (Short p : sw.getEnabledPortNumbers()) { - processNewPort(sw.getId(), p); - } - } - // Update event history - evHistTopoSwitch(sw, EvAction.SWITCH_CONNECTED, "None"); - LDUpdate update = new LDUpdate(sw.getId(), null, - UpdateOperation.SWITCH_UPDATED); - updates.add(update); - } - - /** - * When a switch disconnects we remove any links from our map and notify. - * - * @param The - * id of the switch - */ - @Override - public void removedSwitch(IOFSwitch iofSwitch) { - // Update event history - long sw = iofSwitch.getId(); - evHistTopoSwitch(iofSwitch, EvAction.SWITCH_DISCONNECTED, "None"); - List<Link> eraseList = new ArrayList<Link>(); - lock.writeLock().lock(); - try { - if (switchLinks.containsKey(sw)) { - if (log.isTraceEnabled()) { - log.trace("Handle switchRemoved. Switch {}; removing links {}", - HexString.toHexString(sw), switchLinks.get(sw)); - } - - List<LDUpdate> updateList = new ArrayList<LDUpdate>(); - updateList.add(new LDUpdate(sw, null, - UpdateOperation.SWITCH_REMOVED)); - // add all tuples with an endpoint on this switch to erase list - eraseList.addAll(switchLinks.get(sw)); - - // Sending the updateList, will ensure the updates in this - // list will be added at the end of all the link updates. - // Thus, it is not necessary to explicitly add these updates - // to the queue. - deleteLinks(eraseList, "Switch Removed", updateList); - } else { - // Switch does not have any links. - updates.add(new LDUpdate(sw, null, - UpdateOperation.SWITCH_REMOVED)); + // the switchports must be added to quarantine. + + if (log.isTraceEnabled()) { + log.trace("Deleted link {}", lt); + } } } finally { + if (updateList != null) linkUpdateList.addAll(updateList); + updates.addAll(linkUpdateList); lock.writeLock().unlock(); } } - /** - * We don't react the port changed notifications here. we listen for - * OFPortStatus messages directly. Might consider using this notifier - * instead - */ - @Override - public void switchPortChanged(Long switchId) { - // no-op - } - /** * Delete links incident on a given switch port. - * + * * @param npt * @param reason */ @@ -1665,72 +1657,282 @@ public class LinkDiscoveryManager implements IOFMessageListener, } } - private boolean portEnabled(OFPhysicalPort port) { - if (port == null) return false; - if ((OFPortConfig.OFPPC_PORT_DOWN.getValue() & port.getConfig()) > 0) - return false; - if ((OFPortState.OFPPS_LINK_DOWN.getValue() & port.getState()) > 0) - return false; - // Port STP state doesn't work with multiple VLANs, so ignore it for now - // if ((port.getState() & OFPortState.OFPPS_STP_MASK.getValue()) == - // OFPortState.OFPPS_STP_BLOCK.getValue()) - // return false; - return true; + protected void addLinkToBroadcastDomain(Link lt) { + + NodePortTuple srcNpt, dstNpt; + srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort()); + dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort()); + + if (!portBroadcastDomainLinks.containsKey(srcNpt)) + portBroadcastDomainLinks.put(srcNpt, + new HashSet<Link>()); + portBroadcastDomainLinks.get(srcNpt).add(lt); + + if (!portBroadcastDomainLinks.containsKey(dstNpt)) + portBroadcastDomainLinks.put(dstNpt, + new HashSet<Link>()); + portBroadcastDomainLinks.get(dstNpt).add(lt); } - public Map<NodePortTuple, Set<Link>> getPortBroadcastDomainLinks() { - return portBroadcastDomainLinks; + protected void removeLinkFromBroadcastDomain(Link lt) { + + NodePortTuple srcNpt, dstNpt; + srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort()); + dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort()); + + if (portBroadcastDomainLinks.containsKey(srcNpt)) { + portBroadcastDomainLinks.get(srcNpt).remove(lt); + if (portBroadcastDomainLinks.get(srcNpt).isEmpty()) + portBroadcastDomainLinks.remove(srcNpt); + } + + if (portBroadcastDomainLinks.containsKey(dstNpt)) { + portBroadcastDomainLinks.get(dstNpt).remove(lt); + if (portBroadcastDomainLinks.get(dstNpt).isEmpty()) + portBroadcastDomainLinks.remove(dstNpt); + } + } + + //****************** + // Internal Helper Methods + //****************** + + protected void setControllerTLV() { + // Setting the controllerTLVValue based on current nano time, + // controller's IP address, and the network interface object hash + // the corresponding IP address. + + final int prime = 7867; + InetAddress localIPAddress = null; + NetworkInterface localInterface = null; + + byte[] controllerTLVValue = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; // 8 + // byte + // value. + ByteBuffer bb = ByteBuffer.allocate(10); + + try { + localIPAddress = java.net.InetAddress.getLocalHost(); + localInterface = NetworkInterface.getByInetAddress(localIPAddress); + } catch (Exception e) { + e.printStackTrace(); + } + + long result = System.nanoTime(); + if (localIPAddress != null) + result = result + * prime + + IPv4.toIPv4Address(localIPAddress.getHostAddress()); + if (localInterface != null) + result = result * prime + + localInterface.hashCode(); + // set the first 4 bits to 0. + result = result & (0x0fffffffffffffffL); + + bb.putLong(result); + + bb.rewind(); + bb.get(controllerTLVValue, 0, 8); + + this.controllerTLV = new LLDPTLV().setType((byte) 0x0c) + .setLength((short) controllerTLVValue.length) + .setValue(controllerTLVValue); } + //****************** + // IOFSwitchListener + //****************** + + /** + * We send out LLDP messages when a switch is added to discover the topology + * + * @param sw + * The IOFSwitch that connected to the controller + */ @Override - public Map<Link, LinkInfo> getLinks() { - lock.readLock().lock(); - Map<Link, LinkInfo> result; + public void addedSwitch(IOFSwitch sw) { + + if (sw.getEnabledPortNumbers() != null) { + for (Short p : sw.getEnabledPortNumbers()) { + processNewPort(sw.getId(), p); + } + } + // Update event history + evHistTopoSwitch(sw, EvAction.SWITCH_CONNECTED, "None"); + LDUpdate update = new LDUpdate(sw.getId(), null, + UpdateOperation.SWITCH_UPDATED); + updates.add(update); + } + + /** + * When a switch disconnects we remove any links from our map and notify. + * + * @param The + * id of the switch + */ + @Override + public void removedSwitch(IOFSwitch iofSwitch) { + // Update event history + long sw = iofSwitch.getId(); + evHistTopoSwitch(iofSwitch, EvAction.SWITCH_DISCONNECTED, "None"); + List<Link> eraseList = new ArrayList<Link>(); + lock.writeLock().lock(); try { - result = new HashMap<Link, LinkInfo>(links); + if (switchLinks.containsKey(sw)) { + if (log.isTraceEnabled()) { + log.trace("Handle switchRemoved. Switch {}; removing links {}", + HexString.toHexString(sw), switchLinks.get(sw)); + } + + List<LDUpdate> updateList = new ArrayList<LDUpdate>(); + updateList.add(new LDUpdate(sw, null, + UpdateOperation.SWITCH_REMOVED)); + // add all tuples with an endpoint on this switch to erase list + eraseList.addAll(switchLinks.get(sw)); + + // Sending the updateList, will ensure the updates in this + // list will be added at the end of all the link updates. + // Thus, it is not necessary to explicitly add these updates + // to the queue. + deleteLinks(eraseList, "Switch Removed", updateList); + } else { + // Switch does not have any links. + updates.add(new LDUpdate(sw, null, + UpdateOperation.SWITCH_REMOVED)); + } } finally { - lock.readLock().unlock(); + lock.writeLock().unlock(); + } + } + + /** + * We don't react the port changed notifications here. we listen for + * OFPortStatus messages directly. Might consider using this notifier + * instead + */ + @Override + public void switchPortChanged(Long switchId) { + // no-op + } + + //********************* + // Storage Listener + //********************* + /** + * Sets the IStorageSource to use for ITology + * + * @param storageSource + * the storage source to use + */ + public void setStorageSource(IStorageSourceService storageSource) { + this.storageSource = storageSource; + } + + /** + * Gets the storage source for this ITopology + * + * @return The IStorageSource ITopology is writing to + */ + public IStorageSourceService getStorageSource() { + return storageSource; + } + + @Override + public void rowsModified(String tableName, Set<Object> rowKeys) { + + if (tableName.equals(TOPOLOGY_TABLE_NAME)) { + readTopologyConfigFromStorage(); + return; + } + + Map<Long, IOFSwitch> switches = floodlightProvider.getSwitches(); + ArrayList<IOFSwitch> updated_switches = new ArrayList<IOFSwitch>(); + for (Object key : rowKeys) { + Long swId = new Long(HexString.toLong((String) key)); + if (switches.containsKey(swId)) { + IOFSwitch sw = switches.get(swId); + boolean curr_status = sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH); + boolean new_status = false; + IResultSet resultSet = null; + + try { + resultSet = storageSource.getRow(tableName, key); + for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) { + // In case of multiple rows, use the status in last row? + Map<String, Object> row = it.next().getRow(); + if (row.containsKey(SWITCH_CONFIG_CORE_SWITCH)) { + new_status = ((String) row.get(SWITCH_CONFIG_CORE_SWITCH)).equals("true"); + } + } + } finally { + if (resultSet != null) resultSet.close(); + } + + if (curr_status != new_status) { + updated_switches.add(sw); + } + } else { + if (log.isTraceEnabled()) { + log.trace("Update for switch which has no entry in switch " + + "list (dpid={}), a delete action.", + key); + } + } + } + + for (IOFSwitch sw : updated_switches) { + // Set SWITCH_IS_CORE_SWITCH to it's inverse value + if (sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH)) { + sw.removeAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH); + if (log.isTraceEnabled()) { + log.trace("SWITCH_IS_CORE_SWITCH set to False for {}", + sw); + } + updates.add(new LDUpdate(sw.getId(), + SwitchType.BASIC_SWITCH, + UpdateOperation.SWITCH_UPDATED)); + } else { + sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, + new Boolean(true)); + if (log.isTraceEnabled()) { + log.trace("SWITCH_IS_CORE_SWITCH set to True for {}", sw); + } + updates.add(new LDUpdate(sw.getId(), SwitchType.CORE_SWITCH, + UpdateOperation.SWITCH_UPDATED)); + } } - return result; } - protected void addLinkToBroadcastDomain(Link lt) { - - NodePortTuple srcNpt, dstNpt; - srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort()); - dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort()); - - if (!portBroadcastDomainLinks.containsKey(srcNpt)) - portBroadcastDomainLinks.put(srcNpt, - new HashSet<Link>()); - portBroadcastDomainLinks.get(srcNpt).add(lt); - - if (!portBroadcastDomainLinks.containsKey(dstNpt)) - portBroadcastDomainLinks.put(dstNpt, - new HashSet<Link>()); - portBroadcastDomainLinks.get(dstNpt).add(lt); + @Override + public void rowsDeleted(String tableName, Set<Object> rowKeys) { + // Ignore delete events, the switch delete will do the + // right thing on it's own. + readTopologyConfigFromStorage(); } - protected void removeLinkFromBroadcastDomain(Link lt) { - NodePortTuple srcNpt, dstNpt; - srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort()); - dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort()); + //****************************** + // Internal methods - Config Related + //****************************** - if (portBroadcastDomainLinks.containsKey(srcNpt)) { - portBroadcastDomainLinks.get(srcNpt).remove(lt); - if (portBroadcastDomainLinks.get(srcNpt).isEmpty()) - portBroadcastDomainLinks.remove(srcNpt); - } + protected void readTopologyConfigFromStorage() { + IResultSet topologyResult = storageSource.executeQuery(TOPOLOGY_TABLE_NAME, + null, null, + null); - if (portBroadcastDomainLinks.containsKey(dstNpt)) { - portBroadcastDomainLinks.get(dstNpt).remove(lt); - if (portBroadcastDomainLinks.get(dstNpt).isEmpty()) - portBroadcastDomainLinks.remove(dstNpt); + if (topologyResult.next()) { + boolean apf = topologyResult.getBoolean(TOPOLOGY_AUTOPORTFAST); + autoPortFastFeature = apf; + } else { + this.autoPortFastFeature = AUTOPORTFAST_DEFAULT; } + + if (autoPortFastFeature) + log.info("Setting autoportfast feature to ON"); + else + log.info("Setting autoportfast feature to OFF"); } - // STORAGE METHODS /** * Deletes all links from storage */ @@ -1738,22 +1940,9 @@ public class LinkDiscoveryManager implements IOFMessageListener, storageSource.deleteRowsAsync(LINK_TABLE_NAME, null); } - /** - * Gets the storage key for a LinkTuple - * - * @param lt - * The LinkTuple to get - * @return The storage key as a String - */ - private String getLinkId(Link lt) { - return HexString.toHexString(lt.getSrc()) + "-" + lt.getSrcPort() - + "-" + HexString.toHexString(lt.getDst()) + "-" - + lt.getDstPort(); - } - /** * Writes a LinkTuple and corresponding LinkInfo to storage - * + * * @param lt * The LinkTuple to write * @param linkInfo @@ -1821,6 +2010,17 @@ public class LinkDiscoveryManager implements IOFMessageListener, storageSource.updateRowAsync(LINK_TABLE_NAME, rowValues); } + /** + * Removes a link from storage using an asynchronous call. + * + * @param lt + * The LinkTuple to delete. + */ + protected void removeLinkFromStorage(Link lt) { + String id = getLinkId(lt); + storageSource.deleteRowAsync(LINK_TABLE_NAME, id); + } + public Long readLinkValidTime(Link lt) { // FIXME: We're not currently using this right now, but if we start // to use this again, we probably shouldn't use it in its current @@ -1836,9 +2036,9 @@ public class LinkDiscoveryManager implements IOFMessageListener, resultSet = storageSource.executeQuery(LINK_TABLE_NAME, columns, new OperatorPredicate( - LINK_ID, - OperatorPredicate.Operator.EQ, - id), + LINK_ID, + OperatorPredicate.Operator.EQ, + id), null); if (resultSet.next()) validTime = resultSet.getLong(LINK_VALID_TIME); @@ -1849,171 +2049,44 @@ public class LinkDiscoveryManager implements IOFMessageListener, } /** - * Removes a link from storage using an asynchronous call. - * + * Gets the storage key for a LinkTuple + * * @param lt - * The LinkTuple to delete. - */ - protected void removeLinkFromStorage(Link lt) { - String id = getLinkId(lt); - storageSource.deleteRowAsync(LINK_TABLE_NAME, id); - } - - @Override - public void addListener(ILinkDiscoveryListener listener) { - linkDiscoveryAware.add(listener); - } - - /** - * Register a link discovery aware component - * - * @param linkDiscoveryAwareComponent - */ - public - void - addLinkDiscoveryAware(ILinkDiscoveryListener linkDiscoveryAwareComponent) { - // TODO make this a copy on write set or lock it somehow - this.linkDiscoveryAware.add(linkDiscoveryAwareComponent); - } - - /** - * Deregister a link discovery aware component - * - * @param linkDiscoveryAwareComponent - */ - public - void - removeLinkDiscoveryAware(ILinkDiscoveryListener linkDiscoveryAwareComponent) { - // TODO make this a copy on write set or lock it somehow - this.linkDiscoveryAware.remove(linkDiscoveryAwareComponent); - } - - /** - * Sets the IStorageSource to use for ITology - * - * @param storageSource - * the storage source to use - */ - public void setStorageSource(IStorageSourceService storageSource) { - this.storageSource = storageSource; - } - - /** - * Gets the storage source for this ITopology - * - * @return The IStorageSource ITopology is writing to + * The LinkTuple to get + * @return The storage key as a String */ - public IStorageSourceService getStorageSource() { - return storageSource; - } - - @Override - public boolean isCallbackOrderingPrereq(OFType type, String name) { - return false; - } - - @Override - public boolean isCallbackOrderingPostreq(OFType type, String name) { - return false; - } - - @Override - public void rowsModified(String tableName, Set<Object> rowKeys) { - - if (tableName.equals(TOPOLOGY_TABLE_NAME)) { - readTopologyConfigFromStorage(); - return; - } - - Map<Long, IOFSwitch> switches = floodlightProvider.getSwitches(); - ArrayList<IOFSwitch> updated_switches = new ArrayList<IOFSwitch>(); - for (Object key : rowKeys) { - Long swId = new Long(HexString.toLong((String) key)); - if (switches.containsKey(swId)) { - IOFSwitch sw = switches.get(swId); - boolean curr_status = sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH); - boolean new_status = false; - IResultSet resultSet = null; - - try { - resultSet = storageSource.getRow(tableName, key); - for (Iterator<IResultSet> it = resultSet.iterator(); it.hasNext();) { - // In case of multiple rows, use the status in last row? - Map<String, Object> row = it.next().getRow(); - if (row.containsKey(SWITCH_CONFIG_CORE_SWITCH)) { - new_status = ((String) row.get(SWITCH_CONFIG_CORE_SWITCH)).equals("true"); - } - } - } finally { - if (resultSet != null) resultSet.close(); - } - - if (curr_status != new_status) { - updated_switches.add(sw); - } - } else { - if (log.isTraceEnabled()) { - log.trace("Update for switch which has no entry in switch " - + "list (dpid={}), a delete action.", - (String) key); - } - } - } - - for (IOFSwitch sw : updated_switches) { - // Set SWITCH_IS_CORE_SWITCH to it's inverse value - if (sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH)) { - sw.removeAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH); - if (log.isTraceEnabled()) { - log.trace("SWITCH_IS_CORE_SWITCH set to False for {}", - sw); - } - updates.add(new LDUpdate(sw.getId(), - SwitchType.BASIC_SWITCH, - UpdateOperation.SWITCH_UPDATED)); - } else { - sw.setAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH, - new Boolean(true)); - if (log.isTraceEnabled()) { - log.trace("SWITCH_IS_CORE_SWITCH set to True for {}", sw); - } - updates.add(new LDUpdate(sw.getId(), SwitchType.CORE_SWITCH, - UpdateOperation.SWITCH_UPDATED)); - } - } - } - - @Override - public void rowsDeleted(String tableName, Set<Object> rowKeys) { - // Ignore delete events, the switch delete will do the - // right thing on it's own. - readTopologyConfigFromStorage(); + private String getLinkId(Link lt) { + return HexString.toHexString(lt.getSrc()) + "-" + lt.getSrcPort() + + "-" + HexString.toHexString(lt.getDst()) + "-" + + lt.getDstPort(); } - // IFloodlightModule classes + //*************** + // IFloodlightModule + //*************** @Override - public Collection<Class<? extends IFloodlightService>> - getModuleServices() { - Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); + public Collection<Class<? extends IFloodlightService>> getModuleServices() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); l.add(ILinkDiscoveryService.class); // l.add(ITopologyService.class); return l; } @Override - public Map<Class<? extends IFloodlightService>, IFloodlightService> - getServiceImpls() { - Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); + public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { + Map<Class<? extends IFloodlightService>, IFloodlightService> m = + new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); // We are the class that implements the service m.put(ILinkDiscoveryService.class, this); return m; } @Override - public Collection<Class<? extends IFloodlightService>> - getModuleDependencies() { - Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); l.add(IFloodlightProviderService.class); l.add(IStorageSourceService.class); l.add(IThreadPoolService.class); @@ -2022,10 +2095,8 @@ public class LinkDiscoveryManager implements IOFMessageListener, } @Override - public - void - init(FloodlightModuleContext context) - throws FloodlightModuleException { + public void init(FloodlightModuleContext context) + throws FloodlightModuleException { floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); storageSource = context.getServiceImpl(IStorageSourceService.class); threadPool = context.getServiceImpl(IThreadPoolService.class); @@ -2042,7 +2113,7 @@ public class LinkDiscoveryManager implements IOFMessageListener, log.warn("Error event history size, using default of {} seconds", EVENT_HISTORY_SIZE); } log.debug("Event history size set to {}", EVENT_HISTORY_SIZE); - + // Set the autoportfast feature to false. this.autoPortFastFeature = AUTOPORTFAST_DEFAULT; @@ -2087,8 +2158,7 @@ public class LinkDiscoveryManager implements IOFMessageListener, explanation = "An unknown error occured while sending LLDP " + "messages to switches.", recommendation = LogMessageDoc.CHECK_SWITCH) }) - public - void startUp(FloodlightModuleContext context) { + public void startUp(FloodlightModuleContext context) { // Create our storage tables if (storageSource == null) { log.error("No storage source found."); @@ -2189,7 +2259,9 @@ public class LinkDiscoveryManager implements IOFMessageListener, // Topology Manager's Event History members and methods // **************************************************** - // Topology Manager event history + /** + * Topology Manager event history + */ public EventHistory<EventHistoryTopologySwitch> evHistTopologySwitch; public EventHistory<EventHistoryTopologyLink> evHistTopologyLink; public EventHistory<EventHistoryTopologyCluster> evHistTopologyCluster; @@ -2197,9 +2269,10 @@ public class LinkDiscoveryManager implements IOFMessageListener, public EventHistoryTopologyLink evTopoLink; public EventHistoryTopologyCluster evTopoCluster; - // Switch Added/Deleted - private void - evHistTopoSwitch(IOFSwitch sw, EvAction actn, String reason) { + /** + * Switch Added/Deleted Events + */ + private void evHistTopoSwitch(IOFSwitch sw, EvAction actn, String reason) { if (evTopoSwitch == null) { evTopoSwitch = new EventHistoryTopologySwitch(); } @@ -2262,6 +2335,10 @@ public class LinkDiscoveryManager implements IOFMessageListener, evTopoCluster = evHistTopologyCluster.put(evTopoCluster, action); } + //********************* + // IInfoProvider + //********************* + @Override public Map<String, Object> getInfo(String type) { if (!"summary".equals(type)) return null; @@ -2276,7 +2353,10 @@ public class LinkDiscoveryManager implements IOFMessageListener, return info; } - // IHARoleListener + //*************** + // IHAListener + //*************** + @Override public void roleChanged(Role oldRole, Role newRole) { switch (newRole) { @@ -2309,60 +2389,10 @@ public class LinkDiscoveryManager implements IOFMessageListener, } @Override - public - void - controllerNodeIPsChanged(Map<String, String> curControllerNodeIPs, - Map<String, String> addedControllerNodeIPs, - Map<String, String> removedControllerNodeIPs) { + public void controllerNodeIPsChanged(Map<String, String> curControllerNodeIPs, + Map<String, String> addedControllerNodeIPs, + Map<String, String> removedControllerNodeIPs) { // ignore } - public boolean isAutoPortFastFeature() { - return autoPortFastFeature; - } - - public void setAutoPortFastFeature(boolean autoPortFastFeature) { - this.autoPortFastFeature = autoPortFastFeature; - } - - @Override - public void addMACToIgnoreList(long mac, int ignoreBits) { - MACRange range = new MACRange(); - range.baseMAC = mac; - range.ignoreBits = ignoreBits; - ignoreMACSet.add(range); - } - - private boolean ignorePacketInFromSource(long srcMAC) { - Iterator<MACRange> it = ignoreMACSet.iterator(); - while (it.hasNext()) { - MACRange range = it.next(); - long mask = ~0; - if (range.ignoreBits >= 0 && range.ignoreBits <= 48) { - mask = mask << range.ignoreBits; - if ((range.baseMAC & mask) == (srcMAC & mask)) { - return true; - } - } - } - return false; - } - - public void readTopologyConfigFromStorage() { - IResultSet topologyResult = storageSource.executeQuery(TOPOLOGY_TABLE_NAME, - null, null, - null); - - if (topologyResult.next()) { - boolean apf = topologyResult.getBoolean(TOPOLOGY_AUTOPORTFAST); - autoPortFastFeature = apf; - } else { - this.autoPortFastFeature = AUTOPORTFAST_DEFAULT; - } - - if (autoPortFastFeature) - log.info("Setting autoportfast feature to ON"); - else - log.info("Setting autoportfast feature to OFF"); - } }