Reimplementation of classes Ip4Address/Ip6Address/Ip4Prefix/Ip6Prefix
and the corresponding unit tests.

* Reimplemented classes Ip4Address and Ip6Address by inheriting from
  class IpAddress
* Reimplemented classes Ip4Prefix and Ip6Prefix by inheriting from
  class IpPrefix
* Reimplemented the unit tests Ip4AddressTest and Ip6AddressTest to
  match the corresponding IpAddressTest unit tests
* Reimplemented the unit tests Ip4PrefixTest and Ip6PrefixTest to
  match the corresponding IpPrefixTest unit tests
* Minor refactoring/cleanup of classes IpAddress and IpPrefix
diff --git a/utils/misc/src/main/java/org/onlab/packet/Ip4Address.java b/utils/misc/src/main/java/org/onlab/packet/Ip4Address.java
index 7b7b989..114f126 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ip4Address.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ip4Address.java
@@ -15,203 +15,160 @@
  */
 package org.onlab.packet;
 
+import java.net.InetAddress;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
 import java.nio.ByteBuffer;
-import static com.google.common.base.Preconditions.checkNotNull;
+import java.util.Arrays;
+
+import com.google.common.net.InetAddresses;
 
 /**
- * The class representing an IPv4 address.
+ * A class representing an IPv4 address.
  * This class is immutable.
  */
