http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/net/NetUtils.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/net/NetUtils.java b/utils/src/main/java/com/cloud/utils/net/NetUtils.java new file mode 100644 index 0000000..883129b --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/net/NetUtils.java @@ -0,0 +1,1550 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 com.cloud.utils.net; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.InterfaceAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.URI; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Formatter; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.SortedSet; +import java.util.StringTokenizer; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang.SystemUtils; +import org.apache.commons.net.util.SubnetUtils; +import org.apache.commons.validator.routines.InetAddressValidator; +import org.apache.log4j.Logger; + +import com.cloud.utils.IteratorUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.script.Script; +import com.googlecode.ipv6.IPv6Address; +import com.googlecode.ipv6.IPv6AddressRange; +import com.googlecode.ipv6.IPv6Network; + +public class NetUtils { + protected final static Logger s_logger = Logger.getLogger(NetUtils.class); + + private static final int MAX_CIDR = 32; + private static final int RFC_3021_31_BIT_CIDR = 31; + + public final static String HTTP_PORT = "80"; + public final static String HTTPS_PORT = "443"; + public final static int VPN_PORT = 500; + public final static int VPN_NATT_PORT = 4500; + public final static int VPN_L2TP_PORT = 1701; + public final static int HAPROXY_STATS_PORT = 8081; + + public final static String UDP_PROTO = "udp"; + public final static String TCP_PROTO = "tcp"; + public final static String ANY_PROTO = "any"; + public final static String ICMP_PROTO = "icmp"; + public final static String ALL_PROTO = "all"; + public final static String HTTP_PROTO = "http"; + public final static String SSL_PROTO = "ssl"; + + public final static String ALL_CIDRS = "0.0.0.0/0"; + public final static int PORT_RANGE_MIN = 0; + public final static int PORT_RANGE_MAX = 65535; + + public final static int DEFAULT_AUTOSCALE_VM_DESTROY_TIME = 2 * 60; // Grace period before Vm is destroyed + public final static int DEFAULT_AUTOSCALE_POLICY_INTERVAL_TIME = 30; + public final static int DEFAULT_AUTOSCALE_POLICY_QUIET_TIME = 5 * 60; + private final static Random s_rand = new Random(System.currentTimeMillis()); + + public static long createSequenceBasedMacAddress(final long macAddress) { + return macAddress | 0x060000000000l | (long)s_rand.nextInt(32768) << 25 & 0x00fffe000000l; + } + + public static String getHostName() { + try { + final InetAddress localAddr = InetAddress.getLocalHost(); + if (localAddr != null) { + return localAddr.getHostName(); + } + } catch (final UnknownHostException e) { + s_logger.warn("UnknownHostException when trying to get host name. ", e); + } + return "localhost"; + } + + public static InetAddress getLocalInetAddress() { + try { + return InetAddress.getLocalHost(); + } catch (final UnknownHostException e) { + s_logger.warn("UnknownHostException in getLocalInetAddress().", e); + return null; + } + } + + public static String resolveToIp(final String host) { + try { + final InetAddress addr = InetAddress.getByName(host); + return ipFromInetAddress(addr); + } catch (final UnknownHostException e) { + s_logger.warn("Unable to resolve " + host + " to IP due to UnknownHostException"); + return null; + } + } + + public static InetAddress[] getAllLocalInetAddresses() { + final List<InetAddress> addrList = new ArrayList<InetAddress>(); + try { + for (final NetworkInterface ifc : IteratorUtil.enumerationAsIterable(NetworkInterface.getNetworkInterfaces())) { + if (ifc.isUp() && !ifc.isVirtual()) { + for (final InetAddress addr : IteratorUtil.enumerationAsIterable(ifc.getInetAddresses())) { + addrList.add(addr); + } + } + } + } catch (final SocketException e) { + s_logger.warn("SocketException in getAllLocalInetAddresses().", e); + } + + final InetAddress[] addrs = new InetAddress[addrList.size()]; + if (addrList.size() > 0) { + System.arraycopy(addrList.toArray(), 0, addrs, 0, addrList.size()); + } + return addrs; + } + + public static String[] getLocalCidrs() { + final String defaultHostIp = getDefaultHostIp(); + + final List<String> cidrList = new ArrayList<String>(); + try { + for (final NetworkInterface ifc : IteratorUtil.enumerationAsIterable(NetworkInterface.getNetworkInterfaces())) { + if (ifc.isUp() && !ifc.isVirtual() && !ifc.isLoopback()) { + for (final InterfaceAddress address : ifc.getInterfaceAddresses()) { + final InetAddress addr = address.getAddress(); + final int prefixLength = address.getNetworkPrefixLength(); + if (prefixLength < MAX_CIDR && prefixLength > 0) { + final String ip = ipFromInetAddress(addr); + if (ip.equalsIgnoreCase(defaultHostIp)) { + cidrList.add(ipAndNetMaskToCidr(ip, getCidrNetmask(prefixLength))); + } + } + } + } + } + } catch (final SocketException e) { + s_logger.warn("UnknownHostException in getLocalCidrs().", e); + } + + return cidrList.toArray(new String[0]); + } + + public static String getDefaultHostIp() { + if (SystemUtils.IS_OS_WINDOWS) { + final Pattern pattern = Pattern.compile("\\s*0.0.0.0\\s*0.0.0.0\\s*(\\S*)\\s*(\\S*)\\s*"); + try { + final Process result = Runtime.getRuntime().exec("route print -4"); + final BufferedReader output = new BufferedReader(new InputStreamReader(result.getInputStream())); + + String line = output.readLine(); + while (line != null) { + final Matcher matcher = pattern.matcher(line); + if (matcher.find()) { + return matcher.group(2); + } + line = output.readLine(); + } + } catch (final IOException e) { + s_logger.debug("Caught IOException", e); + } + return null; + } else { + NetworkInterface nic = null; + final String pubNic = getDefaultEthDevice(); + + if (pubNic == null) { + return null; + } + + try { + nic = NetworkInterface.getByName(pubNic); + } catch (final SocketException e) { + return null; + } + + String[] info = null; + try { + info = NetUtils.getNetworkParams(nic); + } catch (final NullPointerException ignored) { + s_logger.debug("Caught NullPointerException when trying to getDefaultHostIp"); + } + if (info != null) { + return info[0]; + } + return null; + } + } + + public static String getDefaultEthDevice() { + if (SystemUtils.IS_OS_MAC) { + final String defDev = Script.runSimpleBashScript("/sbin/route -n get default 2> /dev/null | grep interface | awk '{print $2}'"); + return defDev; + } + final String defaultRoute = Script.runSimpleBashScript("/sbin/route | grep default"); + + if (defaultRoute == null) { + return null; + } + + final String[] defaultRouteList = defaultRoute.split("\\s+"); + + if (defaultRouteList.length != 8) { + return null; + } + + return defaultRouteList[7]; + } + + public static InetAddress getFirstNonLoopbackLocalInetAddress() { + final InetAddress[] addrs = getAllLocalInetAddresses(); + if (addrs != null) { + for (final InetAddress addr : addrs) { + if (s_logger.isInfoEnabled()) { + s_logger.info("Check local InetAddress : " + addr.toString() + ", total count :" + addrs.length); + } + + if (!addr.isLoopbackAddress()) { + return addr; + } + } + } + + s_logger.warn("Unable to determine a non-loopback address, local inet address count :" + addrs.length); + return null; + } + + public static InetAddress[] getInterfaceInetAddresses(final String ifName) { + final List<InetAddress> addrList = new ArrayList<InetAddress>(); + try { + for (final NetworkInterface ifc : IteratorUtil.enumerationAsIterable(NetworkInterface.getNetworkInterfaces())) { + if (ifc.isUp() && !ifc.isVirtual() && ifc.getName().equals(ifName)) { + for (final InetAddress addr : IteratorUtil.enumerationAsIterable(ifc.getInetAddresses())) { + addrList.add(addr); + } + } + } + } catch (final SocketException e) { + s_logger.warn("SocketException in getAllLocalInetAddresses().", e); + } + + final InetAddress[] addrs = new InetAddress[addrList.size()]; + if (addrList.size() > 0) { + System.arraycopy(addrList.toArray(), 0, addrs, 0, addrList.size()); + } + return addrs; + } + + public static String getLocalIPString() { + final InetAddress addr = getLocalInetAddress(); + if (addr != null) { + return ipFromInetAddress(addr); + } + + return "127.0.0.1"; + } + + public static String ipFromInetAddress(final InetAddress addr) { + assert addr != null; + + final byte[] ipBytes = addr.getAddress(); + final StringBuffer sb = new StringBuffer(); + sb.append(ipBytes[0] & 0xff).append("."); + sb.append(ipBytes[1] & 0xff).append("."); + sb.append(ipBytes[2] & 0xff).append("."); + sb.append(ipBytes[3] & 0xff); + + return sb.toString(); + } + + public static boolean isLocalAddress(final InetAddress addr) { + final InetAddress[] addrs = getAllLocalInetAddresses(); + + if (addrs != null) { + for (final InetAddress self : addrs) { + if (self.equals(addr)) { + return true; + } + } + } + return false; + } + + public static boolean isLocalAddress(final String strAddress) { + + InetAddress addr; + try { + addr = InetAddress.getByName(strAddress); + return isLocalAddress(addr); + } catch (final UnknownHostException e) { + } + return false; + } + + public static String getMacAddress(final InetAddress address) { + final StringBuffer sb = new StringBuffer(); + final Formatter formatter = new Formatter(sb); + try { + final NetworkInterface ni = NetworkInterface.getByInetAddress(address); + final byte[] mac = ni.getHardwareAddress(); + + for (int i = 0; i < mac.length; i++) { + formatter.format("%02X%s", mac[i], i < mac.length - 1 ? ":" : ""); + } + } catch (final SocketException e) { + s_logger.error("SocketException when trying to retrieve MAC address", e); + } finally { + formatter.close(); + } + return sb.toString(); + } + + public static long getMacAddressAsLong(final InetAddress address) { + long macAddressAsLong = 0; + try { + final NetworkInterface ni = NetworkInterface.getByInetAddress(address); + final byte[] mac = ni.getHardwareAddress(); + + for (int i = 0; i < mac.length; i++) { + macAddressAsLong |= (long)(mac[i] & 0xff) << (mac.length - i - 1) * 8; + } + + } catch (final SocketException e) { + s_logger.error("SocketException when trying to retrieve MAC address", e); + } + + return macAddressAsLong; + } + + /** + * This method will fail in case we have a 31 Bit prefix network + * See RFC 3021. + * + * In order to avoid calling this method, please check the <code>NetUtils.is31PrefixCidr(cidr)</code> first. + */ + public static boolean ipRangesOverlap(final String startIp1, final String endIp1, final String startIp2, final String endIp2) { + final long startIp1Long = ip2Long(startIp1); + long endIp1Long = startIp1Long; + if (endIp1 != null) { + endIp1Long = ip2Long(endIp1); + } + final long startIp2Long = ip2Long(startIp2); + long endIp2Long = startIp2Long; + if (endIp2 != null) { + endIp2Long = ip2Long(endIp2); + } + + if (startIp1Long == startIp2Long || startIp1Long == endIp2Long || endIp1Long == startIp2Long || endIp1Long == endIp2Long) { + return true; + } else if (startIp1Long > startIp2Long && startIp1Long < endIp2Long) { + return true; + } else if (endIp1Long > startIp2Long && endIp1Long < endIp2Long) { + return true; + } else if (startIp2Long > startIp1Long && startIp2Long < endIp1Long) { + return true; + } else if (endIp2Long > startIp1Long && endIp2Long < endIp1Long) { + return true; + } else { + return false; + } + } + + public static long ip2Long(final String ip) { + final String[] tokens = ip.split("[.]"); + assert tokens.length == 4; + long result = 0; + for (int i = 0; i < tokens.length; i++) { + try { + result = result << 8 | Integer.parseInt(tokens[i]); + } catch (final NumberFormatException e) { + throw new RuntimeException("Incorrect number", e); + } + } + + return result; + } + + public static String long2Ip(final long ip) { + final StringBuilder result = new StringBuilder(15); + result.append(ip >> 24 & 0xff).append("."); + result.append(ip >> 16 & 0xff).append("."); + result.append(ip >> 8 & 0xff).append("."); + result.append(ip & 0xff); + + return result.toString(); + } + + public static long mac2Long(final String macAddress) { + final String[] tokens = macAddress.split(":"); + assert tokens.length == 6; + long result = 0; + for (int i = 0; i < tokens.length; i++) { + result = result << 8; + result |= Integer.parseInt(tokens[i], 16); + } + return result; + } + + public static String[] getNicParams(final String nicName) { + try { + final NetworkInterface nic = NetworkInterface.getByName(nicName); + return getNetworkParams(nic); + } catch (final SocketException e) { + return null; + } + } + + public static String[] getNetworkParams(final NetworkInterface nic) { + final List<InterfaceAddress> addrs = nic.getInterfaceAddresses(); + if (addrs == null || addrs.size() == 0) { + return null; + } + InterfaceAddress addr = null; + for (final InterfaceAddress iaddr : addrs) { + final InetAddress inet = iaddr.getAddress(); + if (!inet.isLinkLocalAddress() && !inet.isLoopbackAddress() && !inet.isMulticastAddress() && inet.getAddress().length == 4) { + addr = iaddr; + break; + } + } + if (addr == null) { + return null; + } + final String[] result = new String[3]; + result[0] = addr.getAddress().getHostAddress(); + try { + final byte[] mac = nic.getHardwareAddress(); + result[1] = byte2Mac(mac); + } catch (final SocketException e) { + s_logger.debug("Caught exception when trying to get the mac address ", e); + } + + result[2] = prefix2Netmask(addr.getNetworkPrefixLength()); + return result; + } + + public static String prefix2Netmask(final short prefix) { + long addr = 0; + for (int i = 0; i < prefix; i++) { + addr = addr | 1 << 31 - i; + } + + return long2Ip(addr); + } + + public static String byte2Mac(final byte[] m) { + final StringBuilder result = new StringBuilder(17); + final Formatter formatter = new Formatter(result); + formatter.format("%02x:%02x:%02x:%02x:%02x:%02x", m[0], m[1], m[2], m[3], m[4], m[5]); + formatter.close(); + return result.toString(); + } + + public static String long2Mac(final long macAddress) { + final StringBuilder result = new StringBuilder(17); + try (Formatter formatter = new Formatter(result)) { + formatter.format("%02x:%02x:%02x:%02x:%02x:%02x", + macAddress >> 40 & 0xff, macAddress >> 32 & 0xff, + macAddress >> 24 & 0xff, macAddress >> 16 & 0xff, + macAddress >> 8 & 0xff, macAddress & 0xff); + } + return result.toString(); + } + + public static boolean isValidPrivateIp(final String ipAddress, final String guestIPAddress) { + + final InetAddress privIp = parseIpAddress(ipAddress); + if (privIp == null) { + return false; + } + if (!privIp.isSiteLocalAddress()) { + return false; + } + + String firstGuestOctet = "10"; + if (guestIPAddress != null && !guestIPAddress.isEmpty()) { + final String[] guestIPList = guestIPAddress.split("\\."); + firstGuestOctet = guestIPList[0]; + } + + final String[] ipList = ipAddress.split("\\."); + if (!ipList[0].equals(firstGuestOctet)) { + return false; + } + + return true; + } + + public static boolean isSiteLocalAddress(final String ipAddress) { + if (ipAddress == null) { + return false; + } else { + final InetAddress ip = parseIpAddress(ipAddress); + if(ip != null) { + return ip.isSiteLocalAddress(); + } + return false; + } + } + + public static boolean validIpRange(final String startIP, final String endIP) { + if (endIP == null || endIP.isEmpty()) { + return true; + } + + final long startIPLong = NetUtils.ip2Long(startIP); + final long endIPLong = NetUtils.ip2Long(endIP); + return startIPLong <= endIPLong; + } + + public static boolean isValidIp(final String ip) { + final InetAddressValidator validator = InetAddressValidator.getInstance(); + + return validator.isValidInet4Address(ip); + } + + public static boolean is31PrefixCidr(final String cidr) { + final boolean isValidCird = isValidCIDR(cidr); + if (isValidCird){ + final String[] cidrPair = cidr.split("\\/"); + final String cidrSize = cidrPair[1]; + + final int cidrSizeNum = Integer.parseInt(cidrSize); + if (cidrSizeNum == RFC_3021_31_BIT_CIDR) { + return true; + } + } + return false; + } + + public static boolean isValidCIDR(final String cidr) { + if (cidr == null || cidr.isEmpty()) { + return false; + } + final String[] cidrPair = cidr.split("\\/"); + if (cidrPair.length != 2) { + return false; + } + final String cidrAddress = cidrPair[0]; + final String cidrSize = cidrPair[1]; + if (!isValidIp(cidrAddress)) { + return false; + } + int cidrSizeNum = -1; + + try { + cidrSizeNum = Integer.parseInt(cidrSize); + } catch (final Exception e) { + return false; + } + + if (cidrSizeNum < 0 || cidrSizeNum > MAX_CIDR) { + return false; + } + + return true; + } + + public static boolean isValidNetmask(final String netmask) { + if (!isValidIp(netmask)) { + return false; + } + + final long ip = ip2Long(netmask); + int count = 0; + boolean finished = false; + for (int i = 31; i >= 0; i--) { + if ((ip >> i & 0x1) == 0) { + finished = true; + } else { + if (finished) { + return false; + } + count += 1; + } + } + + if (count == 0) { + return false; + } + + return true; + } + + private static InetAddress parseIpAddress(final String address) { + final StringTokenizer st = new StringTokenizer(address, "."); + final byte[] bytes = new byte[4]; + + if (st.countTokens() == 4) { + try { + for (int i = 0; i < 4; i++) { + bytes[i] = (byte)Integer.parseInt(st.nextToken()); + } + return InetAddress.getByAddress(address, bytes); + } catch (final NumberFormatException nfe) { + return null; + } catch (final UnknownHostException uhe) { + return null; + } + } + return null; + } + + public static String getCidrFromGatewayAndNetmask(final String gatewayStr, final String netmaskStr) { + final long netmask = ip2Long(netmaskStr); + final long gateway = ip2Long(gatewayStr); + final long firstPart = gateway & netmask; + final long size = getCidrSize(netmaskStr); + return long2Ip(firstPart) + "/" + size; + } + + public static String[] getIpRangeFromCidr(final String cidr, final long size) { + assert size < MAX_CIDR : "You do know this is not for ipv6 right? Keep it smaller than 32 but you have " + size; + final String[] result = new String[2]; + final long ip = ip2Long(cidr); + final long startNetMask = ip2Long(getCidrNetmask(size)); + final long start = (ip & startNetMask) + 1; + long end = start; + + end = end >> MAX_CIDR - size; + + end++; + end = (end << MAX_CIDR - size) - 2; + + result[0] = long2Ip(start); + result[1] = long2Ip(end); + + return result; + } + + public static Set<Long> getAllIpsFromCidr(final String cidr, final long size, final Set<Long> usedIps) { + assert size < MAX_CIDR : "You do know this is not for ipv6 right? Keep it smaller than 32 but you have " + size; + final Set<Long> result = new TreeSet<Long>(); + final long ip = ip2Long(cidr); + final long startNetMask = ip2Long(getCidrNetmask(size)); + long start = (ip & startNetMask) + 1; + long end = start; + + end = end >> MAX_CIDR - size; + + end++; + end = (end << MAX_CIDR - size) - 2; + int maxIps = 255; // get 255 ips as maximum + while (start <= end && maxIps > 0) { + if (!usedIps.contains(start)) { + result.add(start); + maxIps--; + } + start++; + } + + return result; + } + + /** + * Given a cidr, this method returns an ip address within the range but + * is not in the avoid list. + * + * @param startIp ip that the cidr starts with + * @param size size of the cidr + * @param avoid set of ips to avoid + * @return ip that is within the cidr range but not in the avoid set. -1 if unable to find one. + */ + public static long getRandomIpFromCidr(final String startIp, final int size, final SortedSet<Long> avoid) { + return getRandomIpFromCidr(ip2Long(startIp), size, avoid); + + } + + /** + * Given a cidr, this method returns an ip address within the range but + * is not in the avoid list. + * Note: the gateway address has to be specified in the avoid list + * + * @param cidr ip that the cidr starts with + * @param size size of the cidr + * @param avoid set of ips to avoid + * @return ip that is within the cidr range but not in the avoid set. -1 if unable to find one. + */ + public static long getRandomIpFromCidr(final long cidr, final int size, final SortedSet<Long> avoid) { + assert size < MAX_CIDR : "You do know this is not for ipv6 right? Keep it smaller than 32 but you have " + size; + + final long startNetMask = ip2Long(getCidrNetmask(size)); + final long startIp = (cidr & startNetMask) + 1; //exclude the first ip since it isnt valid, e.g., 192.168.10.0 + int range = 1 << MAX_CIDR - size; //e.g., /24 = 2^8 = 256 + range = range - 1; //exclude end of the range since that is the broadcast address, e.g., 192.168.10.255 + + if (avoid.size() >= range) { + return -1; + } + + //Reduce the range by the size of the avoid set + //e.g., cidr = 192.168.10.0, size = /24, avoid = 192.168.10.1, 192.168.10.20, 192.168.10.254 + // range = 2^8 - 1 - 3 = 252 + range = range - avoid.size(); + final int next = s_rand.nextInt(range); //note: nextInt excludes last value + long ip = startIp + next; + for (final Long avoidable : avoid) { + if (ip >= avoidable) { + ip++; + } else { + break; + } + } + + return ip; + } + + public static String getIpRangeStartIpFromCidr(final String cidr, final long size) { + final long ip = ip2Long(cidr); + final long startNetMask = ip2Long(getCidrNetmask(size)); + final long start = (ip & startNetMask) + 1; + return long2Ip(start); + } + + public static String getIpRangeEndIpFromCidr(final String cidr, final long size) { + final long ip = ip2Long(cidr); + final long startNetMask = ip2Long(getCidrNetmask(size)); + final long start = (ip & startNetMask) + 1; + long end = start; + end = end >> MAX_CIDR - size; + + end++; + end = (end << MAX_CIDR - size) - 2; + return long2Ip(end); + } + + public static boolean sameSubnet(final String ip1, final String ip2, final String netmask) { + if (ip1 == null || ip1.isEmpty() || ip2 == null || ip2.isEmpty()) { + return true; + } + final String subnet1 = NetUtils.getSubNet(ip1, netmask); + final String subnet2 = NetUtils.getSubNet(ip2, netmask); + + return subnet1.equals(subnet2); + } + + public static boolean sameSubnetCIDR(final String ip1, final String ip2, final long cidrSize) { + if (ip1 == null || ip1.isEmpty() || ip2 == null || ip2.isEmpty()) { + return true; + } + final String subnet1 = NetUtils.getCidrSubNet(ip1, cidrSize); + final String subnet2 = NetUtils.getCidrSubNet(ip2, cidrSize); + + return subnet1.equals(subnet2); + } + + public static String getSubNet(final String ip, final String netmask) { + final long ipAddr = ip2Long(ip); + final long subnet = ip2Long(netmask); + final long result = ipAddr & subnet; + return long2Ip(result); + } + + public static String getCidrSubNet(final String ip, final long cidrSize) { + final long numericNetmask = 0xffffffff >> MAX_CIDR - cidrSize << MAX_CIDR - cidrSize; + final String netmask = NetUtils.long2Ip(numericNetmask); + return getSubNet(ip, netmask); + } + + public static String ipAndNetMaskToCidr(final String ip, final String netmask) { + if (!isValidIp(ip)) { + return null; + } + + if (!isValidNetmask(netmask)) { + return null; + } + + final long ipAddr = ip2Long(ip); + final long subnet = ip2Long(netmask); + final long result = ipAddr & subnet; + int bits = subnet == 0 ? 0 : 1; + long subnet2 = subnet; + while ((subnet2 = subnet2 >> 1 & subnet) != 0) { + bits++; + } + + return long2Ip(result) + "/" + Integer.toString(bits); + } + + public static String[] ipAndNetMaskToRange(final String ip, final String netmask) { + final long ipAddr = ip2Long(ip); + long subnet = ip2Long(netmask); + final long start = (ipAddr & subnet) + 1; + long end = start; + int bits = subnet == 0 ? 0 : 1; + while ((subnet = subnet >> 1 & subnet) != 0) { + bits++; + } + end = end >> MAX_CIDR - bits; + + end++; + end = (end << MAX_CIDR - bits) - 2; + + return new String[] {long2Ip(start), long2Ip(end)}; + + } + + public static Pair<String, Integer> getCidr(final String cidr) { + final String[] tokens = cidr.split("/"); + return new Pair<String, Integer>(tokens[0], Integer.parseInt(tokens[1])); + } + + public static enum SupersetOrSubset { + isSuperset, isSubset, neitherSubetNorSuperset, sameSubnet, errorInCidrFormat + } + + public static SupersetOrSubset isNetowrkASubsetOrSupersetOfNetworkB(final String cidrA, final String cidrB) { + final Long[] cidrALong = cidrToLong(cidrA); + final Long[] cidrBLong = cidrToLong(cidrB); + long shift = 0; + if (cidrALong == null || cidrBLong == null) { + //implies error in the cidr format + return SupersetOrSubset.errorInCidrFormat; + } + if (cidrALong[1] >= cidrBLong[1]) { + shift = MAX_CIDR - cidrBLong[1]; + } else { + shift = MAX_CIDR - cidrALong[1]; + } + final long result = (cidrALong[0] >> shift) - (cidrBLong[0] >> shift); + if (result == 0) { + if (cidrALong[1] < cidrBLong[1]) { + //this implies cidrA is super set of cidrB + return SupersetOrSubset.isSuperset; + } else if (cidrALong[1].equals(cidrBLong[1])) { + //this implies both the cidrs are equal + return SupersetOrSubset.sameSubnet; + } + // implies cidrA is subset of cidrB + return SupersetOrSubset.isSubset; + } + //this implies no overlap. + return SupersetOrSubset.neitherSubetNorSuperset; + } + + public static boolean isNetworkAWithinNetworkB(final String cidrA, final String cidrB) { + final Long[] cidrALong = cidrToLong(cidrA); + final Long[] cidrBLong = cidrToLong(cidrB); + if (cidrALong == null || cidrBLong == null) { + return false; + } + final long shift = MAX_CIDR - cidrBLong[1]; + return cidrALong[0] >> shift == cidrBLong[0] >> shift; + } + + public static Long[] cidrToLong(final String cidr) { + if (cidr == null || cidr.isEmpty()) { + return null; + } + final String[] cidrPair = cidr.split("\\/"); + if (cidrPair.length != 2) { + return null; + } + final String cidrAddress = cidrPair[0]; + final String cidrSize = cidrPair[1]; + if (!isValidIp(cidrAddress)) { + return null; + } + int cidrSizeNum = -1; + + try { + cidrSizeNum = Integer.parseInt(cidrSize); + } catch (final Exception e) { + return null; + } + final long numericNetmask = 0xffffffff >> MAX_CIDR - cidrSizeNum << MAX_CIDR - cidrSizeNum; + final long ipAddr = ip2Long(cidrAddress); + final Long[] cidrlong = {ipAddr & numericNetmask, (long)cidrSizeNum}; + return cidrlong; + + } + + public static String getCidrSubNet(final String cidr) { + if (cidr == null || cidr.isEmpty()) { + return null; + } + final String[] cidrPair = cidr.split("\\/"); + if (cidrPair.length != 2) { + return null; + } + final String cidrAddress = cidrPair[0]; + final String cidrSize = cidrPair[1]; + if (!isValidIp(cidrAddress)) { + return null; + } + int cidrSizeNum = -1; + + try { + cidrSizeNum = Integer.parseInt(cidrSize); + } catch (final Exception e) { + return null; + } + final long numericNetmask = 0xffffffff >> MAX_CIDR - cidrSizeNum << MAX_CIDR - cidrSizeNum; + final String netmask = NetUtils.long2Ip(numericNetmask); + return getSubNet(cidrAddress, netmask); + } + + public static String getCidrNetmask(final long cidrSize) { + final long numericNetmask = 0xffffffff >> MAX_CIDR - cidrSize << MAX_CIDR - cidrSize; + return long2Ip(numericNetmask); + } + + public static String getCidrNetmask(final String cidr) { + final String[] cidrPair = cidr.split("\\/"); + final long guestCidrSize = Long.parseLong(cidrPair[1]); + return getCidrNetmask(guestCidrSize); + } + + public static String cidr2Netmask(final String cidr) { + final String[] tokens = cidr.split("\\/"); + return getCidrNetmask(Integer.parseInt(tokens[1])); + } + + public static long getCidrSize(final String netmask) { + final long ip = ip2Long(netmask); + int count = 0; + for (int i = 0; i < MAX_CIDR; i++) { + if ((ip >> i & 0x1) == 0) { + count++; + } else { + break; + } + } + + return MAX_CIDR - count; + } + + public static boolean isValidPort(final String p) { + try { + final int port = Integer.parseInt(p); + return !(port > 65535 || port < 1); + } catch (final NumberFormatException e) { + return false; + } + } + + public static boolean isValidPort(final int p) { + return !(p > 65535 || p < 1); + } + + public static boolean isValidLBPort(final String p) { + try { + final int port = Integer.parseInt(p); + return !(port > 65535 || port < 1); + } catch (final NumberFormatException e) { + return false; + } + } + + public static boolean isValidProto(final String p) { + final String proto = p.toLowerCase(); + return proto.equals(TCP_PROTO) || proto.equals(UDP_PROTO) || proto.equals(ICMP_PROTO); + } + + public static boolean isValidSecurityGroupProto(final String p) { + final String proto = p.toLowerCase(); + return proto.equals(TCP_PROTO) || proto.equals(UDP_PROTO) || proto.equals(ICMP_PROTO) || proto.equals(ALL_PROTO); + } + + public static boolean isValidAlgorithm(final String p) { + final String algo = p.toLowerCase(); + return algo.equals("roundrobin") || algo.equals("leastconn") || algo.equals("source"); + } + + public static boolean isValidAutoScaleAction(final String p) { + final String action = p.toLowerCase(); + return action.equals("scaleup") || action.equals("scaledown"); + } + + public static String getLinkLocalNetMask() { + return "255.255.0.0"; + } + + public static String getLinkLocalGateway() { + return "169.254.0.1"; + } + + public static String getLinkLocalCIDR() { + return "169.254.0.0/16"; + } + + public static String[] getLinkLocalIPRange(final int size) { + if (size > 16 || size <= 0) { + return null; + } + /* reserve gateway */ + final String[] range = getIpRangeFromCidr(getLinkLocalGateway(), MAX_CIDR - size); + + if (range[0].equalsIgnoreCase(getLinkLocalGateway())) { + /* remove the gateway */ + long ip = ip2Long(range[0]); + ip += 1; + range[0] = long2Ip(ip); + } + return range; + } + + public static String getLinkLocalIpEnd() { + final String[] cidrPair = getLinkLocalCIDR().split("\\/"); + final String cidr = cidrPair[0]; + + return getIpRangeEndIpFromCidr(cidr, MAX_CIDR - Long.parseLong(cidrPair[1])); + } + + public static String portRangeToString(final int portRange[]) { + return Integer.toString(portRange[0]) + ":" + Integer.toString(portRange[1]); + } + + public static boolean verifyDomainNameLabel(final String hostName, final boolean isHostName) { + // must be between 1 and 63 characters long and may contain only the ASCII letters 'a' through 'z' (in a + // case-insensitive manner), + // the digits '0' through '9', and the hyphen ('-'). + // Can not start with a hyphen and digit, and must not end with a hyphen + // If it's a host name, don't allow to start with digit + + if (hostName.length() > 63 || hostName.length() < 1) { + s_logger.warn("Domain name label must be between 1 and 63 characters long"); + return false; + } else if (!hostName.toLowerCase().matches("[a-z0-9-]*")) { + s_logger.warn("Domain name label may contain only the ASCII letters 'a' through 'z' (in a case-insensitive manner)"); + return false; + } else if (hostName.startsWith("-") || hostName.endsWith("-")) { + s_logger.warn("Domain name label can not start with a hyphen and digit, and must not end with a hyphen"); + return false; + } else if (isHostName && hostName.matches("^[0-9-].*")) { + s_logger.warn("Host name can't start with digit"); + return false; + } + + return true; + } + + public static boolean verifyDomainName(final String domainName) { + // don't allow domain name length to exceed 190 chars (190 + 63 (max host name length) = 253 = max domainName length + if (domainName.length() < 1 || domainName.length() > 190) { + s_logger.trace("Domain name must be between 1 and 190 characters long"); + return false; + } + + if (domainName.startsWith(".") || domainName.endsWith(".")) { + s_logger.trace("Domain name can't start or end with ."); + return false; + } + + final String[] domainNameLabels = domainName.split("\\."); + + for (int i = 0; i < domainNameLabels.length; i++) { + if (!verifyDomainNameLabel(domainNameLabels[i], false)) { + s_logger.warn("Domain name label " + domainNameLabels[i] + " is incorrect"); + return false; + } + } + + return true; + } + + public static String getDhcpRange(final String cidr) { + final String[] splitResult = cidr.split("\\/"); + final long size = Long.parseLong(splitResult[1]); + return NetUtils.getIpRangeStartIpFromCidr(splitResult[0], size); + } + + // Check if 2 CIDRs have exactly same IP Range + public static boolean isSameIpRange(final String cidrA, final String cidrB) { + + if (!NetUtils.isValidCIDR(cidrA)) { + s_logger.info("Invalid value of cidr " + cidrA); + return false; + } + if (!NetUtils.isValidCIDR(cidrB)) { + s_logger.info("Invalid value of cidr " + cidrB); + return false; + } + final String[] cidrPairFirst = cidrA.split("\\/"); + final String[] cidrPairSecond = cidrB.split("\\/"); + + final Long networkSizeFirst = Long.valueOf(cidrPairFirst[1]); + final Long networkSizeSecond = Long.valueOf(cidrPairSecond[1]); + final String ipRangeFirst[] = NetUtils.getIpRangeFromCidr(cidrPairFirst[0], networkSizeFirst); + final String ipRangeSecond[] = NetUtils.getIpRangeFromCidr(cidrPairFirst[0], networkSizeSecond); + + final long startIpFirst = NetUtils.ip2Long(ipRangeFirst[0]); + final long endIpFirst = NetUtils.ip2Long(ipRangeFirst[1]); + final long startIpSecond = NetUtils.ip2Long(ipRangeSecond[0]); + final long endIpSecond = NetUtils.ip2Long(ipRangeSecond[1]); + if (startIpFirst == startIpSecond && endIpFirst == endIpSecond) { + return true; + } + return false; + } + + public static boolean validateGuestCidr(final String cidr) { + // RFC 1918 - The Internet Assigned Numbers Authority (IANA) has reserved the + // following three blocks of the IP address space for private internets: + // 10.0.0.0 - 10.255.255.255 (10/8 prefix) + // 172.16.0.0 - 172.31.255.255 (172.16/12 prefix) + // 192.168.0.0 - 192.168.255.255 (192.168/16 prefix) + + final String cidr1 = "10.0.0.0/8"; + final String cidr2 = "172.16.0.0/12"; + final String cidr3 = "192.168.0.0/16"; + + if (!isValidCIDR(cidr)) { + s_logger.warn("Cidr " + cidr + " is not valid"); + return false; + } + + if (isNetworkAWithinNetworkB(cidr, cidr1) || isNetworkAWithinNetworkB(cidr, cidr2) || isNetworkAWithinNetworkB(cidr, cidr3)) { + return true; + } else { + s_logger.warn("cidr " + cidr + " is not RFC 1918 compliant"); + return false; + } + } + + public static boolean verifyInstanceName(final String instanceName) { + //instance name for cloudstack vms shouldn't contain - and spaces + if (instanceName.contains("-") || instanceName.contains(" ") || instanceName.contains("+")) { + s_logger.warn("Instance name can not contain hyphen, spaces and \"+\" char"); + return false; + } + + return true; + } + + public static boolean isNetworksOverlap(final String cidrA, final String cidrB) { + final Long[] cidrALong = cidrToLong(cidrA); + final Long[] cidrBLong = cidrToLong(cidrB); + if (cidrALong == null || cidrBLong == null) { + return false; + } + final long shift = MAX_CIDR - (cidrALong[1] > cidrBLong[1] ? cidrBLong[1] : cidrALong[1]); + return cidrALong[0] >> shift == cidrBLong[0] >> shift; + } + + public static boolean isValidS2SVpnPolicy(final String policys) { + if (policys == null || policys.isEmpty()) { + return false; + } + for (final String policy : policys.split(",")) { + if (policy.isEmpty()) { + return false; + } + final String cipherHash = policy.split(";")[0]; + if (cipherHash.isEmpty()) { + return false; + } + final String[] list = cipherHash.split("-"); + if (list.length != 2) { + return false; + } + final String cipher = list[0]; + final String hash = list[1]; + if (!cipher.matches("3des|aes128|aes192|aes256")) { + return false; + } + if (!hash.matches("md5|sha1")) { + return false; + } + String pfsGroup = null; + if (!policy.equals(cipherHash)) { + pfsGroup = policy.split(";")[1]; + } + if (pfsGroup != null && !pfsGroup.matches("modp1024|modp1536")) { + return false; + } + } + return true; + } + + public static boolean isValidCidrList(final String cidrList) { + for (final String guestCidr : cidrList.split(",")) { + if (!isValidCIDR(guestCidr)) { + return false; + } + } + return true; + } + + public static boolean validateGuestCidrList(final String guestCidrList) { + for (final String guestCidr : guestCidrList.split(",")) { + if (!validateGuestCidr(guestCidr)) { + return false; + } + } + return true; + } + + public static boolean validateIcmpType(final long icmpType) { + //Source - http://www.erg.abdn.ac.uk/~gorry/course/inet-pages/icmp-code.html + if (!(icmpType >= 0 && icmpType <= 255)) { + s_logger.warn("impcType is not within 0-255 range"); + return false; + } + return true; + } + + public static boolean validateIcmpCode(final long icmpCode) { + + //Source - http://www.erg.abdn.ac.uk/~gorry/course/inet-pages/icmp-code.html + if (!(icmpCode >= 0 && icmpCode <= 15)) { + s_logger.warn("Icmp code should be within 0-15 range"); + return false; + } + + return true; + } + + public static boolean isValidIpv6(final String ip) { + try { + IPv6Address.fromString(ip); + } catch (final IllegalArgumentException ex) { + return false; + } + return true; + } + + public static boolean isValidIp6Cidr(final String ip6Cidr) { + try { + IPv6Network.fromString(ip6Cidr); + } catch (final IllegalArgumentException ex) { + return false; + } + return true; + } + + public static int getIp6CidrSize(final String ip6Cidr) { + IPv6Network network = null; + try { + network = IPv6Network.fromString(ip6Cidr); + } catch (final IllegalArgumentException ex) { + return 0; + } + return network.getNetmask().asPrefixLength(); + } + + // Can cover 127 bits + public static String getIp6FromRange(final String ip6Range) { + final String[] ips = ip6Range.split("-"); + final String startIp = ips[0]; + final IPv6Address start = IPv6Address.fromString(startIp); + final BigInteger gap = countIp6InRange(ip6Range); + BigInteger next = new BigInteger(gap.bitLength(), s_rand); + while (next.compareTo(gap) >= 0) { + next = new BigInteger(gap.bitLength(), s_rand); + } + InetAddress resultAddr = null; + final BigInteger startInt = convertIPv6AddressToBigInteger(start); + if (startInt != null) { + final BigInteger resultInt = startInt.add(next); + try { + resultAddr = InetAddress.getByAddress(resultInt.toByteArray()); + } catch (final UnknownHostException e) { + return null; + } + } + if( resultAddr != null) { + final IPv6Address ip = IPv6Address.fromInetAddress(resultAddr); + return ip.toString(); + } + return null; + } + + //RFC3315, section 9.4 + public static String getDuidLL(final String macAddress) { + final String duid = "00:03:00:01:" + macAddress; + return duid; + } + + private static BigInteger convertIPv6AddressToBigInteger(final IPv6Address addr) { + InetAddress inetAddr; + try { + inetAddr = addr.toInetAddress(); + } catch (final UnknownHostException e) { + return null; + } + return new BigInteger(inetAddr.getAddress()); + } + + // Can cover 127 bits + public static BigInteger countIp6InRange(final String ip6Range) { + if (ip6Range == null) { + return null; + } + final String[] ips = ip6Range.split("-"); + final String startIp = ips[0]; + String endIp = ips[0]; + if (ips.length > 1) { + endIp = ips[1]; + } + try { + final BigInteger startInt = convertIPv6AddressToBigInteger(IPv6Address.fromString(startIp)); + final BigInteger endInt = convertIPv6AddressToBigInteger(IPv6Address.fromString(endIp)); + if (endInt != null && startInt != null && startInt.compareTo(endInt) <= 0) { + return endInt.subtract(startInt).add(BigInteger.ONE); + } + } catch (final IllegalArgumentException ex) { + s_logger.error("Failed to convert a string to an IPv6 address", ex); + } + return null; + } + + public static boolean isIp6InRange(final String ip6, final String ip6Range) { + if (ip6Range == null) { + return false; + } + final String[] ips = ip6Range.split("-"); + final String startIp = ips[0]; + String endIp = null; + if (ips.length > 1) { + endIp = ips[1]; + } + final IPv6Address start = IPv6Address.fromString(startIp); + final IPv6Address end = IPv6Address.fromString(endIp); + final IPv6Address ip = IPv6Address.fromString(ip6); + if (start.compareTo(ip) <= 0 && end.compareTo(ip) >= 0) { + return true; + } + return false; + } + + public static boolean isIp6InNetwork(final String ip6, final String ip6Cidr) { + IPv6Network network = null; + try { + network = IPv6Network.fromString(ip6Cidr); + } catch (final IllegalArgumentException ex) { + return false; + } + final IPv6Address ip = IPv6Address.fromString(ip6); + return network.contains(ip); + } + + public static boolean isIp6RangeOverlap(final String ipRange1, final String ipRange2) { + String[] ips = ipRange1.split("-"); + final String startIp1 = ips[0]; + String endIp1 = null; + if (ips.length > 1) { + endIp1 = ips[1]; + } + final IPv6Address start1 = IPv6Address.fromString(startIp1); + final IPv6Address end1 = IPv6Address.fromString(endIp1); + final IPv6AddressRange range1 = IPv6AddressRange.fromFirstAndLast(start1, end1); + ips = ipRange2.split("-"); + final String startIp2 = ips[0]; + String endIp2 = null; + if (ips.length > 1) { + endIp2 = ips[1]; + } + final IPv6Address start2 = IPv6Address.fromString(startIp2); + final IPv6Address end2 = IPv6Address.fromString(endIp2); + final IPv6AddressRange range2 = IPv6AddressRange.fromFirstAndLast(start2, end2); + return range1.overlaps(range2); + } + + public static String getNextIp6InRange(final String currentIp, final String ipRange) { + final String[] ips = ipRange.split("-"); + final String startIp = ips[0]; + String endIp = null; + if (ips.length > 1) { + endIp = ips[1]; + } + final IPv6Address start = IPv6Address.fromString(startIp); + final IPv6Address end = IPv6Address.fromString(endIp); + final IPv6Address current = IPv6Address.fromString(currentIp); + IPv6Address result = null; + if (current.equals(end)) { + result = start; + } else { + result = current.add(1); + } + String resultIp = null; + if (result != null) { + resultIp = result.toString(); + } + return resultIp; + } + + public static String standardizeIp6Address(final String ip6Addr) { + try { + return IPv6Address.fromString(ip6Addr).toString(); + } catch (final IllegalArgumentException ex) { + throw new IllegalArgumentException("Invalid IPv6 address: " + ex.getMessage()); + } + } + + public static String standardizeIp6Cidr(final String ip6Cidr){ + try { + return IPv6Network.fromString(ip6Cidr).toString(); + } catch (final IllegalArgumentException ex) { + throw new IllegalArgumentException("Invalid IPv6 CIDR: " + ex.getMessage()); + } + } + + static final String VLAN_PREFIX = "vlan://"; + static final int VLAN_PREFIX_LENGTH = VLAN_PREFIX.length(); + + public static boolean isValidVlan(String vlan) { + if (null == vlan || "".equals(vlan)) { + return false; + } + if (vlan.startsWith(VLAN_PREFIX)) { + vlan = vlan.substring(VLAN_PREFIX_LENGTH); + } + try { + final int vnet = Integer.parseInt(vlan); + if (vnet <= 0 || vnet >= 4095) { // the valid range is 1- 4094 + return false; + } + return true; + } catch (final NumberFormatException e) { + return false; + } + } + + static final String VLAN_UNTAGGED = "untagged"; + + public static boolean isSameIsolationId(String one, String other) { + // check nulls + // check empty strings + if ((one == null || one.isEmpty()) && (other == null || other.isEmpty())) { + return true; + } + if (one == null || other == null) { + return false; + } + // check 'untagged' + if (one.contains(VLAN_UNTAGGED) && other.contains(VLAN_UNTAGGED)) { + return true; + } + // if one is a number check the other as number and as 'vlan://' + number + if (one.startsWith(VLAN_PREFIX)) { + one = one.substring(VLAN_PREFIX_LENGTH); + } + if (other.startsWith(VLAN_PREFIX)) { + other = other.substring(VLAN_PREFIX_LENGTH); + } + // check valid uris or numbers + if (one.equalsIgnoreCase(other)) { + return true; + } + + return false; + } + + // Attention maintainers: these pvlan functions should take into account code + // in Networks.BroadcastDomainType, where URI construction is done for other + // types of BroadcastDomainTypes + public static URI generateUriForPvlan(final String primaryVlan, final String isolatedPvlan) { + return URI.create("pvlan://" + primaryVlan + "-i" + isolatedPvlan); + } + + public static String getPrimaryPvlanFromUri(final URI uri) { + final String[] vlans = uri.getHost().split("-"); + if (vlans.length < 1) { + return null; + } + return vlans[0]; + } + + public static String getIsolatedPvlanFromUri(final URI uri) { + final String[] vlans = uri.getHost().split("-"); + if (vlans.length < 2) { + return null; + } + for (final String vlan : vlans) { + if (vlan.startsWith("i")) { + return vlan.replace("i", " ").trim(); + } + } + return null; + } + + public static String generateMacOnIncrease(final String baseMac, final long l) { + long mac = mac2Long(baseMac); + if (l > 0xFFFFl) { + return null; + } + mac = mac + (l << 24); + mac = mac & 0x06FFFFFFFFFFl; + return long2Mac(mac); + } + + public static boolean isIpWithtInCidrRange(final String ipAddress, final String cidr) { + if (!isValidIp(ipAddress)) { + return false; + } + if (!isValidCIDR(cidr)) { + return false; + } + + // check if the gatewayip is the part of the ip range being added. + // RFC 3021 - 31-Bit Prefixes on IPv4 Point-to-Point Links + // GW Netmask Stat IP End IP + // 192.168.24.0 - 255.255.255.254 - 192.168.24.0 - 192.168.24.1 + // https://tools.ietf.org/html/rfc3021 + // Added by Wilder Rodrigues + final SubnetUtils subnetUtils = new SubnetUtils(cidr); + subnetUtils.setInclusiveHostCount(true); + + final boolean isInRange = subnetUtils.getInfo().isInRange(ipAddress); + + return isInRange; + } + + public static Boolean IsIpEqualToNetworkOrBroadCastIp(final String requestedIp, final String cidr, final long size) { + assert size < MAX_CIDR : "You do know this is not for ipv6 right? Keep it smaller than 32 but you have " + size; + + final long ip = ip2Long(cidr); + final long startNetMask = ip2Long(getCidrNetmask(size)); + + final long start = ip & startNetMask; + long end = start; + + end = end >> MAX_CIDR - size; + + end++; + end = (end << MAX_CIDR - size) - 1; + + final long reqIp = ip2Long(requestedIp); + if (reqIp == start || reqIp == end) { + return true; + } + return false; + } + +}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/net/NfsUtils.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/net/NfsUtils.java b/utils/src/main/java/com/cloud/utils/net/NfsUtils.java new file mode 100644 index 0000000..23c1007 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/net/NfsUtils.java @@ -0,0 +1,55 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 com.cloud.utils.net; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; + +public class NfsUtils { + + public static String url2Mount(String urlStr) throws URISyntaxException { + URI url; + url = new URI(urlStr); + return url.getHost() + ":" + url.getPath(); + } + + public static String uri2Mount(URI uri) { + return uri.getHost() + ":" + uri.getPath(); + } + + public static String url2PathSafeString(String urlStr) { + String safe = urlStr.replace(File.separatorChar, '-'); + safe = safe.replace("?", ""); + safe = safe.replace("*", ""); + safe = safe.replace("\\", ""); + safe = safe.replace("/", ""); + return safe; + } + + public static String getHostPart(String nfsPath) { + String toks[] = nfsPath.split(":"); + if (toks != null && toks.length == 2) { + return toks[0]; + } + return null; + } + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/net/UrlUtil.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/net/UrlUtil.java b/utils/src/main/java/com/cloud/utils/net/UrlUtil.java new file mode 100644 index 0000000..539486e --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/net/UrlUtil.java @@ -0,0 +1,63 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 com.cloud.utils.net; + +import java.net.URI; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +public class UrlUtil { + public final static Map<String, String> parseQueryParameters(String query) { + HashMap<String, String> values = new HashMap<String, String>(); + parseQueryParameters(query, false, values); + + return values; + } + + public final static Map<String, String> parseQueryParameters(URL url) { + return parseQueryParameters(url.getQuery()); + } + + public final static Map<String, String> parseQueryParameters(URI url) { + return parseQueryParameters(url.getQuery()); + } + + public final static void parseQueryParameters(String query, boolean lowercaseKeys, Map<String, String> params) { + if (query == null) { + return; + } + + if (query.startsWith("?")) { + query = query.substring(1); + } + + String[] parts = query.split("&"); + for (String part : parts) { + String[] tokens = part.split("="); + + if (lowercaseKeys) { + tokens[0] = tokens[0].toLowerCase(); + } + + params.put(tokens[0], tokens[1]); + } + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/nio/HandlerFactory.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/nio/HandlerFactory.java b/utils/src/main/java/com/cloud/utils/nio/HandlerFactory.java new file mode 100644 index 0000000..f6b9c82 --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/nio/HandlerFactory.java @@ -0,0 +1,28 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 com.cloud.utils.nio; + +/** + * WorkerFactory creates and selects workers. + */ +public interface HandlerFactory { + public Task create(Task.Type type, Link link, byte[] data); + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/83fd8f60/utils/src/main/java/com/cloud/utils/nio/Link.java ---------------------------------------------------------------------- diff --git a/utils/src/main/java/com/cloud/utils/nio/Link.java b/utils/src/main/java/com/cloud/utils/nio/Link.java new file mode 100644 index 0000000..6d6306a --- /dev/null +++ b/utils/src/main/java/com/cloud/utils/nio/Link.java @@ -0,0 +1,567 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 com.cloud.utils.nio; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.util.concurrent.ConcurrentLinkedQueue; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; + +import org.apache.cloudstack.utils.security.SSLUtils; +import org.apache.log4j.Logger; + +import com.cloud.utils.PropertiesUtil; +import com.cloud.utils.db.DbProperties; + +/** + */ +public class Link { + private static final Logger s_logger = Logger.getLogger(Link.class); + + private final InetSocketAddress _addr; + private final NioConnection _connection; + private SelectionKey _key; + private final ConcurrentLinkedQueue<ByteBuffer[]> _writeQueue; + private ByteBuffer _readBuffer; + private ByteBuffer _plaintextBuffer; + private Object _attach; + private boolean _readHeader; + private boolean _gotFollowingPacket; + + private SSLEngine _sslEngine; + public static final String keystoreFile = "/cloudmanagementserver.keystore"; + + public Link(InetSocketAddress addr, NioConnection connection) { + _addr = addr; + _connection = connection; + _readBuffer = ByteBuffer.allocate(2048); + _attach = null; + _key = null; + _writeQueue = new ConcurrentLinkedQueue<ByteBuffer[]>(); + _readHeader = true; + _gotFollowingPacket = false; + } + + public Link(Link link) { + this(link._addr, link._connection); + } + + public Object attachment() { + return _attach; + } + + public void attach(Object attach) { + _attach = attach; + } + + public void setKey(SelectionKey key) { + synchronized (this) { + _key = key; + } + } + + public void setSSLEngine(SSLEngine sslEngine) { + _sslEngine = sslEngine; + } + + /** + * No user, so comment it out. + * + * Static methods for reading from a channel in case + * you need to add a client that doesn't require nio. + * @param ch channel to read from. + * @param bytebuffer to use. + * @return bytes read + * @throws IOException if not read to completion. + public static byte[] read(SocketChannel ch, ByteBuffer buff) throws IOException { + synchronized(buff) { + buff.clear(); + buff.limit(4); + + while (buff.hasRemaining()) { + if (ch.read(buff) == -1) { + throw new IOException("Connection closed with -1 on reading size."); + } + } + + buff.flip(); + + int length = buff.getInt(); + ByteArrayOutputStream output = new ByteArrayOutputStream(length); + WritableByteChannel outCh = Channels.newChannel(output); + + int count = 0; + while (count < length) { + buff.clear(); + int read = ch.read(buff); + if (read < 0) { + throw new IOException("Connection closed with -1 on reading data."); + } + count += read; + buff.flip(); + outCh.write(buff); + } + + return output.toByteArray(); + } + } + */ + + private static void doWrite(SocketChannel ch, ByteBuffer[] buffers, SSLEngine sslEngine) throws IOException { + SSLSession sslSession = sslEngine.getSession(); + ByteBuffer pkgBuf = ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); + SSLEngineResult engResult; + + ByteBuffer headBuf = ByteBuffer.allocate(4); + + int totalLen = 0; + for (ByteBuffer buffer : buffers) { + totalLen += buffer.limit(); + } + + int processedLen = 0; + while (processedLen < totalLen) { + headBuf.clear(); + pkgBuf.clear(); + engResult = sslEngine.wrap(buffers, pkgBuf); + if (engResult.getHandshakeStatus() != HandshakeStatus.FINISHED && engResult.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING && + engResult.getStatus() != SSLEngineResult.Status.OK) { + throw new IOException("SSL: SSLEngine return bad result! " + engResult); + } + + processedLen = 0; + for (ByteBuffer buffer : buffers) { + processedLen += buffer.position(); + } + + int dataRemaining = pkgBuf.position(); + int header = dataRemaining; + int headRemaining = 4; + pkgBuf.flip(); + if (processedLen < totalLen) { + header = header | HEADER_FLAG_FOLLOWING; + } + headBuf.putInt(header); + headBuf.flip(); + + while (headRemaining > 0) { + if (s_logger.isTraceEnabled()) { + s_logger.trace("Writing Header " + headRemaining); + } + long count = ch.write(headBuf); + headRemaining -= count; + } + while (dataRemaining > 0) { + if (s_logger.isTraceEnabled()) { + s_logger.trace("Writing Data " + dataRemaining); + } + long count = ch.write(pkgBuf); + dataRemaining -= count; + } + } + } + + /** + * write method to write to a socket. This method writes to completion so + * it doesn't follow the nio standard. We use this to make sure we write + * our own protocol. + * + * @param ch channel to write to. + * @param buffers buffers to write. + * @throws IOException if unable to write to completion. + */ + public static void write(SocketChannel ch, ByteBuffer[] buffers, SSLEngine sslEngine) throws IOException { + synchronized (ch) { + doWrite(ch, buffers, sslEngine); + } + } + + /* SSL has limitation of 16k, we may need to split packets. 18000 is 16k + some extra SSL informations */ + protected static final int MAX_SIZE_PER_PACKET = 18000; + protected static final int HEADER_FLAG_FOLLOWING = 0x10000; + + public byte[] read(SocketChannel ch) throws IOException { + if (_readHeader) { // Start of a packet + if (_readBuffer.position() == 0) { + _readBuffer.limit(4); + } + + if (ch.read(_readBuffer) == -1) { + throw new IOException("Connection closed with -1 on reading size."); + } + + if (_readBuffer.hasRemaining()) { + s_logger.trace("Need to read the rest of the packet length"); + return null; + } + _readBuffer.flip(); + int header = _readBuffer.getInt(); + int readSize = (short)header; + if (s_logger.isTraceEnabled()) { + s_logger.trace("Packet length is " + readSize); + } + + if (readSize > MAX_SIZE_PER_PACKET) { + throw new IOException("Wrong packet size: " + readSize); + } + + if (!_gotFollowingPacket) { + _plaintextBuffer = ByteBuffer.allocate(2000); + } + + if ((header & HEADER_FLAG_FOLLOWING) != 0) { + _gotFollowingPacket = true; + } else { + _gotFollowingPacket = false; + } + + _readBuffer.clear(); + _readHeader = false; + + if (_readBuffer.capacity() < readSize) { + if (s_logger.isTraceEnabled()) { + s_logger.trace("Resizing the byte buffer from " + _readBuffer.capacity()); + } + _readBuffer = ByteBuffer.allocate(readSize); + } + _readBuffer.limit(readSize); + } + + if (ch.read(_readBuffer) == -1) { + throw new IOException("Connection closed with -1 on read."); + } + + if (_readBuffer.hasRemaining()) { // We're not done yet. + if (s_logger.isTraceEnabled()) { + s_logger.trace("Still has " + _readBuffer.remaining()); + } + return null; + } + + _readBuffer.flip(); + + ByteBuffer appBuf; + + SSLSession sslSession = _sslEngine.getSession(); + SSLEngineResult engResult; + int remaining = 0; + + while (_readBuffer.hasRemaining()) { + remaining = _readBuffer.remaining(); + appBuf = ByteBuffer.allocate(sslSession.getApplicationBufferSize() + 40); + engResult = _sslEngine.unwrap(_readBuffer, appBuf); + if (engResult.getHandshakeStatus() != HandshakeStatus.FINISHED && engResult.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING && + engResult.getStatus() != SSLEngineResult.Status.OK) { + throw new IOException("SSL: SSLEngine return bad result! " + engResult); + } + if (remaining == _readBuffer.remaining()) { + throw new IOException("SSL: Unable to unwrap received data! still remaining " + remaining + "bytes!"); + } + + appBuf.flip(); + if (_plaintextBuffer.remaining() < appBuf.limit()) { + // We need to expand _plaintextBuffer for more data + ByteBuffer newBuffer = ByteBuffer.allocate(_plaintextBuffer.capacity() + appBuf.limit() * 5); + _plaintextBuffer.flip(); + newBuffer.put(_plaintextBuffer); + _plaintextBuffer = newBuffer; + } + _plaintextBuffer.put(appBuf); + if (s_logger.isTraceEnabled()) { + s_logger.trace("Done with packet: " + appBuf.limit()); + } + } + + _readBuffer.clear(); + _readHeader = true; + + if (!_gotFollowingPacket) { + _plaintextBuffer.flip(); + byte[] result = new byte[_plaintextBuffer.limit()]; + _plaintextBuffer.get(result); + return result; + } else { + if (s_logger.isTraceEnabled()) { + s_logger.trace("Waiting for more packets"); + } + return null; + } + } + + public void send(byte[] data) throws ClosedChannelException { + send(data, false); + } + + public void send(byte[] data, boolean close) throws ClosedChannelException { + send(new ByteBuffer[] {ByteBuffer.wrap(data)}, close); + } + + public void send(ByteBuffer[] data, boolean close) throws ClosedChannelException { + ByteBuffer[] item = new ByteBuffer[data.length + 1]; + int remaining = 0; + for (int i = 0; i < data.length; i++) { + remaining += data[i].remaining(); + item[i + 1] = data[i]; + } + + item[0] = ByteBuffer.allocate(4); + item[0].putInt(remaining); + item[0].flip(); + + if (s_logger.isTraceEnabled()) { + s_logger.trace("Sending packet of length " + remaining); + } + + _writeQueue.add(item); + if (close) { + _writeQueue.add(new ByteBuffer[0]); + } + synchronized (this) { + if (_key == null) { + throw new ClosedChannelException(); + } + _connection.change(SelectionKey.OP_WRITE, _key, null); + } + } + + public void send(ByteBuffer[] data) throws ClosedChannelException { + send(data, false); + } + + public synchronized void close() { + if (_key != null) { + _connection.close(_key); + } + } + + public boolean write(SocketChannel ch) throws IOException { + ByteBuffer[] data = null; + while ((data = _writeQueue.poll()) != null) { + if (data.length == 0) { + if (s_logger.isTraceEnabled()) { + s_logger.trace("Closing connection requested"); + } + return true; + } + + ByteBuffer[] raw_data = new ByteBuffer[data.length - 1]; + System.arraycopy(data, 1, raw_data, 0, data.length - 1); + + doWrite(ch, raw_data, _sslEngine); + } + return false; + } + + public InetSocketAddress getSocketAddress() { + return _addr; + } + + public String getIpAddress() { + return _addr.getAddress().toString(); + } + + public synchronized void terminated() { + _key = null; + } + + public synchronized void schedule(Task task) throws ClosedChannelException { + if (_key == null) { + throw new ClosedChannelException(); + } + _connection.scheduleTask(task); + } + + public static SSLContext initSSLContext(boolean isClient) throws GeneralSecurityException, IOException { + InputStream stream; + SSLContext sslContext = null; + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); + KeyStore ks = KeyStore.getInstance("JKS"); + TrustManager[] tms; + + File confFile = PropertiesUtil.findConfigFile("db.properties"); + if (null != confFile && !isClient) { + final String pass = DbProperties.getDbProperties().getProperty("db.cloud.keyStorePassphrase"); + char[] passphrase = "vmops.com".toCharArray(); + if (pass != null) { + passphrase = pass.toCharArray(); + } + String confPath = confFile.getParent(); + String keystorePath = confPath + keystoreFile; + if (new File(keystorePath).exists()) { + stream = new FileInputStream(keystorePath); + } else { + s_logger.warn("SSL: Fail to find the generated keystore. Loading fail-safe one to continue."); + stream = NioConnection.class.getResourceAsStream("/cloud.keystore"); + passphrase = "vmops.com".toCharArray(); + } + ks.load(stream, passphrase); + stream.close(); + kmf.init(ks, passphrase); + tmf.init(ks); + tms = tmf.getTrustManagers(); + } else { + ks.load(null, null); + kmf.init(ks, null); + tms = new TrustManager[1]; + tms[0] = new TrustAllManager(); + } + + sslContext = SSLUtils.getSSLContext(); + sslContext.init(kmf.getKeyManagers(), tms, null); + if (s_logger.isTraceEnabled()) { + s_logger.trace("SSL: SSLcontext has been initialized"); + } + + return sslContext; + } + + public static void doHandshake(SocketChannel ch, SSLEngine sslEngine, boolean isClient) throws IOException { + if (s_logger.isTraceEnabled()) { + s_logger.trace("SSL: begin Handshake, isClient: " + isClient); + } + + SSLEngineResult engResult; + SSLSession sslSession = sslEngine.getSession(); + HandshakeStatus hsStatus; + ByteBuffer in_pkgBuf = ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); + ByteBuffer in_appBuf = ByteBuffer.allocate(sslSession.getApplicationBufferSize() + 40); + ByteBuffer out_pkgBuf = ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); + ByteBuffer out_appBuf = ByteBuffer.allocate(sslSession.getApplicationBufferSize() + 40); + int count; + ch.socket().setSoTimeout(60 * 1000); + InputStream inStream = ch.socket().getInputStream(); + // Use readCh to make sure the timeout on reading is working + ReadableByteChannel readCh = Channels.newChannel(inStream); + + if (isClient) { + hsStatus = SSLEngineResult.HandshakeStatus.NEED_WRAP; + } else { + hsStatus = SSLEngineResult.HandshakeStatus.NEED_UNWRAP; + } + + while (hsStatus != SSLEngineResult.HandshakeStatus.FINISHED) { + if (s_logger.isTraceEnabled()) { + s_logger.trace("SSL: Handshake status " + hsStatus); + } + engResult = null; + if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_WRAP) { + out_pkgBuf.clear(); + out_appBuf.clear(); + out_appBuf.put("Hello".getBytes()); + engResult = sslEngine.wrap(out_appBuf, out_pkgBuf); + out_pkgBuf.flip(); + int remain = out_pkgBuf.limit(); + while (remain != 0) { + remain -= ch.write(out_pkgBuf); + if (remain < 0) { + throw new IOException("Too much bytes sent?"); + } + } + } else if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_UNWRAP) { + in_appBuf.clear(); + // One packet may contained multiply operation + if (in_pkgBuf.position() == 0 || !in_pkgBuf.hasRemaining()) { + in_pkgBuf.clear(); + count = 0; + try { + count = readCh.read(in_pkgBuf); + } catch (SocketTimeoutException ex) { + if (s_logger.isTraceEnabled()) { + s_logger.trace("Handshake reading time out! Cut the connection"); + } + count = -1; + } + if (count == -1) { + throw new IOException("Connection closed with -1 on reading size."); + } + in_pkgBuf.flip(); + } + engResult = sslEngine.unwrap(in_pkgBuf, in_appBuf); + ByteBuffer tmp_pkgBuf = ByteBuffer.allocate(sslSession.getPacketBufferSize() + 40); + int loop_count = 0; + while (engResult.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) { + // The client is too slow? Cut it and let it reconnect + if (loop_count > 10) { + throw new IOException("Too many times in SSL BUFFER_UNDERFLOW, disconnect guest."); + } + // We need more packets to complete this operation + if (s_logger.isTraceEnabled()) { + s_logger.trace("SSL: Buffer underflowed, getting more packets"); + } + tmp_pkgBuf.clear(); + count = ch.read(tmp_pkgBuf); + if (count == -1) { + throw new IOException("Connection closed with -1 on reading size."); + } + tmp_pkgBuf.flip(); + + in_pkgBuf.mark(); + in_pkgBuf.position(in_pkgBuf.limit()); + in_pkgBuf.limit(in_pkgBuf.limit() + tmp_pkgBuf.limit()); + in_pkgBuf.put(tmp_pkgBuf); + in_pkgBuf.reset(); + + in_appBuf.clear(); + engResult = sslEngine.unwrap(in_pkgBuf, in_appBuf); + loop_count++; + } + } else if (hsStatus == SSLEngineResult.HandshakeStatus.NEED_TASK) { + Runnable run; + while ((run = sslEngine.getDelegatedTask()) != null) { + if (s_logger.isTraceEnabled()) { + s_logger.trace("SSL: Running delegated task!"); + } + run.run(); + } + } else if (hsStatus == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) { + throw new IOException("NOT a handshaking!"); + } + if (engResult != null && engResult.getStatus() != SSLEngineResult.Status.OK) { + throw new IOException("Fail to handshake! " + engResult.getStatus()); + } + if (engResult != null) + hsStatus = engResult.getHandshakeStatus(); + else + hsStatus = sslEngine.getHandshakeStatus(); + } + } + +}