/** * Copyright 2013, Big Switch Networks, Inc. * * 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 net.floodlightcontroller.staticentry; import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import net.floodlightcontroller.core.util.AppCookie; import net.floodlightcontroller.staticentry.web.StaticEntryPusherResource; import net.floodlightcontroller.util.ActionUtils; import net.floodlightcontroller.util.GroupUtils; import net.floodlightcontroller.util.InstructionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.databind.MappingJsonFactory; import org.projectfloodlight.openflow.protocol.OFFlowMod; import org.projectfloodlight.openflow.protocol.OFFlowModFlags; import org.projectfloodlight.openflow.protocol.OFGroupMod; import org.projectfloodlight.openflow.protocol.OFInstructionType; import org.projectfloodlight.openflow.protocol.instruction.OFInstruction; import org.projectfloodlight.openflow.protocol.instruction.OFInstructionApplyActions; import org.projectfloodlight.openflow.protocol.instruction.OFInstructionClearActions; import org.projectfloodlight.openflow.protocol.instruction.OFInstructionExperimenter; import org.projectfloodlight.openflow.protocol.instruction.OFInstructionGotoTable; import org.projectfloodlight.openflow.protocol.instruction.OFInstructionMeter; import org.projectfloodlight.openflow.protocol.instruction.OFInstructionStatTrigger; import org.projectfloodlight.openflow.protocol.instruction.OFInstructionWriteActions; import org.projectfloodlight.openflow.protocol.instruction.OFInstructionWriteMetadata; import org.projectfloodlight.openflow.protocol.match.Match; import org.projectfloodlight.openflow.protocol.match.MatchField; import org.projectfloodlight.openflow.protocol.match.MatchFields; import org.projectfloodlight.openflow.types.OFBufferId; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.U64; /** * Represents static entries to be maintained by the controller on the * switches. */ public class StaticEntries { protected static Logger log = LoggerFactory.getLogger(StaticEntries.class); private static final int INFINITE_TIMEOUT = 0; /** * This function generates a random hash for the bottom half of the cookie * * @param fm * @param userCookie * @param name * @return A cookie that encodes the application ID and a hash */ public static U64 computeEntryCookie(int userCookie, String name) { // flow-specific hash is next 20 bits int prime = 211; int hash = 2311; for (int i = 0; i < name.length(); i++) { hash = hash * prime + (int) name.charAt(i); } return AppCookie.makeCookie(StaticEntryPusher.STATIC_ENTRY_APP_ID, hash); } /** * Sets defaults for an OFFlowMod used in the StaticFlowEntryPusher * @param fm The OFFlowMod to set defaults for * @param entryName The name of the entry. Used to compute the cookie. */ public static void initDefaultFlowMod(OFFlowMod.Builder fmb, String entryName) { fmb.setIdleTimeout(INFINITE_TIMEOUT) // not setting these would also work .setHardTimeout(INFINITE_TIMEOUT) .setBufferId(OFBufferId.NO_BUFFER) .setOutPort(OFPort.ANY) .setCookie(computeEntryCookie(0, entryName)) .setPriority(Integer.MAX_VALUE) .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM)); return; } /** * Gets the entry name of a message * @param fmJson The OFFlowMod in a JSON representation * @return The name of the OFFlowMod, null if not found * @throws IOException If there was an error parsing the JSON */ public static String getEntryNameFromJson(String fmJson) throws IOException{ MappingJsonFactory f = new MappingJsonFactory(); JsonParser jp; try { jp = f.createParser(fmJson); } catch (JsonParseException e) { throw new IOException(e); } jp.nextToken(); if (jp.getCurrentToken() != JsonToken.START_OBJECT) { throw new IOException("Expected START_OBJECT"); } while (jp.nextToken() != JsonToken.END_OBJECT) { if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { throw new IOException("Expected FIELD_NAME"); } String n = jp.getCurrentName(); jp.nextToken(); if (jp.getText().equals("")) continue; if (n == StaticEntryPusher.Columns.COLUMN_NAME) return jp.getText(); } return null; } public static Map<String, Object> groupModToStorageEntry(OFGroupMod gm, String sw, String name) { Map<String, Object> entry = new HashMap<String, Object>(); entry.put(StaticEntryPusher.Columns.COLUMN_NAME, name); entry.put(StaticEntryPusher.Columns.COLUMN_SWITCH, sw); entry.put(StaticEntryPusher.Columns.COLUMN_ACTIVE, Boolean.toString(true)); entry.put(StaticEntryPusher.Columns.COLUMN_GROUP_ID, Integer.toString(gm.getGroup().getGroupNumber())); entry.put(StaticEntryPusher.Columns.COLUMN_GROUP_TYPE, GroupUtils.groupTypeToString(gm.getGroupType())); entry.put(StaticEntryPusher.Columns.COLUMN_GROUP_BUCKETS, GroupUtils.groupBucketsToJsonArray(gm.getBuckets())); return entry; } /** * Parses an OFFlowMod (and it's inner Match) to the storage entry format. * @param fm The FlowMod to parse * @param sw The switch the FlowMod is going to be installed on * @param name The name of this static flow entry * @return A Map representation of the storage entry */ public static Map<String, Object> flowModToStorageEntry(OFFlowMod fm, String sw, String name) throws Exception { Map<String, Object> entry = new HashMap<String, Object>(); entry.put(StaticEntryPusher.Columns.COLUMN_NAME, name); entry.put(StaticEntryPusher.Columns.COLUMN_SWITCH, sw); entry.put(StaticEntryPusher.Columns.COLUMN_ACTIVE, Boolean.toString(true)); entry.put(StaticEntryPusher.Columns.COLUMN_PRIORITY, Integer.toString(fm.getPriority())); entry.put(StaticEntryPusher.Columns.COLUMN_IDLE_TIMEOUT, Integer.toString(fm.getIdleTimeout())); entry.put(StaticEntryPusher.Columns.COLUMN_HARD_TIMEOUT, Integer.toString(fm.getHardTimeout())); switch (fm.getVersion()) { case OF_10: if (fm.getActions() != null) { entry.put(StaticEntryPusher.Columns.COLUMN_ACTIONS, ActionUtils.actionsToString(fm.getActions())); } break; case OF_11: case OF_12: case OF_13: case OF_14: case OF_15: /* should have a table ID present */ if (fm.getTableId() != null) { /* if not set, then don't worry about it. Default will be set when built and sent to switch */ entry.put(StaticEntryPusher.Columns.COLUMN_TABLE_ID, Short.toString(fm.getTableId().getValue())); } /* should have a list of instructions, of which apply and write actions could have sublists of actions */ if (fm.getInstructions() != null) { List<OFInstruction> instructions = fm.getInstructions(); for (OFInstruction inst : instructions) { switch (inst.getType()) { case GOTO_TABLE: entry.put(StaticEntryPusher.intructionToColumnName(OFInstructionType.GOTO_TABLE), InstructionUtils.gotoTableToString(((OFInstructionGotoTable) inst))); break; case WRITE_METADATA: entry.put(StaticEntryPusher.intructionToColumnName(OFInstructionType.WRITE_METADATA), InstructionUtils.writeMetadataToString(((OFInstructionWriteMetadata) inst))); break; case WRITE_ACTIONS: entry.put(StaticEntryPusher.intructionToColumnName(OFInstructionType.WRITE_ACTIONS), InstructionUtils.writeActionsToString(((OFInstructionWriteActions) inst))); break; case APPLY_ACTIONS: entry.put(StaticEntryPusher.intructionToColumnName(OFInstructionType.APPLY_ACTIONS), InstructionUtils.applyActionsToString(((OFInstructionApplyActions) inst))); break; case CLEAR_ACTIONS: entry.put(StaticEntryPusher.intructionToColumnName(OFInstructionType.CLEAR_ACTIONS), InstructionUtils.clearActionsToString(((OFInstructionClearActions) inst))); break; case METER: entry.put(StaticEntryPusher.intructionToColumnName(OFInstructionType.METER), InstructionUtils.meterToString(((OFInstructionMeter) inst))); break; case EXPERIMENTER: entry.put(StaticEntryPusher.intructionToColumnName(OFInstructionType.EXPERIMENTER), InstructionUtils.experimenterToString(((OFInstructionExperimenter) inst))); break; case DEPRECATED: entry.put(StaticEntryPusher.intructionToColumnName(OFInstructionType.DEPRECATED), InstructionUtils.deprecatedToString(((OFInstruction) inst))); break; case STAT_TRIGGER: entry.put(StaticEntryPusher.intructionToColumnName(OFInstructionType.STAT_TRIGGER), InstructionUtils.statTriggerToJsonString(((OFInstructionStatTrigger) inst))); break; } } } } Match match = fm.getMatch(); Iterator<MatchField<?>> itr = match.getMatchFields().iterator(); // only get exact or masked fields (not fully wildcarded) while (itr.hasNext()) { MatchField<?> mf = itr.next(); String column = StaticEntryPusher.matchFieldToColumnName(mf.id); if (match.supports(mf) && match.isExact(mf)) { entry.put(column, match.get(mf).toString()); } else if (match.supportsMasked(mf) && match.isPartiallyMasked(mf)) { entry.put(column, match.getMasked(mf).toString()); } else { log.error("Got match for {} but protocol {} does not support said match. Ignoring match.", column, match.getVersion().toString()); } } int result = StaticEntryPusherResource.checkActions(entry); if (result == -1) throw new Exception("Invalid action/instructions"); return entry; } /** * Turns a JSON formatted Static Flow Pusher string into a storage entry * Expects a string in JSON along the lines of: * { * "switch": "AA:BB:CC:DD:EE:FF:00:11", * "name": "flow-mod-1", * "cookie": "0", * "priority": "32768", * "in_port": "1", * "actions": "output=2", * } * @param fmJson The JSON formatted static flow pusher entry * @return The map of the storage entry * @throws IOException If there was an error parsing the JSON */ public static Map<String, Object> jsonToStorageEntry(String fmJson) throws IOException { Map<String, Object> entry = new HashMap<String, Object>(); MappingJsonFactory f = new MappingJsonFactory(); JsonParser jp; String tpSrcPort = null; String tpDstPort = null; String ipProto = null; try { jp = f.createParser(fmJson); } catch (JsonParseException e) { throw new IOException(e); } jp.nextToken(); if (jp.getCurrentToken() != JsonToken.START_OBJECT) { throw new IOException("Expected START_OBJECT"); } while (jp.nextToken() != JsonToken.END_OBJECT) { if (jp.getCurrentToken() != JsonToken.FIELD_NAME) { throw new IOException("Expected FIELD_NAME"); } String n = jp.getCurrentName().toLowerCase().trim(); jp.nextToken(); if (n.equals(StaticEntryPusher.Columns.COLUMN_GROUP_BUCKETS)) { entry.put(n, jp.readValueAsTree().toString()); /* Special case to save the entire JSON bucket tree */ } else if (n.equals(StaticEntryPusher.Columns.COLUMN_TP_SRC)) { entry.put(n, jp.getText()); /* Support for OF1.0 generic transport ports */ tpSrcPort = jp.getText(); } else if (n.equals(StaticEntryPusher.Columns.COLUMN_TP_DST)) { entry.put(n, jp.getText()); /* Support for OF1.0 generic transport ports */ tpDstPort = jp.getText(); } else if (n.equals(StaticEntryPusher.matchFieldToColumnName(MatchFields.IP_PROTO))) { entry.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.IP_PROTO), jp.getText()); ipProto = jp.getText(); /* Support for OF1.0 generic transport ports */ } else { entry.put(n, jp.getText()); /* All others are 'key':'value' pairs */ } } // For OF1.0, transport ports are specified using generic tp_src, tp_dst type strings. // Once the whole json string has been parsed, find out the IpProto to properly assign the ports. // If IpProto not specified, print error, and make sure all TP columns are clear. if (ipProto != null && ipProto.equalsIgnoreCase("tcp")) { if (tpSrcPort != null) { entry.remove(StaticEntryPusher.Columns.COLUMN_TP_SRC); entry.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.TCP_SRC), tpSrcPort); } if (tpDstPort != null) { entry.remove(StaticEntryPusher.Columns.COLUMN_TP_DST); entry.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.TCP_DST), tpDstPort); } } else if (ipProto != null && ipProto.equalsIgnoreCase("udp")) { if (tpSrcPort != null) { entry.remove(StaticEntryPusher.Columns.COLUMN_TP_SRC); entry.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.UDP_SRC), tpSrcPort); } if (tpDstPort != null) { entry.remove(StaticEntryPusher.Columns.COLUMN_TP_DST); entry.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.UDP_DST), tpDstPort); } } else if (ipProto != null && ipProto.equalsIgnoreCase("sctp")) { if (tpSrcPort != null) { entry.remove(StaticEntryPusher.Columns.COLUMN_TP_SRC); entry.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.SCTP_SRC), tpSrcPort); } if (tpDstPort != null) { entry.remove(StaticEntryPusher.Columns.COLUMN_TP_DST); entry.put(StaticEntryPusher.matchFieldToColumnName(MatchFields.SCTP_DST), tpDstPort); } } else { log.debug("Got IP protocol of '{}' and tp-src of '{}' and tp-dst of '" + tpDstPort + "' via SFP REST API", ipProto, tpSrcPort); } return entry; } }