/** * 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.devicemanager.internal; import java.util.ArrayList; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IInfoProvider; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IOFSwitchListener; 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.util.SingletonTask; import net.floodlightcontroller.devicemanager.Device; import net.floodlightcontroller.devicemanager.DeviceAttachmentPoint; import net.floodlightcontroller.devicemanager.DeviceNetworkAddress; import net.floodlightcontroller.devicemanager.IDeviceManagerService; import net.floodlightcontroller.devicemanager.IDeviceManagerAware; import net.floodlightcontroller.linkdiscovery.ILinkDiscovery; import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryListener; import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService; import net.floodlightcontroller.linkdiscovery.SwitchPortTuple; import net.floodlightcontroller.packet.ARP; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.routing.ForwardingBase; 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.topology.ITopologyListener; import net.floodlightcontroller.topology.ITopologyService; import net.floodlightcontroller.util.EventHistory; import net.floodlightcontroller.util.EventHistory.EvAction; import org.openflow.protocol.OFMatch; import org.openflow.protocol.OFMessage; import org.openflow.protocol.OFPacketIn; import org.openflow.protocol.OFPhysicalPort.OFPortConfig; import org.openflow.protocol.OFPhysicalPort.OFPortState; import org.openflow.protocol.OFPortStatus; import org.openflow.protocol.OFPortStatus.OFPortReason; import org.openflow.protocol.OFType; import org.openflow.util.HexString; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * DeviceManager creates Devices based upon MAC addresses seen in the network. * It tracks any network addresses mapped to the Device, and its location * within the network. * * @author David Erickson (daviderickson@cs.stanford.edu) */ public class DeviceManagerImpl implements IDeviceManagerService, IOFMessageListener, IOFSwitchListener, ILinkDiscoveryListener, IFloodlightModule, IStorageSourceListener, ITopologyListener, IInfoProvider { /** * Class to maintain all the device manager maps which consists of four * main maps. * (a) data layer address (mac) to device object * (b) ipv4 address to device object * (c) Switch object to device objects * (d) Switch port tuple object to device objects * * Also maintained is a pending attachment point object which is a * mapping from switch dpid to pending attachment point object * @author subrata */ public class DevMgrMaps { private Map<Long, Device> dataLayerAddressDeviceMap; private Map<Integer, Device> ipv4AddressDeviceMap; // The Integer below if the hashCode iof the device private Map<IOFSwitch, Map<Integer, Device>> switchDeviceMap; // The Integer below is the hashCode of the device private Map<SwitchPortTuple, Map<Integer, Device>> switchPortDeviceMap; private Map<Long, List<PendingAttachmentPoint>> switchUnresolvedAPMap; private Map<String, String> portChannelMap; public Map<Long, Device> getDataLayerAddressDeviceMap() { return dataLayerAddressDeviceMap; } public Map<Integer, Device> getIpv4AddressDeviceMap() { return ipv4AddressDeviceMap; } public Map<IOFSwitch, Map<Integer, Device>> getSwitchDeviceMap() { return switchDeviceMap; } public Map<SwitchPortTuple, Map<Integer, Device>> getSwitchPortDeviceMap() { return switchPortDeviceMap; } public Map<Long, List<PendingAttachmentPoint>> getSwitchUnresolvedAPMap() { return switchUnresolvedAPMap; } // Constructor for the device maps class public DevMgrMaps() { dataLayerAddressDeviceMap = new ConcurrentHashMap<Long, Device>(); ipv4AddressDeviceMap = new ConcurrentHashMap<Integer, Device>(); switchDeviceMap = new ConcurrentHashMap<IOFSwitch, Map<Integer, Device>>(); switchPortDeviceMap = new ConcurrentHashMap<SwitchPortTuple, Map<Integer, Device>>(); switchUnresolvedAPMap = new ConcurrentHashMap<Long, List<PendingAttachmentPoint>>(); portChannelMap = new ConcurrentHashMap<String, String>(); } // *********** // Get methods // *********** protected Collection<Device> getDevicesOnASwitch(IOFSwitch sw) { return switchDeviceMap.get(sw).values(); } protected int getSwitchCount() { return switchDeviceMap.size(); } protected Set<IOFSwitch> getSwitches() { return switchDeviceMap.keySet(); } protected Set<SwitchPortTuple> getSwitchPorts() { return switchPortDeviceMap.keySet(); } protected Collection<Device> getDevices() { lock.readLock().lock(); // Do we need the lock TODO try { return dataLayerAddressDeviceMap.values(); } finally { lock.readLock().unlock(); } } // ***************************************************** // Individual update and delete methods for given device // ***************************************************** private void updateDataLayerAddressDeviceMap(Device d) { dataLayerAddressDeviceMap.put(d.getDataLayerAddressAsLong(), d); } private void delFromDataLayerAddressDeviceMap(Device d) { dataLayerAddressDeviceMap.remove(d.getDataLayerAddressAsLong()); } private void updateIpv4AddressDeviceMap(Device d) { Collection< DeviceNetworkAddress> dNAColl = d.getNetworkAddresses(); for (DeviceNetworkAddress dna : dNAColl) { ipv4AddressDeviceMap.put(dna.getNetworkAddress(), d); } } private void delFromIpv4AddressDeviceMap(Device d) { Collection< DeviceNetworkAddress> dNAColl = d.getNetworkAddresses(); for (DeviceNetworkAddress dna : dNAColl) { ipv4AddressDeviceMap.remove(dna.getNetworkAddress()); } } private void delFromIpv4AddressDeviceMap(Integer ip, Device d) { ipv4AddressDeviceMap.remove(ip); } private void updateSwitchDeviceMap(Device d) { // Find all the attachment points of this device // Then find all the switches from the attachment points // Then update the device in maps for all those switches Collection<DeviceAttachmentPoint> dapColl = d.getAttachmentPoints(); for (DeviceAttachmentPoint dap : dapColl) { SwitchPortTuple swPrt = dap.getSwitchPort(); IOFSwitch sw = swPrt.getSw(); Map<Integer, Device> oneSwDevMap = switchDeviceMap.get(sw); if (oneSwDevMap == null) { oneSwDevMap = new ConcurrentHashMap<Integer, Device>(); switchDeviceMap.put(sw, oneSwDevMap); } oneSwDevMap.put(d.hashCode(), d); } } private void delFromSwitchDeviceMap(Device d) { // Find all the attachment points of this device // Then find all the switches from the attachment points // Then update the device in maps for all those switches Collection<DeviceAttachmentPoint> dapColl = d.getAttachmentPoints(); for (DeviceAttachmentPoint dap : dapColl) { SwitchPortTuple swPrt = dap.getSwitchPort(); IOFSwitch sw = swPrt.getSw(); Map<Integer, Device> oneSwDevMap = switchDeviceMap.get(sw); if (oneSwDevMap != null) { oneSwDevMap.remove(d.hashCode()); } } } private void delFromSwitchDeviceMap(IOFSwitch sw, Device d) { Map<Integer, Device> oneSwDevMap = switchDeviceMap.get(sw); if (oneSwDevMap != null) { oneSwDevMap.remove(d.hashCode()); } } protected void delSwitchfromMaps(IOFSwitch sw) { // Remove all switch:port mappings of the given switch sw Set<SwitchPortTuple> swPortSet = devMgrMaps.getSwitchPorts(); for (SwitchPortTuple swPort : swPortSet) { if (swPort.getSw().equals(sw)) { devMgrMaps.removeSwPort(swPort); } } // Not removing the switch from the SwitchDevice map, let it age out } private void updateSwitchPortDeviceMap(Device d) { // Find all the attachment points of this device // Then update the device in maps for all those attachment points Collection<DeviceAttachmentPoint> dapColl = d.getAttachmentPoints(); for (DeviceAttachmentPoint dap : dapColl) { SwitchPortTuple swPrt = dap.getSwitchPort(); Map<Integer, Device> oneSwPrtMap = switchPortDeviceMap.get(swPrt); if (oneSwPrtMap == null) { oneSwPrtMap = new ConcurrentHashMap<Integer, Device>(); switchPortDeviceMap.put(swPrt, oneSwPrtMap); } oneSwPrtMap.put(d.hashCode(), d); } } private void delFromSwitchPortDeviceMap(Device d) { // Find all the attachment points of this device // Then update the device in maps for all those attachment points Collection<DeviceAttachmentPoint> dapColl = d.getAttachmentPoints(); for (DeviceAttachmentPoint dap : dapColl) { SwitchPortTuple swPrt = dap.getSwitchPort(); Map<Integer, Device> oneSwPrtMap = switchPortDeviceMap.get(swPrt); if (oneSwPrtMap != null) { oneSwPrtMap.remove(d.hashCode()); } } } private void delFromSwitchPortDeviceMap( SwitchPortTuple swPort, Device d) { Map<Integer, Device> oneSwPrtMap = switchPortDeviceMap.get(swPort); if (oneSwPrtMap != null) { oneSwPrtMap.remove(d.hashCode()); } } /** * Overall update, delete and clear methods for the set of maps * @param d the device to update */ private synchronized void updateMaps(Device d) { // Update dataLayerAddressDeviceMap updateDataLayerAddressDeviceMap(d); // Update ipv4AddressDeviceMap updateIpv4AddressDeviceMap(d); // update switchDeviceMap updateSwitchDeviceMap(d); // Update switchPortDeviceMap updateSwitchPortDeviceMap(d); } /** * Delete a device object from the device maps * @param d the device to remove */ private void delFromMaps(Device d) { // Update dataLayerAddressDeviceMap delFromDataLayerAddressDeviceMap(d); // Update ipv4AddressDeviceMap delFromIpv4AddressDeviceMap(d); // update switchDeviceMap delFromSwitchDeviceMap(d); // Update switchPortDeviceMap delFromSwitchPortDeviceMap(d); } /** * Delete a device by a data layer address * @param dlAddr the address */ protected void delFromMaps(long dlAddr) { Device d = devMgrMaps.getDeviceByDataLayerAddr(dlAddr); if (d != null) { delFromMaps(d); } } /** * Clear all the device maps */ private void clearMaps() { dataLayerAddressDeviceMap.clear(); ipv4AddressDeviceMap.clear(); switchUnresolvedAPMap.clear(); switchDeviceMap.clear(); switchPortDeviceMap.clear(); } /** * Remove switch-port and also remove this switch-port as the * attachment point in the device objects if any * @param swPrt The {@link SwitchPortTuple} to remove */ protected void removeSwPort(SwitchPortTuple swPrt) { Map<Integer, Device> switchPortDevices = switchPortDeviceMap.remove(swPrt); if (switchPortDevices == null) { return; } // Update the individual devices by updating its attachment points for (Device d : switchPortDevices.values()) { // Remove the device from the switch->device mapping delDevAttachmentPoint(d.getDataLayerAddressAsLong(), swPrt); evHistAttachmtPt(d.getDataLayerAddressAsLong(), swPrt, EvAction.REMOVED, "SwitchPort removed"); } } /** * Retrieve the device by it's data layer address * @param mac the mac address to look up * @return the device object */ protected Device getDeviceByDataLayerAddr(long mac) { return dataLayerAddressDeviceMap.get(mac); } /** * Check whether the given switch exists in the switch maps * @param sw the switch to check * @return true if the switch exists */ protected boolean isSwitchPresent (IOFSwitch sw) { return switchDeviceMap.containsKey(sw); } /** * Reinitialize portChannelMap upon config change */ protected void clearPortChannelMap() { portChannelMap.clear(); } // *************************************** // Add operations on the device attributes // *************************************** /** * Add a network layer address to the device with the given link * layer address * @param dlAddr The link layer address of the device * @param nwAddr the new network layer address * @param lastSeen the new last seen timestamp for the network address */ protected void addNwAddrByDataLayerAddr(long dlAddr, int nwAddr, Date lastSeen) { Device d = getDeviceByDataLayerAddr(dlAddr); if (d == null) return; Device dCopy = new Device(d); DeviceNetworkAddress na = dCopy.getNetworkAddress(nwAddr); if (na == null) { // this particular address is not in the device na = new DeviceNetworkAddress(nwAddr, lastSeen); dCopy.addNetworkAddress(na); updateMaps(dCopy); } } /** * Delete a network address from a device * @param dlAddr The link layer address of the device * @param nwAddr the new network layer address */ protected void delNwAddrByDataLayerAddr(long dlAddr, int nwAddr) { Device d = getDeviceByDataLayerAddr(dlAddr); if (d == null) return; Device dCopy = new Device(d); DeviceNetworkAddress na = dCopy.getNetworkAddress(nwAddr); if (na != null) { delFromIpv4AddressDeviceMap(nwAddr, d); dCopy.removeNetworkAddress(na); updateMaps(dCopy); removeNetworkAddressFromStorage(d, na); } } /** * Add a device attachment point to the device with the given * link-layer address * @param mac the MAC address * @param swPort the {@link SwitchPortTuple} to add * @param lastSeen the new last seen timestamp */ protected void addDevAttachmentPoint(long mac, SwitchPortTuple swPort, Date lastSeen) { addDevAttachmentPoint(mac, swPort.getSw(), swPort.getPort(), lastSeen); } /** * Add a device attachment point to the device with the given * link-layer address * @param mac the MAC address * @param sw The {@link IOFSwitch} for the new attachment point * @param port The port for the new attachment point * @param lastSeen the new last seen timestamp */ protected boolean addDevAttachmentPoint(long mac, IOFSwitch sw, short port, Date lastSeen) { Device d = getDeviceByDataLayerAddr(mac); // Check if the attachment point is already there SwitchPortTuple swPort = new SwitchPortTuple(sw, port); DeviceAttachmentPoint dap = d.getAttachmentPoint(swPort); if (dap != null) { // The attachment point is already there dap.setLastSeen(lastSeen); // TODO return false; } // new device attachment point for this device dap = new DeviceAttachmentPoint(swPort, lastSeen); Device dCopy = new Device(d); dCopy.addAttachmentPoint(dap); // Now add this updated device to the maps, which will replace // the old copy updateMaps(dCopy); return true; } // ****************************************** // Delete operations on the device attributes // ****************************************** /** * Delete an attachment point from a device * @param d the device * @param swPort the {@link SwitchPortTuple} to remove */ protected void delDevAttachmentPoint(long dlAddr, SwitchPortTuple swPort) { delDevAttachmentPoint(devMgrMaps.getDeviceByDataLayerAddr(dlAddr), swPort.getSw(), swPort.getPort()); } /** * Delete an attachment point from a device * @param d the device * @param sw the {@link IOFSwitch} for the attachment point to remove * @param port the port for the attachment point to remove */ protected boolean delDevAttachmentPoint(Device d, IOFSwitch sw, short port) { // Check if the attachment point is there SwitchPortTuple swPort = new SwitchPortTuple(sw, port); if (d.getAttachmentPoint(swPort) == null) { // The attachment point is NOT there return false; } // Make a copy of this device Device dCopy = new Device(d); DeviceAttachmentPoint dap = dCopy.removeAttachmentPoint(swPort); // Remove the original device from the Switch-port map // This is a no-op when this fn is called from removeSwPort // as the switch-pot itself would be deleted from the map delFromSwitchPortDeviceMap(swPort, d); // Remove the original device from the Switch map delFromSwitchDeviceMap(sw, d); // Now add this updated device to the maps, which will replace // the old copy updateMaps(dCopy); if (log.isDebugEnabled()) { log.debug("Remove AP {} post {} prev {} for Device {}", new Object[] {dap, dCopy.getAttachmentPoints().size(), d.getAttachmentPoints().size(), dCopy}); } removeAttachmentPointFromStorage(d, dap); d = null; return true; } /** * Add a new pending attachment point to the unresolved attachment * point map * @param dpid the DPID of the switch for the attachment point * @param mac the MAC address of the device * @param port the port for the attachment point * @param lastSeen the last seen timestamp */ protected void updateSwitchUnresolvedAPMap(long dpid, long mac, short port, Date lastSeen) { PendingAttachmentPoint pap = new PendingAttachmentPoint(); pap.mac = mac; pap.switchDpid = dpid; pap.switchPort = port; pap.lastSeen = lastSeen; List<PendingAttachmentPoint> papl = devMgrMaps.switchUnresolvedAPMap.get(dpid); if (papl == null) { devMgrMaps.switchUnresolvedAPMap.put(dpid, papl = Collections.synchronizedList( new ArrayList<PendingAttachmentPoint>())); } papl.add(pap); } /** * Add switch-port to port_channel mapping to portChannelMap * @param switch_id * @param port_no * @param port_channel */ protected void addPortToPortChannel(String switch_id, String port_name, String port_channel) { String swPort = switch_id + port_name; portChannelMap.put(swPort, port_channel); } /** * Check if two ports belong to the same port channel * @param swPort1 * @param swPort2 * @return */ protected boolean inSamePortChannel(SwitchPortTuple swPort1, SwitchPortTuple swPort2) { IOFSwitch sw = swPort1.getSw(); String portName = sw.getPort(swPort1.getPort()).getName(); String key = sw.getStringId() + portName; String portChannel1 = portChannelMap.get(key); if (portChannel1 == null) return false; sw = swPort2.getSw(); portName = sw.getPort(swPort2.getPort()).getName(); key = sw.getStringId() + portName; String portChannel2 = portChannelMap.get(key); if (portChannel2 == null) return false; return portChannel1.equals(portChannel2); } } // End of DevMgrMap class definition private DevMgrMaps devMgrMaps; public DevMgrMaps getDevMgrMaps() { return devMgrMaps; } protected static Logger log = LoggerFactory.getLogger(DeviceManagerImpl.class); protected Set<IDeviceManagerAware> deviceManagerAware; protected LinkedList<Update> updates; protected ReentrantReadWriteLock lock; protected volatile boolean shuttingDown = false; protected volatile boolean portChannelConfigChanged = false; // Our dependencies protected IFloodlightProviderService floodlightProvider; protected ILinkDiscoveryService linkDiscovery; protected ITopologyService topology; protected IStorageSourceService storageSource; protected IThreadPoolService threadPool; protected IRestApiService restApi; protected Runnable deviceAgingTimer; protected SingletonTask deviceUpdateTask; protected Date previousStorageAudit; protected static int DEVICE_MAX_AGE = 60 * 60 * 24; protected static int DEVICE_NA_MAX_AGE = 60 * 60 * 2; protected static int DEVICE_AP_MAX_AGE = 60 * 60 * 2; protected static long BD_TO_NBD_TIMEDIFF_MS = 300000; // 5 minutes protected static long BD_TO_BD_TIMEDIFF_MS = 5000; // 5 seconds // This the amount of time that we need for a device to move from // a non-broadcast domain port to a broadcast domain port. // Constants for accessing storage // Table names private static final String DEVICE_TABLE_NAME = "controller_host"; private static final String DEVICE_ATTACHMENT_POINT_TABLE_NAME = "controller_hostattachmentpoint"; private static final String DEVICE_NETWORK_ADDRESS_TABLE_NAME = "controller_hostnetworkaddress"; protected static final String PORT_CHANNEL_TABLE_NAME = "controller_portchannelconfig"; // Column names for the host table private static final String MAC_COLUMN_NAME = "mac"; private static final String VLAN_COLUMN_NAME = "vlan"; // Column names for both the attachment point and network address tables private static final String ID_COLUMN_NAME = "id"; private static final String DEVICE_COLUMN_NAME = "host_id"; private static final String LAST_SEEN_COLUMN_NAME = "last_seen"; // Column names for the attachment point table private static final String SWITCH_COLUMN_NAME = "switch_id"; private static final String PORT_COLUMN_NAME = "inport"; private static final String AP_STATUS_COLUMN_NAME = "status"; // Column names for the network address table private static final String NETWORK_ADDRESS_COLUMN_NAME = "ip"; // Column names for the port channel table protected static final String PC_ID_COLUMN_NAME = "id"; protected static final String PORT_CHANNEL_COLUMN_NAME = "port_channel_id"; protected static final String PC_SWITCH_COLUMN_NAME = "switch"; protected static final String PC_PORT_COLUMN_NAME = "port"; protected enum UpdateType { ADDED, REMOVED, MOVED, ADDRESS_ADDED, ADDRESS_REMOVED, VLAN_CHANGED } /** * Used internally to feed update queue for IDeviceManagerAware listeners */ protected class Update { public Device device; public IOFSwitch oldSw; public Short oldSwPort; public IOFSwitch sw; public Short swPort; public DeviceNetworkAddress address; public UpdateType updateType; public Update(UpdateType type) { this.updateType = type; } } protected class PendingAttachmentPoint { long mac; long switchDpid; short switchPort; Date lastSeen; public long getMac() { return mac; } public long getSwitchDpid() { return switchDpid; } public short getSwitchPort() { return switchPort; } public Date getLastSeen() { return lastSeen; } } public void shutDown() { shuttingDown = true; floodlightProvider.removeOFMessageListener(OFType.PACKET_IN, this); floodlightProvider.removeOFMessageListener(OFType.PORT_STATUS, this); floodlightProvider.removeOFSwitchListener(this); deviceAgingTimer = null; } @Override public String getName() { return "devicemanager"; } /** * Used in processPortStatus to check if the event is delete or shutdown * of a switch-port * @param ps * @return */ private boolean isPortStatusDelOrModify(OFPortStatus ps) { boolean isDelete = ((byte)OFPortReason.OFPPR_DELETE.ordinal() == ps.getReason()); boolean isModify = ((byte)OFPortReason.OFPPR_MODIFY.ordinal() == ps.getReason()); boolean isNotActive = (((OFPortConfig.OFPPC_PORT_DOWN.getValue() & ps.getDesc().getConfig()) > 0) || ((OFPortState.OFPPS_LINK_DOWN.getValue() & ps.getDesc().getState()) > 0)); return (isDelete || (isModify && isNotActive)); } public Command processPortStatusMessage(IOFSwitch sw, OFPortStatus ps) { // if ps is a delete, or a modify where the port is down or // configured down if (isPortStatusDelOrModify(ps)) { SwitchPortTuple id = new SwitchPortTuple(sw, ps.getDesc().getPortNumber()); lock.writeLock().lock(); try { devMgrMaps.removeSwPort(id); } finally { lock.writeLock().unlock(); } } return Command.CONTINUE; } private void handleNewDevice(byte[] mac, Date currentDate, SwitchPortTuple swPort, int nwAddr, Short vlan) { // Create the new device with attachment point and network address Device device = new Device(mac, currentDate); device.addAttachmentPoint(swPort, currentDate); evHistAttachmtPt(mac, swPort, EvAction.ADDED, "New device"); device.addNetworkAddress(nwAddr, currentDate); device.setVlanId(vlan); // Update the maps - insert the device in the maps devMgrMaps.updateMaps(device); writeDeviceToStorage(device, currentDate); updateStatus(device, true); if (log.isDebugEnabled()) { log.debug("New device learned {}", device); } } private boolean isGratArp(Ethernet eth) { if (eth.getPayload() instanceof ARP) { ARP arp = (ARP) eth.getPayload(); if (arp.isGratuitous()) { return true; } } return false; } private int getSrcNwAddr(Ethernet eth, long dlAddr) { if (eth.getPayload() instanceof ARP) { ARP arp = (ARP) eth.getPayload(); if ((arp.getProtocolType() == ARP.PROTO_TYPE_IP) && (Ethernet.toLong(arp.getSenderHardwareAddress()) ==dlAddr)) { return IPv4.toIPv4Address(arp.getSenderProtocolAddress()); } } else if (eth.getPayload() instanceof IPv4) { IPv4 ipv4 = (IPv4) eth.getPayload(); return ipv4.getSourceAddress(); } return 0; } /** * This method is called for every packet-in and should be optimized for * performance. * @param sw * @param pi * @param cntx * @return */ public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { Ethernet eth = IFloodlightProviderService.bcStore.get( cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); // If the packet in comes from a port that's not allowed by higher // level topology, it should be dropped. An L2 bridge can result // in this situation. short pinPort = pi.getInPort(); long pinSw = sw.getId(); if (topology.isAllowed(pinSw, pinPort) == false) { if (eth.getEtherType() == Ethernet.TYPE_BDDP || eth.isMulticast() == false) { return Command.CONTINUE; } else { if (log.isDebugEnabled()) { log.debug("deviceManager: Stopping packet as it is coming" + "in on a port blocked by higher layer on." + "switch ={}, port={}", new Object[] {sw.getStringId(), pinPort}); } return Command.STOP; } } Command ret = Command.CONTINUE; OFMatch match = new OFMatch(); match.loadFromPacket(pi.getPacketData(), pi.getInPort(), sw.getId()); // Add this packet-in to event history evHistPktIn(match); if (log.isTraceEnabled()) log.trace("Entering packet_in processing sw {}, port {}. {} --> {}, type {}", new Object[] { sw.getStringId(), pi.getInPort(), HexString.toHexString(match.getDataLayerSource()), HexString.toHexString(match.getDataLayerDestination()), match.getDataLayerType() }); // Create attachment point/update network address if required SwitchPortTuple switchPort = new SwitchPortTuple(sw, pi.getInPort()); // Don't learn from internal port or invalid port if (topology.isInternal(switchPort.getSw().getId(), switchPort.getPort()) || !isValidInputPort(switchPort.getPort())) { processUpdates(); return Command.CONTINUE; } // Packet arrive at a port from where we can learn the device // if the source is multicast/broadcast ignore it if ((match.getDataLayerSource()[0] & 0x1) != 0) { return Command.CONTINUE; } Long dlAddr = Ethernet.toLong(match.getDataLayerSource()); Short vlan = match.getDataLayerVirtualLan(); if (vlan < 0) vlan = null; int nwSrc = getSrcNwAddr(eth, dlAddr); Device device = devMgrMaps.getDeviceByDataLayerAddr(dlAddr); if (log.isTraceEnabled()) { long dstAddr = Ethernet.toLong(match.getDataLayerDestination()); Device dstDev = devMgrMaps.getDeviceByDataLayerAddr(dstAddr); if (device != null) log.trace(" Src.AttachmentPts: {}", device.getAttachmentPointsMap().keySet()); if (dstDev != null) log.trace(" Dst.AttachmentPts: {}", dstDev.getAttachmentPointsMap().keySet()); } Date currentDate = new Date(); if (device != null) { // Write lock is expensive, check if we have an update first boolean newAttachmentPoint = false; boolean newNetworkAddress = false; boolean updateAttachmentPointLastSeen = false; boolean updateNetworkAddressLastSeen = false; boolean updateNeworkAddressMap = false; boolean updateDeviceVlan = false; boolean clearAttachmentPoints = false; boolean updateDevice = false; DeviceAttachmentPoint attachmentPoint = null; DeviceNetworkAddress networkAddress = null; // Copy-replace of device would be too expensive here device.setLastSeen(currentDate); updateDevice = device.shouldWriteLastSeenToStorage(); // If the attachment point moves from a non-broadcast domain // port to a broadcast domain port too quickly, ignore learning // the broadcast domain attachment point. for ( DeviceAttachmentPoint oldDap : device.getAttachmentPoints() ) { // if the two switches are in the same cluster // and if currSw,CurrPort is not in broadcast domain long currSw = oldDap.getSwitchPort().getSw().getId(); short currPort = oldDap.getSwitchPort().getPort(); long newSw = switchPort.getSw().getId(); short newPort = switchPort.getPort(); if ( (topology.getSwitchClusterId(currSw) == topology.getSwitchClusterId(newSw)) && (topology.isBroadcastDomainPort(currSw, currPort) == false) && (topology.isBroadcastDomainPort(newSw, newPort) == true)) { long dt = currentDate.getTime() - oldDap.getLastSeen().getTime() ; if (dt < BD_TO_NBD_TIMEDIFF_MS) { // if the packet was seen within the last 5 minutes, we should ignore. // it should also ignore processing the packet. if (log.isTraceEnabled()) { log.trace("Surpressing too quick move of {} from non broadcast domain port {} {}" + " to broadcast domain port {} {}. Last seen on non-BD {} sec ago", new Object[] { HexString.toHexString(match.getDataLayerSource()), oldDap.getSwitchPort().getSw().getStringId(), currPort, switchPort.getSw().getStringId(), newPort, dt/1000 } ); } return Command.STOP; } } else if ( (topology.getSwitchClusterId(currSw) == topology.getSwitchClusterId(newSw)) && (topology.isBroadcastDomainPort(currSw, currPort) == true) && (topology.isBroadcastDomainPort(newSw, newPort) == true)) { long dt = currentDate.getTime() - oldDap.getLastSeen().getTime() ; if (dt < BD_TO_BD_TIMEDIFF_MS) { // if the packet was seen within the last 5 seconds, we should ignore. // it should also ignore processing the packet. if (log.isTraceEnabled()) { log.trace("Surpressing too quick move of {} from one broadcast domain port {} {}" + " to another broadcast domain port {} {}. Last seen on BD {} sec ago", new Object[] { HexString.toHexString(match.getDataLayerSource()), oldDap.getSwitchPort().getSw().getStringId(), currPort, switchPort.getSw().getStringId(), newPort, dt/1000 } ); } return Command.STOP; } } } if (isGratArp(eth)) { clearAttachmentPoints = true; } attachmentPoint = device.getAttachmentPoint(switchPort); if (attachmentPoint != null) { updateAttachmentPointLastSeen = true; } else { newAttachmentPoint = true; } if (nwSrc != 0) { networkAddress = device.getNetworkAddress(nwSrc); if (networkAddress != null) { updateNetworkAddressLastSeen = true; } else if (eth != null && (eth.getPayload() instanceof ARP)) { /** MAC-IP association should be learnt from both ARP request and reply. * Since a host learns some other host's mac to ip mapping after receiving * an ARP request from the host. * * However, device's MAC-IP mapping could be wrong if a host sends a ARP request with * an IP other than its own. Unfortunately, there isn't an easy way to allow both learning * and prevent incorrect learning. */ networkAddress = new DeviceNetworkAddress(nwSrc, currentDate); newNetworkAddress = true; } // Also, if this address is currently mapped to a different // device, fix it. This should be rare, so it is OK to do update // the storage from here. // // NOTE: the mapping is observed, and the decision is made based // on that, outside a lock. So the state may change by the time // we get to do the map update. But that is OK since the mapping // will eventually get consistent. Device deviceByNwaddr = this.getDeviceByIPv4Address(nwSrc); if ((deviceByNwaddr != null) && (deviceByNwaddr.getDataLayerAddressAsLong() != device.getDataLayerAddressAsLong())) { updateNeworkAddressMap = true; Device dCopy = new Device(deviceByNwaddr); DeviceNetworkAddress naOld = dCopy.getNetworkAddress(nwSrc); Map<Integer, DeviceNetworkAddress> namap = dCopy.getNetworkAddressesMap(); if (namap.containsKey(nwSrc)) namap.remove(nwSrc); dCopy.setNetworkAddresses(namap.values()); this.devMgrMaps.updateMaps(dCopy); if (naOld !=null) removeNetworkAddressFromStorage(dCopy, naOld); } } if ((vlan == null && device.getVlanId() != null) || (vlan != null && !vlan.equals(device.getVlanId()))) { updateDeviceVlan = true; } if (newAttachmentPoint || newNetworkAddress || updateDeviceVlan || updateNeworkAddressMap) { Device nd = new Device(device); try { // Check if we have seen this attachmentPoint recently, // An exception is thrown if the attachmentPoint is blocked. if (newAttachmentPoint) { attachmentPoint = getNewAttachmentPoint(nd, switchPort); if (attachmentPoint == null) { newAttachmentPoint = false; } else { nd.addAttachmentPoint(attachmentPoint); evHistAttachmtPt(nd.getDataLayerAddressAsLong(), attachmentPoint.getSwitchPort(), EvAction.ADDED, "New AP from pkt-in"); } } if (clearAttachmentPoints) { nd.clearAttachmentPoints(); evHistAttachmtPt(nd, 0L, (short)(-1), EvAction.CLEARED, "Grat. ARP from pkt-in"); } if (newNetworkAddress) { // add the address nd.addNetworkAddress(networkAddress); if (log.isTraceEnabled()) { log.trace("Device {} added IP {}", new Object[] {nd, IPv4.fromIPv4Address(nwSrc)}); } } if (updateDeviceVlan) { nd.setVlanId(vlan); writeDeviceToStorage(nd, currentDate); } } catch (APBlockedException e) { assert(attachmentPoint == null); ret = Command.STOP; // install drop flow to avoid overloading the controller // set hard timeout to 5 seconds to avoid blocking host // forever ForwardingBase.blockHost(floodlightProvider, switchPort, dlAddr, ForwardingBase.FLOWMOD_DEFAULT_HARD_TIMEOUT); } // Update the maps devMgrMaps.updateMaps(nd); // publish the update after devMgrMaps is updated. if (newNetworkAddress) { updateAddress(nd, networkAddress, true); } if (updateDeviceVlan) { updateVlan(nd); } device = nd; } if (updateAttachmentPointLastSeen) { attachmentPoint.setLastSeen(currentDate); if (attachmentPoint.shouldWriteLastSeenToStorage()) writeAttachmentPointToStorage( device, attachmentPoint, currentDate); } if (updateNetworkAddressLastSeen || newNetworkAddress) { if (updateNetworkAddressLastSeen) networkAddress.setLastSeen(currentDate); if (newNetworkAddress || networkAddress.shouldWriteLastSeenToStorage()) writeNetworkAddressToStorage( device, networkAddress, currentDate); } if (updateDevice) { writeDeviceToStorage(device, currentDate); } } else { // device is null if (log.isTraceEnabled()) log.trace(" new device"); handleNewDevice(match.getDataLayerSource(), currentDate, switchPort, nwSrc, vlan); } processUpdates(); return ret; } /** * Check if there exists a state of attachment point flapping. * If not, we return a new attachment point (or resurrect an old one). * If swPort is blocked for this device, throw an exception. * * This must be called with write lock held. */ private DeviceAttachmentPoint getNewAttachmentPoint(Device device, SwitchPortTuple swPort) throws APBlockedException { Date currentDate = new Date(); // First, check if we have an existing attachment point DeviceAttachmentPoint curAttachmentPoint = null; IOFSwitch newSwitch = swPort.getSw(); for (DeviceAttachmentPoint existingAttachmentPoint: device.getAttachmentPoints()) { IOFSwitch existingSwitch = existingAttachmentPoint.getSwitchPort().getSw(); if ((newSwitch == existingSwitch) || ((topology != null) && topology.inSameCluster(newSwitch.getId(), existingSwitch.getId()))) { curAttachmentPoint = existingAttachmentPoint; break; } } if (curAttachmentPoint != null) { Long curDPID = curAttachmentPoint.getSwitchPort().getSw().getId(); Short curPort = curAttachmentPoint.getSwitchPort().getPort(); boolean sameBD = topology.isInSameBroadcastDomain(swPort.getSw().getId(), swPort.getPort(), curDPID, curPort); if (sameBD) return null; } // Do we have an old attachment point? DeviceAttachmentPoint attachmentPoint = device.getOldAttachmentPoint(swPort); if (attachmentPoint == null) { attachmentPoint = new DeviceAttachmentPoint(swPort, currentDate); } else { attachmentPoint.setLastSeen(currentDate); if (attachmentPoint.isBlocked()) { // Attachment point is currently in blocked state // If curAttachmentPoint exists and active, drop the packet if (curAttachmentPoint != null && currentDate.getTime() - curAttachmentPoint.getLastSeen().getTime() < 600000) { throw new APBlockedException("Attachment point is blocked"); } log.info("Unblocking {} for device {}", attachmentPoint.getSwitchPort(), device); attachmentPoint.setBlocked(false); evHistAttachmtPt(device.getDataLayerAddressAsLong(), swPort, EvAction.UNBLOCKED, "packet-in after block timer expired"); } // Remove from old list device.removeOldAttachmentPoint(attachmentPoint); } // Update mappings devMgrMaps.addDevAttachmentPoint( device.getDataLayerAddressAsLong(), swPort, currentDate); evHistAttachmtPt(device.getDataLayerAddressAsLong(), swPort, EvAction.ADDED, "packet-in GNAP"); // If curAttachmentPoint exists, we mark it a conflict and may block it. if (curAttachmentPoint != null) { device.removeAttachmentPoint(curAttachmentPoint); device.addOldAttachmentPoint(curAttachmentPoint); // If two ports are in the same port-channel, we don't treat it // as conflict, but will forward based on the last seen switch-port if (!devMgrMaps.inSamePortChannel(swPort, curAttachmentPoint.getSwitchPort())) { curAttachmentPoint.setConflict(currentDate); if (curAttachmentPoint.isFlapping()) { curAttachmentPoint.setBlocked(true); evHistAttachmtPt(device.getDataLayerAddressAsLong(), curAttachmentPoint.getSwitchPort(), EvAction.BLOCKED, "Conflict"); writeAttachmentPointToStorage(device, curAttachmentPoint, currentDate); log.warn( "Device {}: flapping between {} and {}, block the latter", new Object[] {device, swPort, curAttachmentPoint.getSwitchPort()}); // Check if flapping is between the same switch port if (swPort.getSw().getId() == curAttachmentPoint.getSwitchPort().getSw().getId() && swPort.getPort() == curAttachmentPoint.getSwitchPort().getPort()) { log.warn("Fake flapping on port " + swPort.getPort() + " between sw " + swPort.getSw() + " and " + curAttachmentPoint.getSwitchPort().getSw()); device.removeOldAttachmentPoint(curAttachmentPoint); removeAttachmentPointFromStorage(device, curAttachmentPoint); } } else { removeAttachmentPointFromStorage(device, curAttachmentPoint); evHistAttachmtPt(device.getDataLayerAddressAsLong(), curAttachmentPoint.getSwitchPort(), EvAction.REMOVED, "Conflict"); } } updateMoved(device, curAttachmentPoint.getSwitchPort(), attachmentPoint); if (log.isDebugEnabled()) { log.debug("Device {} moved from {} to {}", new Object[] { device, curAttachmentPoint.getSwitchPort(), swPort}); } } else { updateStatus(device, true); log.debug("Device {} added {}", device, swPort); } writeAttachmentPointToStorage(device, attachmentPoint, currentDate); return attachmentPoint; } private boolean isValidInputPort(Short port) { // Not a physical port. We should not 'discover' attachment points where return ((int)port.shortValue() & 0xff00) != 0xff00 || port.shortValue() == (short)0xfffe; } /** * Removes the specified device from data layer and network layer maps. * Does NOT remove the device from switch and switch:port level maps. * Must be called from within a write lock. * @param device */ protected void delDevice(Device device) { devMgrMaps.delFromMaps(device); removeDeviceDiscoveredStateFromStorage(device); updateStatus(device, false); if (log.isDebugEnabled()) { log.debug("Removed device {}", device); } processUpdates(); } @Override public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) { switch (msg.getType()) { case PACKET_IN: return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx); case PORT_STATUS: return this.processPortStatusMessage(sw, (OFPortStatus) msg); } log.error("received an unexpected message {} from switch {}", msg, sw); return Command.CONTINUE; } /** * @param floodlightProvider the floodlightProvider to set */ public void setFloodlightProvider(IFloodlightProviderService floodlightProvider) { this.floodlightProvider = floodlightProvider; } /** * @param topology the topology to set */ public void setTopology(ITopologyService topology) { this.topology = topology; } /** * @param topology the topology to set */ public void setLinkDiscovery(ILinkDiscoveryService linkDiscovery) { this.linkDiscovery = linkDiscovery; } @Override public Device getDeviceByDataLayerAddress(byte[] address) { if (address.length != Ethernet.DATALAYER_ADDRESS_LENGTH) return null; return getDeviceByDataLayerAddress(Ethernet.toLong(address)); } @Override public Device getDeviceByDataLayerAddress(long address) { return this.devMgrMaps.dataLayerAddressDeviceMap.get(address); } @Override public Device getDeviceByIPv4Address(Integer address) { lock.readLock().lock(); try { return this.devMgrMaps.ipv4AddressDeviceMap.get(address); } finally { lock.readLock().unlock(); } } @Override public void invalidateDeviceAPsByIPv4Address(Integer address) { lock.readLock().lock(); try { Device d = this.devMgrMaps.ipv4AddressDeviceMap.get(address); if (d!= null && d.getAttachmentPoints() != null) { d.getAttachmentPoints().clear(); } } finally { lock.readLock().unlock(); } } @Override public boolean isDeviceKnownToCluster(long deviceId, long switchId) { Device device = devMgrMaps.getDeviceByDataLayerAddr(deviceId); if (device == null) { return false; } /** * Iterate through all APs and check if the switch clusterID matches * with the given clusterId */ for(DeviceAttachmentPoint dap : device.getAttachmentPoints()) { if (dap == null) continue; if (topology.getSwitchClusterId(switchId) == topology.getSwitchClusterId(dap.getSwitchPort().getSw().getId())) { return true; } } return false; } @Override public List<Device> getDevices() { lock.readLock().lock(); try { return new ArrayList<Device>( this.devMgrMaps.dataLayerAddressDeviceMap.values()); } finally { lock.readLock().unlock(); } } @Override public void addedSwitch(IOFSwitch sw) { // Fix up attachment points related to the switch lock.writeLock().lock(); try { Long swDpid = sw.getId(); List<PendingAttachmentPoint> papl = devMgrMaps.switchUnresolvedAPMap.get(swDpid); if (papl != null) { for (PendingAttachmentPoint pap : papl) { Device d = devMgrMaps.getDeviceByDataLayerAddr(pap.mac); if (d == null) continue; // Add attachment point devMgrMaps.addDevAttachmentPoint( pap.mac, sw, pap.switchPort, pap.lastSeen); evHistAttachmtPt(pap.mac, sw.getId(), pap.switchPort, EvAction.ADDED, "Switch Added"); } devMgrMaps.switchUnresolvedAPMap.remove(swDpid); } } finally { lock.writeLock().unlock(); } } @Override public void removedSwitch (IOFSwitch sw) { // remove all devices attached to this switch if (!devMgrMaps.isSwitchPresent(sw)) { // Switch not present return; } lock.writeLock().lock(); try { devMgrMaps.delSwitchfromMaps(sw); } finally { lock.writeLock().unlock(); } } public void addedOrUpdatedLink(long srcSw, short srcPort, int srcPortState, long dstSw, short dstPort, int dstPortState, ILinkDiscovery.LinkType type) { if (((srcPortState & OFPortState.OFPPS_STP_MASK.getValue()) != OFPortState.OFPPS_STP_BLOCK.getValue()) && ((dstPortState & OFPortState.OFPPS_STP_MASK.getValue()) != OFPortState.OFPPS_STP_BLOCK.getValue())) { // Remove all devices living on this switch:port now that it is // internal SwitchPortTuple switchPort = new SwitchPortTuple(floodlightProvider.getSwitches().get(dstSw), dstPort); lock.writeLock().lock(); try { devMgrMaps.removeSwPort(switchPort); } finally { lock.writeLock().unlock(); } } } /** * Process device manager aware updates. Call without any lock held */ protected void processUpdates() { synchronized (updates) { Update update = null; while ((update = updates.poll()) != null) { log.debug ("processUpdates, update: {}", update); if (deviceManagerAware == null) continue; for (IDeviceManagerAware dma : deviceManagerAware) { switch (update.updateType) { case ADDED: dma.deviceAdded(update.device); break; case REMOVED: dma.deviceRemoved(update.device); break; case MOVED: dma.deviceMoved(update.device, update.oldSw, update.oldSwPort, update.sw, update.swPort); break; case ADDRESS_ADDED: dma.deviceNetworkAddressAdded(update.device, update.address); break; case ADDRESS_REMOVED: dma.deviceNetworkAddressRemoved(update.device, update.address); break; case VLAN_CHANGED: dma.deviceVlanChanged(update.device); break; } } } } } /** * Puts an update in queue for the Device. Must be called from within the * write lock. * @param device * @param added */ protected void updateStatus(Device device, boolean added) { synchronized (updates) { Update update; if (added) { update = new Update(UpdateType.ADDED); } else { update = new Update(UpdateType.REMOVED); } update.device = device; this.updates.add(update); } } /** * Puts an update in queue to indicate the Device moved. Must be called * from within the write lock. * @param device The device that has moved. * @param oldSwPort The old switchport * @param newDap The new attachment point */ protected void updateMoved(Device device, SwitchPortTuple oldSwPort, DeviceAttachmentPoint newDap) { // We also clear the attachment points on other islands device.clearAttachmentPoints(); evHistAttachmtPt(device, 0L, (short)(-1), EvAction.CLEARED, "Moved"); device.addAttachmentPoint(newDap); evHistAttachmtPt(device.getDataLayerAddressAsLong(), newDap.getSwitchPort(), EvAction.ADDED, "Moved"); synchronized (updates) { Update update = new Update(UpdateType.MOVED); update.device = device; update.oldSw = oldSwPort.getSw(); update.oldSwPort = oldSwPort.getPort(); update.sw = newDap.getSwitchPort().getSw(); update.swPort = newDap.getSwitchPort().getPort(); this.updates.add(update); } } protected void updateAddress(Device device, DeviceNetworkAddress address, boolean added) { synchronized (updates) { Update update; if (added) { update = new Update(UpdateType.ADDRESS_ADDED); } else { update = new Update(UpdateType.ADDRESS_REMOVED); } update.device = device; update.address = address; this.updates.add(update); } } protected void updateVlan(Device device) { synchronized (updates) { Update update = new Update(UpdateType.VLAN_CHANGED); update.device = device; this.updates.add(update); } } /** * Iterates through all devices and cleans up attachment points */ @Override public void topologyChanged() { deviceUpdateTask.reschedule(10, TimeUnit.MILLISECONDS); } private boolean isNewer(DeviceAttachmentPoint dap1, DeviceAttachmentPoint dap2) { // dap 1 is newer than dap 2 if // (1) if dap 1 is a non-broadcast domain attachment point // (a) if (dap 2 is a non-broadcast domain attachment point // and dap1.lastseen time is after dap2.last seentime // OR // (b) if (dap 2 is a braodcast domain attachment point // and dap1.lastseen time is after (dap2.lastseen-5minutes) // (2) if dap 1 is a broadcast domain attachment point // (a) if dap 2 is a non-broadcast attachment point and // dap1.lastseen is after (dap2.lastseen + 5 minutes) // OR // (b) if dap2 is a broadcastdomain attachment point and // dap2.lastseen SwitchPortTuple sp1, sp2; boolean flag1, flag2; sp1 = dap1.getSwitchPort(); sp2 = dap2.getSwitchPort(); flag1 = topology.isBroadcastDomainPort(sp1.getSw().getId(), sp1.getPort()); flag2 = topology.isBroadcastDomainPort(sp2.getSw().getId(), sp2.getPort()); long ls1 = dap1.getLastSeen().getTime(); long ls2 = dap2.getLastSeen().getTime(); if (flag1 == false) { if (flag2 == false) { return (ls1 > ls2); } else { return (ls1 > (ls2 - BD_TO_NBD_TIMEDIFF_MS)); } } else { if (flag2 == false) { return (ls1 > (ls2 + BD_TO_NBD_TIMEDIFF_MS)); } else { return (ls1 > ls2); } } } /** * Removes any attachment points that are in the same * {@link net.floodlightcontroller.topology.SwitchCluster SwitchCluster} * @param d The device to update the attachment points */ public void cleanupAttachmentPoints(Device d) { // The long here is the SwitchCluster ID Map<Long, DeviceAttachmentPoint> map = new HashMap<Long, DeviceAttachmentPoint>(); // Get only the latest DAPs into a map for (DeviceAttachmentPoint dap : d.getAttachmentPoints()) { if (DeviceAttachmentPoint.isNotNull(dap) && !topology.isInternal(dap.getSwitchPort().getSw().getId(), dap.getSwitchPort().getPort())) { long clusterId = topology.getSwitchClusterId( dap.getSwitchPort().getSw().getId()); // do not use attachment points if the attachment point is // not allowed by topology long swid = dap.getSwitchPort().getSw().getId(); short port = dap.getSwitchPort().getPort(); if (topology.isAllowed(swid, port) == false) continue; if (map.containsKey(clusterId)) { // We compare to see which one is newer, move attachment // point to "old" list. // They are removed after deleting from storage. DeviceAttachmentPoint value = map.get(clusterId); //if (dap.getLastSeen().after(value.getLastSeen())) { if (isNewer(dap, map.get(clusterId))) { map.put(clusterId, dap); d.addOldAttachmentPoint(value); // on copy of device } } else { map.put(clusterId, dap); } } } synchronized (d) { // Since the update below is happening on a copy of the device it // should not impact packetIn processing time due to lock contention d.setAttachmentPoints(map.values()); } } public void clearAllDeviceStateFromMemory() { devMgrMaps.clearMaps(); } private Date ageBoundaryDifference(Date currentDate, long expire) { if (expire == 0) { return new Date(0); } return new Date(currentDate.getTime() - 1000*expire); } // ********************* // Storage Write Methods // ********************* protected void writeDeviceToStorage(Device device, Date currentDate) { Map<String, Object> rowValues = new HashMap<String, Object>(); String macString = device.getDlAddrString(); rowValues.put(MAC_COLUMN_NAME, macString); if (device.getVlanId() != null) rowValues.put(VLAN_COLUMN_NAME, device.getVlanId()); rowValues.put(LAST_SEEN_COLUMN_NAME, currentDate); storageSource.updateRowAsync(DEVICE_TABLE_NAME, rowValues); device.lastSeenWrittenToStorage(currentDate); for (DeviceAttachmentPoint attachmentPoint: device.getAttachmentPoints()) { writeAttachmentPointToStorage(device, attachmentPoint, currentDate); } for (DeviceNetworkAddress networkAddress: device.getNetworkAddresses()) { writeNetworkAddressToStorage(device, networkAddress, currentDate); } } protected void writeAttachmentPointToStorage(Device device, DeviceAttachmentPoint attachmentPoint, Date currentDate) { assert(device != null); assert(attachmentPoint != null); String deviceId = device.getDlAddrString(); SwitchPortTuple switchPort = attachmentPoint.getSwitchPort(); assert(switchPort != null); String switchId = switchPort.getSw().getStringId(); Short port = switchPort.getPort(); String attachmentPointId = deviceId + "|" + switchId + "|" + port.toString(); Map<String, Object> rowValues = new HashMap<String, Object>(); rowValues.put(ID_COLUMN_NAME, attachmentPointId); rowValues.put(DEVICE_COLUMN_NAME, deviceId); rowValues.put(SWITCH_COLUMN_NAME, switchId); rowValues.put(PORT_COLUMN_NAME, port); rowValues.put(LAST_SEEN_COLUMN_NAME, attachmentPoint.getLastSeen()); String status = null; if (attachmentPoint.isBlocked()) status = "blocked: duplicate mac"; rowValues.put(AP_STATUS_COLUMN_NAME, status); storageSource.updateRowAsync(DEVICE_ATTACHMENT_POINT_TABLE_NAME, rowValues); attachmentPoint.lastSeenWrittenToStorage(currentDate); } // ********************** // Storage Remove Methods // ********************** protected void removeAttachmentPointFromStorage(Device device, DeviceAttachmentPoint attachmentPoint) { try { String deviceId = device.getDlAddrString(); SwitchPortTuple switchPort = attachmentPoint.getSwitchPort(); String switchId = switchPort.getSw().getStringId(); Short port = switchPort.getPort(); String attachmentPointId = deviceId + "|" + switchId + "|" + port.toString(); storageSource.deleteRowAsync( DEVICE_ATTACHMENT_POINT_TABLE_NAME, attachmentPointId); } catch (NullPointerException e) { log.warn("Null ptr exception for device {} attach-point {}", device, attachmentPoint); } } protected void writeNetworkAddressToStorage(Device device, DeviceNetworkAddress networkAddress, Date currentDate) { assert(device != null); assert(networkAddress != null); String deviceId = device.getDlAddrString(); String networkAddressString = IPv4.fromIPv4Address( networkAddress.getNetworkAddress()); String networkAddressId = deviceId + "|" + networkAddressString; if (networkAddress.getNetworkAddress() == 0) { log.error("Zero network address for device {}\n {}", device, Thread.currentThread().getStackTrace()); return; } Map<String, Object> rowValues = new HashMap<String, Object>(); rowValues.put(ID_COLUMN_NAME, networkAddressId); rowValues.put(DEVICE_COLUMN_NAME, deviceId); rowValues.put(NETWORK_ADDRESS_COLUMN_NAME, networkAddressString); rowValues.put(LAST_SEEN_COLUMN_NAME, networkAddress.getLastSeen()); storageSource.updateRowAsync(DEVICE_NETWORK_ADDRESS_TABLE_NAME, rowValues); networkAddress.lastSeenWrittenToStorage(currentDate); } protected void removeNetworkAddressFromStorage(Device device, DeviceNetworkAddress networkAddress) { assert(device != null); assert(networkAddress != null); String deviceId = device.getDlAddrString(); String networkAddressString = IPv4.fromIPv4Address( networkAddress.getNetworkAddress()); String networkAddressId = deviceId + "|" + networkAddressString; storageSource.deleteRowAsync(DEVICE_NETWORK_ADDRESS_TABLE_NAME, networkAddressId); } // ******************** // Storage Read Methods // ******************** public boolean readPortChannelConfigFromStorage() { devMgrMaps.clearPortChannelMap(); try { IResultSet pcResultSet = storageSource.executeQuery( PORT_CHANNEL_TABLE_NAME, null, null, null); while (pcResultSet.next()) { String port_channel = pcResultSet.getString(PORT_CHANNEL_COLUMN_NAME); String switch_id = pcResultSet.getString(PC_SWITCH_COLUMN_NAME); String port_name = pcResultSet.getString(PC_PORT_COLUMN_NAME); devMgrMaps.addPortToPortChannel(switch_id, port_name, port_channel); } return true; } catch (StorageException e) { log.error("Error reading port-channel data from storage {}", e); return false; } } public boolean readAllDeviceStateFromStorage() { Date currentDate = new Date(); try { // These methods MUST be called in this order readDevicesFromStorage(currentDate); readDeviceAttachmentPointsFromStorage(currentDate); readDeviceNetworkAddressesFromStorage(currentDate); return true; } catch (StorageException e) { log.error("Error reading device data from storage {}", e); return false; } } /** * Exclude any entries which are older than the * @throws StorageException */ private void readDevicesFromStorage(Date currentDate) throws StorageException { String [] colNames = new String[] {MAC_COLUMN_NAME, VLAN_COLUMN_NAME, LAST_SEEN_COLUMN_NAME}; IResultSet deviceResultSet = storageSource.executeQuery( DEVICE_TABLE_NAME, colNames, null, null); while (deviceResultSet.next()) { Date lastSeen = deviceResultSet.getDate(LAST_SEEN_COLUMN_NAME); String macString = deviceResultSet.getString(MAC_COLUMN_NAME); assert(macString != null); if (macString == null) { log.debug("Ignoring storage entry with no mac address"); continue; } byte[] macBytes = HexString.fromHexString(macString); if (lastSeen == null) lastSeen = new Date(); Device d = new Device(macBytes, lastSeen); if (deviceResultSet.containsColumn(VLAN_COLUMN_NAME)) { d.setVlanId(deviceResultSet.getShort(VLAN_COLUMN_NAME)); if (d.getVlanId() < 0 || d.getVlanId() >= 4096) { log.debug("Ignore storage entry with invalid vlan {}", d); continue; } } devMgrMaps.updateMaps(d); } } private void readDeviceAttachmentPointsFromStorage(Date currentDate) throws StorageException { String [] colNames = new String[] { DEVICE_COLUMN_NAME, SWITCH_COLUMN_NAME, PORT_COLUMN_NAME, LAST_SEEN_COLUMN_NAME}; IResultSet dapResultSet = storageSource.executeQuery( DEVICE_ATTACHMENT_POINT_TABLE_NAME, colNames, null, null); while (dapResultSet.next()) { Date lastSeen = dapResultSet.getDate(LAST_SEEN_COLUMN_NAME); if (lastSeen == null) lastSeen = new Date(); String macString = dapResultSet.getString(DEVICE_COLUMN_NAME); String dpidString = dapResultSet.getString(SWITCH_COLUMN_NAME); if (macString == null || dpidString == null) continue; long mac = HexString.toLong(macString); long swDpid = HexString.toLong(dpidString); IOFSwitch sw = floodlightProvider.getSwitches().get(swDpid); Integer port = dapResultSet.getIntegerObject(PORT_COLUMN_NAME); if (port == null || port > Short.MAX_VALUE) continue; if (sw == null) { // switch has not joined yet devMgrMaps.updateSwitchUnresolvedAPMap( swDpid, mac, port.shortValue(), lastSeen); } else { devMgrMaps.addDevAttachmentPoint( mac, sw, port.shortValue(), lastSeen); evHistAttachmtPt(mac, sw.getId(), port.shortValue(), EvAction.ADDED, "Read from storage"); } } } private void readDeviceNetworkAddressesFromStorage(Date currentDate) throws StorageException { String [] colNames = new String[]{ DEVICE_COLUMN_NAME, NETWORK_ADDRESS_COLUMN_NAME, LAST_SEEN_COLUMN_NAME}; IResultSet dnaResultSet = storageSource.executeQuery( DEVICE_NETWORK_ADDRESS_TABLE_NAME, colNames, null, null); while (dnaResultSet.next()) { Date lastSeen = dnaResultSet.getDate(LAST_SEEN_COLUMN_NAME); if (lastSeen == null) lastSeen = new Date(); String macStr = dnaResultSet.getString(DEVICE_COLUMN_NAME); String netaddr = dnaResultSet.getString( NETWORK_ADDRESS_COLUMN_NAME); if (macStr == null) { continue; } if (netaddr == null) { // If the key is in the database then ignore this record continue; } devMgrMaps.addNwAddrByDataLayerAddr(HexString.toLong(macStr), IPv4.toIPv4Address(netaddr), lastSeen); } } public void removeDeviceDiscoveredStateFromStorage(Device device) { String deviceId = device.getDlAddrString(); // Remove all of the attachment points storageSource.deleteMatchingRowsAsync(DEVICE_ATTACHMENT_POINT_TABLE_NAME, new OperatorPredicate(DEVICE_COLUMN_NAME, OperatorPredicate.Operator.EQ, deviceId)); storageSource.deleteMatchingRowsAsync(DEVICE_NETWORK_ADDRESS_TABLE_NAME, new OperatorPredicate(DEVICE_COLUMN_NAME, OperatorPredicate.Operator.EQ, deviceId)); // Remove the device storageSource.deleteRow(DEVICE_TABLE_NAME, deviceId); } /** * IStorageSource listeners. * Need to optimize if we support a large number of port-channel entries. */ @Override public void rowsModified(String tableName, Set<Object> rowKeys) { portChannelConfigChanged = true; deviceUpdateTask.reschedule(5, TimeUnit.SECONDS); } @Override public void rowsDeleted(String tableName, Set<Object> rowKeys) { portChannelConfigChanged = true; deviceUpdateTask.reschedule(5, TimeUnit.SECONDS); } /** * Remove aged network address from device * * @param device * @param currentDate * @return the new device object since the device is immutable */ private Device removeAgedNetworkAddresses(Device device, Date currentDate) { Collection<DeviceNetworkAddress> addresses = device.getNetworkAddresses(); for (DeviceNetworkAddress address : addresses) { long expire = address.getExpire(); if (expire == 0) { expire = DEVICE_NA_MAX_AGE; } Date agedBoundary = ageBoundaryDifference(currentDate, expire); if (address.getLastSeen().before(agedBoundary)) { devMgrMaps.delNwAddrByDataLayerAddr(device.getDataLayerAddressAsLong(), address.getNetworkAddress().intValue()); } } return devMgrMaps.getDeviceByDataLayerAddr(device.getDataLayerAddressAsLong()); } /** * Remove aged device attachment point * * @param device * @param currentDate * @return the new device object since the device is immutable */ private Device removeAgedAttachmentPoints(Device device, Date currentDate) { Collection<DeviceAttachmentPoint> aps = device.getAttachmentPoints(); long dlAddr = device.getDataLayerAddressAsLong(); for (DeviceAttachmentPoint ap : aps) { int expire = ap.getExpire(); if (expire == 0) { expire = DEVICE_AP_MAX_AGE; } Date agedBoundary = ageBoundaryDifference(currentDate, expire); if (ap.getLastSeen().before(agedBoundary)) { devMgrMaps.delDevAttachmentPoint(dlAddr, ap.getSwitchPort()); evHistAttachmtPt(device.getDataLayerAddressAsLong(), ap.getSwitchPort(), EvAction.REMOVED, "Aged"); } } return devMgrMaps.getDeviceByDataLayerAddr(device.getDataLayerAddressAsLong()); } /** * Age device entry based on an expiry associated with the device. */ private void removeAgedDevices(Date currentDate) { Date deviceAgeBoundary = ageBoundaryDifference(currentDate, DEVICE_MAX_AGE); Collection<Device> deviceColl = devMgrMaps.getDevices(); for (Device device: deviceColl) { device = removeAgedNetworkAddresses(device, currentDate); device = removeAgedAttachmentPoints(device, currentDate); if ((device.getAttachmentPoints().size() == 0) && (device.getNetworkAddresses().size() == 0) && (device.getLastSeen().before(deviceAgeBoundary))) { delDevice(device); } } } protected static int DEVICE_AGING_TIMER= 60 * 15; // in seconds protected static final int DEVICE_AGING_TIMER_INTERVAL = 1; // in seconds /** * Create the deviceAgingTimer, which calls removeAgedDeviceState() * periodically. */ private void enableDeviceAgingTimer() { if (deviceAgingTimer != null) { return; } deviceAgingTimer = new Runnable() { @Override public void run() { Date currentDate = new Date(); removeAgedDevices(currentDate); if (deviceAgingTimer != null) { ScheduledExecutorService ses = threadPool.getScheduledExecutor(); ses.schedule(this, DEVICE_AGING_TIMER, TimeUnit.SECONDS); } } }; threadPool.getScheduledExecutor().schedule( deviceAgingTimer, DEVICE_AGING_TIMER_INTERVAL, TimeUnit.SECONDS); } @Override public boolean isCallbackOrderingPrereq(OFType type, String name) { return (type == OFType.PACKET_IN && name.equals("topology")); } @Override public boolean isCallbackOrderingPostreq(OFType type, String name) { return false; } protected class DeviceUpdateWorker implements Runnable { @Override public void run() { boolean updatePortChannel = portChannelConfigChanged; portChannelConfigChanged = false; if (updatePortChannel) { readPortChannelConfigFromStorage(); } try { log.debug("DeviceUpdateWorker: cleaning up attachment points"); for (IOFSwitch sw : devMgrMaps.getSwitches()) { // If the set of devices connected to the switches we // re-iterate a max of 3 times - WHY? TODO int maxIter = 3; while (maxIter > 0) { try { for (Device d: devMgrMaps.getDevicesOnASwitch(sw)) { Device dCopy = new Device(d); cleanupAttachmentPoints(dCopy); for (DeviceAttachmentPoint dap : dCopy.getOldAttachmentPoints()) { // Don't remove conflict attachment points // with recent activities if (dap.isInConflict() && !updatePortChannel) continue; // Delete from memory after storage, // otherwise an exception will // leave stale attachment points on storage. removeAttachmentPointFromStorage(dCopy, dap); dCopy.removeOldAttachmentPoint(dap); } // Update the maps with the new device copy devMgrMaps.updateMaps(dCopy); } break; } catch (ConcurrentModificationException e) { maxIter--; } catch (NullPointerException e) { } } if (maxIter == 0) { log.warn("Device attachment point clean up " + "attempted three times and failed."); } } log.debug("DeviceUpdateWorker: finished cleaning up device " + "attachment points"); } catch (StorageException e) { log.error("DeviceUpdateWorker had a storage exception, " + "Floodlight exiting"); System.exit(1); } } } @Override public void linkDiscoveryUpdate(LDUpdate update) { if (update.getOperation() == UpdateOperation.SWITCH_UPDATED) { updatedSwitch(update.getSrc(), update.getSrcType()); } else if (update.getOperation() == UpdateOperation.ADD_OR_UPDATE) { this.addedOrUpdatedLink(update.getSrc(), update.getSrcPort(), update.getSrcPortState(), update.getDst(), update.getDstPort(), update.getDstPortState(), update.getType()); } } public void updatedSwitch(long swId, SwitchType stype) { IOFSwitch sw = floodlightProvider.getSwitches().get(swId); if (sw.hasAttribute(IOFSwitch.SWITCH_IS_CORE_SWITCH)) { removedSwitch(sw); } } // ************************************************** // Device Manager's Event History members and methods // ************************************************** // Attachment-point event history public EventHistory<EventHistoryAttachmentPoint> evHistDevMgrAttachPt; public EventHistoryAttachmentPoint evHAP; private void evHistAttachmtPt(long dlAddr, SwitchPortTuple swPrt, EvAction action, String reason) { evHistAttachmtPt( dlAddr, swPrt.getSw().getId(), swPrt.getPort(), action, reason); } private void evHistAttachmtPt(byte [] mac, SwitchPortTuple swPrt, EvAction action, String reason) { evHistAttachmtPt(Ethernet.toLong(mac), swPrt.getSw().getId(), swPrt.getPort(), action, reason); } private void evHistAttachmtPt(Device d, long dpid, short port, EvAction op, String reason) { evHistAttachmtPt(d.getDataLayerAddressAsLong(),dpid, port, op, reason); } private void evHistAttachmtPt(long mac, long dpid, short port, EvAction action, String reason) { if (evHAP == null) { evHAP = new EventHistoryAttachmentPoint(); } evHAP.dpid = dpid; evHAP.port = port; evHAP.mac = mac; evHAP.reason = reason; evHAP = evHistDevMgrAttachPt.put(evHAP, action); } /*** * Packet-In Event history related classes and members * @author subrata * */ public EventHistory<OFMatch> evHistDevMgrPktIn; private void evHistPktIn(OFMatch packetIn) { evHistDevMgrPktIn.put(packetIn, EvAction.PKT_IN); } // IFloodlightModule methods @Override public Collection<Class<? extends IFloodlightService>> getModuleServices() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IDeviceManagerService.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(IDeviceManagerService.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(ITopologyService.class); l.add(ILinkDiscoveryService.class); l.add(IStorageSourceService.class); l.add(IThreadPoolService.class); l.add(IRestApiService.class); return l; } @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { // Wire up all our dependencies floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); topology = context.getServiceImpl(ITopologyService.class); linkDiscovery = context.getServiceImpl(ILinkDiscoveryService.class); storageSource = context.getServiceImpl(IStorageSourceService.class); threadPool = context.getServiceImpl(IThreadPoolService.class); restApi = context.getServiceImpl(IRestApiService.class); // We create this here because there is no ordering guarantee this.deviceManagerAware = new HashSet<IDeviceManagerAware>(); this.updates = new LinkedList<Update>(); this.devMgrMaps = new DevMgrMaps(); this.lock = new ReentrantReadWriteLock(); this.evHistDevMgrAttachPt = new EventHistory<EventHistoryAttachmentPoint>("Attachment-Point"); this.evHistDevMgrPktIn = new EventHistory<OFMatch>("Pakcet-In"); } @Override public void startUp(FloodlightModuleContext context) { // This is our 'constructor' if (linkDiscovery != null) { // Register to get updates from topology linkDiscovery.addListener(this); } else { log.error("Could not add linkdiscovery listener"); } if (topology != null) { topology.addListener(this); } else { log.error("Could not add topology listener"); } // Create our database tables storageSource.createTable(DEVICE_TABLE_NAME, null); storageSource.setTablePrimaryKeyName( DEVICE_TABLE_NAME, MAC_COLUMN_NAME); storageSource.createTable(DEVICE_ATTACHMENT_POINT_TABLE_NAME, null); storageSource.setTablePrimaryKeyName( DEVICE_ATTACHMENT_POINT_TABLE_NAME, ID_COLUMN_NAME); storageSource.createTable(DEVICE_NETWORK_ADDRESS_TABLE_NAME, null); storageSource.setTablePrimaryKeyName( DEVICE_NETWORK_ADDRESS_TABLE_NAME, ID_COLUMN_NAME); storageSource.createTable(PORT_CHANNEL_TABLE_NAME, null); storageSource.setTablePrimaryKeyName( PORT_CHANNEL_TABLE_NAME, PC_ID_COLUMN_NAME); storageSource.addListener(PORT_CHANNEL_TABLE_NAME, this); ScheduledExecutorService ses = threadPool.getScheduledExecutor(); deviceUpdateTask = new SingletonTask(ses, new DeviceUpdateWorker()); // Register for the OpenFlow messages we want floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this); floodlightProvider.addOFMessageListener(OFType.PORT_STATUS, this); // Register for switch events floodlightProvider.addOFSwitchListener(this); floodlightProvider.addInfoProvider("summary", this); // Register our REST API restApi.addRestletRoutable(new DeviceManagerWebRoutable()); // Read all our device state (MACs, IPs, attachment points) from storage readAllDeviceStateFromStorage(); // Device and storage aging. enableDeviceAgingTimer(); } @Override public void addListener(IDeviceManagerAware listener) { deviceManagerAware.add(listener); } @Override public Map<String, Object> getInfo(String type) { if (!"summary".equals(type)) return null; Map<String, Object> info = new HashMap<String, Object>(); info.put("# hosts", devMgrMaps.dataLayerAddressDeviceMap.size()); info.put("# IP Addresses", devMgrMaps.ipv4AddressDeviceMap.size()); int num_aps = 0; for (Map<Integer, Device> devAps : devMgrMaps.switchPortDeviceMap.values()) num_aps += devAps.size(); info.put("# attachment points", num_aps); return info; } }