package org.openstack.atlas.util.ip; import org.openstack.atlas.util.converters.BitConverters; import org.openstack.atlas.util.crypto.HashUtil; import org.openstack.atlas.util.ip.exception.AccountUnHashableException; import org.openstack.atlas.util.ip.exception.IPBigIntegerConversionException; import org.openstack.atlas.util.ip.exception.IPStringConversionException; import java.math.BigInteger; import java.security.NoSuchAlgorithmException; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; import java.util.regex.Matcher; public class IPv6 implements Comparable<IPv6> { private String ip; private static final BigInteger maxIp; private static final Pattern ipPattern; private static final BigInteger byte255; private static final BigInteger max32bit; private static final BigInteger vipOctetMask; static { String ippatternstr = "^(.*::)([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})$|" + "^(.*):([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})$"; ipPattern = Pattern.compile(ippatternstr); maxIp = new BigInteger("340282366920938463463374607431768211455"); byte255 = new BigInteger("255"); max32bit = new BigInteger("2147483648"); vipOctetMask = new BigInteger("18446744073709551615"); } private static int[] splitvals(String ipStr) { int i; int length; int val; String[] words; int[] out; if (ipStr == null) { return null; } words = ipStr.split(":", -1); length = words.length; if (length <= 0) { return null; } out = new int[length]; for (i = 0; i < length; i++) { if (words[i].length() == 0) { out[i] = -1; continue; } val = BitConverters.hex16bit2int(words[i]); if (val == -1) { return null; } out[i] = val; } return out; } private static int countnegatives(int[] in) { int i; int out = 0; for (i = 0; i < in.length; i++) { if (in[i] < 0) { out += 1; } } return out; } public static String expand(String ipStr, int nwords) throws IPStringConversionException { StringBuilder sb = new StringBuilder(); String hex; int[] vals; int[] expanded_vals; int i; vals = splitvals(ipStr); if (vals == null) { throw new IPStringConversionException("Error converting hex to binary in IPv6 ip"); } expanded_vals = expand(vals, nwords); for (i = 0; i < nwords - 1; i++) { hex = String.format("%s:", BitConverters.int16bit2hex(expanded_vals[i])); sb.append(hex); } hex = String.format("%s", BitConverters.int16bit2hex(expanded_vals[nwords - 1])); sb.append(hex); return sb.toString(); } public String expand() throws IPStringConversionException { return bytes2IpString(IpString2bytes(ip)); // Silly but just used the inversion to force expansion } private static int[] expand(int[] vals, int nwords) throws IPStringConversionException { int[] out; int i; int j; int nvals; int negatives; if (vals.length < 3 || vals.length > nwords) { throw new IPStringConversionException("Invalid Number of 16bit words in IPv6 address"); } nvals = vals.length; negatives = countnegatives(vals); out = new int[nwords]; for (i = 0; i < nwords; i++) { out[i] = 0; } if (negatives > 3) { throw new IPStringConversionException("Invalid IPv6 Zero compression"); } if (negatives == 3 && nvals == 3) { return out; // All Zero compression; } if (negatives == 1 && (vals[0] != -1 && vals[nvals - 1] != -1)) { // Middle Compression // Do left expand j = nvals - 1; i = nwords - 1; while (vals[j] != -1) { out[i] = vals[j]; i--; j--; } // Then do right Expand j = 0; i = 0; while (vals[i] != -1) { out[i] = vals[j]; i++; j++; } return out; } if (negatives == 2 && vals[0] == -1 && vals[1] == -1) { // Left Compression j = nvals - 1; i = nwords - 1; while (vals[j] != -1) { out[i] = vals[j]; i--; j--; } return out; } if (negatives == 2 && vals[nvals - 1] == -1 && vals[nvals - 2] == -1) { // Right Compression j = 0; i = 0; while (vals[i] != -1) { out[i] = vals[j]; i++; j++; } return out; } if (negatives == 0 && nvals == nwords) { // No Compression Straight copy; for (i = 0; i < nwords; i++) { out[i] = vals[i]; } return out; } throw new IPStringConversionException("Invalid IPv6 ip"); } public BigInteger toBigInteger() throws IPStringConversionException { BigInteger out; out = new BigInteger(1, this.getBytes()); return out; } public IPv6(BigInteger in) throws IPBigIntegerConversionException { this.ip = bigInteger2IPv6(in).getString(); } public static IPv6 bigInteger2IPv6(BigInteger in) throws IPBigIntegerConversionException { IPv6 out; int byteInt; int i; int j; byte[] bytesOut = new byte[16]; BigInteger bigInt = new BigInteger(in.toByteArray()); if (bigInt.compareTo(BigInteger.ZERO) == -1 || bigInt.compareTo(maxIp) == 1) { throw new IPBigIntegerConversionException("Big Integer out of IPv6 Range"); } for (i = 15; i >= 0; i--) { byteInt = bigInt.and(byte255).intValue(); bytesOut[i] = BitConverters.int2ubyte(byteInt); bigInt = bigInt.shiftRight(8); } try { out = new IPv6(bytesOut); } catch (IPStringConversionException ex) { throw new IPBigIntegerConversionException("Impossible Exception Conditions where pre checked"); } return out; } public static byte[] IpString2bytes(String ipStr) throws IPStringConversionException { int i; int j; int val; byte[] out; String expanded_ipStr; Matcher ipMatch = ipPattern.matcher(ipStr); if (ipMatch.find()) { // Found a RFC4291 2.2.3 ipv4 mixed address String hex_part = ipMatch.group(1); String ip4_part = ipMatch.group(2); if (hex_part == null && ip4_part == null) { hex_part = ipMatch.group(3); ip4_part = ipMatch.group(4); } IPv4 ipv4 = new IPv4(ip4_part); byte[] ipv4_bytes = ipv4.getBytes(); byte[] hex_bytes = hex_part.getBytes(); int last = hex_bytes.length - 1; int hi_word = (BitConverters.ubyte2int(ipv4_bytes[0]) << 8) + (BitConverters.ubyte2int(ipv4_bytes[1])); int lo_word = (BitConverters.ubyte2int(ipv4_bytes[2]) << 8) + (BitConverters.ubyte2int(ipv4_bytes[3])); String hi_str = BitConverters.int16bit2hex(hi_word); String lo_str = BitConverters.int16bit2hex(lo_word); expanded_ipStr = expand(hex_part, 6) + String.format(":%s:%s", hi_str, lo_str); } else { String hex_part = ipStr; expanded_ipStr = expand(hex_part, 8); } i = 0; out = new byte[16]; for (String word : expanded_ipStr.split(":")) { val = BitConverters.hex16bit2int(word); out[i] = BitConverters.int2ubyte((val & 0xff00) >> 8); out[i + 1] = BitConverters.int2ubyte((val & 0x00ff)); i += 2; } return out; } public static String bytes2IpString(byte[] in) throws IPStringConversionException { int i; int hi; int lo; StringBuilder sb = new StringBuilder(); String hex; if (in.length != 16) { String msg = "Error IPv6 requires byte array of length 16"; throw new IPStringConversionException(msg); } for (i = 0; i < 14; i += 2) { hi = BitConverters.ubyte2int(in[i]) << 8; lo = BitConverters.ubyte2int(in[i + 1]); hex = String.format("%s:", BitConverters.int16bit2hex(hi | lo)); sb.append(hex); } hi = BitConverters.ubyte2int(in[14]) << 8; lo = BitConverters.ubyte2int(in[15]); sb.append(String.format("%s", BitConverters.int16bit2hex(hi | lo))); return sb.toString(); } public IPv6() { } public IPv6(String ip) { this.ip = ip; } public IPv6(byte[] in) throws IPStringConversionException { this.ip = bytes2IpString(in); } public void setIp(byte[] in) throws IPStringConversionException { ip = bytes2IpString(in); } public String getString() { return ip; } public byte[] getBytes() throws IPStringConversionException { return IpString2bytes(this.ip); } public void setIp(String ip) { this.ip = ip; } public void insertInteger(int in, int startBit) throws IPStringConversionException { int size = 32; byte[] intBytes; BigInteger bigInt; intBytes = BitConverters.uint2bytes(in); bigInt = new BigInteger(1, intBytes); this.insertBigInteger(bigInt, startBit, size); } public void insertBigInteger(BigInteger in, int startBit, int size) throws IPStringConversionException { BigInteger bigInt; BigInteger thisIpInt; BigInteger mask; BigInteger invMask; bigInt = in.or(BigInteger.ZERO); thisIpInt = this.toBigInteger(); mask = maxIp.shiftRight(128 - size); mask = mask.shiftLeft(128 - startBit - size); invMask = mask.xor(maxIp); bigInt = bigInt.shiftLeft(128 - startBit - size); thisIpInt = thisIpInt.and(invMask); bigInt = bigInt.and(mask); thisIpInt = thisIpInt.or(bigInt); try { this.ip = new IPv6(thisIpInt).expand(); } catch (IPBigIntegerConversionException ex) { throw new IPStringConversionException("Impossible exception conditions where already checked"); } } public void setAccountPartition(int accountId) throws IPStringConversionException { byte[] accountBytes = String.format("%s", accountId).getBytes(); byte[] accountSha1; try { accountSha1 = HashUtil.sha1sum(accountBytes, 0, 4); } catch (NoSuchAlgorithmException ex) { throw new AccountUnHashableException("Account was not sha1 Sum Hashable", ex); } BigInteger sha1Int = new BigInteger(1, accountSha1); this.insertBigInteger(sha1Int, 64, 32); } public void setVipOctets(int lo) throws IPStringConversionException { this.insertInteger(lo, 96); } public int getVipOctets() throws IPStringConversionException { Integer out; BigInteger bigInt = this.toBigInteger(); bigInt = bigInt.and(vipOctetMask); out = bigInt.intValue(); if (out < 0) { throw new IPStringConversionException("Negative vipOctet found"); } return out; } public void setClusterPartition(IPv6Cidr clusterCidr) throws IPStringConversionException { String newIp; BigInteger ipDelta = this.toBigInteger(); BigInteger mask = new BigInteger(1, clusterCidr.getMaskBytes()); BigInteger cluster = new BigInteger(1, clusterCidr.getIpBytes()); BigInteger invMask = mask.xor(maxIp); ipDelta = ipDelta.and(invMask); cluster = cluster.and(mask); ipDelta = ipDelta.or(cluster); try { this.ip = new IPv6(ipDelta).getString(); } catch (IPBigIntegerConversionException ex) { throw new IPStringConversionException("Unknown failure converting string. BigNum should have already been trimmed"); } } @Override public String toString() { String out; try { return this.expand(); } catch (IPStringConversionException ex) { return String.format("{%s:%s}", ex.getClass().getName(), ex.getMessage()); } } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final IPv6 that = (IPv6) obj; String thisIp; String thatIp; try { thisIp = this.ip == null ? null : this.expand(); } catch (IPStringConversionException ex) { thisIp = this.ip; } try { thatIp = that.ip == null ? null : that.expand(); } catch (IPStringConversionException ex) { thatIp = that.ip; } if ((thisIp == null) ? (thatIp != null) : !thisIp.equals(thatIp)) { return false; } return true; } @Override public int hashCode() { int hash = 5; String thisIp; try { thisIp = this.ip == null ? null : this.expand(); } catch (IPStringConversionException ex) { thisIp = this.ip; } hash = 97 * hash + (this.ip != null ? this.ip.hashCode() : 0); return hash; } @Override public int compareTo(IPv6 that) { BigInteger thisInt; BigInteger thatInt; int out; try { thisInt = this.toBigInteger(); thatInt = that.toBigInteger(); } catch (IPStringConversionException ex) { throw new IllegalArgumentException("One IP address was invalid"); } out = thisInt.compareTo(thatInt); return out; } }