package net.floodlightcontroller.core.internal; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.HAListenerTypeMarker; import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IOFConnectionBackend; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IOFSwitch.SwitchStatus; import net.floodlightcontroller.core.IOFSwitchBackend; import net.floodlightcontroller.core.IOFSwitchDriver; import net.floodlightcontroller.core.IOFSwitchListener; import net.floodlightcontroller.core.LogicalOFMessageCategory; import net.floodlightcontroller.core.PortChangeType; import net.floodlightcontroller.core.SwitchDescription; import net.floodlightcontroller.core.SwitchSyncRepresentation; import net.floodlightcontroller.core.internal.Controller.IUpdate; import net.floodlightcontroller.core.internal.Controller.ModuleLoaderState; 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.debugcounter.IDebugCounterService; import org.projectfloodlight.openflow.protocol.OFControllerRole; import org.projectfloodlight.openflow.protocol.OFFactories; import org.projectfloodlight.openflow.protocol.OFFactory; import org.projectfloodlight.openflow.protocol.OFFeaturesReply; import org.projectfloodlight.openflow.protocol.OFMessage; 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.IPv4Address; import org.projectfloodlight.openflow.types.OFAuxId; import org.projectfloodlight.openflow.types.TableId; import org.projectfloodlight.openflow.types.TransportPort; import org.projectfloodlight.openflow.types.U32; import org.sdnplatform.sync.IStoreClient; import org.sdnplatform.sync.IStoreListener; import org.sdnplatform.sync.ISyncService; import org.sdnplatform.sync.Versioned; import org.sdnplatform.sync.error.SyncException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.MappingJsonFactory; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelOption; import io.netty.channel.group.DefaultChannelGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.util.HashedWheelTimer; import io.netty.util.Timer; import io.netty.util.concurrent.GlobalEventExecutor; /** * The Switch Manager class contains most of the code involved with dealing * with switches. The Switch manager keeps track of the switches known to the controller, * their status, and any important information about the switch lifecycle. The * Switch Manager also provides the switch service, which allows other modules * to hook in switch listeners and get basic access to switch information. * * @author gregor, capveg, sovietaced, rizard * */ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListener, IHAListener, IFloodlightModule, IOFSwitchService, IStoreListener<DatapathId> { private static final Logger log = LoggerFactory.getLogger(OFSwitchManager.class); private static volatile OFControllerRole role; private static SwitchManagerCounters counters; private static ISyncService syncService; private static IStoreClient<DatapathId, SwitchSyncRepresentation> storeClient; public static final String SWITCH_SYNC_STORE_NAME = OFSwitchManager.class.getCanonicalName() + ".stateStore"; private static int tcpSendBufferSize = 4 * 1024 * 1024; private static int workerThreads = 16; /* perform r/w I/O on accepted connections (switches) */ private static int bossThreads = 1; /* just listens and accepts on server socket; workers handle r/w I/O */ private static int connectionBacklog = 1000; /* pending connections boss thread will queue to accept */ private static int connectionTimeoutMsec = 60000; /* how long to allow TCP handshake to complete (default is 60ish secs) */ private static TransportPort openFlowPort = TransportPort.of(6653); private static Set<IPv4Address> openFlowAddresses = new HashSet<IPv4Address>(); private static String keyStorePassword; private static String keyStore; protected static boolean clearTablesOnInitialConnectAsMaster = false; protected static boolean clearTablesOnEachTransitionToMaster = false; protected static Map<DatapathId, TableId> forwardToControllerFlowsUpToTableByDpid; protected static TableId forwardToControllerFlowsUpToTable = TableId.of(4); /* this should cover most HW switches that have a couple SW-based flow tables */ protected static List<U32> ofBitmaps; protected static OFFactory defaultFactory; private static ConcurrentHashMap<DatapathId, OFSwitchHandshakeHandler> switchHandlers; private static ConcurrentHashMap<DatapathId, IOFSwitchBackend> switches; private static ConcurrentHashMap<DatapathId, IOFSwitch> syncedSwitches; protected static Map<DatapathId, OFControllerRole> switchInitialRole; private static ISwitchDriverRegistry driverRegistry; private Set<LogicalOFMessageCategory> logicalOFMessageCategories = new CopyOnWriteArraySet<LogicalOFMessageCategory>(); private static final List<IAppHandshakePluginFactory> handshakePlugins = new CopyOnWriteArrayList<IAppHandshakePluginFactory>(); private static int numRequiredConnections = -1; // ISwitchService protected static Set<IOFSwitchListener> switchListeners; // Module Dependencies private static IFloodlightProviderService floodlightProvider; private static IDebugCounterService debugCounterService; private static NioEventLoopGroup bossGroup; private static NioEventLoopGroup workerGroup; private static DefaultChannelGroup cg; protected static Timer timer; /** IHAListener Implementation **/ @Override public void transitionToActive() { role = HARole.ACTIVE.getOFRole(); } @Override public void transitionToStandby() { role = HARole.STANDBY.getOFRole(); } /** IOFSwitchManager Implementation **/ @Override public SwitchManagerCounters getCounters() { return counters; } private void addUpdateToQueue(IUpdate iUpdate) { floodlightProvider.addUpdateToQueue(iUpdate); } @Override public synchronized void switchAdded(IOFSwitchBackend sw) { DatapathId dpid = sw.getId(); IOFSwitchBackend oldSw = switches.put(dpid, sw); if (oldSw == sw) { // Note == for object equality, not .equals for value counters.errorActivatedSwitchNotPresent.increment(); log.error("Switch {} added twice?", sw); return; } else if (oldSw != null) { // This happens either when we have switches with duplicate // DPIDs or when a switch reconnects before we saw the // disconnect counters.switchWithSameDpidActivated.increment(); log.warn("New switch added {} for already-added switch {}", sw, oldSw); // We need to disconnect and remove the old switch oldSw.cancelAllPendingRequests(); addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.REMOVED)); oldSw.disconnect(); } /* * Set some other config options for this switch. */ if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_13) >= 0) { if (forwardToControllerFlowsUpToTableByDpid.containsKey(sw.getId())) { sw.setMaxTableForTableMissFlow(forwardToControllerFlowsUpToTableByDpid.get(sw.getId())); } else { sw.setMaxTableForTableMissFlow(forwardToControllerFlowsUpToTable); } } } @Override public synchronized void switchStatusChanged(IOFSwitchBackend sw, SwitchStatus oldStatus, SwitchStatus newStatus) { DatapathId dpid = sw.getId(); IOFSwitchBackend presentSw = switches.get(dpid); if (presentSw != sw) { // Note == for object equality, not .equals for value counters.errorActivatedSwitchNotPresent .increment(); log.debug("Switch {} status change but not present in sync manager", sw); return; } if(newStatus == SwitchStatus.MASTER && role != OFControllerRole.ROLE_MASTER) { counters.invalidSwitchActivatedWhileSlave.increment(); log.error("Switch {} activated but controller not MASTER", sw); sw.disconnect(); return; // only react to switch connections when master } if(!oldStatus.isVisible() && newStatus.isVisible()) { // the switch has just become visible. Send 'add' notification to our // listeners addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.ADDED)); } else if((oldStatus.isVisible() && !newStatus.isVisible())) { addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.REMOVED)); } // note: no else if - both may be true if(oldStatus != SwitchStatus.MASTER && newStatus == SwitchStatus.MASTER ) { counters.switchActivated.increment(); addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.ACTIVATED)); } else if(oldStatus == SwitchStatus.MASTER && newStatus != SwitchStatus.MASTER ) { counters.switchDeactivated.increment(); addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.DEACTIVATED)); } } @Override public synchronized void switchDisconnected(IOFSwitchBackend sw) { DatapathId dpid = sw.getId(); IOFSwitchBackend presentSw = switches.get(dpid); if (presentSw != sw) { // Note == for object equality, not .equals for value counters.errorActivatedSwitchNotPresent.increment(); log.warn("Switch {} disconnect but not present in sync manager", sw); return; } counters.switchDisconnected.increment(); switches.remove(dpid); } @Override public void handshakeDisconnected(DatapathId dpid) { switchHandlers.remove(dpid); } public Iterable<IOFSwitch> getActiveSwitches() { ImmutableList.Builder<IOFSwitch> builder = ImmutableList.builder(); for(IOFSwitch sw: switches.values()) { if(sw.getStatus().isControllable()) builder.add(sw); } return builder.build(); } public Map<DatapathId, IOFSwitch> getAllSwitchMap(boolean showInvisible) { if(showInvisible) { return ImmutableMap.<DatapathId, IOFSwitch>copyOf(switches); } else { ImmutableMap.Builder<DatapathId, IOFSwitch> builder = ImmutableMap.builder(); for(IOFSwitch sw: switches.values()) { if(sw.getStatus().isVisible()) builder.put(sw.getId(), sw); } return builder.build(); } } @Override public Map<DatapathId, IOFSwitch> getAllSwitchMap() { return getAllSwitchMap(true); } @Override public Set<DatapathId> getAllSwitchDpids() { return getAllSwitchMap().keySet(); } public Set<DatapathId> getAllSwitchDpids(boolean showInvisible) { return getAllSwitchMap(showInvisible).keySet(); } @Override public IOFSwitch getSwitch(DatapathId dpid) { return switches.get(dpid); } @Override public IOFSwitch getActiveSwitch(DatapathId dpid) { IOFSwitchBackend sw = switches.get(dpid); if(sw != null && sw.getStatus().isVisible()) return sw; else return null; } enum SwitchUpdateType { ADDED, REMOVED, ACTIVATED, DEACTIVATED, PORTCHANGED, OTHERCHANGE } /** * Update message indicating a switch was added or removed */ class SwitchUpdate implements IUpdate { private final DatapathId swId; private final SwitchUpdateType switchUpdateType; private final OFPortDesc port; private final PortChangeType changeType; public SwitchUpdate(DatapathId swId, SwitchUpdateType switchUpdateType) { this(swId, switchUpdateType, null, null); } public SwitchUpdate(DatapathId swId, SwitchUpdateType switchUpdateType, OFPortDesc port, PortChangeType changeType) { if (switchUpdateType == SwitchUpdateType.PORTCHANGED) { if (port == null) { throw new NullPointerException("Port must not be null " + "for PORTCHANGED updates"); } if (changeType == null) { throw new NullPointerException("ChangeType must not be " + "null for PORTCHANGED updates"); } } else { if (port != null || changeType != null) { throw new IllegalArgumentException("port and changeType " + "must be null for " + switchUpdateType + " updates"); } } this.swId = swId; this.switchUpdateType = switchUpdateType; this.port = port; this.changeType = changeType; } @Override public void dispatch() { if (log.isTraceEnabled()) { log.trace("Dispatching switch update {} {}", swId, switchUpdateType); } if (switchListeners != null) { for (IOFSwitchListener listener : switchListeners) { switch(switchUpdateType) { case ADDED: // don't count here. We have more specific // counters before the update is created listener.switchAdded(swId); break; case REMOVED: // don't count here. We have more specific // counters before the update is created listener.switchRemoved(swId); break; case PORTCHANGED: counters.switchPortChanged .increment(); listener.switchPortChanged(swId, port, changeType); break; case ACTIVATED: // don't count here. We have more specific // counters before the update is created listener.switchActivated(swId); break; case DEACTIVATED: // Called on master to slave transitions, ROLE_STATUS message. listener.switchDeactivated(swId); break; case OTHERCHANGE: counters.switchOtherChange .increment(); listener.switchChanged(swId); break; } } } } } /** * Handles a new OF Connection * @param IOFConnectionBackend connection an opened OF Connection * @param OFFeaturesReply featuresReply the features reply received for the opened connection. * It is needed for the rest of the switch handshake. */ @Override public void connectionOpened(IOFConnectionBackend connection, OFFeaturesReply featuresReply) { DatapathId dpid = connection.getDatapathId(); OFAuxId auxId = connection.getAuxId(); log.debug("{} opened", connection); if(auxId.equals(OFAuxId.MAIN)) { // Create a new switch handshake handler OFSwitchHandshakeHandler handler = new OFSwitchHandshakeHandler(connection, featuresReply, this, floodlightProvider.getRoleManager(), timer); OFSwitchHandshakeHandler oldHandler = switchHandlers.put(dpid, handler); // Disconnect all the handler's connections if(oldHandler != null){ log.debug("{} is a new main connection, killing old handler connections", connection); oldHandler.cleanup(); } handler.beginHandshake(); } else { OFSwitchHandshakeHandler handler = switchHandlers.get(dpid); if(handler != null) { handler.auxConnectionOpened(connection); } // Connections have arrived before the switchhandler is ready else { log.warn("{} arrived before main connection, closing connection", connection); connection.disconnect(); } } } @Override public synchronized void notifyPortChanged(IOFSwitchBackend sw, OFPortDesc port, PortChangeType changeType) { Preconditions.checkNotNull(sw, "switch must not be null"); Preconditions.checkNotNull(port, "port must not be null"); Preconditions.checkNotNull(changeType, "changeType must not be null"); if (role != OFControllerRole.ROLE_MASTER) { counters.invalidPortsChanged.increment(); return; } if (!switches.containsKey(sw.getId())) { counters.invalidPortsChanged.increment(); return; } if(sw.getStatus().isVisible()) { // no need to count here. SwitchUpdate.dispatch will count // the portchanged SwitchUpdate update = new SwitchUpdate(sw.getId(), SwitchUpdateType.PORTCHANGED, port, changeType); addUpdateToQueue(update); } } @Override public IOFSwitchBackend getOFSwitchInstance(IOFConnectionBackend connection, SwitchDescription description, OFFactory factory, DatapathId datapathId) { return driverRegistry.getOFSwitchInstance(connection, description, factory, datapathId); } @Override public void handleMessage(IOFSwitchBackend sw, OFMessage m, FloodlightContext bContext) { floodlightProvider.handleMessage(sw, m, bContext); } @Override public void handleOutgoingMessage(IOFSwitch sw, OFMessage m) { floodlightProvider.handleOutgoingMessage(sw, m); } @Override public void addOFSwitchDriver(String manufacturerDescriptionPrefix, IOFSwitchDriver driver) { driverRegistry.addSwitchDriver(manufacturerDescriptionPrefix, driver); } @Override public ImmutableList<OFSwitchHandshakeHandler> getSwitchHandshakeHandlers() { return ImmutableList.copyOf(switchHandlers.values()); } @Override public int getNumRequiredConnections() { Preconditions.checkState(numRequiredConnections >= 0, "numRequiredConnections not calculated"); return numRequiredConnections; } public Set<LogicalOFMessageCategory> getLogicalOFMessageCategories() { return logicalOFMessageCategories; } private int calcNumRequiredConnections() { if(!logicalOFMessageCategories.isEmpty()){ // We use tree set here to maintain ordering TreeSet<OFAuxId> auxConnections = new TreeSet<OFAuxId>(); for(LogicalOFMessageCategory category : logicalOFMessageCategories){ auxConnections.add(category.getAuxId()); } OFAuxId first = auxConnections.first(); OFAuxId last = auxConnections.last(); // Check for contiguous set (1....size()) if(first.equals(OFAuxId.MAIN)) { if(last.getValue() != auxConnections.size() - 1){ throw new IllegalStateException("Logical OF message categories must maintain contiguous OF Aux Ids! i.e. (0,1,2,3,4,5)"); } return auxConnections.size() - 1; } else if(first.equals(OFAuxId.of(1))) { if(last.getValue() != auxConnections.size()){ throw new IllegalStateException("Logical OF message categories must maintain contiguous OF Aux Ids! i.e. (1,2,3,4,5)"); } return auxConnections.size(); } else { throw new IllegalStateException("Logical OF message categories must start at 0 (MAIN) or 1"); } } else { return 0; } } /** ISwitchService Implementation **/ @Override public void addOFSwitchListener(IOFSwitchListener listener) { switchListeners.add(listener); } @Override public void removeOFSwitchListener(IOFSwitchListener listener) { switchListeners.remove(listener); } @Override public void registerLogicalOFMessageCategory(LogicalOFMessageCategory category) { logicalOFMessageCategories.add(category); } @Override public boolean isCategoryRegistered(LogicalOFMessageCategory category) { return logicalOFMessageCategories.contains(category); } @Override public void registerHandshakePlugin(IAppHandshakePluginFactory factory) { Preconditions.checkState(floodlightProvider.getModuleLoaderState() == ModuleLoaderState.INIT, "handshakeplugins can only be registered when the module loader is in state INIT!"); handshakePlugins.add(factory); } @Override public List<IAppHandshakePluginFactory> getHandshakePlugins() { return handshakePlugins; } /* IFloodlightModule Implementation */ @Override public Collection<Class<? extends IFloodlightService>> getModuleServices() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IOFSwitchService.class); return l; } @Override public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { Map<Class<? extends IFloodlightService>, IFloodlightService> m = new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); m.put(IOFSwitchService.class, this); return m; } @Override public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>(); l.add(IFloodlightProviderService.class); l.add(IDebugCounterService.class); l.add(ISyncService.class); return l; } @Override public void init(FloodlightModuleContext context) throws FloodlightModuleException { // Module dependencies floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); debugCounterService = context.getServiceImpl(IDebugCounterService.class); syncService = context.getServiceImpl(ISyncService.class); // Module variables switchHandlers = new ConcurrentHashMap<DatapathId, OFSwitchHandshakeHandler>(); switches = new ConcurrentHashMap<DatapathId, IOFSwitchBackend>(); syncedSwitches = new ConcurrentHashMap<DatapathId, IOFSwitch>(); counters = new SwitchManagerCounters(debugCounterService); driverRegistry = new NaiveSwitchDriverRegistry(this); switchListeners = new CopyOnWriteArraySet<IOFSwitchListener>(); timer = new HashedWheelTimer(); /* TODO try { storeClient = syncService.getStoreClient( SWITCH_SYNC_STORE_NAME, DatapathId.class, SwitchSyncRepresentation.class); storeClient.addStoreListener(this); } catch (UnknownStoreException e) { throw new FloodlightModuleException("Error while setting up sync store client", e); } */ /* * Get SSL config. * * If a password is blank, the password field may or may not be specified. * If it is specified, an empty string will be expected for blank. * * The path MUST be specified if SSL is enabled. */ Map<String, String> configParams = context.getConfigParams(this); String path = configParams.get("keyStorePath"); String pass = configParams.get("keyStorePassword"); String useSsl = configParams.get("useSsl"); if (useSsl == null || path == null || path.isEmpty() || (!useSsl.equalsIgnoreCase("yes") && !useSsl.equalsIgnoreCase("true") && !useSsl.equalsIgnoreCase("yep") && !useSsl.equalsIgnoreCase("ja") && !useSsl.equalsIgnoreCase("stimmt") ) ) { log.warn("SSL disabled. Using unsecure connections between Floodlight and switches."); OFSwitchManager.keyStore = null; OFSwitchManager.keyStorePassword = null; } else { log.info("SSL enabled. Using secure connections between Floodlight and switches."); log.info("SSL keystore path: {}, password: {}", path, (pass == null ? "" : pass)); OFSwitchManager.keyStore = path; OFSwitchManager.keyStorePassword = (pass == null ? "" : pass); } /* * Get config to define what to do when a switch connects. * * If a field is blank or unspecified, it will default */ String clearInitial = configParams.get("clearTablesOnInitialHandshakeAsMaster"); String clearLater = configParams.get("clearTablesOnEachTransitionToMaster"); if (clearInitial == null || clearInitial.isEmpty() || (!clearInitial.equalsIgnoreCase("yes") && !clearInitial.equalsIgnoreCase("true") && !clearInitial.equalsIgnoreCase("yep") && !clearInitial.equalsIgnoreCase("ja") && !clearInitial.equalsIgnoreCase("stimmt"))) { log.info("Clear switch flow tables on initial handshake as master: FALSE"); OFSwitchManager.clearTablesOnInitialConnectAsMaster = false; } else { log.info("Clear switch flow tables on initial handshake as master: TRUE"); OFSwitchManager.clearTablesOnInitialConnectAsMaster = true; } if (clearLater == null || clearLater.isEmpty() || (!clearLater.equalsIgnoreCase("yes") && !clearLater.equalsIgnoreCase("true") && !clearLater.equalsIgnoreCase("yep") && !clearLater.equalsIgnoreCase("ja") && !clearLater.equalsIgnoreCase("stimmt"))) { log.info("Clear switch flow tables on each transition to master: FALSE"); OFSwitchManager.clearTablesOnEachTransitionToMaster = false; } else { log.info("Clear switch flow tables on each transition to master: TRUE"); OFSwitchManager.clearTablesOnEachTransitionToMaster = true; } //Define initial role per switch String switchesInitialState = configParams.get("switchesInitialState"); switchInitialRole = jsonToSwitchInitialRoleMap(switchesInitialState); log.debug("SwitchInitialRole: {}", switchInitialRole.entrySet()); /* * Get default max table for forward to controller flows. * Internal default set as class variable at top of OFSwitchManager. */ String defaultFlowsUpToTable = configParams.get("defaultMaxTablesToReceiveTableMissFlow"); /* Backward compatibility */ if (defaultFlowsUpToTable == null || defaultFlowsUpToTable.isEmpty()) { defaultFlowsUpToTable = configParams.get("defaultMaxTableToReceiveTableMissFlow"); } if (defaultFlowsUpToTable != null && !defaultFlowsUpToTable.isEmpty()) { defaultFlowsUpToTable = defaultFlowsUpToTable.toLowerCase().trim(); try { forwardToControllerFlowsUpToTable = TableId.of(defaultFlowsUpToTable.startsWith("0x") ? Integer.parseInt(defaultFlowsUpToTable.replaceFirst("0x", ""), 16) : Integer.parseInt(defaultFlowsUpToTable)); log.info("Setting {} as the default max tables to receive table-miss flow", forwardToControllerFlowsUpToTable.toString()); } catch (IllegalArgumentException e) { log.error("Invalid table ID {} for default max tables to receive table-miss flow. Using pre-set of {}", defaultFlowsUpToTable, forwardToControllerFlowsUpToTable.toString()); } } else { log.info("Default max tables to receive table-miss flow not configured. Using {}", forwardToControllerFlowsUpToTable.toString()); } /* * Get config to define which tables per switch will get a * default forward-to-controller flow. This can be used to * reduce the number of such flows if only a reduced set of * tables are being used. */ String maxPerDpid = configParams.get("maxTablesToReceiveTableMissFlowPerDpid"); /* Backward compatibility */ if (maxPerDpid == null || maxPerDpid.isEmpty()) { maxPerDpid = configParams.get("maxTableToReceiveTableMissFlowPerDpid"); } forwardToControllerFlowsUpToTableByDpid = jsonToSwitchTableIdMap(maxPerDpid); /* * Get config to determine what versions of OpenFlow we will * support. The versions will determine the hello's header * version as well as the OF1.3.1 version bitmap contents. */ String protocols = configParams.get("supportedOpenFlowVersions"); List<OFVersion> ofVersions = new ArrayList<OFVersion>(); if (protocols != null && !protocols.isEmpty()) { protocols = protocols.toLowerCase(); /* * Brute-force check for all known versions. */ if (protocols.contains("1.0") || protocols.contains("10")) { ofVersions.add(OFVersion.OF_10); } if (protocols.contains("1.1") || protocols.contains("11")) { ofVersions.add(OFVersion.OF_11); } if (protocols.contains("1.2") || protocols.contains("12")) { ofVersions.add(OFVersion.OF_12); } if (protocols.contains("1.3") || protocols.contains("13")) { ofVersions.add(OFVersion.OF_13); } if (protocols.contains("1.4") || protocols.contains("14")) { ofVersions.add(OFVersion.OF_14); } if (protocols.contains("1.5") || protocols.contains("15")) { ofVersions.add(OFVersion.OF_15); } /* * TODO This will need to be updated if/when * Loxi is updated to support > 1.5. * * if (protocols.contains("1.6") || protocols.contains("16")) { * ofVersions.add(OFVersion.OF_16); * } */ } else { log.warn("Supported OpenFlow versions not specified. Using Loxi-defined {}", OFVersion.values()); ofVersions.addAll(Arrays.asList(OFVersion.values())); } /* Sanity check */ if (ofVersions.isEmpty()) { throw new IllegalStateException("OpenFlow version list should never be empty at this point. Make sure it's being populated in OFSwitchManager's init function."); } defaultFactory = computeInitialFactory(ofVersions); ofBitmaps = computeOurVersionBitmaps(ofVersions); log.debug("Computed OpenFlow version bitmap as {}", ofBitmaps); log.info("OpenFlow version {} will be advertised to switches. Supported fallback versions {}", defaultFactory.getVersion(), ofVersions); /* OpenFlow listen TCP port */ String ofPort = configParams.get("openFlowPort"); if (!Strings.isNullOrEmpty(ofPort)) { try { openFlowPort = TransportPort.of(Integer.parseInt(ofPort)); } catch (Exception e) { log.error("Invalid OpenFlow port {}, {}", ofPort, e); throw new FloodlightModuleException("Invalid OpenFlow port of " + ofPort + " in config"); } } /* Netty worker threads */ String threads = configParams.get("workerThreads"); if (!Strings.isNullOrEmpty(threads)) { workerThreads = Integer.parseInt(threads); } /* Netty boss threads */ threads = configParams.get("bossThreads"); if (!Strings.isNullOrEmpty(threads)) { bossThreads = Integer.parseInt(threads); } /* Netty TCP connection timeout */ String timeout = configParams.get("connectionTimeoutMs"); if (!Strings.isNullOrEmpty(timeout)) { connectionTimeoutMsec = Integer.parseInt(timeout); } /* Netty boss thread pending connection accept backlog */ String backlog = configParams.get("connectionBacklog"); if (!Strings.isNullOrEmpty(backlog)) { connectionBacklog = Integer.parseInt(backlog); } /* OpenFlow listen addresses */ String addresses = configParams.get("openFlowAddresses"); if (!Strings.isNullOrEmpty(addresses)) { try { openFlowAddresses = Collections.singleton(IPv4Address.of(addresses)); //TODO support list of addresses for multi-honed controllers } catch (Exception e) { log.error("Invalid OpenFlow address {}, {}", addresses, e); throw new FloodlightModuleException("Invalid OpenFlow address of " + addresses + " in config"); } log.debug("OpenFlow addresses set to {}", openFlowAddresses); } else { openFlowAddresses.add(IPv4Address.NONE); } /* OpenFlow port TCP send buffer size */ String tcpBuffer = configParams.get("tcpSendBufferSizeBytes"); if (!Strings.isNullOrEmpty(tcpBuffer)) { tcpSendBufferSize = Integer.parseInt(tcpBuffer); } log.info("Listening for OpenFlow switches on {}:{}", openFlowAddresses, openFlowPort); log.info("OpenFlow socket config: " + "{} boss thread(s), " + "{} worker thread(s), " + "{} ms TCP connection timeout, " + "max {} connection backlog, " + "{} byte TCP send buffer size", new Object[] { bossThreads, workerThreads, connectionTimeoutMsec, connectionBacklog, tcpSendBufferSize }); } /** * Find the max version supplied in the supported * versions list and use it as the default, which * will subsequently be used in our hello message * header's version field. * * The factory can be later "downgraded" to a lower * version depending on what's computed during the * version-negotiation part of the handshake. * * @param ofVersions the OpenFlow versions we support * @return the highest-version OFFactory we support */ private OFFactory computeInitialFactory(List<OFVersion> ofVersions) { /* This should NEVER happen. Double-checking. */ if (ofVersions == null || ofVersions.isEmpty()) { throw new IllegalStateException("OpenFlow version list should never be null or empty at this point. Make sure it's set in the OFSwitchManager."); } OFVersion highest = null; for (OFVersion v : ofVersions) { if (highest == null) { highest = v; } else if (v.compareTo(highest) > 0) { highest = v; } } /* * This assumes highest != null, which * it won't be if the list of versions * is not empty. */ return OFFactories.getFactory(highest); } /** * Based on the list of OFVersions provided as input (or from Loxi), * create a list of bitmaps for use in version negotiation during a * cross-version OpenFlow handshake where both parties support * OpenFlow versions >= 1.3.1. * * @param ofVersions the OpenFlow versions we support * @return list of bitmaps for the versions of OpenFlow we support */ private List<U32> computeOurVersionBitmaps(List<OFVersion> ofVersions) { /* This should NEVER happen. Double-checking. */ if (ofVersions == null || ofVersions.isEmpty()) { throw new IllegalStateException("OpenFlow version list should never be null or empty at this point. Make sure it's set in the OFSwitchManager."); } int pos = 1; /* initial bitmap in list */ int size = 32; /* size of a U32 */ int tempBitmap = 0; /* maintain the current bitmap we're working on */ List<U32> bitmaps = new ArrayList<U32>(); ArrayList<OFVersion> sortedVersions = new ArrayList<OFVersion>(ofVersions); Collections.sort(sortedVersions); for (OFVersion v : sortedVersions) { /* Move on to another bitmap */ if (v.getWireVersion() > pos * size - 1 ) { bitmaps.add(U32.ofRaw(tempBitmap)); tempBitmap = 0; pos++; } tempBitmap = tempBitmap | (1 << (v.getWireVersion() % size)); } if (tempBitmap != 0) { bitmaps.add(U32.ofRaw(tempBitmap)); } return bitmaps; } private static Map<DatapathId, TableId> jsonToSwitchTableIdMap(String json) { MappingJsonFactory f = new MappingJsonFactory(); JsonParser jp; Map<DatapathId, TableId> retValue = new HashMap<DatapathId, TableId>(); if (json == null || json.isEmpty()) { return retValue; } try { try { jp = f.createParser(json); } catch (JsonParseException e) { throw new IOException(e); } jp.nextToken(); if (jp.getCurrentToken() != JsonToken.START_OBJECT) { throw new IOException("Expected START_OBJECT"); } while (jp.nextToken() != JsonToken.END_OBJECT) { if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { throw new IOException("Expected FIELD_NAME"); } String n = jp.getCurrentName(); jp.nextToken(); if (jp.getText().equals("")) { continue; } DatapathId dpid; try { n = n.trim(); dpid = DatapathId.of(n); TableId tablesToGetDefaultFlow; String value = jp.getText(); if (value != null && !value.isEmpty()) { value = value.trim().toLowerCase(); try { tablesToGetDefaultFlow = TableId.of( value.startsWith("0x") ? Integer.parseInt(value.replaceFirst("0x", ""), 16) : Integer.parseInt(value) ); /* will throw exception if outside valid TableId number range */ retValue.put(dpid, tablesToGetDefaultFlow); log.debug("Setting max tables to receive table-miss flow to {} for DPID {}", tablesToGetDefaultFlow.toString(), dpid.toString()); } catch (IllegalArgumentException e) { /* catches both IllegalArgumentExcpt. and NumberFormatExcpt. */ log.error("Invalid value of {} for max tables to receive table-miss flow for DPID {}. Using default of {}.", value, dpid.toString()); } } } catch (NumberFormatException e) { log.error("Invalid DPID format {} for max tables to receive table-miss flow for specific DPID. Using default for the intended DPID.", n); } } } catch (IOException e) { log.error("Using default for remaining DPIDs. JSON formatting error in max tables to receive table-miss flow for DPID input String: {}", e); } return retValue; } @Override public void startUp(FloodlightModuleContext context) throws FloodlightModuleException { startUpBase(context); bootstrapNetty(); } /** * Startup method that includes everything besides the netty boostrap. * This has been isolated for testing. * @param context floodlight module context * @throws FloodlightModuleException */ public void startUpBase(FloodlightModuleContext context) throws FloodlightModuleException { // Initial Role role = floodlightProvider.getRole().getOFRole(); // IRoleListener floodlightProvider.addHAListener(this); loadLogicalCategories(); } /** * Bootstraps netty, the server that handles all openflow connections */ public void bootstrapNetty() { try { bossGroup = new NioEventLoopGroup(bossThreads); workerGroup = new NioEventLoopGroup(workerThreads); ServerBootstrap bootstrap = new ServerBootstrap() .group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_REUSEADDR, true) .option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.SO_SNDBUF, tcpSendBufferSize) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeoutMsec) .option(ChannelOption.SO_BACKLOG, connectionBacklog); OFChannelInitializer initializer = new OFChannelInitializer( this, this, debugCounterService, timer, ofBitmaps, defaultFactory, keyStore, keyStorePassword); bootstrap.childHandler(initializer); cg = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); Set<InetSocketAddress> addrs = new HashSet<InetSocketAddress>(); if (openFlowAddresses.isEmpty()) { cg.add(bootstrap.bind(new InetSocketAddress(InetAddress.getByAddress(IPv4Address.NONE.getBytes()), openFlowPort.getPort())).channel()); } else { for (IPv4Address ip : openFlowAddresses) { addrs.add(new InetSocketAddress(InetAddress.getByAddress(ip.getBytes()), openFlowPort.getPort())); } } for (InetSocketAddress sa : addrs) { cg.add(bootstrap.bind(sa).channel()); log.debug("Listening for switch connections on {}", sa); } } catch (Exception e) { throw new RuntimeException(e); } } /** * Performs startup related actions for logical OF message categories. * Setting the categories list to immutable ensures that unsupported operation * exceptions will be activated if modifications are attempted. */ public void loadLogicalCategories() { logicalOFMessageCategories = ImmutableSet.copyOf(logicalOFMessageCategories); numRequiredConnections = calcNumRequiredConnections(); } @Override public String getName() { return null; } @Override public boolean isCallbackOrderingPrereq(HAListenerTypeMarker type, String name) { return false; } @Override public boolean isCallbackOrderingPostreq(HAListenerTypeMarker type, String name) { return false; } @Override public void controllerNodeIPsChanged(Map<String, String> curControllerNodeIPs, Map<String, String> addedControllerNodeIPs, Map<String, String> removedControllerNodeIPs) { } @Override public void keysModified(Iterator<DatapathId> keys, UpdateType type) { if (type == UpdateType.LOCAL) { // We only care for remote updates return; } while(keys.hasNext()) { DatapathId key = keys.next(); Versioned<SwitchSyncRepresentation> versionedSwitch = null; try { versionedSwitch = storeClient.get(key); } catch (SyncException e) { log.error("Exception while retrieving switch " + key.toString() + " from sync store. Skipping", e); continue; } if (log.isTraceEnabled()) { log.trace("Reveiced switch store notification: key={}, " + "entry={}", key, versionedSwitch.getValue()); } // versionedSwtich won't be null. storeClient.get() always // returns a non-null or throws an exception if (versionedSwitch.getValue() == null) { switchRemovedFromStore(key); continue; } SwitchSyncRepresentation storedSwitch = versionedSwitch.getValue(); IOFSwitch sw = getSwitch(storedSwitch.getDpid()); //TODO need to get IOFSwitchBackend setFeaturesReply(storedSwitch.getFeaturesReply(sw.getOFFactory())); if (!key.equals(storedSwitch.getFeaturesReply(sw.getOFFactory()).getDatapathId())) { log.error("Inconsistent DPIDs from switch sync store: " + "key is {} but sw.getId() says {}. Ignoring", key.toString(), sw.getId()); continue; } switchAddedToStore(sw); } } /** * Called when we receive a store notification about a switch that * has been removed from the sync store * @param dpid */ private synchronized void switchRemovedFromStore(DatapathId dpid) { if (floodlightProvider.getRole() != HARole.STANDBY) { return; // only read from store if slave } IOFSwitch oldSw = syncedSwitches.remove(dpid); if (oldSw != null) { addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.REMOVED)); } } /** * Called when we receive a store notification about a new or updated * switch. * @param sw */ private synchronized void switchAddedToStore(IOFSwitch sw) { if (floodlightProvider.getRole() != HARole.STANDBY) { return; // only read from store if slave } DatapathId dpid = sw.getId(); IOFSwitch oldSw = syncedSwitches.put(dpid, sw); if (oldSw == null) { addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.ADDED)); } else { // The switch already exists in storage, see if anything // has changed sendNotificationsIfSwitchDiffers(oldSw, sw); } } /** * Check if the two switches differ in their ports or in other * fields and if they differ enqueue a switch update * @param oldSw * @param newSw */ private synchronized void sendNotificationsIfSwitchDiffers(IOFSwitch oldSw, IOFSwitch newSw) { for (OFPortDesc oldPort : oldSw.getPorts()) { if (newSw.getPort(oldPort.getPortNo()) == null) { /* delete */ SwitchUpdate update = new SwitchUpdate(newSw.getId(), SwitchUpdateType.PORTCHANGED, oldPort, PortChangeType.DELETE); addUpdateToQueue(update); } else { /* in common; some form of update */ OFPortDesc newPort = newSw.getPort(oldPort.getPortNo()); if (newPort.getState().contains(OFPortState.LINK_DOWN) && /* went down */ !oldPort.getState().contains(OFPortState.LINK_DOWN)) { SwitchUpdate update = new SwitchUpdate(newSw.getId(), SwitchUpdateType.PORTCHANGED, newPort, PortChangeType.DOWN); addUpdateToQueue(update); } else if (newPort.getState().contains(OFPortState.LIVE) && /* went up */ !oldPort.getState().contains(OFPortState.LIVE)) { SwitchUpdate update = new SwitchUpdate(newSw.getId(), SwitchUpdateType.PORTCHANGED, newPort, PortChangeType.UP); addUpdateToQueue(update); } else if (!newPort.equals(oldPort)) { SwitchUpdate update = new SwitchUpdate(newSw.getId(), SwitchUpdateType.PORTCHANGED, newPort, PortChangeType.OTHER_UPDATE); addUpdateToQueue(update); } } } for (OFPortDesc newPort : newSw.getPorts()) { if (oldSw.getPort(newPort.getPortNo()) == null) { /* add */ SwitchUpdate update = new SwitchUpdate(newSw.getId(), SwitchUpdateType.PORTCHANGED, newPort, PortChangeType.ADD); addUpdateToQueue(update); } } } /** * Tulio Ribeiro * @param String json * @return Map<DatapathId, OFControllerRole> */ private static Map<DatapathId, OFControllerRole> jsonToSwitchInitialRoleMap(String json) { MappingJsonFactory f = new MappingJsonFactory(); JsonParser jp; Map<DatapathId, OFControllerRole> retValue = new HashMap<DatapathId, OFControllerRole>(); if (json == null || json.isEmpty()) { return retValue; } try { try { jp = f.createParser(json); } catch (JsonParseException e) { throw new IOException(e); } jp.nextToken(); if (jp.getCurrentToken() != JsonToken.START_OBJECT) { throw new IOException("Expected START_OBJECT"); } while (jp.nextToken() != JsonToken.END_OBJECT) { if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { throw new IOException("Expected FIELD_NAME"); } String n = jp.getCurrentName(); jp.nextToken(); if (jp.getText().equals("")) { continue; } DatapathId dpid; OFControllerRole ofcr=OFControllerRole.ROLE_NOCHANGE; try { n = n.trim(); dpid = DatapathId.of(n); ofcr = OFControllerRole.valueOf(jp.getText()); retValue.put(dpid, ofcr); } catch (NumberFormatException e) { log.error("Invalid DPID format: {}, or OFControllerRole: {}", n, ofcr); } } } catch (IOException e) { log.error("Problem: {}", e); } return retValue; } @Override public void addSwitchEvent(DatapathId switchDpid, String reason, boolean flushNow) {} }