-public final class Ip4Address implements Comparable<Ip4Address> {
-    private final int value;
-
-    /** The length of the address in bytes (octets). */
-    public static final int BYTE_LENGTH = 4;
-
-    /** The length of the address in bits. */
-    public static final int BIT_LENGTH = BYTE_LENGTH * Byte.SIZE;
+public final class Ip4Address extends IpAddress {
+    public static final IpAddress.Version VERSION = IpAddress.Version.INET;
+    public static final int BYTE_LENGTH = IpAddress.INET_BYTE_LENGTH;
+    public static final int BIT_LENGTH = IpAddress.INET_BIT_LENGTH;
 
     /**
-     * Default constructor.
+     * Constructor for given IP address version and address octets.
+     *
+     * @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
      */
-    public Ip4Address() {
-        this.value = 0;
+    private Ip4Address(byte[] value) {
+        super(VERSION, value);
     }
 
     /**
-     * Copy constructor.
+     * Returns the integer value of this IPv4 address.
      *
-     * @param other the object to copy from
+     * @return the IPv4 address's value as an integer
      */
-    public Ip4Address(Ip4Address other) {
-        this.value = other.value;
+    public int toInt() {
+        ByteBuffer bb = ByteBuffer.wrap(super.toOctets());
+        return bb.getInt();
     }
 
     /**
-     * Constructor from an integer value.
+     * Converts an integer into an IPv4 address.
      *
-     * @param value the value to use
+     * @param value an integer representing an IPv4 address value
+     * @return an IPv4 address
      */
-    public Ip4Address(int value) {
-        this.value = value;
+    public static Ip4Address valueOf(int value) {
+        byte[] bytes =
+            ByteBuffer.allocate(INET_BYTE_LENGTH).putInt(value).array();
+        return new Ip4Address(bytes);
     }
 
     /**
-     * Constructor from a byte array with the IPv4 address stored in network
-     * byte order (i.e., the most significant byte first).
+     * Converts a byte array into an IPv4 address.
      *
-     * @param value the value to use
+     * @param value the IPv4 address value stored in network byte order
+     * (i.e., the most significant byte first)
+     * @return an IPv4 address
+     * @throws IllegalArgumentException if the argument is invalid
      */
-    public Ip4Address(byte[] value) {
-        this(value, 0);
+    public static Ip4Address valueOf(byte[] value) {
+        return new Ip4Address(value);
     }
 
     /**
-     * Constructor from a byte array with the IPv4 address stored in network
-     * byte order (i.e., the most significant byte first), and a given offset
-     * from the beginning of the byte array.
-     *
+     * Converts a byte array and a given offset from the beginning of the
+     * array into an IPv4 address.
+     * <p>
+     * The IP address is stored in network byte order (i.e., the most
+     * significant byte first).
+     * </p>
      * @param value the value to use
      * @param offset the offset in bytes from the beginning of the byte array
+     * @return an IPv4 address
+     * @throws IllegalArgumentException if the arguments are invalid
      */
-    public Ip4Address(byte[] value, int offset) {
-        checkNotNull(value);
-
-        // Verify the arguments
-        if ((offset < 0) || (offset + BYTE_LENGTH > value.length)) {
-            String msg;
-            if (value.length < BYTE_LENGTH) {
-                msg = "Invalid IPv4 address array: array length: " +
-                    value.length + ". Must be at least " + BYTE_LENGTH;
-            } else {
-                msg = "Invalid IPv4 address array: array offset: " +
-                    offset + ". Must be in the interval [0, " +
-                    (value.length - BYTE_LENGTH) + "]";
-            }
-            throw new IllegalArgumentException(msg);
-        }
-
-        // Read the address
-        ByteBuffer bb = ByteBuffer.wrap(value);
-        this.value = bb.getInt(offset);
+    public static Ip4Address valueOf(byte[] value, int offset) {
+        IpAddress.checkArguments(VERSION, value, offset);
+        byte[] bc = Arrays.copyOfRange(value, offset, value.length);
+        return Ip4Address.valueOf(bc);
     }
 
     /**
-     * Constructs an IPv4 address from a string representation of the address.
-     *<p>
-     * Example: "1.2.3.4"
+     * Converts an InetAddress into an IPv4 address.
      *
-     * @param value the value to use
+     * @param inetAddress the InetAddress value to use. It must contain an IPv4
+     * address
+     * @return an IPv4 address
+     * @throws IllegalArgumentException if the argument is invalid
      */
-    public Ip4Address(String value) {
-        checkNotNull(value);
-
-        String[] splits = value.split("\\.");
-        if (splits.length != 4) {
-            final String msg = "Invalid IPv4 address string: " + value;
+    public static Ip4Address valueOf(InetAddress inetAddress) {
+        byte[] bytes = inetAddress.getAddress();
+        if (inetAddress instanceof Inet4Address) {
+            return new Ip4Address(bytes);
+        }
+        if ((inetAddress instanceof Inet6Address) ||
+            (bytes.length == INET6_BYTE_LENGTH)) {
+            final String msg = "Invalid IPv4 version address string: " +
+                inetAddress.toString();
             throw new IllegalArgumentException(msg);
         }
-
-        int result = 0;
-        for (int i = 0; i < BYTE_LENGTH; i++) {
-            result |= Integer.parseInt(splits[i]) <<
-                ((BYTE_LENGTH - (i + 1)) * Byte.SIZE);
+        // Use the number of bytes as a hint
+        if (bytes.length == INET_BYTE_LENGTH) {
+            return new Ip4Address(bytes);
         }
-        this.value = result;
+        final String msg = "Unrecognized IP version address string: " +
+            inetAddress.toString();
+        throw new IllegalArgumentException(msg);
     }
 
     /**
-     * Gets the IPv4 address as a byte array.
+     * Converts an IPv4 string literal (e.g., "10.2.3.4") into an IP address.
      *
-     * @return a byte array with the IPv4 address stored in network byte order
-     * (i.e., the most significant byte first).
+     * @param value an IPv4 address value in string form
+     * @return an IPv4 address
+     * @throws IllegalArgumentException if the argument is invalid
      */
-    public byte[] toOctets() {
-        return ByteBuffer.allocate(BYTE_LENGTH).putInt(value).array();
+    public static Ip4Address 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 IPv4 network mask prefix.
      *
-     * @param prefixLen the length of the mask prefix. Must be in the interval
-     * [0, 32].
+     * @param prefixLength the length of the mask prefix. Must be in the
+     * interval [0, 32]
      * @return a new IPv4 address that contains a mask prefix of the
      * specified length
+     * @throws IllegalArgumentException if the argument is invalid
      */
-    public static Ip4Address makeMaskPrefix(int prefixLen) {
-        // Verify the prefix length
-        if ((prefixLen < 0) || (prefixLen > Ip4Address.BIT_LENGTH)) {
-            final String msg = "Invalid IPv4 prefix length: " + prefixLen +
-                ". Must be in the interval [0, 32].";
-            throw new IllegalArgumentException(msg);
-        }
-
-        long v =
-            (0xffffffffL << (Ip4Address.BIT_LENGTH - prefixLen)) & 0xffffffffL;
-        return new Ip4Address((int) v);
+    public static Ip4Address makeMaskPrefix(int prefixLength) {
+        byte[] mask = IpAddress.makeMaskPrefixArray(VERSION, prefixLength);
+        return new Ip4Address(mask);
     }
 
     /**
      * Creates an IPv4 address by masking it with a network mask of given
      * mask length.
      *
-     * @param addr the address to mask
-     * @param prefixLen the length of the mask prefix. Must be in the interval
-     * [0, 32].
+     * @param address the address to mask
+     * @param prefixLength the length of the mask prefix. Must be in the
+     * interval [0, 32]
      * @return a new IPv4 address that is masked with a mask prefix of the
      * specified length
+     * @throws IllegalArgumentException if the prefix length is invalid
      */
-    public static Ip4Address makeMaskedAddress(final Ip4Address addr,
-                                               int prefixLen) {
-        Ip4Address mask = Ip4Address.makeMaskPrefix(prefixLen);
-        long v = addr.value & mask.value;
-
-        return new Ip4Address((int) v);
-    }
-
-    /**
-     * Gets the value of the IPv4 address.
-     *
-     * @return the value of the IPv4 address
-     */
-    public int getValue() {
-        return value;
-    }
-
-    /**
-     * Converts the IPv4 value to a '.' separated string.
-     *
-     * @return the IPv4 value as a '.' separated string
-     */
-    @Override
-    public String toString() {
-        return ((this.value >> 24) & 0xff) + "." +
-                ((this.value >> 16) & 0xff) + "." +
-                ((this.value >> 8) & 0xff) + "." +
-                (this.value & 0xff);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (!(o instanceof Ip4Address)) {
-            return false;
-        }
-        Ip4Address other = (Ip4Address) o;
-        if (this.value != other.value) {
-            return false;
-        }
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        return this.value;
-    }
-
-    @Override
-    public int compareTo(Ip4Address o) {
-        Long lv = ((long) this.value) & 0xffffffffL;
-        Long rv = ((long) o.value) & 0xffffffffL;
-        return lv.compareTo(rv);
+    public static Ip4Address makeMaskedAddress(final Ip4Address address,
+                                               int prefixLength) {
+        byte[] net = makeMaskedAddressArray(address, prefixLength);
+        return Ip4Address.valueOf(net);
     }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/Ip4Prefix.java b/utils/misc/src/main/java/org/onlab/packet/Ip4Prefix.java
index e3b5246..ca9fc61 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ip4Prefix.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ip4Prefix.java
@@ -15,110 +15,90 @@
  */
 package org.onlab.packet;
 
-import java.util.Objects;
-
 /**
  * The class representing an IPv4 network address.
  * This class is immutable.
  */
-public final class Ip4Prefix {
-    private final Ip4Address address;           // The IPv4 address
-    private final short prefixLen;              // The prefix length
+public final class Ip4Prefix extends IpPrefix {
+    public static final IpAddress.Version VERSION = IpAddress.Version.INET;
+    // Maximum network mask length
+    public static final int MAX_MASK_LENGTH = IpPrefix.MAX_INET_MASK_LENGTH;
 
     /**
-     * Default constructor.
+     * Constructor for given IPv4 address, and a prefix length.
+     *
+     * @param address the IPv4 address
+     * @param prefixLength the prefix length
+     * @throws IllegalArgumentException if the prefix length value is invalid
      */
-    public Ip4Prefix() {
-        this.address = new Ip4Address();
-        this.prefixLen = 0;
+    private Ip4Prefix(Ip4Address address, int prefixLength) {
+        super(address, prefixLength);
     }
 
     /**
-     * Copy constructor.
+     * Returns the IPv4 address value of the prefix.
      *
-     * @param other the object to copy from
+     * @return the IPv4 address value of the prefix
      */
-    public Ip4Prefix(Ip4Prefix other) {
-        this.address = new Ip4Address(other.address);
-        this.prefixLen = other.prefixLen;
+    public Ip4Address address() {
+        IpAddress a = super.address();
+        return (Ip4Address) a;
     }
 
     /**
-     * Constructor for a given address and prefix length.
+     * Converts an integer and a prefix length into an IPv4 prefix.
      *
-     * @param address   the address to use
-     * @param prefixLen the prefix length to use
+     * @param address an integer representing the IPv4 address
+     * @param prefixLength the prefix length
+     * @return an IPv4 prefix
+     * @throws IllegalArgumentException if the prefix length value is invalid
      */
-    public Ip4Prefix(Ip4Address address, short prefixLen) {
-        this.address = Ip4Address.makeMaskedAddress(address, prefixLen);
-        this.prefixLen = prefixLen;
+    public static Ip4Prefix valueOf(int address, int prefixLength) {
+        return new Ip4Prefix(Ip4Address.valueOf(address), prefixLength);
     }
 
     /**
-     * Constructs an IPv4 prefix from a string representation of the
-     * prefix.
-     *<p>
-     * Example: "1.2.0.0/16"
+     * Converts a byte array and a prefix length into an IPv4 prefix.
      *
-     * @param value the value to use
+     * @param address the IPv4 address value stored in network byte order
+     * @param prefixLength the prefix length
+     * @return an IPv4 prefix
+     * @throws IllegalArgumentException if the prefix length value is invalid
      */
-    public Ip4Prefix(String value) {
-        String[] splits = value.split("/");
-        if (splits.length != 2) {
-            throw new IllegalArgumentException("Specified IPv4 prefix must contain an IPv4 " +
-                    "address and a prefix length separated by '/'");
+    public static Ip4Prefix valueOf(byte[] address, int prefixLength) {
+        return new Ip4Prefix(Ip4Address.valueOf(address), prefixLength);
+    }
+
+    /**
+     * Converts an IPv4 address and a prefix length into an IPv4 prefix.
+     *
+     * @param address the IPv4 address
+     * @param prefixLength the prefix length
+     * @return an IPv4 prefix
+     * @throws IllegalArgumentException if the prefix length value is invalid
+     */
+    public static Ip4Prefix valueOf(Ip4Address address, int prefixLength) {
+        return new Ip4Prefix(address, prefixLength);
+    }
+
+    /**
+     * Converts a CIDR (slash) notation string (e.g., "10.1.0.0/16")
+     * into an IPv4 prefix.
+     *
+     * @param address an IP prefix in string form (e.g., "10.1.0.0/16")
+     * @return an IPv4 prefix
+     * @throws IllegalArgumentException if the arguments are invalid
+     */
+    public static Ip4Prefix valueOf(String address) {
+        final String[] parts = address.split("/");
+        if (parts.length != 2) {
+            String msg = "Malformed IPv4 prefix string: " + address + "." +
+                "Address must take form \"x.x.x.x/y\"";
+            throw new IllegalArgumentException(msg);
         }
-        this.prefixLen = Short.decode(splits[1]);
-        this.address = Ip4Address.makeMaskedAddress(new Ip4Address(splits[0]),
-                this.prefixLen);
-    }
+        Ip4Address ipAddress = Ip4Address.valueOf(parts[0]);
+        int prefixLength = Integer.parseInt(parts[1]);
 
-    /**
-     * Gets the address value of the IPv4 prefix.
-     *
-     * @return the address value of the IPv4 prefix
-     */
-    public Ip4Address getAddress() {
-        return address;
-    }
-
-    /**
-     * Gets the prefix length value of the IPv4 prefix.
-     *
-     * @return the prefix length value of the IPv4 prefix
-     */
-    public short getPrefixLen() {
-        return prefixLen;
-    }
-
-    /**
-     * Converts the IPv4 prefix value to an "address/prefixLen" string.
-     *
-     * @return the IPv4 prefix value as an "address/prefixLen" string
-     */
-    @Override
-    public String toString() {
-        return this.address.toString() + "/" + this.prefixLen;
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (other == this) {
-            return true;
-        }
-
-        if (!(other instanceof Ip4Prefix)) {
-            return false;
-        }
-
-        Ip4Prefix otherIp4Prefix = (Ip4Prefix) other;
-
-        return Objects.equals(this.address, otherIp4Prefix.address)
-                && this.prefixLen == otherIp4Prefix.prefixLen;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(address, prefixLen);
+        return new Ip4Prefix(ipAddress, prefixLength);
     }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/Ip6Address.java b/utils/misc/src/main/java/org/onlab/packet/Ip6Address.java
index 8ad9112..d353422 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ip6Address.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ip6Address.java
@@ -16,260 +16,137 @@
 package org.onlab.packet;
 
 import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.util.Objects;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.util.Arrays;
 
 import com.google.common.net.InetAddresses;
-import com.google.common.primitives.UnsignedLongs;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
 
 /**
- * The class representing an IPv6 address.
+ * A class representing an IPv6 address.
  * This class is immutable.
  */
-public final class Ip6Address implements Comparable<Ip6Address> {
-    private final long valueHigh;    // The higher (more significant) 64 bits
-    private final long valueLow;     // The lower (less significant) 64 bits
-
-    /** The length of the address in bytes (octets). */
-    public static final int BYTE_LENGTH = 16;
-
-    /** The length of the address in bits. */
-    public static final int BIT_LENGTH = BYTE_LENGTH * Byte.SIZE;
+public final class Ip6Address extends IpAddress {
+    public static final IpAddress.Version VERSION = IpAddress.Version.INET6;
+    public static final int BYTE_LENGTH = IpAddress.INET6_BYTE_LENGTH;
+    public static final int BIT_LENGTH = IpAddress.INET6_BIT_LENGTH;
 
     /**
-     * Default constructor.
+     * Constructor for given IP address version and address octets.
+     *
+     * @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
      */
-    public Ip6Address() {
-        this.valueHigh = 0;
-        this.valueLow = 0;
+    private Ip6Address(byte[] value) {
+        super(VERSION, value);
     }
 
     /**
-     * Copy constructor.
+     * Converts a byte array into an IPv6 address.
      *
-     * @param other the object to copy from
+     * @param value the IPv6 address value stored in network byte order
+     * (i.e., the most significant byte first)
+     * @return an IPv6 address
+     * @throws IllegalArgumentException if the argument is invalid
      */
-    public Ip6Address(Ip6Address other) {
-        this.valueHigh = other.valueHigh;
-        this.valueLow = other.valueLow;
+    public static Ip6Address valueOf(byte[] value) {
+        return new Ip6Address(value);
     }
 
     /**
-     * Constructor from integer values.
-     *
-     * @param valueHigh the higher (more significant) 64 bits of the address
-     * @param valueLow  the lower (less significant) 64 bits of the address
-     */
-    public Ip6Address(long valueHigh, long valueLow) {
-        this.valueHigh = valueHigh;
-        this.valueLow = valueLow;
-    }
-
-    /**
-     * Constructor from a byte array with the IPv6 address stored in network
-     * byte order (i.e., the most significant byte first).
-     *
-     * @param value the value to use
-     */
-    public Ip6Address(byte[] value) {
-        this(value, 0);
-    }
-
-    /**
-     * Constructor from a byte array with the IPv6 address stored in network
-     * byte order (i.e., the most significant byte first), and a given offset
-     * from the beginning of the byte array.
-     *
+     * Converts a byte array and a given offset from the beginning of the
+     * array into an IPv6 address.
+     * <p>
+     * The IP address is stored in network byte order (i.e., the most
+     * significant byte first).
+     * </p>
      * @param value the value to use
      * @param offset the offset in bytes from the beginning of the byte array
+     * @return an IPv6 address
+     * @throws IllegalArgumentException if the arguments are invalid
      */
-    public Ip6Address(byte[] value, int offset) {
-        checkNotNull(value);
-
-        // Verify the arguments
-        if ((offset < 0) || (offset + BYTE_LENGTH > value.length)) {
-            String msg;
-            if (value.length < BYTE_LENGTH) {
-                msg = "Invalid IPv6 address array: array length: " +
-                    value.length + ". Must be at least " + BYTE_LENGTH;
-            } else {
-                msg = "Invalid IPv6 address array: array offset: " +
-                    offset + ". Must be in the interval [0, " +
-                    (value.length - BYTE_LENGTH) + "]";
-            }
-            throw new IllegalArgumentException(msg);
-        }
-
-        // Read the address
-        ByteBuffer bb = ByteBuffer.wrap(value);
-        bb.position(offset);
-        this.valueHigh = bb.getLong();
-        this.valueLow = bb.getLong();
+    public static Ip6Address valueOf(byte[] value, int offset) {
+        IpAddress.checkArguments(VERSION, value, offset);
+        byte[] bc = Arrays.copyOfRange(value, offset, value.length);
+        return Ip6Address.valueOf(bc);
     }
 
     /**
-     * Constructs an IPv6 address from a string representation of the address.
-     *<p>
-     * Example: "1111:2222::8888"
+     * Converts an InetAddress into an IPv6 address.
      *
-     * @param value the value to use
+     * @param inetAddress the InetAddress value to use. It must contain an IPv6
+     * address
+     * @return an IPv6 address
+     * @throws IllegalArgumentException if the argument is invalid
      */
-    public Ip6Address(String value) {
-        checkNotNull(value);
-
-        if (value.isEmpty()) {
-            final String msg = "Specified IPv6 cannot be an empty string";
+    public static Ip6Address valueOf(InetAddress inetAddress) {
+        byte[] bytes = inetAddress.getAddress();
+        if (inetAddress instanceof Inet6Address) {
+            return new Ip6Address(bytes);
+        }
+        if ((inetAddress instanceof Inet4Address) ||
+            (bytes.length == INET_BYTE_LENGTH)) {
+            final String msg = "Invalid IPv6 version address string: " +
+                inetAddress.toString();
             throw new IllegalArgumentException(msg);
         }
-        InetAddress addr = null;
+        // Use the number of bytes as a hint
+        if (bytes.length == INET6_BYTE_LENGTH) {
+            return new Ip6Address(bytes);
+        }
+        final String msg = "Unrecognized IP version address string: " +
+            inetAddress.toString();
+        throw new IllegalArgumentException(msg);
+    }
+
+    /**
+     * Converts an IPv6 string literal (e.g., "1111:2222::8888") into an IP
+     * address.
+     *
+     * @param value an IPv6 address value in string form
+     * @return an IPv6 address
+     * @throws IllegalArgumentException if the argument is invalid
+     */
+    public static Ip6Address valueOf(String value) {
+        InetAddress inetAddress = null;
         try {
-            addr = InetAddresses.forString(value);
+            inetAddress = InetAddresses.forString(value);
         } catch (IllegalArgumentException e) {
-            final String msg = "Invalid IPv6 address string: " + value;
+            final String msg = "Invalid IP address string: " + value;
             throw new IllegalArgumentException(msg);
         }
-        byte[] bytes = addr.getAddress();
-        ByteBuffer bb = ByteBuffer.wrap(bytes);
-        this.valueHigh = bb.getLong();
-        this.valueLow = bb.getLong();
-    }
-
-    /**
-     * Gets the IPv6 address as a byte array.
-     *
-     * @return a byte array with the IPv6 address stored in network byte order
-     * (i.e., the most significant byte first).
-     */
-    public byte[] toOctets() {
-        return ByteBuffer.allocate(BYTE_LENGTH)
-            .putLong(valueHigh).putLong(valueLow).array();
+        return valueOf(inetAddress);
     }
 
     /**
      * Creates an IPv6 network mask prefix.
      *
-     * @param prefixLen the length of the mask prefix. Must be in the interval
-     * [0, 128].
+     * @param prefixLength the length of the mask prefix. Must be in the
+     * interval [0, 128]
      * @return a new IPv6 address that contains a mask prefix of the
      * specified length
+     * @throws IllegalArgumentException if the arguments are invalid
      */
-    public static Ip6Address makeMaskPrefix(int prefixLen) {
-        long vh, vl;
-
-        // Verify the prefix length
-        if ((prefixLen < 0) || (prefixLen > Ip6Address.BIT_LENGTH)) {
-            final String msg = "Invalid IPv6 prefix length: " + prefixLen +
-                ". Must be in the interval [0, 128].";
-            throw new IllegalArgumentException(msg);
-        }
-
-        if (prefixLen == 0) {
-            //
-            // NOTE: Apparently, the result of "<< 64" shifting to the left
-            // results in all 1s instead of all 0s, hence we handle it as
-            // a special case.
-            //
-            vh = 0;
-            vl = 0;
-        } else if (prefixLen <= 64) {
-            vh = (0xffffffffffffffffL << (64 - prefixLen)) & 0xffffffffffffffffL;
-            vl = 0;
-        } else {
-            vh = -1L;           // All 1s
-            vl = (0xffffffffffffffffL << (128 - prefixLen)) & 0xffffffffffffffffL;
-        }
-        return new Ip6Address(vh, vl);
+    public static Ip6Address makeMaskPrefix(int prefixLength) {
+        byte[] mask = IpAddress.makeMaskPrefixArray(VERSION, prefixLength);
+        return new Ip6Address(mask);
     }
 
     /**
      * Creates an IPv6 address by masking it with a network mask of given
      * mask length.
      *
-     * @param addr the address to mask
-     * @param prefixLen the length of the mask prefix. Must be in the interval
-     * [0, 128].
+     * @param address the address to mask
+     * @param prefixLength the length of the mask prefix. Must be in the
+     * interval [0, 128]
      * @return a new IPv6 address that is masked with a mask prefix of the
      * specified length
+     * @throws IllegalArgumentException if the prefix length is invalid
      */
-    public static Ip6Address makeMaskedAddress(final Ip6Address addr,
-                                               int prefixLen) {
-        Ip6Address mask = Ip6Address.makeMaskPrefix(prefixLen);
-        long vh = addr.valueHigh & mask.valueHigh;
-        long vl = addr.valueLow & mask.valueLow;
-
-        return new Ip6Address(vh, vl);
-    }
-
-    /**
-     * Gets the value of the higher (more significant) 64 bits of the address.
-     *
-     * @return the value of the higher (more significant) 64 bits of the
-     * address
-     */
-    public long getValueHigh() {
-        return valueHigh;
-    }
-
-    /**
-     * Gets the value of the lower (less significant) 64 bits of the address.
-     *
-     * @return the value of the lower (less significant) 64 bits of the
-     * address
-     */
-    public long getValueLow() {
-        return valueLow;
-    }
-
-    /**
-     * Converts the IPv6 value to a ':' separated string.
-     *
-     * @return the IPv6 value as a ':' separated string
-     */
-    @Override
-    public String toString() {
-        ByteBuffer bb = ByteBuffer.allocate(Ip6Address.BYTE_LENGTH);
-        bb.putLong(valueHigh);
-        bb.putLong(valueLow);
-        InetAddress inetAddr = null;
-        try {
-            inetAddr = InetAddress.getByAddress(bb.array());
-        } catch (UnknownHostException e) {
-            // Should never happen
-            checkState(false, "Internal error: Ip6Address.toString()");
-            return "::";
-        }
-        return InetAddresses.toAddrString(inetAddr);
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (!(o instanceof Ip6Address)) {
-            return false;
-        }
-        Ip6Address other = (Ip6Address) o;
-        return this.valueHigh == other.valueHigh
-                && this.valueLow == other.valueLow;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(valueHigh, valueLow);
-    }
-
-    @Override
-    public int compareTo(Ip6Address o) {
-        // Compare the high-order 64-bit value
-        if (this.valueHigh != o.valueHigh) {
-            return UnsignedLongs.compare(this.valueHigh, o.valueHigh);
-        }
-        // Compare the low-order 64-bit value
-        if (this.valueLow != o.valueLow) {
-            return UnsignedLongs.compare(this.valueLow, o.valueLow);
-        }
-        return 0;
+    public static Ip6Address makeMaskedAddress(final Ip6Address address,
+                                               int prefixLength) {
+        byte[] net = makeMaskedAddressArray(address, prefixLength);
+        return Ip6Address.valueOf(net);
     }
 }
diff --git a/utils/misc/src/main/java/org/onlab/packet/Ip6Prefix.java b/utils/misc/src/main/java/org/onlab/packet/Ip6Prefix.java
index 5422ae1..ca9985e 100644
--- a/utils/misc/src/main/java/org/onlab/packet/Ip6Prefix.java
+++ b/utils/misc/src/main/java/org/onlab/packet/Ip6Prefix.java
@@ -15,110 +15,79 @@
  */
 package org.onlab.packet;
 
-import java.util.Objects;
-
 /**
  * The class representing an IPv6 network address.
  * This class is immutable.
  */
-public final class Ip6Prefix {
-    private final Ip6Address address;           // The IPv6 address
-    private final short prefixLen;              // The prefix length
+public final class Ip6Prefix extends IpPrefix {
+    public static final IpAddress.Version VERSION = IpAddress.Version.INET6;
+    // Maximum network mask length
+    public static final int MAX_MASK_LENGTH = IpPrefix.MAX_INET6_MASK_LENGTH;
 
     /**
-     * Default constructor.
+     * Constructor for given IPv6 address, and a prefix length.
+     *
+     * @param address the IPv6 address
+     * @param prefixLength the prefix length
+     * @throws IllegalArgumentException if the prefix length value is invalid
      */
-    public Ip6Prefix() {
-        this.address = new Ip6Address();
-        this.prefixLen = 0;
+    private Ip6Prefix(Ip6Address address, int prefixLength) {
+        super(address, prefixLength);
     }
 
     /**
-     * Copy constructor.
+     * Returns the IPv6 address value of the prefix.
      *
-     * @param other the object to copy from
+     * @return the IPv6 address value of the prefix
      */
-    public Ip6Prefix(Ip6Prefix other) {
-        this.address = new Ip6Address(other.address);
-        this.prefixLen = other.prefixLen;
+    public Ip6Address address() {
+        IpAddress a = super.address();
+        return (Ip6Address) a;
     }
 
     /**
-     * Constructor for a given address and prefix length.
+     * Converts a byte array and a prefix length into an IPv6 prefix.
      *
-     * @param address   the address to use
-     * @param prefixLen the prefix length to use
+     * @param address the IPv6 address value stored in network byte order
+     * @param prefixLength the prefix length
+     * @return an IPv6 prefix
+     * @throws IllegalArgumentException if the prefix length value is invalid
      */
-    public Ip6Prefix(Ip6Address address, short prefixLen) {
-        this.address = Ip6Address.makeMaskedAddress(address, prefixLen);
-        this.prefixLen = prefixLen;
+    public static Ip6Prefix valueOf(byte[] address, int prefixLength) {
+        return new Ip6Prefix(Ip6Address.valueOf(address), prefixLength);
     }
 
     /**
-     * Constructs an IPv6 prefix from a string representation of the
-     * prefix.
-     *<p>
-     * Example: "1111:2222::/32"
+     * Converts an IPv6 address and a prefix length into an IPv6 prefix.
      *
-     * @param value the value to use
+     * @param address the IPv6 address
+     * @param prefixLength the prefix length
+     * @return an IPv6 prefix
+     * @throws IllegalArgumentException if the prefix length value is invalid
      */
-    public Ip6Prefix(String value) {
-        String[] splits = value.split("/");
-        if (splits.length != 2) {
-            throw new IllegalArgumentException("Specified IPv6 prefix must contain an IPv6 " +
-                    "address and a prefix length separated by '/'");
+    public static Ip6Prefix valueOf(Ip6Address address, int prefixLength) {
+        return new Ip6Prefix(address, prefixLength);
+    }
+
+    /**
+     * Converts a CIDR (slash) notation string (e.g., "1111:2222::/64")
+     * into an IPv6 prefix.
+     *
+     * @param address an IP prefix in string form (e.g.,"1111:2222::/64")
+     * @return an IPv6 prefix
+     * @throws IllegalArgumentException if the arguments are invalid
+     */
+    public static Ip6Prefix valueOf(String address) {
+        final String[] parts = address.split("/");
+        if (parts.length != 2) {
+            String msg = "Malformed IPv6 prefix string: " + address + "." +
+                "Address must take form " +
+                "\"xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/y\"";
+            throw new IllegalArgumentException(msg);
         }
-        this.prefixLen = Short.decode(splits[1]);
-        this.address = Ip6Address.makeMaskedAddress(new Ip6Address(splits[0]),
-                this.prefixLen);
-    }
+        Ip6Address ipAddress = Ip6Address.valueOf(parts[0]);
+        int prefixLength = Integer.parseInt(parts[1]);
 
-    /**
-     * Gets the address value of the IPv6 prefix.
-     *
-     * @return the address value of the IPv6 prefix
-     */
-    public Ip6Address getAddress() {
-        return address;
-    }
-
-    /**
-     * Gets the prefix length value of the IPv6 prefix.
-     *
-     * @return the prefix length value of the IPv6 prefix
-     */
-    public short getPrefixLen() {
-        return prefixLen;
-    }
-
-    /**
-     * Converts the IPv6 prefix value to an "address/prefixLen" string.
-     *
-     * @return the IPv6 prefix value as an "address/prefixLen" string
-     */
-    @Override
-    public String toString() {
-        return this.address.toString() + "/" + this.prefixLen;
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (other == this) {
-            return true;
-        }
-
-        if (!(other instanceof Ip6Prefix)) {
-            return false;
-        }
-
-        Ip6Prefix otherIp6Prefix = (Ip6Prefix) other;
-
-        return Objects.equals(this.address, otherIp6Prefix.address)
-                && this.prefixLen == otherIp6Prefix.prefixLen;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(address, prefixLen);
+        return new Ip6Prefix(ipAddress, prefixLength);
     }
 }
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 f685e79..45c0207 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpAddress.java
@@ -30,8 +30,9 @@
 
 /**
  * A class representing an IP address.
+ * This class is immutable.
  */
-public final class IpAddress implements Comparable<IpAddress> {
+public class IpAddress implements Comparable<IpAddress> {
     // IP Versions
     public enum Version { INET, INET6 };
 
@@ -52,7 +53,7 @@
      * (i.e., the most significant byte first)
      * @throws IllegalArgumentException if the arguments are invalid
      */
-    private IpAddress(Version version, byte[] value) {
+    protected IpAddress(Version version, byte[] value) {
         checkArguments(version, value, 0);
         this.version = version;
         switch (version) {
@@ -88,7 +89,7 @@
     }
 
     /**
-     * Returns the integral value of this IP address.
+     * Returns the integer value of this IP address.
      * TODO: This method should be moved to Ip4Address.
      *
      * @return the IP address's value as an integer
@@ -219,31 +220,7 @@
      * @throws IllegalArgumentException if the arguments are invalid
      */
     public static IpAddress makeMaskPrefix(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));
-        }
+        byte[] mask = makeMaskPrefixArray(version, prefixLength);
         return new IpAddress(version, mask);
     }
 
@@ -251,24 +228,26 @@
      * Creates an IP address by masking it with a network mask of given
      * mask length.
      *
-     * @param addr the address to mask
+     * @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 addr,
+    public static IpAddress makeMaskedAddress(final IpAddress address,
                                               int prefixLength) {
-        IpAddress mask = IpAddress.makeMaskPrefix(addr.version(),
-                                                  prefixLength);
-        byte[] net = new byte[mask.octets.length];
-
-        // Mask each byte
-        for (int i = 0; i < net.length; i++) {
-            net[i] = (byte) (addr.octets[i] & mask.octets[i]);
+        // TODO: The code below should go away and replaced with generics
+        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);
         }
-        return IpAddress.valueOf(addr.version(), net);
     }
 
     @Override
@@ -352,8 +331,7 @@
      * array with the address
      * @throws IllegalArgumentException if any of the arguments is invalid
      */
-    private static void checkArguments(Version version, byte[] value,
-                                       int offset) {
+    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)) {
@@ -371,4 +349,67 @@
             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;
+    }
 }
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 4fef5a4..83592aa 100644
--- a/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
+++ b/utils/misc/src/main/java/org/onlab/packet/IpPrefix.java
@@ -20,12 +20,13 @@
 /**
  * A class representing an IP prefix. A prefix consists of an IP address and
  * a subnet mask.
+ * This class is immutable.
  * <p>
  * NOTE: The stored IP address in the result IP prefix is masked to
  * contain zeroes in all bits after the prefix length.
  * </p>
  */
-public final class IpPrefix {
+public class IpPrefix {
     // 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;
@@ -40,7 +41,7 @@
      * @param prefixLength the prefix length
      * @throws IllegalArgumentException if the prefix length value is invalid
      */
-    private IpPrefix(IpAddress address, int prefixLength) {
+    protected IpPrefix(IpAddress address, int prefixLength) {
         checkPrefixLength(address.version(), prefixLength);
         this.address = IpAddress.makeMaskedAddress(address, prefixLength);
         this.prefixLength = (short) prefixLength;
@@ -100,7 +101,7 @@
     }
 
     /**
-     * Converts an IP address and a prefix length into IP prefix.
+     * Converts an IP address and a prefix length into an IP prefix.
      *
      * @param address the IP address
      * @param prefixLength the prefix length
@@ -112,10 +113,11 @@
     }
 
     /**
-     * Converts a CIDR (slash) notation string (e.g., "10.1.0.0/16") into an
-     * IP prefix.
+     * Converts a CIDR (slash) notation string (e.g., "10.1.0.0/16" or
+     * "1111:2222::/64") into an IP prefix.
      *
-     * @param address an IP prefix in string form, e.g. "10.1.0.0/16"
+     * @param address an IP prefix in string form (e.g. "10.1.0.0/16" or
+     * "1111:2222::/64")
      * @return an IP prefix
      * @throws IllegalArgumentException if the arguments are invalid
      */
@@ -123,7 +125,8 @@
         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\"";
+                "Address must take form \"x.x.x.x/y\" or " +
+                "\"xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/y\"";
             throw new IllegalArgumentException(msg);
         }
         IpAddress ipAddress = IpAddress.valueOf(parts[0]);