package com.netifera.platform.util.addresses.inet; import java.net.InetAddress; import java.util.Arrays; import java.util.Locale; import com.netifera.platform.util.addresses.AddressFormatException; import com.netifera.platform.util.addresses.EUI64Address; import com.netifera.platform.util.addresses.INetworkAddress; import com.netifera.platform.util.addresses.NetworkFamily; /* * see RFC 2373: IP Version 6 Addressing Architecture */ public class IPv6Address extends InternetAddress { private static final long serialVersionUID = 3470405877439887455L; private final byte[] bytes; public final static int BYTESLENGTH = 16; /* 8 groups of 4 hexadecimal digits, each group separated by a colon ':' */ public final static int MAX_TEXTUAL_LENGTH = 39; // 8 * 4 + 7 public final static IPv6Address loopback; public final static IPv6Address any; // unspecified final public NetworkFamily getNetworkFamily() { return NetworkFamily.AF_INET6; } static { any = new IPv6Address(new byte[BYTESLENGTH]); byte[] bytes = new byte[BYTESLENGTH]; bytes[15] = 1; loopback = new IPv6Address(bytes); } public int getDataSize() { return BYTESLENGTH * 8; } /** * @param bytes IPv6 address in network byte order, the array must be at * least 16 bytes long * * @exception AddressFormatException if the array is less than 16 bytes */ public IPv6Address(final byte[] bytes) { super(); if (bytes.length < BYTESLENGTH) { throw new AddressFormatException("Bad address size: " + bytes.length); } this.bytes = new byte[BYTESLENGTH]; System.arraycopy(bytes, 0, this.bytes, 0, BYTESLENGTH); } public IPv6Address(final IPv4Address ipv4) { super(); if (ipv4.addressData == 0) { bytes = any.bytes; return; } bytes = bytesMapped(ipv4.toBytes()); } /** * @param ipString the IP address * * @exception AddressFormatException */ public IPv6Address(final String ipString) { this(stringParse(ipString)); } private static byte[] bytesAt(final byte[] bytes, final int offset, final int length) { byte[] result = new byte[length]; System.arraycopy(bytes, offset, result, 0, length); return result; } private byte[] bytesMapped(final byte[] ipv4Bytes) { return fillMapped(new byte[BYTESLENGTH], ipv4Bytes); } private static byte[] fillMapped(byte[] ipv6Bytes, byte[] ipv4Bytes) { for (int i = 0; i < 10; i++) { ipv6Bytes[11] = 0; } ipv6Bytes[10] = (byte)0xff; ipv6Bytes[11] = (byte)0xff; System.arraycopy(ipv4Bytes, 0, ipv6Bytes, 12, 4); return ipv6Bytes; } private static byte[] stringParse(final String ipString) { int length = ipString.length(); if (length < 2) { throw new AddressFormatException(ipString); } String string = ipString; // literal addresses if (ipString.charAt(0) == '[' && ipString.charAt(length - 1) == ']') { string = ipString.substring(1, length - 1); } else if (ipString.toLowerCase(Locale.ENGLISH).endsWith(".ipv6-literal.net")) { string = ipString.split("\\.")[0].replace('-', ':'); } // any address if ("::".equals(string)) { return any.bytes; } // ipv4 mapped if (string.contains(".")) { int index = string.lastIndexOf(':'); long ipv4Num = IPv4Address.stringParse( string.substring(index + 1)) & 0xFFFFFFFFL; string = string.substring(0, index) + String.format( ":%04x:%04x", ipv4Num >> 16, ipv4Num & 0xffff); } String[] parts = string.split(":", -1); if (parts.length > BYTESLENGTH / 2) { throw new AddressFormatException(ipString); } // compressed notation if (string.contains("::")) { int index = string.indexOf("::"); boolean doublecolon_at_end = index == length - 2; StringBuilder sb = new StringBuilder(MAX_TEXTUAL_LENGTH + 1); if (index == 0) { // first member compressed: add first '0' sb.append('0'); } else { sb.append(string.substring(0, index)); } int count = BYTESLENGTH / 2 - parts.length; if (doublecolon_at_end) { // add final ':0' count += 1; } for (int i = 0; i <= count; i++) { sb.append(":0"); } if (!doublecolon_at_end) { // add rest after double colon sb.append(string.substring(index + 1)); } string = sb.toString(); } parts = string.split(":"); if (parts.length != BYTESLENGTH / 2) { throw new AddressFormatException(ipString); } byte[] bytes = new byte[BYTESLENGTH]; for (int i = 0; i < BYTESLENGTH; i += 2) { int value = Integer.parseInt(parts[i / 2], 0x10); bytes[i] = (byte) (value >> 8); bytes[i + 1] = (byte) (value & 0xff); } return bytes; } /** Converts the network address in network byte order from an array to * numbers-and-dots notation. * * @param address IPv6 address in network byte order, byte array at least * 16 bytes long * * @return The string representation of the IP * * @exception AddressFormatException */ public static String stringFormat(final byte[] bytes) { if (bytes.length < BYTESLENGTH) { throw new AddressFormatException("Array too short, len=" + bytes.length); } StringBuffer buffer = new StringBuffer(); boolean v4mapped = isV4Mapped(bytes); if (v4mapped || isV4Compatible(bytes)) { buffer.append("::"); if (v4mapped) { buffer.append("ffff:"); } IPv4Address ipv4 = new IPv4Address(bytesAt(bytes, 12, 4)); switch (ipv4.addressData) { case 0: break; case 1: buffer.append('1'); break; default: buffer.append(ipv4.toString()); } } else { // normalize "::" int offset = -1; int size = 0; // FIXME probably TOO expensive int j0 = 2; for (; j0 < BYTESLENGTH && bytes[j0] != 0; j0 += 2); for (int i = BYTESLENGTH - j0; offset == -1 && i >= 3; i -= 2) { for (int j = j0; offset == -1 && j <= BYTESLENGTH - i; j++) { if (Arrays.equals(bytesAt(any.bytes, 0, i), bytesAt(bytes, j, i))) { offset = j; size = i; } } } for (int i=0; i<BYTESLENGTH; i+=2) { if (i == offset) { if (offset + size == BYTESLENGTH) { buffer.append("::"); break; } buffer.append(':'); i += size - 2; continue; } if (i != 0) { buffer.append(':'); } buffer.append(String.format("%x", Integer.valueOf(((bytes[i] & 0xff) << 8) + (bytes[i + 1] & 0xff)))); } } return buffer.toString(); } @Override public boolean equals(final Object obj) { if (this == obj){ return true; } if (!(obj instanceof IPv6Address)){ return false; } return Arrays.equals(((IPv6Address)obj).bytes, bytes); } @Override public int hashCode(){ return Arrays.hashCode(bytes); } public int compareTo(INetworkAddress other) { if (!(other instanceof InternetAddress)) { return -1; } if (other instanceof IPv4Address) { if (isV4Mapped() || isV4Compatible()) { return toIPv4Address().compareTo(other); } return 1; } IPv6Address address = (IPv6Address) other; int value; for (int i = 0; i < BYTESLENGTH; i++) { value = (bytes[i] & 0xff) - (address.bytes[i] & 0xff); if (value != 0) { return value; // TODO shift i // james } } return 0; } @Override public byte[] toBytes() { return bytes.clone(); } /** * @return an IPv4Address if Mapped or Compatible * * @exception AddressFormatException if not IPv4 mapped/compatible */ public IPv4Address toIPv4Address() { if (isV4Compatible(bytes) || isV4Mapped(bytes)) { return new IPv4Address(bytesAt(bytes, 12, 4)); } throw new AddressFormatException("Address is not mapped/compatible"); } private int octet(final int index) { return (bytes[index] & 0xFF); } @Override public String toString() { return stringFormat(bytes); } @Override public boolean isUnspecified() { return Arrays.equals(any.bytes, bytes); } @Override public boolean isLoopback() { return Arrays.equals(loopback.bytes, bytes); } @Override public boolean isMultiCast() { return octet(0) == 0xff; } @Override public boolean isLinkLocal() { return (octet(0) == 0xfe) && (octet(1) & 0x80) != 0; } /* XXX RFC 3879 deprecates RFC 3513: * "this prefix MUST no longer be supported in new implementations." * FEC0::/48 <- ok? FEC0::/10 <- bad */ /** * Site-local addresses are equivalent to the IPv4 private address space. * * <p>The scope of a site-local address is the site.</p> */ public boolean isSiteLocal() { return (octet(0) == 0xfe) && ((octet(1) & 0xc0) != 0); } /** * Aggregatable global unicast addresses are equivalent to public IPv4 * addresses. They are globally routable and reachable on the IPv6 Internet. * * Aggregatable global unicast addresses are also known as global addresses. */ public boolean isGlobalUnicast() { return octet(0) >> 5 == 1; } /* RFC 4193 */ public boolean isUniqueLocal() { return (octet(0) & 0xfc) == 0xfc; } // TODO: Multicast Scope: (Node/Link/Site/Organization)-local | Global @Override public boolean isPrivate() { return isUniqueLocal() || isSiteLocal() // XXX address range specification abandoned || isLinkLocal(); // generated automatically by the OS's IP layer } /** * Utility routine to check if this IPv6 address is an IPv4 mapped address. * * <p>IPv4 mapped addresses constitute a special class of IPv6 addresses: * This address type has its first 80 bits set to zero, the next 16 set * to one.</p> * * @return a <code>boolean</code> indicating if the address is an IPv4 * mapped address; or false otherwise. */ public boolean isV4Mapped() { return isV4Mapped(bytes); } private static boolean isV4Mapped(final byte[] bytes) { for (int i = 0; i < 10; i++) { if (bytes[i] != 0) { return false; } } return bytes[10] == (byte)0xff && bytes[11] == (byte)0xff; } /** * Utility routine to check if this IPv6 address is an IPv4 compatible * address. * * <p>Such an IPv6 address has its first 96 bits set to zero.</p> * * <p><i>This address type has been deprecated by RFC 4291.</i></p> * * @return a <code>boolean</code> indicating if the address is an IPv4 * compatible address; or false otherwise. */ public boolean isV4Compatible() { return isV4Compatible(bytes); } private static boolean isV4Compatible(final byte[] bytes) { for (int i = 0; i < 12; i++) { if (bytes[i] != 0) { return false; } } return true; } /* Derived from the interface's built-in 48-bit IEEE 802 address. */ @Deprecated // TODO check prefix first, else return null // james public EUI64Address getInterfaceIdentifier() { byte[] data = new byte[EUI64Address.MACLENGTH]; System.arraycopy(bytes, BYTESLENGTH - EUI64Address.MACLENGTH, data, 0, EUI64Address.MACLENGTH); return new EUI64Address(data); } @Override public IPv6Netblock createNetblock(int maskBitCount) { return new IPv6Netblock(this, maskBitCount); } /** * @return an IPv6Address */ public static IPv6Address fromInetAddress(final InetAddress addr) { InternetAddress ipaddr = fromBytes(addr.getAddress()); if (ipaddr instanceof IPv4Address) { return new IPv6Address((IPv4Address)ipaddr); // FIXME verify } return (IPv6Address) ipaddr; } /** * @return an IPv6Address */ public static IPv6Address fromString(final String address) { InternetAddress addr = InternetAddress.fromString(address); if (addr instanceof IPv4Address) { return new IPv6Address((IPv4Address)addr); } return (IPv6Address) addr; } }