/* * Copyright 2016-present Open Networking Laboratory * * 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 org.onosproject.isis.controller.impl; import com.fasterxml.jackson.databind.JsonNode; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.channel.AdaptiveReceiveBufferSizePredictor; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelPipelineFactory; import org.jboss.netty.channel.FixedReceiveBufferSizePredictorFactory; import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; import org.onlab.packet.Ip4Address; import org.onlab.packet.MacAddress; import org.onlab.packet.TpPort; import org.onosproject.isis.controller.IsisInterface; import org.onosproject.isis.controller.IsisNetworkType; import org.onosproject.isis.controller.IsisProcess; import org.onosproject.isis.controller.IsisRouterType; import org.onosproject.isis.controller.topology.IsisAgent; import org.onosproject.isis.controller.topology.IsisLink; import org.onosproject.isis.controller.topology.IsisRouter; import org.onosproject.isis.io.util.IsisConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.NetworkInterface; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import static org.onlab.util.Tools.groupedThreads; /** * Representation of an ISIS controller. */ public class Controller { protected static final int BUFFER_SIZE = 4 * 1024 * 1024; private static final Logger log = LoggerFactory.getLogger(Controller.class); private static final int RETRY_INTERVAL = 4; private final int peerWorkerThreads = 16; byte[] configPacket = null; private List<IsisProcess> processes = null; private IsisChannelHandler isisChannelHandler; private NioClientSocketChannelFactory peerExecFactory; private ClientBootstrap peerBootstrap = null; private TpPort isisPort = TpPort.tpPort(IsisConstants.SPORT); private ScheduledExecutorService connectExecutor = null; private int connectRetryCounter = 0; private int connectRetryTime; private ScheduledFuture future = null; private IsisAgent agent; /** * Deactivates ISIS controller. */ public void isisDeactivate() { disconnectExecutor(); processes = null; peerExecFactory.shutdown(); } /** * Sets ISIS agent. * * @param agent ISIS agent instance */ public void setAgent(IsisAgent agent) { this.agent = agent; } /** * Updates the processes configuration. * * @param jsonNode json node instance * @throws Exception might throws parse exception */ public void updateConfig(JsonNode jsonNode) throws Exception { log.debug("Controller::UpdateConfig called"); configPacket = new byte[IsisConstants.CONFIG_LENGTH]; byte numberOfInterface = 0; // number of interfaces to configure configPacket[0] = (byte) 0xFF; // its a conf packet - identifier List<IsisProcess> isisProcesses = getConfig(jsonNode); for (IsisProcess isisProcess : isisProcesses) { log.debug("IsisProcessDetails : " + isisProcess); for (IsisInterface isisInterface : isisProcess.isisInterfaceList()) { DefaultIsisInterface isisInterfaceImpl = (DefaultIsisInterface) isisInterface; log.debug("IsisInterfaceDetails : " + isisInterface); numberOfInterface++; configPacket[2 * numberOfInterface] = (byte) isisInterfaceImpl.interfaceIndex(); if (isisInterface.networkType() == IsisNetworkType.BROADCAST && isisInterfaceImpl.reservedPacketCircuitType() == IsisRouterType.L1.value()) { configPacket[(2 * numberOfInterface) + 1] = (byte) 0; } else if (isisInterface.networkType() == IsisNetworkType.BROADCAST && isisInterfaceImpl.reservedPacketCircuitType() == IsisRouterType.L2.value()) { configPacket[(2 * numberOfInterface) + 1] = (byte) 1; } else if (isisInterface.networkType() == IsisNetworkType.P2P) { configPacket[(2 * numberOfInterface) + 1] = (byte) 2; } else if (isisInterface.networkType() == IsisNetworkType.BROADCAST && isisInterfaceImpl.reservedPacketCircuitType() == IsisRouterType.L1L2.value()) { configPacket[(2 * numberOfInterface) + 1] = (byte) 3; } } } configPacket[1] = numberOfInterface; //First time configuration if (processes == null) { if (!isisProcesses.isEmpty()) { processes = isisProcesses; connectPeer(); } } else { isisChannelHandler.updateInterfaceMap(isisProcesses); //Send the config packet isisChannelHandler.sentConfigPacket(configPacket); } } /** * Initializes the netty client channel connection. */ private void initConnection() { if (peerBootstrap != null) { return; } peerBootstrap = createPeerBootStrap(); peerBootstrap.setOption("reuseAddress", true); peerBootstrap.setOption("tcpNoDelay", true); peerBootstrap.setOption("keepAlive", true); peerBootstrap.setOption("receiveBufferSize", Controller.BUFFER_SIZE); peerBootstrap.setOption("receiveBufferSizePredictorFactory", new FixedReceiveBufferSizePredictorFactory( Controller.BUFFER_SIZE)); peerBootstrap.setOption("receiveBufferSizePredictor", new AdaptiveReceiveBufferSizePredictor(64, 1024, 65536)); peerBootstrap.setOption("child.keepAlive", true); peerBootstrap.setOption("child.tcpNoDelay", true); peerBootstrap.setOption("child.sendBufferSize", Controller.BUFFER_SIZE); peerBootstrap.setOption("child.receiveBufferSize", Controller.BUFFER_SIZE); peerBootstrap.setOption("child.receiveBufferSizePredictorFactory", new FixedReceiveBufferSizePredictorFactory( Controller.BUFFER_SIZE)); peerBootstrap.setOption("child.reuseAddress", true); isisChannelHandler = new IsisChannelHandler(this, processes); ChannelPipelineFactory pfact = new IsisPipelineFactory(isisChannelHandler); peerBootstrap.setPipelineFactory(pfact); } /** * Creates peer boot strap. * * @return client bootstrap instance */ private ClientBootstrap createPeerBootStrap() { if (peerWorkerThreads == 0) { peerExecFactory = new NioClientSocketChannelFactory( Executors.newCachedThreadPool(groupedThreads("onos/isis", "boss-%d")), Executors.newCachedThreadPool(groupedThreads("onos/isis", "worker-%d"))); return new ClientBootstrap(peerExecFactory); } else { peerExecFactory = new NioClientSocketChannelFactory( Executors.newCachedThreadPool(groupedThreads("onos/isis", "boss-%d")), Executors.newCachedThreadPool(groupedThreads("onos/isis", "worker-%d")), peerWorkerThreads); return new ClientBootstrap(peerExecFactory); } } /** * Gets all configured processes. * * @return all configured processes */ public List<IsisProcess> getAllConfiguredProcesses() { return processes; } /** * Gets the list of processes configured. * * @param json posted json * @return list of processes configured */ private List<IsisProcess> getConfig(JsonNode json) throws Exception { List<IsisProcess> isisProcessesList = new ArrayList<>(); JsonNode jsonNodes = json; if (jsonNodes == null) { return isisProcessesList; } jsonNodes.forEach(jsonNode -> { List<IsisInterface> interfaceList = new ArrayList<>(); for (JsonNode jsonNode1 : jsonNode.path(IsisConstants.INTERFACE)) { IsisInterface isisInterface = new DefaultIsisInterface(); String index = jsonNode1.path(IsisConstants.INTERFACEINDEX).asText(); if (isPrimitive(index)) { int input = Integer.parseInt(index); if (input < 1 || input > 255) { log.debug("Wrong interface index: {}", index); continue; } isisInterface.setInterfaceIndex(Integer.parseInt(index)); } else { log.debug("Wrong interface index {}", index); continue; } Ip4Address ipAddress = getInterfaceIp(isisInterface.interfaceIndex()); if (ipAddress != null && !ipAddress.equals(IsisConstants.DEFAULTIP)) { isisInterface.setInterfaceIpAddress(ipAddress); } else { log.debug("Wrong interface index {}. No matching interface in system.", index); continue; } MacAddress macAddress = getInterfaceMac(isisInterface.interfaceIndex()); if (macAddress != null) { isisInterface.setInterfaceMacAddress(macAddress); } else { log.debug("Wrong interface index {}. No matching interface in system.", index); continue; } String mask = getInterfaceMask(isisInterface.interfaceIndex()); if (mask != null) { try { isisInterface.setNetworkMask(InetAddress.getByName(mask).getAddress()); } catch (UnknownHostException e) { log.debug("Wrong interface index {}. Error while getting network mask.", index); } } else { log.debug("Wrong interface index {}. Error while getting network mask.", index); continue; } isisInterface.setIntermediateSystemName(jsonNode1 .path(IsisConstants.INTERMEDIATESYSTEMNAME) .asText()); String systemId = jsonNode1.path(IsisConstants.SYSTEMID).asText(); if (isValidSystemId(systemId)) { isisInterface.setSystemId(systemId); } else { log.debug("Wrong systemId: {} for interface index {}.", systemId, index); continue; } String circuitType = jsonNode1.path(IsisConstants.RESERVEDPACKETCIRCUITTYPE).asText(); if (isPrimitive(circuitType)) { int input = Integer.parseInt(circuitType); if (input < 1 || input > 3) { log.debug("Wrong ReservedPacketCircuitType: {} for interface index {}.", circuitType, index); continue; } isisInterface.setReservedPacketCircuitType(input); } else { log.debug("Wrong ReservedPacketCircuitType: {} for interface index {}.", circuitType, index); continue; } String networkType = jsonNode1.path(IsisConstants.NETWORKTYPE).asText(); if (isPrimitive(networkType)) { int input = Integer.parseInt(networkType); if (input < 1 || input > 2) { log.debug("Wrong networkType: {} for interface index {}.", networkType, index); continue; } isisInterface.setNetworkType(IsisNetworkType.get(input)); } else { log.debug("Wrong networkType: {} for interface index {}.", networkType, index); continue; } String areaAddress = jsonNode1.path(IsisConstants.AREAADDRESS).asText(); if (isPrimitive(areaAddress)) { if (areaAddress.length() > 7) { log.debug("Wrong areaAddress: {} for interface index {}.", areaAddress, index); continue; } isisInterface.setAreaAddress(areaAddress); } else { log.debug("Wrong areaAddress: {} for interface index {}.", areaAddress, index); continue; } String circuitId = jsonNode1.path(IsisConstants.CIRCUITID).asText(); if (isPrimitive(circuitId)) { int input = Integer.parseInt(circuitId); if (input < 1) { log.debug("Wrong circuitId: {} for interface index {}.", circuitId, index); continue; } isisInterface.setCircuitId(circuitId); } else { log.debug("Wrong circuitId: {} for interface index {}.", circuitId, index); continue; } String holdingTime = jsonNode1.path(IsisConstants.HOLDINGTIME).asText(); if (isPrimitive(holdingTime)) { int input = Integer.parseInt(holdingTime); if (input < 1 || input > 255) { log.debug("Wrong holdingTime: {} for interface index {}.", holdingTime, index); continue; } isisInterface.setHoldingTime(input); } else { log.debug("Wrong holdingTime: {} for interface index {}.", holdingTime, index); continue; } String helloInterval = jsonNode1.path(IsisConstants.HELLOINTERVAL).asText(); if (isPrimitive(helloInterval)) { int interval = Integer.parseInt(helloInterval); if (interval > 0 && interval <= 255) { isisInterface.setHelloInterval(interval); } else { log.debug("Wrong hello interval: {} for interface index {}.", helloInterval, index); continue; } } else { log.debug("Wrong hello interval: {} for interface index {}.", helloInterval, index); continue; } interfaceList.add(isisInterface); } if (!interfaceList.isEmpty()) { IsisProcess process = new DefaultIsisProcess(); process.setProcessId(jsonNode.path(IsisConstants.PROCESSESID).asText()); process.setIsisInterfaceList(interfaceList); isisProcessesList.add(process); } }); return isisProcessesList; } /** * Returns interface MAC by index. * * @param interfaceIndex interface index * @return interface IP by index */ private MacAddress getInterfaceMac(int interfaceIndex) { MacAddress macAddress = null; try { NetworkInterface networkInterface = NetworkInterface.getByIndex(interfaceIndex); macAddress = MacAddress.valueOf(networkInterface.getHardwareAddress()); } catch (Exception e) { log.debug("Error while getting Interface IP by index"); return macAddress; } return macAddress; } /** * Returns interface IP by index. * * @param interfaceIndex interface index * @return interface IP by index */ private Ip4Address getInterfaceIp(int interfaceIndex) { Ip4Address ipAddress = null; try { NetworkInterface networkInterface = NetworkInterface.getByIndex(interfaceIndex); Enumeration ipAddresses = networkInterface.getInetAddresses(); while (ipAddresses.hasMoreElements()) { InetAddress address = (InetAddress) ipAddresses.nextElement(); if (!address.isLinkLocalAddress()) { ipAddress = Ip4Address.valueOf(address.getAddress()); break; } } } catch (Exception e) { log.debug("Error while getting Interface IP by index"); return IsisConstants.DEFAULTIP; } return ipAddress; } /** * Returns interface MAC by index. * * @param interfaceIndex interface index * @return interface IP by index */ private String getInterfaceMask(int interfaceIndex) { String subnetMask = null; try { Ip4Address ipAddress = getInterfaceIp(interfaceIndex); NetworkInterface networkInterface = NetworkInterface.getByInetAddress( InetAddress.getByName(ipAddress.toString())); Enumeration ipAddresses = networkInterface.getInetAddresses(); int index = 0; while (ipAddresses.hasMoreElements()) { InetAddress address = (InetAddress) ipAddresses.nextElement(); if (!address.isLinkLocalAddress()) { break; } index++; } int prfLen = networkInterface.getInterfaceAddresses().get(index).getNetworkPrefixLength(); int shft = 0xffffffff << (32 - prfLen); int oct1 = ((byte) ((shft & 0xff000000) >> 24)) & 0xff; int oct2 = ((byte) ((shft & 0x00ff0000) >> 16)) & 0xff; int oct3 = ((byte) ((shft & 0x0000ff00) >> 8)) & 0xff; int oct4 = ((byte) (shft & 0x000000ff)) & 0xff; subnetMask = oct1 + "." + oct2 + "." + oct3 + "." + oct4; } catch (Exception e) { log.debug("Error while getting Interface network mask by index"); return subnetMask; } return subnetMask; } /** * Checks if primitive or not. * * @param value input value * @return true if number else false */ private boolean isPrimitive(String value) { boolean status = true; value = value.trim(); if (value.length() < 1) { return false; } for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); if (!Character.isDigit(c)) { status = false; break; } } return status; } /** * Checks if system id is valid or not. * * @param value input value * @return true if valid else false */ private boolean isValidSystemId(String value) { value = value.trim(); boolean status = true; if (value.length() != 14) { return false; } for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); if (!Character.isDigit(c)) { if (!((i == 4 || i == 9) && c == '.')) { status = false; break; } } } return status; } /** * Disconnects the executor. */ public void disconnectExecutor() { if (connectExecutor != null) { future.cancel(true); connectExecutor.shutdownNow(); connectExecutor = null; } } /** * Connects to peer. */ public void connectPeer() { scheduleConnectionRetry(this.connectRetryTime); } /** * Retry connection with exponential back-off mechanism. * * @param retryDelay retry delay */ private void scheduleConnectionRetry(long retryDelay) { if (connectExecutor == null) { connectExecutor = Executors.newSingleThreadScheduledExecutor(); } future = connectExecutor.schedule(new ConnectionRetry(), retryDelay, TimeUnit.MINUTES); } /** * Adds device details. * * @param isisRouter ISIS router instance */ public void addDeviceDetails(IsisRouter isisRouter) { agent.addConnectedRouter(isisRouter); } /** * Removes device details. * * @param isisRouter Isis router instance */ public void removeDeviceDetails(IsisRouter isisRouter) { agent.removeConnectedRouter(isisRouter); } /** * Adds link details. * * @param isisLink ISIS link instance */ public void addLinkDetails(IsisLink isisLink) { agent.addLink(isisLink); } /** * Removes link details. * * @param isisLink ISIS link instance */ public void removeLinkDetails(IsisLink isisLink) { agent.deleteLink(isisLink); } /** * Returns the isisAgent instance. * * @return agent */ public IsisAgent agent() { return this.agent; } /** * Implements ISIS connection and manages connection to peer with back-off mechanism in case of failure. */ class ConnectionRetry implements Runnable { @Override public void run() { log.debug("Connect to peer {}", IsisConstants.SHOST); initConnection(); isisChannelHandler.sentConfigPacket(configPacket); InetSocketAddress connectToSocket = new InetSocketAddress(IsisConstants.SHOST, isisPort.toInt()); try { peerBootstrap.connect(connectToSocket).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (!future.isSuccess()) { connectRetryCounter++; log.error("Connection failed, ConnectRetryCounter {} remote host {}", connectRetryCounter, IsisConstants.SHOST); /* * Reconnect to peer on failure is exponential till 4 mins, later on retry after every 4 * mins. */ if (connectRetryTime < RETRY_INTERVAL) { connectRetryTime = (connectRetryTime != 0) ? connectRetryTime * 2 : 1; } scheduleConnectionRetry(connectRetryTime); } else { //Send the config packet isisChannelHandler.sentConfigPacket(configPacket); connectRetryCounter++; log.info("Connected to remote host {}, Connect Counter {}", IsisConstants.SHOST, connectRetryCounter); disconnectExecutor(); return; } } }); } catch (Exception e) { log.info("Connect peer exception : " + e.toString()); disconnectExecutor(); } } } }