/* * 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 com.google.common.net.InetAddresses; import com.google.common.primitives.UnsignedBytes; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Objects; /** * 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; } } /** * Default constructor for Kryo serialization. */ protected IpAddress() { this.version = null; this.octets = null; } /** * 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 StringBuilder buff = new StringBuilder(); 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(); } }