/* * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.controller.sal.match.extensible; import java.io.Serializable; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import org.opendaylight.controller.sal.utils.NetUtils; /** * Represents the generic match criteria for a network frame/packet/message * It contains a collection of individual field match * */ @XmlRootElement @XmlAccessorType(XmlAccessType.NONE) public class Match implements Cloneable, Serializable { private static final long serialVersionUID = 1L; private Map> fields; public Match() { fields = new HashMap>(); } public Match(Match match) { fields = new HashMap>(match.fields); } /** * Generic setter for frame/packet/message's header field against which to match * * @param field the fields parameters as MAtchField object */ public void setField(MatchField field) { if (field.isValid()) { fields.put(field.getType(), field); } } /** * Generic method to clear a field from the match */ public void clearField(String type) { fields.remove(type); } /** * Generic getter for fields against which the match is programmed * * @param type frame/packet/message's header field type * @return */ public MatchField getField(String type) { return fields.get(type); } /** * Returns the list of MatchType fields the match is set for * * @return List of individual MatchType fields. */ public List getMatchesList() { return new ArrayList(fields.keySet()); } /** * Returns the list of MatchFields the match is set for * * @return List of individual MatchField values. */ @XmlElement(name="matchField") public List> getMatchFields() { return new ArrayList>(fields.values()); } /** * Returns whether this match is for an IPv6 flow */ public boolean isIPv6() { if (isPresent(DlType.TYPE)) { for (MatchField field : fields.values()) { if (!field.isV6()) { return false; } } } return true; } /** * Returns whether this match is for an IPv4 flow */ public boolean isIPv4() { return !isIPv6(); } /** * Returns whether for the specified field type the match is to be considered "any" * Equivalent to say this match does not care about the value of the specified field * * @param type * @return */ public boolean isAny(String type) { return !fields.containsKey(type); } /** * Returns whether a match for the specified field type is configured * * @param type * @return */ public boolean isPresent(String type) { return (fields.get(type) != null); } public boolean isEmpty() { return fields.isEmpty(); } @Override public Match clone() { Match cloned = null; try { cloned = (Match) super.clone(); cloned.fields = new HashMap>(); for (Entry> entry : this.fields.entrySet()) { cloned.fields.put(entry.getKey(), entry.getValue().clone()); } } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } return cloned; } /** * Returns a reversed version of this match * For example, in the reversed version the network source and destination * addresses will be exchanged. Non symmetric match field will not be * copied over into the reversed match version, like input port. * * @return */ public Match reverse() { Match reverse = new Match(); for (MatchField field : fields.values()) { reverse.setField(field.hasReverse()? field.getReverse() : field.clone()); } // Reset asymmetric fields reverse.clearField(InPort.TYPE); return reverse; } /** * Check whether the current match conflicts with the passed filter match * This match conflicts with the filter if for at least a MatchType defined * in the filter match, the respective MatchFields differ or are not * compatible * * In other words the function returns true if the set of packets described * by one match and the set of packets described by the other match are * disjoint. Equivalently, if the intersection of the two sets of packets * described by the two org.opendaylight.controller.sal.matches is an empty. * * For example, Let's suppose the filter has the following MatchFields: * DL_TYPE = 0x800 * NW_DST = 172.20.30.110/24 * * while this match has the following MatchFields: * DL_TYPE = 0x800 * NW_DST = 172.20.30.45/24 * TP_DST = 80 * * Then the function would return false as the two Match are not * conflicting. * * Note: the mask value is taken into account only for MatchType.NW_SRC and * MatchType.NW_DST * * @param match * the Match describing the filter * @return true if the set of packets described by one match and the set of * packets described by the other match are disjoint, false * otherwise */ public boolean conflictWithFilter(Match filter) { return !this.intersetcs(filter); } /** * Merge the current Match fields with the fields of the filter Match. A * check is first run to see if this Match is compatible with the filter * Match. If it is not, the merge is not attempted. * * The result is the match object representing the intersection of the set * of packets described by this match with the set of packets described by * the filter match. If the intersection of the two sets is empty, the * return match will be null. * * @param filter * the match with which attempting the merge * @return a new Match object describing the set of packets represented by * the intersection of this and the filter org.opendaylight.controller.sal.matches. null if the * intersection is empty. */ public Match mergeWithFilter(Match filter) { return this.getIntersection(filter); } /** * Return the match representing the intersection of the set of packets * described by this match with the set of packets described by the other * match. Such as m.getIntersection(m) == m, m.getIntersection(u) == m and * m.getIntersection(o) == o where u is an empty match (universal set, all * packets) and o is the null match (empty set). * * @param other * the match with which computing the intersection * @return a new Match object representing the intersection of the set of * packets described by this match with the set of packets described * by the other match. null when the intersection is the empty set. */ public Match getIntersection(Match other) { // If no intersection, return the empty set if (!this.intersetcs(other)) { return null; } // Check if any of the two is the universal match if (this.isEmpty()) { return other.clone(); } if (other.isEmpty()) { return this.clone(); } // Get all the match types for both filters Set allTypes = new HashSet(this.fields.keySet()); allTypes.addAll(new HashSet(other.fields.keySet())); // Derive the intersection Match intersection = new Match(); for (String type : allTypes) { if (this.isAny(type) && other.isAny(type)) { continue; } if (this.isAny(type)) { intersection.setField(other.getField(type).clone()); continue; } else if (other.isAny(type)) { intersection.setField(this.getField(type).clone()); continue; } // Either they are equal or it is about IP address switch (type) { // When it is about IP address, take the wider prefix address // between the twos case NwSrc.TYPE: case NwDst.TYPE: MatchField thisField = this.getField(type); MatchField otherField = other.getField(type); InetAddress thisAddress = (InetAddress) thisField.getValue(); InetAddress otherAddress = (InetAddress) otherField.getValue(); InetAddress thisMask = (InetAddress) thisField.getMask(); InetAddress otherMask = (InetAddress) otherField.getMask(); int thisMaskLen = (thisMask == null) ? ((thisAddress instanceof Inet4Address) ? 32 : 128) : NetUtils .getSubnetMaskLength(thisMask); int otherMaskLen = (otherMask == null) ? ((otherAddress instanceof Inet4Address) ? 32 : 128) : NetUtils .getSubnetMaskLength(otherMask); InetAddress subnetPrefix = null; InetAddress subnetMask = null; if (thisMaskLen < otherMaskLen) { subnetPrefix = NetUtils.getSubnetPrefix(otherAddress, otherMaskLen); subnetMask = otherMask; } else { subnetPrefix = NetUtils.getSubnetPrefix(thisAddress, thisMaskLen); subnetMask = thisMask; } MatchField field = (type.equals(NwSrc.TYPE)) ? new NwSrc(subnetPrefix, subnetMask) : new NwDst( subnetPrefix, subnetMask); intersection.setField(field); break; default: // this and other match field are equal for this type, pick this // match field intersection.setField(this.getField(type).clone()); } } return intersection; } /** * Checks whether the intersection of the set of packets described by this * match with the set of packets described by the other match is non empty * * For example, if this match is: DL_SRC = 00:cc:bb:aa:11:22 * * and the other match is: DL_TYPE = 0x800 NW_SRC = 1.2.3.4 * * then their respective matching packets set intersection is non empty: * DL_SRC = 00:cc:bb:aa:11:22 DL_TYPE = 0x800 NW_SRC = 1.2.3.4 * * @param other * the other match with which testing the intersection * @return true if the intersection of the respective matching packets sets * is non empty */ public boolean intersetcs(Match other) { // No intersection with the empty set if (other == null) { return false; } // Always intersection with the universal set if (this.isEmpty() || other.isEmpty()) { return true; } // Get all the match types for both filters Set allTypes = new HashSet(this.fields.keySet()); allTypes.addAll(new HashSet(other.fields.keySet())); // Iterate through all the match types defined in the two filters for (String type : allTypes) { if (this.isAny(type) || other.isAny(type)) { continue; } MatchField thisField = this.getField(type); MatchField otherField = other.getField(type); switch (type) { case DlSrc.TYPE: case DlDst.TYPE: if (!Arrays.equals((byte[]) thisField.getValue(), (byte[]) otherField.getValue())) { return false; } break; case NwSrc.TYPE: case NwDst.TYPE: InetAddress thisAddress = (InetAddress) thisField.getValue(); InetAddress otherAddress = (InetAddress) otherField.getValue(); // Validity check if (thisAddress instanceof Inet4Address && otherAddress instanceof Inet6Address || thisAddress instanceof Inet6Address && otherAddress instanceof Inet4Address) { return false; } InetAddress thisMask = (InetAddress) thisField.getMask(); InetAddress otherMask = (InetAddress) otherField.getMask(); if (NetUtils.inetAddressConflict(thisAddress, otherAddress, thisMask, otherMask) && NetUtils.inetAddressConflict(otherAddress, thisAddress, otherMask, thisMask)) { return false; } break; default: if (!thisField.getValue().equals(otherField.getValue())) { return false; } } } return true; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((fields == null) ? 0 : fields.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof Match)) { return false; } Match other = (Match) obj; if (fields == null) { if (other.fields != null) { return false; } } else if (!fields.equals(other.fields)) { return false; } return true; } @Override public String toString() { return "Match[" + fields.values() + "]"; } }