/* * Copyright (c) 2013 Big Switch Networks, Inc. * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html * * 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 org.sdnplatform.netvirt.core; import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import org.openflow.protocol.OFMatch; import org.openflow.util.HexString; import org.sdnplatform.core.annotations.LogMessageCategory; import org.sdnplatform.core.annotations.LogMessageDoc; import org.sdnplatform.core.util.MutableInteger; import org.sdnplatform.devicemanager.IDevice; import org.sdnplatform.netvirt.core.VNSAccessControlList.VNSAclMatchResult; import org.sdnplatform.packet.Ethernet; import org.sdnplatform.packet.ICMP; import org.sdnplatform.packet.IPv4; import org.sdnplatform.packet.TCP; import org.sdnplatform.packet.UDP; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Represents a NetVirt ACL entry, which is part of a parent AccessControlList * sequence No * type (mac, ip, <0-255>, icmp, udp, tcp) * permit/deny * * @author shudong.zhou@bigswitch.com */ @LogMessageCategory("Network Virtualization") public class VNSAccessControlListEntry implements Comparable<VNSAccessControlListEntry> { // Constants for ACL actions protected static Map<String, VNSAclMatchResult> actionMap = new HashMap<String, VNSAclMatchResult>(); static { actionMap.put("permit", VNSAclMatchResult.ACL_PERMIT); actionMap.put("deny", VNSAclMatchResult.ACL_DENY); } // Constants for ACL types and IPPROTO mapping protected static final int IPPROTO_INVALID = -1; public static final int IPPROTO_ALL = 256; public static final int ICMPTYPE_ALL = 256; public static final int ETHERTYPE_ALL = 65536; public static final int VLAN_ALL = 4096; protected static Map<String, Integer> aclTypeMap = new HashMap<String, Integer>(); static { aclTypeMap.put("ip", IPPROTO_ALL); aclTypeMap.put("ipproto", IPPROTO_INVALID); aclTypeMap.put("icmp", new Integer(IPv4.PROTOCOL_ICMP)); aclTypeMap.put("udp", new Integer(IPv4.PROTOCOL_UDP)); aclTypeMap.put("tcp", new Integer(IPv4.PROTOCOL_TCP)); aclTypeMap.put("mac", IPPROTO_INVALID); } protected static Logger logger = LoggerFactory.getLogger(VNSAccessControlListEntry.class); protected VNSAccessControlList parentACL; protected int seqNo; protected String aclType; // must be one of NetVirtAclEntryType protected String action; protected VNSAclMatchResult aclResult; protected int ipproto; protected int srcIp, srcIpMask, dstIp, dstIpMask; protected int srcTpPort, dstTpPort; protected String srcTpPortOp, dstTpPortOp; protected short icmpType; protected byte[] srcMac, dstMac; protected int etherType; protected short vlan; /** * Static method for transport level port comparison */ public static boolean tpPortEq(int port1, int port2) { return port1 == port2; } public static boolean tpPortNeq(int port1, int port2) { return port1 != port2; } /** * @param parentACL the parent of the ACLEntry */ public VNSAccessControlListEntry(int seqNo, VNSAccessControlList parentACL) { super(); this.seqNo = seqNo; this.parentACL = parentACL; } public VNSAccessControlList getParentACL() { return parentACL; } public void setParentACL(VNSAccessControlList parentACL) { if (parentACL == null) throw new NullPointerException("parentACL cannot be null"); this.parentACL = parentACL; } public int getSeqNo() { return this.seqNo; } public void setSeqNo(int seqNo) { this.seqNo = seqNo; } public String getAction() { return this.action; } public void setAction(String action) throws Exception { if (actionMap.containsKey(action)) { this.action = action; this.aclResult = actionMap.get(action); return; } throw new Exception("Invalid acl action" + action); } public String getType() { return aclType; } public void setType(String type) throws Exception { aclType = type; if (aclTypeMap.containsKey(type)) { aclType = type; ipproto = aclTypeMap.get(type); } else { ipproto = Integer.parseInt(type); if (ipproto <= IPPROTO_INVALID || ipproto >= IPPROTO_ALL) { throw new Exception("Invalid IPPROTO " + type); } aclType = "ipproto"; } } public int getSrcIp() { return srcIp; } public void setSrcIp(String srcIp) { if (srcIp == null) return; this.srcIp = IPv4.toIPv4Address(srcIp); } public int getSrcIpMask() { return srcIpMask; } public void setSrcIpMask(String srcIpMask) { if (srcIpMask == null) { this.srcIpMask = -1; return; } this.srcIpMask = IPv4.toIPv4Address(srcIpMask); } public int getDstIp() { return dstIp; } public void setDstIp(String dstIp) { if (dstIp == null) return; this.dstIp = IPv4.toIPv4Address(dstIp); } public int getDstIpMask() { return dstIpMask; } public void setDstIpMask(String dstIpMask) { if (dstIpMask == null) { this.dstIpMask = -1; return; } this.dstIpMask = IPv4.toIPv4Address(dstIpMask); } public int getSrcTpPort() { return this.srcTpPort; } public void setSrcTpPort(int srcTpPort) throws Exception { if (srcTpPort < 0 || srcTpPort > 65535) { throw new Exception("Invalid source transport port number " + srcTpPort); } this.srcTpPort = srcTpPort; } public int getDstTpPort() { return this.dstTpPort; } public void setDstTpPort(int dstTpPort) throws Exception { if (dstTpPort < 0 || dstTpPort > 65535) { throw new Exception("Invalid destination transport port number " + srcTpPort); } this.dstTpPort = dstTpPort; } public String getSrcTpPortOp() { return this.srcTpPortOp; } public void setSrcTpPortOp(String op) throws Exception { if (op == null) { this.srcTpPortOp = "any"; return; } if ("eq".equals(op) || "neq".equals(op) || "any".equals(op)) { this.srcTpPortOp = op; } else { throw new Exception("Invalid source transport port op " + op); } } public String getDstTpPortOp() { return this.dstTpPortOp; } public void setDstTpPortOp(String op) throws Exception { if (op == null) { this.srcTpPortOp = "any"; return; } if ("eq".equals(op) || "neq".equals(op) || "any".equals(op)) { this.dstTpPortOp = op; } else { throw new Exception("Invalid destination transport op " + op); } } public void setIcmpType(int icmpType) throws Exception { if (icmpType < 0 || icmpType > ICMPTYPE_ALL) { throw new Exception("Invalid ICMP type " + icmpType); } this.icmpType = (short) icmpType; } public byte[] getSrcMac() { return this.srcMac; } public void setSrcMac(String srcMac) { if (srcMac == null || srcMac.isEmpty()) this.srcMac = null; else this.srcMac = HexString.fromHexString(srcMac); } public byte[] getDstMac() { return this.dstMac; } public void setDstMac(String dstMac) { if (dstMac == null || dstMac.isEmpty()) this.dstMac = null; else this.dstMac = HexString.fromHexString(dstMac); } public void setEtherType(int etherType) throws Exception { if (etherType < 0 || etherType > ETHERTYPE_ALL) { throw new Exception("Invalid ether type " + etherType); } this.etherType = etherType; } public int getVlan() { return this.vlan; } public void setVlan(int vlan) throws Exception { if (vlan < 0 || vlan > VLAN_ALL) { throw new Exception("Invalid vlan value " + vlan); } this.vlan = (short) vlan; } /** * Returns IPv4 if src/dst/proto matches, else return null */ private IPv4 getMatchedIPv4(Ethernet eth, MutableInteger wildcards) { wildcards.setValue(wildcards.intValue() & ~OFMatch.OFPFW_DL_TYPE); if (eth.getPayload() instanceof IPv4) { IPv4 ipv4 = (IPv4) eth.getPayload(); // First, check for protocol match if (IPPROTO_ALL != ipproto) { wildcards.setValue(wildcards.intValue() & ~OFMatch.OFPFW_NW_PROTO); if (ipv4.getProtocol() != ipproto) return null; } // Check for src/dst address match int srcAddr = ipv4.getSourceAddress(); int dstAddr = ipv4.getDestinationAddress(); Boolean match = ((srcAddr & ~srcIpMask) == (srcIp & ~srcIpMask) && (dstAddr & ~dstIpMask) == (dstIp & ~dstIpMask)); // Figure out the widest possible address wildcard mask int srcWildcardBits = 32; int dstWildcardBits = 32; if (!match) { if ((srcAddr & ~srcIpMask) != (srcIp & ~srcIpMask)) { srcWildcardBits = getWildcardBits(srcAddr, srcIp, srcIpMask, false); } else { dstWildcardBits = getWildcardBits(dstAddr, dstIp, dstIpMask, false); } } else { srcWildcardBits = getWildcardBits(srcAddr, srcIp, srcIpMask, true); dstWildcardBits = getWildcardBits(dstAddr, dstIp, dstIpMask, true); } adjustIpWildcards(srcWildcardBits, dstWildcardBits, wildcards); return match ? ipv4 : null; } return null; } private int getWildcardBits(int addr, int ip, int ipMask, boolean match) { int maskBits = 0; int tmp = 1; // count the least significant contiguous bits while ((ipMask & tmp) != 0) { maskBits++; tmp <<= 1; } if (match) { // can only match as wide as the mask return maskBits; } // keep increasing mask until we get a match do { ipMask |= (1 << maskBits); maskBits++; } while ((addr & ~ipMask) != (ip & ~ipMask)); return maskBits - 1; } private void adjustIpWildcards(int srcWildcardBits, int dstWildcardBits, MutableInteger wildcards) { if (srcWildcardBits < 32) { int oldBits = (wildcards.intValue() & OFMatch.OFPFW_NW_SRC_MASK) >> OFMatch.OFPFW_NW_SRC_SHIFT; if (srcWildcardBits < oldBits) { wildcards.setValue((wildcards.intValue() & ~OFMatch.OFPFW_NW_SRC_MASK) | (srcWildcardBits << OFMatch.OFPFW_NW_SRC_SHIFT)); } } if (dstWildcardBits < 32) { int oldBits = (wildcards.intValue() & OFMatch.OFPFW_NW_DST_MASK) >> OFMatch.OFPFW_NW_DST_SHIFT; if (dstWildcardBits < oldBits) { wildcards.setValue((wildcards.intValue() & ~(OFMatch.OFPFW_NW_DST_MASK)) | (dstWildcardBits << OFMatch.OFPFW_NW_DST_SHIFT)); } } } /** * Match on IP addresses only */ public boolean match_ip (Ethernet eth, IDevice srcDev, IDevice dstDev, MutableInteger wildcards) { return getMatchedIPv4(eth, wildcards) != null; } /** * Match on ip addresses and protocol number */ public boolean match_ipproto (Ethernet eth, IDevice srcDev, IDevice dstDev, MutableInteger wildcards) { return getMatchedIPv4(eth, wildcards) != null; } /** * Compares tcp/udp ports in packet against acl specs */ private boolean matchTpPort(String op, int port1, int port2, MutableInteger wildcards, int tpFlag) { if ("eq".equals(op)) { wildcards.setValue(wildcards.intValue() & ~tpFlag); return port1 == port2; } else if ("neq".equals(op)) { wildcards.setValue(wildcards.intValue() & ~tpFlag); return port1 != port2; } else { // any return true; } } /** * Match on udp ports and ip addresses */ public boolean match_udp (Ethernet eth, IDevice srcDev, IDevice dstDev, MutableInteger wildcards) { IPv4 ipv4 = getMatchedIPv4(eth, wildcards); if (ipv4 != null) { assert(ipv4.getPayload() instanceof UDP); UDP udp = (UDP) ipv4.getPayload(); return matchTpPort(srcTpPortOp, srcTpPort, udp.getSourcePort(), wildcards, OFMatch.OFPFW_TP_SRC) && matchTpPort(dstTpPortOp, dstTpPort, udp.getDestinationPort(), wildcards, OFMatch.OFPFW_TP_DST); } return false; } /** * Match on tcp ports and ip addresses */ public boolean match_tcp (Ethernet eth, IDevice srcDev, IDevice dstDev, MutableInteger wildcards) { IPv4 ipv4 = getMatchedIPv4(eth, wildcards); if (ipv4 != null) { assert(ipv4.getPayload() instanceof TCP); TCP tcp = (TCP) ipv4.getPayload(); return matchTpPort(srcTpPortOp, srcTpPort, tcp.getSourcePort(), wildcards, OFMatch.OFPFW_TP_SRC) && matchTpPort(dstTpPortOp, dstTpPort, tcp.getDestinationPort(), wildcards, OFMatch.OFPFW_TP_DST); } return false; } /** * Match on ip addresses and ICMP type */ public boolean match_icmp (Ethernet eth, IDevice srcDev, IDevice dstDev, MutableInteger wildcards) { IPv4 ipv4 = getMatchedIPv4(eth, wildcards); if (ipv4 != null) { // protocol already matched assert(ipv4.getPayload() instanceof ICMP); ICMP icmp = (ICMP) ipv4.getPayload(); if (ICMPTYPE_ALL == icmpType) return true; // Openflow spec uses TP_SRC to specify icmp type wildcards.setValue(wildcards.intValue() & ~OFMatch.OFPFW_TP_SRC); return icmp.getIcmpType() == icmpType; } return false; } /* * Match configured Vlan tag in the ACL if any, with all the vlan tags * associated with the device. * * XXX Filtering vlan tags through ACLs would impact address-space * separation, as we solely vlan tags at the moment to achieve this * separation. */ private boolean matchVlan (IDevice dev) { if (dev != null) { /* * Iterate through all associated vlan tags for this device and * find if there is a match. */ for (Short devVlan : dev.getVlanId()) { if (devVlan != null && devVlan.equals(vlan)) return true; } } /* * No vlan match was found in this acl entry. */ return false; } /** * Match on Ethernet addresses and type */ public boolean match_mac (Ethernet eth, IDevice srcDev, IDevice dstDev, MutableInteger wildcards) { /* * Unicast : (srcDev == null) => DENY * (dstDev == null) => PERMIT, so that the device learning * process can be triggered * * Non-Unicast: (srcDev == null) => DENY * (dstDev == null) => Match against the packet * */ /* * Check if source mac address has been configured in this acl entry. */ if (srcMac != null) { /* * If there is no source device, we should essentially drop the * packet. */ if (srcDev == null) return false ; /* * Compare the source device's MAC address with the source mac * address configured in this ACL entry. * * byte[].equals() doesn't compare content, a Java feature. Use * Arrays.equals() instead. */ if (!Arrays.equals(Ethernet.toByteArray(srcDev.getMACAddress()), srcMac)) { return false ; } } /* * Check if the destination mac address has been configured in this * acl entry. */ if (dstMac != null) { byte[] tmpMac; /* * If we do know about the destination device, compare its MAC * address with what has been configured in this ACL entry. */ if (dstDev != null) { tmpMac = Ethernet.toByteArray(dstDev.getMACAddress()); } else { /* * For Unicast packets, if there is no destination device * present, we [force] return a match so that the destination * entity device can be properly learnt asap. */ if (!eth.isMulticast() && !eth.isBroadcast()) return true; /* * Use packet's destination mac address instead of the device's. * XXX Need to be re-addressed for multicast rewrite in future. */ tmpMac = eth.getDestinationMACAddress(); } /* * We now have the proper MAC address to use. Compare it against * what has been configured in this ACL entry. */ if (!Arrays.equals(tmpMac, dstMac)) return false; } /* * Check if vlan tag has been configured in this ACL entry. */ if (VLAN_ALL != vlan) { /* * Update the wildcards bit mask, with the fact that VLAN tag field * is not a wild card, by resetting the OFPFW_DL_VLAN bit. */ wildcards.setValue(wildcards.intValue() & ~OFMatch.OFPFW_DL_VLAN); /* * Match configured vlan tag with all the vlan tags associated with * the source and destination device. */ if (!matchVlan(srcDev) && !matchVlan(dstDev)) { /* * XXX Should we go ahead and match against whats in the pkt ? */ if (eth.getVlanID() != vlan) { return false; } } } /* * Check if etherType has been configured in this ACL entry. */ if (ETHERTYPE_ALL != etherType) { /* * Note down that etherType match is no longer a wild card. */ wildcards.setValue(wildcards.intValue() & ~OFMatch.OFPFW_DL_TYPE); /* * If the packet's etherType does not match with whats been * configured, then this acl entry does not match with this packet. */ if (eth.getEtherType() != etherType) return false; } /* * All configured mac related attributes if any match correctly * with the received packet or its associated devices. */ return true; } /** * Match this ACL entry against Ethernet packet eth. */ @LogMessageDoc(level="ERROR", message="Failed to invoke ACL match", explanation="Failed to match flow against ACL", recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) public VNSAclMatchResult matchAcl (Ethernet eth, IDevice srcDev, IDevice dstDev, MutableInteger wildcards) { try { Method matchm = this.getClass() .getMethod("match_" + aclType, new Class[] {eth.getClass(), IDevice.class, IDevice.class, MutableInteger.class}); if ((Boolean) matchm.invoke(this, eth, srcDev, dstDev, wildcards)) { return aclResult; } } catch (Exception e) { logger.error("Failed to invoke ACL match", e); } return VNSAclMatchResult.ACL_NO_MATCH; } @Override public boolean equals(Object arg0) { if (arg0 instanceof VNSAccessControlListEntry) { return (this.seqNo == ((VNSAccessControlListEntry)arg0).seqNo && this.parentACL.equals(((VNSAccessControlListEntry)arg0).parentACL)); } return super.equals(arg0); } @Override public int hashCode() { return (this.seqNo + "|" + this.parentACL.getName()).hashCode(); } @Override public int compareTo(VNSAccessControlListEntry o) { // higher priority is sorted first if (parentACL.equals(o.parentACL)) { return (new Integer(o.seqNo)).compareTo(seqNo); } else { return (parentACL.compareTo(o.parentACL)); } } private String toString_IpMask(int ip, int mask) { if (mask == -1) return " any"; return " " + IPv4.fromIPv4Address(ip) + " " + IPv4.fromIPv4Address(mask); } private String toString_tpPort(String op, int port) { if ("any".equals(op)) return " any"; return " " + op + " " + port; } public String toString_ip() { return aclType + toString_IpMask(srcIp, srcIpMask) + toString_IpMask(dstIp, dstIpMask); } public String toString_ipproto() { return Integer.toString(ipproto) + toString_IpMask(srcIp, srcIpMask) + toString_IpMask(dstIp, dstIpMask); } private String toString_tcp_udp() { return aclType + toString_IpMask(srcIp, srcIpMask) + toString_tpPort(srcTpPortOp, srcTpPort) + toString_IpMask(dstIp, dstIpMask) + toString_tpPort(dstTpPortOp, dstTpPort); } public String toString_udp() { return toString_tcp_udp(); } public String toString_tcp() { return toString_tcp_udp(); } public String toString_icmp() { return aclType + toString_IpMask(srcIp, srcIpMask) + toString_IpMask(dstIp, dstIpMask) + " " + Integer.toString(icmpType); } public String toString_mac() { return aclType + " " + (srcMac == null ? "any" : HexString.toHexString(srcMac)) + " " + (dstMac == null ? "any" : HexString.toHexString(dstMac)) + " " + etherType + " " + vlan; } @Override public String toString(){ StringBuffer sb = new StringBuffer(); sb.append("ACL: " + parentACL.getName()); sb.append(" seqNo: " + seqNo); sb.append(" " + action + " "); try { Method matchm = this.getClass().getMethod("toString_" + aclType); sb.append((String)matchm.invoke(this)); } catch (Exception e) { sb.append(e.toString()); } return sb.toString(); } }