/*
* Copyright 2016 christopher.metter.
*
* 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 de.uniwuerzburg.info3.ofcprobe.vswitch.connection.flowtable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import de.uniwuerzburg.info3.ofcprobe.vswitch.connection.OFConnection1_zero;
import de.uniwuerzburg.info3.ofcprobe.vswitch.main.config.Config;
import org.openflow.protocol.OFError;
import org.openflow.protocol.OFFlowMod;
import org.openflow.protocol.OFFlowRemoved;
import org.openflow.protocol.OFMatch;
import org.openflow.protocol.OFFlowRemoved.OFFlowRemovedReason;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.action.OFAction;
import org.openflow.protocol.action.OFActionOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The OFFlowMod Handler handles all incoming OF_FLOW_MOD messages
*
* @author Christopher Metter(christopher.metter@informatik.uni-wuerzburg.de)
*
*/
public class OFFlowModHandler {
/**
* Logger
*/
private static final Logger logger = LoggerFactory.getLogger(OFConnection1_zero.class);
/**
* The Config
*/
private Config config;
/**
* The FlowTable
*/
private OFFlowTable flow_table;
/**
* Constructor
*
* @param config the config
*/
public OFFlowModHandler(Config config) {
this.config = config;
this.flow_table = new OFFlowTable(config);
}
/**
* Handles INcoming FlowMods
*
* @param flow_mod incoming FlowMOds
* @return Answer to FlowMods
*/
public List<OFMessage> handleOFFlowMod(OFFlowMod flow_mod) {
List<OFMessage> output = new ArrayList<OFMessage>();
OFMatch match = flow_mod.getMatch();
OFFlowTableEntry entry = new OFFlowTableEntry(flow_mod);
short flags = flow_mod.getFlags();
boolean sendFlowRemoved = false;
boolean checkOverlap = false;
switch (flags) {
case OFFlowMod.OFPFF_SEND_FLOW_REM:
// Send flow removed message when flow expires or is deleted.
sendFlowRemoved = true;
break;
case OFFlowMod.OFPFF_CHECK_OVERLAP:
// the switch must check that there are no conflicting entries with the same priority
// If there is one, the flow mod fails and an error code is returned.
checkOverlap = true;
break;
case OFFlowMod.OFPFF_EMERG:
// the switch must consider this flow entry as an emergency entry, and only use it for forwarding when disconnected from the controller.
// like omg dead http://youtu.be/z6jtpqGPW-8
return null;
default:
logger.trace("No FLOW-MOD Flag: {} from FlOW-MOD: {}", flow_mod.getFlags(), flow_mod.toString());
break;
}
if (!areLegalActions(flow_mod.getActions())) {
OFError wrongActionsError = new OFError();
wrongActionsError.setXid(flow_mod.getXid());
wrongActionsError.setErrorType(OFError.OFErrorType.OFPET_BAD_ACTION);
wrongActionsError.setErrorCode(OFError.OFBadActionCode.OFPBAC_BAD_OUT_PORT);
wrongActionsError.setOffendingMsg(flow_mod);
output.add(wrongActionsError);
return output;
}
switch (flow_mod.getCommand()) {
case OFFlowMod.OFPFC_ADD:
/* OFPFC_ADD: New flow. */
if (checkOverlap) {
if (this.flow_table.checkOverlap(match)) {
OFError error = new OFError();
error.setXid(flow_mod.getXid());
error.setErrorType(OFError.OFErrorType.OFPET_FLOW_MOD_FAILED);
error.setErrorCode(OFError.OFFlowModFailedCode.OFPFMFC_OVERLAP);
error.setOffendingMsg(flow_mod);
output.add(error);
return output;
}
}
if (!this.flow_table.isFull()) {
this.flow_table.removeMatchingFlows(match, true, sendFlowRemoved);
this.flow_table.insert(match, entry);
} else {
if (this.flow_table.containsMatch(match)) {
this.flow_table.insert(match, entry);
} else {
OFError tableFull = new OFError();
tableFull.setXid(flow_mod.getXid());
tableFull.setErrorType(OFError.OFErrorType.OFPET_FLOW_MOD_FAILED);
tableFull.setErrorCode(OFError.OFFlowModFailedCode.OFPFMFC_ALL_TABLES_FULL);
tableFull.setOffendingMsg(flow_mod);
output.add(tableFull);
return output;
}
}
break;
case OFFlowMod.OFPFC_MODIFY:
/* OFPFC_MODIFY: Modify all matching flows. */
if (checkOverlap) {
if (this.flow_table.checkOverlap(match)) {
OFError error = new OFError();
error.setXid(flow_mod.getXid());
error.setErrorType(OFError.OFErrorType.OFPET_FLOW_MOD_FAILED);
error.setErrorCode(OFError.OFFlowModFailedCode.OFPFMFC_OVERLAP);
error.setOffendingMsg(flow_mod);
output.add(error);
return output;
}
}
Map<OFMatch, OFFlowTableEntry> matchingFlows = this.flow_table.getMatchingFlows(match, false);
Map<OFMatch, OFFlowTableEntry> modifiedFlows = new TreeMap<OFMatch, OFFlowTableEntry>();
if (!matchingFlows.isEmpty()) {
Iterator<Map.Entry<OFMatch, OFFlowTableEntry>> modifyIter = matchingFlows.entrySet().iterator();
while (modifyIter.hasNext()) {
Map.Entry<OFMatch, OFFlowTableEntry> currentEntry = modifyIter.next();
currentEntry.getValue().setActions(flow_mod.getActions());
currentEntry.getValue().updateCookie(flow_mod.getCookie());
modifiedFlows.put(currentEntry.getKey(), currentEntry.getValue());
}
this.flow_table.modifyMatchingFlows(modifiedFlows);
} else {
if (!this.flow_table.isFull()) {
this.flow_table.insert(match, new OFFlowTableEntry(flow_mod));
} else {
OFError tableFull = new OFError();
tableFull.setXid(flow_mod.getXid());
tableFull.setErrorType(OFError.OFErrorType.OFPET_FLOW_MOD_FAILED);
tableFull.setErrorCode(OFError.OFFlowModFailedCode.OFPFMFC_ALL_TABLES_FULL);
tableFull.setOffendingMsg(flow_mod);
output.add(tableFull);
return output;
}
}
break;
case OFFlowMod.OFPFC_MODIFY_STRICT:
/* OFPFC_MODIFY_STRICT: Modify entry strictly matching wildcards */
Map<OFMatch, OFFlowTableEntry> matchingFlowsStrict = this.flow_table.getMatchingFlows(match, true);
Map<OFMatch, OFFlowTableEntry> modifiedFlowsStrict = new TreeMap<OFMatch, OFFlowTableEntry>();
if (matchingFlowsStrict.isEmpty()) {
Iterator<Map.Entry<OFMatch, OFFlowTableEntry>> modifyStrictIter = matchingFlowsStrict.entrySet().iterator();
while (modifyStrictIter.hasNext()) {
Map.Entry<OFMatch, OFFlowTableEntry> currentEntry = modifyStrictIter.next();
currentEntry.getValue().setActions(flow_mod.getActions());
currentEntry.getValue().updateCookie(flow_mod.getCookie());
modifiedFlowsStrict.put(currentEntry.getKey(), currentEntry.getValue());
}
this.flow_table.modifyMatchingFlows(modifiedFlowsStrict);
} else {
if (!this.flow_table.isFull()) {
this.flow_table.insert(match, new OFFlowTableEntry(flow_mod));
} else {
OFError tableFull = new OFError();
tableFull.setXid(flow_mod.getXid());
tableFull.setErrorType(OFError.OFErrorType.OFPET_FLOW_MOD_FAILED);
tableFull.setErrorCode(OFError.OFFlowModFailedCode.OFPFMFC_ALL_TABLES_FULL);
tableFull.setOffendingMsg(flow_mod);
output.add(tableFull);
return output;
}
}
break;
case OFFlowMod.OFPFC_DELETE:
/* OFPFC_DELETE: Delete all matching flows. */
List<OFMatch> removedMatches = this.flow_table.removeMatchingFlows(match, false, sendFlowRemoved);
if (sendFlowRemoved && !removedMatches.isEmpty()) {
Iterator<OFMatch> matchIter = removedMatches.iterator();
while (matchIter.hasNext()) {
OFFlowRemoved flow_removed = new OFFlowRemoved();
flow_removed.setReason(OFFlowRemovedReason.OFPRR_DELETE);
flow_removed.setXid(flow_mod.getXid());
flow_removed.setCookie(flow_mod.getCookie());
flow_removed.setMatch(matchIter.next());
output.add(flow_removed);
}
return output;
}
break;
case OFFlowMod.OFPFC_DELETE_STRICT:
/* OFPFC_DELETE_STRICT: Strictly match wildcards and priority. */
List<OFMatch> removedStrictMatches = this.flow_table.removeMatchingFlows(match, true, sendFlowRemoved);
if (sendFlowRemoved && !removedStrictMatches.isEmpty()) {
Iterator<OFMatch> matchStrictIter = removedStrictMatches.iterator();
while (matchStrictIter.hasNext()) {
OFFlowRemoved flow_removed = new OFFlowRemoved();
flow_removed.setReason(OFFlowRemovedReason.OFPRR_DELETE);
flow_removed.setXid(flow_mod.getXid());
flow_removed.setCookie(flow_mod.getCookie());
flow_removed.setMatch(matchStrictIter.next());
output.add(flow_removed);
}
return output;
}
break;
default:
logger.warn("Undefined FLOW_MOD Command: {}", flow_mod.getCommand());
break;
}
return output;
}
/**
* Are these OFActions legal meaning are they executable on this switch?
*
* @param actions the Actions
* @return legal?
*/
private boolean areLegalActions(List<OFAction> actions) {
Iterator<OFAction> actionIter = actions.iterator();
while (actionIter.hasNext()) {
if (!isLegalAction(actionIter.next())) {
return false;
}
}
return true;
}
/**
* Checks one OFACtion for legallity (e.g. is Outport > maxPortNum)
*
* @param action the Action
* @return legal?
*/
private boolean isLegalAction(OFAction action) {
switch (action.getType()) {
case OPAQUE_ENQUEUE:
break;
case OUTPUT:
OFActionOutput actionOutput = (OFActionOutput) action;
short port = actionOutput.getPort();
return (this.config.getSwitchConfig().getPortCountperSwitch() >= port);
case SET_DL_DST:
break;
case SET_DL_SRC:
break;
case SET_NW_DST:
break;
case SET_NW_SRC:
break;
case SET_NW_TOS:
break;
case SET_TP_DST:
break;
case SET_TP_SRC:
break;
case SET_VLAN_PCP:
break;
case SET_VLAN_VID:
break;
case STRIP_VLAN:
break;
case VENDOR:
break;
default:
break;
}
return true;
}
/**
* Get Flow Table
*
* @return the flow Table
*/
public OFFlowTable getFlowTable() {
return this.flow_table;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((flow_table == null) ? 0 : flow_table.hashCode());
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
OFFlowModHandler other = (OFFlowModHandler) obj;
if (flow_table == null) {
if (other.flow_table != null) {
return false;
}
} else if (!flow_table.equals(other.flow_table)) {
return false;
}
return true;
}
}