blob: 5147f6c315c6f879ca4b9b8ed7fa96d8df02d5bc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
//CHECKSTYLE:OFF
package org.onlab.onos.openflow.controller.impl;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
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.IdleStateAwareChannelHandler;
import org.jboss.netty.handler.timeout.IdleStateEvent;
import org.jboss.netty.handler.timeout.ReadTimeoutException;
import org.onlab.onos.openflow.controller.driver.OpenFlowSwitchDriver;
import org.onlab.onos.openflow.controller.driver.SwitchStateException;
import org.projectfloodlight.openflow.exceptions.OFParseError;
import org.projectfloodlight.openflow.protocol.OFAsyncGetReply;
import org.projectfloodlight.openflow.protocol.OFBadRequestCode;
import org.projectfloodlight.openflow.protocol.OFBarrierReply;
import org.projectfloodlight.openflow.protocol.OFBarrierRequest;
import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
import org.projectfloodlight.openflow.protocol.OFDescStatsRequest;
import org.projectfloodlight.openflow.protocol.OFEchoReply;
import org.projectfloodlight.openflow.protocol.OFEchoRequest;
import org.projectfloodlight.openflow.protocol.OFErrorMsg;
import org.projectfloodlight.openflow.protocol.OFErrorType;
import org.projectfloodlight.openflow.protocol.OFExperimenter;
import org.projectfloodlight.openflow.protocol.OFFactory;
import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
import org.projectfloodlight.openflow.protocol.OFFlowModFailedCode;
import org.projectfloodlight.openflow.protocol.OFFlowRemoved;
import org.projectfloodlight.openflow.protocol.OFGetConfigReply;
import org.projectfloodlight.openflow.protocol.OFGetConfigRequest;
import org.projectfloodlight.openflow.protocol.OFHello;
import org.projectfloodlight.openflow.protocol.OFHelloElem;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply;
import org.projectfloodlight.openflow.protocol.OFPortDescStatsRequest;
import org.projectfloodlight.openflow.protocol.OFPortStatus;
import org.projectfloodlight.openflow.protocol.OFQueueGetConfigReply;
import org.projectfloodlight.openflow.protocol.OFRoleReply;
import org.projectfloodlight.openflow.protocol.OFSetConfig;
import org.projectfloodlight.openflow.protocol.OFStatsReply;
import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags;
import org.projectfloodlight.openflow.protocol.OFStatsType;
import org.projectfloodlight.openflow.protocol.OFType;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
import org.projectfloodlight.openflow.protocol.errormsg.OFFlowModFailedErrorMsg;
import org.projectfloodlight.openflow.types.U32;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Channel handler deals with the switch connection and dispatches
* switch messages to the appropriate locations.
*/
class OFChannelHandler extends IdleStateAwareChannelHandler {
private static final Logger log = LoggerFactory.getLogger(OFChannelHandler.class);
private final Controller controller;
private OpenFlowSwitchDriver sw;
private long thisdpid; // channelHandler cached value of connected switch id
private Channel channel;
// State needs to be volatile because the HandshakeTimeoutHandler
// needs to check if the handshake is complete
private volatile ChannelState state;
// When a switch with a duplicate dpid is found (i.e we already have a
// connected switch with the same dpid), the new switch is immediately
// disconnected. At that point netty callsback channelDisconnected() which
// proceeds to cleaup switch state - we need to ensure that it does not cleanup
// switch state for the older (still connected) switch
private volatile Boolean duplicateDpidFound;
// Temporary storage for switch-features and port-description
private OFFeaturesReply featuresReply;
private OFPortDescStatsReply portDescReply;
// a concurrent ArrayList to temporarily store port status messages
// before we are ready to deal with them
private final CopyOnWriteArrayList<OFPortStatus> pendingPortStatusMsg;
//Indicates the openflow version used by this switch
protected OFVersion ofVersion;
protected OFFactory factory13;
protected OFFactory factory10;
/** transaction Ids to use during handshake. Since only one thread
* calls into an OFChannelHandler instance, we don't need atomic.
* We will count down
*/
private int handshakeTransactionIds = -1;
/**
* Create a new unconnected OFChannelHandler.
* @param controller
*/
OFChannelHandler(Controller controller) {
this.controller = controller;
this.state = ChannelState.INIT;
this.pendingPortStatusMsg = new CopyOnWriteArrayList<OFPortStatus>();
factory13 = controller.getOFMessageFactory13();
factory10 = controller.getOFMessageFactory10();
duplicateDpidFound = Boolean.FALSE;
}
// XXX S consider if necessary
public void disconnectSwitch() {
sw.disconnectSwitch();
}
//*************************
// Channel State Machine
//*************************
/**
* The state machine for handling the switch/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) {
@Override
void processOFMessage(OFChannelHandler h, OFMessage m)
throws IOException, SwitchStateException {
illegalMessageReceived(h, m);
}
@Override
void processOFError(OFChannelHandler h, OFErrorMsg m)
throws IOException {
// need to implement since its abstract but it will never
// be called
}
@Override
void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
throws IOException {
unhandledMessageReceived(h, m);
}
},
/**
* We send a OF 1.3 HELLO to the switch and wait for a Hello from the switch.
* Once we receive the reply, we decide on OF 1.3 or 1.0 switch - no other
* protocol version is accepted.
* We send an OFFeaturesRequest depending on the protocol version selected
* Next state is WAIT_FEATURES_REPLY
*/
WAIT_HELLO(false) {
@Override
void processOFHello(OFChannelHandler h, OFHello m)
throws IOException {
// TODO We could check for the optional bitmap, but for now
// we are just checking the version number.
if (m.getVersion() == OFVersion.OF_13) {
log.debug("Received {} Hello from {}", m.getVersion(),
h.channel.getRemoteAddress());
h.ofVersion = OFVersion.OF_13;
} else if (m.getVersion() == OFVersion.OF_10) {
log.debug("Received {} Hello from {} - switching to OF "
+ "version 1.0", m.getVersion(),
h.channel.getRemoteAddress());
h.ofVersion = OFVersion.OF_10;
} else {
log.error("Received Hello of version {} from switch at {}. "
+ "This controller works with OF1.0 and OF1.3 "
+ "switches. Disconnecting switch ...",
m.getVersion(), h.channel.getRemoteAddress());
h.channel.disconnect();
return;
}
h.sendHandshakeFeaturesRequestMessage();
h.setState(WAIT_FEATURES_REPLY);
}
@Override
void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
throws IOException, SwitchStateException {
illegalMessageReceived(h, m);
}
@Override
void processOFStatisticsReply(OFChannelHandler h,
OFStatsReply m)
throws IOException, SwitchStateException {
illegalMessageReceived(h, m);
}
@Override
void processOFError(OFChannelHandler h, OFErrorMsg m) {
logErrorDisconnect(h, m);
}
@Override
void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
throws IOException {
unhandledMessageReceived(h, m);
}
},
/**
* We are waiting for a features reply message. Once we receive it, the
* behavior depends on whether this is a 1.0 or 1.3 switch. For 1.0,
* we send a SetConfig request, barrier, and GetConfig request and the
* next state is WAIT_CONFIG_REPLY. For 1.3, we send a Port description
* request and the next state is WAIT_PORT_DESC_REPLY.
*/
WAIT_FEATURES_REPLY(false) {
@Override
void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
throws IOException {
h.thisdpid = m.getDatapathId().getLong();
log.debug("Received features reply for switch at {} with dpid {}",
h.getSwitchInfoString(), h.thisdpid);
h.featuresReply = m; //temp store
if (h.ofVersion == OFVersion.OF_10) {
h.sendHandshakeSetConfig();
h.setState(WAIT_CONFIG_REPLY);
} else {
//version is 1.3, must get switchport information
h.sendHandshakeOFPortDescRequest();
h.setState(WAIT_PORT_DESC_REPLY);
}
}
@Override
void processOFStatisticsReply(OFChannelHandler h,
OFStatsReply m)
throws IOException, SwitchStateException {
illegalMessageReceived(h, m);
}
@Override
void processOFError(OFChannelHandler h, OFErrorMsg m) {
logErrorDisconnect(h, m);
}
@Override
void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
throws IOException {
unhandledMessageReceived(h, m);
}
},
/**
* We are waiting for a description of the 1.3 switch ports.
* Once received, we send a SetConfig request
* Next State is WAIT_CONFIG_REPLY
*/
WAIT_PORT_DESC_REPLY(false) {
@Override
void processOFStatisticsReply(OFChannelHandler h, OFStatsReply m)
throws SwitchStateException {
// Read port description
if (m.getStatsType() != OFStatsType.PORT_DESC) {
log.warn("Expecting port description stats but received stats "
+ "type {} from {}. Ignoring ...", m.getStatsType(),
h.channel.getRemoteAddress());
return;
}
if (m.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
log.warn("Stats reply indicates more stats from sw {} for "
+ "port description - not currently handled",
h.getSwitchInfoString());
}
h.portDescReply = (OFPortDescStatsReply) m; // temp store
log.info("Received port desc reply for switch at {}",
h.getSwitchInfoString());
try {
h.sendHandshakeSetConfig();
} catch (IOException e) {
log.error("Unable to send setConfig after PortDescReply. "
+ "Error: {}", e.getMessage());
}
h.setState(WAIT_CONFIG_REPLY);
}
@Override
void processOFError(OFChannelHandler h, OFErrorMsg m)
throws IOException, SwitchStateException {
logErrorDisconnect(h, m);
}
@Override
void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
throws IOException, SwitchStateException {
unhandledMessageReceived(h, m);
}
},
/**
* We are waiting for a config reply message. Once we receive it
* we send a DescriptionStatsRequest to the switch.
* Next state: WAIT_DESCRIPTION_STAT_REPLY
*/
WAIT_CONFIG_REPLY(false) {
@Override
void processOFGetConfigReply(OFChannelHandler h, OFGetConfigReply m)
throws IOException {
if (m.getMissSendLen() == 0xffff) {
log.trace("Config Reply from switch {} confirms "
+ "miss length set to 0xffff",
h.getSwitchInfoString());
} else {
// FIXME: we can't really deal with switches that don't send
// full packets. Shouldn't we drop the connection here?
log.warn("Config Reply from switch {} has"
+ "miss length set to {}",
h.getSwitchInfoString(),
m.getMissSendLen());
}
h.sendHandshakeDescriptionStatsRequest();
h.setState(WAIT_DESCRIPTION_STAT_REPLY);
}
@Override
void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m) {
// do nothing;
}
@Override
void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
throws IOException, SwitchStateException {
illegalMessageReceived(h, m);
}
@Override
void processOFStatisticsReply(OFChannelHandler h,
OFStatsReply m)
throws IOException, SwitchStateException {
log.error("Received multipart(stats) message sub-type {}",
m.getStatsType());
illegalMessageReceived(h, m);
}
@Override
void processOFError(OFChannelHandler h, OFErrorMsg m) {
logErrorDisconnect(h, m);
}
@Override
void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
throws IOException {
h.pendingPortStatusMsg.add(m);
}
},
/**
* We are waiting for a OFDescriptionStat message from the switch.
* Once we receive any stat message we try to parse it. If it's not
* a description stats message we disconnect. If its the expected
* description stats message, we:
* - use the switch driver to bind the switch and get an IOFSwitch instance
* - setup the IOFSwitch instance
* - add switch controller and send the initial role
* request to the switch.
* Next state: WAIT_INITIAL_ROLE
* In the typical case, where switches support role request messages
* the next state is where we expect the role reply message.
* In the special case that where the switch does not support any kind
* of role request messages, we don't send a role message, but we do
* request mastership from the registry service. This controller
* should become master once we hear back from the registry service.
* All following states will have a h.sw instance!
*/
WAIT_DESCRIPTION_STAT_REPLY(false) {
@Override
void processOFStatisticsReply(OFChannelHandler h, OFStatsReply m)
throws SwitchStateException {
// Read description, if it has been updated
if (m.getStatsType() != OFStatsType.DESC) {
log.warn("Expecting Description stats but received stats "
+ "type {} from {}. Ignoring ...", m.getStatsType(),
h.channel.getRemoteAddress());
return;
}
log.info("Received switch description reply from switch at {}",
h.channel.getRemoteAddress());
OFDescStatsReply drep = (OFDescStatsReply) m;
// Here is where we differentiate between different kinds of switches
h.sw = h.controller.getOFSwitchInstance(h.thisdpid, drep, h.ofVersion);
h.sw.setOFVersion(h.ofVersion);
h.sw.setFeaturesReply(h.featuresReply);
h.sw.setPortDescReply(h.portDescReply);
h.sw.setConnected(true);
h.sw.setChannel(h.channel);
// boolean success = h.sw.connectSwitch();
//
// if (!success) {
// disconnectDuplicate(h);
// return;
// }
// set switch information
log.debug("Switch {} bound to class {}, description {}",
new Object[] {h.sw, h.sw.getClass(), drep });
//Put switch in EQUAL mode until we hear back from the global registry
//log.debug("Setting new switch {} to EQUAL and sending Role request",
// h.sw.getStringId());
//h.sw.activateEqualSwitch();
//h.setSwitchRole(RoleState.EQUAL);
h.sw.startDriverHandshake();
if (h.sw.isDriverHandshakeComplete()) {
if (!h.sw.connectSwitch()) {
disconnectDuplicate(h);
}
h.setState(ACTIVE);
} else {
h.setState(WAIT_SWITCH_DRIVER_SUB_HANDSHAKE);
}
}
@Override
void processOFError(OFChannelHandler h, OFErrorMsg m) {
logErrorDisconnect(h, m);
}
@Override
void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
throws IOException, SwitchStateException {
illegalMessageReceived(h, m);
}
@Override
void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
throws IOException {
h.pendingPortStatusMsg.add(m);
}
},
/**
* We are waiting for the respective switch driver to complete its
* configuration. Notice that we do not consider this to be part of the main
* switch-controller handshake. But we do consider it as a step that comes
* before we declare the switch as available to the controller.
* Next State: depends on the role of this controller for this switch - either
* MASTER or EQUAL.
*/
WAIT_SWITCH_DRIVER_SUB_HANDSHAKE(true) {
@Override
void processOFError(OFChannelHandler h, OFErrorMsg m)
throws IOException {
// will never be called. We override processOFMessage
}
@Override
void processOFMessage(OFChannelHandler h, OFMessage m)
throws IOException, SwitchStateException {
if (h.sw.isDriverHandshakeComplete()) {
moveToActive(h);
h.state.processOFMessage(h, m);
return;
}
if (m.getType() == OFType.ECHO_REQUEST) {
processOFEchoRequest(h, (OFEchoRequest) m);
} else if (m.getType() == OFType.ECHO_REPLY) {
processOFEchoReply(h, (OFEchoReply) m);
} else if (m.getType() == OFType.ROLE_REPLY) {
h.sw.handleRole(m);
} else if (m.getType() == OFType.ERROR) {
if (!h.sw.handleRoleError((OFErrorMsg)m)) {
h.sw.processDriverHandshakeMessage(m);
if (h.sw.isDriverHandshakeComplete()) {
moveToActive(h);
}
}
} else {
if (m.getType() == OFType.EXPERIMENTER &&
((OFExperimenter) m).getExperimenter() ==
RoleManager.NICIRA_EXPERIMENTER) {
h.sw.handleNiciraRole(m);
} else {
h.sw.processDriverHandshakeMessage(m);
if (h.sw.isDriverHandshakeComplete()) {
moveToActive(h);
}
}
}
}
@Override
void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
throws IOException, SwitchStateException {
h.pendingPortStatusMsg.add(m);
}
private void moveToActive(OFChannelHandler h) {
boolean success = h.sw.connectSwitch();
h.setState(ACTIVE);
if (!success) {
disconnectDuplicate(h);
}
}
},
/**
* This controller is in MASTER role for this switch. We enter this state
* after requesting and winning control from the global registry.
* The main handshake as well as the switch-driver sub-handshake
* is complete at this point.
* // XXX S reconsider below
* In the (near) future we may deterministically assign controllers to
* switches at startup.
* We only leave this state if the switch disconnects or
* if we send a role request for SLAVE /and/ receive the role reply for
* SLAVE.
*/
ACTIVE(true) {
@Override
void processOFError(OFChannelHandler h, OFErrorMsg m)
throws IOException, SwitchStateException {
// if we get here, then the error message is for something else
if (m.getErrType() == OFErrorType.BAD_REQUEST &&
((OFBadRequestErrorMsg) m).getCode() ==
OFBadRequestCode.EPERM) {
// We are the master controller and the switch returned
// a permission error. This is a likely indicator that
// the switch thinks we are slave. Reassert our
// role
// FIXME: this could be really bad during role transitions
// if two controllers are master (even if its only for
// a brief period). We might need to see if these errors
// persist before we reassert
h.sw.reassertRole();
} else if (m.getErrType() == OFErrorType.FLOW_MOD_FAILED &&
((OFFlowModFailedErrorMsg) m).getCode() ==
OFFlowModFailedCode.ALL_TABLES_FULL) {
h.sw.setTableFull(true);
} else {
logError(h, m);
}
h.dispatchMessage(m);
}
@Override
void processOFStatisticsReply(OFChannelHandler h,
OFStatsReply m) {
if (m.getStatsType().equals(OFStatsType.PORT_DESC)) {
h.sw.setPortDescReply((OFPortDescStatsReply) m);
}
h.dispatchMessage(m);
}
@Override
void processOFExperimenter(OFChannelHandler h, OFExperimenter m)
throws SwitchStateException {
h.sw.handleNiciraRole(m);
}
@Override
void processOFRoleReply(OFChannelHandler h, OFRoleReply m)
throws SwitchStateException {
h.sw.handleRole(m);
}
@Override
void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
throws SwitchStateException {
handlePortStatusMessage(h, m, true);
h.dispatchMessage(m);
}
@Override
void processOFPacketIn(OFChannelHandler h, OFPacketIn m) {
// OFPacketOut out =
// h.sw.factory().buildPacketOut()
// .setXid(m.getXid())
// .setBufferId(m.getBufferId()).build();
// h.sw.sendMsg(out);
h.dispatchMessage(m);
}
@Override
void processOFFlowRemoved(OFChannelHandler h,
OFFlowRemoved m) {
h.dispatchMessage(m);
}
@Override
void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m) {
h.dispatchMessage(m);
}
@Override
void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) {
h.sw.setFeaturesReply(m);
h.dispatchMessage(m);
}
};
private final boolean handshakeComplete;
ChannelState(boolean handshakeComplete) {
this.handshakeComplete = handshakeComplete;
}
/**
* Is this a state in which the handshake has completed?
* @return true if the handshake is complete
*/
public boolean isHandshakeComplete() {
return handshakeComplete;
}
/**
* Get a string specifying the switch connection, state, and
* message received. To be used as message for SwitchStateException
* or log messages
* @param h The channel handler (to get switch information_
* @param m The OFMessage that has just been received
* @param details A string giving more details about the exact nature
* of the problem.
* @return display string
*/
// needs to be protected because enum members are actually subclasses
protected String getSwitchStateMessage(OFChannelHandler h,
OFMessage m,
String details) {
return String.format("Switch: [%s], State: [%s], received: [%s]"
+ ", details: %s",
h.getSwitchInfoString(),
this.toString(),
m.getType().toString(),
details);
}
/**
* We have an OFMessage we didn't expect given the current state and
* we want to treat this as an error.
* We currently throw an exception that will terminate the connection
* However, we could be more forgiving
* @param h the channel handler that received the message
* @param m the message
* @throws SwitchStateException we always throw the exception
*/
// needs to be protected because enum members are actually subclasses
protected void illegalMessageReceived(OFChannelHandler h, OFMessage m)
throws SwitchStateException {
String msg = getSwitchStateMessage(h, m,
"Switch should never send this message in the current state");
throw new SwitchStateException(msg);
}
/**
* We have an OFMessage we didn't expect given the current state and
* we want to ignore the message.
* @param h the channel handler the received the message
* @param m the message
*/
protected void unhandledMessageReceived(OFChannelHandler h,
OFMessage m) {
if (log.isDebugEnabled()) {
String msg = getSwitchStateMessage(h, m,
"Ignoring unexpected message");
log.debug(msg);
}
}
/**
* Log an OpenFlow error message from a switch.
* @param h The switch that sent the error
* @param error The error message
*/
protected void logError(OFChannelHandler h, OFErrorMsg error) {
log.error("{} from switch {} in state {}",
new Object[] {
error,
h.getSwitchInfoString(),
this.toString()});
}
/**
* Log an OpenFlow error message from a switch and disconnect the
* channel.
*
* @param h the IO channel for this switch.
* @param error The error message
*/
protected void logErrorDisconnect(OFChannelHandler h, OFErrorMsg error) {
logError(h, error);
h.channel.disconnect();
}
/**
* log an error message for a duplicate dpid and disconnect this channel.
* @param h the IO channel for this switch.
*/
protected void disconnectDuplicate(OFChannelHandler h) {
log.error("Duplicated dpid or incompleted cleanup - "
+ "disconnecting channel {}", h.getSwitchInfoString());
h.duplicateDpidFound = Boolean.TRUE;
h.channel.disconnect();
}
/**
* Handles all pending port status messages before a switch is declared
* activated in MASTER or EQUAL role. Note that since this handling
* precedes the activation (and therefore notification to IOFSwitchListerners)
* the changes to ports will already be visible once the switch is
* activated. As a result, no notifications are sent out for these
* pending portStatus messages.
* @param h
* @throws SwitchStateException
*/
protected void handlePendingPortStatusMessages(OFChannelHandler h) {
try {
handlePendingPortStatusMessages(h, 0);
} catch (SwitchStateException e) {
log.error(e.getMessage());
}
}
private void handlePendingPortStatusMessages(OFChannelHandler h, int index)
throws SwitchStateException {
if (h.sw == null) {
String msg = "State machine error: switch is null. Should never " +
"happen";
throw new SwitchStateException(msg);
}
ArrayList<OFPortStatus> temp = new ArrayList<OFPortStatus>();
for (OFPortStatus ps: h.pendingPortStatusMsg) {
temp.add(ps);
handlePortStatusMessage(h, ps, false);
}
temp.clear();
// expensive but ok - we don't expect too many port-status messages
// note that we cannot use clear(), because of the reasons below
h.pendingPortStatusMsg.removeAll(temp);
// the iterator above takes a snapshot of the list - so while we were
// dealing with the pending port-status messages, we could have received
// newer ones. Handle them recursively, but break the recursion after
// five steps to avoid an attack.
if (!h.pendingPortStatusMsg.isEmpty() && ++index < 5) {
handlePendingPortStatusMessages(h, index);
}
}
/**
* Handle a port status message.
*
* Handle a port status message by updating the port maps in the
* IOFSwitch instance and notifying Controller about the change so
* it can dispatch a switch update.
*
* @param h The OFChannelHhandler that received the message
* @param m The PortStatus message we received
* @param doNotify if true switch port changed events will be
* dispatched
* @throws SwitchStateException
*
*/
protected void handlePortStatusMessage(OFChannelHandler h, OFPortStatus m,
boolean doNotify) throws SwitchStateException {
if (h.sw == null) {
String msg = getSwitchStateMessage(h, m,
"State machine error: switch is null. Should never " +
"happen");
throw new SwitchStateException(msg);
}
h.sw.handleMessage(m);
}
/**
* Process an OF message received on the channel and
* update state accordingly.
*
* The main "event" of the state machine. Process the received message,
* send follow up message if required and update state if required.
*
* Switches on the message type and calls more specific event handlers
* for each individual OF message type. If we receive a message that
* is supposed to be sent from a controller to a switch we throw
* a SwitchStateExeption.
*
* The more specific handlers can also throw SwitchStateExceptions
*
* @param h The OFChannelHandler that received the message
* @param m The message we received.
* @throws SwitchStateException
* @throws IOException
*/
void processOFMessage(OFChannelHandler h, OFMessage m)
throws IOException, SwitchStateException {
switch(m.getType()) {
case HELLO:
processOFHello(h, (OFHello) m);
break;
case BARRIER_REPLY:
processOFBarrierReply(h, (OFBarrierReply) m);
break;
case ECHO_REPLY:
processOFEchoReply(h, (OFEchoReply) m);
break;
case ECHO_REQUEST:
processOFEchoRequest(h, (OFEchoRequest) m);
break;
case ERROR:
processOFError(h, (OFErrorMsg) m);
break;
case FEATURES_REPLY:
processOFFeaturesReply(h, (OFFeaturesReply) m);
break;
case FLOW_REMOVED:
processOFFlowRemoved(h, (OFFlowRemoved) m);
break;
case GET_CONFIG_REPLY:
processOFGetConfigReply(h, (OFGetConfigReply) m);
break;
case PACKET_IN:
processOFPacketIn(h, (OFPacketIn) m);
break;
case PORT_STATUS:
processOFPortStatus(h, (OFPortStatus) m);
break;
case QUEUE_GET_CONFIG_REPLY:
processOFQueueGetConfigReply(h, (OFQueueGetConfigReply) m);
break;
case STATS_REPLY: // multipart_reply in 1.3
processOFStatisticsReply(h, (OFStatsReply) m);
break;
case EXPERIMENTER:
processOFExperimenter(h, (OFExperimenter) m);
break;
case ROLE_REPLY:
processOFRoleReply(h, (OFRoleReply) m);
break;
case GET_ASYNC_REPLY:
processOFGetAsyncReply(h, (OFAsyncGetReply) m);
break;
// The following messages are sent to switches. The controller
// should never receive them
case SET_CONFIG:
case GET_CONFIG_REQUEST:
case PACKET_OUT:
case PORT_MOD:
case QUEUE_GET_CONFIG_REQUEST:
case BARRIER_REQUEST:
case STATS_REQUEST: // multipart request in 1.3
case FEATURES_REQUEST:
case FLOW_MOD:
case GROUP_MOD:
case TABLE_MOD:
case GET_ASYNC_REQUEST:
case SET_ASYNC:
case METER_MOD:
default:
illegalMessageReceived(h, m);
break;
}
}
/*-----------------------------------------------------------------
* Default implementation for message handlers in any state.
*
* Individual states must override these if they want a behavior
* that differs from the default.
*
* In general, these handlers simply ignore the message and do
* nothing.
*
* There are some exceptions though, since some messages really
* are handled the same way in every state (e.g., ECHO_REQUST) or
* that are only valid in a single state (e.g., HELLO, GET_CONFIG_REPLY
-----------------------------------------------------------------*/
void processOFHello(OFChannelHandler h, OFHello m)
throws IOException, SwitchStateException {
// we only expect hello in the WAIT_HELLO state
illegalMessageReceived(h, m);
}
void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m)
throws IOException {
// Silently ignore.
}
void processOFEchoRequest(OFChannelHandler h, OFEchoRequest m)
throws IOException {
if (h.ofVersion == null) {
log.error("No OF version set for {}. Not sending Echo REPLY",
h.channel.getRemoteAddress());
return;
}
OFFactory factory = (h.ofVersion == OFVersion.OF_13) ?
h.controller.getOFMessageFactory13() : h.controller.getOFMessageFactory10();
OFEchoReply reply = factory
.buildEchoReply()
.setXid(m.getXid())
.setData(m.getData())
.build();
h.channel.write(Collections.singletonList(reply));
}
void processOFEchoReply(OFChannelHandler h, OFEchoReply m)
throws IOException {
// Do nothing with EchoReplies !!
}
// no default implementation for OFError
// every state must override it
abstract void processOFError(OFChannelHandler h, OFErrorMsg m)
throws IOException, SwitchStateException;
void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
throws IOException, SwitchStateException {
unhandledMessageReceived(h, m);
}
void processOFFlowRemoved(OFChannelHandler h, OFFlowRemoved m)
throws IOException {
unhandledMessageReceived(h, m);
}
void processOFGetConfigReply(OFChannelHandler h, OFGetConfigReply m)
throws IOException, SwitchStateException {
// we only expect config replies in the WAIT_CONFIG_REPLY state
illegalMessageReceived(h, m);
}
void processOFPacketIn(OFChannelHandler h, OFPacketIn m)
throws IOException {
unhandledMessageReceived(h, m);
}
// no default implementation. Every state needs to handle it.
abstract void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
throws IOException, SwitchStateException;
void processOFQueueGetConfigReply(OFChannelHandler h,
OFQueueGetConfigReply m)
throws IOException {
unhandledMessageReceived(h, m);
}
void processOFStatisticsReply(OFChannelHandler h, OFStatsReply m)
throws IOException, SwitchStateException {
unhandledMessageReceived(h, m);
}
void processOFExperimenter(OFChannelHandler h, OFExperimenter m)
throws IOException, SwitchStateException {
// TODO: it might make sense to parse the vendor message here
// into the known vendor messages we support and then call more
// specific event handlers
unhandledMessageReceived(h, m);
}
void processOFRoleReply(OFChannelHandler h, OFRoleReply m)
throws SwitchStateException, IOException {
unhandledMessageReceived(h, m);
}
void processOFGetAsyncReply(OFChannelHandler h,
OFAsyncGetReply m) {
unhandledMessageReceived(h, m);
}
}
//*************************
// Channel handler methods
//*************************
@Override
public void channelConnected(ChannelHandlerContext ctx,
ChannelStateEvent e) throws Exception {
channel = e.getChannel();
log.info("New switch connection from {}",
channel.getRemoteAddress());
sendHandshakeHelloMessage();
setState(ChannelState.WAIT_HELLO);
}
@Override
public void channelDisconnected(ChannelHandlerContext ctx,
ChannelStateEvent e) throws Exception {
log.info("Switch disconnected callback for sw:{}. Cleaning up ...",
getSwitchInfoString());
if (thisdpid != 0) {
if (!duplicateDpidFound) {
// if the disconnected switch (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
// switch was a duplicate-dpid, calling the method below would clear
// all state for the original switch (with the same dpid),
// which we obviously don't want.
log.info("{}:removal called", getSwitchInfoString());
if (sw != null) {
sw.removeConnectedSwitch();
}
} else {
// A duplicate was disconnected on this ChannelHandler,
// this is the same switch reconnecting, but the original state was
// not cleaned up - XXX check liveness of original ChannelHandler
log.info("{}:duplicate found", getSwitchInfoString());
duplicateDpidFound = Boolean.FALSE;
}
} else {
log.warn("no dpid in channelHandler registered for "
+ "disconnected switch {}", getSwitchInfoString());
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
throws Exception {
if (e.getCause() instanceof ReadTimeoutException) {
// switch timeout
log.error("Disconnecting switch {} due to read timeout",
getSwitchInfoString());
ctx.getChannel().close();
} else if (e.getCause() instanceof HandshakeTimeoutException) {
log.error("Disconnecting switch {}: failed to complete handshake",
getSwitchInfoString());
ctx.getChannel().close();
} else if (e.getCause() instanceof ClosedChannelException) {
log.debug("Channel for sw {} already closed", getSwitchInfoString());
} else if (e.getCause() instanceof IOException) {
log.error("Disconnecting switch {} due to IO Error: {}",
getSwitchInfoString(), e.getCause().getMessage());
if (log.isDebugEnabled()) {
// still print stack trace if debug is enabled
log.debug("StackTrace for previous Exception: ", e.getCause());
}
ctx.getChannel().close();
} else if (e.getCause() instanceof SwitchStateException) {
log.error("Disconnecting switch {} due to switch state error: {}",
getSwitchInfoString(), e.getCause().getMessage());
if (log.isDebugEnabled()) {
// still print stack trace if debug is enabled
log.debug("StackTrace for previous Exception: ", e.getCause());
}
ctx.getChannel().close();
} else if (e.getCause() instanceof OFParseError) {
log.error("Disconnecting switch "
+ getSwitchInfoString() +
" due to message parse failure",
e.getCause());
ctx.getChannel().close();
} else if (e.getCause() instanceof RejectedExecutionException) {
log.warn("Could not process message: queue full");
} else {
log.error("Error while processing message from switch "
+ getSwitchInfoString()
+ "state " + this.state, e.getCause());
ctx.getChannel().close();
}
}
@Override
public String toString() {
return getSwitchInfoString();
}
@Override
public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e)
throws Exception {
OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
OFMessage m = factory.buildEchoRequest().build();
log.debug("Sending Echo Request on idle channel: {}",
e.getChannel().getPipeline().getLast().toString());
e.getChannel().write(Collections.singletonList(m));
// XXX S some problems here -- echo request has no transaction id, and
// echo reply is not correlated to the echo request.
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
if (e.getMessage() instanceof List) {
@SuppressWarnings("unchecked")
List<OFMessage> msglist = (List<OFMessage>) e.getMessage();
for (OFMessage ofm : msglist) {
// Do the actual packet processing
state.processOFMessage(this, ofm);
}
} else {
state.processOFMessage(this, (OFMessage) e.getMessage());
}
}
//*************************
// Channel utility methods
//*************************
/**
* Is this a state in which the handshake has completed?
* @return true if the handshake is complete
*/
public boolean isHandshakeComplete() {
return this.state.isHandshakeComplete();
}
private void dispatchMessage(OFMessage m) {
sw.handleMessage(m);
}
/**
* Return a string describing this switch based on the already available
* information (DPID and/or remote socket).
* @return display string
*/
private String getSwitchInfoString() {
if (sw != null) {
return sw.toString();
}
String channelString;
if (channel == null || channel.getRemoteAddress() == null) {
channelString = "?";
} else {
channelString = channel.getRemoteAddress().toString();
}
String dpidString;
if (featuresReply == null) {
dpidString = "?";
} else {
dpidString = featuresReply.getDatapathId().toString();
}
return String.format("[%s DPID[%s]]", channelString, dpidString);
}
/**
* Update the channels state. Only called from the state machine.
* TODO: enforce restricted state transitions
* @param state
*/
private void setState(ChannelState state) {
this.state = state;
}
/**
* Send hello message to the switch using the handshake transactions ids.
* @throws IOException
*/
private void sendHandshakeHelloMessage() throws IOException {
// The OF protocol requires us to start things off by sending the highest
// version of the protocol supported.
// bitmap represents OF1.0 (ofp_version=0x01) and OF1.3 (ofp_version=0x04)
// see Sec. 7.5.1 of the OF1.3.4 spec
U32 bitmap = U32.ofRaw(0x00000012);
OFHelloElem hem = factory13.buildHelloElemVersionbitmap()
.setBitmaps(Collections.singletonList(bitmap))
.build();
OFMessage.Builder mb = factory13.buildHello()
.setXid(this.handshakeTransactionIds--)
.setElements(Collections.singletonList(hem));
log.info("Sending OF_13 Hello to {}", channel.getRemoteAddress());
channel.write(Collections.singletonList(mb.build()));
}
/**
* Send featuresRequest msg to the switch using the handshake transactions ids.
* @throws IOException
*/
private void sendHandshakeFeaturesRequestMessage() throws IOException {
OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
OFMessage m = factory.buildFeaturesRequest()
.setXid(this.handshakeTransactionIds--)
.build();
channel.write(Collections.singletonList(m));
}
/**
* Send the configuration requests to tell the switch we want full
* packets.
* @throws IOException
*/
private void sendHandshakeSetConfig() throws IOException {
OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
//log.debug("Sending CONFIG_REQUEST to {}", channel.getRemoteAddress());
List<OFMessage> msglist = new ArrayList<OFMessage>(3);
// Ensure we receive the full packet via PacketIn
// FIXME: We don't set the reassembly flags.
OFSetConfig sc = factory
.buildSetConfig()
.setMissSendLen((short) 0xffff)
.setXid(this.handshakeTransactionIds--)
.build();
msglist.add(sc);
// Barrier
OFBarrierRequest br = factory
.buildBarrierRequest()
.setXid(this.handshakeTransactionIds--)
.build();
msglist.add(br);
// Verify (need barrier?)
OFGetConfigRequest gcr = factory
.buildGetConfigRequest()
.setXid(this.handshakeTransactionIds--)
.build();
msglist.add(gcr);
channel.write(msglist);
}
/**
* send a description state request.
* @throws IOException
*/
private void sendHandshakeDescriptionStatsRequest() throws IOException {
// Get Description to set switch-specific flags
OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
OFDescStatsRequest dreq = factory
.buildDescStatsRequest()
.setXid(handshakeTransactionIds--)
.build();
channel.write(Collections.singletonList(dreq));
}
private void sendHandshakeOFPortDescRequest() throws IOException {
// Get port description for 1.3 switch
OFPortDescStatsRequest preq = factory13
.buildPortDescStatsRequest()
.setXid(handshakeTransactionIds--)
.build();
channel.write(Collections.singletonList(preq));
}
ChannelState getStateForTesting() {
return state;
}
}