/** * Copyright 2011, Big Switch Networks, Inc. * Originally created by David Erickson, Stanford University * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. **/ package net.floodlightcontroller.linkdiscovery.internal; import java.net.NetworkInterface; import java.net.SocketException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.annotation.Nonnull; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.HAListenerTypeMarker; import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IShutdownService; import net.floodlightcontroller.core.PortChangeType; import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IInfoProvider; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IOFSwitchListener; import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.core.types.NodePortTuple; import net.floodlightcontroller.core.util.SingletonTask; import net.floodlightcontroller.debugcounter.IDebugCounter; import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.linkdiscovery.ILinkDiscovery; import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LDUpdate; import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.LinkType; import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.SwitchType; import net.floodlightcontroller.linkdiscovery.ILinkDiscovery.UpdateOperation; import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryListener; import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService; import net.floodlightcontroller.linkdiscovery.Link; import net.floodlightcontroller.linkdiscovery.web.LinkDiscoveryWebRoutable; import net.floodlightcontroller.packet.BSN; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.LLDP; import net.floodlightcontroller.packet.LLDPTLV; import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.storage.IResultSet; import net.floodlightcontroller.storage.IStorageSourceListener; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.storage.OperatorPredicate; import net.floodlightcontroller.storage.StorageException; import net.floodlightcontroller.threadpool.IThreadPoolService; import net.floodlightcontroller.util.OFMessageUtils; import org.projectfloodlight.openflow.protocol.OFControllerRole; import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFPacketIn; import org.projectfloodlight.openflow.protocol.OFPacketOut; import org.projectfloodlight.openflow.protocol.OFPortDesc; import org.projectfloodlight.openflow.protocol.OFPortState; import org.projectfloodlight.openflow.protocol.OFVersion; import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.EthType; import org.projectfloodlight.openflow.types.MacAddress; import org.projectfloodlight.openflow.types.OFBufferId; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.U64; import org.projectfloodlight.openflow.protocol.OFType; import org.projectfloodlight.openflow.protocol.action.OFAction; import org.projectfloodlight.openflow.protocol.match.MatchField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class sends out LLDP messages containing the sending switch's datapath * id as well as the outgoing port number. Received LLrescDP messages that match * a known switch cause a new LinkTuple to be created according to the invariant * rules listed below. This new LinkTuple is also passed to routing if it exists * to trigger updates. This class also handles removing links that are * associated to switch ports that go down, and switches that are disconnected. * Invariants: -portLinks and switchLinks will not contain empty Sets outside of * critical sections -portLinks contains LinkTuples where one of the src or dst * SwitchPortTuple matches the map key -switchLinks contains LinkTuples where * one of the src or dst SwitchPortTuple's id matches the switch id -Each * LinkTuple will be indexed into switchLinks for both src.id and dst.id, and * portLinks for each src and dst -The updates queue is only added to from * within a held write lock * * @edited Ryan Izard, rizard@g.clemson.edu, ryan.izard@bigswitch.com */ public class LinkDiscoveryManager implements IOFMessageListener, IOFSwitchListener, IStorageSourceListener, ILinkDiscoveryService, IFloodlightModule, IInfoProvider { protected static final Logger log = LoggerFactory.getLogger(LinkDiscoveryManager.class); public static final String MODULE_NAME = "linkdiscovery"; // Names of table/fields for links in the storage API private static final String TOPOLOGY_TABLE_NAME = "controller_topologyconfig"; private static final String TOPOLOGY_ID = "id"; private static final String TOPOLOGY_AUTOPORTFAST = "autoportfast"; private static final String LINK_TABLE_NAME = "controller_link"; private static final String LINK_ID = "id"; private static final String LINK_SRC_SWITCH = "src_switch_id"; private static final String LINK_SRC_PORT = "src_port"; private static final String LINK_DST_SWITCH = "dst_switch_id"; private static final String LINK_DST_PORT = "dst_port"; private static final String LINK_VALID_TIME = "valid_time"; private static final String LINK_TYPE = "link_type"; private static final String SWITCH_CONFIG_TABLE_NAME = "controller_switchconfig"; protected IFloodlightProviderService floodlightProviderService; protected IOFSwitchService switchService; protected IStorageSourceService storageSourceService; protected IThreadPoolService threadPoolService; protected IRestApiService restApiService; protected IDebugCounterService debugCounterService; protected IShutdownService shutdownService; // Role protected HARole role; // LLDP and BDDP fields private static final byte[] LLDP_STANDARD_DST_MAC_STRING = MacAddress.of("01:80:c2:00:00:0e").getBytes(); 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 // BigSwitch OUI is 5C:16:C7, so 5D:16:C7 is the multicast version // private static final String LLDP_BSN_DST_MAC_STRING = // "5d:16:c7:00:00:01"; private static final String LLDP_BSN_DST_MAC_STRING = "ff:ff:ff:ff:ff:ff"; // Direction TLVs are used to indicate if the LLDPs were sent // periodically or in response to a recieved LLDP private static final byte TLV_DIRECTION_TYPE = 0x73; 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(TLV_DIRECTION_TYPE) .setLength(TLV_DIRECTION_LENGTH) .setValue(TLV_DIRECTION_VALUE_FORWARD); private static final LLDPTLV reverseTLV = new LLDPTLV().setType(TLV_DIRECTION_TYPE) .setLength(TLV_DIRECTION_LENGTH) .setValue(TLV_DIRECTION_VALUE_REVERSE); // Link discovery task details. protected SingletonTask discoveryTask; protected final int DISCOVERY_TASK_INTERVAL = 1; protected final int LINK_TIMEOUT = 35; // timeout as part of LLDP process. protected final int LLDP_TO_ALL_INTERVAL = 15; // 15 seconds. protected long lldpClock = 0; // This value is intentionally kept higher than LLDP_TO_ALL_INTERVAL. // If we want to identify link failures faster, we could decrease this // value to a small number, say 1 or 2 sec. protected final int LLDP_TO_KNOWN_INTERVAL = 20; // LLDP frequency for known // links protected LLDPTLV controllerTLV; protected ReentrantReadWriteLock lock; int lldpTimeCount = 0; /* * Latency tracking */ protected static int LATENCY_HISTORY_SIZE = 10; protected static double LATENCY_UPDATE_THRESHOLD = 0.50; /** * Flag to indicate if automatic port fast is enabled or not. Default is set * to false -- Initialized in the init method as well. */ protected boolean AUTOPORTFAST_DEFAULT = false; protected boolean autoPortFastFeature = AUTOPORTFAST_DEFAULT; /** * Map from link to the most recent time it was verified functioning */ protected Map<Link, LinkInfo> links; /** * Map from switch id to a set of all links with it as an endpoint */ protected Map<DatapathId, Set<Link>> switchLinks; /** * Map from a id:port to the set of links containing it as an endpoint */ protected Map<NodePortTuple, Set<Link>> portLinks; protected volatile boolean shuttingDown = false; /* * topology aware components are called in the order they were added to the * the array */ protected ArrayList<ILinkDiscoveryListener> linkDiscoveryAware; protected BlockingQueue<LDUpdate> updates; protected Thread updatesThread; /** * List of ports through which LLDP/BDDPs are not sent. */ protected Set<NodePortTuple> suppressLinkDiscovery; /** * A list of ports that are quarantined for discovering links through them. * Data traffic from these ports are not allowed until the ports are * released from quarantine. */ protected LinkedBlockingQueue<NodePortTuple> quarantineQueue; protected LinkedBlockingQueue<NodePortTuple> maintenanceQueue; protected LinkedBlockingQueue<NodePortTuple> toRemoveFromQuarantineQueue; protected LinkedBlockingQueue<NodePortTuple> toRemoveFromMaintenanceQueue; /** * Quarantine task */ protected SingletonTask bddpTask; protected final int BDDP_TASK_INTERVAL = 100; // 100 ms. protected final int BDDP_TASK_SIZE = 10; // # of ports per iteration private class MACRange { MacAddress baseMAC; int ignoreBits; } protected Set<MACRange> ignoreMACSet; private IHAListener haListener; /** * Debug Counters */ private IDebugCounter ctrQuarantineDrops; private IDebugCounter ctrIgnoreSrcMacDrops; private IDebugCounter ctrIncoming; private IDebugCounter ctrLinkLocalDrops; private IDebugCounter ctrLldpEol; private final String PACKAGE = LinkDiscoveryManager.class.getPackage().getName(); //********************* // ILinkDiscoveryService //********************* @Override public OFPacketOut generateLLDPMessage(IOFSwitch iofSwitch, OFPort port, boolean isStandard, boolean isReverse) { OFPortDesc ofpPort = iofSwitch.getPort(port); if (log.isTraceEnabled()) { log.trace("Sending LLDP packet out of swich: {}, port: {}, reverse: {}", new Object[] {iofSwitch.getId().toString(), port.toString(), Boolean.toString(isReverse)}); } // 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-00 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); DatapathId dpid = iofSwitch.getId(); dpidBB.putLong(dpid.getLong()); // 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.getHwAddr().getBytes(); byte[] zeroMac = { 0, 0, 0, 0, 0, 0 }; if (Arrays.equals(srcMac, zeroMac)) { log.warn("Port {}/{} has zero hardware address" + "overwrite with lower 6 bytes of dpid", dpid.toString(), ofpPort.getPortNo().getPortNumber()); System.arraycopy(dpidArray, 2, srcMac, 0, 6); } // set the portId to the outgoing port portBB.putShort(port.getShortPortNumber()); 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); } /* * Introduce a new TLV for med-granularity link latency detection. * If same controller, can assume system clock is the same, but * cannot guarantee processing time or account for network congestion. * * Need to include our OpenFlow OUI - 00-26-E1-01 (note 01; 00 is DPID); * save last 8 bytes for long (time in ms). * * Note Long.SIZE is in bits (64). */ long time = System.currentTimeMillis(); long swLatency = iofSwitch.getLatency().getValue(); if (log.isTraceEnabled()) { log.trace("SETTING LLDP LATENCY TLV: Current Time {}; {} control plane latency {}; sum {}", new Object[] { time, iofSwitch.getId(), swLatency, time + swLatency }); } byte[] timestampTLVValue = ByteBuffer.allocate(Long.SIZE / 8 + 4) .put((byte) 0x00) .put((byte) 0x26) .put((byte) 0xe1) .put((byte) 0x01) /* 0x01 is what we'll use to differentiate DPID (0x00) from time (0x01) */ .putLong(time + swLatency /* account for our switch's one-way latency */) .array(); LLDPTLV timestampTLV = new LLDPTLV() .setType((byte) 127) .setLength((short) timestampTLVValue.length) .setValue(timestampTLVValue); /* Now add TLV to our LLDP packet */ lldp.getOptionalTLVList().add(timestampTLV); Ethernet ethernet; if (isStandard) { ethernet = new Ethernet().setSourceMACAddress(ofpPort.getHwAddr()) .setDestinationMACAddress(LLDP_STANDARD_DST_MAC_STRING) .setEtherType(EthType.LLDP); ethernet.setPayload(lldp); } else { BSN bsn = new BSN(BSN.BSN_TYPE_BDDP); bsn.setPayload(lldp); ethernet = new Ethernet().setSourceMACAddress(ofpPort.getHwAddr()) .setDestinationMACAddress(LLDP_BSN_DST_MAC_STRING) .setEtherType(EthType.of(Ethernet.TYPE_BSN & 0xffff)); /* treat as unsigned */ ethernet.setPayload(bsn); } // serialize and wrap in a packet out byte[] data = ethernet.serialize(); OFPacketOut.Builder pob = iofSwitch.getOFFactory().buildPacketOut() .setBufferId(OFBufferId.NO_BUFFER) .setActions(getDiscoveryActions(iofSwitch, port)) .setData(data); OFMessageUtils.setInPort(pob, OFPort.CONTROLLER); log.debug("{}", pob.build()); return pob.build(); } /** * Get the LLDP sending period in seconds. * * @return LLDP sending period in seconds. */ public int getLldpFrequency() { return LLDP_TO_KNOWN_INTERVAL; } /** * 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; } /** * Add a switch port to the suppressed LLDP list. Remove any known links on * the switch port. */ @Override public void AddToSuppressLLDPs(DatapathId sw, OFPort port) { NodePortTuple npt = new NodePortTuple(sw, port); this.suppressLinkDiscovery.add(npt); deleteLinksOnPort(npt, "LLDP suppressed."); } /** * Remove a switch port from the suppressed LLDP list. Discover links on * that switchport. */ @Override public void RemoveFromSuppressLLDPs(DatapathId sw, OFPort port) { NodePortTuple npt = new NodePortTuple(sw, port); this.suppressLinkDiscovery.remove(npt); discover(npt); } public boolean isShuttingDown() { return shuttingDown; } @Override public boolean isTunnelPort(DatapathId sw, OFPort port) { return false; } @Override public ILinkDiscovery.LinkType getLinkType(Link lt, LinkInfo info) { if (info.getUnicastValidTime() != null) { return ILinkDiscovery.LinkType.DIRECT_LINK; } else if (info.getMulticastValidTime() != null) { return ILinkDiscovery.LinkType.MULTIHOP_LINK; } return ILinkDiscovery.LinkType.INVALID_LINK; } @Override public Set<OFPort> getQuarantinedPorts(DatapathId sw) { Set<OFPort> qPorts = new HashSet<OFPort>(); Iterator<NodePortTuple> iter = quarantineQueue.iterator(); while (iter.hasNext()) { NodePortTuple npt = iter.next(); if (npt.getNodeId().equals(sw)) { qPorts.add(npt.getPortId()); } } return qPorts; } @Override public Map<DatapathId, Set<Link>> getSwitchLinks() { return this.switchLinks; } @Override public void addMACToIgnoreList(MacAddress mac, int ignoreBits) { MACRange range = new MACRange(); range.baseMAC = mac; range.ignoreBits = ignoreBits; ignoreMACSet.add(range); } @Override public boolean isAutoPortFastFeature() { return autoPortFastFeature; } @Override public void setAutoPortFastFeature(boolean autoPortFastFeature) { this.autoPortFastFeature = autoPortFastFeature; } @Override public void addListener(ILinkDiscoveryListener listener) { linkDiscoveryAware.add(listener); } @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; } @Override public LinkInfo getLinkInfo(Link link) { lock.readLock().lock(); LinkInfo linkInfo = links.get(link); LinkInfo retLinkInfo = null; if (linkInfo != null) { retLinkInfo = new LinkInfo(linkInfo); } lock.readLock().unlock(); return retLinkInfo; } @Override public String getName() { return MODULE_NAME; } //********************* // OFMessage Listener //********************* @Override public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { switch (msg.getType()) { case PACKET_IN: ctrIncoming.increment(); return this.handlePacketIn(sw.getId(), (OFPacketIn) msg, cntx); default: break; } return Command.CONTINUE; } @Override public boolean isCallbackOrderingPrereq(OFType type, String name) { return false; } @Override public boolean isCallbackOrderingPostreq(OFType type, String name) { return false; } //*********************************** // Internal Methods - Packet-in Processing Related //*********************************** protected Command handlePacketIn(DatapathId sw, OFPacketIn pi, FloodlightContext cntx) { Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)); if (eth.getPayload() instanceof 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, inPort, false, cntx); } else if (eth.getPayload() instanceof LLDP) { return handleLldp((LLDP) eth.getPayload(), sw, inPort, true, cntx); } else if (eth.getEtherType().getValue() < 1536 && eth.getEtherType().getValue() >= 17) { long destMac = eth.getDestinationMACAddress().getLong(); if ((destMac & LINK_LOCAL_MASK) == LINK_LOCAL_VALUE) { ctrLinkLocalDrops.increment(); if (log.isTraceEnabled()) { log.trace("Ignoring packet addressed to 802.1D/Q " + "reserved address."); } return Command.STOP; } } else if (eth.getEtherType().getValue() < 17) { log.error("Received invalid ethertype of {}.", eth.getEtherType()); return Command.STOP; } if (ignorePacketInFromSource(eth.getSourceMACAddress())) { ctrIgnoreSrcMacDrops.increment(); return Command.STOP; } // If packet-in is from a quarantine port, stop processing. NodePortTuple npt = new NodePortTuple(sw, inPort); if (quarantineQueue.contains(npt)) { ctrQuarantineDrops.increment(); return Command.STOP; } return Command.CONTINUE; } private boolean ignorePacketInFromSource(MacAddress 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.getLong() & mask) == (srcMAC.getLong() & mask)) { return true; } } } return false; } private Command handleLldp(LLDP lldp, DatapathId sw, OFPort inPort, boolean isStandard, FloodlightContext cntx) { // If LLDP is suppressed on this port, ignore received packet as well IOFSwitch iofSwitch = switchService.getSwitch(sw); log.debug("Received LLDP packet on sw {}, port {}", sw, inPort); if (!isIncomingDiscoveryAllowed(sw, inPort, isStandard)) return Command.STOP; // If this is a malformed LLDP exit if (lldp.getPortId() == null || lldp.getPortId().getLength() != 3) { return Command.STOP; } long myId = ByteBuffer.wrap(controllerTLV.getValue()).getLong(); long otherId = 0; boolean myLLDP = false; Boolean isReverse = null; ByteBuffer portBB = ByteBuffer.wrap(lldp.getPortId().getValue()); portBB.position(1); OFPort remotePort = OFPort.of(portBB.getShort()); IOFSwitch remoteSwitch = null; long timestamp = 0; // 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 = switchService.getSwitch(DatapathId.of(dpidBB.getLong(4))); } else if (lldptlv.getType() == 127 && lldptlv.getLength() == 12 && lldptlv.getValue()[0] == 0x0 && lldptlv.getValue()[1] == 0x26 && lldptlv.getValue()[2] == (byte) 0xe1 && lldptlv.getValue()[3] == 0x01) { /* 0x01 for timestamp */ ByteBuffer tsBB = ByteBuffer.wrap(lldptlv.getValue()); /* skip OpenFlow OUI (4 bytes above) */ long swLatency = iofSwitch.getLatency().getValue(); timestamp = tsBB.getLong(4); /* include the RX switch latency to "subtract" it */ if (log.isTraceEnabled()) { log.trace("RECEIVED LLDP LATENCY TLV: Got timestamp of {}; Switch {} latency of {}", new Object[] { timestamp, iofSwitch.getId(), iofSwitch.getLatency().getValue() }); } timestamp = timestamp + swLatency; } 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; } } 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 (after checking IDs) if (isStandard) { if (log.isTraceEnabled()) { log.trace("Got a standard LLDP=[{}] that was not sent by" + " this controller. 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; } return Command.STOP; } 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; } if (!remoteSwitch.portEnabled(remotePort)) { if (log.isTraceEnabled()) { log.trace("Ignoring link with disabled source port: switch {} port {} {}", new Object[] { remoteSwitch.getId().toString(), remotePort, remoteSwitch.getPort(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 {} {}", new Object[] { remoteSwitch.getId().toString(), remotePort, remoteSwitch.getPort(remotePort)}); } return Command.STOP; } if (!iofSwitch.portEnabled(inPort)) { if (log.isTraceEnabled()) { log.trace("Ignoring link with disabled dest port: switch {} port {} {}", new Object[] { sw.toString(), inPort.getPortNumber(), iofSwitch.getPort(inPort).getPortNo().getPortNumber()}); } return Command.STOP; } // Store the time of update to this link, and push it out to // routingEngine long time = System.currentTimeMillis(); U64 latency = (timestamp != 0 && (time - timestamp) > 0) ? U64.of(time - timestamp) : U64.ZERO; if (log.isTraceEnabled()) { log.trace("COMPUTING FINAL DATAPLANE LATENCY: Current time {}; Dataplane+{} latency {}; Overall latency from {} to {} is {}", new Object[] { time, iofSwitch.getId(), timestamp, remoteSwitch.getId(), iofSwitch.getId(), String.valueOf(latency.getValue()) }); } Link lt = new Link(remoteSwitch.getId(), remotePort, iofSwitch.getId(), inPort, latency); if (!isLinkAllowed(lt.getSrc(), lt.getSrcPort(), lt.getDst(), lt.getDstPort())) return Command.STOP; // Continue only if link is allowed. Date lastLldpTime = null; Date lastBddpTime = null; Date firstSeenTime = new Date(System.currentTimeMillis()); if (isStandard) { lastLldpTime = new Date(firstSeenTime.getTime()); } else { lastBddpTime = new Date(firstSeenTime.getTime()); } LinkInfo newLinkInfo = new LinkInfo(firstSeenTime, lastLldpTime, lastBddpTime); addOrUpdateLink(lt, newLinkInfo); // 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. newLinkInfo = links.get(lt); if (newLinkInfo != null && isStandard && isReverse == false) { Link reverseLink = new Link(lt.getDst(), lt.getDstPort(), lt.getSrc(), lt.getSrcPort(), U64.ZERO); /* latency not used; not important what the value is, since it's intentionally not in equals() */ LinkInfo reverseInfo = links.get(reverseLink); if (reverseInfo == null) { // the reverse link does not exist. if (newLinkInfo.getFirstSeenTime().getTime() > System.currentTimeMillis() - LINK_TIMEOUT) { log.debug("Sending reverse LLDP for link {}", lt); this.sendDiscoveryMessage(lt.getDst(), lt.getDstPort(), isStandard, true); } } } // 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(), latency); // srcPortState and dstPort state are reversed. LinkInfo reverseInfo = new LinkInfo(firstSeenTime, lastLldpTime, lastBddpTime); addOrUpdateLink(reverseLink, reverseInfo); } // Queue removal of 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()); flagToRemoveFromQuarantineQueue(nptSrc); flagToRemoveFromMaintenanceQueue(nptSrc); flagToRemoveFromQuarantineQueue(nptDst); flagToRemoveFromMaintenanceQueue(nptDst); // Consume this message ctrLldpEol.increment(); return Command.STOP; } //*********************************** // Internal Methods - Port Status/ New Port Processing Related //*********************************** /** * 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(DatapathId sw, OFPort p) { if (isLinkDiscoverySuppressed(sw, p)) { // Do nothing as link discovery is suppressed. return; } IOFSwitch iofSwitch = switchService.getSwitch(sw); if (iofSwitch == null) { return; } NodePortTuple npt = new NodePortTuple(sw, p); discover(sw, p); addToQuarantineQueue(npt); } //*********************************** // Internal Methods - Discovery Related //*********************************** 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()); } if (linkDiscoveryAware != null && !updateList.isEmpty()) { if (log.isDebugEnabled()) { log.debug("Dispatching link discovery update {} {} {} {} {} {}ms for {}", new Object[] { update.getOperation(), update.getSrc(), update.getSrcPort(), update.getDst(), update.getDstPort(), update.getLatency().getValue(), 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); } protected boolean isLinkDiscoverySuppressed(DatapathId sw, OFPort portNumber) { return this.suppressLinkDiscovery.contains(new NodePortTuple(sw, portNumber)); } protected void discoverLinks() { // timeout known links. timeoutLinks(); // increment LLDP clock lldpClock = (lldpClock + 1) % LLDP_TO_ALL_INTERVAL; if (lldpClock == 0) { if (log.isTraceEnabled()) log.trace("Sending LLDP out on all ports."); discoverOnAllPorts(); } } /** * 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); } } } /** * 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); } } /** * 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)); }*/ protected void flagToRemoveFromQuarantineQueue(NodePortTuple npt) { if (toRemoveFromQuarantineQueue.contains(npt) == false) { toRemoveFromQuarantineQueue.add(npt); } } /** * Add a switch port to maintenance queue. * * @param npt */ protected void addToMaintenanceQueue(NodePortTuple npt) { if (maintenanceQueue.contains(npt) == false) { maintenanceQueue.add(npt); } } /** * 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)); } */ protected void flagToRemoveFromMaintenanceQueue(NodePortTuple npt) { if (toRemoveFromMaintenanceQueue.contains(npt) == false) { toRemoveFromMaintenanceQueue.add(npt); } } /** * 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>(); while (count < BDDP_TASK_SIZE && quarantineQueue.peek() != null) { NodePortTuple npt; npt = quarantineQueue.remove(); /* * Do not send a discovery message if we already have received one * from another switch on this same port. In other words, if * handleLldp() determines there is a new link between two ports of * two switches, then there is no need to re-discover the link again. * * By flagging the item in handleLldp() and waiting to remove it * from the queue when processBDDPLists() runs, we can guarantee a * PORT_STATUS update is generated and dispatched below by * generateSwitchPortStatusUpdate(). */ if (!toRemoveFromQuarantineQueue.remove(npt)) { sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, false); } /* * Still add the item to the list though, so that the PORT_STATUS update * is generated below at the end of this function. */ nptList.add(npt); count++; } count = 0; while (count < BDDP_TASK_SIZE && maintenanceQueue.peek() != null) { NodePortTuple npt; npt = maintenanceQueue.remove(); /* * Same as above, except we don't care about the PORT_STATUS message; * we only want to avoid sending the discovery message again. */ if (!toRemoveFromMaintenanceQueue.remove(npt)) { sendDiscoveryMessage(npt.getNodeId(), npt.getPortId(), false, false); } count++; } for (NodePortTuple npt : nptList) { generateSwitchPortStatusUpdate(npt.getNodeId(), npt.getPortId()); } } private void generateSwitchPortStatusUpdate(DatapathId sw, OFPort port) { UpdateOperation operation; IOFSwitch iofSwitch = switchService.getSwitch(sw); if (iofSwitch == null) return; OFPortDesc ofp = iofSwitch.getPort(port); if (ofp == null) return; Set<OFPortState> srcPortState = ofp.getState(); boolean portUp = !srcPortState.contains(OFPortState.STP_BLOCK); if (portUp) { operation = UpdateOperation.PORT_UP; } else { operation = UpdateOperation.PORT_DOWN; } updates.add(new LDUpdate(sw, port, operation)); } protected void discover(NodePortTuple npt) { discover(npt.getNodeId(), npt.getPortId()); } protected void discover(DatapathId sw, OFPort port) { sendDiscoveryMessage(sw, port, true, false); } /** * Check if incoming discovery messages are enabled or not. * @param sw * @param port * @param isStandard * @return */ protected boolean isIncomingDiscoveryAllowed(DatapathId sw, OFPort port, boolean isStandard) { if (isLinkDiscoverySuppressed(sw, port)) { /* Do not process LLDPs from this port as suppressLLDP is set */ return false; } IOFSwitch iofSwitch = switchService.getSwitch(sw); if (iofSwitch == null) { return false; } if (port == OFPort.LOCAL) return false; OFPortDesc ofpPort = iofSwitch.getPort(port); if (ofpPort == null) { if (log.isTraceEnabled()) { log.trace("Null physical port. sw={}, port={}", sw.toString(), port.getPortNumber()); } return false; } return true; } /** * Check if outgoing discovery messages are enabled or not. * @param sw * @param port * @param isStandard * @param isReverse * @return */ protected boolean isOutgoingDiscoveryAllowed(DatapathId sw, OFPort port, boolean isStandard, boolean isReverse) { if (isLinkDiscoverySuppressed(sw, port)) { /* Dont send LLDPs out of this port as suppressLLDP is set */ return false; } IOFSwitch iofSwitch = switchService.getSwitch(sw); if (iofSwitch == null) { return false; } else if (iofSwitch.getControllerRole() == OFControllerRole.ROLE_SLAVE) { return false; } if (port == OFPort.LOCAL) return false; OFPortDesc ofpPort = iofSwitch.getPort(port); if (ofpPort == null) { if (log.isTraceEnabled()) { log.trace("Null physical port. sw={}, port={}", sw.toString(), port.getPortNumber()); } return false; } else { return true; } } /** * 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, OFPort port) { // set actions List<OFAction> actions = new ArrayList<OFAction>(); actions.add(sw.getOFFactory().actions().buildOutput().setPort(port).build()); return actions; } /** * 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 */ protected boolean sendDiscoveryMessage(DatapathId sw, OFPort port, boolean isStandard, boolean isReverse) { // Takes care of all checks including null pointer checks. if (!isOutgoingDiscoveryAllowed(sw, port, isStandard, isReverse)) { return false; } IOFSwitch iofSwitch = switchService.getSwitch(sw); if (iofSwitch == null) { // fix dereference violations in case race conditions return false; } return iofSwitch.write(generateLLDPMessage(iofSwitch, port, isStandard, isReverse)); } /** * Send LLDPs to all switch-ports */ protected void discoverOnAllPorts() { log.info("Sending LLDP packets out of all the enabled ports"); // Send standard LLDPs for (DatapathId sw : switchService.getAllSwitchDpids()) { IOFSwitch iofSwitch = switchService.getSwitch(sw); if (iofSwitch == null) continue; if (!iofSwitch.isActive()) continue; /* can't do anything if the switch is SLAVE */ Collection<OFPort> c = iofSwitch.getEnabledPortNumbers(); if (c != null) { for (OFPort ofp : c) { if (isLinkDiscoverySuppressed(sw, ofp)) { continue; } log.trace("Enabled port: {}", ofp); sendDiscoveryMessage(sw, ofp, true, false); // If the switch port is not already in the maintenance // queue, add it. NodePortTuple npt = new NodePortTuple(sw, ofp); addToMaintenanceQueue(npt); } } } } protected UpdateOperation getUpdateOperation(OFPortState srcPortState, OFPortState dstPortState) { boolean added = ((srcPortState != OFPortState.STP_BLOCK) && (dstPortState != OFPortState.STP_BLOCK)); if (added) { return UpdateOperation.LINK_UPDATED; } else { return UpdateOperation.LINK_REMOVED; } } protected UpdateOperation getUpdateOperation(OFPortState srcPortState) { boolean portUp = (srcPortState != OFPortState.STP_BLOCK); if (portUp) { return UpdateOperation.PORT_UP; } else { return UpdateOperation.PORT_DOWN; } } //************************************ // Internal Methods - Link Operations Related //************************************ /** * This method is used to specifically ignore/consider specific links. */ protected boolean isLinkAllowed(DatapathId src, OFPort srcPort, DatapathId dst, OFPort dstPort) { return true; } private boolean addLink(Link lt, LinkInfo newInfo) { NodePortTuple srcNpt, dstNpt; srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort()); dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort()); // index it by switch source if (!switchLinks.containsKey(lt.getSrc())) switchLinks.put(lt.getSrc(), new HashSet<Link>()); switchLinks.get(lt.getSrc()).add(lt); // index it by switch dest if (!switchLinks.containsKey(lt.getDst())) switchLinks.put(lt.getDst(), new HashSet<Link>()); switchLinks.get(lt.getDst()).add(lt); // index both ends by switch:port if (!portLinks.containsKey(srcNpt)) portLinks.put(srcNpt, new HashSet<Link>()); portLinks.get(srcNpt).add(lt); if (!portLinks.containsKey(dstNpt)) portLinks.put(dstNpt, new HashSet<Link>()); portLinks.get(dstNpt).add(lt); newInfo.addObservedLatency(lt.getLatency()); return true; } /** * Determine if a link should be updated and set the time stamps if it should. * Also, determine the correct latency value for the link. An existing link * will have a list of latencies associated with its LinkInfo. If enough time has * elapsed to determine a good latency baseline average and the new average is * greater or less than the existing latency value by a set threshold, then the * latency should be updated. This allows for latencies to be smoothed and reduces * the number of link updates due to small fluctuations (or outliers) in instantaneous * link latency values. * * @param lt with observed latency. Will be replaced with latency to use. * @param existingInfo with past observed latencies and time stamps * @param newInfo with updated time stamps * @return true if update occurred; false if no update should be dispatched */ protected boolean updateLink(@Nonnull Link lk, @Nonnull LinkInfo existingInfo, @Nonnull LinkInfo newInfo) { boolean linkChanged = false; boolean ignoreBDDP_haveLLDPalready = false; /* * Check if we are transitioning from one link type to another. * A transition is: * -- going from no LLDP time to an LLDP time (is OpenFlow link) * -- going from an LLDP time to a BDDP time (is non-OpenFlow link) * * Note: Going from LLDP to BDDP means our LLDP link must have timed * out already (null in existing LinkInfo). Otherwise, we'll flap * between mulitcast and unicast links. */ if (existingInfo.getMulticastValidTime() == null && newInfo.getMulticastValidTime() != null) { if (existingInfo.getUnicastValidTime() == null) { /* unicast must be null to go to multicast */ log.debug("Link is BDDP. Changed."); linkChanged = true; /* detected BDDP */ } else { ignoreBDDP_haveLLDPalready = true; } } else if (existingInfo.getUnicastValidTime() == null && newInfo.getUnicastValidTime() != null) { log.debug("Link is LLDP. Changed."); linkChanged = true; /* detected LLDP */ } /* * If we're undergoing an LLDP update (non-null time), grab the new LLDP time. * If we're undergoing a BDDP update (non-null time), grab the new BDDP time. * * Only do this if the new LinkInfo is non-null for each respective field. * We want to overwrite an existing LLDP/BDDP time stamp with null if it's * still valid. */ if (newInfo.getUnicastValidTime() != null) { existingInfo.setUnicastValidTime(newInfo.getUnicastValidTime()); } else if (newInfo.getMulticastValidTime() != null) { existingInfo.setMulticastValidTime(newInfo.getMulticastValidTime()); } /* * Update Link latency if we've accumulated enough latency data points * and if the average exceeds +/- the current stored latency by the * defined update threshold. */ U64 currentLatency = existingInfo.getCurrentLatency(); U64 latencyToUse = existingInfo.addObservedLatency(lk.getLatency()); if (currentLatency == null) { /* no-op; already 'changed' as this is a new link */ } else if (!latencyToUse.equals(currentLatency) && !ignoreBDDP_haveLLDPalready) { log.debug("Updating link {} latency to {}ms", lk.toKeyString(), latencyToUse.getValue()); lk.setLatency(latencyToUse); linkChanged = true; } else { log.trace("No need to update link latency {}", lk.toString()); } return linkChanged; } protected boolean addOrUpdateLink(Link lt, LinkInfo newInfo) { boolean linkChanged = false; lock.writeLock().lock(); try { /* * Put the new info only if new. We want a single LinkInfo * to exist per Link. This will allow us to track latencies * without having to conduct a deep, potentially expensive * copy each time a link is updated. */ LinkInfo existingInfo = null; if (links.get(lt) == null) { links.put(lt, newInfo); /* Only put if doesn't exist or null value */ } else { existingInfo = links.get(lt); } /* Update existing LinkInfo with most recent time stamp */ if (existingInfo != null && existingInfo.getFirstSeenTime().before(newInfo.getFirstSeenTime())) { existingInfo.setFirstSeenTime(newInfo.getFirstSeenTime()); } if (log.isTraceEnabled()) { log.trace("addOrUpdateLink: {} {}", lt, (newInfo.getMulticastValidTime() != null) ? "multicast" : "unicast"); } UpdateOperation updateOperation = null; linkChanged = false; if (existingInfo == null) { addLink(lt, newInfo); updateOperation = UpdateOperation.LINK_UPDATED; linkChanged = true; // Log direct links only. Multi-hop links may be numerous // Add all to event history LinkType linkType = getLinkType(lt, newInfo); if (linkType == ILinkDiscovery.LinkType.DIRECT_LINK) { log.debug("Inter-switch link detected: {}", lt); } } else { linkChanged = updateLink(lt, existingInfo, newInfo); if (linkChanged) { updateOperation = UpdateOperation.LINK_UPDATED; LinkType linkType = getLinkType(lt, newInfo); if (linkType == ILinkDiscovery.LinkType.DIRECT_LINK) { log.debug("Inter-switch link updated: {}", lt); } } } if (linkChanged) { // find out if the link was added or removed here. updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(), lt.getDst(), lt.getDstPort(), lt.getLatency(), getLinkType(lt, newInfo), updateOperation)); /* Update link structure (FIXME shouldn't have to do this, since it should be the same object) */ Iterator<Entry<Link, LinkInfo>> it = links.entrySet().iterator(); while (it.hasNext()) { Entry<Link, LinkInfo> entry = it.next(); if (entry.getKey().equals(lt)) { entry.getKey().setLatency(lt.getLatency()); break; } } } // Write changes to storage. This will always write the updated // valid time, plus the port states if they've changed (i.e. if // they weren't set to null in the previous block of code. writeLinkToStorage(lt, newInfo); } finally { lock.writeLock().unlock(); } return linkChanged; } /** * Delete a link * * @param link * - link to be deleted. * @param reason * - reason why the link is deleted. */ protected void deleteLink(Link link, String reason) { if (link == null) return; List<Link> linkList = new ArrayList<Link>(); linkList.add(link); deleteLinks(linkList, reason); } /** * Removes links from memory and storage. * * @param links * The List of @LinkTuple to delete. */ protected void deleteLinks(List<Link> links, String reason) { deleteLinks(links, reason, null); } /** * Removes links from memory and storage. * * @param links * The List of @LinkTuple to delete. */ protected void deleteLinks(List<Link> links, String reason, List<LDUpdate> updateList) { NodePortTuple srcNpt, dstNpt; List<LDUpdate> linkUpdateList = new ArrayList<LDUpdate>(); lock.writeLock().lock(); try { for (Link lt : links) { srcNpt = new NodePortTuple(lt.getSrc(), lt.getSrcPort()); dstNpt = new NodePortTuple(lt.getDst(), lt.getDstPort()); if (switchLinks.containsKey(lt.getSrc())) { switchLinks.get(lt.getSrc()).remove(lt); if (switchLinks.get(lt.getSrc()).isEmpty()) this.switchLinks.remove(lt.getSrc()); } if (this.switchLinks.containsKey(lt.getDst())) { switchLinks.get(lt.getDst()).remove(lt); if (this.switchLinks.get(lt.getDst()).isEmpty()) this.switchLinks.remove(lt.getDst()); } if (this.portLinks.get(srcNpt) != null) { this.portLinks.get(srcNpt).remove(lt); if (this.portLinks.get(srcNpt).isEmpty()) this.portLinks.remove(srcNpt); } if (this.portLinks.get(dstNpt) != null) { this.portLinks.get(dstNpt).remove(lt); if (this.portLinks.get(dstNpt).isEmpty()) this.portLinks.remove(dstNpt); } LinkInfo info = this.links.remove(lt); LinkType linkType = getLinkType(lt, info); linkUpdateList.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(), lt.getDst(), lt.getDstPort(), lt.getLatency(), linkType, UpdateOperation.LINK_REMOVED)); // remove link from storage. removeLinkFromStorage(lt); // TODO Whenever link is removed, it has to checked if // the switchports must be added to quarantine. if (linkType == ILinkDiscovery.LinkType.DIRECT_LINK) { log.info("Inter-switch link removed: {}", lt); } else if (log.isTraceEnabled()) { log.trace("Deleted link {}", lt); } } } finally { if (updateList != null) linkUpdateList.addAll(updateList); updates.addAll(linkUpdateList); lock.writeLock().unlock(); } } /** * Delete links incident on a given switch port. * * @param npt * @param reason */ protected void deleteLinksOnPort(NodePortTuple npt, String reason) { List<Link> eraseList = new ArrayList<Link>(); if (this.portLinks.containsKey(npt)) { if (log.isTraceEnabled()) { log.trace("handlePortStatus: Switch {} port #{} " + "removing links {}", new Object[] { npt.getNodeId().toString(), npt.getPortId(), this.portLinks.get(npt) }); } eraseList.addAll(this.portLinks.get(npt)); deleteLinks(eraseList, reason); } } /** * Iterates through the list of links and deletes if the last discovery * message reception time exceeds timeout values. */ protected void timeoutLinks() { List<Link> eraseList = new ArrayList<Link>(); Long curTime = System.currentTimeMillis(); boolean unicastTimedOut = false; /* Reentrant required here because deleteLink also write locks. */ lock.writeLock().lock(); try { Iterator<Entry<Link, LinkInfo>> it = this.links.entrySet().iterator(); while (it.hasNext()) { Entry<Link, LinkInfo> entry = it.next(); Link lt = entry.getKey(); LinkInfo info = entry.getValue(); /* Timeout the unicast and multicast LLDP valid times independently. */ if ((info.getUnicastValidTime() != null) && (info.getUnicastValidTime().getTime() + (this.LINK_TIMEOUT * 1000) < curTime)) { unicastTimedOut = true; info.setUnicastValidTime(null); } if ((info.getMulticastValidTime() != null) && (info.getMulticastValidTime().getTime() + (this.LINK_TIMEOUT * 1000) < curTime)) { info.setMulticastValidTime(null); } /* * Add to the erase list only if the unicast time is null * and the multicast time is null as well. Otherwise, if * only the unicast time is null and we just set it to * null (meaning it just timed out), then we transition * from unicast to multicast. */ if (info.getUnicastValidTime() == null && info.getMulticastValidTime() == null) { eraseList.add(entry.getKey()); } else if (unicastTimedOut) { /* Just moved from unicast to multicast. */ updates.add(new LDUpdate(lt.getSrc(), lt.getSrcPort(), lt.getDst(), lt.getDstPort(), lt.getLatency(), getLinkType(lt, info), UpdateOperation.LINK_UPDATED)); } } if (!eraseList.isEmpty()) { deleteLinks(eraseList, "LLDP timeout"); } } finally { lock.writeLock().unlock(); } } //****************** // 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; byte[] controllerTLVValue = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; // 8 // byte // value. ByteBuffer bb = ByteBuffer.allocate(10); long result = System.nanoTime(); try{ // Use some data specific to the machine this controller is // running on. In this case: the list of network interfaces Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); if (ifaces != null) { result = result * prime + ifaces.hashCode(); } } catch (SocketException e) { log.warn("Could not get list of interfaces of local machine to " + "encode in TLV: {}", e.toString()); } // 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 //****************** private void handlePortDown(DatapathId switchId, OFPort portNumber) { NodePortTuple npt = new NodePortTuple(switchId, portNumber); deleteLinksOnPort(npt, "Port Status Changed"); LDUpdate update = new LDUpdate(switchId, portNumber, UpdateOperation.PORT_DOWN); updates.add(update); } /** * 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(DatapathId switchId, OFPortDesc port, PortChangeType type) { switch (type) { case UP: processNewPort(switchId, port.getPortNo()); break; case DELETE: case DOWN: handlePortDown(switchId, port.getPortNo()); break; case OTHER_UPDATE: case ADD: // This is something other than port add or delete. // Topology does not worry about this. // If for some reason the port features change, which // we may have to react. break; } } @Override public void switchAdded(DatapathId switchId) { // no-op // We don't do anything at switch added, but we do only when the // switch is activated. } @Override public void switchRemoved(DatapathId sw) { List<Link> eraseList = new ArrayList<Link>(); lock.writeLock().lock(); try { if (switchLinks.containsKey(sw)) { if (log.isTraceEnabled()) { log.trace("Handle switchRemoved. Switch {}; removing links {}", sw.toString(), switchLinks.get(sw)); } List<LDUpdate> updateList = new ArrayList<LDUpdate>(); updateList.add(new LDUpdate(sw, SwitchType.BASIC_SWITCH, 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, SwitchType.BASIC_SWITCH, UpdateOperation.SWITCH_REMOVED)); } } finally { lock.writeLock().unlock(); } } @Override public void switchActivated(DatapathId switchId) { IOFSwitch sw = switchService.getSwitch(switchId); if (sw == null) //fix dereference violation in case race conditions return; if (sw.getEnabledPortNumbers() != null) { for (OFPort p : sw.getEnabledPortNumbers()) { processNewPort(sw.getId(), p); } } LDUpdate update = new LDUpdate(sw.getId(), SwitchType.BASIC_SWITCH, UpdateOperation.SWITCH_UPDATED); updates.add(update); } @Override public void switchChanged(DatapathId switchId) { // no-op } //********************* // Storage Listener //********************* /** * Sets the IStorageSource to use for Topology * * @param storageSource * the storage source to use */ public void setStorageSource(IStorageSourceService storageSourceService) { this.storageSourceService = storageSourceService; } /** * Gets the storage source for this ITopology * * @return The IStorageSource ITopology is writing to */ public IStorageSourceService getStorageSource() { return storageSourceService; } @Override public void rowsModified(String tableName, Set<Object> rowKeys) { if (tableName.equals(TOPOLOGY_TABLE_NAME)) { readTopologyConfigFromStorage(); return; } } @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(); } //****************************** // Internal methods - Config Related //****************************** protected void readTopologyConfigFromStorage() { IResultSet topologyResult = storageSourceService.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.debug("Setting autoportfast feature to ON"); else log.debug("Setting autoportfast feature to OFF"); } /** * Deletes all links from storage */ void clearAllLinks() { storageSourceService.deleteRowsAsync(LINK_TABLE_NAME, null); } /** * Writes a LinkTuple and corresponding LinkInfo to storage * * @param lt * The LinkTuple to write * @param linkInfo * The LinkInfo to write */ protected void writeLinkToStorage(Link lt, LinkInfo linkInfo) { LinkType type = getLinkType(lt, linkInfo); // Write only direct links. Do not write links to external // L2 network. // if (type != LinkType.DIRECT_LINK && type != LinkType.TUNNEL) { // return; // } Map<String, Object> rowValues = new HashMap<String, Object>(); String id = getLinkId(lt); rowValues.put(LINK_ID, id); rowValues.put(LINK_VALID_TIME, linkInfo.getUnicastValidTime()); String srcDpid = lt.getSrc().toString(); rowValues.put(LINK_SRC_SWITCH, srcDpid); rowValues.put(LINK_SRC_PORT, lt.getSrcPort()); if (type == LinkType.DIRECT_LINK) rowValues.put(LINK_TYPE, "internal"); else if (type == LinkType.MULTIHOP_LINK) rowValues.put(LINK_TYPE, "external"); else if (type == LinkType.TUNNEL) rowValues.put(LINK_TYPE, "tunnel"); else rowValues.put(LINK_TYPE, "invalid"); String dstDpid = lt.getDst().toString(); rowValues.put(LINK_DST_SWITCH, dstDpid); rowValues.put(LINK_DST_PORT, lt.getDstPort()); storageSourceService.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); storageSourceService.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 // form, because it's doing synchronous storage calls. Depending // on the context this may still be OK, but if it's being called // on the packet in processing thread it should be reworked to // use asynchronous storage calls. Long validTime = null; IResultSet resultSet = null; try { String[] columns = { LINK_VALID_TIME }; String id = getLinkId(lt); resultSet = storageSourceService.executeQuery(LINK_TABLE_NAME, columns, new OperatorPredicate( LINK_ID, OperatorPredicate.Operator.EQ, id), null); if (resultSet.next()) validTime = resultSet.getLong(LINK_VALID_TIME); } finally { if (resultSet != null) resultSet.close(); } return validTime; } /** * 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 lt.getSrc().toString() + "-" + lt.getSrcPort() + "-" + lt.getDst().toString() + "-" + lt.getDstPort(); } //*************** // IFloodlightModule //*************** @Override 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>(); // 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>>(); l.add(IFloodlightProviderService.class); l.add(IStorageSourceService.class); l.add(IThreadPoolService.class); l.add(IRestApiService.class); l.add(IShutdownService.class); return l; } @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class); switchService = context.getServiceImpl(IOFSwitchService.class); storageSourceService = context.getServiceImpl(IStorageSourceService.class); threadPoolService = context.getServiceImpl(IThreadPoolService.class); restApiService = context.getServiceImpl(IRestApiService.class); debugCounterService = context.getServiceImpl(IDebugCounterService.class); shutdownService = context.getServiceImpl(IShutdownService.class); // read our config options Map<String, String> configOptions = context.getConfigParams(this); try { String histSize = configOptions.get("event-history-size"); if (histSize != null) { EVENT_HISTORY_SIZE = Short.parseShort(histSize); } } catch (NumberFormatException e) { log.warn("Error event history size. Using default of {} seconds", EVENT_HISTORY_SIZE); } log.debug("Event history size set to {}", EVENT_HISTORY_SIZE); try { String latencyHistorySize = configOptions.get("latency-history-size"); if (latencyHistorySize != null) { LATENCY_HISTORY_SIZE = Integer.parseInt(latencyHistorySize); } } catch (NumberFormatException e) { log.warn("Error in latency history size. Using default of {} LLDP intervals", LATENCY_HISTORY_SIZE); } log.info("Link latency history set to {} LLDP data points", LATENCY_HISTORY_SIZE, LATENCY_HISTORY_SIZE); try { String latencyUpdateThreshold = configOptions.get("latency-update-threshold"); if (latencyUpdateThreshold != null) { LATENCY_UPDATE_THRESHOLD = Double.parseDouble(latencyUpdateThreshold); } } catch (NumberFormatException e) { log.warn("Error in latency update threshold. Can be from 0 to 1.", LATENCY_UPDATE_THRESHOLD); } log.info("Latency update threshold set to +/-{} ({}%) of rolling historical average", LATENCY_UPDATE_THRESHOLD, LATENCY_UPDATE_THRESHOLD * 100); // Set the autoportfast feature to false. this.autoPortFastFeature = AUTOPORTFAST_DEFAULT; // We create this here because there is no ordering guarantee this.linkDiscoveryAware = new ArrayList<ILinkDiscoveryListener>(); this.lock = new ReentrantReadWriteLock(); this.updates = new LinkedBlockingQueue<LDUpdate>(); this.links = new HashMap<Link, LinkInfo>(); this.portLinks = new HashMap<NodePortTuple, Set<Link>>(); this.suppressLinkDiscovery = Collections.synchronizedSet(new HashSet<NodePortTuple>()); this.switchLinks = new HashMap<DatapathId, Set<Link>>(); this.quarantineQueue = new LinkedBlockingQueue<NodePortTuple>(); this.maintenanceQueue = new LinkedBlockingQueue<NodePortTuple>(); this.toRemoveFromQuarantineQueue = new LinkedBlockingQueue<NodePortTuple>(); this.toRemoveFromMaintenanceQueue = new LinkedBlockingQueue<NodePortTuple>(); this.ignoreMACSet = Collections.newSetFromMap( new ConcurrentHashMap<MACRange,Boolean>()); this.haListener = new HAListenerDelegate(); this.floodlightProviderService.addHAListener(this.haListener); registerLinkDiscoveryDebugCounters(); } @Override public void startUp(FloodlightModuleContext context) throws FloodlightModuleException { // Initialize role to floodlight provider role. this.role = floodlightProviderService.getRole(); // Create our storage tables if (storageSourceService == null) { log.error("No storage source found."); return; } storageSourceService.createTable(TOPOLOGY_TABLE_NAME, null); storageSourceService.setTablePrimaryKeyName(TOPOLOGY_TABLE_NAME, TOPOLOGY_ID); readTopologyConfigFromStorage(); storageSourceService.createTable(LINK_TABLE_NAME, null); storageSourceService.setTablePrimaryKeyName(LINK_TABLE_NAME, LINK_ID); storageSourceService.deleteMatchingRows(LINK_TABLE_NAME, null); // Register for storage updates for the switch table try { storageSourceService.addListener(SWITCH_CONFIG_TABLE_NAME, this); storageSourceService.addListener(TOPOLOGY_TABLE_NAME, this); } catch (StorageException ex) { log.error("Error in installing listener for " + "switch table {}", SWITCH_CONFIG_TABLE_NAME); } ScheduledExecutorService ses = threadPoolService.getScheduledExecutor(); // To be started by the first switch connection discoveryTask = new SingletonTask(ses, new Runnable() { @Override public void run() { try { if (role == null || role == HARole.ACTIVE) { /* don't send if we just transitioned to STANDBY */ discoverLinks(); } } catch (StorageException e) { shutdownService.terminate("Storage exception in LLDP send timer. Terminating process " + e, 0); } catch (Exception e) { log.error("Exception in LLDP send timer.", e); } finally { if (!shuttingDown) { // null role implies HA mode is not enabled. if (role == null || role == HARole.ACTIVE) { log.trace("Rescheduling discovery task as role = {}", role); discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL, TimeUnit.SECONDS); } else { log.trace("Stopped LLDP rescheduling due to role = {}.", role); } } } } }); // null role implies HA mode is not enabled. if (role == null || role == HARole.ACTIVE) { log.trace("Setup: Rescheduling discovery task. role = {}", role); discoveryTask.reschedule(DISCOVERY_TASK_INTERVAL, TimeUnit.SECONDS); } else { log.trace("Setup: Not scheduling LLDP as role = {}.", role); } // Setup the BDDP task. It is invoked whenever switch port tuples // are added to the quarantine list. bddpTask = new SingletonTask(ses, new QuarantineWorker()); bddpTask.reschedule(BDDP_TASK_INTERVAL, TimeUnit.MILLISECONDS); updatesThread = new Thread(new Runnable() { @Override public void run() { while (true) { try { doUpdatesThread(); } catch (InterruptedException e) { return; } } } }, "Topology Updates"); updatesThread.start(); // Register for the OpenFlow messages we want to receive floodlightProviderService.addOFMessageListener(OFType.PACKET_IN, this); floodlightProviderService.addOFMessageListener(OFType.PORT_STATUS, this); // Register for switch updates switchService.addOFSwitchListener(this); floodlightProviderService.addHAListener(this.haListener); floodlightProviderService.addInfoProvider("summary", this); if (restApiService != null) restApiService.addRestletRoutable(new LinkDiscoveryWebRoutable()); setControllerTLV(); } // **************************************************** // Link Discovery DebugCounters and DebugEvents // **************************************************** private void registerLinkDiscoveryDebugCounters() throws FloodlightModuleException { if (debugCounterService == null) { log.error("Debug Counter Service not found."); } debugCounterService.registerModule(PACKAGE); ctrIncoming = debugCounterService.registerCounter(PACKAGE, "incoming", "All incoming packets seen by this module"); ctrLldpEol = debugCounterService.registerCounter(PACKAGE, "lldp-eol", "End of Life for LLDP packets"); ctrLinkLocalDrops = debugCounterService.registerCounter(PACKAGE, "linklocal-drops", "All link local packets dropped by this module"); ctrIgnoreSrcMacDrops = debugCounterService.registerCounter(PACKAGE, "ignore-srcmac-drops", "All packets whose srcmac is configured to be dropped by this module"); ctrQuarantineDrops = debugCounterService.registerCounter(PACKAGE, "quarantine-drops", "All packets arriving on quarantined ports dropped by this module", IDebugCounterService.MetaData.WARN); } //********************* // IInfoProvider //********************* @Override public Map<String, Object> getInfo(String type) { if (!"summary".equals(type)) return null; Map<String, Object> info = new HashMap<String, Object>(); int numDirectLinks = 0; for (Set<Link> links : switchLinks.values()) { for (Link link : links) { LinkInfo linkInfo = this.getLinkInfo(link); if (linkInfo != null && linkInfo.getLinkType() == LinkType.DIRECT_LINK) { numDirectLinks++; } } } info.put("# inter-switch links", numDirectLinks / 2); info.put("# quarantine ports", quarantineQueue.size()); return info; } //*************** // IHAListener //*************** private class HAListenerDelegate implements IHAListener { @Override public void transitionToActive() { log.warn("Sending LLDPs due to HA change from STANDBY->ACTIVE"); LinkDiscoveryManager.this.role = HARole.ACTIVE; clearAllLinks(); readTopologyConfigFromStorage(); log.debug("Role Change to Master: Rescheduling discovery tasks"); discoveryTask.reschedule(1, TimeUnit.MICROSECONDS); } @Override public void controllerNodeIPsChanged(Map<String, String> curControllerNodeIPs, Map<String, String> addedControllerNodeIPs, Map<String, String> removedControllerNodeIPs) { // ignore } @Override public String getName() { return MODULE_NAME; } @Override public boolean isCallbackOrderingPrereq(HAListenerTypeMarker type, String name) { return false; } @Override public boolean isCallbackOrderingPostreq(HAListenerTypeMarker type, String name) { return "tunnelmanager".equals(name); } @Override public void transitionToStandby() { log.warn("Disabling LLDPs due to HA change from ACTIVE->STANDBY"); LinkDiscoveryManager.this.role = HARole.STANDBY; } } @Override public void switchDeactivated(DatapathId switchId) { } }