package net.floodlightcontroller.util;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.projectfloodlight.openflow.protocol.OFFactories;
import org.projectfloodlight.openflow.protocol.OFVersion;
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.ArpOpcode;
import org.projectfloodlight.openflow.types.EthType;
import org.projectfloodlight.openflow.types.ICMPv4Code;
import org.projectfloodlight.openflow.types.ICMPv4Type;
import org.projectfloodlight.openflow.types.IPv4Address;
import org.projectfloodlight.openflow.types.IPv4AddressWithMask;
import org.projectfloodlight.openflow.types.IPv6AddressWithMask;
import org.projectfloodlight.openflow.types.IPv6FlowLabel;
import org.projectfloodlight.openflow.types.IpDscp;
import org.projectfloodlight.openflow.types.IpEcn;
import org.projectfloodlight.openflow.types.IpProtocol;
import org.projectfloodlight.openflow.types.MacAddress;
import org.projectfloodlight.openflow.types.OFBooleanValue;
import org.projectfloodlight.openflow.types.OFMetadata;
import org.projectfloodlight.openflow.types.OFVlanVidMatch;
import org.projectfloodlight.openflow.types.OFVlanVidMatchWithMask;
import org.projectfloodlight.openflow.types.PacketType;
import org.projectfloodlight.openflow.types.TransportPort;
import org.projectfloodlight.openflow.types.U16;
import org.projectfloodlight.openflow.types.U32;
import org.projectfloodlight.openflow.types.U64;
import org.projectfloodlight.openflow.types.U8;
import org.projectfloodlight.openflow.types.VlanPcp;
import org.projectfloodlight.openflow.types.OFPort;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
/**
* Utilities for working with Matches. Includes workarounds for
* current Loxi limitations/bugs.
*
* Convert OFInstructions to and from dpctl/ofctl-style strings.
* Used primarily by the static flow pusher to store and retreive
* flow entries.
*
* @author Ryan Izard <ryan.izard@bigswitch.com, rizard@g.clemson.edu>
*
* Includes string methods adopted from OpenFlowJ for OpenFlow 1.0.
*
* @author David Erickson (daviderickson@cs.stanford.edu)
* @author Rob Sherwood (rob.sherwood@stanford.edu)
*/
public class MatchUtils {
private static final Logger log = LoggerFactory.getLogger(MatchUtils.class);
/* List of Strings for marshalling and unmarshalling to human readable forms.
* Classes that convert from Match and String should reference these fields for a
* common string representation throughout the controller. The StaticFlowEntryPusher
* is one such example that references these strings. The REST API for the SFEP will
* expect the JSON string to be formatted using these strings for the applicable fields.
*/
public static final String STR_IN_PORT = "in_port";
/* These are special port IDs */
public static final String STR_PORT_LOCAL = "local";
public static final String STR_PORT_CONTROLLER = "controller";
public static final String STR_PORT_ALL = "all";
public static final String STR_PORT_IN_PORT = STR_IN_PORT;
public static final String STR_PORT_FLOOD = "flood";
public static final String STR_PORT_NORMAL = "normal";
public static final String STR_PORT_TABLE = "table";
public static final String STR_PORT_MAX = "max";
public static final String STR_PORT_ANY = "any";
public static final String STR_IN_PHYS_PORT = "in_phys_port";
public static final String STR_DL_DST = "eth_dst";
public static final String STR_DL_SRC = "eth_src";
public static final String STR_DL_TYPE = "eth_type";
public static final String STR_DL_VLAN = "eth_vlan_vid";
public static final String STR_DL_VLAN_PCP = "eth_vlan_pcp";
public static final String STR_NW_DST = "ipv4_dst";
public static final String STR_NW_SRC = "ipv4_src";
public static final String STR_IPV6_DST = "ipv6_dst";
public static final String STR_IPV6_SRC = "ipv6_src";
public static final String STR_IPV6_FLOW_LABEL = "ipv6_label";
public static final String STR_IPV6_EXTHDR = "ipv6_exthdr";
public static final String STR_IPV6_ND_SLL = "ipv6_nd_sll";
public static final String STR_IPV6_ND_TARGET = "ipv6_nd_target";
public static final String STR_IPV6_ND_TLL = "ipv6_nd_tll";
public static final String STR_NW_PROTO = "ip_proto";
public static final String STR_NW_TOS = "ip_tos";
public static final String STR_NW_ECN = "ip_ecn";
public static final String STR_NW_DSCP = "ip_dscp";
public static final String STR_SCTP_DST = "sctp_dst";
public static final String STR_SCTP_SRC = "sctp_src";
public static final String STR_UDP_DST = "udp_dst";
public static final String STR_UDP_SRC = "udp_src";
public static final String STR_TCP_DST = "tcp_dst";
public static final String STR_TCP_SRC = "tcp_src";
public static final String STR_TP_DST = "tp_dst"; // support for OF1.0 generic transport ports
public static final String STR_TP_SRC = "tp_src";
public static final String STR_ICMP_TYPE = "icmpv4_type";
public static final String STR_ICMP_CODE = "icmpv4_code";
public static final String STR_ICMPV6_TYPE = "icmpv6_type";
public static final String STR_ICMPV6_CODE = "icmpv6_code";
public static final String STR_ARP_OPCODE = "arp_opcode";
public static final String STR_ARP_SHA = "arp_sha";
public static final String STR_ARP_DHA = "arp_tha";
public static final String STR_ARP_SPA = "arp_spa";
public static final String STR_ARP_DPA = "arp_tpa";
public static final String STR_MPLS_LABEL = "mpls_label";
public static final String STR_MPLS_TC = "mpls_tc";
public static final String STR_MPLS_BOS = "mpls_bos";
public static final String STR_METADATA = "metadata";
public static final String STR_TUNNEL_ID = "tunnel_id";
public static final String STR_TUNNEL_IPV4_SRC = "tunnel_ipv4_src";
public static final String STR_TUNNEL_IPV4_DST = "tunnel_ipv4_dst";
public static final String STR_PBB_ISID = "pbb_isid"; //TODO support when Loxi does
public static final String STR_PBB_UCA = "pbb_uca";
public static final String STR_TCP_FLAGS = "tcp_flags";
public static final String STR_OVS_TCP_FLAGS = "ovs_tcp_flags";
public static final String STR_ACTSET_OUTPUT = "actset_output";
public static final String STR_PACKET_TYPE = "packet_type";
public static final String SET_FIELD_DELIM = "->";
private static Map<MatchFields, MatchField<?>> ALL_MATCH_FIELDS;
private static Map<MatchFields, Set<OFVersion>> SUPPORTED_OFVERSIONS;
private static final Match.Builder v10 = OFFactories.getFactory(OFVersion.OF_10).buildMatch();
private static final Match.Builder v11 = OFFactories.getFactory(OFVersion.OF_11).buildMatch();
private static final Match.Builder v12 = OFFactories.getFactory(OFVersion.OF_12).buildMatch();
private static final Match.Builder v13 = OFFactories.getFactory(OFVersion.OF_13).buildMatch();
private static final Match.Builder v14 = OFFactories.getFactory(OFVersion.OF_14).buildMatch();
private static final Match.Builder v15 = OFFactories.getFactory(OFVersion.OF_15).buildMatch();
private static void initGlobals() {
Map<MatchFields, MatchField<?>> tmpAll = new HashMap<MatchFields, MatchField<?>>();
Map<MatchFields, Set<OFVersion>> tmpVer = new HashMap<MatchFields, Set<OFVersion>>();
for (Field field : MatchField.ARP_OP.getClass().getFields()) {
if (log.isTraceEnabled()) {
log.trace("Checking MatchField {}", field.getName());
}
if (field.getType() == MatchField.class) {
try {
/* null b/c static final field we're trying to access */
MatchField<?> f = (MatchField<?>) field.get(null);
synchronized (MatchUtils.class) {
tmpAll.put(f.id, f); /* !MUST! set this before SUPPORTED_OFVERSIONS */
tmpVer.put(f.id, getSupportedOFVersions(f.id));
}
if (log.isDebugEnabled()) {
log.debug("Added MatchField {} to list of all MatchFields", f.getName());
}
} catch (IllegalArgumentException | IllegalAccessException e) {
log.error("Could not add MatchField {} to list of all MatchFields", field.getName());
}
}
}
ALL_MATCH_FIELDS = ImmutableMap.copyOf(tmpAll);
SUPPORTED_OFVERSIONS = ImmutableMap.copyOf(tmpVer);
}
public static Set<OFVersion> getSupportedOFVersions(MatchFields m) {
if (ALL_MATCH_FIELDS == null || SUPPORTED_OFVERSIONS == null) {
initGlobals();
}
Set<OFVersion> v = SUPPORTED_OFVERSIONS.get(m);
if (v != null) {
return v; /* should already be unmodifiable */
}
v = new HashSet<OFVersion>();
if (v10.supports(getMatchField(m))) {
v.add(OFVersion.OF_10);
}
if (v11.supports(getMatchField(m))) {
v.add(OFVersion.OF_11);
}
if (v12.supports(getMatchField(m))) {
v.add(OFVersion.OF_12);
}
if (v13.supports(getMatchField(m))) {
v.add(OFVersion.OF_13);
}
if (v14.supports(getMatchField(m))) {
v.add(OFVersion.OF_14);
}
if (v15.supports(getMatchField(m))) {
v.add(OFVersion.OF_15);
}
SUPPORTED_OFVERSIONS.put(m, ImmutableSet.copyOf(v));
return SUPPORTED_OFVERSIONS.get(m);
}
public static MatchField<?> getMatchField(MatchFields m) {
if (ALL_MATCH_FIELDS == null || SUPPORTED_OFVERSIONS == null) {
initGlobals();
}
return ALL_MATCH_FIELDS.get(m);
}
/**
* Get the string name of a MatchField as it's exposed through
* the REST API of Floodlight. This is, unfortunately, not the
* same name as the ones used internally within MatchField.
* @param mf
* @return
*/
public static String getMatchFieldName(MatchFields mf) {
String key = "";
switch (mf) {
case ARP_OP:
key = STR_ARP_OPCODE;
break;
case ARP_SHA:
key = STR_ARP_SHA;
break;
case ARP_SPA:
key = STR_ARP_SPA;
break;
case ARP_THA:
key = STR_ARP_DHA;
break;
case ARP_TPA:
key = STR_ARP_DPA;
break;
case ETH_DST:
key = STR_DL_DST;
break;
case ETH_SRC:
key = STR_DL_SRC;
break;
case ETH_TYPE:
key = STR_DL_TYPE;
break;
case ICMPV4_CODE:
key = STR_ICMP_CODE;
break;
case ICMPV4_TYPE:
key = STR_ICMP_TYPE;
break;
case ICMPV6_CODE:
key = STR_ICMPV6_CODE;
break;
case ICMPV6_TYPE:
key = STR_ICMPV6_TYPE;
break;
case IN_PHY_PORT:
key = STR_IN_PHYS_PORT;
break;
case IN_PORT:
key = STR_IN_PORT;
break;
case IPV4_DST:
key = STR_NW_DST;
break;
case IPV4_SRC:
key = STR_NW_SRC;
break;
case IPV6_DST:
key = STR_IPV6_DST;
break;
case IPV6_EXTHDR:
key = STR_IPV6_EXTHDR;
break;
case IPV6_FLABEL:
key = STR_IPV6_FLOW_LABEL;
break;
case IPV6_ND_SLL:
key = STR_IPV6_ND_SLL;
break;
case IPV6_ND_TARGET:
key = STR_IPV6_ND_TARGET;
break;
case IPV6_ND_TLL:
key = STR_IPV6_ND_TLL;
break;
case IPV6_SRC:
key = STR_IPV6_SRC;
break;
case IP_DSCP:
key = STR_NW_DSCP;
break;
case IP_ECN:
key = STR_NW_ECN;
break;
case IP_PROTO:
key = STR_NW_PROTO;
break;
case METADATA:
key = STR_METADATA;
break;
case MPLS_BOS:
key = STR_MPLS_BOS;
break;
case MPLS_LABEL:
key = STR_MPLS_LABEL;
break;
case MPLS_TC:
key = STR_MPLS_TC;
break;
case PBB_UCA:
key = STR_PBB_UCA;
break;
case SCTP_DST:
key = STR_SCTP_DST;
break;
case SCTP_SRC:
key = STR_SCTP_SRC;
break;
case TCP_DST:
key = STR_TCP_DST;
break;
case TCP_SRC:
key = STR_TCP_SRC;
break;
case TUNNEL_ID:
key = STR_TUNNEL_ID;
break;
case TUNNEL_IPV4_DST:
key = STR_TUNNEL_IPV4_DST;
break;
case TUNNEL_IPV4_SRC:
key = STR_TUNNEL_IPV4_SRC;
break;
case UDP_DST:
key = STR_UDP_DST;
break;
case UDP_SRC:
key = STR_UDP_SRC;
break;
case VLAN_PCP:
key = STR_DL_VLAN_PCP;
break;
case VLAN_VID:
key = STR_DL_VLAN;
break;
case PACKET_TYPE:
key = STR_PACKET_TYPE;
break;
case TCP_FLAGS:
key = STR_TCP_FLAGS;
break;
case OVS_TCP_FLAGS:
key = STR_OVS_TCP_FLAGS;
break;
case ACTSET_OUTPUT:
key = STR_ACTSET_OUTPUT;
break;
/* NOTE: keep BSN MatchFields to eliminate need for default case.
Unaccounted for fields will then produce warning in future */
case BSN_EGR_PORT_GROUP_ID:
case BSN_GLOBAL_VRF_ALLOWED:
case BSN_INGRESS_PORT_GROUP_ID:
case BSN_INNER_ETH_DST:
case BSN_INNER_ETH_SRC:
case BSN_INNER_VLAN_VID:
case BSN_IN_PORTS_128:
case BSN_IN_PORTS_512:
case BSN_L2_CACHE_HIT:
case BSN_L3_DST_CLASS_ID:
case BSN_L3_INTERFACE_CLASS_ID:
case BSN_L3_SRC_CLASS_ID:
case BSN_LAG_ID:
case BSN_TCP_FLAGS:
case BSN_UDF0:
case BSN_UDF1:
case BSN_UDF2:
case BSN_UDF3:
case BSN_UDF4:
case BSN_UDF5:
case BSN_UDF6:
case BSN_UDF7:
case BSN_VFI:
case BSN_VLAN_XLATE_PORT_GROUP_ID:
case BSN_VRF:
case BSN_VXLAN_NETWORK_ID:
log.warn("Ignoring BSN MatchField {}", mf);
break;
}
return key;
}
/**
* Create a point-to-point match for two devices at the IP layer.
* Takes an existing match (e.g. from a PACKET_IN), and masks all
* MatchFields leaving behind:
* IN_PORT
* VLAN_VID
* ETH_TYPE
* ETH_SRC
* ETH_DST
* IPV4_SRC
* IPV4_DST
* IP_PROTO (might remove this)
*
* If one of the above MatchFields is wildcarded in Match m,
* that MatchField will be wildcarded in the returned Match.
*
* @param m The match to remove all L4+ MatchFields from
* @return A new Match object with all MatchFields masked/wildcared
* except for those listed above.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static Match maskL4AndUp(Match m) {
Match.Builder mb = m.createBuilder();
Iterator<MatchField<?>> itr = m.getMatchFields().iterator(); // only get exact or masked fields (not fully wildcarded)
while(itr.hasNext()) {
MatchField mf = itr.next();
// restrict MatchFields only to L3 and below: IN_PORT, ETH_TYPE, ETH_SRC, ETH_DST, IPV4_SRC, IPV4_DST, IP_PROTO (this one debatable...)
// if a MatchField is not in the access list below, it will not be set --> it will be left wildcarded (default)
if (mf.equals(MatchField.IN_PORT) || mf.equals(MatchField.ETH_TYPE) || mf.equals(MatchField.ETH_SRC) || mf.equals(MatchField.ETH_DST) ||
mf.equals(MatchField.IPV4_SRC) || mf.equals(MatchField.IPV4_DST) || mf.equals(MatchField.IP_PROTO)) {
if (m.isExact(mf)) {
mb.setExact(mf, m.get(mf));
} else if (m.isPartiallyMasked(mf)) {
mb.setMasked((MatchField<?>) mf, m.getMasked(mf));
} else {
// it's either exact, masked, or wildcarded
// itr only contains exact and masked MatchFields
// we should never get here
}
}
}
return mb.build();
}
/**
* Retains all fields in the Match parent. Converts the parent to an
* equivalent Match of OFVersion version. No polite check is done to verify
* if MatchFields in parent are supported in a Match of OFVersion
* version. An exception will be thrown if there are any unsupported
* fields during the conversion process.
*
* Note that a Match.Builder is returned. This is a convenience for cases
* where MatchFields might be modified, added, or removed prior to being
* built (e.g. in Forwarding/Routing between switches of different OFVersions).
* Simply build the returned Match.Builder if you would like to treat this
* function as a strict copy-to-version.
*
* @param parent, the Match to convert
* @param version, the OFVersion to convert parent to
* @return a Match.Builder of the newly-converted Match
*/
@SuppressWarnings("unchecked")
public static Match.Builder convertToVersion(Match parent, OFVersion version) {
/* Builder retains a parent MatchField list, but list will not be used to
* build the new match if the builder's set methods have been invoked; only
* additions will be built, and all parent MatchFields will be ignored,
* even if they were not modified by the new builder. Create a builder, and
* walk through m's list of non-wildcarded MatchFields. Set them all in the
* new builder by invoking a set method for each. This will make them persist
* in the Match built from this builder if the user decides to add or subtract
* from the MatchField list.
*/
Match.Builder mb = OFFactories.getFactory(version).buildMatch();
Iterator<MatchField<?>> itr = parent.getMatchFields().iterator(); // only get exact or masked fields (not fully wildcarded)
while (itr.hasNext()) {
@SuppressWarnings("rawtypes")
MatchField mf = itr.next();
if (parent.isExact(mf)) {
mb.setExact(mf, parent.get(mf));
} else if (parent.isPartiallyMasked(mf)) {
mb.setMasked((MatchField<?>) mf, parent.getMasked(mf));
} else {
// it's either exact, masked, or wildcarded
// itr only contains exact and masked MatchFields
// we should never get here
}
}
return mb;
}
/**
*
* Workaround for bug in Loxi:
*
* Create a builder from an existing Match object. Unlike Match's
* createBuilder(), this utility function will preserve all of
* Match m's MatchFields, even if new MatchFields are set or modified
* with the builder after it is returned to the calling function.
*
* All original MatchFields in m will be set if the build() method is
* invoked upon the returned builder. After the builder is returned, if
* a MatchField is modified via setExact(), setMasked(), or wildcard(),
* the newly modified MatchField will replace the original found in m.
*
* @param m; the match to create the builder from
* @return Match.Builder; the builder that can be modified, and when built,
* will retain all of m's MatchFields, unless you explicitly overwrite them.
*/
public static Match.Builder createRetentiveBuilder(Match m) {
return convertToVersion(m, m.getVersion());
}
/**
* Create a Match builder the same OF version as Match m. The returned builder
* will not retain any MatchField information from Match m and will
* essentially return a clean-slate Match builder with no parent history.
* This simple method is included as a wrapper to provide the opposite functionality
* of createRetentiveBuilder().
*
* @param m; the match to create the builder from
* @return Match.Builder; the builder retains no history from the parent Match m
*/
public static Match.Builder createForgetfulBuilder(Match m) {
return OFFactories.getFactory(m.getVersion()).buildMatch();
}
/**
* Create a duplicate Match object from Match m.
*
* @param m; the match to copy
* @return Match; the new copy of Match m
*/
public static Match createCopy(Match m) {
return m.createBuilder().build(); // will use parent MatchFields to produce the new Match only if the builder is never modified
}
/**
* TODO NOT IMPLEMENTED!
*
* Returns empty string right now.
*/
@Deprecated
public static String toString(Match match) {
return "";
}
/**
* Based on the method from OFMatch in openflowj 1.0.
* Set this Match's parameters based on a comma-separated key=value pair
* dpctl-style string, e.g., from the output of OFMatch.toString(). The
* exact syntax for each key is defined by the string constants at the top
* of MatchUtils.java. <br>
*
* @param match
* a key=value comma separated string, e.g.
* "in_port=5,nw_dst=192.168.0.0/16,tp_src=80"
* @param the OF version to construct this match for
* @throws IllegalArgumentException
* on unexpected key or value
*/
public static Match fromString(String match, OFVersion ofVersion) throws IllegalArgumentException {
boolean ver10 = false;
if (match.equals("") || match.equalsIgnoreCase("any") || match.equalsIgnoreCase("all") || match.equals("[]")) {
match = "Match[]";
}
// Split into pairs of key=value
String[] tokens = match.split("[\\[,\\]]");
int initArg = 0;
if (tokens[0].equals("Match")) {
initArg = 1;
}
// Split up key=value pairs into [key, value], and insert into array-deque
int i;
String[] tmp;
ArrayDeque<String[]> llValues = new ArrayDeque<String[]>();
for (i = initArg; i < tokens.length; i++) {
tmp = tokens[i].split("=");
if (tmp.length != 2) {
throw new IllegalArgumentException("Token " + tokens[i] + " does not have form 'key=value' parsing " + match);
}
tmp[0] = tmp[0].toLowerCase(); // try to make key parsing case insensitive
llValues.add(tmp); // llValues contains [key, value] pairs. Create a queue of pairs to process.
}
Match.Builder mb = OFFactories.getFactory(ofVersion).buildMatch();
//Determine if the OF version is 1.0 before adding a flow
if (ofVersion.equals(OFVersion.OF_10)) {
ver10 = true;
}
while (!llValues.isEmpty()) {
IpProtocol ipProto = null;
String[] key_value = llValues.pollFirst(); // pop off the first element; this completely removes it from the queue.
/* Extract the data and its mask */
String[] dataMask = key_value[1].split("/");
if (dataMask.length > 2) {
throw new IllegalArgumentException("[Data, Mask] " + dataMask + " does not have form 'data/mask' or 'data'" + key_value[1]);
} else if (dataMask.length == 1) {
dataMask[0] = dataMask[0].trim().toLowerCase();
log.debug("No mask detected in Match string: {}", key_value[1]);
} else if (dataMask.length == 2) {
dataMask[0] = dataMask[0].trim().toLowerCase();
dataMask[1] = dataMask[1].trim().toLowerCase();
log.debug("Detected mask in Match string: {}", key_value[1]);
}
switch (key_value[0]) {
case STR_IN_PORT:
if (dataMask.length == 1) {
mb.setExact(MatchField.IN_PORT, portFromString(dataMask[0]));
} else {
mb.setMasked(MatchField.IN_PORT, portFromString(dataMask[0]), portFromString(dataMask[1]));
}
break;
case STR_DL_DST: /* Only accept hex-string for MAC addresses */
if (dataMask.length == 1) {
mb.setExact(MatchField.ETH_DST, MacAddress.of(dataMask[0]));
} else {
mb.setMasked(MatchField.ETH_DST, MacAddress.of(dataMask[0]), MacAddress.of(dataMask[1]));
}
break;
case STR_DL_SRC:
if (dataMask.length == 1) {
mb.setExact(MatchField.ETH_SRC, MacAddress.of(dataMask[0]));
} else {
mb.setMasked(MatchField.ETH_SRC, MacAddress.of(dataMask[0]), MacAddress.of(dataMask[1]));
}
break;
case STR_DL_TYPE:
if (dataMask.length == 1) {
mb.setExact(MatchField.ETH_TYPE, EthType.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.ETH_TYPE, EthType.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
EthType.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
break;
case STR_DL_VLAN:
if (dataMask.length == 1) {
mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofRawVid(ParseUtils.parseHexOrDecShort(dataMask[0])));
} else {
mb.setMasked(MatchField.VLAN_VID, OFVlanVidMatchWithMask.of(
OFVlanVidMatch.ofRawVid(ParseUtils.parseHexOrDecShort(dataMask[0])),
OFVlanVidMatch.ofRawVid(ParseUtils.parseHexOrDecShort(dataMask[1]))));
}
break;
case STR_DL_VLAN_PCP:
if (dataMask.length == 1) {
mb.setExact(MatchField.VLAN_PCP, VlanPcp.of(U8.t(ParseUtils.parseHexOrDecShort(dataMask[0]))));
} else {
mb.setMasked(MatchField.VLAN_PCP, VlanPcp.of(U8.t(ParseUtils.parseHexOrDecShort(dataMask[0]))),
VlanPcp.of(U8.t(ParseUtils.parseHexOrDecShort(dataMask[1]))));
}
break;
case STR_NW_DST: /* Only accept dotted-decimal for IPv4 addresses */
mb.setMasked(MatchField.IPV4_DST, IPv4AddressWithMask.of(key_value[1]));
break;
case STR_NW_SRC:
mb.setMasked(MatchField.IPV4_SRC, IPv4AddressWithMask.of(key_value[1]));
break;
case STR_IPV6_DST: /* Only accept hex-string for IPv6 addresses */
if (ver10 == true) {
throw new IllegalArgumentException("OF Version incompatible");
}
mb.setMasked(MatchField.IPV6_DST, IPv6AddressWithMask.of(key_value[1]));
break;
case STR_IPV6_SRC:
if (ver10 == true) {
throw new IllegalArgumentException("OF Version incompatible");
}
mb.setMasked(MatchField.IPV6_SRC, IPv6AddressWithMask.of(key_value[1]));
break;
case STR_IPV6_FLOW_LABEL:
if (ver10 == true) {
throw new IllegalArgumentException("OF Version incompatible");
}
if (dataMask.length == 1) {
mb.setExact(MatchField.IPV6_FLABEL, IPv6FlowLabel.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.IPV6_FLABEL, IPv6FlowLabel.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
IPv6FlowLabel.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
break;
case STR_NW_PROTO:
if (dataMask.length == 1) {
mb.setExact(MatchField.IP_PROTO, IpProtocol.of(ParseUtils.parseHexOrDecShort(dataMask[0])));
} else {
mb.setMasked(MatchField.IP_PROTO, IpProtocol.of(ParseUtils.parseHexOrDecShort(dataMask[0])),
IpProtocol.of(ParseUtils.parseHexOrDecShort(dataMask[1])));
}
break;
case STR_NW_TOS:
if (dataMask.length == 1) {
mb.setExact(MatchField.IP_ECN, IpEcn.of((byte)(ParseUtils.parseHexOrDecShort(dataMask[0]) & 0x03)));
mb.setExact(MatchField.IP_DSCP, IpDscp.of((byte)((ParseUtils.parseHexOrDecShort(dataMask[0]) & 0xFC) >> 2)));
} else {
mb.setMasked(MatchField.IP_ECN, IpEcn.of((byte)(ParseUtils.parseHexOrDecShort(dataMask[0]) & 0x03)),
IpEcn.of((byte)(ParseUtils.parseHexOrDecShort(dataMask[1]) & 0x03)));
mb.setMasked(MatchField.IP_DSCP, IpDscp.of((byte)((ParseUtils.parseHexOrDecShort(dataMask[0]) & 0xFC) >> 2)),
IpDscp.of((byte)((ParseUtils.parseHexOrDecShort(dataMask[1]) & 0xFC) >> 2)));
}
break;
case STR_NW_ECN:
if (dataMask.length == 1) {
mb.setExact(MatchField.IP_ECN, IpEcn.of(U8.t(ParseUtils.parseHexOrDecShort(dataMask[0]))));
} else {
mb.setMasked(MatchField.IP_ECN, IpEcn.of(U8.t(ParseUtils.parseHexOrDecShort(dataMask[0]))),
IpEcn.of(U8.t(ParseUtils.parseHexOrDecShort(dataMask[1]))));
}
break;
case STR_NW_DSCP:
if (dataMask.length == 1) {
mb.setExact(MatchField.IP_DSCP, IpDscp.of(U8.t(ParseUtils.parseHexOrDecShort(dataMask[0]))));
} else {
mb.setMasked(MatchField.IP_DSCP, IpDscp.of(U8.t(ParseUtils.parseHexOrDecShort(dataMask[0]))),
IpDscp.of(U8.t(ParseUtils.parseHexOrDecShort(dataMask[1]))));
}
break;
case STR_SCTP_DST: // for transport ports, if we don't know the transport protocol yet, postpone parsing this [key, value] pair until we know. Put it at the back of the queue.
if (mb.get(MatchField.IP_PROTO) == null) {
llValues.add(key_value); // place it back if we can't proceed yet
} else {
if (dataMask.length == 1) {
mb.setExact(MatchField.SCTP_DST, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.SCTP_DST, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
}
break;
case STR_SCTP_SRC:
if (mb.get(MatchField.IP_PROTO) == null) {
llValues.add(key_value); // place it back if we can't proceed yet
} else {
if (dataMask.length == 1) {
mb.setExact(MatchField.SCTP_SRC, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.SCTP_SRC, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
}
break;
case STR_UDP_DST:
if (mb.get(MatchField.IP_PROTO) == null) {
llValues.add(key_value); // place it back if we can't proceed yet
} else {
if (dataMask.length == 1) {
mb.setExact(MatchField.UDP_DST, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.UDP_DST, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
}
break;
case STR_UDP_SRC:
if (mb.get(MatchField.IP_PROTO) == null) {
llValues.add(key_value); // place it back if we can't proceed yet
} else {
if (dataMask.length == 1) {
mb.setExact(MatchField.UDP_SRC, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.UDP_SRC, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
}
break;
case STR_TCP_DST:
if (mb.get(MatchField.IP_PROTO) == null) {
llValues.add(key_value); // place it back if we can't proceed yet
} else {
if (dataMask.length == 1) {
mb.setExact(MatchField.TCP_DST, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.TCP_DST, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
}
break;
case STR_TCP_SRC:
if (mb.get(MatchField.IP_PROTO) == null) {
llValues.add(key_value); // place it back if we can't proceed yet
} else {
if (dataMask.length == 1) {
mb.setExact(MatchField.TCP_SRC, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.TCP_SRC, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
}
break;
case STR_TP_DST: // support for OF1.0 generic transport ports
if ((ipProto = mb.get(MatchField.IP_PROTO)) == null) {
llValues.add(key_value); // place it back if we can't proceed yet
} else if (ipProto == IpProtocol.TCP){
if (dataMask.length == 1) {
mb.setExact(MatchField.TCP_DST, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.TCP_DST, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
} else if (ipProto == IpProtocol.UDP){
if (dataMask.length == 1) {
mb.setExact(MatchField.UDP_DST, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.UDP_DST, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
} else if (ipProto == IpProtocol.SCTP){
if (dataMask.length == 1) {
mb.setExact(MatchField.SCTP_DST, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.SCTP_DST, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
}
break;
case STR_TP_SRC:
if ((ipProto = mb.get(MatchField.IP_PROTO)) == null) {
llValues.add(key_value); // place it back if we can't proceed yet
} else if (ipProto == IpProtocol.TCP){
if (dataMask.length == 1) {
mb.setExact(MatchField.TCP_SRC, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.TCP_SRC, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
} else if (ipProto == IpProtocol.UDP){
if (dataMask.length == 1) {
mb.setExact(MatchField.UDP_SRC, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.UDP_SRC, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
} else if (ipProto == IpProtocol.SCTP){
if (dataMask.length == 1) {
mb.setExact(MatchField.SCTP_SRC, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.SCTP_SRC, TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
TransportPort.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
}
break;
case STR_ICMP_TYPE:
if (dataMask.length == 1) {
mb.setExact(MatchField.ICMPV4_TYPE, ICMPv4Type.of(ParseUtils.parseHexOrDecShort(dataMask[0])));
} else {
mb.setMasked(MatchField.ICMPV4_TYPE, ICMPv4Type.of(ParseUtils.parseHexOrDecShort(dataMask[0])),
ICMPv4Type.of(ParseUtils.parseHexOrDecShort(dataMask[1])));
}
break;
case STR_ICMP_CODE:
if (dataMask.length == 1) {
mb.setExact(MatchField.ICMPV4_CODE, ICMPv4Code.of(ParseUtils.parseHexOrDecShort(dataMask[0])));
} else {
mb.setMasked(MatchField.ICMPV4_CODE, ICMPv4Code.of(ParseUtils.parseHexOrDecShort(dataMask[0])),
ICMPv4Code.of(ParseUtils.parseHexOrDecShort(dataMask[1])));
}
break;
case STR_ICMPV6_TYPE:
if (ver10 == true) {
throw new IllegalArgumentException("OF Version incompatible");
}
if (dataMask.length == 1) {
mb.setExact(MatchField.ICMPV6_TYPE, U8.of(ParseUtils.parseHexOrDecShort(dataMask[0])));
} else {
mb.setMasked(MatchField.ICMPV6_TYPE, U8.of(ParseUtils.parseHexOrDecShort(dataMask[0])),
U8.of(ParseUtils.parseHexOrDecShort(dataMask[1])));
}
break;
case STR_ICMPV6_CODE:
if (ver10 == true) {
throw new IllegalArgumentException("OF Version incompatible");
}
if (dataMask.length == 1) {
mb.setExact(MatchField.ICMPV6_CODE, U8.of(ParseUtils.parseHexOrDecShort(dataMask[0])));
} else {
mb.setMasked(MatchField.ICMPV6_CODE, U8.of(ParseUtils.parseHexOrDecShort(dataMask[0])),
U8.of(ParseUtils.parseHexOrDecShort(dataMask[1])));
}
break;
case STR_IPV6_ND_SLL:
if (ver10 == true) {
throw new IllegalArgumentException("OF Version incompatible");
}
if (dataMask.length == 1) {
mb.setExact(MatchField.IPV6_ND_SLL, MacAddress.of(dataMask[0]));
} else {
mb.setMasked(MatchField.IPV6_ND_SLL, MacAddress.of(dataMask[0]), MacAddress.of(dataMask[1]));
}
break;
case STR_IPV6_ND_TLL:
if (ver10 == true) {
throw new IllegalArgumentException("OF Version incompatible");
}
if (dataMask.length == 1) {
mb.setExact(MatchField.IPV6_ND_TLL, MacAddress.of(dataMask[0]));
} else {
mb.setMasked(MatchField.IPV6_ND_TLL, MacAddress.of(dataMask[0]), MacAddress.of(dataMask[1]));
}
break;
case STR_IPV6_ND_TARGET:
if (ver10 == true) {
throw new IllegalArgumentException("OF Version incompatible");
}
mb.setMasked(MatchField.IPV6_ND_TARGET, IPv6AddressWithMask.of(key_value[1]));
break;
case STR_IPV6_EXTHDR:
if (dataMask.length == 1) {
mb.setExact(MatchField.IPV6_EXTHDR, U16.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.IPV6_EXTHDR, U16.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
U16.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
break;
case STR_ARP_OPCODE:
if (dataMask.length == 1) {
mb.setExact(MatchField.ARP_OP, ArpOpcode.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.ARP_OP, ArpOpcode.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
ArpOpcode.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
break;
case STR_ARP_SHA:
if (dataMask.length == 1) {
mb.setExact(MatchField.ARP_SHA, MacAddress.of(dataMask[0]));
} else {
mb.setMasked(MatchField.ARP_SHA, MacAddress.of(dataMask[0]), MacAddress.of(dataMask[1]));
}
break;
case STR_ARP_DHA:
if (dataMask.length == 1) {
mb.setExact(MatchField.ARP_THA, MacAddress.of(dataMask[0]));
} else {
mb.setMasked(MatchField.ARP_THA, MacAddress.of(dataMask[0]), MacAddress.of(dataMask[1]));
}
break;
case STR_ARP_SPA:
mb.setMasked(MatchField.ARP_SPA, IPv4AddressWithMask.of(key_value[1]));
break;
case STR_ARP_DPA:
mb.setMasked(MatchField.ARP_TPA, IPv4AddressWithMask.of(key_value[1]));
break;
case STR_MPLS_LABEL:
if (dataMask.length == 1) {
mb.setExact(MatchField.MPLS_LABEL, U32.of(ParseUtils.parseHexOrDecLong(dataMask[0])));
} else {
mb.setMasked(MatchField.MPLS_LABEL, U32.of(ParseUtils.parseHexOrDecLong(dataMask[0])),
U32.of(ParseUtils.parseHexOrDecLong(dataMask[1])));
}
break;
case STR_MPLS_TC:
if (dataMask.length == 1) {
mb.setExact(MatchField.MPLS_TC, U8.of(ParseUtils.parseHexOrDecShort(dataMask[0])));
} else {
mb.setMasked(MatchField.MPLS_TC, U8.of(ParseUtils.parseHexOrDecShort(dataMask[0])),
U8.of(ParseUtils.parseHexOrDecShort(dataMask[1])));
}
break;
case STR_MPLS_BOS:
mb.setExact(MatchField.MPLS_BOS, OFBooleanValue.of(Boolean.parseBoolean(key_value[1])));
break;
case STR_METADATA:
if (dataMask.length == 1) {
mb.setExact(MatchField.METADATA, OFMetadata.ofRaw(ParseUtils.parseHexOrDecLong(dataMask[0])));
} else {
mb.setMasked(MatchField.METADATA, OFMetadata.ofRaw(ParseUtils.parseHexOrDecLong(dataMask[0])),
OFMetadata.ofRaw(ParseUtils.parseHexOrDecLong(dataMask[1])));
}
break;
case STR_TUNNEL_ID:
if (dataMask.length == 1) {
mb.setExact(MatchField.TUNNEL_ID, U64.of(ParseUtils.parseHexOrDecLong(dataMask[0])));
} else {
mb.setMasked(MatchField.TUNNEL_ID, U64.of(ParseUtils.parseHexOrDecLong(dataMask[0])),
U64.of(ParseUtils.parseHexOrDecLong(dataMask[1])));
}
break;
case STR_TUNNEL_IPV4_SRC:
if (dataMask.length == 1) {
mb.setExact(MatchField.TUNNEL_IPV4_SRC, IPv4Address.of(key_value[1]));
} else {
mb.setMasked(MatchField.TUNNEL_IPV4_SRC, IPv4AddressWithMask.of(key_value[1]));
}
break;
case STR_TUNNEL_IPV4_DST:
if (dataMask.length == 1) {
mb.setExact(MatchField.TUNNEL_IPV4_DST, IPv4Address.of(key_value[1]));
} else {
mb.setMasked(MatchField.TUNNEL_IPV4_DST, IPv4AddressWithMask.of(key_value[1]));
}
break;
case STR_PBB_ISID:
log.warn("Ignoring unimplemented OXM {}", key_value[0]);
/*TODO no-op. Not implemented.
if (key_value[1].startsWith("0x")) {
mb.setExact(MatchField.pb, U64.of(Long.parseLong(key_value[1].replaceFirst("0x", ""), 16)));
} else {
mb.setExact(MatchField., U64.of(Long.parseLong(key_value[1])));
} */
break;
case STR_PBB_UCA:
mb.setExact(MatchField.PBB_UCA, OFBooleanValue.of(Boolean.parseBoolean(key_value[1])));
break;
case STR_TCP_FLAGS:
if (dataMask.length == 1) {
mb.setExact(MatchField.TCP_FLAGS, U16.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.TCP_FLAGS, U16.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
U16.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
break;
case STR_OVS_TCP_FLAGS:
if (dataMask.length == 1) {
mb.setExact(MatchField.OVS_TCP_FLAGS, U16.of(ParseUtils.parseHexOrDecInt(dataMask[0])));
} else {
mb.setMasked(MatchField.OVS_TCP_FLAGS, U16.of(ParseUtils.parseHexOrDecInt(dataMask[0])),
U16.of(ParseUtils.parseHexOrDecInt(dataMask[1])));
}
break;
case STR_ACTSET_OUTPUT:
/* TODO when loxi bug fixed if (!mb.supports(MatchField.ACTSET_OUTPUT)) {
log.warn("Match {} unsupported in OpenFlow version {}", MatchField.ACTSET_OUTPUT, ofVersion);
break;
} else {
log.warn("Why are we here?");
}*/
if (dataMask.length == 1) {
mb.setExact(MatchField.ACTSET_OUTPUT, portFromString(dataMask[0]));
} else {
mb.setMasked(MatchField.ACTSET_OUTPUT, portFromString(dataMask[0]), portFromString(dataMask[1]));
}
break;
case STR_PACKET_TYPE:
if (dataMask.length != 2) {
log.error("Ignoring invalid PACKET_TYPE OXM. Must specify namespace and namespace type in the form 'ns/nstype'");
} else {
mb.setExact(MatchField.PACKET_TYPE, PacketType.of(ParseUtils.parseHexOrDecInt(dataMask[0]),
ParseUtils.parseHexOrDecInt(dataMask[1])));
}
break;
default:
throw new IllegalArgumentException("unknown token " + key_value + " parsing " + match);
}
}
return mb.build();
}
public static OFPort portFromString(String s) {
if (s == null) {
throw new IllegalArgumentException("Port string cannot be null");
}
s = s.trim().toLowerCase();
switch (s) {
case MatchUtils.STR_PORT_ALL:
return OFPort.ALL;
case MatchUtils.STR_PORT_CONTROLLER:
return OFPort.CONTROLLER;
case MatchUtils.STR_PORT_FLOOD:
return OFPort.FLOOD;
case MatchUtils.STR_PORT_IN_PORT:
return OFPort.IN_PORT;
case MatchUtils.STR_PORT_LOCAL:
return OFPort.LOCAL;
case MatchUtils.STR_PORT_NORMAL:
return OFPort.NORMAL;
case MatchUtils.STR_PORT_TABLE:
return OFPort.TABLE;
case MatchUtils.STR_PORT_MAX:
return OFPort.MAX;
case MatchUtils.STR_PORT_ANY:
return OFPort.ANY;
default:
log.debug("Port {} was not a special port string. Parsing as raw int or hex", s);
}
try {
return OFPort.of(U32.of(ParseUtils.parseHexOrDecLong(s)).getRaw());
} catch (NumberFormatException e) {
log.error("Could not parse port '{}'", s);
return null;
}
}
public static String portToString(OFPort p) {
if (p == null) {
throw new IllegalArgumentException("Port cannot be null");
}
if (p.equals(OFPort.ALL)) {
return STR_PORT_ALL;
}
if (p.equals(OFPort.ANY)) {
return STR_PORT_ANY;
}
if (p.equals(OFPort.CONTROLLER)) {
return STR_PORT_CONTROLLER;
}
if (p.equals(OFPort.FLOOD)) {
return STR_PORT_FLOOD;
}
if (p.equals(OFPort.IN_PORT)) {
return STR_PORT_IN_PORT;
}
if (p.equals(OFPort.LOCAL)) {
return STR_PORT_LOCAL;
}
if (p.equals(OFPort.NORMAL)) {
return STR_PORT_NORMAL;
}
if (p.equals(OFPort.MAX)) {
return STR_PORT_MAX;
}
if (p.equals(OFPort.TABLE)) {
return STR_PORT_TABLE;
}
return Integer.toString(p.getPortNumber());
}
}