Refactor the IpPrefix API and implementation:
* Now IpPrefix uses IpAddress to represent the subnet address
* The IpPrefix subnet address is masked-out by the prefix length.
E.g., IpPrefix("1.2.3.4/24") is now stored as IpPrefix("1.2.3.0/24")
* Removed IpPrefix methods that are not used or don't apply anymore
* Replaced usage of IpPrefix with IpAddress where appropriate
diff --git a/utils/misc/src/main/java/org/onlab/packet/IpAddress.java b/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
index 6af87a7..487fb5b 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
@@ -21,7 +21,8 @@
import static com.google.common.base.Preconditions.checkNotNull;
/**
- * A class representing an IPv4 address.
+ * A class representing an IP address.
+ * TODO: Add support for IPv6 as well.
*/
public final class IpAddress implements Comparable<IpAddress> {
// IP Versions
@@ -44,8 +45,6 @@
* @param value the IP address value
*/
private IpAddress(Version version, byte[] value) {
- checkNotNull(value);
-
this.version = version;
this.octets = Arrays.copyOf(value, INET_BYTE_LENGTH);
}
@@ -53,7 +52,7 @@
/**
* Converts an integer into an IPv4 address.
*
- * @param value an integer representing an IPv4 value
+ * @param value an integer representing an IPv4 address value
* @return an IP address
*/
public static IpAddress valueOf(int value) {
@@ -70,6 +69,7 @@
* @return an IP address
*/
public static IpAddress valueOf(byte[] value) {
+ checkNotNull(value);
return new IpAddress(Version.INET, value);
}
@@ -106,13 +106,13 @@
/**
* Converts a dotted-decimal string (x.x.x.x) into an IPv4 address.
*
- * @param address a IP address in string form, e.g. "10.0.0.1".
+ * @param address an IP address in string form, e.g. "10.0.0.1"
* @return an IP address
*/
public static IpAddress valueOf(String address) {
final String[] net = address.split("\\.");
if (net.length != INET_BYTE_LENGTH) {
- String msg = "Malformed IPv4 address string; " +
+ String msg = "Malformed IPv4 address string: " + address + "." +
"Address must have four decimal values separated by dots (.)";
throw new IllegalArgumentException(msg);
}
@@ -154,20 +154,21 @@
/**
* Creates an IP network mask prefix.
*
- * @param prefixLen the length of the mask prefix. Must be in the interval
- * [0, 32] for IPv4
+ * @param prefixLength the length of the mask prefix. Must be in the
+ * interval [0, 32] for IPv4
* @return a new IP address that contains a mask prefix of the
* specified length
*/
- public static IpAddress makeMaskPrefix(int prefixLen) {
+ public static IpAddress makeMaskPrefix(int prefixLength) {
// Verify the prefix length
- if ((prefixLen < 0) || (prefixLen > INET_BIT_LENGTH)) {
- final String msg = "Invalid IPv4 prefix length: " + prefixLen +
+ if ((prefixLength < 0) || (prefixLength > INET_BIT_LENGTH)) {
+ final String msg = "Invalid IPv4 prefix length: " + prefixLength +
". Must be in the interval [0, 32].";
throw new IllegalArgumentException(msg);
}
- long v = (0xffffffffL << (INET_BIT_LENGTH - prefixLen)) & 0xffffffffL;
+ long v =
+ (0xffffffffL << (INET_BIT_LENGTH - prefixLength)) & 0xffffffffL;
return IpAddress.valueOf((int) v);
}
@@ -176,14 +177,14 @@
* mask length.
*
* @param addr the address to mask
- * @param prefixLen the length of the mask prefix. Must be in the interval
- * [0, 32] for IPv4
+ * @param prefixLength the length of the mask prefix. Must be in the
+ * interval [0, 32] for IPv4
* @return a new IP address that is masked with a mask prefix of the
* specified length
*/
public static IpAddress makeMaskedAddress(final IpAddress addr,
- int prefixLen) {
- IpAddress mask = IpAddress.makeMaskPrefix(prefixLen);
+ int prefixLength) {
+ IpAddress mask = IpAddress.makeMaskPrefix(prefixLength);
byte[] net = new byte[INET_BYTE_LENGTH];
// Mask each byte
@@ -207,7 +208,7 @@
@Override
public boolean equals(Object obj) {
- if (obj == this) {
+ if (this == obj) {
return true;
}
if ((obj == null) || (getClass() != obj.getClass())) {
@@ -221,7 +222,7 @@
@Override
/*
* (non-Javadoc)
- * format is "x.x.x.x" for IPv4 addresses.
+ * The format is "x.x.x.x" for IPv4 addresses.
*
* @see java.lang.Object#toString()
*/
diff --git a/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java b/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
index e864125..f15946a 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
@@ -15,285 +15,174 @@
*/
package org.onlab.packet;
-import java.util.Arrays;
+import java.util.Objects;
/**
- * A class representing an IPv4 prefix.
+ * A class representing an IP prefix.
+ * TODO: Add support for IPv6 as well.
* <p/>
* A prefix consists of an IP address and a subnet mask.
+ * NOTE: The stored IP address in the result IP prefix is masked to
+ * contain zeroes in all bits after the prefix length.
*/
public final class IpPrefix {
-
- // TODO a comparator for netmasks? E.g. for sorting by prefix match order.
-
- // IP Versions: IPv4 and IPv6
- public enum Version { INET, INET6 };
-
// Maximum network mask length
public static final int MAX_INET_MASK_LENGTH = IpAddress.INET_BIT_LENGTH;
public static final int MAX_INET6_MASK_LENGTH = IpAddress.INET6_BIT_LENGTH;
- //no mask (no network), e.g. a simple address
- private static final int DEFAULT_MASK = 0;
+ private final IpAddress address;
+ private final short prefixLength;
/**
- * Default value indicating an unspecified address.
- */
- private static final byte[] ANY = new byte[] {0, 0, 0, 0};
-
- private final Version version;
- private final byte[] octets;
- private final int netmask;
-
- /**
- * Constructor for given IP address version, prefix address octets,
- * and network mask length.
+ * Constructor for given IP address, and a prefix length.
*
- * @param ver the IP address version
- * @param octets the IP prefix address octets
- * @param netmask the network mask length
+ * @param address the IP address
+ * @param prefixLength the prefix length
*/
- private IpPrefix(Version ver, byte[] octets, int netmask) {
- this.version = ver;
- this.octets = Arrays.copyOf(octets, IpAddress.INET_BYTE_LENGTH);
- this.netmask = netmask;
+ private IpPrefix(IpAddress address, int prefixLength) {
+ checkPrefixLength(prefixLength);
+ this.address = IpAddress.makeMaskedAddress(address, prefixLength);
+ this.prefixLength = (short) prefixLength;
}
/**
- * Converts a byte array into an IP address.
+ * Checks whether the prefix length is valid.
*
- * @param address a byte array
- * @param netmask the CIDR value subnet mask
- * @return an IP address
+ * @param prefixLength the prefix length value to check
+ * @throws IllegalArgumentException if the prefix length value is invalid
*/
- public static IpPrefix valueOf(byte[] address, int netmask) {
- return new IpPrefix(Version.INET, address, netmask);
- }
-
- /**
- * Helper to convert an integer into a byte array.
- *
- * @param address the integer to convert
- * @return a byte array
- */
- private static byte[] bytes(int address) {
- byte[] bytes = new byte [IpAddress.INET_BYTE_LENGTH];
- for (int i = 0; i < IpAddress.INET_BYTE_LENGTH; i++) {
- bytes[i] = (byte) ((address >> (IpAddress.INET_BYTE_LENGTH
- - (i + 1)) * 8) & 0xff);
+ private static void checkPrefixLength(int prefixLength) {
+ if ((prefixLength < 0) || (prefixLength > MAX_INET_MASK_LENGTH)) {
+ String msg = "Invalid prefix length " + prefixLength + ". " +
+ "The value must be in the interval [0, " +
+ MAX_INET_MASK_LENGTH + "]";
+ throw new IllegalArgumentException(msg);
}
-
- return bytes;
}
/**
- * Converts an integer into an IPv4 address.
+ * Converts an integer and a prefix length into an IPv4 prefix.
*
- * @param address an integer representing an IP value
- * @param netmask the CIDR value subnet mask
- * @return an IP address
+ * @param address an integer representing the IPv4 address
+ * @param prefixLength the prefix length
+ * @return an IP prefix
*/
- public static IpPrefix valueOf(int address, int netmask) {
- return new IpPrefix(Version.INET, bytes(address), netmask);
+ public static IpPrefix valueOf(int address, int prefixLength) {
+ return new IpPrefix(IpAddress.valueOf(address), prefixLength);
}
/**
- * Converts a dotted-decimal string (x.x.x.x) into an IPv4 address. The
- * string can also be in CIDR (slash) notation. If the netmask is omitted,
- * it will be set to DEFAULT_MASK (0).
+ * Converts a byte array and a prefix length into an IP prefix.
*
- * @param address a IP address in string form, e.g. "10.0.0.1", "10.0.0.1/24"
- * @return an IP address
+ * @param address the IP address value stored in network byte order
+ * @param prefixLength the prefix length
+ * @return an IP prefix
+ */
+ public static IpPrefix valueOf(byte[] address, int prefixLength) {
+ return new IpPrefix(IpAddress.valueOf(address), prefixLength);
+ }
+
+ /**
+ * Converts an IP address and a prefix length into IP prefix.
+ *
+ * @param address the IP address
+ * @param prefixLength the prefix length
+ * @return an IP prefix
+ */
+ public static IpPrefix valueOf(IpAddress address, int prefixLength) {
+ return new IpPrefix(address, prefixLength);
+ }
+
+ /**
+ * Converts a CIDR (slash) notation string (e.g., "10.1.0.0/16") into an
+ * IP prefix.
+ *
+ * @param value an IP prefix in string form, e.g. "10.1.0.0/16"
+ * @return an IP prefix
*/
public static IpPrefix valueOf(String address) {
+ final String[] parts = address.split("/");
+ if (parts.length != 2) {
+ String msg = "Malformed IP prefix string: " + address + "." +
+ "Address must take form \"x.x.x.x/y\"";
+ throw new IllegalArgumentException(msg);
+ }
+ IpAddress ipAddress = IpAddress.valueOf(parts[0]);
+ int prefixLength = Integer.parseInt(parts[1]);
- final String[] parts = address.split("\\/");
- if (parts.length > 2) {
- throw new IllegalArgumentException("Malformed IP address string; "
- + "Address must take form \"x.x.x.x\" or \"x.x.x.x/y\"");
- }
-
- int mask = DEFAULT_MASK;
- if (parts.length == 2) {
- mask = Integer.parseInt(parts[1]);
- if (mask > MAX_INET_MASK_LENGTH) {
- throw new IllegalArgumentException(
- "Value of subnet mask cannot exceed "
- + MAX_INET_MASK_LENGTH);
- }
- }
-
- final String[] net = parts[0].split("\\.");
- if (net.length != IpAddress.INET_BYTE_LENGTH) {
- throw new IllegalArgumentException("Malformed IP address string; "
- + "Address must have four decimal values separated by dots (.)");
- }
- final byte[] bytes = new byte[IpAddress.INET_BYTE_LENGTH];
- for (int i = 0; i < IpAddress.INET_BYTE_LENGTH; i++) {
- bytes[i] = (byte) Short.parseShort(net[i], 10);
- }
- return new IpPrefix(Version.INET, bytes, mask);
+ return new IpPrefix(ipAddress, prefixLength);
}
/**
- * Returns the IP version of this address.
+ * Returns the IP version of the prefix.
*
- * @return the version
+ * @return the IP version of the prefix
*/
- public Version version() {
- return this.version;
+ public IpAddress.Version version() {
+ return address.version();
}
/**
- * Returns the IP address as a byte array.
+ * Returns the IP address value of the prefix.
*
- * @return a byte array
+ * @return the IP address value of the prefix
*/
- public byte[] toOctets() {
- return Arrays.copyOf(this.octets, IpAddress.INET_BYTE_LENGTH);
+ public IpAddress address() {
+ return address;
}
/**
* Returns the IP address prefix length.
*
- * @return prefix length
+ * @return the IP address prefix length
*/
public int prefixLength() {
- return netmask;
+ return prefixLength;
}
/**
- * Returns the integral value of this IP address.
+ * Determines whether a given IP prefix is contained within this prefix.
*
- * @return the IP address's value as an integer
- */
- public int toInt() {
- int val = 0;
- for (int i = 0; i < octets.length; i++) {
- val <<= 8;
- val |= octets[i] & 0xff;
- }
- return val;
- }
-
- /**
- * Helper for computing the mask value from CIDR.
- *
- * @return an integer bitmask
- */
- private int mask() {
- int shift = MAX_INET_MASK_LENGTH - this.netmask;
- return ((Integer.MAX_VALUE >>> (shift - 1)) << shift);
- }
-
- /**
- * Returns the subnet mask in IpAddress form.
- *
- * @return the subnet mask as an IpAddress
- */
- public IpAddress netmask() {
- return IpAddress.valueOf(mask());
- }
-
- /**
- * Returns the network portion of this address as an IpAddress.
- * The netmask of the returned IpAddress is the current mask. If this
- * address doesn't have a mask, this returns an all-0 IpAddress.
- *
- * @return the network address or null
- */
- public IpPrefix network() {
- if (netmask == DEFAULT_MASK) {
- return new IpPrefix(version, ANY, DEFAULT_MASK);
- }
-
- byte[] net = new byte [4];
- byte[] mask = bytes(mask());
- for (int i = 0; i < IpAddress.INET_BYTE_LENGTH; i++) {
- net[i] = (byte) (octets[i] & mask[i]);
- }
- return new IpPrefix(version, net, netmask);
- }
-
- /**
- * Returns the host portion of the IPAddress, as an IPAddress.
- * The netmask of the returned IpAddress is the current mask. If this
- * address doesn't have a mask, this returns a copy of the current
- * address.
- *
- * @return the host address
- */
- public IpPrefix host() {
- if (netmask == DEFAULT_MASK) {
- new IpPrefix(version, octets, netmask);
- }
-
- byte[] host = new byte [IpAddress.INET_BYTE_LENGTH];
- byte[] mask = bytes(mask());
- for (int i = 0; i < IpAddress.INET_BYTE_LENGTH; i++) {
- host[i] = (byte) (octets[i] & ~mask[i]);
- }
- return new IpPrefix(version, host, netmask);
- }
-
- /**
- * Returns an IpAddress of the bytes contained in this prefix.
- * FIXME this is a hack for now and only works because IpPrefix doesn't
- * mask the input bytes on creation.
- *
- * @return the IpAddress
- */
- public IpAddress toIpAddress() {
- return IpAddress.valueOf(octets);
- }
-
- public boolean isMasked() {
- return mask() != 0;
- }
-
- /**
- * Determines whether a given address is contained within this IpAddress'
- * network.
- *
- * @param other another IP address that could be contained in this network
- * @return true if the other IP address is contained in this address'
- * network, otherwise false
+ * @param other the IP prefix to test
+ * @return true if the other IP prefix is contained in this prefix,
+ * otherwise false
*/
public boolean contains(IpPrefix other) {
- if (this.netmask <= other.netmask) {
- // Special case where they're both /32 addresses
- if (this.netmask == MAX_INET_MASK_LENGTH) {
- return Arrays.equals(octets, other.octets);
- }
-
- // Mask the other address with our network mask
- IpPrefix otherMasked =
- IpPrefix.valueOf(other.octets, netmask).network();
-
- return network().equals(otherMasked);
+ if (this.prefixLength > other.prefixLength) {
+ return false; // This prefix has smaller prefix size
}
- return false;
+
+ //
+ // Mask the other address with my prefix length.
+ // If the other prefix is within this prefix, the masked address must
+ // be same as the address of this prefix.
+ //
+ IpAddress maskedAddr =
+ IpAddress.makeMaskedAddress(other.address, this.prefixLength);
+ return this.address.equals(maskedAddr);
}
- public boolean contains(IpAddress address) {
- // Need to get the network address because prefixes aren't automatically
- // masked on creation
- IpPrefix meMasked = network();
-
- IpPrefix otherMasked =
- IpPrefix.valueOf(address.toOctets(), netmask).network();
-
- return Arrays.equals(meMasked.octets, otherMasked.octets);
+ /**
+ * Determines whether a given IP address is contained within this prefix.
+ *
+ * @param other the IP address to test
+ * @return true if the IP address is contained in this prefix, otherwise
+ * false
+ */
+ public boolean contains(IpAddress other) {
+ //
+ // Mask the other address with my prefix length.
+ // If the other prefix is within this prefix, the masked address must
+ // be same as the address of this prefix.
+ //
+ IpAddress maskedAddr =
+ IpAddress.makeMaskedAddress(other, this.prefixLength);
+ return this.address.equals(maskedAddr);
}
@Override
public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + netmask;
- result = prime * result + Arrays.hashCode(octets);
- result = prime * result + ((version == null) ? 0 : version.hashCode());
- return result;
+ return Objects.hash(address, prefixLength);
}
@Override
@@ -301,46 +190,26 @@
if (this == obj) {
return true;
}
- if (obj == null) {
- return false;
- }
- if (getClass() != obj.getClass()) {
+ if ((obj == null) || (getClass() != obj.getClass())) {
return false;
}
IpPrefix other = (IpPrefix) obj;
- if (netmask != other.netmask) {
- return false;
- }
- // TODO not quite right until we mask the input
- if (!Arrays.equals(octets, other.octets)) {
- return false;
- }
- if (version != other.version) {
- return false;
- }
- return true;
+ return ((prefixLength == other.prefixLength) &&
+ address.equals(other.address));
}
@Override
/*
* (non-Javadoc)
- * format is "x.x.x.x" for non-masked (netmask 0) addresses,
- * and "x.x.x.x/y" for masked addresses.
+ * The format is "x.x.x.x/y" for IPv4 prefixes.
*
* @see java.lang.Object#toString()
*/
public String toString() {
final StringBuilder builder = new StringBuilder();
- for (final byte b : this.octets) {
- if (builder.length() > 0) {
- builder.append(".");
- }
- builder.append(String.format("%d", b & 0xff));
- }
- if (netmask != DEFAULT_MASK) {
- builder.append("/");
- builder.append(String.format("%d", netmask));
- }
+ builder.append(address.toString());
+ builder.append("/");
+ builder.append(String.format("%d", prefixLength));
return builder.toString();
}
}
diff --git a/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java b/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java
index 90ebafd..ce6a1e3 100644
--- a/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java
+++ b/utils/misc/src/test/java/org/onlab/packet/IpPrefixTest.java
@@ -22,7 +22,7 @@
import java.util.Arrays;
import org.junit.Test;
-import org.onlab.packet.IpPrefix.Version;
+import org.onlab.packet.IpAddress.Version;
import com.google.common.testing.EqualsTester;
@@ -30,8 +30,9 @@
private static final byte [] BYTES1 = new byte [] {0xa, 0x0, 0x0, 0xa};
private static final byte [] BYTES2 = new byte [] {0xa, 0x0, 0x0, 0xb};
- private static final int INTVAL1 = 167772170;
- private static final int INTVAL2 = 167772171;
+ private static final int INTVAL0 = 0x0a000000;
+ private static final int INTVAL1 = 0x0a00000a;
+ private static final int INTVAL2 = 0x0a00000b;
private static final String STRVAL = "10.0.0.12/16";
private static final int MASK_LENGTH = 16;
@@ -59,27 +60,29 @@
@Test
public void basics() {
IpPrefix ip1 = IpPrefix.valueOf(BYTES1, MASK_LENGTH);
- final byte [] bytes = new byte [] {0xa, 0x0, 0x0, 0xa};
+ final byte [] bytes = new byte [] {0xa, 0x0, 0x0, 0x0};
- //check fields
+ // check fields
assertEquals("incorrect IP Version", Version.INET, ip1.version());
assertEquals("incorrect netmask", 16, ip1.prefixLength());
- assertTrue("faulty toOctets()", Arrays.equals(bytes, ip1.toOctets()));
- assertEquals("faulty toInt()", INTVAL1, ip1.toInt());
- assertEquals("faulty toString()", "10.0.0.10/16", ip1.toString());
+ assertTrue("faulty toOctets()",
+ Arrays.equals(bytes, ip1.address().toOctets()));
+ assertEquals("faulty toInt()", INTVAL0, ip1.address().toInt());
+ assertEquals("faulty toString()", "10.0.0.0/16", ip1.toString());
}
@Test
public void netmasks() {
// masked
IpPrefix ip1 = IpPrefix.valueOf(BYTES1, MASK_LENGTH);
-
- IpPrefix host = IpPrefix.valueOf("0.0.0.10/16");
- IpPrefix network = IpPrefix.valueOf("10.0.0.0/16");
- assertEquals("incorrect host address", host, ip1.host());
- assertEquals("incorrect network address", network, ip1.network());
- assertEquals("incorrect netmask", "255.255.0.0", ip1.netmask().toString());
-
+ IpPrefix ip2 = IpPrefix.valueOf("10.0.0.10/16");
+ IpPrefix ip3 = IpPrefix.valueOf("10.0.0.0/16");
+ assertEquals("incorrect binary masked address",
+ ip1.toString(), "10.0.0.0/16");
+ assertEquals("incorrect string masked address",
+ ip2.toString(), "10.0.0.0/16");
+ assertEquals("incorrect network address",
+ ip2.toString(), "10.0.0.0/16");
}
@Test