| /* |
| * Copyright 2015-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.pcep.controller.impl; |
| |
| import java.io.IOException; |
| import java.net.InetSocketAddress; |
| import java.net.SocketAddress; |
| import java.nio.channels.ClosedChannelException; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.concurrent.RejectedExecutionException; |
| |
| import org.jboss.netty.channel.Channel; |
| import org.jboss.netty.channel.ChannelHandlerContext; |
| import org.jboss.netty.channel.ChannelStateEvent; |
| import org.jboss.netty.channel.ExceptionEvent; |
| import org.jboss.netty.channel.MessageEvent; |
| import org.jboss.netty.handler.timeout.IdleState; |
| import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler; |
| import org.jboss.netty.handler.timeout.IdleStateEvent; |
| import org.jboss.netty.handler.timeout.IdleStateHandler; |
| import org.jboss.netty.handler.timeout.ReadTimeoutException; |
| import org.onlab.packet.IpAddress; |
| import org.onosproject.pcep.controller.ClientCapability; |
| import org.onosproject.pcep.controller.PccId; |
| import org.onosproject.pcep.controller.driver.PcepClientDriver; |
| import org.onosproject.pcepio.exceptions.PcepParseException; |
| import org.onosproject.pcepio.protocol.PcepError; |
| import org.onosproject.pcepio.protocol.PcepErrorInfo; |
| import org.onosproject.pcepio.protocol.PcepErrorMsg; |
| import org.onosproject.pcepio.protocol.PcepErrorObject; |
| import org.onosproject.pcepio.protocol.PcepFactory; |
| import org.onosproject.pcepio.protocol.PcepMessage; |
| import org.onosproject.pcepio.protocol.PcepOpenMsg; |
| import org.onosproject.pcepio.protocol.PcepOpenObject; |
| import org.onosproject.pcepio.protocol.PcepType; |
| import org.onosproject.pcepio.protocol.PcepVersion; |
| import org.onosproject.pcepio.types.PceccCapabilityTlv; |
| import org.onosproject.pcepio.types.StatefulPceCapabilityTlv; |
| import org.onosproject.pcepio.types.PcepErrorDetailInfo; |
| import org.onosproject.pcepio.types.PcepValueType; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * Channel handler deals with the pcc client connection and dispatches |
| * messages from client to the appropriate locations. |
| */ |
| class PcepChannelHandler extends IdleStateAwareChannelHandler { |
| static final byte DEADTIMER_MAXIMUM_VALUE = (byte) 0xFF; |
| static final byte KEEPALIVE_MULTIPLE_FOR_DEADTIMER = 4; |
| private static final Logger log = LoggerFactory.getLogger(PcepChannelHandler.class); |
| private final Controller controller; |
| private PcepClientDriver pc; |
| private PccId thispccId; |
| private Channel channel; |
| private byte sessionId = 0; |
| private byte keepAliveTime; |
| private byte deadTime; |
| private ClientCapability capability; |
| private PcepPacketStatsImpl pcepPacketStats; |
| static final int MAX_WRONG_COUNT_PACKET = 5; |
| static final int BYTE_MASK = 0xFF; |
| |
| // State needs to be volatile because the HandshakeTimeoutHandler |
| // needs to check if the handshake is complete |
| private volatile ChannelState state; |
| |
| // When a pcc client with a ip addresss is found (i.e we already have a |
| // connected client with the same ip), the new client is immediately |
| // disconnected. At that point netty callsback channelDisconnected() which |
| // proceeds to cleaup client state - we need to ensure that it does not cleanup |
| // client state for the older (still connected) client |
| private volatile Boolean duplicatePccIdFound; |
| |
| //Indicates the pcep version used by this pcc client |
| protected PcepVersion pcepVersion; |
| protected PcepFactory factory1; |
| |
| /** |
| * Create a new unconnected PcepChannelHandler. |
| * @param controller parent controller |
| */ |
| PcepChannelHandler(Controller controller) { |
| this.controller = controller; |
| this.state = ChannelState.INIT; |
| factory1 = controller.getPcepMessageFactory1(); |
| duplicatePccIdFound = Boolean.FALSE; |
| pcepPacketStats = new PcepPacketStatsImpl(); |
| } |
| |
| /** |
| * To disconnect a PCC. |
| */ |
| public void disconnectClient() { |
| pc.disconnectClient(); |
| } |
| |
| //************************* |
| // Channel State Machine |
| //************************* |
| |
| /** |
| * The state machine for handling the client/channel state. All state |
| * transitions should happen from within the state machine (and not from other |
| * parts of the code) |
| */ |
| enum ChannelState { |
| /** |
| * Initial state before channel is connected. |
| */ |
| INIT(false) { |
| |
| }, |
| /** |
| * Once the session is established, wait for open message. |
| */ |
| OPENWAIT(false) { |
| @Override |
| void processPcepMessage(PcepChannelHandler h, PcepMessage m) throws IOException, PcepParseException { |
| |
| log.debug("Message received in OPEN WAIT State"); |
| |
| //check for open message |
| if (m.getType() != PcepType.OPEN) { |
| // When the message type is not open message increment the wrong packet statistics |
| h.processUnknownMsg(); |
| log.debug("message is not OPEN message"); |
| } else { |
| |
| h.pcepPacketStats.addInPacket(); |
| PcepOpenMsg pOpenmsg = (PcepOpenMsg) m; |
| //Do Capability negotiation. |
| h.capabilityNegotiation(pOpenmsg); |
| log.debug("Sending handshake OPEN message"); |
| h.sessionId = pOpenmsg.getPcepOpenObject().getSessionId(); |
| h.pcepVersion = pOpenmsg.getPcepOpenObject().getVersion(); |
| |
| //setting keepalive and deadTimer |
| byte yKeepalive = pOpenmsg.getPcepOpenObject().getKeepAliveTime(); |
| byte yDeadTimer = pOpenmsg.getPcepOpenObject().getDeadTime(); |
| h.keepAliveTime = yKeepalive; |
| if (yKeepalive < yDeadTimer) { |
| h.deadTime = yDeadTimer; |
| } else { |
| if (DEADTIMER_MAXIMUM_VALUE > (yKeepalive * KEEPALIVE_MULTIPLE_FOR_DEADTIMER)) { |
| h.deadTime = (byte) (yKeepalive * KEEPALIVE_MULTIPLE_FOR_DEADTIMER); |
| } else { |
| h.deadTime = DEADTIMER_MAXIMUM_VALUE; |
| } |
| } |
| h.sendHandshakeOpenMessage(); |
| h.pcepPacketStats.addOutPacket(); |
| h.setState(KEEPWAIT); |
| } |
| } |
| }, |
| /** |
| * Once the open messages are exchanged, wait for keep alive message. |
| */ |
| KEEPWAIT(false) { |
| @Override |
| void processPcepMessage(PcepChannelHandler h, PcepMessage m) throws IOException, PcepParseException { |
| log.debug("message received in KEEPWAIT state"); |
| //check for keep alive message |
| if (m.getType() != PcepType.KEEP_ALIVE) { |
| // When the message type is not keep alive message increment the wrong packet statistics |
| h.processUnknownMsg(); |
| log.debug("message is not KEEPALIVE message"); |
| } else { |
| // Set the client connected status |
| h.pcepPacketStats.addInPacket(); |
| final SocketAddress address = h.channel.getRemoteAddress(); |
| if (!(address instanceof InetSocketAddress)) { |
| throw new IOException("Invalid client connection. Pcc is indentifed based on IP"); |
| } |
| log.debug("sending keep alive message in KEEPWAIT state"); |
| |
| final InetSocketAddress inetAddress = (InetSocketAddress) address; |
| h.thispccId = PccId.pccId(IpAddress.valueOf(inetAddress.getAddress())); |
| h.pc = h.controller.getPcepClientInstance(h.thispccId, h.sessionId, h.pcepVersion, |
| h.pcepPacketStats); |
| //Get pc instance and set capabilities |
| h.pc.setCapability(h.capability); |
| // set the status of pcc as connected |
| h.pc.setConnected(true); |
| h.pc.setChannel(h.channel); |
| |
| // set any other specific parameters to the pcc |
| h.pc.setPcVersion(h.pcepVersion); |
| h.pc.setPcSessionId(h.sessionId); |
| h.pc.setPcKeepAliveTime(h.keepAliveTime); |
| h.pc.setPcDeadTime(h.deadTime); |
| int keepAliveTimer = h.keepAliveTime & BYTE_MASK; |
| int deadTimer = h.deadTime & BYTE_MASK; |
| if (0 == h.keepAliveTime) { |
| h.deadTime = 0; |
| } |
| // handle keep alive and dead time |
| if (keepAliveTimer != PcepPipelineFactory.DEFAULT_KEEP_ALIVE_TIME |
| || deadTimer != PcepPipelineFactory.DEFAULT_DEAD_TIME) { |
| |
| h.channel.getPipeline().replace("idle", "idle", |
| new IdleStateHandler(PcepPipelineFactory.TIMER, deadTimer, keepAliveTimer, 0)); |
| } |
| log.debug("Dead timer : " + deadTimer); |
| log.debug("Keep alive time : " + keepAliveTimer); |
| |
| //set the state handshake completion. |
| h.sendKeepAliveMessage(); |
| h.pcepPacketStats.addOutPacket(); |
| h.setHandshakeComplete(true); |
| |
| if (!h.pc.connectClient()) { |
| disconnectDuplicate(h); |
| } else { |
| h.setState(ESTABLISHED); |
| } |
| } |
| } |
| }, |
| /** |
| * Once the keep alive messages are exchanged, the state is established. |
| */ |
| ESTABLISHED(true) { |
| @Override |
| void processPcepMessage(PcepChannelHandler h, PcepMessage m) throws IOException, PcepParseException { |
| |
| //h.channel.getPipeline().remove("waittimeout"); |
| log.debug("Message received in established state " + m.getType()); |
| //dispatch the message |
| h.dispatchMessage(m); |
| } |
| }; |
| private boolean handshakeComplete; |
| |
| ChannelState(boolean handshakeComplete) { |
| this.handshakeComplete = handshakeComplete; |
| } |
| |
| void processPcepMessage(PcepChannelHandler h, PcepMessage m) throws IOException, PcepParseException { |
| // do nothing |
| } |
| |
| /** |
| * Is this a state in which the handshake has completed. |
| * |
| * @return true if the handshake is complete |
| */ |
| public boolean isHandshakeComplete() { |
| return this.handshakeComplete; |
| } |
| |
| protected void disconnectDuplicate(PcepChannelHandler h) { |
| log.error("Duplicated Pcc IP or incompleted cleanup - " + "disconnecting channel {}", |
| h.getClientInfoString()); |
| h.duplicatePccIdFound = Boolean.TRUE; |
| h.channel.disconnect(); |
| } |
| |
| /** |
| * Sets handshake complete status. |
| * |
| * @param handshakeComplete status of handshake |
| */ |
| public void setHandshakeComplete(boolean handshakeComplete) { |
| this.handshakeComplete = handshakeComplete; |
| } |
| |
| } |
| |
| @Override |
| public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { |
| channel = e.getChannel(); |
| log.info("PCC connected from {}", channel.getRemoteAddress()); |
| |
| // Wait for open message from pcc client |
| setState(ChannelState.OPENWAIT); |
| } |
| |
| @Override |
| public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { |
| log.info("Pcc disconnected callback for pc:{}. Cleaning up ...", getClientInfoString()); |
| if (thispccId != null) { |
| if (!duplicatePccIdFound) { |
| // if the disconnected client (on this ChannelHandler) |
| // was not one with a duplicate-dpid, it is safe to remove all |
| // state for it at the controller. Notice that if the disconnected |
| // client was a duplicate-ip, calling the method below would clear |
| // all state for the original client (with the same ip), |
| // which we obviously don't want. |
| log.debug("{}:removal called", getClientInfoString()); |
| if (pc != null) { |
| pc.removeConnectedClient(); |
| } |
| } else { |
| // A duplicate was disconnected on this ChannelHandler, |
| // this is the same client reconnecting, but the original state was |
| // not cleaned up - XXX check liveness of original ChannelHandler |
| log.debug("{}:duplicate found", getClientInfoString()); |
| duplicatePccIdFound = Boolean.FALSE; |
| } |
| } else { |
| log.warn("no pccip in channelHandler registered for " + "disconnected client {}", getClientInfoString()); |
| } |
| } |
| |
| @Override |
| public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { |
| PcepErrorMsg errMsg; |
| log.info("exceptionCaught: " + e.toString()); |
| |
| if (e.getCause() instanceof ReadTimeoutException) { |
| if (ChannelState.OPENWAIT == state) { |
| // When ReadTimeout timer is expired in OPENWAIT state, it is considered |
| // OpenWait timer. |
| errMsg = getErrorMsg(PcepErrorDetailInfo.ERROR_TYPE_1, PcepErrorDetailInfo.ERROR_VALUE_2); |
| log.debug("Sending PCEP-ERROR message to PCC."); |
| channel.write(Collections.singletonList(errMsg)); |
| channel.close(); |
| state = ChannelState.INIT; |
| return; |
| } else if (ChannelState.KEEPWAIT == state) { |
| // When ReadTimeout timer is expired in KEEPWAIT state, is is considered |
| // KeepWait timer. |
| errMsg = getErrorMsg(PcepErrorDetailInfo.ERROR_TYPE_1, PcepErrorDetailInfo.ERROR_VALUE_7); |
| log.debug("Sending PCEP-ERROR message to PCC."); |
| channel.write(Collections.singletonList(errMsg)); |
| channel.close(); |
| state = ChannelState.INIT; |
| return; |
| } |
| } else if (e.getCause() instanceof ClosedChannelException) { |
| log.debug("Channel for pc {} already closed", getClientInfoString()); |
| } else if (e.getCause() instanceof IOException) { |
| log.error("Disconnecting client {} due to IO Error: {}", getClientInfoString(), e.getCause().getMessage()); |
| if (log.isDebugEnabled()) { |
| // still print stack trace if debug is enabled |
| log.debug("StackTrace for previous Exception: ", e.getCause()); |
| } |
| channel.close(); |
| } else if (e.getCause() instanceof PcepParseException) { |
| PcepParseException errMsgParse = (PcepParseException) e.getCause(); |
| byte errorType = errMsgParse.getErrorType(); |
| byte errorValue = errMsgParse.getErrorValue(); |
| |
| if ((errorType == (byte) 0x0) && (errorValue == (byte) 0x0)) { |
| processUnknownMsg(); |
| } else { |
| errMsg = getErrorMsg(errorType, errorValue); |
| log.debug("Sending PCEP-ERROR message to PCC."); |
| channel.write(Collections.singletonList(errMsg)); |
| } |
| } else if (e.getCause() instanceof RejectedExecutionException) { |
| log.warn("Could not process message: queue full"); |
| } else { |
| log.error("Error while processing message from client " + getClientInfoString() + "state " + this.state); |
| channel.close(); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return getClientInfoString(); |
| } |
| |
| @Override |
| public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) throws Exception { |
| if (!isHandshakeComplete()) { |
| return; |
| } |
| |
| if (e.getState() == IdleState.READER_IDLE) { |
| // When no message is received on channel for read timeout, then close |
| // the channel |
| log.info("Disconnecting client {} due to read timeout", getClientInfoString()); |
| ctx.getChannel().close(); |
| } else if (e.getState() == IdleState.WRITER_IDLE) { |
| // Send keep alive message |
| log.debug("Sending keep alive message due to IdleState timeout " + pc.toString()); |
| pc.sendMessage(Collections.singletonList(pc.factory().buildKeepaliveMsg().build())); |
| } |
| } |
| |
| @Override |
| public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { |
| if (e.getMessage() instanceof List) { |
| @SuppressWarnings("unchecked") |
| List<PcepMessage> msglist = (List<PcepMessage>) e.getMessage(); |
| for (PcepMessage pm : msglist) { |
| // Do the actual packet processing |
| state.processPcepMessage(this, pm); |
| } |
| } else { |
| state.processPcepMessage(this, (PcepMessage) e.getMessage()); |
| } |
| } |
| |
| /** |
| * To set the handshake status. |
| * |
| * @param handshakeComplete value is handshake status |
| */ |
| public void setHandshakeComplete(boolean handshakeComplete) { |
| this.state.setHandshakeComplete(handshakeComplete); |
| } |
| |
| /** |
| * Is this a state in which the handshake has completed. |
| * |
| * @return true if the handshake is complete |
| */ |
| public boolean isHandshakeComplete() { |
| return this.state.isHandshakeComplete(); |
| } |
| |
| /** |
| * To handle the pcep message. |
| * |
| * @param m pcep message |
| */ |
| private void dispatchMessage(PcepMessage m) { |
| pc.handleMessage(m); |
| } |
| |
| /** |
| * Return a string describing this client based on the already available |
| * information (ip address and/or remote socket). |
| * |
| * @return display string |
| */ |
| private String getClientInfoString() { |
| if (pc != null) { |
| return pc.toString(); |
| } |
| String channelString; |
| if (channel == null || channel.getRemoteAddress() == null) { |
| channelString = "?"; |
| } else { |
| channelString = channel.getRemoteAddress().toString(); |
| } |
| String pccIpString; |
| // TODO : implement functionality to get pcc id string |
| pccIpString = "?"; |
| return String.format("[%s PCCIP[%s]]", channelString, pccIpString); |
| } |
| |
| /** |
| * Update the channels state. Only called from the state machine. |
| * |
| * @param state |
| */ |
| private void setState(ChannelState state) { |
| this.state = state; |
| } |
| |
| /** |
| * Send handshake open message. |
| * |
| * @throws IOException,PcepParseException |
| */ |
| private void sendHandshakeOpenMessage() throws IOException, PcepParseException { |
| PcepOpenObject pcepOpenobj = factory1.buildOpenObject() |
| .setSessionId(sessionId) |
| .setKeepAliveTime(keepAliveTime) |
| .setDeadTime(deadTime) |
| .build(); |
| PcepMessage msg = factory1.buildOpenMsg() |
| .setPcepOpenObj(pcepOpenobj) |
| .build(); |
| log.debug("Sending OPEN message to {}", channel.getRemoteAddress()); |
| channel.write(Collections.singletonList(msg)); |
| } |
| |
| //Capability negotiation |
| private void capabilityNegotiation(PcepOpenMsg pOpenmsg) { |
| LinkedList<PcepValueType> tlvList = pOpenmsg.getPcepOpenObject().getOptionalTlv(); |
| boolean pceccCapability = false; |
| boolean statefulPceCapability = false; |
| boolean pcInstantiationCapability = false; |
| |
| ListIterator<PcepValueType> listIterator = tlvList.listIterator(); |
| while (listIterator.hasNext()) { |
| PcepValueType tlv = listIterator.next(); |
| |
| switch (tlv.getType()) { |
| case PceccCapabilityTlv.TYPE: |
| pceccCapability = true; |
| break; |
| case StatefulPceCapabilityTlv.TYPE: |
| statefulPceCapability = true; |
| StatefulPceCapabilityTlv stetefulPcCapTlv = (StatefulPceCapabilityTlv) tlv; |
| if (stetefulPcCapTlv.getIFlag()) { |
| pcInstantiationCapability = true; |
| } |
| break; |
| default: |
| continue; |
| } |
| } |
| this.capability = new ClientCapability(pceccCapability, statefulPceCapability, pcInstantiationCapability); |
| } |
| |
| /** |
| * Send keep alive message. |
| * |
| * @throws IOException when channel is disconnected |
| * @throws PcepParseException while building keep alive message |
| */ |
| private void sendKeepAliveMessage() throws IOException, PcepParseException { |
| PcepMessage msg = factory1.buildKeepaliveMsg().build(); |
| log.debug("Sending KEEPALIVE message to {}", channel.getRemoteAddress()); |
| channel.write(Collections.singletonList(msg)); |
| } |
| |
| /** |
| * Send error message and close channel with pcc. |
| */ |
| private void sendErrMsgAndCloseChannel() { |
| // TODO send error message |
| channel.close(); |
| } |
| |
| /** |
| * Send error message when an invalid message is received. |
| * |
| * @throws PcepParseException while building error message |
| */ |
| private void sendErrMsgForInvalidMsg() throws PcepParseException { |
| byte errorType = 0x02; |
| byte errorValue = 0x00; |
| PcepErrorMsg errMsg = getErrorMsg(errorType, errorValue); |
| channel.write(Collections.singletonList(errMsg)); |
| } |
| |
| /** |
| * Builds pcep error message based on error value and error type. |
| * |
| * @param errorType pcep error type |
| * @param errorValue pcep error value |
| * @return pcep error message |
| * @throws PcepParseException while bulding error message |
| */ |
| public PcepErrorMsg getErrorMsg(byte errorType, byte errorValue) throws PcepParseException { |
| LinkedList<PcepErrorObject> llerrObj = new LinkedList<>(); |
| PcepErrorMsg errMsg; |
| |
| PcepErrorObject errObj = factory1.buildPcepErrorObject() |
| .setErrorValue(errorValue) |
| .setErrorType(errorType) |
| .build(); |
| |
| llerrObj.add(errObj); |
| |
| //If Error caught in other than Openmessage |
| LinkedList<PcepError> llPcepErr = new LinkedList<>(); |
| |
| PcepError pcepErr = factory1.buildPcepError() |
| .setErrorObjList(llerrObj) |
| .build(); |
| |
| llPcepErr.add(pcepErr); |
| |
| PcepErrorInfo errInfo = factory1.buildPcepErrorInfo() |
| .setPcepErrorList(llPcepErr) |
| .build(); |
| |
| errMsg = factory1.buildPcepErrorMsg() |
| .setPcepErrorInfo(errInfo) |
| .build(); |
| return errMsg; |
| } |
| |
| /** |
| * Process unknown pcep message received. |
| * |
| * @throws PcepParseException while building pcep error message |
| */ |
| public void processUnknownMsg() throws PcepParseException { |
| Date now = null; |
| if (pcepPacketStats.wrongPacketCount() == 0) { |
| now = new Date(); |
| pcepPacketStats.setTime(now.getTime()); |
| pcepPacketStats.addWrongPacket(); |
| sendErrMsgForInvalidMsg(); |
| } |
| |
| if (pcepPacketStats.wrongPacketCount() > 1) { |
| Date lastest = new Date(); |
| pcepPacketStats.addWrongPacket(); |
| //converting to seconds |
| if (((lastest.getTime() - pcepPacketStats.getTime()) / 1000) > 60) { |
| now = lastest; |
| pcepPacketStats.setTime(now.getTime()); |
| pcepPacketStats.resetWrongPacket(); |
| pcepPacketStats.addWrongPacket(); |
| } else if (((int) (lastest.getTime() - now.getTime()) / 1000) < 60) { |
| if (MAX_WRONG_COUNT_PACKET <= pcepPacketStats.wrongPacketCount()) { |
| //reset once wrong packet count reaches MAX_WRONG_COUNT_PACKET |
| pcepPacketStats.resetWrongPacket(); |
| // max wrong packets received send error message and close the session |
| sendErrMsgAndCloseChannel(); |
| } |
| } |
| } |
| } |
| } |