blob: bf0140658eb9f1be22ef05caf02d39941e69389b [file] [log] [blame]
/*
* Copyright 2014-present 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.UnknownHostException;
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);
}
/**
* Returns the IP address as InetAddress.
*
* @return InetAddress
*/
public InetAddress toInetAddress() {
try {
return InetAddress.getByAddress(octets);
} catch (UnknownHostException e) {
// Should never reach here
return null;
}
}
/**
* 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();
}
}