| /* |
| * 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(); |
| } |
| } |
| } |
| } |