| /* |
| * Copyright 2017-present Open Networking Foundation |
| * |
| * 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.ofagent.impl; |
| |
| import io.netty.channel.Channel; |
| import io.netty.channel.ChannelDuplexHandler; |
| import io.netty.channel.ChannelHandlerContext; |
| import io.netty.handler.timeout.ReadTimeoutException; |
| import org.onosproject.ofagent.api.OFSwitch; |
| import org.projectfloodlight.openflow.protocol.OFErrorMsg; |
| import org.projectfloodlight.openflow.protocol.OFMessage; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.IOException; |
| import java.nio.channels.ClosedChannelException; |
| import java.util.concurrent.RejectedExecutionException; |
| |
| import static org.onosproject.ofagent.impl.OFChannelHandler.ChannelState.INIT; |
| |
| /** |
| * Implementation of OpenFlow channel handler. |
| * It processes OpenFlow message according to the channel state. |
| */ |
| public final class OFChannelHandler extends ChannelDuplexHandler { |
| |
| private static final String MSG_CHANNEL_STATE = "Set channel(%s) state: %s"; |
| |
| private final Logger log = LoggerFactory.getLogger(getClass()); |
| private final OFSwitch ofSwitch; |
| |
| private Channel channel; |
| private ChannelState state; |
| |
| enum ChannelState { |
| |
| INIT { |
| @Override |
| void processOFMessage(final OFChannelHandler handler, |
| final OFMessage msg) { |
| logProcessOFMessageDetails(handler, msg, this); |
| // TODO implement |
| } |
| }, |
| WAIT_HELLO { |
| @Override |
| void processOFMessage(final OFChannelHandler handler, |
| final OFMessage msg) { |
| logProcessOFMessageDetails(handler, msg, this); |
| switch (msg.getType()) { |
| case HELLO: |
| handler.setState(ChannelState.WAIT_FEATURE_REQUEST); |
| break; |
| default: |
| handler.illegalMessageReceived(msg); |
| break; |
| } |
| } |
| }, |
| WAIT_FEATURE_REQUEST { |
| @Override |
| void processOFMessage(final OFChannelHandler handler, |
| final OFMessage msg) { |
| logProcessOFMessageDetails(handler, msg, this); |
| switch (msg.getType()) { |
| case FEATURES_REQUEST: |
| handler.ofSwitch.processFeaturesRequest(handler.channel, msg); |
| handler.setState(ChannelState.ESTABLISHED); |
| break; |
| case ECHO_REQUEST: |
| handler.ofSwitch.processEchoRequest(handler.channel, msg); |
| break; |
| case ERROR: |
| handler.logErrorClose((OFErrorMsg) msg); |
| break; |
| default: |
| handler.illegalMessageReceived(msg); |
| break; |
| |
| } |
| } |
| }, |
| ESTABLISHED { |
| @Override |
| void processOFMessage(final OFChannelHandler handler, |
| final OFMessage msg) { |
| logProcessOFMessageDetails(handler, msg, this); |
| // TODO implement |
| // TODO add this channel to ofSwitch role service |
| switch (msg.getType()) { |
| case STATS_REQUEST: |
| //TODO implement |
| //TODO: use vNetService to build OFPortDesc. |
| handler.ofSwitch.processStatsRequest(handler.channel, msg); |
| break; |
| case SET_CONFIG: |
| //TODO implement |
| handler.ofSwitch.processSetConfigMessage(handler.channel, msg); |
| break; |
| case GET_CONFIG_REQUEST: |
| //TODO implement |
| handler.ofSwitch.processGetConfigRequest(handler.channel, msg); |
| break; |
| case BARRIER_REQUEST: |
| //TODO implement |
| handler.ofSwitch.processBarrierRequest(handler.channel, msg); |
| break; |
| case ECHO_REQUEST: |
| handler.ofSwitch.processEchoRequest(handler.channel, msg); |
| break; |
| case ROLE_REQUEST: |
| handler.ofSwitch.processRoleRequest(handler.channel, msg); |
| break; |
| case PACKET_OUT: |
| // TODO: check if this is lldp - ignore if it is not lldp |
| handler.ofSwitch.processLldp(handler.channel, msg); |
| break; |
| case FLOW_MOD: |
| case PORT_MOD: |
| case GROUP_MOD: |
| case METER_MOD: |
| case TABLE_MOD: |
| handler.ofSwitch.processControllerCommand(handler.channel, msg); |
| break; |
| case ERROR: |
| handler.logErrorClose((OFErrorMsg) msg); |
| break; |
| default: |
| handler.unhandledMessageReceived(msg); |
| break; |
| } |
| } |
| }; |
| |
| abstract void processOFMessage(final OFChannelHandler handler, final OFMessage msg); |
| |
| private static void logProcessOFMessageDetails(final OFChannelHandler handler, |
| final OFMessage msg, ChannelState chnState) { |
| handler.log.trace("Channel state: {} dpid: {} processOFMessage type: {} nsg: {}", |
| chnState, handler.ofSwitch.dpid(), msg.getType(), msg); |
| } |
| } |
| |
| /** |
| * Default constructor. |
| * |
| * @param ofSwitch openflow switch that owns this channel |
| */ |
| public OFChannelHandler(OFSwitch ofSwitch) { |
| super(); |
| this.ofSwitch = ofSwitch; |
| setState(INIT); |
| } |
| |
| @Override |
| public void channelActive(ChannelHandlerContext ctx) throws Exception { |
| this.channel = ctx.channel(); |
| // FIXME move this to channel handler and add channel when OF handshake is done |
| ofSwitch.addControllerChannel(channel); |
| try { |
| ofSwitch.sendOfHello(channel); |
| log.trace("Send OF_13 Hello to {}", channel.remoteAddress()); |
| setState(ChannelState.WAIT_HELLO); |
| } catch (Exception ex) { |
| log.error("Failed sending OF_13 Hello to {} for {}", channel.remoteAddress(), ex.getMessage()); |
| } |
| } |
| |
| @Override |
| public void channelInactive(ChannelHandlerContext ctx) { |
| ofSwitch.deleteControllerChannel(channel); |
| log.info("Device {} disconnected from controller {}", ofSwitch.dpid(), channel.remoteAddress()); |
| } |
| |
| @Override |
| public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { |
| try { |
| state.processOFMessage(this, (OFMessage) msg); |
| } catch (Exception ex) { |
| ctx.fireExceptionCaught(ex); |
| } |
| } |
| |
| @Override |
| public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { |
| if (cause instanceof ReadTimeoutException) { |
| log.error("Connection closed because of ReadTimeoutException {}", cause.getMessage()); |
| } else if (cause instanceof ClosedChannelException) { |
| log.error("ClosedChannelException occurred"); |
| return; |
| } else if (cause instanceof RejectedExecutionException) { |
| log.error("Could not process message: queue full"); |
| } else if (cause instanceof IOException) { |
| log.error("IOException occurred"); |
| } else { |
| log.error("Error while processing message from switch {}", cause.getMessage()); |
| } |
| channel.close(); |
| } |
| |
| private void setState(ChannelState state) { |
| this.state = state; |
| if (state != INIT) { |
| log.debug(String.format(MSG_CHANNEL_STATE, channel.remoteAddress(), state.name())); |
| } |
| } |
| |
| private void logErrorClose(OFErrorMsg errorMsg) { |
| log.error("{} from switch {} in state {}", |
| errorMsg, |
| ofSwitch.dpid(), |
| state); |
| channel.close(); |
| } |
| |
| private void illegalMessageReceived(OFMessage ofMessage) { |
| log.warn("Controller should never send message {} to switch {} in current state {}", |
| ofMessage.getType(), ofSwitch.dpid(), state); |
| } |
| |
| private void unhandledMessageReceived(OFMessage ofMessage) { |
| log.warn("Unexpected message {} received for switch {} in state {}", |
| ofMessage.getType(), ofSwitch.dpid(), state); |
| } |
| } |