| /* |
| * Copyright 2014-2016 Open Networking Laboratory |
| * |
| * Licensed 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 org.onlab.packet; |
| |
| import java.net.InetAddress; |
| import java.net.Inet4Address; |
| import java.net.Inet6Address; |
| import java.nio.ByteBuffer; |
| import java.util.Arrays; |
| import java.util.Objects; |
| import com.google.common.net.InetAddresses; |
| import com.google.common.primitives.UnsignedBytes; |
| |
| |
| /** |
| * A class representing an IP address. |
| * This class is immutable. |
| */ |
| public class IpAddress implements Comparable<IpAddress> { |
| private static final int BIT_MASK = 0x000000ff; |
| |
| // IP Versions |
| public enum Version { INET, INET6 }; |
| |
| // lengths of address, in bytes |
| public static final int INET_BYTE_LENGTH = 4; |
| public static final int INET_BIT_LENGTH = INET_BYTE_LENGTH * Byte.SIZE; |
| public static final int INET6_BYTE_LENGTH = 16; |
| public static final int INET6_BIT_LENGTH = INET6_BYTE_LENGTH * Byte.SIZE; |
| |
| private final Version version; |
| private final byte[] octets; |
| |
| /** |
| * Constructor for given IP address version and address octets. |
| * |
| * @param version the IP address version |
| * @param value the IP address value stored in network byte order |
| * (i.e., the most significant byte first) |
| * @throws IllegalArgumentException if the arguments are invalid |
| */ |
| protected IpAddress(Version version, byte[] value) { |
| checkArguments(version, value, 0); |
| this.version = version; |
| switch (version) { |
| case INET: |
| this.octets = Arrays.copyOf(value, INET_BYTE_LENGTH); |
| break; |
| case INET6: |
| this.octets = Arrays.copyOf(value, INET6_BYTE_LENGTH); |
| break; |
| default: |
| // Should not be reached |
| this.octets = null; |
| break; |
| } |
| } |
| |
| /** |
| * Returns the IP version of this address. |
| * |
| * @return the version |
| */ |
| public Version version() { |
| return this.version; |
| } |
| |
| /** |
| * Tests whether the IP version of this address is IPv4. |
| * |
| * @return true if the IP version of this address is IPv4, otherwise false. |
| */ |
| public boolean isIp4() { |
| return (version() == Ip4Address.VERSION); |
| } |
| |
| /** |
| * Tests whether the IP version of this address is IPv6. |
| * |
| * @return true if the IP version of this address is IPv6, otherwise false. |
| */ |
| public boolean isIp6() { |
| return (version() == Ip6Address.VERSION); |
| } |
| |
| /** |
| * Gets the {@link Ip4Address} view of the IP address. |
| * |
| * @return the {@link Ip4Address} view of the IP address if it is IPv4, |
| * otherwise null |
| */ |
| public Ip4Address getIp4Address() { |
| if (!isIp4()) { |
| return null; |
| } |
| |
| // Return this object itself if it is already instance of Ip4Address |
| if (this instanceof Ip4Address) { |
| return (Ip4Address) this; |
| } |
| return Ip4Address.valueOf(octets); |
| } |
| |
| /** |
| * Gets the {@link Ip6Address} view of the IP address. |
| * |
| * @return the {@link Ip6Address} view of the IP address if it is IPv6, |
| * otherwise null |
| */ |
| public Ip6Address getIp6Address() { |
| if (!isIp6()) { |
| return null; |
| } |
| |
| // Return this object itself if it is already instance of Ip6Address |
| if (this instanceof Ip6Address) { |
| return (Ip6Address) this; |
| } |
| return Ip6Address.valueOf(octets); |
| } |
| |
| /** |
| * Returns the IP address as a byte array. |
| * |
| * @return a byte array |
| */ |
| public byte[] toOctets() { |
| return Arrays.copyOf(octets, octets.length); |
| } |
| |
| /** |
| * Computes the IP address byte length for a given IP version. |
| * |
| * @param version the IP version |
| * @return the IP address byte length for the IP version |
| * @throws IllegalArgumentException if the IP version is invalid |
| */ |
| public static int byteLength(Version version) { |
| switch (version) { |
| case INET: |
| return INET_BYTE_LENGTH; |
| case INET6: |
| return INET6_BYTE_LENGTH; |
| default: |
| String msg = "Invalid IP version " + version; |
| throw new IllegalArgumentException(msg); |
| } |
| } |
| |
| /** |
| * Converts an integer into an IPv4 address. |
| * |
| * @param value an integer representing an IPv4 address value |
| * @return an IP address |
| */ |
| public static IpAddress valueOf(int value) { |
| byte[] bytes = |
| ByteBuffer.allocate(INET_BYTE_LENGTH).putInt(value).array(); |
| return new IpAddress(Version.INET, bytes); |
| } |
| |
| /** |
| * Converts a byte array into an IP address. |
| * |
| * @param version the IP address version |
| * @param value the IP address value stored in network byte order |
| * (i.e., the most significant byte first) |
| * @return an IP address |
| * @throws IllegalArgumentException if the arguments are invalid |
| */ |
| public static IpAddress valueOf(Version version, byte[] value) { |
| return new IpAddress(version, value); |
| } |
| |
| /** |
| * Converts a byte array and a given offset from the beginning of the |
| * array into an IP address. |
| * <p> |
| * The IP address is stored in network byte order (i.e., the most |
| * significant byte first). |
| * </p> |
| * @param version the IP address version |
| * @param value the value to use |
| * @param offset the offset in bytes from the beginning of the byte array |
| * @return an IP address |
| * @throws IllegalArgumentException if the arguments are invalid |
| */ |
| public static IpAddress valueOf(Version version, byte[] value, |
| int offset) { |
| checkArguments(version, value, offset); |
| byte[] bc = Arrays.copyOfRange(value, offset, value.length); |
| return IpAddress.valueOf(version, bc); |
| } |
| |
| /** |
| * Converts an InetAddress into an IP address. |
| * |
| * @param inetAddress the InetAddress value to use |
| * @return an IP address |
| * @throws IllegalArgumentException if the argument is invalid |
| */ |
| public static IpAddress valueOf(InetAddress inetAddress) { |
| byte[] bytes = inetAddress.getAddress(); |
| if (inetAddress instanceof Inet4Address) { |
| return new IpAddress(Version.INET, bytes); |
| } |
| if (inetAddress instanceof Inet6Address) { |
| return new IpAddress(Version.INET6, bytes); |
| } |
| // Use the number of bytes as a hint |
| if (bytes.length == INET_BYTE_LENGTH) { |
| return new IpAddress(Version.INET, bytes); |
| } |
| if (bytes.length == INET6_BYTE_LENGTH) { |
| return new IpAddress(Version.INET6, bytes); |
| } |
| final String msg = "Unrecognized IP version address string: " + |
| inetAddress.toString(); |
| throw new IllegalArgumentException(msg); |
| } |
| |
| /** |
| * Converts an IPv4 or IPv6 string literal (e.g., "10.2.3.4" or |
| * "1111:2222::8888") into an IP address. |
| * |
| * @param value an IP address value in string form |
| * @return an IP address |
| * @throws IllegalArgumentException if the argument is invalid |
| */ |
| public static IpAddress valueOf(String value) { |
| InetAddress inetAddress = null; |
| try { |
| inetAddress = InetAddresses.forString(value); |
| } catch (IllegalArgumentException e) { |
| final String msg = "Invalid IP address string: " + value; |
| throw new IllegalArgumentException(msg); |
| } |
| return valueOf(inetAddress); |
| } |
| |
| /** |
| * Creates an IP network mask prefix. |
| * |
| * @param version the IP address version |
| * @param prefixLength the length of the mask prefix. Must be in the |
| * interval [0, 32] for IPv4, or [0, 128] for IPv6 |
| * @return a new IP address that contains a mask prefix of the |
| * specified length |
| * @throws IllegalArgumentException if the arguments are invalid |
| */ |
| public static IpAddress makeMaskPrefix(Version version, int prefixLength) { |
| byte[] mask = makeMaskPrefixArray(version, prefixLength); |
| return new IpAddress(version, mask); |
| } |
| |
| /** |
| * Creates an IP address by masking it with a network mask of given |
| * mask length. |
| * |
| * @param address the address to mask |
| * @param prefixLength the length of the mask prefix. Must be in the |
| * interval [0, 32] for IPv4, or [0, 128] for IPv6 |
| * @return a new IP address that is masked with a mask prefix of the |
| * specified length |
| * @throws IllegalArgumentException if the prefix length is invalid |
| */ |
| public static IpAddress makeMaskedAddress(final IpAddress address, |
| int prefixLength) { |
| if (address instanceof Ip4Address) { |
| Ip4Address ip4a = (Ip4Address) address; |
| return Ip4Address.makeMaskedAddress(ip4a, prefixLength); |
| } else if (address instanceof Ip6Address) { |
| Ip6Address ip6a = (Ip6Address) address; |
| return Ip6Address.makeMaskedAddress(ip6a, prefixLength); |
| } else { |
| byte[] net = makeMaskedAddressArray(address, prefixLength); |
| return IpAddress.valueOf(address.version(), net); |
| } |
| } |
| |
| /** |
| * Check if this IP address is zero. |
| * |
| * @return true if this address is zero |
| */ |
| public boolean isZero() { |
| for (byte b : octets) { |
| if (b != 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Check if this IP address is self-assigned. |
| * |
| * @return true if this address is self-assigned |
| */ |
| public boolean isSelfAssigned() { |
| return isIp4() && octets[0] == (byte) 169; |
| } |
| |
| /** |
| * Check if this IP address is a multicast address. |
| * |
| * @return true if this address a multicast address |
| */ |
| public boolean isMulticast() { |
| return isIp4() ? |
| Ip4Prefix.IPV4_MULTICAST_PREFIX.contains(this.getIp4Address()) : |
| Ip6Prefix.IPV6_MULTICAST_PREFIX.contains(this.getIp6Address()); |
| } |
| |
| @Override |
| public int compareTo(IpAddress o) { |
| // Compare first the version |
| if (this.version != o.version) { |
| return this.version.compareTo(o.version); |
| } |
| |
| // Compare the bytes, one-by-one |
| for (int i = 0; i < this.octets.length; i++) { |
| if (this.octets[i] != o.octets[i]) { |
| return UnsignedBytes.compare(this.octets[i], o.octets[i]); |
| } |
| } |
| return 0; // Equal |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(version, Arrays.hashCode(octets)); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if ((obj == null) || (!(obj instanceof IpAddress))) { |
| return false; |
| } |
| IpAddress other = (IpAddress) obj; |
| return (version == other.version) && |
| Arrays.equals(octets, other.octets); |
| } |
| |
| @Override |
| /* |
| * (non-Javadoc) |
| * The string representation of the IP address: "x.x.x.x" for IPv4 |
| * addresses, or ':' separated string for IPv6 addresses. |
| * |
| * @see java.lang.Object#toString() |
| */ |
| public String toString() { |
| // FIXME InetAddress is super slow |
| switch (version) { |
| case INET: |
| return String.format("%d.%d.%d.%d", octets[0] & 0xff, |
| octets[1] & 0xff, |
| octets[2] & 0xff, |
| octets[3] & 0xff); |
| case INET6: |
| default: |
| return ipv6ToStringHelper(); |
| } |
| } |
| |
| /** |
| * Generates an IP prefix. |
| * |
| * @return the IP prefix of the IP address |
| */ |
| public IpPrefix toIpPrefix() { |
| |
| if (isIp4()) { |
| return IpPrefix.valueOf(new IpAddress(Version.INET, octets), |
| Ip4Address.BIT_LENGTH); |
| } else { |
| return IpPrefix.valueOf(new IpAddress(Version.INET6, octets), |
| Ip6Address.BIT_LENGTH); |
| } |
| } |
| |
| /** |
| * Gets the IP address name for the IP address version. |
| * |
| * @param version the IP address version |
| * @return the IP address name for the IP address version |
| */ |
| private static String addressName(Version version) { |
| switch (version) { |
| case INET: |
| return "IPv4"; |
| case INET6: |
| return "IPv6"; |
| default: |
| break; |
| } |
| return "UnknownIP(" + version + ")"; |
| } |
| |
| /** |
| * Checks whether the arguments are valid. |
| * |
| * @param version the IP address version |
| * @param value the IP address value stored in a byte array |
| * @param offset the offset in bytes from the beginning of the byte |
| * array with the address |
| * @throws IllegalArgumentException if any of the arguments is invalid |
| */ |
| static void checkArguments(Version version, byte[] value, int offset) { |
| // Check the offset and byte array length |
| int addrByteLength = byteLength(version); |
| if ((offset < 0) || (offset + addrByteLength > value.length)) { |
| String msg; |
| if (value.length < addrByteLength) { |
| msg = "Invalid " + addressName(version) + |
| " address array: array length: " + value.length + |
| ". Must be at least " + addrByteLength; |
| } else { |
| msg = "Invalid " + addressName(version) + |
| " address array: array offset: " + offset + |
| ". Must be in the interval [0, " + |
| (value.length - addrByteLength) + "]"; |
| } |
| throw new IllegalArgumentException(msg); |
| } |
| } |
| |
| /** |
| * Creates a byte array for IP network mask prefix. |
| * |
| * @param version the IP address version |
| * @param prefixLength the length of the mask prefix. Must be in the |
| * interval [0, 32] for IPv4, or [0, 128] for IPv6 |
| * @return a byte array that contains a mask prefix of the |
| * specified length |
| * @throws IllegalArgumentException if the arguments are invalid |
| */ |
| static byte[] makeMaskPrefixArray(Version version, int prefixLength) { |
| int addrByteLength = byteLength(version); |
| int addrBitLength = addrByteLength * Byte.SIZE; |
| |
| // Verify the prefix length |
| if ((prefixLength < 0) || (prefixLength > addrBitLength)) { |
| final String msg = "Invalid IP prefix length: " + prefixLength + |
| ". Must be in the interval [0, " + addrBitLength + "]."; |
| throw new IllegalArgumentException(msg); |
| } |
| |
| // Number of bytes and extra bits that should be all 1s |
| int maskBytes = prefixLength / Byte.SIZE; |
| int maskBits = prefixLength % Byte.SIZE; |
| byte[] mask = new byte[addrByteLength]; |
| |
| // Set the bytes and extra bits to 1s |
| for (int i = 0; i < maskBytes; i++) { |
| mask[i] = (byte) 0xff; // Set mask bytes to 1s |
| } |
| for (int i = maskBytes; i < addrByteLength; i++) { |
| mask[i] = 0; // Set remaining bytes to 0s |
| } |
| if (maskBits > 0) { |
| mask[maskBytes] = (byte) (0xff << (Byte.SIZE - maskBits)); |
| } |
| return mask; |
| } |
| |
| /** |
| * Creates a byte array that represents an IP address masked with |
| * a network mask of given mask length. |
| * |
| * @param addr the address to mask |
| * @param prefixLength the length of the mask prefix. Must be in the |
| * interval [0, 32] for IPv4, or [0, 128] for IPv6 |
| * @return a byte array that represents the IP address masked with |
| * a mask prefix of the specified length |
| * @throws IllegalArgumentException if the prefix length is invalid |
| */ |
| static byte[] makeMaskedAddressArray(final IpAddress addr, |
| int prefixLength) { |
| byte[] mask = IpAddress.makeMaskPrefixArray(addr.version(), |
| prefixLength); |
| byte[] net = new byte[mask.length]; |
| |
| // Mask each byte |
| for (int i = 0; i < net.length; i++) { |
| net[i] = (byte) (addr.octets[i] & mask[i]); |
| } |
| return net; |
| } |
| |
| /** |
| * Creates a string based on the IPv6 recommendations for canonical representations found here: |
| * https://tools.ietf.org/html/rfc5952#section-1. |
| * @return A properly formatted IPv6 canonical representation. |
| */ |
| private String ipv6ToStringHelper() { |
| //Populate a buffer with the string of the full address with leading zeros stripped |
| StringBuffer buff = new StringBuffer(); |
| buff.append(String.format("%x:%x:%x:%x:%x:%x:%x:%x", |
| (((octets[0] & BIT_MASK) << 8) | (octets[1] & BIT_MASK)), |
| (((octets[2] & BIT_MASK) << 8) | (octets[3] & BIT_MASK)), |
| (((octets[4] & BIT_MASK) << 8) | (octets[5] & BIT_MASK)), |
| (((octets[6] & BIT_MASK) << 8) | (octets[7] & BIT_MASK)), |
| (((octets[8] & BIT_MASK) << 8) | (octets[9] & BIT_MASK)), |
| (((octets[10] & BIT_MASK) << 8) | (octets[11] & BIT_MASK)), |
| (((octets[12] & BIT_MASK) << 8) | (octets[13] & BIT_MASK)), |
| (((octets[14] & BIT_MASK) << 8) | (octets[15] & BIT_MASK)))); |
| //Initialize variables for tracking longest zero subsequence, tiebreaking by first occurence |
| int longestSeqStart, longestSeqLen, currSeqStart, currSeqLen; |
| longestSeqStart = 0; |
| longestSeqLen = 0; |
| currSeqStart = 0; |
| currSeqLen = 0; |
| |
| for (int index = 0; index < buff.length(); index++) { |
| if (buff.charAt(index) == ':') { |
| if (currSeqLen != 0 && buff.charAt(index + 1) == '0') { |
| currSeqLen += 1; |
| } |
| } else if (buff.charAt(index) == '0' && ((index == 0) || (buff.charAt(index - 1) == ':'))) { |
| if (currSeqLen == 0) { |
| currSeqStart = index; |
| } |
| currSeqLen += 1; |
| } else { |
| if (currSeqLen > longestSeqLen) { |
| longestSeqStart = currSeqStart; |
| longestSeqLen = currSeqLen; |
| } |
| currSeqLen = 0; |
| } |
| } |
| |
| if (currSeqLen > longestSeqLen) { |
| longestSeqLen = currSeqLen; |
| longestSeqStart = currSeqStart; |
| } |
| if (longestSeqLen > 1) { |
| if (buff.length() == (longestSeqStart + longestSeqLen)) { |
| buff.append(':'); |
| } |
| |
| buff.delete(longestSeqStart, longestSeqStart + longestSeqLen); |
| |
| if (longestSeqStart == 0) { |
| buff.insert(0, ':'); |
| } |
| } |
| |
| return buff.toString(); |
| } |
| } |