package org.zstack.utils.network; import org.apache.commons.lang.StringUtils; import org.apache.commons.net.util.SubnetUtils; import org.zstack.utils.DebugUtils; import org.zstack.utils.ShellResult; import org.zstack.utils.ShellUtils; import org.zstack.utils.Utils; import org.zstack.utils.data.Pair; import org.zstack.utils.logging.CLogger; import java.io.IOException; import java.net.*; import java.nio.ByteBuffer; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class NetworkUtils { private static final CLogger logger = Utils.getLogger(NetworkUtils.class); private static final Set<String> validNetmasks = new HashSet<String>(); static { validNetmasks.add("255.255.255.255"); validNetmasks.add("255.255.255.254"); validNetmasks.add("255.255.255.252"); validNetmasks.add("255.255.255.248"); validNetmasks.add("255.255.255.240"); validNetmasks.add("255.255.255.224"); validNetmasks.add("255.255.255.192"); validNetmasks.add("255.255.255.128"); validNetmasks.add("255.255.255.0"); validNetmasks.add("255.255.254.0"); validNetmasks.add("255.255.252.0"); validNetmasks.add("255.255.248.0"); validNetmasks.add("255.255.240.0"); validNetmasks.add("255.255.224.0"); validNetmasks.add("255.255.192.0"); validNetmasks.add("255.255.128.0"); validNetmasks.add("255.255.0.0"); validNetmasks.add("255.254.0.0"); validNetmasks.add("255.252.0.0"); validNetmasks.add("255.248.0.0"); validNetmasks.add("255.240.0.0"); validNetmasks.add("255.224.0.0"); validNetmasks.add("255.192.0.0"); validNetmasks.add("255.128.0.0"); validNetmasks.add("255.0.0.0"); validNetmasks.add("254.0.0.0"); validNetmasks.add("252.0.0.0"); validNetmasks.add("248.0.0.0"); validNetmasks.add("240.0.0.0"); validNetmasks.add("224.0.0.0"); validNetmasks.add("192.0.0.0"); validNetmasks.add("128.0.0.0"); validNetmasks.add("0.0.0.0"); } public static boolean isHostname(String hostname) { String PATTERN = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"; Pattern pattern = Pattern.compile(PATTERN); Matcher matcher = pattern.matcher(hostname); return matcher.matches(); } public static boolean isIpv4Address(String ip) { String PATTERN = "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"; Pattern pattern = Pattern.compile(PATTERN); Matcher matcher = pattern.matcher(ip); return matcher.matches(); } public static boolean isNetmask(String netmask) { return validNetmasks.contains(netmask); } public static boolean isNetmaskExcept(String netmask, String except) { return validNetmasks.contains(netmask) && !netmask.equals(except); } public static boolean isCidr(String cidr) { Pattern pattern = Pattern.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(\\d|[1-2]\\d|3[0-2]))$"); Matcher matcher = pattern.matcher(cidr); return matcher.find(); } public static long bytesToLong(byte[] bytes) { long value = 0; for (int i = 0; i < bytes.length; i++) { value = (value << 8) + (bytes[i] & 0xff); } return value; } public static long ipv4StringToLong(String ip) { validateIp(ip); try { InetAddress ia = InetAddress.getByName(ip); byte[] bytes = ia.getAddress(); return bytesToLong(bytes); } catch (UnknownHostException e) { throw new IllegalArgumentException(String.format("%s is not a valid ipv4 address", ip), e); } } public static boolean isIpv4InRange(String ip, String startIp, String endIp) { long ipl = ipv4StringToLong(ip); long startIpl = ipv4StringToLong(startIp); long endIpl = ipv4StringToLong(endIp); return (ipl <= endIpl && ipl >= startIpl); } public static String longToIpv4String(long ip) { byte[] bytes = ByteBuffer.allocate(4).putInt((int) ip).array(); try { InetAddress ia = InetAddress.getByAddress(bytes); return ia.getHostAddress(); } catch (UnknownHostException e) { throw new IllegalArgumentException(String.format("%s cannot convert to a valid ipv4 address", ip), e); } } public static int ipRangeLength(String startIp, String endIp) { validateIp(startIp); validateIp(endIp); return (int)(ipv4StringToLong(endIp) - ipv4StringToLong(startIp) + 1); } private static void validateIp(String ip) { if (!isIpv4Address(ip)) { throw new IllegalArgumentException(String.format("%s is not a valid ipv4 address", ip)); } } private static void validateIpRange(String startIp, String endIp) { validateIp(startIp); validateIp(endIp); long s = ipv4StringToLong(startIp); long e = ipv4StringToLong(endIp); if (s > e) { throw new IllegalArgumentException(String.format("[%s, %s] is not an invalid ip range, end ip must be greater than start ip", startIp, endIp)); } } public static boolean isIpv4RangeOverlap(String startIp1, String endIp1, String startIp2, String endIp2) { validateIpRange(startIp1, endIp1); validateIpRange(startIp2, endIp2); long s1 = ipv4StringToLong(startIp1); long e1 = ipv4StringToLong(endIp1); long s2 = ipv4StringToLong(startIp2); long e2 = ipv4StringToLong(endIp2); if ((s1 >= s2 && s1 <= e2) || (s1 <= s2 && s2 <= e1)) { return true; } return false; } public static boolean isConsecutiveRange(List<Long> allocatedIps) { return allocatedIps.get(allocatedIps.size() - 1) - allocatedIps.get(0) == allocatedIps.size(); } private static boolean isConsecutiveRange(Long[] allocatedIps) { return allocatedIps[allocatedIps.length - 1] - allocatedIps[0] + 1 == allocatedIps.length; } private static String[] longToIpv4String(Long[] ips) { String[] ret = new String[ips.length]; for (int i=0; i<ips.length; i++) { ret[i] = longToIpv4String(ips[i]); } return ret; } private static long findFirstHoleByDichotomy(Long[] allocatedIps) { if (isConsecutiveRange(allocatedIps)) { String[] ips = longToIpv4String(allocatedIps); List<String> dips = new ArrayList<String>(allocatedIps.length); Collections.addAll(dips, ips); String err = "You can not ask me to find a hole in consecutive range!!! " + dips; assert false : err; } if (allocatedIps.length == 2) { return allocatedIps[0] + 1; } int mIndex = allocatedIps.length / 2; Long[] part1 = Arrays.copyOfRange(allocatedIps, 0, mIndex); Long[] part2 = Arrays.copyOfRange(allocatedIps, mIndex, allocatedIps.length); if (part1.length == 1) { if (isConsecutiveRange(part2)) { /* For special case there are only three items like [1, 3, 4]*/ return part1[0] + 1; } else { /* For special case there are only three items like [1, 5, 9] that are all inconsecutive */ Long[] tmp = new Long[] { part1[0], part2[0] }; if (!isConsecutiveRange(tmp)) { return part1[0] + 1; } } } /*For special case that hole is in the middle of array. for example, [1, 2, 4, 5]*/ if (isConsecutiveRange(part1) && isConsecutiveRange(part2)) { return part1[part1.length-1] + 1; } if (!isConsecutiveRange(part1)) { return findFirstHoleByDichotomy(part1); } else { return findFirstHoleByDichotomy(part2); } } // The allocatedIps must be sorted! public static Long findFirstAvailableIpv4Address(Long startIp, Long endIp, Long[] allocatedIps) { Long ret = null; if (startIp > endIp) { throw new IllegalArgumentException(String.format("[%s, %s] is an invalid ip range, end ip must be greater than start ip", longToIpv4String(startIp), longToIpv4String(endIp))); } if (startIp.equals(endIp) && allocatedIps.length == 0 ) { return startIp; } if (allocatedIps.length == 0) { return startIp; } long lastAllocatedIp = allocatedIps[allocatedIps.length-1]; long firstAllocatedIp = allocatedIps[0]; if (firstAllocatedIp < startIp || lastAllocatedIp > endIp) { throw new IllegalArgumentException(String.format("[%s, %s] is an invalid allocated ip range, it's not a sub range of ip range[%s, %s]", longToIpv4String(firstAllocatedIp), longToIpv4String(lastAllocatedIp), longToIpv4String(startIp), longToIpv4String(endIp))); } if (allocatedIps.length == endIp - startIp + 1) { /* The ip range is fully occupied*/ return null; } if (firstAllocatedIp > startIp) { /* The allocatedIps doesn't begin with startIp, then startIp is first available one*/ return startIp; } if (isConsecutiveRange(allocatedIps)) { /* the allocated ip range is consecutive, allocate the first one out of allocated ip range */ ret = lastAllocatedIp + 1; assert ret <= endIp; return ret; } /* Now the allocated ip range is inconsecutive, we are going to find out the first *hole* in it */ return findFirstHoleByDichotomy(allocatedIps); } public static String findFirstAvailableIpv4Address(String startIp, String endIp, Long[] allocatedIps) { Long ret = findFirstAvailableIpv4Address(ipv4StringToLong(startIp), ipv4StringToLong(endIp), allocatedIps); return ret == null ? null : longToIpv4String(ret); } public static String randomAllocateIpv4Address(Long startIp, Long endIp, List<Long> allocatedIps) { int total = (int)(endIp - startIp + 1); if (startIp.equals(endIp) && allocatedIps.size() == 0){ return longToIpv4String(startIp); } if (total == allocatedIps.size()) { return null; } BitSet full = new BitSet(total); for (long alloc : allocatedIps) { full.set((int) (alloc-startIp)); } Random random = new Random(); int next = random.nextInt(total); int a = full.nextClearBit(next); if (a >= total) { a = full.nextClearBit(0); } return longToIpv4String(a + startIp); } public static String randomAllocateIpv4Address(String startIp, String endIp, List<Long> allocatedIps) { return randomAllocateIpv4Address(ipv4StringToLong(startIp), ipv4StringToLong(endIp), allocatedIps); } public static int getTotalIpInRange(String startIp, String endIp) { validateIpRange(startIp, endIp); long s = ipv4StringToLong(startIp); long e = ipv4StringToLong(endIp); return (int) (e - s + 1); } public static String getIpAddressByName(String hostName) throws UnknownHostException { InetAddress ia = InetAddress.getByName(hostName); return ia.getHostAddress(); } public static String generateMacWithDeviceId(short deviceId) { int seed = new Random().nextInt(); String seedStr = Integer.toHexString(seed); if (seedStr.length() < 8) { String compensate = StringUtils.repeat("0", 8 - seedStr.length()); seedStr = compensate + seedStr; } String octet2 = seedStr.substring(0, 2); String octet3 = seedStr.substring(2, 4); String octet4 = seedStr.substring(4, 6); String octet5 = seedStr.substring(6, 8); StringBuilder sb = new StringBuilder("fa").append(":"); sb.append(octet2).append(":"); sb.append(octet3).append(":"); sb.append(octet4).append(":"); sb.append(octet5).append(":"); String deviceIdStr = Integer.toHexString(deviceId); if (deviceIdStr.length() < 2) { deviceIdStr = "0" + deviceIdStr; } sb.append(deviceIdStr); return sb.toString(); } public static List<Pair<String, String>> findConsecutiveIpRange(Collection<String> ips) { List<Pair<String, String>> ret = new ArrayList<Pair<String, String>>(); if (ips.isEmpty()) { return ret; } TreeSet<Long> orderIps = new TreeSet<Long>(); for (String ip : ips) { orderIps.add(ipv4StringToLong(ip)); } Long s = null; Long e = null; for (Long ip : orderIps) { if (s == null || e == null) { s = ip; e = ip; } else if (e.equals(ip - 1)) { e = ip; } else { Pair<String, String> range = new Pair<String, String>(); range.first(longToIpv4String(s)); range.second(longToIpv4String(e)); ret.add(range); s = ip; e = ip; } } Pair<String, String> range = new Pair<String, String>(); range.first(longToIpv4String(s)); range.second(longToIpv4String(e)); ret.add(range); return ret; } public static boolean isRemotePortOpen(String ip, int port, int timeout) { Socket socket = null; socket = new Socket(); try { socket.setReuseAddress(true); SocketAddress sa = new InetSocketAddress(ip, port); socket.connect(sa, timeout); return socket.isConnected(); } catch (SocketException e) { logger.debug(String.format("unable to connect remote port[ip:%s, port:%s], %s", ip, port, e.getMessage())); return false; } catch (IOException e) { logger.debug(String.format("unable to connect remote port[ip:%s, port:%s], %s", ip, port, e.getMessage())); return false; } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { logger.warn(e.getMessage(), e); } } } } public static List<String> getFreeIpInRange(String startIp, String endIp, List<String> usedIps, int limit, String start) { long s = ipv4StringToLong(startIp); long e = ipv4StringToLong(endIp); long f = ipv4StringToLong(start); List<String> res = new ArrayList<String>(); for (long i = s > f ? s : f; i<=e; i++) { String ip = longToIpv4String(i); if (!usedIps.contains(ip)) { res.add(ip); } if (res.size() >= limit) { break; } } return res; } public static List<String> getAllMac() { try { List<String> macs = new ArrayList<String>(); Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces(); while (ifaces.hasMoreElements()) { NetworkInterface iface = ifaces.nextElement(); byte[] mac = iface.getHardwareAddress(); if (mac == null) { // lo device continue; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < mac.length; i++) { sb.append(String.format("%02X%s", mac[i], (i < mac.length - 1) ? ":" : "")); } macs.add(sb.toString().toLowerCase()); } return macs; } catch (SocketException e) { throw new RuntimeException(e); } } public static boolean isCidrOverlap(String cidr1, String cidr2) { DebugUtils.Assert(isCidr(cidr1), String.format("%s is not a cidr", cidr1)); DebugUtils.Assert(isCidr(cidr2), String.format("%s is not a cidr", cidr2)); SubnetUtils su1 = new SubnetUtils(cidr1); SubnetUtils su2 = new SubnetUtils(cidr2); SubnetUtils.SubnetInfo info1 = su1.getInfo(); SubnetUtils.SubnetInfo info2 = su2.getInfo(); return isIpv4RangeOverlap(info1.getLowAddress(), info1.getHighAddress(), info2.getLowAddress(), info2.getHighAddress()); } public static boolean isIpv4InCidr(String ipv4, String cidr) { DebugUtils.Assert(isCidr(cidr), String.format("%s is not a cidr", cidr)); validateIp(ipv4); SubnetUtils.SubnetInfo info = new SubnetUtils(cidr).getInfo(); return isIpv4InRange(ipv4, info.getLowAddress(), info.getHighAddress()); } public static boolean isIpRoutedByDefaultGateway(String ip) { ShellResult res = ShellUtils.runAndReturn(String.format("ip route get %s | grep -q \"via $(ip route | awk '/default/ {print $3}')\"", ip)); return res.isReturnCode(0); } }